diff --git a/skee/BodyMorphInterface.cpp b/skee/BodyMorphInterface.cpp new file mode 100644 index 0000000..c8c4f21 --- /dev/null +++ b/skee/BodyMorphInterface.cpp @@ -0,0 +1,1674 @@ +#include "BodyMorphInterface.h" +#include "OverrideInterface.h" +#include "OverlayInterface.h" +#include "ShaderUtilities.h" +#include "StringTable.h" + +#include "FileUtils.h" +#include "NifUtils.h" + +#include +#include +#include +#include +#include +#include + +#include "skse64/PluginAPI.h" +#include "skse64/GameReferences.h" +#include "skse64/GameStreams.h" +#include "skse64/GameRTTI.h" +#include "skse64/GameObjects.h" +#include "skse64/GameForms.h" +#include "skse64/GameData.h" +#include "skse64/GameExtraData.h" + +#include "skse64/NiNodes.h" +#include "skse64/NiGeometry.h" +#include "skse64/NiExtraData.h" +#include "skse64/NiRTTI.h" + +extern BodyMorphInterface g_bodyMorphInterface; +extern OverrideInterface g_overrideInterface; +extern OverlayInterface g_overlayInterface; +extern StringTable g_stringTable; +extern SKSETaskInterface * g_task; +extern bool g_parallelMorphing; +extern UInt16 g_bodyMorphMode; +extern bool g_enableBodyGen; + +UInt32 BodyMorphInterface::GetVersion() +{ + return kCurrentPluginVersion; +} + +void BodyMorphInterface::LoadMods() +{ + DataHandler * dataHandler = DataHandler::GetSingleton(); + if (dataHandler) + { + std::string fixedPath = "Meshes\\" + std::string(MORPH_MOD_DIRECTORY); + UInt8 modCount = dataHandler->modList.loadedMods.count; + for (UInt32 i = 0; i < modCount; i++) + { + ModInfo * modInfo = dataHandler->modList.loadedMods[i]; + std::string templatesPath = fixedPath + std::string(modInfo->name) + "\\templates.ini"; + ReadBodyMorphTemplates(templatesPath.c_str()); + } + + for (UInt32 i = 0; i < modCount; i++) + { + ModInfo * modInfo = dataHandler->modList.loadedMods[i]; + std::string morphsPath = fixedPath + std::string(modInfo->name) + "\\morphs.ini"; + ReadBodyMorphs(morphsPath.c_str()); + } + } +} + +void BodyMorphInterface::Revert() +{ + SimpleLocker locker(&actorMorphs.m_lock); + actorMorphs.m_data.clear(); +} + +void BodyMorphInterface::SetMorph(TESObjectREFR * actor, BSFixedString morphName, BSFixedString morphKey, float relative) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + + SimpleLocker locker(&actorMorphs.m_lock); + actorMorphs.m_data[handle][morphName][morphKey] = relative; +} + +float BodyMorphInterface::GetMorph(TESObjectREFR * actor, BSFixedString morphName, BSFixedString morphKey) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + + SimpleLocker locker(&actorMorphs.m_lock); + auto & it = actorMorphs.m_data.find(handle); + if(it != actorMorphs.m_data.end()) + { + auto & mit = it->second.find(morphName); + if (mit != it->second.end()) + { + auto & kit = mit->second.find(morphKey); + if (kit != mit->second.end()) + { + return kit->second; + } + } + } + + return 0.0; +} + +void BodyMorphInterface::ClearMorph(TESObjectREFR * actor, BSFixedString morphName, BSFixedString morphKey) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + + SimpleLocker locker(&actorMorphs.m_lock); + auto & it = actorMorphs.m_data.find(handle); + if (it != actorMorphs.m_data.end()) + { + auto & mit = it->second.find(morphName); + if (mit != it->second.end()) + { + auto & kit = mit->second.find(morphKey); + if (kit != mit->second.end()) + { + mit->second.erase(kit); + } + } + } +} + +bool BodyMorphInterface::HasBodyMorph(TESObjectREFR * actor, BSFixedString morphName, BSFixedString morphKey) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + + SimpleLocker locker(&actorMorphs.m_lock); + auto & it = actorMorphs.m_data.find(handle); + if (it != actorMorphs.m_data.end()) + { + auto & kit = it->second.find(morphName); + if (kit != it->second.end()) + { + auto & mit = kit->second.find(morphKey); + if(mit != kit->second.end()) + return true; + } + } + + return false; +} + +float BodyMorphInterface::GetBodyMorphs(TESObjectREFR * actor, BSFixedString morphName) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + + SimpleLocker locker(&actorMorphs.m_lock); + auto & it = actorMorphs.m_data.find(handle); + if (it != actorMorphs.m_data.end()) + { + auto & mit = it->second.find(morphName); + if (mit != it->second.end()) + { + float morphSum = 0; + for (auto & morph : mit->second) + { + if (g_bodyMorphMode == 2 && morph.second > morphSum) + { + morphSum = morph.second; + } + else + { + morphSum += morph.second; + } + } + + if (g_bodyMorphMode == 1) + { + morphSum /= mit->second.size(); + } + + return morphSum; + } + } + + return 0.0; +} + +bool BodyMorphInterface::HasBodyMorphKey(TESObjectREFR * actor, BSFixedString morphKey) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + + SimpleLocker locker(&actorMorphs.m_lock); + auto & it = actorMorphs.m_data.find(handle); + if (it != actorMorphs.m_data.end()) + { + for (auto & mit : it->second) + { + auto & kit = mit.second.find(morphKey); + if (kit != mit.second.end()) + { + return true; + } + } + } + + return false; +} + +void BodyMorphInterface::ClearBodyMorphKeys(TESObjectREFR * actor, BSFixedString morphKey) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + + SimpleLocker locker(&actorMorphs.m_lock); + auto & it = actorMorphs.m_data.find(handle); + if (it != actorMorphs.m_data.end()) + { + for (auto & mit : it->second) + { + auto & kit = mit.second.find(morphKey); + if (kit != mit.second.end()) + { + mit.second.erase(kit); + } + } + } +} + +bool BodyMorphInterface::HasBodyMorphName(TESObjectREFR * actor, BSFixedString morphName) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + + SimpleLocker locker(&actorMorphs.m_lock); + auto & it = actorMorphs.m_data.find(handle); + if (it != actorMorphs.m_data.end()) + { + auto & kit = it->second.find(morphName); + if (kit != it->second.end()) + { + return true; + } + } + + return false; +} + +void BodyMorphInterface::ClearBodyMorphNames(TESObjectREFR * actor, BSFixedString morphName) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + + SimpleLocker locker(&actorMorphs.m_lock); + auto & it = actorMorphs.m_data.find(handle); + if (it != actorMorphs.m_data.end()) + { + auto & mit = it->second.find(morphName); + if (mit != it->second.end()) + { + mit->second.clear(); + } + } +} + +void BodyMorphInterface::ClearMorphs(TESObjectREFR * actor) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + + SimpleLocker locker(&actorMorphs.m_lock); + auto & it = actorMorphs.m_data.find(handle); + if(it != actorMorphs.m_data.end()) + { + actorMorphs.m_data.erase(it); + } +} + +bool BodyMorphInterface::HasMorphs(TESObjectREFR * actor) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + + SimpleLocker locker(&actorMorphs.m_lock); + auto & it = actorMorphs.m_data.find(handle); + if (it != actorMorphs.m_data.end()) + { + return true; + } + + return false; +} + +void TriShapeFullVertexData::ApplyMorph(UInt16 vertCount, NiPoint3 * vertices, float factor) +{ + if (!vertices) + return; + + if (g_parallelMorphing) + { + concurrency::parallel_for_each(m_vertexDeltas.begin(), m_vertexDeltas.end(), [&](const TriShapeVertexDelta & vert) + { + const UInt16 vertexIndex = vert.index; + const NiPoint3 * vertexDiff = &vert.diff; + if (vertexIndex < vertCount) + { + vertices[vertexIndex].x += vertexDiff->x * factor; + vertices[vertexIndex].y += vertexDiff->y * factor; + vertices[vertexIndex].z += vertexDiff->z * factor; + } + }, concurrency::static_partitioner()); + } + else + { + UInt32 size = m_vertexDeltas.size(); + for (UInt32 i = 0; i < size; i++) + { + TriShapeVertexDelta * vert = &m_vertexDeltas.at(i); + UInt16 vertexIndex = vert->index; + NiPoint3 * vertexDiff = &vert->diff; + if (vertexIndex < vertCount) + { + vertices[vertexIndex].x += vertexDiff->x * factor; + vertices[vertexIndex].y += vertexDiff->y * factor; + vertices[vertexIndex].z += vertexDiff->z * factor; + } + else { + _DMESSAGE("%s - Vertex %d out of bounds X:%f Y:%f Z:%f", __FUNCTION__, vertexIndex, vertexDiff->x, vertexDiff->y, vertexDiff->z); + } + } + } +} + +void TriShapePackedVertexData::ApplyMorph(UInt16 vertCount, NiPoint3 * vertices, float factor) +{ + if (!vertices) + return; + + if (g_parallelMorphing) + { + concurrency::parallel_for_each(m_vertexDeltas.begin(), m_vertexDeltas.end(), [&](const TriShapePackedVertexDelta & vert) + { + UInt16 vertexIndex = vert.index; + if (vertexIndex < vertCount) + { + vertices[vertexIndex].x += (float)vert.x * m_multiplier * factor; + vertices[vertexIndex].y += (float)vert.y * m_multiplier * factor; + vertices[vertexIndex].z += (float)vert.z * m_multiplier * factor; + } + }, concurrency::static_partitioner()); + } + else + { + UInt32 size = m_vertexDeltas.size(); + for (UInt32 i = 0; i < size; i++) + { + TriShapePackedVertexDelta * vert = &m_vertexDeltas.at(i); + + UInt16 vertexIndex = vert->index; + if (vertexIndex < vertCount) + { + vertices[vertexIndex].x += (float)vert->x * m_multiplier * factor; + vertices[vertexIndex].y += (float)vert->y * m_multiplier * factor; + vertices[vertexIndex].z += (float)vert->z * m_multiplier * factor; + } + else { + _DMESSAGE("%s - Vertex %d out of bounds X:%f Y:%f Z:%f", __FUNCTION__, vertexIndex, vert->x, vert->y, vert->z); + } + } + } +} + +void BodyMorphMap::ApplyMorphs(TESObjectREFR * refr, UInt16 vertexCount, NiPoint3* targetGeometry, NiPoint3 * storageGeometry) const +{ + for (auto & morph : *this) + { + float morphFactor = g_bodyMorphInterface.GetBodyMorphs(refr, morph.first); + morph.second->ApplyMorph(vertexCount, storageGeometry, morphFactor); + morph.second->ApplyMorph(vertexCount, targetGeometry, morphFactor); + } +} + +bool BodyMorphMap::HasMorphs(TESObjectREFR * refr) const +{ + for (auto & morph : *this) + { + float morphFactor = g_bodyMorphInterface.GetBodyMorphs(refr, morph.first); + if (morphFactor != 0.0f) + return true; + } + + return false; +} +/* +class ShadowSceneNode +{ +public: + static ShadowSceneNode * GetSingleton() + { + return *((ShadowSceneNode **)0x01BA7680); + } + + MEMBER_FN_PREFIX(ShadowSceneNode); + DEFINE_MEMBER_FN(UnkFunc0, void, 0xC77EB0, NiAVObject * object, UInt8 unk1, UInt8 unk2); + DEFINE_MEMBER_FN(UnkFunc1, void, 0xC781D0, NiAVObject * object, UInt8 unk1); +}; +*/ + +// 567250 +// 5F48C0 + +//typedef UInt32(*_DetachArmorModel)(NiAVObject * object); +//extern const _DetachArmorModel DetachArmorModel = (_DetachArmorModel)0x005F4A70; + + +//typedef UInt32(*_DetachObjectModel)(UInt32 handle, NiAVObject * object); +//extern const _DetachObjectModel DetachObjectModel = (_DetachObjectModel)0x0046BF90; + + + +// 46BF90 +#ifndef _NO_REATTACH +typedef UInt32(*_UpdateReferenceNode)(UInt32 handle, NiAVObject * object); +extern const _UpdateReferenceNode UpdateReferenceNode = (_UpdateReferenceNode)0x0046BF90; +#endif + +void TriShapeMap::ApplyMorph(TESObjectREFR * refr, NiAVObject * rootNode, bool isAttaching, const std::pair & bodyMorph) +{ + BSFixedString nodeName = bodyMorph.first.data; + BSGeometry * triShape = rootNode->GetAsBSGeometry(); + NiAVObject * bodyNode = triShape ? triShape : rootNode->GetObjectByName(&nodeName.data); + if (bodyNode) + { +#ifdef FIXME_MORPHS + BSGeometry * bodyGeometry = bodyNode->GetAsBSGeometry(); + if (bodyGeometry) + { + NiGeometryData * geometryData = niptr_cast(bodyGeometry->m_spModelData); + NiSkinInstance * skinInstance = niptr_cast(bodyGeometry->m_spSkinInstance); + if (geometryData && skinInstance) { + // Undo morphs on the old shape + BSFaceGenBaseMorphExtraData * bodyData = (BSFaceGenBaseMorphExtraData *)bodyGeometry->GetExtraData("FOD"); + if (bodyData) { + NiAutoRefCounter arc(bodyData); + // Undo old morphs for this trishape + if (g_parallelMorphing) + { + concurrency::parallel_for((UInt16)0, geometryData->m_usVertices, [&](UInt16 i) + { + if (!isAttaching) + geometryData->m_pkVertex[i] -= bodyData->vertexData[i]; + bodyData->vertexData[i] = NiPoint3(0, 0, 0); + }, concurrency::static_partitioner()); + } + else + { + for (UInt16 i = 0; i < geometryData->m_usVertices; i++) { + if (!isAttaching) + geometryData->m_pkVertex[i] -= bodyData->vertexData[i]; + bodyData->vertexData[i] = NiPoint3(0, 0, 0); + } + } + + geometryData->m_usDirtyFlags = 0x0001; + } + + // Apply new morphs to new shape + if (bodyMorph.second.HasMorphs(refr)) + { + NiGeometryData * targetShapeData = nullptr; + CALL_MEMBER_FN(geometryData, DeepCopy)((NiObject **)&targetShapeData); + + NiSkinInstance * newSkinInstance = skinInstance->Clone(); + if (newSkinInstance) + { + // If we've created a new skin instance, create a new partition too + if (newSkinInstance != skinInstance) + { + NiSkinPartition * skinPartition = niptr_cast(skinInstance->m_spSkinPartition); + if (skinPartition) { + NiSkinPartition * newSkinPartition = nullptr; + CALL_MEMBER_FN(skinPartition, DeepCopy)((NiObject **)&newSkinPartition); + + newSkinInstance->m_spSkinPartition = newSkinPartition; + newSkinPartition->DecRef(); + } + } + if (targetShapeData) { + // No old morphs, add one + if (!bodyData) { + bodyData = BSFaceGenBaseMorphExtraData::Create(targetShapeData, false); + bodyGeometry->AddExtraData(bodyData); + } + + if (bodyData) { + NiAutoRefCounter arc(bodyData); + bodyMorph.second.ApplyMorphs(refr, targetShapeData->m_usVertices, targetShapeData->m_pkVertex, bodyData->vertexData); + } + + bodyGeometry->SetModelData(targetShapeData); + bodyGeometry->SetSkinInstance(newSkinInstance); + targetShapeData->DecRef(); + + targetShapeData->m_usDirtyFlags = 0x0001; + } + } + } + } + } +#endif + } +} + +void TriShapeMap::ApplyMorphs(TESObjectREFR * refr, NiAVObject * rootNode, bool isAttaching) +{ + for (const auto & it : *this) + { + ApplyMorph(refr, rootNode, isAttaching, it); + } +} + +void MorphCache::ApplyMorphs(TESObjectREFR * refr, NiAVObject * rootNode, bool isAttaching) +{ + TriShapeMap triShapeMap; + + // Find the BODYTRI and cache it + VisitObjects(rootNode, [&](NiAVObject* object) { + NiStringExtraData * stringData = ni_cast(object->GetExtraData("BODYTRI"), NiStringExtraData); + if (stringData) { + BSFixedString filePath = CreateTRIPath(stringData->m_pString); + CacheFile(filePath.data); + auto & it = m_data.find(filePath); + if (it != m_data.end()) { + triShapeMap = it->second; + return true; + } + } + + return false; + }); + + if (!triShapeMap.empty()) + triShapeMap.ApplyMorphs(refr, rootNode, isAttaching); + + Shrink(); +} + +class EquippedItemCollector +{ +public: + typedef std::vector FoundItems; + + EquippedItemCollector() {} + + bool Accept(InventoryEntryData* pEntryData) + { + if (!pEntryData) + return true; + + if (pEntryData->countDelta < 1) + return true; + + ExtendDataList* pExtendList = pEntryData->extendDataList; + if (!pExtendList) + return true; + + SInt32 n = 0; + BaseExtraList* pExtraDataList = pExtendList->GetNthItem(n); + while (pExtraDataList) + { + if (pExtraDataList->HasType(kExtraData_Worn) || pExtraDataList->HasType(kExtraData_WornLeft)) + m_found.push_back(pEntryData->type); + + n++; + pExtraDataList = pExtendList->GetNthItem(n); + } + + return true; + } + + FoundItems& Found() + { + return m_found; + } +private: + FoundItems m_found; +}; + +TESObjectARMO * GetActorSkin(Actor * actor) +{ + TESNPC * npc = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (npc) { + if (npc->skinForm.skin) + return npc->skinForm.skin; + } + TESRace * actorRace = actor->race; + if (actorRace) + return actorRace->skin.skin; + + if (npc) { + actorRace = npc->race.race; + if (actorRace) + return actorRace->skin.skin; + } + + return NULL; +} + +void MorphCache::UpdateMorphs(TESObjectREFR * refr) +{ + if(!refr) + return; + + Actor * actor = DYNAMIC_CAST(refr, TESObjectREFR, Actor); + if (!actor) + return; + + EquippedItemCollector::FoundItems foundData; + ExtraContainerChanges * extraContainer = static_cast(refr->extraData.GetByType(kExtraData_ContainerChanges)); + if (extraContainer) { + if (extraContainer->data && extraContainer->data->objList) { + EquippedItemCollector itemFinder; + extraContainer->data->objList->Visit(itemFinder); + foundData = itemFinder.Found(); + } + } + + TESObjectARMO * skin = GetActorSkin(actor); + if (skin) + foundData.push_back(skin); + + for (auto foundItem : foundData) { + TESObjectARMO * armor = DYNAMIC_CAST(foundItem, TESForm, TESObjectARMO); + if (armor) { + for (UInt32 i = 0; i < armor->armorAddons.count; i++) { + TESObjectARMA * arma = NULL; + if (armor->armorAddons.GetNthItem(i, arma)) { + if (arma->isValidRace(actor->race)) { // Only search AAs that fit this race + VisitArmorAddon(actor, armor, arma, [&](bool isFirstPerson, NiNode * skeletonRoot, NiAVObject * armorNode) { + ApplyMorphs(refr, armorNode); +#ifdef _NO_REATTACH + g_overlayInterface.RebuildOverlays(armor->bipedObject.GetSlotMask(), arma->biped.GetSlotMask(), refr, skeletonRoot, armorNode); +#endif + }); + } + } + } + } + } +#ifndef _NO_REATTACH + CALL_MEMBER_FN(actor->processManager, SetEquipFlag)(ActorProcessManager::kFlags_Unk01 | ActorProcessManager::kFlags_Unk02 | ActorProcessManager::kFlags_Mobile); + CALL_MEMBER_FN(actor->processManager, UpdateEquipment)(actor); +#endif +} + +BSFixedString MorphCache::CreateTRIPath(const char * relativePath) +{ + if(relativePath == "") + return BSFixedString(""); + + std::string targetPath = "meshes\\"; + targetPath += std::string(relativePath); + std::transform(targetPath.begin(), targetPath.end(), targetPath.begin(), ::tolower); + return BSFixedString(targetPath.c_str()); +} + +void MorphCache::Shrink() +{ + while (totalMemory > memoryLimit && m_data.size() > 0) + { + auto & it = std::min_element(m_data.begin(), m_data.end(), [](std::pair a, std::pair b) + { + return (a.second.accessed < b.second.accessed); + }); + + UInt32 size = it->second.memoryUsage; + m_data.erase(it); + totalMemory -= size; + } + + if (m_data.size() == 0) // Just in case we erased but messed up + totalMemory = sizeof(MorphCache); +} + +bool MorphCache::CacheFile(const char * relativePath) +{ + BSFixedString filePath(relativePath); + if(relativePath == "") + return false; + + auto & it = m_data.find(filePath); + if (it != m_data.end()) { + it->second.accessed = std::time(nullptr); + return false; + } + +#ifdef _DEBUG + _MESSAGE("%s - Parsing: %s", __FUNCTION__, filePath.data); +#endif + + BSResourceNiBinaryStream binaryStream(filePath.data); + if(binaryStream.IsValid()) + { + TriShapeMap trishapeMap; + + UInt32 fileFormat = 0; + trishapeMap.memoryUsage += binaryStream.Read((char *)&fileFormat, sizeof(UInt32)); + + bool packed = false; + if (fileFormat != 'TRI\0' && fileFormat != 'TRIP') + return false; + + if (fileFormat == 'TRIP') + packed = true; + + UInt32 trishapeCount = 0; + if (!packed) + trishapeMap.memoryUsage += binaryStream.Read((char *)&trishapeCount, sizeof(UInt32)); + else + trishapeMap.memoryUsage += binaryStream.Read((char *)&trishapeCount, sizeof(UInt16)); + + char trishapeNameRaw[MAX_PATH]; + for (UInt32 i = 0; i < trishapeCount; i++) + { + memset(trishapeNameRaw, 0, MAX_PATH); + + UInt8 size = 0; + trishapeMap.memoryUsage += binaryStream.Read((char *)&size, sizeof(UInt8)); + trishapeMap.memoryUsage += binaryStream.Read(trishapeNameRaw, size); + BSFixedString trishapeName(trishapeNameRaw); + +#ifdef _DEBUG + _MESSAGE("%s - Reading TriShape %s", __FUNCTION__, trishapeName.data); +#endif + + if (!packed) { + UInt32 trishapeBlockSize = 0; + trishapeMap.memoryUsage += binaryStream.Read((char *)&trishapeBlockSize, sizeof(UInt32)); + } + + char morphNameRaw[MAX_PATH]; + + BodyMorphMap morphMap; + + UInt32 morphCount = 0; + if (!packed) + trishapeMap.memoryUsage += binaryStream.Read((char *)&morphCount, sizeof(UInt32)); + else + trishapeMap.memoryUsage += binaryStream.Read((char *)&morphCount, sizeof(UInt16)); + + for (UInt32 j = 0; j < morphCount; j++) + { + memset(morphNameRaw, 0, MAX_PATH); + + UInt8 tsize = 0; + trishapeMap.memoryUsage += binaryStream.Read((char *)&tsize, sizeof(UInt8)); + trishapeMap.memoryUsage += binaryStream.Read(morphNameRaw, tsize); + BSFixedString morphName(morphNameRaw); + +#ifdef _DEBUG + _MESSAGE("%s - Reading Morph %s at (%08X)", __FUNCTION__, morphName.data, binaryStream.GetOffset()); +#endif + if (tsize == 0) { + _WARNING("%s - %s - Read empty name morph at (%08X)", __FUNCTION__, filePath.data, binaryStream.GetOffset()); + } + + if (!packed) { + UInt32 morphBlockSize = 0; + trishapeMap.memoryUsage += binaryStream.Read((char *)&morphBlockSize, sizeof(UInt32)); + } + + UInt32 vertexNum = 0; + float multiplier = 0.0f; + if(!packed) { + trishapeMap.memoryUsage += binaryStream.Read((char *)&vertexNum, sizeof(UInt32)); + } + else { + trishapeMap.memoryUsage += binaryStream.Read((char *)&multiplier, sizeof(float)); + trishapeMap.memoryUsage += binaryStream.Read((char *)&vertexNum, sizeof(UInt16)); + } + + if (vertexNum == 0) { + _WARNING("%s - %s - Read morph %s on %s with no vertices at (%08X)", __FUNCTION__, filePath.data, morphName.data, trishapeName.data, binaryStream.GetOffset()); + } + if (multiplier == 0.0f) { + _WARNING("%s - %s - Read morph %s on %s with zero multiplier at (%08X)", __FUNCTION__, filePath.data, morphName.data, trishapeName.data, binaryStream.GetOffset()); + } + +#ifdef _DEBUG + _MESSAGE("%s - Total Vertices read: %d at (%08X)", __FUNCTION__, vertexNum, binaryStream.GetOffset()); +#endif + if (vertexNum > (std::numeric_limits::max)()) + { + _ERROR("%s - %s - Too many vertices for %s on %s read: %d at (%08X)", __FUNCTION__, filePath.data, morphName.data, vertexNum, trishapeName.data, binaryStream.GetOffset()); + return false; + } + + TriShapeVertexDataPtr vertexData; + TriShapeFullVertexDataPtr fullVertexData; + TriShapePackedVertexDataPtr packedVertexData; + if (!packed) + { + fullVertexData = std::make_shared(); + for (UInt32 k = 0; k < vertexNum; k++) + { + TriShapeVertexDelta vertexDelta; + trishapeMap.memoryUsage += binaryStream.Read((char *)&vertexDelta.index, sizeof(UInt32)); + trishapeMap.memoryUsage += binaryStream.Read((char *)&vertexDelta.diff, sizeof(NiPoint3)); + fullVertexData->m_vertexDeltas.push_back(vertexDelta); + } + + vertexData = fullVertexData; + } + else + { + packedVertexData = std::make_shared(); + packedVertexData->m_multiplier = multiplier; + + for (UInt32 k = 0; k < vertexNum; k++) + { + TriShapePackedVertexDelta vertexDelta; + trishapeMap.memoryUsage += binaryStream.Read((char *)&vertexDelta.index, sizeof(UInt16)); + trishapeMap.memoryUsage += binaryStream.Read((char *)&vertexDelta.x, sizeof(SInt16)); + trishapeMap.memoryUsage += binaryStream.Read((char *)&vertexDelta.y, sizeof(SInt16)); + trishapeMap.memoryUsage += binaryStream.Read((char *)&vertexDelta.z, sizeof(SInt16)); + + packedVertexData->m_vertexDeltas.push_back(vertexDelta); + } + + vertexData = packedVertexData; + } + + morphMap.emplace(morphName, vertexData); + } + + trishapeMap.emplace(trishapeName, morphMap); + + } + + trishapeMap.accessed = std::time(nullptr); + + Lock(); + m_data.emplace(relativePath, trishapeMap); + totalMemory += trishapeMap.memoryUsage; + Release(); + + _DMESSAGE("%s - Loaded %s (%d bytes)", __FUNCTION__, relativePath, trishapeMap.memoryUsage); + return true; + } + else + { + _ERROR("%s - Failed to load %s", __FUNCTION__, relativePath); + } + + + return false; +} + +void BodyMorphInterface::SetCacheLimit(UInt32 limit) +{ + morphCache.memoryLimit = limit; +} + +void BodyMorphInterface::ApplyVertexDiff(TESObjectREFR * refr, NiAVObject * rootNode, bool erase) +{ + if(!refr || !rootNode) { +#ifdef _DEBUG + _MESSAGE("%s - Error no reference or node found.", __FUNCTION__); +#endif + return; + } + +#ifdef _DEBUG + _MESSAGE("%s - Applying Vertex Diffs to %08X on %s", __FUNCTION__, refr->formID, rootNode->m_name); +#endif + morphCache.ApplyMorphs(refr, rootNode, erase); +} + +void BodyMorphInterface::ApplyBodyMorphs(TESObjectREFR * refr) +{ +#ifdef _DEBUG + _MESSAGE("%s - Updating morphs for %08X.", __FUNCTION__, refr->formID); +#endif + morphCache.UpdateMorphs(refr); +} + +NIOVTaskUpdateModelWeight::NIOVTaskUpdateModelWeight(Actor * actor) +{ + m_formId = actor->formID; +} + +void NIOVTaskUpdateModelWeight::Dispose(void) +{ + delete this; +} + +void NIOVTaskUpdateModelWeight::Run() +{ + TESForm * form = LookupFormByID(m_formId); + Actor * actor = DYNAMIC_CAST(form, TESForm, Actor); + if(actor) { + g_bodyMorphInterface.ApplyBodyMorphs(actor); + } +} + +void BodyMorphInterface::VisitMorphs(TESObjectREFR * actor, std::function * map)> functor) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + auto & it = actorMorphs.m_data.find(handle); + if (it != actorMorphs.m_data.end()) + { + for (auto & morph : it->second) + { + functor(morph.first, &morph.second); + } + } +} + +void BodyMorphInterface::VisitKeys(TESObjectREFR * actor, BSFixedString name, std::function functor) +{ + UInt64 handle = g_overrideInterface.GetHandle(actor, TESObjectREFR::kTypeID); + auto & it = actorMorphs.m_data.find(handle); + if (it != actorMorphs.m_data.end()) + { + auto & mit = it->second.find(name); + if (mit != it->second.end()) + { + for (auto & morph : mit->second) + { + functor(morph.first, morph.second); + } + } + } +} + + +void BodyMorphInterface::UpdateModelWeight(TESObjectREFR * refr, bool immediate) +{ + Actor * actor = DYNAMIC_CAST(refr, TESObjectREFR, Actor); + if(actor) { + NIOVTaskUpdateModelWeight * updateTask = new NIOVTaskUpdateModelWeight(actor); + if (immediate) { + updateTask->Run(); + updateTask->Dispose(); + } + else + { + g_task->AddTask(updateTask); + } + } +} + +bool BodyMorphInterface::ReadBodyMorphTemplates(BSFixedString filePath) +{ + BSResourceNiBinaryStream file(filePath.data); + if (!file.IsValid()) { + return false; + } + + UInt32 lineCount = 0; + std::string str = ""; + + while (BSReadLine(&file, &str)) + { + lineCount++; + str = std::trim(str); + if (str.length() == 0) + continue; + if (str.at(0) == '#') + continue; + + std::vector side = std::explode(str, '='); + if (side.size() < 2) { + _ERROR("%s - Error - Line (%d) loading a morph from %s has no left-hand side.", __FUNCTION__, lineCount, filePath.data); + continue; + } + + std::string lSide = std::trim(side[0]); + std::string rSide = std::trim(side[1]); + + BSFixedString templateName = lSide.c_str(); + + BodyGenTemplatePtr bodyGenSets = std::make_shared(); + + std::string error = ""; + std::vector sets = std::explode(rSide, '/'); + for (UInt32 i = 0; i < sets.size(); i++) { + sets[i] = std::trim(sets[i]); + + BodyGenMorphs bodyMorphs; + + std::vector morphs = std::explode(sets[i], ','); + for (UInt32 j = 0; j < morphs.size(); j++) { + morphs[j] = std::trim(morphs[j]); + + std::vector selectors = std::explode(morphs[j], '|'); + + BodyGenMorphSelector selector; + + for (UInt32 k = 0; k < selectors.size(); k++) { + selectors[k] = std::trim(selectors[k]); + + std::vector pairs = std::explode(selectors[k], '@'); + if (pairs.size() < 2) { + error = "Must have value pair with @"; + break; + } + + std::string morphName = std::trim(pairs[0]); + if (morphName.length() == 0) { + error = "Empty morph name"; + break; + } + + std::string morphValues = std::trim(pairs[1]); + if (morphValues.length() == 0) { + error = "Empty values"; + break; + } + + float lowerValue = 0; + float upperValue = 0; + + std::vector range = std::explode(morphValues, ':'); + if (range.size() > 1) { + std::string lowerRange = std::trim(range[0]); + if (lowerRange.length() == 0) { + error = "Empty lower range"; + break; + } + + lowerValue = strtof(lowerRange.c_str(), NULL); + + std::string upperRange = std::trim(range[1]); + if (upperRange.length() == 0) { + error = "Empty upper range"; + break; + } + + upperValue = strtof(upperRange.c_str(), NULL); + } + else { + lowerValue = strtof(morphValues.c_str(), NULL); + upperValue = lowerValue; + } + + BodyGenMorphData morphData; + morphData.name = morphName.c_str(); + morphData.lower = lowerValue; + morphData.upper = upperValue; + selector.push_back(morphData); + } + + if (error.length() > 0) + break; + + bodyMorphs.push_back(selector); + } + + if (error.length() > 0) + break; + + bodyGenSets->push_back(bodyMorphs); + } + + if (error.length() > 0) { + _ERROR("%s - Error - Line (%d) could not parse morphs from %s. (%s)", __FUNCTION__, lineCount, filePath.data, error.c_str()); + continue; + } + + if (bodyGenSets->size() > 0) + bodyGenTemplates[templateName] = bodyGenSets; + } + + return true; +} + +bool BodyMorphInterface::ReadBodyMorphs(BSFixedString filePath) +{ + BSResourceNiBinaryStream file(filePath.data); + if (!file.IsValid()) { + return false; + } + + UInt32 lineCount = 0; + std::string str = ""; + UInt32 totalTargets = 0; + + while (BSReadLine(&file, &str)) + { + lineCount++; + str = std::trim(str); + if (str.length() == 0) + continue; + if (str.at(0) == '#') + continue; + + std::vector side = std::explode(str, '='); + if (side.size() < 2) { + _ERROR("%s - Error - %s (%d) loading a morph from %s has no left-hand side.", __FUNCTION__, filePath.data, lineCount); + continue; + } + + std::string lSide = std::trim(side[0]); + std::string rSide = std::trim(side[1]); + + std::vector form = std::explode(lSide, '|'); + if (form.size() < 2) { + _ERROR("%s - Error - %s (%d) morph left side from %s missing mod name or formID.", __FUNCTION__, filePath.data, lineCount); + continue; + } + + std::vector activeNPCs; + std::string modNameText = std::trim(form[0]); + if (_strnicmp(modNameText.c_str(), "All", 3) == 0) + { + std::string genderText = std::trim(form[1]); + UInt8 gender = 0; + if (_strnicmp(genderText.c_str(), "Male", 4) == 0) + gender = 0; + else if (_strnicmp(genderText.c_str(), "Female", 6) == 0) + gender = 1; + else { + _ERROR("%s - Error - %s (%d) invalid gender specified.", __FUNCTION__, filePath.data, lineCount); + continue; + } + + DataHandler * dataHandler = DataHandler::GetSingleton(); + + bool raceFilter = false; + TESRace * foundRace = nullptr; + if (form.size() >= 3) + { + std::string raceText = std::trim(form[2]); + for (UInt32 i = 0; i < dataHandler->races.count; i++) + { + TESRace * race = nullptr; + if (dataHandler->races.GetNthItem(i, race)) { + if (race && _strnicmp(raceText.c_str(), race->editorId.data, raceText.size()) == 0) { + foundRace = race; + break; + } + } + } + raceFilter = true; + + if (foundRace == nullptr) + { + _ERROR("%s - Error - %s (%d) invalid race %s specified.", __FUNCTION__, filePath.data, lineCount, raceText.c_str()); + continue; + } + } + + for (UInt32 i = 0; i < dataHandler->npcs.count; i++) + { + TESNPC * npc = nullptr; + if (dataHandler->npcs.GetNthItem(i, npc)) { + if (npc && npc->nextTemplate == nullptr && CALL_MEMBER_FN(npc, GetSex)() == gender && (raceFilter == false || npc->race.race == foundRace)) + activeNPCs.push_back(npc); + } + } + } + else + { + BSFixedString modText(modNameText.c_str()); + UInt8 modIndex = DataHandler::GetSingleton()->GetModIndex(modText.data); + if (modIndex == -1) { + _WARNING("%s - Warning - %s (%d) Mod %s not a loaded mod.", __FUNCTION__, filePath.data, lineCount, modText.data); + continue; + } + + std::string formIdText = std::trim(form[1]); + UInt32 formLower = strtoul(formIdText.c_str(), NULL, 16); + + if (formLower == 0) { + _ERROR("%s - Error - %s (%d) invalid formID.", __FUNCTION__, filePath.data, lineCount); + continue; + } + + UInt32 formId = modIndex << 24 | formLower & 0xFFFFFF; + TESForm * foundForm = LookupFormByID(formId); + if (!foundForm) { + _ERROR("%s - Error - %s (%d) invalid form %08X.", __FUNCTION__, filePath.data, lineCount, formId); + continue; + } + + // Dont apply randomization to the player + if (formId == 7) + continue; + + TESNPC * npc = DYNAMIC_CAST(foundForm, TESForm, TESNPC); + if (!npc) { + _ERROR("%s - Error - %s (%d) invalid form %08X not an ActorBase.", __FUNCTION__, filePath.data, lineCount, formId); + continue; + } + + activeNPCs.push_back(npc); + } + + BodyGenDataTemplatesPtr dataTemplates = std::make_shared(); + std::string error = ""; + std::vector sets = std::explode(rSide, ','); + for (UInt32 i = 0; i < sets.size(); i++) { + sets[i] = std::trim(sets[i]); + std::vector selectors = std::explode(sets[i], '|'); + BodyTemplateList templateList; + for (UInt32 k = 0; k < selectors.size(); k++) { + selectors[k] = std::trim(selectors[k]); + BSFixedString templateName(selectors[k].c_str()); + auto & temp = bodyGenTemplates.find(templateName); + if (temp != bodyGenTemplates.end()) + templateList.push_back(temp->second); + else + _WARNING("%s - Warning - %s (%d) template %s not found.", __FUNCTION__, filePath.data, lineCount, templateName.data); + } + + dataTemplates->push_back(templateList); + } + + for (auto & npc : activeNPCs) + { + bodyGenData[npc] = dataTemplates; +#ifdef _DEBUG + _DMESSAGE("%s - Read target %s", __FUNCTION__, npc->fullName.name.data); +#endif + } + + totalTargets += activeNPCs.size(); + } + + _DMESSAGE("%s - Read %d target(s) from %s", __FUNCTION__, totalTargets, filePath.data); + return true; +} + +UInt32 BodyGenMorphSelector::Evaluate(std::function eval) +{ + if (size() > 0) { + std::random_device rd; + std::default_random_engine gen(rd()); + std::uniform_int_distribution<> rndMorph(0, size() - 1); + + auto & bodyMorph = at(rndMorph(gen)); + std::uniform_real_distribution<> rndValue(bodyMorph.lower, bodyMorph.upper); + float val = rndValue(gen); + if (val != 0) { + eval(bodyMorph.name, val); + return 1; + } + } + + return 0; +} + +UInt32 BodyGenMorphs::Evaluate(std::function eval) +{ + UInt32 total = 0; + for (auto value : *this) { + if (value.size() < 1) + continue; + + total += value.Evaluate(eval); + } + + return total; +} + +UInt32 BodyGenTemplate::Evaluate(std::function eval) +{ + if (size() > 0) { + std::random_device rd; + std::default_random_engine gen(rd()); + std::uniform_int_distribution<> rnd(0, size() - 1); + + auto & morphs = at(rnd(gen)); + return morphs.Evaluate(eval); + } + + return 0; +} + +UInt32 BodyTemplateList::Evaluate(std::function eval) +{ + if (size() > 0) { + std::random_device rd; + std::default_random_engine gen(rd()); + std::uniform_int_distribution<> rnd(0, size() - 1); + + auto & bodyTemplate = at(rnd(gen)); + return bodyTemplate->Evaluate(eval); + } + + return 0; +} + +UInt32 BodyGenDataTemplates::Evaluate(std::function eval) +{ + UInt32 total = 0; + for (auto & tempList : *this) + { + total += tempList.Evaluate(eval); + } + + return total; +} + +UInt32 BodyMorphInterface::EvaluateBodyMorphs(TESObjectREFR * actor) +{ + TESNPC * actorBase = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (actorBase) { + BodyGenData::iterator morphSet = bodyGenData.end(); + do { + morphSet = bodyGenData.find(actorBase); + actorBase = actorBase->nextTemplate; + } while (actorBase && morphSet == bodyGenData.end()); + + // Found a matching template + if (morphSet != bodyGenData.end()) { + std::random_device rd; + std::default_random_engine gen(rd()); + + auto & templates = morphSet->second; + UInt32 ret = templates->Evaluate([&](BSFixedString morphName, float value) + { + SetMorph(actor, morphName, "RSMBodyGen", value); + }); + return ret; + } + } + + return 0; +} + +void BodyMorphInterface::VisitStrings(std::function functor) +{ + SimpleLocker locker(&actorMorphs.m_lock); + for (auto & i1 : actorMorphs.m_data) { + for (auto & i2 : i1.second) { + functor(i2.first); + for (auto & i3 : i2.second) { + functor(i3.first); + } + } + } +} + +void BodyMorphInterface::VisitActors(std::function functor) +{ + SimpleLocker locker(&actorMorphs.m_lock); + for (auto & actor : actorMorphs.m_data) { + TESObjectREFR * refr = (TESObjectREFR *)g_overrideInterface.GetObject(actor.first, TESObjectREFR::kTypeID); + if (refr) { + functor(refr); + } + } +} + +// Serialize Morph +void BodyMorph::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + intfc->OpenRecord('MRPV', kVersion); + + UInt8 morphLength = strlen(m_name.data); + intfc->WriteRecordData(&morphLength, sizeof(morphLength)); + intfc->WriteRecordData(m_name.data, morphLength); + intfc->WriteRecordData(&m_value, sizeof(m_value)); +} + +bool BodyMorph::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + m_name = ""; + m_value = 0.0; + + if(intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'MRPV': + { + char * stringName = NULL; + UInt8 stringLength; + if (!intfc->ReadRecordData(&stringLength, sizeof(stringLength))) + { + _ERROR("%s - Error loading body morph name length", __FUNCTION__); + error = true; + return error; + } + + stringName = new char[stringLength + 1]; + if(!intfc->ReadRecordData(stringName, stringLength)) { + _ERROR("%s - Error loading body morph name", __FUNCTION__); + error = true; + return error; + } + stringName[stringLength] = 0; + m_name = BSFixedString(stringName); + + if (!intfc->ReadRecordData(&m_value, sizeof(m_value))) { + _ERROR("%s - Error loading body morph value", __FUNCTION__); + error = true; + return error; + } + } + break; + default: + { + _ERROR("%s - Error loading unexpected chunk type %08X (%.4s)", __FUNCTION__, type, &type); + error = true; + return error; + } + } + } + + return error; +} + +// Serialize Morph Set +void BodyMorphSet::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + intfc->OpenRecord('MRST', kVersion); + + UInt32 numMorphs = this->size(); + intfc->WriteRecordData(&numMorphs, sizeof(numMorphs)); + +#ifdef _DEBUG + _MESSAGE("%s - Saving %d morphs", __FUNCTION__, numMorphs); +#endif + + for(auto it = this->begin(); it != this->end(); ++it) + const_cast((*it)).Save(intfc, kVersion); +} + +bool BodyMorphSet::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + if(intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'MRST': + { + // Override Count + UInt32 numMorphs = 0; + if (!intfc->ReadRecordData(&numMorphs, sizeof(numMorphs))) + { + _ERROR("%s - Error loading morph set count", __FUNCTION__); + error = true; + return error; + } + + for (UInt32 i = 0; i < numMorphs; i++) + { + BodyMorph value; + if (!value.Load(intfc, version)) + { + if(value.m_name == BSFixedString("")) + continue; + +#ifdef _DEBUG + _MESSAGE("%s - Loaded morph %s %f", __FUNCTION__, value.m_name, value.m_value); +#endif + + this->insert(value); + } + else + { + _ERROR("%s - Error loading morph value", __FUNCTION__); + error = true; + return error; + } + } + + break; + } + default: + { + _ERROR("%s - Error loading unexpected chunk type %08X (%.4s)", __FUNCTION__, type, &type); + error = true; + return error; + } + } + } + + return error; +} + +// Serialize ActorMorphs +void ActorMorphs::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + for(auto & morph : m_data) { + intfc->OpenRecord('MRPH', kVersion); + + // Key + UInt64 handle = morph.first; + intfc->WriteRecordData(&handle, sizeof(handle)); + +#ifdef _DEBUG + _MESSAGE("%s - Saving Morph Handle %016llX", __FUNCTION__, handle); +#endif + + // Value + morph.second.Save(intfc, kVersion); + } +} + + +// Serialize Morph Set +void BodyMorphData::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + intfc->OpenRecord('MRDT', kVersion); + + UInt32 numMorphs = this->size(); + intfc->WriteRecordData(&numMorphs, sizeof(numMorphs)); + +#ifdef _DEBUG + _MESSAGE("%s - Saving %d morphs", __FUNCTION__, numMorphs); +#endif + + for (auto & morph : *this) + { + g_stringTable.WriteString(intfc, morph.first, kVersion); + + UInt32 numKeys = morph.second.size(); + intfc->WriteRecordData(&numKeys, sizeof(numKeys)); + + for (auto & keys : morph.second) + { + g_stringTable.WriteString(intfc, keys.first, kVersion); + intfc->WriteRecordData(&keys.second, sizeof(keys.second)); + } + } +} + +bool BodyMorphData::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + if (intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'MRDT': + { + UInt32 numMorphs = 0; + if (!intfc->ReadRecordData(&numMorphs, sizeof(numMorphs))) + { + _ERROR("%s - Error loading morph count", __FUNCTION__); + error = true; + return error; + } + + for (UInt32 i = 0; i < numMorphs; i++) + { + BSFixedString morphName = g_stringTable.ReadString(intfc, kVersion); + + UInt32 numKeys = 0; + if (!intfc->ReadRecordData(&numKeys, sizeof(numKeys))) + { + _ERROR("%s - Error loading morph key count", __FUNCTION__); + error = true; + return error; + } + + std::unordered_map pairs; + for (UInt32 i = 0; i < numKeys; i++) + { + BSFixedString keyName = g_stringTable.ReadString(intfc, kVersion); + + float value = 0; + if (!intfc->ReadRecordData(&value, sizeof(value))) { + _ERROR("%s - Error loading body morph value", __FUNCTION__); + error = true; + return error; + } + + // If the keys were mapped by mod name, skip them if they arent in load order + std::string strKey(keyName.data); + BSFixedString ext(strKey.substr(strKey.find_last_of(".") + 1).c_str()); + if (ext == BSFixedString("esp") || ext == BSFixedString("esm")) + { + if (!DataHandler::GetSingleton()->LookupModByName(keyName.data)) + continue; + } + + pairs.insert_or_assign(keyName, value); + } + + insert_or_assign(morphName, pairs); + } + + break; + } + default: + { + _ERROR("%s - Error loading unexpected chunk type %08X (%.4s)", __FUNCTION__, type, &type); + error = true; + return error; + } + } + } + + return error; +} + +bool ActorMorphs::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + bool error = false; + + UInt64 handle; + // Key + if (!intfc->ReadRecordData(&handle, sizeof(handle))) + { + _ERROR("%s - Error loading MorphSet key", __FUNCTION__); + error = true; + return error; + } + + BodyMorphSet morphSet; + BodyMorphData morphMap; + + if (kVersion == BodyMorphInterface::kSerializationVersion1) + { + if (morphSet.Load(intfc, kVersion)) + { + _ERROR("%s - Error loading MorphSet", __FUNCTION__); + error = true; + return error; + } + } + else if (kVersion == BodyMorphInterface::kSerializationVersion2) + { + if (morphMap.Load(intfc, kVersion)) + { + _ERROR("%s - Error loading MorphMap", __FUNCTION__); + error = true; + return error; + } + } + + if (!morphSet.empty()) + { + for (auto & morph : morphSet) + { + morphMap[morph.m_name]["RSMLegacy"] = morph.m_value; + } + } + + UInt64 newHandle = 0; + + // Skip if handle is no longer valid. + if (! intfc->ResolveHandle(handle, &newHandle)) + return false; + + if(morphMap.empty()) + return false; + + if (g_enableBodyGen) + { + m_data.insert_or_assign(newHandle, morphMap); + + TESObjectREFR * refr = (TESObjectREFR *)g_overrideInterface.GetObject(handle, TESObjectREFR::kTypeID); + +#ifdef _DEBUG + if (refr) + _MESSAGE("%s - Loaded MorphSet Handle %016llX actor %s", __FUNCTION__, newHandle, CALL_MEMBER_FN(refr, GetReferenceName)()); +#endif + + g_bodyMorphInterface.UpdateModelWeight(refr); + } + + return error; +} + +// Serialize Morphs +void BodyMorphInterface::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + actorMorphs.Save(intfc, kVersion); +} + +bool BodyMorphInterface::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + return actorMorphs.Load(intfc, kVersion); +} \ No newline at end of file diff --git a/skee/BodyMorphInterface.h b/skee/BodyMorphInterface.h new file mode 100644 index 0000000..e3522e6 --- /dev/null +++ b/skee/BodyMorphInterface.h @@ -0,0 +1,272 @@ +#pragma once + +#include "IPluginInterface.h" +#include "IHashType.h" + +#include "skse64/GameTypes.h" +#include "skse64/GameThreads.h" +#include "skse64/NiTypes.h" + +#include +#include +#include +#include +#include +#include +#include + +class TESObjectREFR; +struct SKSESerializationInterface; +class BSResourceNiBinaryStream; +class NiAVObject; +class TESObjectARMO; +class TESObjectARMA; +class NiExtraData; +class TESNPC; + +#define MORPH_MOD_DIRECTORY "actors\\character\\BodyGenData\\" + +class BodyMorph +{ +public: + bool operator<(const BodyMorph & rhs) const { return m_name < rhs.m_name; } + bool operator==(const BodyMorph & rhs) const { return m_name == rhs.m_name; } + + BSFixedString m_name; + float m_value; + + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); +}; + +class BodyMorphSet : public std::set +{ +public: + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); +}; + +class BodyMorphData : public std::unordered_map> +{ +public: + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); +}; + +class ActorMorphs : public SafeDataHolder> +{ + friend class BodyMorphInterface; +public: + typedef std::unordered_map MorphMap; + + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); +}; + +class TriShapeVertexDelta +{ +public: + UInt16 index; + NiPoint3 diff; +}; + +class TriShapePackedVertexDelta +{ +public: + UInt16 index; + SInt16 x; + SInt16 y; + SInt16 z; +}; + +class TriShapeVertexData +{ +public: + virtual void ApplyMorph(UInt16 vertCount, NiPoint3 * vertices, float factor) = 0; +}; +typedef std::shared_ptr TriShapeVertexDataPtr; + +class TriShapeFullVertexData : public TriShapeVertexData +{ +public: + virtual void ApplyMorph(UInt16 vertCount, NiPoint3 * vertices, float factor); + + std::vector m_vertexDeltas; +}; +typedef std::shared_ptr TriShapeFullVertexDataPtr; + +class TriShapePackedVertexData : public TriShapeVertexData +{ +public: + virtual void ApplyMorph(UInt16 vertCount, NiPoint3 * vertices, float factor); + + float m_multiplier; + std::vector m_vertexDeltas; +}; +typedef std::shared_ptr TriShapePackedVertexDataPtr; + + +class BodyMorphMap : public std::unordered_map +{ +public: + void ApplyMorphs(TESObjectREFR * refr, UInt16 vertexCount, NiPoint3* targetGeometry, NiPoint3 * storageGeometry) const; + bool HasMorphs(TESObjectREFR * refr) const; +}; + +class TriShapeMap : public std::unordered_map +{ +public: + TriShapeMap() + { + memoryUsage = sizeof(TriShapeMap); + accessed = 0; + } + + void ApplyMorphs(TESObjectREFR * refr, NiAVObject * rootNode, bool erase = false); + void ApplyMorph(TESObjectREFR * refr, NiAVObject * rootNode, bool erase, const std::pair & bodyMorph); + + UInt32 memoryUsage; + std::time_t accessed; +}; + + +class MorphCache : public SafeDataHolder> +{ + friend class BodyMorphInterface; + +public: + MorphCache() + { + totalMemory = sizeof(MorphCache); + memoryLimit = totalMemory; + } + + typedef std::unordered_map FileMap; + + BSFixedString CreateTRIPath(const char * relativePath); + bool CacheFile(const char * modelPath); + + void ApplyMorphs(TESObjectREFR * refr, NiAVObject * rootNode, bool erase = false); + void UpdateMorphs(TESObjectREFR * refr); + + void Shrink(); + +private: + UInt32 memoryLimit; + UInt32 totalMemory; +}; + +class NIOVTaskUpdateModelWeight : public TaskDelegate +{ +public: + virtual void Run(); + virtual void Dispose(); + + NIOVTaskUpdateModelWeight(Actor * actor); + +private: + UInt32 m_formId; +}; + +class BodyGenMorphData +{ +public: + BSFixedString name; + float lower; + float upper; +}; + +class BodyGenMorphSelector : public std::vector +{ +public: + UInt32 Evaluate(std::function eval); +}; + +class BodyGenMorphs : public std::vector +{ +public: + UInt32 Evaluate(std::function eval); +}; + +class BodyGenTemplate : public std::vector +{ +public: + UInt32 Evaluate(std::function eval); +}; +typedef std::shared_ptr BodyGenTemplatePtr; + + +typedef std::unordered_map BodyGenTemplates; + +class BodyTemplateList : public std::vector +{ +public: + UInt32 Evaluate(std::function eval); +}; + +class BodyGenDataTemplates : public std::vector +{ +public: + UInt32 Evaluate(std::function eval); +}; +typedef std::shared_ptr BodyGenDataTemplatesPtr; + +typedef std::unordered_map BodyGenData; + +class BodyMorphInterface : public IPluginInterface +{ +public: + enum + { + kCurrentPluginVersion = 3, + kSerializationVersion1 = 1, + kSerializationVersion2 = 2, + kSerializationVersion = kSerializationVersion2 + }; + virtual UInt32 GetVersion(); + + // Serialization + virtual void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + virtual bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); + virtual void Revert(); + + void LoadMods(); + + virtual void SetMorph(TESObjectREFR * actor, BSFixedString morphName, BSFixedString morphKey, float relative); + virtual float GetMorph(TESObjectREFR * actor, BSFixedString morphName, BSFixedString morphKey); + virtual void ClearMorph(TESObjectREFR * actor, BSFixedString morphName, BSFixedString morphKey); + + virtual float GetBodyMorphs(TESObjectREFR * actor, BSFixedString morphName); + virtual void ClearBodyMorphNames(TESObjectREFR * actor, BSFixedString morphName); + + virtual void VisitMorphs(TESObjectREFR * actor, std::function *)> functor); + virtual void VisitKeys(TESObjectREFR * actor, BSFixedString name, std::function functor); + + virtual void ClearMorphs(TESObjectREFR * actor); + + virtual void ApplyVertexDiff(TESObjectREFR * refr, NiAVObject * rootNode, bool erase = false); + virtual void ApplyBodyMorphs(TESObjectREFR * refr); + virtual void UpdateModelWeight(TESObjectREFR * refr, bool immediate = false); + + virtual void SetCacheLimit(UInt32 limit); + virtual bool HasMorphs(TESObjectREFR * actor); + + virtual bool ReadBodyMorphs(BSFixedString filePath); + virtual bool ReadBodyMorphTemplates(BSFixedString filePath); + virtual UInt32 EvaluateBodyMorphs(TESObjectREFR * actor); + + virtual bool HasBodyMorph(TESObjectREFR * actor, BSFixedString morphName, BSFixedString morphKey); + virtual bool HasBodyMorphName(TESObjectREFR * actor, BSFixedString morphName); + virtual bool HasBodyMorphKey(TESObjectREFR * actor, BSFixedString morphKey); + virtual void ClearBodyMorphKeys(TESObjectREFR * actor, BSFixedString morphKey); + virtual void VisitStrings(std::function functor); + virtual void VisitActors(std::function functor); + +private: + ActorMorphs actorMorphs; + MorphCache morphCache; + BodyGenTemplates bodyGenTemplates; + BodyGenData bodyGenData; +}; \ No newline at end of file diff --git a/skee/CDXBrush.cpp b/skee/CDXBrush.cpp new file mode 100644 index 0000000..4ade9cf --- /dev/null +++ b/skee/CDXBrush.cpp @@ -0,0 +1,486 @@ +#ifdef FIXME + +#include "CDXBrush.h" +#include "CDXMesh.h" +#include "CDXUndo.h" +#include "CDXShader.h" +#include "CDXMaterial.h" + +double g_brushProperties[CDXBrush::kBrushTypes][CDXBrush::kBrushProperties][CDXBrush::kBrushPropertyValues]; + +void CDXBrush::InitGlobals() +{ + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Radius][kBrushPropertyValue_Value] = 0.5; + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Radius][kBrushPropertyValue_Interval] = 0.01; + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Radius][kBrushPropertyValue_Min] = 0.01; + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Radius][kBrushPropertyValue_Max] = 2.00; + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Strength][kBrushPropertyValue_Value] = 0.0; + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Strength][kBrushPropertyValue_Interval] = 0.0; + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Strength][kBrushPropertyValue_Min] = 0.0; + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Strength][kBrushPropertyValue_Max] = 0.0; + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Falloff][kBrushPropertyValue_Value] = 0.0; + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Falloff][kBrushPropertyValue_Interval] = 0.0; + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Falloff][kBrushPropertyValue_Min] = 0.0; + g_brushProperties[kBrushType_Mask_Add][kBrushProperty_Falloff][kBrushPropertyValue_Max] = 0.0; + + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Radius][kBrushPropertyValue_Value] = 0.5; + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Radius][kBrushPropertyValue_Interval] = 0.01; + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Radius][kBrushPropertyValue_Min] = 0.01; + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Radius][kBrushPropertyValue_Max] = 2.00; + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Strength][kBrushPropertyValue_Value] = 0.0; + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Strength][kBrushPropertyValue_Interval] = 0.0; + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Strength][kBrushPropertyValue_Min] = 0.0; + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Strength][kBrushPropertyValue_Max] = 0.0; + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Falloff][kBrushPropertyValue_Value] = 0.0; + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Falloff][kBrushPropertyValue_Interval] = 0.0; + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Falloff][kBrushPropertyValue_Min] = 0.0; + g_brushProperties[kBrushType_Mask_Subtract][kBrushProperty_Falloff][kBrushPropertyValue_Max] = 0.0; + + g_brushProperties[kBrushType_Inflate][kBrushProperty_Radius][kBrushPropertyValue_Value] = 0.5; + g_brushProperties[kBrushType_Inflate][kBrushProperty_Radius][kBrushPropertyValue_Interval] = 0.01; + g_brushProperties[kBrushType_Inflate][kBrushProperty_Radius][kBrushPropertyValue_Min] = 0.01; + g_brushProperties[kBrushType_Inflate][kBrushProperty_Radius][kBrushPropertyValue_Max] = 2.00; + g_brushProperties[kBrushType_Inflate][kBrushProperty_Strength][kBrushPropertyValue_Value] = 0.01; + g_brushProperties[kBrushType_Inflate][kBrushProperty_Strength][kBrushPropertyValue_Interval] = 0.001; + g_brushProperties[kBrushType_Inflate][kBrushProperty_Strength][kBrushPropertyValue_Min] = 0.001; + g_brushProperties[kBrushType_Inflate][kBrushProperty_Strength][kBrushPropertyValue_Max] = 1.000; + g_brushProperties[kBrushType_Inflate][kBrushProperty_Falloff][kBrushPropertyValue_Value] = 1.0; + g_brushProperties[kBrushType_Inflate][kBrushProperty_Falloff][kBrushPropertyValue_Interval] = 0.001; + g_brushProperties[kBrushType_Inflate][kBrushProperty_Falloff][kBrushPropertyValue_Min] = 0.0; + g_brushProperties[kBrushType_Inflate][kBrushProperty_Falloff][kBrushPropertyValue_Max] = 1.0; + + g_brushProperties[kBrushType_Deflate][kBrushProperty_Radius][kBrushPropertyValue_Value] = 0.5; + g_brushProperties[kBrushType_Deflate][kBrushProperty_Radius][kBrushPropertyValue_Interval] = 0.01; + g_brushProperties[kBrushType_Deflate][kBrushProperty_Radius][kBrushPropertyValue_Min] = 0.01; + g_brushProperties[kBrushType_Deflate][kBrushProperty_Radius][kBrushPropertyValue_Max] = 2.00; + g_brushProperties[kBrushType_Deflate][kBrushProperty_Strength][kBrushPropertyValue_Value] = 0.01; + g_brushProperties[kBrushType_Deflate][kBrushProperty_Strength][kBrushPropertyValue_Interval] = 0.001; + g_brushProperties[kBrushType_Deflate][kBrushProperty_Strength][kBrushPropertyValue_Min] = 0.001; + g_brushProperties[kBrushType_Deflate][kBrushProperty_Strength][kBrushPropertyValue_Max] = 1.000; + g_brushProperties[kBrushType_Deflate][kBrushProperty_Falloff][kBrushPropertyValue_Value] = 1.0; + g_brushProperties[kBrushType_Deflate][kBrushProperty_Falloff][kBrushPropertyValue_Interval] = 0.001; + g_brushProperties[kBrushType_Deflate][kBrushProperty_Falloff][kBrushPropertyValue_Min] = 0.0; + g_brushProperties[kBrushType_Deflate][kBrushProperty_Falloff][kBrushPropertyValue_Max] = 1.0; + + g_brushProperties[kBrushType_Smooth][kBrushProperty_Radius][kBrushPropertyValue_Value] = 0.5; + g_brushProperties[kBrushType_Smooth][kBrushProperty_Radius][kBrushPropertyValue_Interval] = 0.01; + g_brushProperties[kBrushType_Smooth][kBrushProperty_Radius][kBrushPropertyValue_Min] = 0.01; + g_brushProperties[kBrushType_Smooth][kBrushProperty_Radius][kBrushPropertyValue_Max] = 2.00; + g_brushProperties[kBrushType_Smooth][kBrushProperty_Strength][kBrushPropertyValue_Value] = 0.05; + g_brushProperties[kBrushType_Smooth][kBrushProperty_Strength][kBrushPropertyValue_Interval] = 0.001; + g_brushProperties[kBrushType_Smooth][kBrushProperty_Strength][kBrushPropertyValue_Min] = 0.001; + g_brushProperties[kBrushType_Smooth][kBrushProperty_Strength][kBrushPropertyValue_Max] = 1.000; + g_brushProperties[kBrushType_Smooth][kBrushProperty_Falloff][kBrushPropertyValue_Value] = 1.0; + g_brushProperties[kBrushType_Smooth][kBrushProperty_Falloff][kBrushPropertyValue_Interval] = 0.001; + g_brushProperties[kBrushType_Smooth][kBrushProperty_Falloff][kBrushPropertyValue_Min] = 0.0; + g_brushProperties[kBrushType_Smooth][kBrushProperty_Falloff][kBrushPropertyValue_Max] = 1.0; + + g_brushProperties[kBrushType_Move][kBrushProperty_Radius][kBrushPropertyValue_Value] = 0.5; + g_brushProperties[kBrushType_Move][kBrushProperty_Radius][kBrushPropertyValue_Interval] = 0.01; + g_brushProperties[kBrushType_Move][kBrushProperty_Radius][kBrushPropertyValue_Min] = 0.01; + g_brushProperties[kBrushType_Move][kBrushProperty_Radius][kBrushPropertyValue_Max] = 2.00; + g_brushProperties[kBrushType_Move][kBrushProperty_Strength][kBrushPropertyValue_Value] = 0.01; + g_brushProperties[kBrushType_Move][kBrushProperty_Strength][kBrushPropertyValue_Interval] = 0.001; + g_brushProperties[kBrushType_Move][kBrushProperty_Strength][kBrushPropertyValue_Min] = 0.001; + g_brushProperties[kBrushType_Move][kBrushProperty_Strength][kBrushPropertyValue_Max] = 1.000; + g_brushProperties[kBrushType_Move][kBrushProperty_Falloff][kBrushPropertyValue_Value] = 1.0; + g_brushProperties[kBrushType_Move][kBrushProperty_Falloff][kBrushPropertyValue_Interval] = 0.001; + g_brushProperties[kBrushType_Move][kBrushProperty_Falloff][kBrushPropertyValue_Min] = 0.0; + g_brushProperties[kBrushType_Move][kBrushProperty_Falloff][kBrushPropertyValue_Max] = 1.0; +} + +CDXBrush::CDXBrush() +{ + m_mirror = true; +} + +void CDXBasicBrush::EndStroke() +{ + for (auto stroke : m_strokes) { + stroke->End(); + if (stroke->Length() > 0) { + stroke->Apply(g_undoStack.Push(stroke)); + } + } + + m_strokes.clear(); +} + +CDXHitIndexMap CDXBasicBrush::GetHitIndices(CDXPickInfo & pickInfo, CDXEditableMesh * mesh) +{ + return CDXHitIndexMap(); +} + +float CDXBrush::CalculateFalloff(float & dist) +{ + double radius = m_property[kBrushProperty_Radius][kBrushPropertyValue_Value]; + double falloff = m_property[kBrushProperty_Falloff][kBrushPropertyValue_Value]; + + if (falloff > 1.0) + falloff = 1.0; + if (falloff < 0) + falloff = 0; + + // X1 = 0 Y1 = 1.0 + // X2 = 1.0 Y2 = falloff + + // m = Y2 - Y1 + // X2 - X1 + // y - Y1 = m(x - X1) + + //double y = (falloff - 1.0) * (dist / radius) + 1.0; + //y = y*y*(3 - 2 * y); + + // X1 = 1.0 + // Y1 = falloff + // X2 = 0 + // Y2 = 1.0 + + // m = Y2 - Y1 + // X2 - X1 + // y - Y1 = m(x - X1) + + double y = -(1.0 - (1.0 - falloff)) * ((dist / radius) - 1.0) + (1.0 - falloff); + y = y*y*(3 - 2 * y); + + return y; + + /*double p = 1.0f; + + double x = dist / radius; // (radius - dist)/dist; + if (x <= (falloff - 1.0f) / (falloff)) { + p = 1.0f; + } + else { + float fx; + float p1 = 0.0f; + float p2 = 0.0f; + if (falloff >= 1.0f) { + fx = (falloff - 1.0f) - (falloff*x); + p1 = (1.0f - fx*fx); + p2 = (1.0f - pow(fx, 4)); + } + else if (falloff > 0.0f) { + fx = x / falloff; + p1 = (1.0f - x*x); + p2 = (1.0f - pow(fx, 4)); + } + + p = p1*p2; + if (p < 0.0f) p = 0.0f; + } + + return p;*/ +} + +CDXHitIndexMap CDXBasicHitBrush::GetHitIndices(CDXPickInfo & pickInfo, CDXEditableMesh * mesh) +{ + CDXMeshVert * pVertices = mesh->LockVertices(); + CDXHitIndexMap hitVertex; + for (UInt32 i = 0; i < mesh->GetVertexCount(); i++) { + if (FilterVertex(mesh, pVertices, i)) + continue; + CDXVec3 vTest = pVertices[i].Position; + CDXVec3 vDiff = pickInfo.origin - vTest; + float testRadius = D3DXVec3Length(&vDiff); // Spherical radius + if (testRadius <= m_property[kBrushProperty_Radius][kBrushPropertyValue_Value]) { + hitVertex.emplace(i, CalculateFalloff(testRadius)); + } + } + mesh->UnlockVertices(); + return hitVertex; +} + +bool CDXBasicHitBrush::FilterVertex(CDXEditableMesh * mesh, CDXMeshVert * pVertices, CDXMeshIndex i) +{ + if (pVertices[i].Color != COLOR_UNSELECTED) + return true; + + return false; +} + +bool CDXBasicHitBrush::BeginStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror) +{ + if (!pickInfo.isHit) + return false; + + CDXStrokePtr stroke = CreateStroke(this, mesh); + stroke->SetMirror(isMirror); + stroke->Begin(pickInfo); + m_strokes.push_back(stroke); + + // Count the beginning as the first stroke + return UpdateStroke(pickInfo, mesh, isMirror); +} + +bool CDXBrushPickerBegin::Pick(CDXPickInfo & pickInfo, CDXMesh * mesh, bool isMirror) +{ + if (mesh->IsEditable()) + return m_brush->BeginStroke(pickInfo, (CDXEditableMesh*)mesh, isMirror); + + return false; +} + +bool CDXBrushPickerUpdate::Pick(CDXPickInfo & pickInfo, CDXMesh * mesh, bool isMirror) +{ + if (mesh->IsEditable()) + return m_brush->UpdateStroke(pickInfo, (CDXEditableMesh*)mesh, isMirror); + + return false; +} + +CDXMaskAddBrush::CDXMaskAddBrush() : CDXBasicHitBrush() +{ + for (UInt32 p = 0; p < CDXBrush::kBrushProperties; p++) + for (UInt32 v = 0; v < CDXBrush::kBrushPropertyValues; v++) + m_property[p][v] = g_brushProperties[CDXBrush::kBrushType_Mask_Add][p][v]; +} + +CDXStrokePtr CDXMaskAddBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(this, mesh); +} + +bool CDXMaskAddBrush::UpdateStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror) +{ + if (!pickInfo.isHit) + return false; + + for (auto stroke : m_strokes) { + if (stroke->IsMirror() != isMirror) + continue; + + auto hitVertex = GetHitIndices(pickInfo, stroke->GetMesh()); + for (auto i : hitVertex) { + CDXStroke::Info strokeInfo; + strokeInfo.index = i.first; + strokeInfo.strength = m_property[kBrushProperty_Strength][kBrushPropertyValue_Value]; + strokeInfo.falloff = i.second; + stroke->Update(&strokeInfo); + } + } + + return true; +} + +CDXMaskSubtractBrush::CDXMaskSubtractBrush() : CDXMaskAddBrush() +{ + for (UInt32 p = 0; p < CDXBrush::kBrushProperties; p++) + for (UInt32 v = 0; v < CDXBrush::kBrushPropertyValues; v++) + m_property[p][v] = g_brushProperties[CDXBrush::kBrushType_Mask_Subtract][p][v]; +} + +CDXStrokePtr CDXMaskSubtractBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(this, mesh); +} + +bool CDXMaskSubtractBrush::FilterVertex(CDXEditableMesh * mesh, CDXMeshVert * pVertices, CDXMeshIndex i) +{ + return false; +} + +CDXInflateBrush::CDXInflateBrush() : CDXBasicHitBrush() +{ + for (UInt32 p = 0; p < CDXBrush::kBrushProperties; p++) + for (UInt32 v = 0; v < CDXBrush::kBrushPropertyValues; v++) + m_property[p][v] = g_brushProperties[CDXBrush::kBrushType_Inflate][p][v]; +} + +CDXStrokePtr CDXInflateBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(this, mesh); +} + +bool CDXInflateBrush::UpdateStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror) +{ + if (!pickInfo.isHit) + return false; + + for (auto stroke : m_strokes) { + if (stroke->IsMirror() != isMirror) + continue; + + auto hitVertex = GetHitIndices(pickInfo, stroke->GetMesh()); + + CDXVec3 normal(0, 0, 0); + for (auto i : hitVertex) { + normal += mesh->CalculateVertexNormal(i.first); + } + D3DXVec3Normalize(&normal, &normal); + + for (auto i : hitVertex) { + CDXInflateStroke::InflateInfo strokeInfo; + strokeInfo.index = i.first; + strokeInfo.strength = m_property[kBrushProperty_Strength][kBrushPropertyValue_Value]; + strokeInfo.falloff = i.second; + strokeInfo.normal = normal; + stroke->Update(&strokeInfo); + } + } + + return true; +} + +CDXDeflateBrush::CDXDeflateBrush() : CDXInflateBrush() +{ + for (UInt32 p = 0; p < CDXBrush::kBrushProperties; p++) + for (UInt32 v = 0; v < CDXBrush::kBrushPropertyValues; v++) + m_property[p][v] = g_brushProperties[CDXBrush::kBrushType_Deflate][p][v]; +} + +CDXStrokePtr CDXDeflateBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(this, mesh); +} + +CDXSmoothBrush::CDXSmoothBrush() : CDXBasicHitBrush() +{ + for (UInt32 p = 0; p < CDXBrush::kBrushProperties; p++) + for (UInt32 v = 0; v < CDXBrush::kBrushPropertyValues; v++) + m_property[p][v] = g_brushProperties[CDXBrush::kBrushType_Smooth][p][v]; +} + +CDXStrokePtr CDXSmoothBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(this, mesh); +} + +bool CDXSmoothBrush::FilterVertex(CDXEditableMesh * mesh, CDXMeshVert * pVertices, CDXMeshIndex i) +{ + if (CDXBasicHitBrush::FilterVertex(mesh, pVertices, i)) + return true; + + if (mesh->IsEdgeVertex(i)) + return true; + + return false; +} + +bool CDXSmoothBrush::UpdateStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror) +{ + if (!pickInfo.isHit) + return false; + + for (auto stroke : m_strokes) { + if (stroke->IsMirror() != isMirror) + continue; + + auto hitVertex = GetHitIndices(pickInfo, stroke->GetMesh()); + for (auto i : hitVertex) { + CDXStroke::Info strokeInfo; + strokeInfo.index = i.first; + strokeInfo.strength = m_property[kBrushProperty_Strength][kBrushPropertyValue_Value]; + strokeInfo.falloff = i.second; + stroke->Update(&strokeInfo); + } + } + + return true; +} + +CDXMoveBrush::CDXMoveBrush() : CDXBasicHitBrush() +{ + for (UInt32 p = 0; p < CDXBrush::kBrushProperties; p++) + for (UInt32 v = 0; v < CDXBrush::kBrushPropertyValues; v++) + m_property[p][v] = g_brushProperties[CDXBrush::kBrushType_Move][p][v]; +} + +CDXStrokePtr CDXMoveBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(this, mesh); +} + +bool CDXMoveBrush::BeginStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror) +{ + if (!pickInfo.isHit) + return false; + + CDXStrokePtr stroke = CreateStroke(this, mesh); + stroke->SetMirror(isMirror); + auto hitIndices = GetHitIndices(pickInfo, mesh); + auto moveStroke = std::dynamic_pointer_cast(stroke); + if (moveStroke) + moveStroke->SetHitIndices(hitIndices); + stroke->Begin(pickInfo); + m_strokes.push_back(stroke); + return true; +} +/* +#ifdef _DEBUG +#include +#include +#include +#include +#include +#include +#include + +struct DebugOutImpl { + DebugOutImpl(const char* location, int line, bool enabled_ = true) + :enabled(enabled_) + { + ss << location << line << "): " << std::this_thread::get_id() << " - "; + } + ~DebugOutImpl() { ss << '\n'; OutputDebugStringA(ss.str().c_str()); } + void operator()(const char* msg) { if (enabled) ss << msg; } + void operator()(const wchar_t* msg) { if (enabled) ss << wtoa(msg); } + std::ostringstream& operator()() { return ss; } + template + void operator()(const char_type* format, ...) { + if (enabled) { + char_type buf[4096]; + va_list ap; + va_start(ap, format); + vsnprintf_s(buf, 4096, format, ap); + va_end(ap); + operator()(buf); + } + } +private: + static std::string wtoa(const wchar_t* ptr, size_t len = -1){ + if (len == -1) len = wcslen(ptr); + std::string r(WideCharToMultiByte(CP_THREAD_ACP, 0, ptr, len, nullptr, 0, 0, 0), '\0'); + if (r.size() == 0) throw std::system_error(GetLastError(), std::system_category()); + if (0 == WideCharToMultiByte(CP_THREAD_ACP, 0, ptr, len, &r[0], r.size(), 0, 0)) + throw std::system_error(GetLastError(), std::system_category(), "error converting wide string to narrow"); + return r; + } + static inline std::string wtoa(const std::wstring& wstr) { return wtoa(&wstr[0], wstr.size()); } + static int vsnprintf_s(char* buffer, int bufsize, const char* format, va_list ap) { return ::vsnprintf_s(buffer, bufsize, _TRUNCATE, format, ap); } + static int vsnprintf_s(wchar_t* buffer, int bufsize, const wchar_t* format, va_list ap) { return ::vswprintf_s(buffer, bufsize, format, ap); } + + std::ostringstream ss; + bool enabled; +}; +#define DebugOut DebugOutImpl(__FILE__ "(", __LINE__) +#endif +*/ +bool CDXMoveBrush::UpdateStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror) +{ + for (auto stroke : m_strokes) { + if (stroke->IsMirror() != isMirror) + continue; + + CDXMoveStroke * moveStroke = static_cast(stroke.get()); + for (auto i : moveStroke->GetHitIndices()) { + CDXMoveStroke::MoveInfo strokeInfo; + strokeInfo.index = i.first; + strokeInfo.strength = m_property[kBrushProperty_Strength][kBrushPropertyValue_Value]; + strokeInfo.falloff = i.second; +/*#ifdef _DEBUG + DebugOut("%d - %f", strokeInfo.index, strokeInfo.falloff); +#endif*/ + CDXRayInfo rayStart = moveStroke->GetRayInfo(); + CDXVec3 d = pickInfo.ray.point - rayStart.point; + if (isMirror) + d.x = -d.x; + strokeInfo.offset = d; + stroke->Update(&strokeInfo); + } + } + + return true; +} + +#endif \ No newline at end of file diff --git a/skee/CDXBrush.h b/skee/CDXBrush.h new file mode 100644 index 0000000..2652532 --- /dev/null +++ b/skee/CDXBrush.h @@ -0,0 +1,179 @@ +#ifdef FIXME + +#ifndef __CDXBRUSH__ +#define __CDXBRUSH__ + +#pragma once + +#include "CDXPicker.h" +#include "CDXMesh.h" +#include "CDXUndo.h" +#include "CDXEditableMesh.h" +#include "CDXStroke.h" + +#include +#include + +class CDXBrush +{ +public: + CDXBrush(); + + enum BrushType { + kBrushType_None = -1, + kBrushType_Mask_Add = 0, + kBrushType_Mask_Subtract, + kBrushType_Inflate, + kBrushType_Deflate, + kBrushType_Move, + kBrushType_Smooth, + kBrushTypes + }; + + enum BrushProperty + { + kBrushProperty_Radius = 0, + kBrushProperty_Strength, + kBrushProperty_Falloff, + kBrushProperties + }; + + enum BrushPropertyValue + { + kBrushPropertyValue_Value = 0, + kBrushPropertyValue_Min, + kBrushPropertyValue_Max, + kBrushPropertyValue_Interval, + kBrushPropertyValues + }; + + static void InitGlobals(); + + virtual BrushType GetType() { return kBrushType_None; } + + double GetProperty(BrushProperty prop, BrushPropertyValue val) { return m_property[prop][val]; } + void SetProperty(BrushProperty prop, BrushPropertyValue val, double newRadius) { m_property[prop][val] = newRadius; } + + void SetMirror(bool m) { m_mirror = m; } + bool IsMirror() const { return m_mirror; } + + float CalculateFalloff(float & dist); + + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) = 0; + virtual CDXHitIndexMap GetHitIndices(CDXPickInfo & pickInfo, CDXEditableMesh * mesh) = 0; + virtual bool FilterVertex(CDXEditableMesh * mesh, CDXMeshVert * pVertices, CDXMeshIndex i) = 0; + virtual bool BeginStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror) = 0; + virtual bool UpdateStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror) = 0; + virtual void EndStroke() = 0; + +protected: + CDXStrokeList m_strokes; + double m_property[kBrushProperties][kBrushPropertyValues]; + bool m_mirror; +}; + +class CDXBasicBrush : public CDXBrush +{ +public: + virtual CDXHitIndexMap GetHitIndices(CDXPickInfo & pickInfo, CDXEditableMesh * mesh); + virtual bool FilterVertex(CDXEditableMesh * mesh, CDXMeshVert* pVertices, CDXMeshIndex i) { return false; } + virtual void EndStroke(); +}; + +class CDXBasicHitBrush : public CDXBasicBrush +{ +public: + virtual CDXHitIndexMap GetHitIndices(CDXPickInfo & pickInfo, CDXEditableMesh * mesh); + virtual bool BeginStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror); + virtual bool FilterVertex(CDXEditableMesh * mesh, CDXMeshVert * pVertices, CDXMeshIndex i); +}; + +class CDXBrushPicker : public CDXPicker +{ +public: + CDXBrushPicker(CDXBrush * brush) : m_brush(brush), m_mirror(true) { } + virtual bool Pick(CDXPickInfo & pickInfo, CDXMesh * mesh, bool isMirror) = 0; + virtual bool Mirror() const { return m_mirror; } + + void SetMirror(bool m) { m_mirror = m; } + + CDXBrush * GetBrush() { return m_brush; } + +protected: + bool m_mirror; + CDXBrush * m_brush; +}; + +class CDXBrushPickerBegin : public CDXBrushPicker +{ +public: + CDXBrushPickerBegin(CDXBrush * brush) : CDXBrushPicker(brush) { } + virtual bool Pick(CDXPickInfo & pickInfo, CDXMesh * mesh, bool isMirror); +}; + +class CDXBrushPickerUpdate : public CDXBrushPicker +{ +public: + CDXBrushPickerUpdate(CDXBrush * brush) : CDXBrushPicker(brush) { } + virtual bool Pick(CDXPickInfo & pickInfo, CDXMesh * mesh, bool isMirror); +}; + +class CDXMaskAddBrush : public CDXBasicHitBrush +{ +public: + CDXMaskAddBrush(); + virtual BrushType GetType() { return kBrushType_Mask_Add; } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); + virtual bool UpdateStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror); +}; + +class CDXMaskSubtractBrush : public CDXMaskAddBrush +{ +public: + CDXMaskSubtractBrush(); + + virtual BrushType GetType() { return kBrushType_Mask_Subtract; } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); + virtual bool FilterVertex(CDXEditableMesh * mesh, CDXMeshVert * pVertices, CDXMeshIndex i); +}; + +class CDXInflateBrush : public CDXBasicHitBrush +{ +public: + CDXInflateBrush(); + virtual BrushType GetType() { return kBrushType_Inflate; } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); + virtual bool UpdateStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror); +}; + +class CDXDeflateBrush : public CDXInflateBrush +{ +public: + CDXDeflateBrush(); + virtual BrushType GetType() { return kBrushType_Deflate; } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); +}; + +class CDXSmoothBrush : public CDXBasicHitBrush +{ +public: + CDXSmoothBrush(); + virtual BrushType GetType() { return kBrushType_Smooth; } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); + virtual bool UpdateStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror); + virtual bool FilterVertex(CDXEditableMesh * mesh, CDXMeshVert * pVertices, CDXMeshIndex i); +}; + +class CDXMoveBrush : public CDXBasicHitBrush +{ +public: + CDXMoveBrush(); + virtual BrushType GetType() { return kBrushType_Move; } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); + virtual bool BeginStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror); + virtual bool UpdateStroke(CDXPickInfo & pickInfo, CDXEditableMesh * mesh, bool isMirror); +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXCamera.cpp b/skee/CDXCamera.cpp new file mode 100644 index 0000000..35a29e1 --- /dev/null +++ b/skee/CDXCamera.cpp @@ -0,0 +1,387 @@ +#include "CDXCamera.h" + +#ifdef FIXME + +CDXArcBall::CDXArcBall() +{ + Reset(); + m_vDownPt = D3DXVECTOR3( 0, 0, 0 ); + m_vCurrentPt = D3DXVECTOR3( 0, 0, 0 ); + m_Offset.x = m_Offset.y = 0; +} + +//-------------------------------------------------------------------------------------- +void CDXArcBall::Reset() +{ + D3DXQuaternionIdentity( &m_qDown ); + D3DXQuaternionIdentity( &m_qNow ); + D3DXMatrixIdentity( &m_mRotation ); + D3DXMatrixIdentity( &m_mTranslation ); + D3DXMatrixIdentity( &m_mTranslationDelta ); + m_bDrag = FALSE; + m_fRadiusTranslation = 1.0f; + m_fRadius = 1.0f; +} + +//-------------------------------------------------------------------------------------- +D3DXVECTOR3 CDXArcBall::ScreenToVector( float fScreenPtX, float fScreenPtY ) +{ + // Scale to screen + FLOAT x = -( fScreenPtX - m_Offset.x - m_nWidth / 2 ) / ( m_fRadius * m_nWidth / 2 ); + FLOAT y = ( fScreenPtY - m_Offset.y - m_nHeight / 2 ) / ( m_fRadius * m_nHeight / 2 ); + + FLOAT z = 0.0f; + FLOAT mag = x * x + y * y; + + if( mag > 1.0f ) + { + FLOAT scale = 1.0f / sqrtf( mag ); + x *= scale; + y *= scale; + } + else + z = sqrtf( 1.0f - mag ); + + // Return vector + return D3DXVECTOR3( x, y, z ); +} + +//-------------------------------------------------------------------------------------- +D3DXQUATERNION CDXArcBall::QuatFromBallPoints( const D3DXVECTOR3& vFrom, const D3DXVECTOR3& vTo ) +{ + D3DXVECTOR3 vPart; + float fDot = D3DXVec3Dot( &vFrom, &vTo ); + D3DXVec3Cross( &vPart, &vFrom, &vTo ); + + return D3DXQUATERNION( vPart.x, vPart.y, vPart.z, fDot ); +} + +//-------------------------------------------------------------------------------------- +void CDXArcBall::OnRotateBegin( int nX, int nY ) +{ + // Only enter the drag state if the click falls + // inside the click rectangle. + if( nX >= m_Offset.x && + nX < m_Offset.x + m_nWidth && + nY >= m_Offset.y && + nY < m_Offset.y + m_nHeight ) + { + m_bDrag = true; + m_qDown = m_qNow; + m_vDownPt = ScreenToVector( ( float )nX, ( float )nY ); + } +} + +//-------------------------------------------------------------------------------------- +void CDXArcBall::OnRotate( int nX, int nY ) +{ + if( m_bDrag ) + { + m_vCurrentPt = ScreenToVector( ( float )nX, ( float )nY ); + m_qNow = m_qDown * QuatFromBallPoints( m_vDownPt, m_vCurrentPt ); + } +} + +//-------------------------------------------------------------------------------------- +void CDXArcBall::OnRotateEnd() +{ + m_bDrag = false; +} + + +//-------------------------------------------------------------------------------------- +// Calculates the projection matrix based on input params +//-------------------------------------------------------------------------------------- +VOID CDXModelViewerCamera::SetProjParams( FLOAT fFOV, FLOAT fAspect, FLOAT fNearPlane, + FLOAT fFarPlane ) +{ + // Set attributes for the projection matrix + m_fFOV = fFOV; + m_fAspect = fAspect; + m_fNearPlane = fNearPlane; + m_fFarPlane = fFarPlane; + + D3DXMatrixPerspectiveFovLH( &m_mProj, fFOV, fAspect, fNearPlane, fFarPlane ); +} +/* +void CDXModelViewerCamera::UpdateMouseDelta(int currentX, int currentY) +{ + POINT ptCurMouseDelta; + POINT ptCurMousePos; + ptCurMousePos.x = currentX; + ptCurMousePos.y = currentY; + + // Calc how far it's moved since last frame + ptCurMouseDelta.x = ptCurMousePos.x - m_ptLastMousePosition.x; + ptCurMouseDelta.y = ptCurMousePos.y - m_ptLastMousePosition.y; + + // Record current position for next time + m_ptLastMousePosition = ptCurMousePos; + + // Smooth the relative mouse data over a few frames so it isn't + // jerky when moving slowly at low frame rates. + float fPercentOfNew = 1.0f / m_fFramesToSmoothMouseData; + float fPercentOfOld = 1.0f - fPercentOfNew; + m_vMouseDelta.x = m_vMouseDelta.x * fPercentOfOld + ptCurMouseDelta.x * fPercentOfNew; + m_vMouseDelta.y = m_vMouseDelta.y * fPercentOfOld + ptCurMouseDelta.y * fPercentOfNew; + + m_vRotVelocity = m_vMouseDelta * m_fRotationScaler; +} +*/ +//-------------------------------------------------------------------------------------- +// Clamps pV to lie inside m_vMinBoundary & m_vMaxBoundary +//-------------------------------------------------------------------------------------- +void CDXModelViewerCamera::ConstrainToBoundary( D3DXVECTOR3* pV ) +{ + // Constrain vector to a bounding box + pV->x = __max( pV->x, m_vMinBoundary.x ); + pV->y = __max( pV->y, m_vMinBoundary.y ); + pV->z = __max( pV->z, m_vMinBoundary.z ); + + pV->x = __min( pV->x, m_vMaxBoundary.x ); + pV->y = __min( pV->y, m_vMaxBoundary.y ); + pV->z = __min( pV->z, m_vMaxBoundary.z ); +} + +//-------------------------------------------------------------------------------------- +// Constructor +//-------------------------------------------------------------------------------------- +CDXModelViewerCamera::CDXModelViewerCamera() +{ + D3DXVECTOR3 vEyePt = D3DXVECTOR3( 0.0f, 0.0f, 0.0f ); + D3DXVECTOR3 vLookatPt = D3DXVECTOR3( 0.0f, 0.0f, 1.0f ); + + // Setup the view matrix + SetViewParams( &vEyePt, &vLookatPt ); + + // Setup the projection matrix + SetProjParams( D3DX_PI / 4, 1.0f, 1.0f, 1000.0f ); + + //m_ptLastMousePosition.x = 0; + //m_ptLastMousePosition.y = 0; + + m_fCameraYawAngle = 0.0f; + m_fCameraPitchAngle = 0.0f; + + //SetRect( &m_rcDrag, LONG_MIN, LONG_MIN, LONG_MAX, LONG_MAX ); + m_vVelocity = D3DXVECTOR3( 0, 0, 0 ); + //m_bMovementDrag = false; + //m_vVelocityDrag = D3DXVECTOR3( 0, 0, 0 ); + //m_fDragTimer = 0.0f; + //m_fTotalDragTimeToZero = 0.25; + //m_vRotVelocity = D3DXVECTOR2( 0, 0 ); + + //m_fRotationScaler = 0.01f; + //m_fMoveScaler = 5.0f; + + //m_vMouseDelta = D3DXVECTOR2( 0, 0 ); + //m_fFramesToSmoothMouseData = 2.0f; + + m_bClipToBoundary = false; + m_vMinBoundary = D3DXVECTOR3( -1, -1, -1 ); + m_vMaxBoundary = D3DXVECTOR3( 1, 1, 1 ); + + D3DXMatrixIdentity( &m_mWorld ); + D3DXMatrixIdentity( &m_mModelRot ); + D3DXMatrixIdentity( &m_mModelLastRot ); + D3DXMatrixIdentity( &m_mCameraRotLast ); + m_vModelCenter = D3DXVECTOR3( 0, 0, 0 ); + m_fRadius = 5.0f; + m_fDefaultRadius = 5.0f; + m_fMinRadius = 1.0f; + m_fPanMultiplier = -0.05f; + m_fMaxRadius = FLT_MAX; + m_bDragSinceLastUpdate = true; +} + +void CDXModelViewerCamera::OnRotate(int currentX, int currentY) +{ + //UpdateMouseDelta(currentX, currentY); + m_WorldArcBall.OnRotate(currentX, currentY); + m_bDragSinceLastUpdate = true; + Update(); +} + +void CDXModelViewerCamera::OnMoveBegin(int currentX, int currentY) +{ + m_vDragLook = m_vLookAt; + m_vDragStart = D3DXVECTOR2(currentX, currentY); + m_vDragPos = m_vDragStart; +} + +void CDXModelViewerCamera::OnMove(int x, int y) +{ + m_vDragPos = D3DXVECTOR2(x, y); + + m_vLookAt = m_vDragLook; + + D3DXVECTOR2 panDelta = (m_vDragPos - m_vDragStart) * m_fPanMultiplier; + + m_vLookAt.x += -panDelta.x; + m_vLookAt.z += panDelta.y; + + m_bDragSinceLastUpdate = true; + Update(); +} + +void CDXModelViewerCamera::OnMoveEnd() +{ + m_vDragLook = m_vLookAt; + m_vDragStart = D3DXVECTOR2(0, 0); + m_vDragPos = D3DXVECTOR2(0, 0); +} + +void CDXModelViewerCamera::Update() +{ + // If no dragged has happend since last time FrameMove is called, + // and no camera key is held down, then no need to handle again. + if( !m_bDragSinceLastUpdate ) + return; + m_bDragSinceLastUpdate = false; + + // Simple euler method to calculate position delta + //D3DXVECTOR3 vPosDelta = m_vVelocity; + + // Change the radius from the camera to the model based on wheel scrolling + m_fRadius = __min( m_fMaxRadius, m_fRadius ); + m_fRadius = __max( m_fMinRadius, m_fRadius ); + + // Get the inverse of the arcball's rotation matrix + D3DXMATRIX mCameraRot; + D3DXMatrixInverse( &mCameraRot, NULL, m_ViewArcBall.GetRotationMatrix() ); + + //D3DXMATRIX mCameraRot; + //D3DXMatrixRotationYawPitchRoll(&mCameraRot, m_fCameraYawAngle, m_fCameraPitchAngle, 0); + + // Transform vectors based on camera's rotation matrix + D3DXVECTOR3 vWorldUp, vWorldAhead; + D3DXVECTOR3 vLocalUp = D3DXVECTOR3( 0, 1, 0 ); + D3DXVECTOR3 vLocalAhead = D3DXVECTOR3( 0, 0, 1 ); + D3DXVec3TransformCoord( &vWorldUp, &vLocalUp, &mCameraRot ); + D3DXVec3TransformCoord( &vWorldAhead, &vLocalAhead, &mCameraRot ); + + // Transform the position delta by the camera's rotation + /*D3DXVECTOR3 vPosDeltaWorld; + D3DXVec3TransformCoord( &vPosDeltaWorld, &vPosDelta, &mCameraRot ); + + // Move the lookAt position + m_vLookAt += vPosDeltaWorld; + if( m_bClipToBoundary ) + ConstrainToBoundary( &m_vLookAt );*/ + + /*m_vEye += vPosDeltaWorld; + if (m_bClipToBoundary) + ConstrainToBoundary(&m_vEye);*/ + + // Update the eye point based on a radius away from the lookAt position + m_vEye = m_vLookAt - vWorldAhead * m_fRadius; + //m_vLookAt = m_vEye + vWorldAhead * m_fRadius; + + // Update the view matrix + D3DXMatrixLookAtLH( &m_mView, &m_vEye, &m_vLookAt, &vWorldUp ); + + D3DXMATRIX mInvView; + D3DXMatrixInverse( &mInvView, NULL, &m_mView ); + mInvView._41 = mInvView._42 = mInvView._43 = 0; + + D3DXMATRIX mModelLastRotInv; + D3DXMatrixInverse( &mModelLastRotInv, NULL, &m_mModelLastRot ); + + // Accumulate the delta of the arcball's rotation in view space. + // Note that per-frame delta rotations could be problematic over long periods of time. + D3DXMATRIX mModelRot; + mModelRot = *m_WorldArcBall.GetRotationMatrix(); + m_mModelRot *= m_mView * mModelLastRotInv * mModelRot * mInvView; + + m_mCameraRotLast = mCameraRot; + m_mModelLastRot = mModelRot; + + // Since we're accumulating delta rotations, we need to orthonormalize + // the matrix to prevent eventual matrix skew + D3DXVECTOR3* pXBasis = ( D3DXVECTOR3* )&m_mModelRot._11; + D3DXVECTOR3* pYBasis = ( D3DXVECTOR3* )&m_mModelRot._21; + D3DXVECTOR3* pZBasis = ( D3DXVECTOR3* )&m_mModelRot._31; + D3DXVec3Normalize( pXBasis, pXBasis ); + D3DXVec3Cross( pYBasis, pZBasis, pXBasis ); + D3DXVec3Normalize( pYBasis, pYBasis ); + D3DXVec3Cross( pZBasis, pXBasis, pYBasis ); + + // Translate the rotation matrix to the same position as the lookAt position + //m_mModelRot._41 = m_vLookAt.x; + //m_mModelRot._42 = m_vLookAt.y; + //m_mModelRot._43 = m_vLookAt.z; + + m_mModelRot._41 = m_vModelCenter.x; + m_mModelRot._42 = m_vModelCenter.y; + m_mModelRot._43 = m_vModelCenter.z; + + // Translate world matrix so its at the center of the model + D3DXMATRIX mTrans; + //D3DXMatrixTranslation( &mTrans, -m_vModelCenter.x, -m_vModelCenter.y, -m_vModelCenter.z ); + D3DXMatrixScaling(&mTrans, -1.0, 1.0, 1.0); + m_mWorld = mTrans * m_mModelRot; +} + +//-------------------------------------------------------------------------------------- +// Reset the camera's position back to the default +//-------------------------------------------------------------------------------------- +VOID CDXModelViewerCamera::Reset() +{ + SetViewParams( &m_vDefaultEye, &m_vDefaultLookAt ); + + D3DXMatrixIdentity( &m_mWorld ); + D3DXMatrixIdentity( &m_mModelRot ); + D3DXMatrixIdentity( &m_mModelLastRot ); + D3DXMatrixIdentity( &m_mCameraRotLast ); + + m_fRadius = m_fDefaultRadius; + m_WorldArcBall.Reset(); + m_ViewArcBall.Reset(); +} + + +//-------------------------------------------------------------------------------------- +// Override for setting the view parameters +//-------------------------------------------------------------------------------------- +void CDXModelViewerCamera::SetViewParams( D3DXVECTOR3* pvEyePt, D3DXVECTOR3* pvLookatPt ) +{ + if( NULL == pvEyePt || NULL == pvLookatPt ) + return; + + m_vDefaultEye = m_vEye = *pvEyePt; + m_vDefaultLookAt = m_vLookAt = *pvLookatPt; + + // Calc the view matrix + D3DXVECTOR3 vUp( 0,1,0 ); + D3DXMatrixLookAtLH( &m_mView, pvEyePt, pvLookatPt, &vUp ); + + D3DXMATRIX mInvView; + D3DXMatrixInverse( &mInvView, NULL, &m_mView ); + + // The axis basis vectors and camera position are stored inside the + // position matrix in the 4 rows of the camera's world matrix. + // To figure out the yaw/pitch of the camera, we just need the Z basis vector + D3DXVECTOR3* pZBasis = ( D3DXVECTOR3* )&mInvView._31; + + m_fCameraYawAngle = atan2f( pZBasis->x, pZBasis->z ); + float fLen = sqrtf( pZBasis->z * pZBasis->z + pZBasis->x * pZBasis->x ); + m_fCameraPitchAngle = -atan2f( pZBasis->y, fLen ); + + // Propogate changes to the member arcball + D3DXQUATERNION quat; + D3DXMATRIXA16 mRotation; + D3DXMatrixLookAtLH( &mRotation, pvEyePt, pvLookatPt, &vUp ); + D3DXMatrixRotationYawPitchRoll(&mRotation, 0, -D3DX_PI/2, 0); + D3DXQuaternionRotationMatrix( &quat, &mRotation ); + m_ViewArcBall.SetQuatNow( quat ); + + // Set the radius according to the distance + D3DXVECTOR3 vEyeToPoint; + D3DXVec3Subtract( &vEyeToPoint, pvLookatPt, pvEyePt ); + SetRadius( D3DXVec3Length( &vEyeToPoint ) ); + + // View information changed. FrameMove should be called. + m_bDragSinceLastUpdate = true; +} + +#endif \ No newline at end of file diff --git a/skee/CDXCamera.h b/skee/CDXCamera.h new file mode 100644 index 0000000..5dbe211 --- /dev/null +++ b/skee/CDXCamera.h @@ -0,0 +1,159 @@ +#ifndef _CDXCAMERA_H_ +#define _CDXCAMERA_H_ +#pragma once + +#ifdef FIXME + +class CDXArcBall +{ +public: + CDXArcBall(); + + // Functions to change behavior + void Reset(); + void SetTranslationRadius( float fRadiusTranslation ) { m_fRadiusTranslation = fRadiusTranslation; } + void SetWindow( int nWidth, int nHeight, float fRadius = 0.9f ) { m_nWidth = nWidth; m_nHeight = nHeight; m_fRadius = fRadius; m_vCenter = D3DXVECTOR2(m_nWidth/2.0f,m_nHeight/2.0f); } + void SetOffset( int nX, int nY ) { m_Offset.x = nX; m_Offset.y = nY; } + + // Call these from client and use GetRotationMatrix() to read new rotation matrix + void OnRotateBegin( int nX, int nY ); // start the rotation (pass current mouse position) + void OnRotate( int nX, int nY ); // continue the rotation (pass current mouse position) + void OnRotateEnd(); // end the rotation + + int GetWidth() const { return m_nWidth; } + int GetHeight() const { return m_nHeight; } + + // Functions to get/set state + const D3DXMATRIX* GetRotationMatrix() { return D3DXMatrixRotationQuaternion(&m_mRotation, &m_qNow); }; + const D3DXMATRIX* GetTranslationMatrix() const { return &m_mTranslation; } + const D3DXMATRIX* GetTranslationDeltaMatrix() const { return &m_mTranslationDelta; } + bool IsBeingDragged() const { return m_bDrag; } + D3DXQUATERNION GetQuatNow() const { return m_qNow; } + void SetQuatNow( D3DXQUATERNION q ) { m_qNow = q; } + + static D3DXQUATERNION WINAPI QuatFromBallPoints( const D3DXVECTOR3& vFrom, const D3DXVECTOR3& vTo ); + + +protected: + D3DXMATRIXA16 m_mRotation; // Matrix for arc ball's orientation + D3DXMATRIXA16 m_mTranslation; // Matrix for arc ball's position + D3DXMATRIXA16 m_mTranslationDelta; // Matrix for arc ball's position + + POINT m_Offset; // window offset, or upper-left corner of window + int m_nWidth; // arc ball's window width + int m_nHeight; // arc ball's window height + D3DXVECTOR2 m_vCenter; // center of arc ball + float m_fRadius; // arc ball's radius in screen coords + float m_fRadiusTranslation; // arc ball's radius for translating the target + + D3DXQUATERNION m_qDown; // Quaternion before button down + D3DXQUATERNION m_qNow; // Composite quaternion for current drag + bool m_bDrag; // Whether user is dragging arc ball + + POINT m_ptLastMouse; // position of last mouse point + D3DXVECTOR3 m_vDownPt; // starting point of rotation arc + D3DXVECTOR3 m_vCurrentPt; // current point of rotation arc + + D3DXVECTOR3 ScreenToVector( float fScreenPtX, float fScreenPtY ); +}; + +class CDXModelViewerCamera +{ +public: + CDXModelViewerCamera(); + + const D3DXMATRIX* GetViewMatrix() const { return &m_mView; } + const D3DXMATRIX* GetProjMatrix() const { return &m_mProj; } + const D3DXVECTOR3* GetEyePt() const { return &m_vEye; } + const D3DXVECTOR3* GetLookAtPt() const { return &m_vLookAt; } + + void Update(); + void Reset(); + void SetViewParams( D3DXVECTOR3* pvEyePt, D3DXVECTOR3* pvLookatPt ); + void SetWindow( int nWidth, int nHeight, float fArcballRadius=0.9f ) { m_WorldArcBall.SetWindow( nWidth, nHeight, fArcballRadius ); m_ViewArcBall.SetWindow( nWidth, nHeight, fArcballRadius ); } + void SetRadius( float fDefaultRadius=5.0f, float fMinRadius=1.0f, float fMaxRadius=FLT_MAX ) { m_fDefaultRadius = m_fRadius = fDefaultRadius; m_fMinRadius = fMinRadius; m_fMaxRadius = fMaxRadius; m_bDragSinceLastUpdate = true; } + void SetModelCenter( D3DXVECTOR3 vModelCenter ) { m_vModelCenter = vModelCenter; } + void SetViewQuat( D3DXQUATERNION q ) { m_ViewArcBall.SetQuatNow( q ); m_bDragSinceLastUpdate = true; } + void SetWorldQuat( D3DXQUATERNION q ) { m_WorldArcBall.SetQuatNow( q ); m_bDragSinceLastUpdate = true; } + void SetProjParams( FLOAT fFOV, FLOAT fAspect, FLOAT fNearPlane, FLOAT fFarPlane ); + //void UpdateMouseDelta(int currentX, int currentY); + void ConstrainToBoundary( D3DXVECTOR3* pV ); + float GetRadius() const { return m_fRadius; } + + int GetWidth() const { return m_WorldArcBall.GetWidth(); } + int GetHeight() const { return m_WorldArcBall.GetHeight(); } + + void OnRotateBegin(int x, int y) { m_WorldArcBall.OnRotateBegin(x, y); } + void OnRotate(int x, int y); + void OnRotateEnd() { m_WorldArcBall.OnRotateEnd(); } + + void OnMoveBegin(int x, int y); + void OnMove(int x, int y); + void OnMoveEnd(); + + void SetPanSpeed(float speed) { m_fPanMultiplier = speed; }; + + // Functions to get state + const D3DXMATRIX* GetWorldMatrix() const { return &m_mWorld; } + void SetWorldMatrix( D3DXMATRIX &mWorld ) { m_mWorld = mWorld; m_bDragSinceLastUpdate = true; } + +protected: + D3DXMATRIX m_mView; // View matrix + D3DXMATRIX m_mProj; // Projection matrix + + //D3DXVECTOR3 m_vKeyboardDirection; // Direction vector of keyboard input + //POINT m_ptLastMousePosition; // Last absolute position of mouse cursor + //D3DXVECTOR2 m_vMouseDelta; // Mouse relative delta smoothed over a few frames + //float m_fFramesToSmoothMouseData; // Number of frames to smooth mouse data over + + D3DXVECTOR3 m_vDefaultEye; // Default camera eye position + D3DXVECTOR3 m_vDefaultLookAt; // Default LookAt position + D3DXVECTOR3 m_vEye; // Camera eye position + D3DXVECTOR3 m_vLookAt; // LookAt position + float m_fCameraYawAngle; // Yaw angle of camera + float m_fCameraPitchAngle; // Pitch angle of camera + + float m_fPanMultiplier; + D3DXVECTOR3 m_vDragLook; + D3DXVECTOR2 m_vDragStart; + D3DXVECTOR2 m_vDragPos; + + //RECT m_rcDrag; // Rectangle within which a drag can be initiated. + D3DXVECTOR3 m_vVelocity; // Velocity of camera + //bool m_bMovementDrag; // If true, then camera movement will slow to a stop otherwise movement is instant + //D3DXVECTOR3 m_vVelocityDrag; // Velocity drag force + //float m_fDragTimer; // Countdown timer to apply drag + //float m_fTotalDragTimeToZero; // Time it takes for velocity to go from full to 0 + //D3DXVECTOR2 m_vRotVelocity; // Velocity of camera + + float m_fFOV; // Field of view + float m_fAspect; // Aspect ratio + float m_fNearPlane; // Near plane + float m_fFarPlane; // Far plane + + //float m_fRotationScaler; // Scaler for rotation + //float m_fMoveScaler; // Scaler for movement + + bool m_bClipToBoundary; // If true, then the camera will be clipped to the boundary + D3DXVECTOR3 m_vMinBoundary; // Min point in clip boundary + D3DXVECTOR3 m_vMaxBoundary; // Max point in clip boundary + + CDXArcBall m_WorldArcBall; + CDXArcBall m_ViewArcBall; + D3DXVECTOR3 m_vModelCenter; + D3DXMATRIX m_mModelLastRot; // Last arcball rotation matrix for model + D3DXMATRIX m_mModelRot; // Rotation matrix of model + D3DXMATRIX m_mWorld; // World matrix of model + + float m_fRadius; // Distance from the camera to model + float m_fDefaultRadius; // Distance from the camera to model + float m_fMinRadius; // Min radius + float m_fMaxRadius; // Max radius + bool m_bDragSinceLastUpdate; // True if mouse drag has happened since last time FrameMove is called. + + D3DXMATRIX m_mCameraRotLast; + +}; +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXEditableMesh.cpp b/skee/CDXEditableMesh.cpp new file mode 100644 index 0000000..98c04b7 --- /dev/null +++ b/skee/CDXEditableMesh.cpp @@ -0,0 +1,125 @@ +#include "CDXEditableMesh.h" +#include "CDXMaterial.h" +#include "CDXShader.h" + +#ifdef FIXME + +CDXEditableMesh::CDXEditableMesh() : CDXMesh() +{ + m_wireframe = true; + m_locked = false; +} + +CDXEditableMesh::~CDXEditableMesh() +{ + m_adjacency.clear(); + m_vertexEdges.clear(); +} + +bool CDXEditableMesh::IsEditable() +{ + return true; +} + +bool CDXEditableMesh::IsLocked() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_locked; +} + +bool CDXEditableMesh::ShowWireframe() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_wireframe; +} +void CDXEditableMesh::SetShowWireframe(bool wf) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + m_wireframe = wf; +} +void CDXEditableMesh::SetLocked(bool l) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + m_locked = l; +} + +void CDXEditableMesh::VisitAdjacencies(CDXMeshIndex i, std::function functor) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + auto it = m_adjacency.find(i); + if (it != m_adjacency.end()) { + for (auto adj : it->second) { + if (functor(adj)) + break; + } + } +} + +void CDXEditableMesh::Render(LPDIRECT3DDEVICE9 pDevice, CDXShader * shader) +{ + pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); + + CDXMesh::Render(pDevice, shader); + + // Render again but in wireframe + if (m_wireframe) { + pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME); + + if (m_material) { + m_material->SetWireframe(true); + CDXMesh::Render(pDevice, shader); + m_material->SetWireframe(false); + } + } +} + +CDXVec3 CDXEditableMesh::CalculateVertexNormal(CDXMeshIndex i) +{ + CDXMeshVert* pVertices = NULL; + CDXMeshIndex* pIndices = NULL; + + D3DXVECTOR3 vNormal(0, 0, 0); + LPDIRECT3DVERTEXBUFFER9 pVB = GetVertexBuffer(); + pVB->Lock(0, 0, (void**)&pVertices, 0); + if (!pVertices) + return vNormal; + + auto it = m_adjacency.find(i); + if (it != m_adjacency.end()) { + for (auto tri : it->second) { + CDXMeshVert * v1 = &pVertices[tri.v1]; + CDXMeshVert * v2 = &pVertices[tri.v2]; + CDXMeshVert * v3 = &pVertices[tri.v3]; + + D3DXVECTOR3 e1 = *(D3DXVECTOR3*)v2 - *(D3DXVECTOR3*)v1; + D3DXVECTOR3 e2 = *(D3DXVECTOR3*)v3 - *(D3DXVECTOR3*)v2; + D3DXVec3Cross(&vNormal, &e1, &e2); + D3DXVec3Normalize(&vNormal, &vNormal); + } + D3DXVec3Normalize(&vNormal, &vNormal); + } + + pVB->Unlock(); + return vNormal; +} + +bool CDXEditableMesh::IsEdgeVertex(CDXMeshIndex i) const +{ + auto it = m_vertexEdges.find(i); + if (it != m_vertexEdges.end()) + return true; + + return false; +} + +#endif \ No newline at end of file diff --git a/skee/CDXEditableMesh.h b/skee/CDXEditableMesh.h new file mode 100644 index 0000000..a06b5e1 --- /dev/null +++ b/skee/CDXEditableMesh.h @@ -0,0 +1,60 @@ +#ifdef FIXME + +#ifndef __CDXEDITABLEMESH__ +#define __CDXEDITABLEMESH__ + +#pragma once + +#include "CDXMesh.h" + +#include +#include +#include +#include +#include +#include + +typedef std::unordered_map CDXMaskMap; +typedef std::pair CDXMaskPair; + +typedef std::unordered_map CDXVectorMap; +typedef std::pair CDXVectorPair; + +typedef std::vector CDXAdjacentList; +typedef std::map CDXAdjacencyMap; + +typedef std::unordered_map CDXEdgeMap; +typedef std::unordered_set CDXVertexEdgeList; + +#define COLOR_UNSELECTED D3DCOLOR_RGBA(255, 255, 255, 255) +#define COLOR_SELECTED D3DCOLOR_RGBA(0, 0, 255, 255) + +class CDXEditableMesh : public CDXMesh +{ +public: + CDXEditableMesh(); + ~CDXEditableMesh(); + + virtual void Render(LPDIRECT3DDEVICE9 pDevice, CDXShader * shader); + virtual bool IsEditable(); + virtual bool IsLocked(); + + bool ShowWireframe(); + void SetShowWireframe(bool wf); + void SetLocked(bool l); + + void VisitAdjacencies(CDXMeshIndex i, std::function functor); + bool IsEdgeVertex(CDXMeshIndex i) const; + + CDXVec3 CalculateVertexNormal(CDXMeshIndex i); + +protected: + CDXAdjacencyMap m_adjacency; + CDXVertexEdgeList m_vertexEdges; + bool m_wireframe; + bool m_locked; +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXEditableScene.cpp b/skee/CDXEditableScene.cpp new file mode 100644 index 0000000..af45ebd --- /dev/null +++ b/skee/CDXEditableScene.cpp @@ -0,0 +1,65 @@ +#include "CDXEditableScene.h" +#include "CDXShader.h" + +#ifdef FIXME + +extern CDXModelViewerCamera g_Camera; +extern CDXUndoStack g_undoStack; + +CDXEditableScene::CDXEditableScene() : CDXScene() +{ + m_currentBrush = CDXBrush::kBrushType_None; +} + +void CDXEditableScene::CreateBrushes() +{ + m_brushes.push_back(new CDXMaskAddBrush); + m_brushes.push_back(new CDXMaskSubtractBrush); + m_brushes.push_back(new CDXInflateBrush); + m_brushes.push_back(new CDXDeflateBrush); + m_brushes.push_back(new CDXSmoothBrush); + m_brushes.push_back(new CDXMoveBrush); +} + +void CDXEditableScene::ReleaseBrushes() +{ + for (auto brush : m_brushes) + delete brush; + + m_brushes.clear(); +} + +void CDXEditableScene::Setup(LPDIRECT3DDEVICE9 pDevice) +{ + CreateBrushes(); + CDXScene::Setup(pDevice); +} + +void CDXEditableScene::Release() +{ + CDXScene::Release(); + ReleaseBrushes(); + g_undoStack.Release(); +} + +CDXBrush * CDXEditableScene::GetBrush(CDXBrush::BrushType brushType) +{ + for (auto brush : m_brushes) { + if (brush->GetType() == brushType) + return brush; + } + + return NULL; +} + +CDXBrush * CDXEditableScene::GetCurrentBrush() +{ + return GetBrush(m_currentBrush); +} + +void CDXEditableScene::SetCurrentBrush(CDXBrush::BrushType brushType) +{ + m_currentBrush = brushType; +} + +#endif \ No newline at end of file diff --git a/skee/CDXEditableScene.h b/skee/CDXEditableScene.h new file mode 100644 index 0000000..199736a --- /dev/null +++ b/skee/CDXEditableScene.h @@ -0,0 +1,35 @@ +#ifdef FIXME + +#ifndef __CDXEDITABLESCENE__ +#define __CDXEDITABLESCENE__ + +#pragma once + +#include "CDXScene.h" + +typedef std::vector CDXBrushList; + +class CDXEditableScene : public CDXScene +{ +public: + CDXEditableScene(); + + virtual void Setup(LPDIRECT3DDEVICE9 pDevice); + virtual void Release(); + + virtual void CreateBrushes(); + virtual void ReleaseBrushes(); + + CDXBrushList & GetBrushes() { return m_brushes; } + CDXBrush * GetBrush(CDXBrush::BrushType brushType); + CDXBrush * GetCurrentBrush(); + void SetCurrentBrush(CDXBrush::BrushType brushType); + +protected: + CDXBrush::BrushType m_currentBrush; + CDXBrushList m_brushes; +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXMaterial.cpp b/skee/CDXMaterial.cpp new file mode 100644 index 0000000..3b3d2c1 --- /dev/null +++ b/skee/CDXMaterial.cpp @@ -0,0 +1,174 @@ +#include "CDXMaterial.h" + +#ifdef FIXME + +CDXMaterial::CDXMaterial() +{ + m_diffuseTexture = NULL; + m_specularColor = D3DXVECTOR3(1.0f, 1.0f, 1.0f); + m_diffuseColor = D3DXVECTOR3(1.0f, 1.0f, 1.0f); + m_ambientColor = D3DXVECTOR3(1.0f, 1.0f, 1.0f); + m_alphaThreshold = 0; + m_shaderFlags1 = 0; + m_shaderFlags2 = 0; + SetAlphaBlending(false); + SetSrcBlendMode(ALPHA_SRCALPHA); + SetDestBlendMode(ALPHA_INVSRCALPHA); + SetTestMode(TEST_ALWAYS); + m_wireframe = false; +} + +CDXMaterial::~CDXMaterial() +{ + +} + +void CDXMaterial::Release() +{ + SetDiffuseTexture(NULL); +} + +void CDXMaterial::SetDiffuseTexture(LPDIRECT3DBASETEXTURE9 texture) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + if(m_diffuseTexture) + m_diffuseTexture->Release(); + m_diffuseTexture = texture; + if(m_diffuseTexture) + m_diffuseTexture->AddRef(); +} + +void CDXMaterial::SetDiffuseColor(D3DXVECTOR3 color) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + m_diffuseColor = color; +} +void CDXMaterial::SetSpecularColor(D3DXVECTOR3 color) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + m_specularColor = color; +} +void CDXMaterial::SetAmbientColor(D3DXVECTOR3 color) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + m_ambientColor = color; +} +void CDXMaterial::SetWireframeColor(D3DXVECTOR3 color) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + m_wireframeColor = color; +} + +D3DXVECTOR3 & CDXMaterial::GetDiffuseColor() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_diffuseColor; +} +D3DXVECTOR3 & CDXMaterial::GetSpecularColor() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_specularColor; +} +D3DXVECTOR3 & CDXMaterial::GetAmbientColor() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_ambientColor; +} +D3DXVECTOR3 & CDXMaterial::GetWireframeColor() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_wireframeColor; +} + +void CDXMaterial::SetShaderFlags1(UInt32 flags) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + m_shaderFlags1 = flags; +} +void CDXMaterial::SetShaderFlags2(UInt32 flags) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + m_shaderFlags2 = flags; +} + +void CDXMaterial::SetFlags(UInt16 flags) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + m_uFlags = flags; +} + +void CDXMaterial::SetAlphaBlending(bool bAlpha) +{ + SetBit(bAlpha, ALPHA_BLEND_MASK); +} + +bool CDXMaterial::GetAlphaBlending() const +{ + return GetBit(ALPHA_BLEND_MASK); +} + +void CDXMaterial::SetSrcBlendMode(AlphaFunction eSrcBlend) +{ + SetField((UInt16)(eSrcBlend), SRC_BLEND_MASK, SRC_BLEND_POS); +} + +CDXMaterial::AlphaFunction CDXMaterial::GetSrcBlendMode() const +{ + return (CDXMaterial::AlphaFunction)GetField(SRC_BLEND_MASK, SRC_BLEND_POS); +} + +void CDXMaterial::SetDestBlendMode(AlphaFunction eDestBlend) +{ + SetField((UInt16)(eDestBlend), DEST_BLEND_MASK, DEST_BLEND_POS); +} + +CDXMaterial::AlphaFunction CDXMaterial::GetDestBlendMode() const +{ + return (CDXMaterial::AlphaFunction)GetField(DEST_BLEND_MASK, DEST_BLEND_POS); +} + +void CDXMaterial::SetAlphaTesting(bool bAlpha) +{ + SetBit(bAlpha, TEST_ENABLE_MASK); +} +//--------------------------------------------------------------------------- +bool CDXMaterial::GetAlphaTesting() const +{ + return GetBit(TEST_ENABLE_MASK); +} +//--------------------------------------------------------------------------- +void CDXMaterial::SetTestMode(TestFunction eTestFunc) +{ + SetField((UInt16)(eTestFunc), TEST_FUNC_MASK, TEST_FUNC_POS); +} +//--------------------------------------------------------------------------- +CDXMaterial::TestFunction CDXMaterial::GetTestMode() const +{ + return (CDXMaterial::TestFunction) GetField(TEST_FUNC_MASK, TEST_FUNC_POS); +} + +#endif \ No newline at end of file diff --git a/skee/CDXMaterial.h b/skee/CDXMaterial.h new file mode 100644 index 0000000..7d66f44 --- /dev/null +++ b/skee/CDXMaterial.h @@ -0,0 +1,173 @@ +#ifdef FIXME + +#ifndef __CDXMATERIAL__ +#define __CDXMATERIAL__ + +#pragma once + +#include + +#define DeclareFlags(type) \ + private: \ + type m_uFlags; \ + void SetField(type uVal, type uMask, type uPos) \ + { \ + m_uFlags = (m_uFlags & ~uMask) | (uVal << uPos); \ + } \ + type GetField(type uMask, type uPos) const \ + { \ + return (m_uFlags & uMask) >> uPos; \ + } \ + void SetBit(bool bVal, type uMask) \ + { \ + if (bVal) \ + { \ + m_uFlags |= uMask; \ + } \ + else \ + { \ + m_uFlags &= ~uMask; \ + } \ + }\ + bool GetBit(type uMask) const \ + { \ + return (m_uFlags & uMask) != 0; \ + } + +static UInt32 mappedAlphaFunctions[] = { + D3DBLEND_ONE, + D3DBLEND_ZERO, + D3DBLEND_SRCCOLOR, + D3DBLEND_INVSRCCOLOR, + D3DBLEND_DESTCOLOR, + D3DBLEND_INVDESTCOLOR, + D3DBLEND_SRCALPHA, + D3DBLEND_INVSRCALPHA, + D3DBLEND_DESTALPHA, + D3DBLEND_INVDESTALPHA, + D3DBLEND_SRCALPHASAT +}; + +static UInt32 mappedTestFunctions[] = { + D3DCMP_ALWAYS, + D3DCMP_LESS, + D3DCMP_EQUAL, + D3DCMP_LESSEQUAL, + D3DCMP_GREATER, + D3DCMP_NOTEQUAL, + D3DCMP_GREATEREQUAL, + D3DCMP_NEVER, +}; + +class CDXMaterial +{ +public: + CDXMaterial(); + ~CDXMaterial(); + + void Release(); + + void SetDiffuseTexture(LPDIRECT3DBASETEXTURE9 texture); + LPDIRECT3DBASETEXTURE9 GetDiffuseTexture() const { return m_diffuseTexture; } + + void SetDiffuseColor(D3DXVECTOR3 color); + void SetSpecularColor(D3DXVECTOR3 color); + void SetAmbientColor(D3DXVECTOR3 color); + void SetWireframeColor(D3DXVECTOR3 color); + + D3DXVECTOR3 & GetDiffuseColor(); + D3DXVECTOR3 & GetSpecularColor(); + D3DXVECTOR3 & GetAmbientColor(); + D3DXVECTOR3 & GetWireframeColor(); + + UInt32 GetShaderFlags1() const { return m_shaderFlags1; } + UInt32 GetShaderFlags2() const { return m_shaderFlags2; } + + void SetShaderFlags1(UInt32 flags); + void SetShaderFlags2(UInt32 flags); + + void SetFlags(UInt16 flags); + + enum AlphaFunction + { + ALPHA_ONE, + ALPHA_ZERO, + ALPHA_SRCCOLOR, + ALPHA_INVSRCCOLOR, + ALPHA_DESTCOLOR, + ALPHA_INVDESTCOLOR, + ALPHA_SRCALPHA, + ALPHA_INVSRCALPHA, + ALPHA_DESTALPHA, + ALPHA_INVDESTALPHA, + ALPHA_SRCALPHASAT, + ALPHA_MAX_MODES + }; + + enum + { + ALPHA_BLEND_MASK = 0x0001, + SRC_BLEND_MASK = 0x001e, + SRC_BLEND_POS = 1, + DEST_BLEND_MASK = 0x01e0, + DEST_BLEND_POS = 5, + TEST_ENABLE_MASK = 0x0200, + TEST_FUNC_MASK = 0x1c00, + TEST_FUNC_POS = 10, + ALPHA_NOSORTER_MASK = 0x2000 + }; + + enum TestFunction + { + TEST_ALWAYS, + TEST_LESS, + TEST_EQUAL, + TEST_LESSEQUAL, + TEST_GREATER, + TEST_NOTEQUAL, + TEST_GREATEREQUAL, + TEST_NEVER, + TEST_MAX_MODES + }; + + void SetAlphaBlending(bool bAlpha); + bool GetAlphaBlending() const; + + void SetSrcBlendMode(AlphaFunction eSrcBlend); + AlphaFunction GetSrcBlendMode() const; + + void SetDestBlendMode(AlphaFunction eDestBlend); + AlphaFunction GetDestBlendMode() const; + + void SetAlphaTesting(bool bAlpha); + bool GetAlphaTesting() const; + + void SetTestMode(TestFunction eTestFunc); + TestFunction GetTestMode() const; + + void SetAlphaThreshold(UInt8 thresh) { m_alphaThreshold = thresh; } + UInt8 GetAlphaThreshold() const { return m_alphaThreshold; } + + bool IsWireframe() const { return m_wireframe; } + void SetWireframe(bool w) { m_wireframe = w; } + +protected: + LPDIRECT3DBASETEXTURE9 m_diffuseTexture; + + D3DXVECTOR3 m_specularColor; + D3DXVECTOR3 m_diffuseColor; + D3DXVECTOR3 m_ambientColor; + D3DXVECTOR3 m_wireframeColor; + DeclareFlags(UInt16); + UInt32 m_shaderFlags1; + UInt32 m_shaderFlags2; + UInt8 m_alphaThreshold; + bool m_wireframe; +#ifdef CDX_MUTEX + std::mutex m_mutex; +#endif +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXMesh.cpp b/skee/CDXMesh.cpp new file mode 100644 index 0000000..a266909 --- /dev/null +++ b/skee/CDXMesh.cpp @@ -0,0 +1,326 @@ +#include "CDXMesh.h" +#include "CDXShader.h" +#include "CDXCamera.h" +#include "CDXMaterial.h" +#include "CDXPicker.h" + +#ifdef FIXME + +CDXMesh::CDXMesh() +{ + m_vertexBuffer = NULL; + m_vertCount = 0; + m_indexBuffer = NULL; + m_primitiveCount = 0; + m_visible = true; + m_material = NULL; + m_primitiveType = D3DPT_TRIANGLELIST; + + D3DXMatrixIdentity(&m_transform); +} + +CDXMesh::~CDXMesh() +{ + +} + +void CDXMesh::Release() +{ + m_vertCount = 0; + m_primitiveCount = 0; + m_visible = false; + if(m_vertexBuffer) { + m_vertexBuffer->Release(); + m_vertexBuffer = NULL; + } + if(m_indexBuffer) { + m_indexBuffer->Release(); + m_indexBuffer = NULL; + } + if(m_material) { + m_material->Release(); + delete m_material; + m_material = NULL; + } +} + +void CDXMesh::SetMaterial(CDXMaterial * material) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + m_material = material; +} + +CDXMaterial * CDXMesh::GetMaterial() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_material; +} + +void CDXMesh::SetVisible(bool visible) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + m_visible = visible; +} +bool CDXMesh::IsVisible() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_visible; +} + +LPDIRECT3DVERTEXBUFFER9 CDXMesh::GetVertexBuffer() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_vertexBuffer; +} +LPDIRECT3DINDEXBUFFER9 CDXMesh::GetIndexBuffer() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_indexBuffer; +} + +UInt32 CDXMesh::GetPrimitiveCount() +{ + return m_primitiveCount; +} +UInt32 CDXMesh::GetVertexCount() +{ + return m_vertCount; +} + +D3DXVECTOR3 CalculateFaceNormal(UInt32 f, CDXMeshIndex * faces, CDXMeshVert * vertices) +{ + D3DXVECTOR3 vNormal(0, 0, 0); + CDXMeshVert * v1 = &vertices[faces[f]]; + CDXMeshVert * v2 = &vertices[faces[f + 1]]; + CDXMeshVert * v3 = &vertices[faces[f + 2]]; + + D3DXVECTOR3 e1 = *(D3DXVECTOR3*)v2 - *(D3DXVECTOR3*)v1; + D3DXVECTOR3 e2 = *(D3DXVECTOR3*)v3 - *(D3DXVECTOR3*)v2; + + D3DXVec3Cross(&vNormal, &e1, &e2); + D3DXVec3Normalize(&vNormal, &vNormal); + return vNormal; +} + +bool IntersectSphere(float radius, float & dist, CDXVec3 & center, CDXVec3 & rayOrigin, CDXVec3 & rayDir) +{ + //FLOAT t0 = -1, t1 = -1; // solutions for t if the ray intersects + + CDXVec3 L = center - rayOrigin; + float tca = D3DXVec3Dot(&L, &rayDir); + if (tca < 0) return false; + float d2 = D3DXVec3Dot(&L, &L) - tca * tca; + if (d2 > radius) return false; + float thc = sqrt(radius - d2); + //t0 = tca - thc; + //t1 = tca + thc; + dist = d2; + return true; +} + +bool IntersectTriangle( const CDXVec3& orig, const CDXVec3& dir, CDXVec3& v0, CDXVec3& v1, CDXVec3& v2, float* t, float* u, float* v ) +{ + // Find vectors for two edges sharing vert0 + CDXVec3 edge1 = v1 - v0; + CDXVec3 edge2 = v2 - v0; + + // Begin calculating determinant - also used to calculate U parameter + CDXVec3 pvec; + D3DXVec3Cross(&pvec, &dir, &edge2); + + // If determinant is near zero, ray lies in plane of triangle + float det = D3DXVec3Dot(&edge1, &pvec); + + CDXVec3 tvec; + if(det > 0) { + tvec = orig - v0; + } else { + tvec = v0 - orig; + det = -det; + } + + if(det < 0.0001f) + return false; + + // Calculate U parameter and test bounds + *u = D3DXVec3Dot(&tvec, &pvec); + if(*u < 0.0f || *u > det) + return false; + + // Prepare to test V parameter + CDXVec3 qvec; + D3DXVec3Cross(&qvec, &tvec, &edge1); + + // Calculate V parameter and test bounds + *v = D3DXVec3Dot(&dir, &qvec); + if(*v < 0.0f || *u + *v > det) + return false; + + // Calculate t, scale parameters, ray intersects triangle + *t = D3DXVec3Dot(&edge2, &qvec); + float fInvDet = 1.0f / det; + *t *= fInvDet; + *u *= fInvDet; + *v *= fInvDet; + + return true; +} + +bool CDXMesh::Pick(CDXRayInfo & rayInfo, CDXPickInfo & pickInfo) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + CDXMeshVert* pVertices = LockVertices(); + CDXMeshIndex* pIndices = LockIndices(); + + if (!pVertices || !pIndices) + return false; + + float hitDist = FLT_MAX; + CDXVec3 hitNormal(0, 0, 0); + + // Edges = Face * 3 + for(UInt32 e = 0; e < m_primitiveCount * 3; e += 3) + { + CDXVec3 v0 = pVertices[pIndices[e + 0]].Position; + CDXVec3 v1 = pVertices[pIndices[e + 1]].Position; + CDXVec3 v2 = pVertices[pIndices[e + 2]].Position; + + // Calculate the norm of the face + CDXVec3 fNormal(0,0,0); + CDXVec3 f1 = v1 - v0; + CDXVec3 f2 = v2 - v1; + D3DXVec3Cross(&fNormal, &f1, &f2); + D3DXVec3Normalize(&fNormal, &fNormal); + + // Normalize the direction, just in case + CDXVec3 vDir = rayInfo.direction; + D3DXVec3Normalize(&vDir, &vDir); + + // Skip faces that are in the same direction as the ray + if(D3DXVec3Dot(&vDir, &fNormal) >= 0) + continue; + + // Skip face that doesn't intersect with the ray + float fDist = -1; + float fBary1 = 0; + float fBary2 = 0; + if (!IntersectTriangle(rayInfo.origin, rayInfo.direction, v0, v1, v2, &fDist, &fBary1, &fBary2)) + continue; + + if (fDist < hitDist) { + hitDist = fDist; + hitNormal = fNormal; + } + } + + pickInfo.ray = rayInfo; + pickInfo.dist = hitDist; + + if (hitDist != FLT_MAX) { + CDXVec3 vHit = rayInfo.origin + rayInfo.direction * hitDist; + pickInfo.origin = vHit; + pickInfo.normal = hitNormal; + pickInfo.isHit = true; + } + else { + pickInfo.origin = CDXVec3(0, 0, 0); + pickInfo.normal = CDXVec3(0, 0, 0); + pickInfo.isHit = false; + } + + UnlockVertices(); + UnlockIndices(); + return pickInfo.isHit; +} + +CDXMeshVert* CDXMesh::LockVertices() +{ + CDXMeshVert* pVertices = NULL; + if (!m_vertexBuffer) + return NULL; + + if (FAILED(m_vertexBuffer->Lock(0, 0, (void**)&pVertices, D3DLOCK_DISCARD))) + return NULL; + + return pVertices; +} + +CDXMeshIndex* CDXMesh::LockIndices() +{ + CDXMeshIndex* pIndices = NULL; + if (!m_indexBuffer) + return NULL; + + if (FAILED(m_indexBuffer->Lock(0, 0, (void**)&pIndices, D3DLOCK_DISCARD))) + return NULL; + + return pIndices; +} + +void CDXMesh::UnlockVertices() +{ + m_vertexBuffer->Unlock(); +} +void CDXMesh::UnlockIndices() +{ + m_indexBuffer->Unlock(); +} + +void CDXMesh::Render(LPDIRECT3DDEVICE9 pDevice, CDXShader * shader) +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + ID3DXEffect * effect = shader->GetEffect(); + effect->SetMatrix(shader->m_hTransform, &m_transform); + + if (m_material && m_material->IsWireframe()) { + effect->SetTechnique(effect->GetTechniqueByName("Wireframe")); + effect->SetTexture(shader->m_hTexture, NULL); + } else if (m_material && m_material->GetDiffuseTexture()) { + effect->SetTechnique(effect->GetTechniqueByName("TexturedSpecular")); + effect->SetTexture(shader->m_hTexture, m_material->GetDiffuseTexture()); + } else { + effect->SetTechnique(effect->GetTechniqueByName("Specular")); + effect->SetTexture(shader->m_hTexture, NULL); + } + + effect->CommitChanges(); + + UINT iPass, cPasses; + effect->Begin(&cPasses, 0); + for (iPass = 0; iPass < cPasses; iPass++) + { + effect->BeginPass(iPass); + Pass(pDevice, iPass, shader); + effect->EndPass(); + } + + effect->End(); +} + +void CDXMesh::Pass(LPDIRECT3DDEVICE9 pDevice, UInt32 iPass, CDXShader * shader) +{ + if (!m_vertexBuffer || !m_indexBuffer) + return; + + pDevice->SetStreamSource(0, m_vertexBuffer, 0, sizeof(CDXMeshVert)); + pDevice->SetIndices(m_indexBuffer); + pDevice->DrawIndexedPrimitive((D3DPRIMITIVETYPE)m_primitiveType, 0, 0, m_vertCount, 0, m_primitiveCount); +} + +#endif \ No newline at end of file diff --git a/skee/CDXMesh.h b/skee/CDXMesh.h new file mode 100644 index 0000000..9d2c49f --- /dev/null +++ b/skee/CDXMesh.h @@ -0,0 +1,164 @@ +#pragma once + +#ifdef FIXME + +#ifdef CDX_MUTEX +#include +#endif + +#include + +typedef D3DXMATRIX CDXMatrix; +typedef unsigned short CDXMeshIndex; +typedef D3DXVECTOR3 CDXVec3; +typedef D3DXVECTOR2 CDXVec2; +typedef D3DCOLOR CDXColor; + +typedef std::set CDXMeshIndexSet; +typedef std::map CDXHitIndexMap; + +struct CDXMeshEdge +{ + CDXMeshIndex p1; + CDXMeshIndex p2; + + CDXMeshEdge(CDXMeshIndex _p1, CDXMeshIndex _p2) + { + p1 = _p1; + p2 = _p2; + } +}; + +struct CDXMeshFace +{ + CDXMeshIndex v1; + CDXMeshIndex v2; + CDXMeshIndex v3; + + CDXMeshFace(CDXMeshIndex _v1, CDXMeshIndex _v2, CDXMeshIndex _v3) + { + v1 = _v1; + v2 = _v2; + v3 = _v3; + } +}; + +namespace std { + template<> struct hash < CDXMeshEdge > + { + std::size_t operator() (const CDXMeshEdge & t) const + { + return ((t.p2 << 16) | (t.p1 & 0xFFFF)); + } + + }; + + template <> struct equal_to < CDXMeshEdge > + { + bool operator() (const CDXMeshEdge& t1, const CDXMeshEdge& t2) const + { + return ((t1.p1 == t2.p1) && (t1.p2 == t2.p2)); + } + }; + + template <> struct hash < CDXMeshFace > { + std::size_t operator() (const CDXMeshFace& t) const { + char* d = (char*)&t; + size_t len = sizeof(CDXMeshFace); + size_t hash, i; + for (hash = i = 0; i < len; ++i) + { + hash += d[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; + } + }; + template <> struct equal_to < CDXMeshFace > + { + bool operator() (const CDXMeshFace& t1, const CDXMeshFace& t2) const + { + return ((t1.v1 == t2.v1) && (t1.v2 == t2.v2) && (t1.v3 == t2.v3)); + } + }; +} + +struct CDXMeshVert +{ + CDXVec3 Position; + CDXVec3 Normal; + CDXVec2 Tex; + CDXColor Color; +}; + +const static D3DVERTEXELEMENT9 VertexDecl[6] = +{ + { 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 }, + { 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 }, + { 0, 24, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 }, + { 0, 32, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 }, + D3DDECL_END() +}; + +class CDXMaterial; +class CDXPicker; +class CDXShader; +class CDXEditableMesh; +class CDXRayInfo; +class CDXPickInfo; + +class CDXMesh +{ +public: + CDXMesh(); + ~CDXMesh(); + + virtual void Render(LPDIRECT3DDEVICE9 pDevice, CDXShader * shader); + virtual void Pass(LPDIRECT3DDEVICE9 pDevice, UInt32 iPass, CDXShader * shader); + virtual void Release(); + virtual bool Pick(CDXRayInfo & rayInfo, CDXPickInfo & pickInfo); + virtual bool IsEditable() { return false; } + virtual bool IsLocked() { return true; } + + void SetVisible(bool visible); + bool IsVisible(); + + void SetMaterial(CDXMaterial * material); + CDXMaterial * GetMaterial(); + + CDXMeshVert* LockVertices(); + CDXMeshIndex* LockIndices(); + + void UnlockVertices(); + void UnlockIndices(); + + LPDIRECT3DVERTEXBUFFER9 GetVertexBuffer(); + LPDIRECT3DINDEXBUFFER9 GetIndexBuffer(); + + UInt32 GetPrimitiveCount(); + UInt32 GetVertexCount(); + +protected: + bool m_visible; + LPDIRECT3DVERTEXBUFFER9 m_vertexBuffer; + UInt32 m_vertCount; + LPDIRECT3DINDEXBUFFER9 m_indexBuffer; + UInt32 m_primitiveCount; + UInt8 m_primitiveType; + CDXMaterial * m_material; + CDXMatrix m_transform; + +#ifdef CDX_MUTEX + std::mutex m_mutex; +#endif +}; + +D3DXVECTOR3 CalculateFaceNormal(UInt32 f, CDXMeshIndex * faces, CDXMeshVert * vertices); +bool IntersectSphere(float radius, float & dist, CDXVec3 & center, CDXVec3 & rayOrigin, CDXVec3 & rayDir); +bool IntersectTriangle(const CDXVec3& orig, const CDXVec3& dir, CDXVec3& v0, CDXVec3& v1, CDXVec3& v2, float* t, float* u, float* v); + +#endif \ No newline at end of file diff --git a/skee/CDXNifBrush.cpp b/skee/CDXNifBrush.cpp new file mode 100644 index 0000000..1218ae7 --- /dev/null +++ b/skee/CDXNifBrush.cpp @@ -0,0 +1,36 @@ +#include "CDXNifBrush.h" +#include "CDXNifCommands.h" + +#ifdef FIXME + +CDXStrokePtr CDXNifMaskAddBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(brush, mesh); +} + +CDXStrokePtr CDXNifMaskSubtractBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(brush, mesh); +} + +CDXStrokePtr CDXNifInflateBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(brush, mesh); +} + +CDXStrokePtr CDXNifDeflateBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(brush, mesh); +} + +CDXStrokePtr CDXNifSmoothBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(brush, mesh); +} + +CDXStrokePtr CDXNifMoveBrush::CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + return std::make_shared(brush, mesh); +} + +#endif \ No newline at end of file diff --git a/skee/CDXNifBrush.h b/skee/CDXNifBrush.h new file mode 100644 index 0000000..4061448 --- /dev/null +++ b/skee/CDXNifBrush.h @@ -0,0 +1,54 @@ +#ifdef FIXME + +#ifndef __CDXNIFBRUSH__ +#define __CDXNIFBRUSH__ + +#pragma once + +#include "CDXBrush.h" + +class CDXNifMaskAddBrush : public CDXMaskAddBrush +{ +public: + CDXNifMaskAddBrush() : CDXMaskAddBrush() { } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); +}; + +class CDXNifMaskSubtractBrush : public CDXMaskSubtractBrush +{ +public: + CDXNifMaskSubtractBrush() : CDXMaskSubtractBrush() { } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); +}; + +class CDXNifInflateBrush : public CDXInflateBrush +{ +public: + CDXNifInflateBrush() : CDXInflateBrush() { } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); +}; + +class CDXNifDeflateBrush : public CDXDeflateBrush +{ +public: + CDXNifDeflateBrush() : CDXDeflateBrush() { } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); +}; + +class CDXNifSmoothBrush : public CDXSmoothBrush +{ +public: + CDXNifSmoothBrush() : CDXSmoothBrush() { } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); +}; + +class CDXNifMoveBrush : public CDXMoveBrush +{ +public: + CDXNifMoveBrush() : CDXMoveBrush() { } + virtual CDXStrokePtr CreateStroke(CDXBrush * brush, CDXEditableMesh * mesh); +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXNifCommands.cpp b/skee/CDXNifCommands.cpp new file mode 100644 index 0000000..9fddad9 --- /dev/null +++ b/skee/CDXNifCommands.cpp @@ -0,0 +1,524 @@ +#ifdef FIXME + +#include "CDXNifCommands.h" +#include "CDXNifMesh.h" +#include "CDXNifScene.h" + +#include "skse64/GameData.h" +#include "skse64/GameReferences.h" +#include "skse64/GameRTTI.h" +#include "skse64/GameMenus.h" + +#include "skse64/NiGeometry.h" +#include "skse64/NiExtraData.h" + +#include "MorphHandler.h" +#include "FileUtils.h" +#include "NifUtils.h" + +#include "skse64/ScaleformCallbacks.h" + +#include "skse64/PluginAPI.h" + +extern MorphHandler g_morphHandler; +extern SKSETaskInterface * g_task; +extern CDXNifScene g_World; + +void ApplyMorphData(NiGeometry * geometry, CDXVectorMap & vectorMap, float multiplier) +{ + Actor * actor = g_World.GetWorkingActor(); + TESNPC * npc = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + + // Create mapped extra vertex data for NPC + auto sculptTarget = g_morphHandler.GetSculptTarget(npc, true); + if (sculptTarget) { + std::string headPartName = geometry->m_name; + BGSHeadPart * headPart = GetHeadPartByName(headPartName); + if (headPart) { + auto sculptHost = sculptTarget->GetSculptHost(SculptData::GetHostByPart(headPart), true); + if (sculptHost) { + BSFaceGenBaseMorphExtraData * morphData = (BSFaceGenBaseMorphExtraData *)geometry->GetExtraData("FOD"); + if (morphData) { + for (auto it : vectorMap) { + // Store it in the NPC mapped data + NiPoint3 temp = *(NiPoint3*)&it.second; + temp *= multiplier; + + sculptHost->add(std::make_pair(it.first, temp)); + + // Write it to FaceGen + morphData->vertexData[it.first] += temp; + } + + // Update FaceGen + if (g_task) + g_task->AddTask(new CRGNTaskUpdateModel(geometry)); + } + } + } + } +} + +void AddStrokeCommand(CDXStroke * stroke, NiGeometry * geometry, SInt32 id) +{ + if (g_task) + g_task->AddUITask(new CRGNUITaskAddStroke(stroke, geometry, id)); +} + +void CDXNifInflateStroke::Undo() +{ + CDXInflateStroke::Undo(); + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, -1.0); + } +} + +void CDXNifInflateStroke::Redo() +{ + CDXInflateStroke::Redo(); + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + } +} + +void CDXNifDeflateStroke::Undo() +{ + CDXDeflateStroke::Undo(); + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, -1.0); + } +} + +void CDXNifDeflateStroke::Redo() +{ + CDXDeflateStroke::Redo(); + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + } +} + +void CDXNifSmoothStroke::Undo() +{ + CDXSmoothStroke::Undo(); + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, -1.0); + } +} +void CDXNifSmoothStroke::Redo() +{ + CDXSmoothStroke::Redo(); + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + } +} + +void CDXNifMoveStroke::Undo() +{ + CDXMoveStroke::Undo(); + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, -1.0); + } +} + +void CDXNifMoveStroke::Redo() +{ + CDXMoveStroke::Redo(); + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + } +} + +void CDXNifInflateStroke::Apply(SInt32 i) +{ + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + AddStrokeCommand(this, geometry, i); + } +} + +void CDXNifDeflateStroke::Apply(SInt32 i) +{ + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + AddStrokeCommand(this, geometry, i); + } +} + +void CDXNifSmoothStroke::Apply(SInt32 i) +{ + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + AddStrokeCommand(this, geometry, i); + } +} + +void CDXNifMoveStroke::Apply(SInt32 i) +{ + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + AddStrokeCommand(this, geometry, i); + } +} + +void CDXNifMaskAddStroke::Apply(SInt32 i) +{ + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + AddStrokeCommand(this, geometry, i); + } +} + +void CDXNifMaskSubtractStroke::Apply(SInt32 i) +{ + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + AddStrokeCommand(this, geometry, i); + } +} + + +void CDXNifResetMask::Apply(SInt32 i) +{ + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + if (g_task) + g_task->AddUITask(new CRGNUITaskStandardCommand(this, geometry, i)); + } +} + +CRGNTaskUpdateModel::CRGNTaskUpdateModel(NiGeometry * geometry) +{ + m_geometry = geometry; + if (m_geometry) + m_geometry->IncRef(); +} + +void CRGNTaskUpdateModel::Run() +{ + if (m_geometry) + UpdateModelFace(m_geometry); +} + +void CRGNTaskUpdateModel::Dispose() +{ + if (m_geometry) + m_geometry->DecRef(); + delete this; +} + +CRGNUITaskAddStroke::CRGNUITaskAddStroke(CDXStroke * stroke, NiGeometry * geometry, SInt32 id) +{ + m_id = id; + m_stroke = stroke; + m_geometry = geometry; + if (m_geometry) + m_geometry->IncRef(); +} + +void CRGNUITaskAddStroke::Dispose() +{ + if (m_geometry) + m_geometry->DecRef(); + delete this; +} + +void CRGNUITaskAddStroke::Run() +{ + IMenu * menu = MenuManager::GetSingleton()->GetMenu(&UIStringHolder::GetSingleton()->raceSexMenu); + if (menu && menu->view) { + GFxValue obj; + menu->view->CreateObject(&obj); + GFxValue commandId; + commandId.SetNumber(m_id); + obj.SetMember("id", &commandId); + GFxValue type; + type.SetNumber(m_stroke->GetUndoType()); + obj.SetMember("type", &type); + GFxValue strokeType; + strokeType.SetNumber(m_stroke->GetStrokeType()); + obj.SetMember("stroke", &strokeType); + GFxValue vertices; + vertices.SetNumber(m_stroke->Length()); + obj.SetMember("vertices", &vertices); + GFxValue mirror; + mirror.SetBool(m_stroke->IsMirror()); + obj.SetMember("mirror", &mirror); + GFxValue partName; + partName.SetString(m_geometry->m_name); + obj.SetMember("part", &partName); + + FxResponseArgsList args; + args.Add(&obj); + InvokeFunction(menu->view, "AddAction", &args); + } +} + +CRGNUITaskStandardCommand::CRGNUITaskStandardCommand(CDXUndoCommand * cmd, NiGeometry * geometry, SInt32 id) +{ + m_id = id; + m_cmd = cmd; + m_geometry = geometry; + if (m_geometry) + m_geometry->IncRef(); +} + +void CRGNUITaskStandardCommand::Dispose() +{ + if (m_geometry) + m_geometry->DecRef(); + delete this; +} + +void CRGNUITaskStandardCommand::Run() +{ + IMenu * menu = MenuManager::GetSingleton()->GetMenu(&UIStringHolder::GetSingleton()->raceSexMenu); + if (menu && menu->view) { + GFxValue obj; + menu->view->CreateObject(&obj); + GFxValue commandId; + commandId.SetNumber(m_id); + obj.SetMember("id", &commandId); + GFxValue type; + type.SetNumber(m_cmd->GetUndoType()); + obj.SetMember("type", &type); + GFxValue partName; + partName.SetString(m_geometry->m_name); + obj.SetMember("part", &partName); + + FxResponseArgsList args; + args.Add(&obj); + InvokeFunction(menu->view, "AddAction", &args); + } +} + +CDXNifResetSculpt::CDXNifResetSculpt(CDXNifMesh * mesh) : CDXUndoCommand() +{ + m_mesh = mesh; + + Actor * actor = g_World.GetWorkingActor(); + TESNPC * npc = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + NiGeometry * geometry = m_mesh->GetNifGeometry(); + + if (geometry) { + // Create mapped extra vertex data for NPC + auto sculptTarget = g_morphHandler.GetSculptTarget(npc, false); + if (sculptTarget) { + std::string headPartName = geometry->m_name; + BGSHeadPart * headPart = GetHeadPartByName(headPartName); + if (headPart) { + auto sculptHost = sculptTarget->GetSculptHost(SculptData::GetHostByPart(headPart), false); + if (sculptHost) { + CDXMeshVert* pVertices = m_mesh->LockVertices(); + for (auto it : *sculptHost) { + // Skip masked vertices + if (pVertices[it.first].Color != COLOR_UNSELECTED) + continue; + // Store it in the NPC mapped data + CDXVec3 temp = *(CDXVec3*)&it.second; + pVertices[it.first].Position -= temp; + m_current.emplace(it.first, -temp); + } + + m_mesh->UnlockVertices(); + } + } + } + } +} + +CDXNifResetSculpt::~CDXNifResetSculpt() +{ + m_current.clear(); +} + +CDXUndoCommand::UndoType CDXNifResetSculpt::GetUndoType() +{ + return kUndoType_ResetSculpt; +} + +void CDXNifResetSculpt::Redo() +{ + CDXMeshVert* pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Do what we have now + for (auto it : m_current) + pVertices[it.first].Position += it.second; + + m_mesh->UnlockVertices(); + + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + } +} + +void CDXNifResetSculpt::Undo() +{ + CDXMeshVert* pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Undo what we did + for (auto it : m_current) + pVertices[it.first].Position -= it.second; + + m_mesh->UnlockVertices(); + + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, -1.0); + } +} + +void CDXNifResetSculpt::Apply(SInt32 i) +{ + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + if (g_task) + g_task->AddUITask(new CRGNUITaskStandardCommand(this, geometry, i)); + } +} + +CDXNifImportGeometry::CDXNifImportGeometry(CDXNifMesh * mesh, NiGeometry * source) : CDXUndoCommand() +{ + m_mesh = mesh; + + Actor * actor = g_World.GetWorkingActor(); + TESNPC * npc = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + NiGeometry * geometry = m_mesh->GetNifGeometry(); + + if (geometry) { + // Create mapped extra vertex data for NPC + auto sculptTarget = g_morphHandler.GetSculptTarget(npc, true); + if (sculptTarget) { + std::string headPartName = geometry->m_name; + BGSHeadPart * headPart = GetHeadPartByName(headPartName); + if (headPart) { + auto sculptHost = sculptTarget->GetSculptHost(SculptData::GetHostByPart(headPart), true); + if (sculptHost) { + // Create differences from source and destination geometry + NiGeometryData * dstData = niptr_cast(geometry->m_spModelData); + NiGeometryData * srcData = niptr_cast(source->m_spModelData); + + NiTransform dstTransform = GetGeometryTransform(geometry); + NiTransform srcTransform = GetGeometryTransform(source); + + if (dstData && srcData && dstData->m_usVertices == srcData->m_usVertices) { + + CDXMeshVert* pVertices = m_mesh->LockVertices(); + + for (UInt32 i = 0; i < srcData->m_usVertices; i++) { + + if (pVertices[i].Color != COLOR_UNSELECTED) + continue; + + NiPoint3 diff = (srcTransform * srcData->m_pkVertex[i]) - (dstTransform * dstData->m_pkVertex[i]); + CDXVec3 temp = *(CDXVec3*)&diff; + + pVertices[i].Position += temp; + + // Store it in the action + m_current.emplace(i, temp); + } + + m_mesh->UnlockVertices(); + } + } + } + } + } +} + +CDXNifImportGeometry::~CDXNifImportGeometry() +{ + m_current.clear(); +} + +CDXUndoCommand::UndoType CDXNifImportGeometry::GetUndoType() +{ + return kUndoType_Import; +} + +void CDXNifImportGeometry::Redo() +{ + CDXMeshVert* pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Do what we have now + for (auto it : m_current) + pVertices[it.first].Position += it.second; + + m_mesh->UnlockVertices(); + + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + } +} + +void CDXNifImportGeometry::Undo() +{ + CDXMeshVert* pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Undo what we did + for (auto it : m_current) + pVertices[it.first].Position -= it.second; + + m_mesh->UnlockVertices(); + + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, -1.0); + } +} + +void CDXNifImportGeometry::Apply(SInt32 i) +{ + CDXNifMesh * nifMesh = static_cast(m_mesh); + NiGeometry * geometry = nifMesh->GetNifGeometry(); + if (geometry) { + ApplyMorphData(geometry, m_current, 1.0); + if (g_task) + g_task->AddUITask(new CRGNUITaskStandardCommand(this, geometry, i)); + } +} + +#endif \ No newline at end of file diff --git a/skee/CDXNifCommands.h b/skee/CDXNifCommands.h new file mode 100644 index 0000000..415606a --- /dev/null +++ b/skee/CDXNifCommands.h @@ -0,0 +1,163 @@ +#ifdef FIXME + +#ifndef __CDXNIFCOMMANDS__ +#define __CDXNIFCOMMANDS__ + +#pragma once + +#include "CDXStroke.h" +#include "CDXResetMask.h" +#include "CDXUndo.h" + +#include "skse64/GameThreads.h" +#include "skse64/Hooks_UI.h" + +class CDXNifMesh; + +class NiGeometry; +class UIDelegate; + +class CDXNifMaskAddStroke : public CDXMaskAddStroke +{ +public: + CDXNifMaskAddStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXMaskAddStroke(brush, mesh) { } + + virtual void Apply(SInt32 i); +}; + +class CDXNifMaskSubtractStroke : public CDXMaskSubtractStroke +{ +public: + CDXNifMaskSubtractStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXMaskSubtractStroke(brush, mesh) { } + + virtual void Apply(SInt32 i); +}; + +class CDXNifInflateStroke : public CDXInflateStroke +{ +public: + CDXNifInflateStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXInflateStroke(brush, mesh) { } + + virtual void Apply(SInt32 i); + virtual void Undo(); + virtual void Redo(); +}; + +class CDXNifDeflateStroke : public CDXDeflateStroke +{ +public: + CDXNifDeflateStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXDeflateStroke(brush, mesh) { } + + virtual void Apply(SInt32 i); + virtual void Undo(); + virtual void Redo(); +}; + +class CDXNifSmoothStroke : public CDXSmoothStroke +{ +public: + CDXNifSmoothStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXSmoothStroke(brush, mesh) { } + + virtual void Apply(SInt32 i); + virtual void Undo(); + virtual void Redo(); +}; + + +class CDXNifMoveStroke : public CDXMoveStroke +{ +public: + CDXNifMoveStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXMoveStroke(brush, mesh) { } + + virtual void Apply(SInt32 i); + virtual void Undo(); + virtual void Redo(); +}; + +class CDXNifResetMask : public CDXResetMask +{ +public: + CDXNifResetMask(CDXMesh * mesh) : CDXResetMask(mesh) { } + + virtual void Apply(SInt32 i); +}; + +class CDXNifResetSculpt : public CDXUndoCommand +{ +public: + CDXNifResetSculpt(CDXNifMesh * mesh); + ~CDXNifResetSculpt(); + + virtual UndoType GetUndoType(); + virtual void Undo(); + virtual void Redo(); + virtual void Apply(SInt32 i); + + UInt32 Length() const { return m_current.size(); } + +private: + CDXNifMesh * m_mesh; + CDXVectorMap m_current; +}; + +class CDXNifImportGeometry : public CDXUndoCommand +{ +public: + CDXNifImportGeometry(CDXNifMesh * mesh, NiGeometry * source); + ~CDXNifImportGeometry(); + + virtual UndoType GetUndoType(); + virtual void Undo(); + virtual void Redo(); + virtual void Apply(SInt32 i); + + UInt32 Length() const { return m_current.size(); } + +private: + CDXNifMesh * m_mesh; + CDXVectorMap m_current; +}; + +class CRGNTaskUpdateModel : public TaskDelegate +{ +public: + CRGNTaskUpdateModel(NiGeometry * geometry); + + virtual void Run(); + virtual void Dispose(); + +private: + NiGeometry * m_geometry; +}; + +class CRGNUITaskAddStroke : public UIDelegate_v1 +{ +public: + CRGNUITaskAddStroke(CDXStroke * stroke, NiGeometry * geometry, SInt32 i); + + virtual void Run(); + virtual void Dispose(); + +private: + CDXStroke * m_stroke; + SInt32 m_id; + NiGeometry * m_geometry; +}; + +class CRGNUITaskStandardCommand : public UIDelegate_v1 +{ +public: + CRGNUITaskStandardCommand(CDXUndoCommand * cmd, NiGeometry * geometry, SInt32 i); + + virtual void Run(); + virtual void Dispose(); + +private: + CDXUndoCommand * m_cmd; + SInt32 m_id; + NiGeometry * m_geometry; +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXNifMesh.cpp b/skee/CDXNifMesh.cpp new file mode 100644 index 0000000..7633e3a --- /dev/null +++ b/skee/CDXNifMesh.cpp @@ -0,0 +1,281 @@ +#ifdef FIXME + +#include "CDXNifMesh.h" +#include "CDXScene.h" +#include "CDXShader.h" + +#include "skse64/NiGeometry.h" +#include "skse64/NiRTTI.h" +#include "skse64/NiExtraData.h" + +#include "NifUtils.h" + +#include +#include +#include +#include +#include + +CDXNifMesh::CDXNifMesh() : CDXEditableMesh() +{ + m_material = NULL; + m_geometry = NULL; + m_morphable = false; +} + +CDXNifMesh::~CDXNifMesh() +{ + if (m_geometry) { + m_geometry->DecRef(); + } +} + +NiGeometry * CDXNifMesh::GetNifGeometry() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_geometry; +} + +bool CDXNifMesh::IsMorphable() +{ +#ifdef CDX_MUTEX + std::lock_guard guard(m_mutex); +#endif + return m_morphable; +} + +CDXNifMesh * CDXNifMesh::Create(LPDIRECT3DDEVICE9 pDevice, NiGeometry * geometry) +{ + UInt32 vertCount = 0; + UInt32 triangleCount = 0; + LPDIRECT3DVERTEXBUFFER9 vertexBuffer = NULL; + LPDIRECT3DINDEXBUFFER9 indexBuffer = NULL; + LPDIRECT3DBASETEXTURE9 diffuseTexture = NULL; + UInt16 alphaFlags = 0; + UInt8 alphaThreshold = 0; + UInt32 shaderFlags1 = 0; + UInt32 shaderFlags2 = 0; + + CDXNifMesh * nifMesh = new CDXNifMesh; + + if (geometry) { + geometry->IncRef(); + nifMesh->m_geometry = geometry; + } + + if (geometry) + { + NiTriBasedGeomData * geometryData = niptr_cast(geometry->m_spModelData); + if (geometryData) + { + NiTriShapeData * triShapeData = ni_cast(geometryData, NiTriShapeData); + NiTriStripsData * triStripsData = ni_cast(geometryData, NiTriStripsData); + if (triShapeData || triStripsData) + { + // Pre-transform + NiTransform localTransform = GetGeometryTransform(geometry); + BSLightingShaderProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if (shaderProperty) { + BSLightingShaderMaterial * material = shaderProperty->material; + if (material) { + NiTexture * diffuse = niptr_cast(material->diffuse); + if (diffuse) { + NiTexture::RendererData * rendererData = diffuse->rendererData; + if (rendererData) { + NiTexture::NiDX9TextureData * dx9RendererData = (NiTexture::NiDX9TextureData *)rendererData; + diffuseTexture = dx9RendererData->texture; + } + } + } + + shaderFlags1 = shaderProperty->shaderFlags1; + shaderFlags2 = shaderProperty->shaderFlags2; + } + + NiAlphaProperty * alphaProperty = niptr_cast(geometry->m_spPropertyState); + if (alphaProperty) { + alphaFlags = alphaProperty->alphaFlags; + alphaThreshold = alphaProperty->alphaThreshold; + } + + vertCount = geometryData->m_usVertices; + triangleCount = geometryData->m_usTriangles; + + nifMesh->m_vertCount = vertCount; + nifMesh->m_primitiveCount = triangleCount; + + BSFaceGenBaseMorphExtraData * morphData = (BSFaceGenBaseMorphExtraData *)geometry->GetExtraData("FOD"); + if (morphData) { + nifMesh->m_morphable = true; + } + + pDevice->CreateVertexBuffer(geometryData->m_usVertices*sizeof(CDXMeshVert), 0, 0, D3DPOOL_MANAGED, &vertexBuffer, NULL); + + CDXMeshVert* pVertices = NULL; + CDXMeshIndex* pIndices = NULL; + + if (triShapeData) + pDevice->CreateIndexBuffer(triangleCount*sizeof(CDXMeshFace), 0, D3DFMT_INDEX16, D3DPOOL_MANAGED, &indexBuffer, NULL); + else if (triStripsData) + pDevice->CreateIndexBuffer(GetStripLengthSum(triStripsData)*sizeof(CDXMeshIndex), 0, D3DFMT_INDEX16, D3DPOOL_MANAGED, &indexBuffer, NULL); + + // lock i_buffer and load the indices into it + indexBuffer->Lock(0, 0, (void**)&pIndices, 0); + if (triShapeData) { + memcpy(pIndices, triShapeData->m_pusTriList, triShapeData->m_uiTriListLength*sizeof(CDXMeshIndex)); + nifMesh->m_primitiveType = D3DPT_TRIANGLELIST; + } + else if (triStripsData) { + memcpy(pIndices, triStripsData->m_pusStripLists, GetStripLengthSum(triStripsData)*sizeof(CDXMeshIndex)); + nifMesh->m_primitiveCount = GetStripLengthSum(triStripsData); + nifMesh->m_primitiveType = D3DPT_TRIANGLESTRIP; + } + nifMesh->m_indexBuffer = indexBuffer; + indexBuffer->Unlock(); + + vertexBuffer->Lock(0, 0, (void**)&pVertices, 0); + for (UInt32 i = 0; i < vertCount; i++) { + NiPoint3 xformed = localTransform * geometryData->m_pkVertex[i]; + NiPoint2 uv = geometryData->m_pkTexture[i]; + pVertices[i].Position = *(D3DXVECTOR3*)&xformed; + D3DXVECTOR3 vNormal(0, 0, 0); + pVertices[i].Normal = vNormal; + pVertices[i].Tex = *(D3DXVECTOR2*)&uv; + pVertices[i].Color = COLOR_UNSELECTED; + + // Build adjacency table + if (nifMesh->m_morphable) { + for (UInt32 f = 0; f < triangleCount; f++) { + if (triShapeData) { + CDXMeshFace * face = (CDXMeshFace *)&pIndices[f * 3]; + if (i == face->v1 || i == face->v2 || i == face->v3) + nifMesh->m_adjacency[i].push_back(*face); + } + else if (triStripsData) { + UInt16 v1 = 0, v2 = 0, v3 = 0; + GetTriangleIndices(triStripsData, f, v1, v2, v3); + if (i == v1 || i == v2 || i == v3) + nifMesh->m_adjacency[i].push_back(CDXMeshFace(v1, v2, v3)); + } + } + } + } + + nifMesh->m_vertexBuffer = vertexBuffer; + + // Don't need edge table if not editable + if (nifMesh->m_morphable) { + CDXEdgeMap edges; + for (UInt32 f = 0; f < triangleCount; f++) { + + if (triShapeData) { + CDXMeshFace * face = (CDXMeshFace *)&pIndices[f * 3]; + auto it = edges.emplace(CDXMeshEdge(min(face->v1, face->v2), max(face->v1, face->v2)), 1); + if (it.second == false) + it.first->second++; + it = edges.emplace(CDXMeshEdge(min(face->v2, face->v3), max(face->v2, face->v3)), 1); + if (it.second == false) + it.first->second++; + it = edges.emplace(CDXMeshEdge(min(face->v3, face->v1), max(face->v3, face->v1)), 1); + if (it.second == false) + it.first->second++; + } + else if (triStripsData) { + UInt16 v1 = 0, v2 = 0, v3 = 0; + GetTriangleIndices(triStripsData, f, v1, v2, v3); + auto it = edges.emplace(CDXMeshEdge(min(v1, v2), max(v1, v2)), 1); + if (it.second == false) + it.first->second++; + it = edges.emplace(CDXMeshEdge(min(v2, v3), max(v2, v3)), 1); + if (it.second == false) + it.first->second++; + it = edges.emplace(CDXMeshEdge(min(v3, v1), max(v3, v1)), 1); + if (it.second == false) + it.first->second++; + } + } + + for (auto e : edges) { + if (e.second == 1) { + nifMesh->m_vertexEdges.insert(e.first.p1); + nifMesh->m_vertexEdges.insert(e.first.p2); + } + } + } + + // Only need vertex normals when it's editable + if (nifMesh->m_morphable) { + for (UInt32 i = 0; i < vertCount; i++) { + // Setup normals + D3DXVECTOR3 vNormal(0, 0, 0); + if (!geometryData->m_pkNormal) + vNormal = nifMesh->CalculateVertexNormal(i); + else + vNormal = *(D3DXVECTOR3*)&geometryData->m_pkNormal[i]; + + pVertices[i].Normal = vNormal; + } + } + + vertexBuffer->Unlock(); + + CDXMaterial * material = new CDXMaterial; + material->SetDiffuseTexture(diffuseTexture); + material->SetSpecularColor(D3DXVECTOR3(1.0f, 1.0f, 1.0f)); + material->SetAmbientColor(D3DXVECTOR3(0.2f, 0.2f, 0.2f)); + material->SetDiffuseColor(D3DXVECTOR3(1.0f, 1.0f, 1.0f)); + material->SetWireframeColor(D3DXVECTOR3(1.0f, 1.0f, 1.0f)); + material->SetShaderFlags1(shaderFlags1); + material->SetShaderFlags2(shaderFlags2); + if (alphaFlags != 0) { + material->SetFlags(alphaFlags); + material->SetAlphaThreshold(alphaThreshold); + } + + nifMesh->m_material = material; + } + } + } + + if (!nifMesh->m_morphable) + nifMesh->SetLocked(true); + + return nifMesh; +} + +void CDXNifMesh::Pass(LPDIRECT3DDEVICE9 pDevice, UInt32 iPass, CDXShader * shader) +{ + ID3DXEffect * effect = shader->GetEffect(); + if (m_material) { + effect->SetValue(shader->m_hSpecular, m_material->GetSpecularColor(), sizeof(CDXVec3)); + effect->SetValue(shader->m_hAmbient, m_material->GetAmbientColor(), sizeof(CDXVec3)); + effect->SetValue(shader->m_hDiffuse, m_material->GetDiffuseColor(), sizeof(CDXVec3)); + effect->SetValue(shader->m_hWireframeColor, m_material->GetWireframeColor(), sizeof(CDXVec3)); + + UInt32 alphaFunc = mappedTestFunctions[m_material->GetTestMode()]; + UInt32 srcBlend = mappedAlphaFunctions[m_material->GetSrcBlendMode()]; + UInt32 destBlend = mappedAlphaFunctions[m_material->GetDestBlendMode()]; + + bool isDoubleSided = (m_material->GetShaderFlags2() & BSShaderProperty::kSLSF2_Double_Sided) == BSShaderProperty::kSLSF2_Double_Sided; + bool zBufferTest = (m_material->GetShaderFlags1() & BSShaderProperty::kSLSF1_ZBuffer_Test) == BSShaderProperty::kSLSF1_ZBuffer_Test; + bool zBufferWrite = (m_material->GetShaderFlags2() & BSShaderProperty::kSLSF2_ZBuffer_Write) == BSShaderProperty::kSLSF2_ZBuffer_Write; + + pDevice->SetRenderState(D3DRS_ZENABLE, zBufferTest ? TRUE : FALSE); + pDevice->SetRenderState(D3DRS_ZWRITEENABLE, zBufferWrite ? TRUE : FALSE); + pDevice->SetRenderState(D3DRS_CULLMODE, isDoubleSided ? D3DCULL_NONE : D3DCULL_CW); + pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); + pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, m_material->GetAlphaBlending() ? TRUE : FALSE); + pDevice->SetRenderState(D3DRS_ALPHATESTENABLE, m_material->GetAlphaTesting() ? TRUE : FALSE); + pDevice->SetRenderState(D3DRS_ALPHAREF, m_material->GetAlphaThreshold()); + pDevice->SetRenderState(D3DRS_ALPHAFUNC, alphaFunc); + pDevice->SetRenderState(D3DRS_SRCBLEND, srcBlend); + pDevice->SetRenderState(D3DRS_DESTBLEND, destBlend); + } + effect->CommitChanges(); + + CDXMesh::Pass(pDevice, iPass, shader); +} + +#endif \ No newline at end of file diff --git a/skee/CDXNifMesh.h b/skee/CDXNifMesh.h new file mode 100644 index 0000000..9af3d3d --- /dev/null +++ b/skee/CDXNifMesh.h @@ -0,0 +1,37 @@ +#ifdef FIXME + +#ifndef __CDXNIFMESH__ +#define __CDXNIFMESH__ + +#pragma once + +#include "CDXEditableMesh.h" +#include "CDXMaterial.h" + +#include + +class NiGeometry; +class CDXScene; +class CDXShader; + +class CDXNifMesh : public CDXEditableMesh +{ +public: + CDXNifMesh(); + ~CDXNifMesh(); + + static CDXNifMesh * Create(LPDIRECT3DDEVICE9 pDevice, NiGeometry * geometry); + + virtual void Pass(LPDIRECT3DDEVICE9 pDevice, UInt32 iPass, CDXShader * shader); + + NiGeometry * GetNifGeometry(); + bool IsMorphable(); + +private: + bool m_morphable; + NiGeometry * m_geometry; +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXNifScene.cpp b/skee/CDXNifScene.cpp new file mode 100644 index 0000000..a7c468e --- /dev/null +++ b/skee/CDXNifScene.cpp @@ -0,0 +1,94 @@ +#ifdef FIXME + +#include "CDXNifScene.h" +#include "CDXNifMesh.h" +#include "CDXNifBrush.h" +#include "CDXMaterial.h" +#include "CDXShader.h" + +#include "skse64/GameTypes.h" + +#include "skse64/NiRenderer.h" +#include "skse64/NiTextures.h" +#include "skse64/NiProperties.h" +#include "skse64/NiNodes.h" + +#include "skse64/ScaleformLoader.h" + +CDXNifScene::CDXNifScene() : CDXEditableScene() +{ + m_textureGroup = NULL; + m_currentBrush = CDXBrush::kBrushType_Smooth; + m_actor = NULL; + + m_width = 512; + m_height = 512; +} + +void CDXNifScene::CreateBrushes() +{ + m_brushes.push_back(new CDXNifMaskAddBrush); + m_brushes.push_back(new CDXNifMaskSubtractBrush); + m_brushes.push_back(new CDXNifInflateBrush); + m_brushes.push_back(new CDXNifDeflateBrush); + m_brushes.push_back(new CDXNifSmoothBrush); + m_brushes.push_back(new CDXNifMoveBrush); +} + +void CDXNifScene::Setup(LPDIRECT3DDEVICE9 pDevice) +{ + if (m_textureGroup) + Release(); + + CDXEditableScene::Setup(pDevice); + + BSScaleformImageLoader * imageLoader = GFxLoader::GetSingleton()->imageLoader; + if (!imageLoader) { + _ERROR("%s - No image loader found", __FUNCTION__); + return; + } + NiTexture::FormatPrefs format; + format.mipMapped = 2; + format.alphaFormat = 2; + format.pixelLayout = 6; + BSFixedString meshTexture("headMesh"); + m_textureGroup = CreateRenderTargetGroup(&meshTexture, m_width, m_height, &format, 0, 1, 0, 0, 0, 0, 0); + if (!m_textureGroup) { + _ERROR("%s - Failed to create dynamic texture", __FUNCTION__); + return; + } + + m_textureGroup->renderedTexture[0]->name = meshTexture.data; + CALL_MEMBER_FN(imageLoader, AddVirtualImage)((NiTexture**)&m_textureGroup->renderedTexture[0]); +} + +void CDXNifScene::Release() +{ + if(m_textureGroup) { + NiTexture * texture = m_textureGroup->renderedTexture[0]; + if(texture) { + BSScaleformImageLoader * imageLoader = GFxLoader::GetSingleton()->imageLoader; + UInt8 ret = CALL_MEMBER_FN(imageLoader, ReleaseVirtualImage)(&texture); + } + + m_textureGroup->DecRef(); + } + + ReleaseImport(); + + m_textureGroup = NULL; + m_actor = NULL; + + CDXEditableScene::Release(); +} + +void CDXNifScene::ReleaseImport() +{ + if (m_importRoot) { + m_importRoot->DecRef(); + } + + m_importRoot = NULL; +} + +#endif \ No newline at end of file diff --git a/skee/CDXNifScene.h b/skee/CDXNifScene.h new file mode 100644 index 0000000..56443c8 --- /dev/null +++ b/skee/CDXNifScene.h @@ -0,0 +1,42 @@ +#ifdef FIXME + +#ifndef __CDXNIFSCENE__ +#define __CDXNIFSCENE__ + +#pragma once + +#include "CDXEditableScene.h" + +class BSRenderTargetGroup; +class BSScaleformImageLoader; +class Actor; +class NiNode; + +class CDXNifScene : public CDXEditableScene +{ +public: + CDXNifScene(); + + virtual void Setup(LPDIRECT3DDEVICE9 pDevice); + virtual void Release(); + virtual void CreateBrushes(); + + BSRenderTargetGroup * GetTextureGroup() { return m_textureGroup; } + + void SetWorkingActor(Actor * actor) { m_actor = actor; } + Actor* GetWorkingActor() { return m_actor; } + + void SetImportRoot(NiNode * node) { m_importRoot = node; } + NiNode * GetImportRoot() { return m_importRoot; } + + void ReleaseImport(); + +protected: + Actor * m_actor; + BSRenderTargetGroup * m_textureGroup; + NiNode * m_importRoot; +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXPicker.cpp b/skee/CDXPicker.cpp new file mode 100644 index 0000000..01f972f --- /dev/null +++ b/skee/CDXPicker.cpp @@ -0,0 +1 @@ +#include "CDXPicker.h" \ No newline at end of file diff --git a/skee/CDXPicker.h b/skee/CDXPicker.h new file mode 100644 index 0000000..78f11ce --- /dev/null +++ b/skee/CDXPicker.h @@ -0,0 +1,62 @@ +#ifdef FIXME + +#ifndef __CDXPICKER__ +#define __CDXPICKER__ + +#pragma once + +#include "CDXMesh.h" + +class CDXRayInfo +{ +public: + CDXVec3 origin; + CDXVec3 direction; + CDXVec3 point; + + CDXRayInfo() + { + Clear(); + } + + void Clear() + { + origin = CDXVec3(0, 0, 0); + direction = CDXVec3(0, 0, 0); + point = CDXVec3(0, 0, 0); + } +}; + +class CDXPickInfo +{ +public: + CDXVec3 origin; + CDXVec3 normal; + CDXRayInfo ray; + float dist; + bool isHit; + + CDXPickInfo() + { + Clear(); + } + + void Clear() + { + dist = FLT_MAX; + origin = CDXVec3(0, 0, 0); + normal = CDXVec3(0, 0, 0); + isHit = false; + } +}; + +class CDXPicker +{ +public: + virtual bool Pick(CDXPickInfo & pickInfo, CDXMesh * mesh, bool isMirror) = 0; + virtual bool Mirror() const = 0; +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXResetMask.cpp b/skee/CDXResetMask.cpp new file mode 100644 index 0000000..4400b9a --- /dev/null +++ b/skee/CDXResetMask.cpp @@ -0,0 +1,68 @@ +#ifdef FIXME + +#include "CDXResetMask.h" + +CDXResetMask::CDXResetMask(CDXMesh * mesh) +{ + m_mesh = mesh; + + CDXMeshVert* pVertices = NULL; + LPDIRECT3DVERTEXBUFFER9 pVB = m_mesh->GetVertexBuffer(); + + pVB->Lock(0, 0, (void**)&pVertices, 0); + for (CDXMeshIndex i = 0; i < m_mesh->GetVertexCount(); i++) { + CDXColor unselected = COLOR_UNSELECTED; + if (pVertices[i].Color != unselected) { + m_previous[i] = pVertices[i].Color; + pVertices[i].Color = unselected; + m_current[i] = unselected; + } + } + + pVB->Unlock(); +} + +CDXResetMask::~CDXResetMask() +{ + m_current.clear(); + m_previous.clear(); +} + +CDXUndoCommand::UndoType CDXResetMask::GetUndoType() +{ + return kUndoType_ResetMask; +} + +void CDXResetMask::Redo() +{ + CDXMeshVert* pVertices = NULL; + CDXMeshIndex* pIndices = NULL; + + LPDIRECT3DVERTEXBUFFER9 pVB = m_mesh->GetVertexBuffer(); + + pVB->Lock(0, 0, (void**)&pVertices, 0); + + // Do what we have now + for (auto it : m_current) + pVertices[it.first].Color = it.second; + + pVB->Unlock(); +} + +void CDXResetMask::Undo() +{ + CDXMeshVert* pVertices = NULL; + CDXMeshIndex* pIndices = NULL; + + LPDIRECT3DVERTEXBUFFER9 pVB = m_mesh->GetVertexBuffer(); + + pVB->Lock(0, 0, (void**)&pVertices, 0); + + // Undo what we did + for (auto it : m_previous) + pVertices[it.first].Color = it.second; + + pVB->Unlock(); +} + +#endif \ No newline at end of file diff --git a/skee/CDXResetMask.h b/skee/CDXResetMask.h new file mode 100644 index 0000000..4c1a742 --- /dev/null +++ b/skee/CDXResetMask.h @@ -0,0 +1,29 @@ +#ifdef FIXME + +#ifndef __CDXRESETSELECTION__ +#define __CDXRESETSELECTION__ + +#pragma once + +#include "CDXUndo.h" +#include "CDXEditableMesh.h" + +class CDXResetMask : public CDXUndoCommand +{ +public: + CDXResetMask::CDXResetMask(CDXMesh * mesh); + virtual ~CDXResetMask(); + + virtual UndoType GetUndoType(); + virtual void Redo(); + virtual void Undo(); + +protected: + CDXMesh * m_mesh; + CDXMaskMap m_previous; + CDXMaskMap m_current; +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXScene.cpp b/skee/CDXScene.cpp new file mode 100644 index 0000000..323b651 --- /dev/null +++ b/skee/CDXScene.cpp @@ -0,0 +1,286 @@ +#ifdef FIXME + +#include "CDXScene.h" +#include "CDXCamera.h" +#include "CDXMesh.h" +#include "CDXShader.h" +#include "CDXMaterial.h" +#include "CDXPicker.h" +#include "CDXBrush.h" + +#include + +CDXModelViewerCamera g_Camera; + +CDXScene::CDXScene() +{ + m_shader = new CDXShader; + m_pStateBlock = NULL; + m_pMeshDecl = NULL; + m_visible = true; + m_width = 1024; + m_height = 1024; +} + +CDXScene::~CDXScene() +{ + if(m_shader) { + delete m_shader; + m_shader = NULL; + } +} + +void CDXScene::Release() +{ + if(m_pStateBlock) { + m_pStateBlock->Release(); + m_pStateBlock = NULL; + } + if(m_pMeshDecl) { + m_pMeshDecl->Release(); + m_pMeshDecl = NULL; + } + if(m_shader) { + m_shader->Release(); + } + + for(auto it : m_meshes) { + it->Release(); + delete it; + } + + m_meshes.clear(); +} + +void CDXScene::Setup(LPDIRECT3DDEVICE9 pDevice) +{ + pDevice->CreateVertexDeclaration(VertexDecl, &m_pMeshDecl); + + m_shader->CreateEffect(pDevice); + + // Setup the camera's view parameters + CDXVec3 vecEye(30.0f, 0.0f, 0.0f); + CDXVec3 vecAt (0.0f, 0.0f, 0.0f); + g_Camera.SetWindow(m_width, m_height); + g_Camera.SetViewParams(&vecEye, &vecAt); + g_Camera.Update(); +} + +void CDXScene::Begin(LPDIRECT3DDEVICE9 pDevice) +{ + if(!m_pStateBlock) + pDevice->CreateStateBlock(D3DSBT_ALL,&m_pStateBlock); + + m_pStateBlock->Capture(); +} + +void CDXScene::End(LPDIRECT3DDEVICE9 pDevice) +{ + m_pStateBlock->Apply(); +} + +void CDXScene::Render(LPDIRECT3DDEVICE9 pDevice) +{ + CDXMatrix16 mWorld; + CDXMatrix16 mView; + CDXMatrix16 mProj; + CDXMatrix16 mWorldViewProjection; + + pDevice->Clear(0,NULL,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,0,1.0f,0); + + ID3DXEffect * pEffect = m_shader->GetEffect(); + if(!pEffect) + return; + + mWorld = *g_Camera.GetWorldMatrix(); + mView = *g_Camera.GetViewMatrix(); + mProj = *g_Camera.GetProjMatrix(); + mWorldViewProjection = mWorld * mView * mProj; + + CDXVec3 vAmbient = CDXVec3(0.0f, 0.0f, 0.0f); + CDXVec3 vDiffuse = CDXVec3(1.0f, 1.0f, 1.0f); + CDXVec3 vSpecular = CDXVec3(1.0f, 1.0f, 1.0f); + int nShininess = 0; + float fAlpha = 1.0f; + + pEffect->SetMatrix(m_shader->m_hWorldViewProjection, &mWorldViewProjection); + pEffect->SetMatrix(m_shader->m_hWorld, &mWorld); + pEffect->SetValue(m_shader->m_hCameraPosition, g_Camera.GetEyePt(), sizeof(CDXVec3)); + pEffect->SetValue(m_shader->m_hAmbient, vAmbient, sizeof(CDXVec3)); + pEffect->SetValue(m_shader->m_hDiffuse, vDiffuse, sizeof(CDXVec3)); + pEffect->SetValue(m_shader->m_hSpecular, vSpecular, sizeof(CDXVec3)); + pEffect->SetFloat(m_shader->m_hOpacity, fAlpha); + pEffect->SetInt(m_shader->m_hSpecularPower, nShininess); + + pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE); + //pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME); + pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); + pDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); + pDevice->SetRenderState(D3DRS_ZWRITEENABLE, TRUE); + pDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); + pDevice->SetRenderState(D3DRS_LASTPIXEL, TRUE); + pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE); + pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO); + pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW); + pDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL); + pDevice->SetRenderState(D3DRS_ALPHAREF, 0); + pDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_ALWAYS); + pDevice->SetRenderState(D3DRS_DITHERENABLE, FALSE); + pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); + pDevice->SetRenderState(D3DRS_FOGENABLE, FALSE); + pDevice->SetRenderState(D3DRS_SPECULARENABLE, FALSE); + //pDevice->SetRenderState(D3DRS_FOGCOLOR, 0); + //pDevice->SetRenderState(D3DRS_FOGTABLEMODE, D3DFOG_NONE); + //pDevice->SetRenderState(D3DRS_FOGSTART, *((DWORD*) (&fFogStart))); + //pDevice->SetRenderState(D3DRS_FOGEND, *((DWORD*) (&fFogEnd)); + //pDevice->SetRenderState(D3DRS_FOGDENSITY, *((DWORD*) (&fFogDensity)); + pDevice->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE); + pDevice->SetRenderState(D3DRS_STENCILENABLE, FALSE); + pDevice->SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP); + pDevice->SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_KEEP); + pDevice->SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP); + pDevice->SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS); + pDevice->SetRenderState(D3DRS_STENCILREF, 0); + pDevice->SetRenderState(D3DRS_STENCILMASK, 0xFFFFFFFF); + pDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xFFFFFFFF); + pDevice->SetRenderState(D3DRS_TEXTUREFACTOR, 0xFFFFFFFF); + pDevice->SetRenderState(D3DRS_CLIPPING, TRUE); + pDevice->SetRenderState(D3DRS_LIGHTING, TRUE); + pDevice->SetRenderState(D3DRS_AMBIENT, 0); + pDevice->SetRenderState(D3DRS_COLORVERTEX, TRUE); + pDevice->SetRenderState(D3DRS_LOCALVIEWER, TRUE); + pDevice->SetRenderState(D3DRS_NORMALIZENORMALS, FALSE); + pDevice->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_COLOR1); + pDevice->SetRenderState(D3DRS_SPECULARMATERIALSOURCE, D3DMCS_COLOR2); + pDevice->SetRenderState(D3DRS_AMBIENTMATERIALSOURCE, D3DMCS_MATERIAL); + pDevice->SetRenderState(D3DRS_EMISSIVEMATERIALSOURCE, D3DMCS_MATERIAL); + pDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_DISABLE); + pDevice->SetRenderState(D3DRS_CLIPPLANEENABLE, 0); + pDevice->SetRenderState(D3DRS_MULTISAMPLEANTIALIAS, TRUE); + pDevice->SetRenderState(D3DRS_MULTISAMPLEMASK, 0xFFFFFFFF); + pDevice->SetRenderState(D3DRS_PATCHEDGESTYLE, D3DPATCHEDGE_DISCRETE); + pDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, FALSE); + pDevice->SetRenderState(D3DRS_COLORWRITEENABLE, 0x0000000F); + pDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); + pDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE); + pDevice->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, 0); + pDevice->SetRenderState(D3DRS_ANTIALIASEDLINEENABLE, FALSE); + pDevice->SetRenderState(D3DRS_CCW_STENCILFAIL, D3DSTENCILOP_KEEP); + pDevice->SetRenderState(D3DRS_CCW_STENCILZFAIL, D3DSTENCILOP_KEEP); + pDevice->SetRenderState(D3DRS_CCW_STENCILPASS, D3DSTENCILOP_KEEP); + pDevice->SetRenderState(D3DRS_CCW_STENCILFUNC, D3DCMP_ALWAYS); + pDevice->SetRenderState(D3DRS_COLORWRITEENABLE1, 0x0000000f); + pDevice->SetRenderState(D3DRS_COLORWRITEENABLE2, 0x0000000f); + pDevice->SetRenderState(D3DRS_COLORWRITEENABLE3, 0x0000000f); + pDevice->SetRenderState(D3DRS_BLENDFACTOR, 0xffffffff); + pDevice->SetRenderState(D3DRS_SRGBWRITEENABLE, 0); + pDevice->SetRenderState(D3DRS_DEPTHBIAS, 0); + pDevice->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, FALSE); + pDevice->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE); + pDevice->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_ZERO); + pDevice->SetRenderState(D3DRS_BLENDOPALPHA, D3DBLENDOP_ADD); + + pDevice->SetVertexDeclaration( m_pMeshDecl ); + + for(auto mesh : m_meshes) { + if(mesh->IsVisible()) { + mesh->Render(pDevice, m_shader); + } + } +} + +void CDXScene::AddMesh(CDXMesh * mesh) +{ + if(mesh) + m_meshes.push_back(mesh); +} + +bool CDXScene::Pick(int x, int y, CDXPicker & picker) +{ + CDXRayInfo rayInfo; + CDXRayInfo mRayInfo; + CDXVec3 mousePoint; + mousePoint.x = x; + mousePoint.y = -y; + mousePoint.z = 1.0f; + + const CDXMatrix* pmatProj = g_Camera.GetProjMatrix(); + + // Compute the vector of the pick ray in screen space + CDXVec3 v; + v.x = (((2.0f * x) / g_Camera.GetWidth()) - 1) / pmatProj->_11; + v.y = -(((2.0f * y) / g_Camera.GetHeight()) - 1) / pmatProj->_22; + v.z = 1.0f; + + // Get the inverse view matrix + const CDXMatrix matView = *g_Camera.GetViewMatrix(); + const CDXMatrix matWorld = *g_Camera.GetWorldMatrix(); + CDXMatrix mWorldView = matWorld * matView; + CDXMatrix m; + D3DXMatrixInverse( &m, NULL, &mWorldView ); + + // Transform the screen space pick ray into 3D space + rayInfo.direction.x = v.x * m._11 + v.y * m._21 + v.z * m._31; + rayInfo.direction.y = v.x * m._12 + v.y * m._22 + v.z * m._32; + rayInfo.direction.z = v.x * m._13 + v.y * m._23 + v.z * m._33; + rayInfo.origin.x = m._41; + rayInfo.origin.y = m._42; + rayInfo.origin.z = m._43; + rayInfo.point.x = mousePoint.x * m._11 + mousePoint.y * m._21 + mousePoint.z * m._31; + rayInfo.point.y = mousePoint.x * m._12 + mousePoint.y * m._22 + mousePoint.z * m._32; + rayInfo.point.z = mousePoint.x * m._13 + mousePoint.y * m._23 + mousePoint.z * m._33; + + // Create mirror raycast + mRayInfo = rayInfo; + mRayInfo.direction.x = -mRayInfo.direction.x; + mRayInfo.origin.x = -mRayInfo.origin.x; + + // Find closest collision points + bool hitMesh = false; + CDXPickInfo pickInfo; + CDXPickInfo mPickInfo; + for (auto mesh : m_meshes) + { + if (mesh->IsLocked()) + continue; + + CDXPickInfo castInfo; + if (mesh->Pick(rayInfo, castInfo)) + hitMesh = true; + + if ((!pickInfo.isHit && castInfo.isHit) || (castInfo.isHit && castInfo.dist < pickInfo.dist)) + pickInfo = castInfo; + + if (picker.Mirror()) { + + CDXPickInfo mCastInfo; + if (mesh->Pick(mRayInfo, mCastInfo)) + hitMesh = true; + + if ((!mPickInfo.isHit && mCastInfo.isHit) || (mCastInfo.isHit && mCastInfo.dist < mPickInfo.dist)) + mPickInfo = mCastInfo; + } + } + + pickInfo.ray = rayInfo; + mPickInfo.ray = mRayInfo; + + bool hitVertices = false; + for (auto mesh : m_meshes) + { + if (mesh->IsLocked()) + continue; + + if (picker.Pick(pickInfo, mesh, false)) + hitVertices = true; + + if (picker.Mirror()) { + if (picker.Pick(mPickInfo, mesh, true)) + hitVertices = true; + } + } + + return hitVertices; +} + +#endif \ No newline at end of file diff --git a/skee/CDXScene.h b/skee/CDXScene.h new file mode 100644 index 0000000..998fb82 --- /dev/null +++ b/skee/CDXScene.h @@ -0,0 +1,66 @@ +#ifdef FIXME + +#ifndef __CDXSCENE__ +#define __CDXSCENE__ + +#pragma once + +#include "CDXCamera.h" +#include "CDXBrush.h" +#include + +class CDXMesh; +class CDXShader; +class CDXPicker; +class CDXBrush; +class CDXMaskAddBrush; +class CDXInflateBrush; + +typedef D3DXMATRIXA16 CDXMatrix16; +typedef D3DXMATRIX CDXMatrix; +typedef std::vector CDXMeshList; + +class CDXScene +{ +public: + CDXScene(); + ~CDXScene(); + + virtual void Setup(LPDIRECT3DDEVICE9 pDevice); + virtual void Release(); + + virtual void Render(LPDIRECT3DDEVICE9 pDevice); + + virtual void Begin(LPDIRECT3DDEVICE9 pDevice); + virtual void End(LPDIRECT3DDEVICE9 pDevice); + + UInt32 GetNumMeshes() { return m_meshes.size(); } + CDXMesh * GetNthMesh(UInt32 i) { return m_meshes.at(i); } + + void AddMesh(CDXMesh * mesh); + bool Pick(int x, int y, CDXPicker & picker); + + CDXShader * GetShader() { return m_shader; } + void SetVisible(bool visible) { m_visible = visible; } + bool IsVisible() const { return m_visible; } + + UInt32 GetWidth() const { return m_height; } + UInt32 GetHeight() const { return m_height; } + +protected: + bool m_visible; + + UInt32 m_width; + UInt32 m_height; + + CDXShader * m_shader; + + IDirect3DVertexDeclaration9* m_pMeshDecl; + IDirect3DStateBlock9* m_pStateBlock; + + CDXMeshList m_meshes; +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXShader.cpp b/skee/CDXShader.cpp new file mode 100644 index 0000000..2304b1c --- /dev/null +++ b/skee/CDXShader.cpp @@ -0,0 +1,281 @@ +#ifdef FIXME + +#include "CDXShader.h" + +CDXShader::CDXShader() +{ + m_pEffect = NULL; + m_hAmbient = NULL; + m_hDiffuse = NULL; + m_hSpecular = NULL; + m_hOpacity = NULL; + m_hSpecularPower = NULL; + m_hLightColor = NULL; + m_hLightPosition = NULL; + m_hCameraPosition = NULL; + m_hTexture = NULL; + m_hTime = NULL; + m_hWorld = NULL; + m_hWorldViewProjection = NULL; + m_hTransform = NULL; + m_hWireframeColor = NULL; +} + +void CDXShader::Release() +{ + if(m_pEffect) { + m_pEffect->Release(); + m_pEffect = NULL; + } +} + +void CDXShader::CreateEffect(LPDIRECT3DDEVICE9 pDevice) +{ + /*const char* g_strBuffer = "float3 g_vMaterialAmbient : Ambient = float3( 0.2f, 0.2f, 0.2f ); // Material's ambient color\r\n" + "float3 g_vMaterialDiffuse : Diffuse = float3( 1.0f, 1.0f, 1.0f ); // Material's diffuse color\r\n" + "float3 g_vMaterialSpecular : Specular = float3( 1.0f, 1.0f, 1.0f ); // Material's specular color\r\n" + "float g_fMaterialAlpha : Opacity = 1.0f;\r\n" + "int g_nMaterialShininess : SpecularPower = 32;\r\n" + "float3 g_vLightColor : LightColor = float3( 1.0f, 1.0f, 1.0f ); // Light color\r\n" + "float3 g_vLightPosition : LightPosition = float3( 50.0f, 10.0f, 0.0f ); // Light position\r\n" + "float3 g_vCameraPosition : CameraPosition;\r\n" + "texture g_MeshTexture : Texture; // Color texture for mesh\r\n" + "float g_fTime : Time; // App's time in seconds\r\n" + "float4x4 g_mWorld : World; // World matrix\r\n" + "float4x4 g_mWorldViewProjection : WorldViewProjection; // World * View * Projection matrix\r\n" + "sampler MeshTextureSampler = \r\n" + " sampler_state\r\n" + "{\r\n" + " Texture = ;\r\n" + " MipFilter = LINEAR;\r\n" + " MinFilter = LINEAR;\r\n" + " MagFilter = LINEAR;\r\n" + "};\r\n" + "void Projection( float4 vPosObject: POSITION,\r\n" + " float3 vNormalObject: NORMAL,\r\n" + " float2 vTexCoordIn: TEXCOORD0,\r\n" + " out float4 vPosProj: POSITION,\r\n" + " out float2 vTexCoordOut: TEXCOORD0,\r\n" + " out float4 vColorOut: COLOR0,\r\n" + " uniform bool bSpecular\r\n" + " )\r\n" + "{\r\n" + " float4 vPosWorld = mul( vPosObject, g_mWorld );\r\n" + " vPosProj = mul( vPosObject, g_mWorldViewProjection );\r\n" + " float3 vNormalWorld = mul( vNormalObject, (float3x3)g_mWorld );\r\n" + " vTexCoordOut = vTexCoordIn;\r\n" + " float3 vLight = normalize( g_vLightPosition - vPosWorld.xyz );\r\n" + " vColorOut.rgb = g_vLightColor * g_vMaterialAmbient;\r\n" + " vColorOut.rgb += g_vLightColor * g_vMaterialDiffuse * saturate( dot( vLight, vNormalWorld ) );\r\n" + " if( bSpecular )\r\n" + " {\r\n" + " float3 vCamera = normalize(vPosWorld.xyz - g_vCameraPosition);\r\n" + " float3 vReflection = reflect( vLight, vNormalWorld );\r\n" + " float fPhongValue = saturate( dot( vReflection, vCamera ) );\r\n" + " vColorOut.rgb += g_vMaterialSpecular * pow(fPhongValue, g_nMaterialShininess);\r\n" + " }\r\n" + " vColorOut.a = g_fMaterialAlpha;\r\n" + "}\r\n" + "void Lighting( float2 vTexCoord: TEXCOORD0,\r\n" + " float4 vColorIn: COLOR0,\r\n" + " out float4 vColorOut: COLOR0,\r\n" + " uniform bool bTexture )\r\n" + "{\r\n" + " vColorOut = vColorIn;\r\n" + " if( bTexture )\r\n" + " vColorOut.rgb *= tex2D( MeshTextureSampler, vTexCoord );\r\n" + "}\r\n" + "technique Specular\r\n" + "{\r\n" + " pass P0\r\n" + " {\r\n" + " VertexShader = compile vs_2_0 Projection(true);\r\n" + " PixelShader = compile ps_2_0 Lighting(false);\r\n" + " }\r\n" + "}\r\n" + "technique NoSpecular\r\n" + "{\r\n" + " pass P0\r\n" + " {\r\n" + " VertexShader = compile vs_2_0 Projection(false);\r\n" + " PixelShader = compile ps_2_0 Lighting(false);\r\n" + " }\r\n" + "}\r\n" + "technique TexturedSpecular\r\n" + "{\r\n" + " pass P0\r\n" + " {\r\n" + " VertexShader = compile vs_2_0 Projection(true);\r\n" + " PixelShader = compile ps_2_0 Lighting(true);\r\n" + " }\r\n" + "}\r\n" + "technique TexturedNoSpecular\r\n" + "{\r\n" + " pass P0\r\n" + " {\r\n" + " VertexShader = compile vs_2_0 Projection(false);\r\n" + " PixelShader = compile ps_2_0 Lighting(true);\r\n" + " }\r\n" + "}\r\n";*/ + const char* g_strBuffer = "float3 g_vMaterialAmbient : Ambient = float3( 0.2f, 0.2f, 0.2f ); // Material's ambient color\r\n" + "float3 g_vMaterialDiffuse : Diffuse = float3( 1.0f, 1.0f, 1.0f ); // Material's diffuse color\r\n" + "float3 g_vMaterialSpecular : Specular = float3( 1.0f, 1.0f, 1.0f ); // Material's specular color\r\n" + "float g_fMaterialAlpha : Opacity = 1.0f;\r\n" + "float g_fMaterialAlphaThreshold : AlphaThreshold = 0.0f;\r\n" + "int g_nMaterialShininess : SpecularPower = 32;\r\n" + "float3 g_vLightColor : LightColor = float3( 1.0f, 1.0f, 1.0f ); // Light color\r\n" + "float3 g_vLightPosition : LightPosition = float3( 0.0f, -100.0f, 0.0f ); // Light position\r\n" + "float3 g_vCameraPosition : CameraPosition;\r\n" + "float3 g_vWireframe : WireframeColor = float3( 1.0f, 1.0f, 1.0f);\r\n" + "texture g_MeshTexture : Texture; // Color texture for mesh\r\n" + "float g_fTime : Time; // App's time in seconds\r\n" + "float4x4 g_mWorld : World; // World matrix\r\n" + "float4x4 g_mWorldViewProjection : WorldViewProjection; // World * View * Projection matrix\r\n" + "float4x4 g_mTransform: Transform;\r\n" + "sampler DiffuseSampler = \r\n" + " sampler_state\r\n" + "{\r\n" + " Texture = ;\r\n" + " MipFilter = LINEAR;\r\n" + " MinFilter = LINEAR;\r\n" + " MagFilter = LINEAR;\r\n" + " AddressU = WRAP;\r\n" + " AddressV = WRAP;\r\n" + " AddressW = WRAP;\r\n" + "};\r\n" + "void Basic( float4 vPosObject: POSITION0,\r\n" + " float3 vNormalObject: NORMAL0,\r\n" + " float2 vTexCoordIn: TEXCOORD0,\r\n" + " float4 vColorIn: COLOR0,\r\n" + " float4 vColorSelect: COLOR1,\r\n" + " out float4 vPosProj: POSITION,\r\n" + " out float2 vTexCoordOut: TEXCOORD0,\r\n" + " out float4 vColorOut: COLOR0,\r\n" + " uniform float fLine )\r\n" + "{\r\n" + " float4 vPosTransform = mul(vPosObject, g_mTransform);\r\n" + " vPosTransform.xyz *= fLine;\r\n" + " vPosProj = mul( vPosTransform, g_mWorldViewProjection );\r\n" + " vTexCoordOut = vTexCoordIn;\r\n" + " vColorOut.a = g_fMaterialAlpha;\r\n" + " vColorOut.rgb = g_vWireframe;\r\n" + "}\r\n" + "void Projection( float4 vPosObject: POSITION0,\r\n" + " float3 vNormalObject: NORMAL0,\r\n" + " float2 vTexCoordIn: TEXCOORD0,\r\n" + " float4 vColorIn: COLOR0,\r\n" + " out float4 vPosProj: POSITION,\r\n" + " out float2 vTexCoordOut: TEXCOORD0,\r\n" + " out float4 vColorOut: COLOR0,\r\n" + " uniform bool bSpecular )\r\n" + "{\r\n" + " float4 vPosTransform = mul(vPosObject, g_mTransform);\r\n" + " float4 vPosWorld = mul( vPosTransform, g_mWorld );\r\n" + " vPosProj = mul( vPosTransform, g_mWorldViewProjection );\r\n" + " float3 vNormalWorld = mul( vNormalObject, (float3x3)g_mWorld );\r\n" + " vTexCoordOut = vTexCoordIn;\r\n" + " float3 vLight = normalize( g_vLightPosition - vPosWorld.xyz );\r\n" + " vColorOut.rgb = g_vLightColor * g_vMaterialAmbient;\r\n" + " vColorOut.rgb += g_vLightColor * g_vMaterialDiffuse * saturate( dot( vLight, vNormalWorld ) );\r\n" + " if( bSpecular ) {\r\n" + " float3 vCamera = normalize(vPosWorld.xyz - g_vCameraPosition);\r\n" + " float3 vReflection = reflect( vLight, vNormalWorld );\r\n" + " float fPhongValue = saturate( dot( vReflection, vCamera ) );\r\n" + " vColorOut.rgb += g_vMaterialSpecular * pow(fPhongValue, g_nMaterialShininess);\r\n" + " }\r\n" + " vColorOut.a = g_fMaterialAlpha;\r\n" + " vColorOut *= vColorIn;\r\n" + "}\r\n" + "void Lighting( float4 vPos: POSITION0,\r\n" + " float2 vTexCoord: TEXCOORD0,\r\n" + " float4 vColorIn: COLOR0,\r\n" + " out float4 vColorOut: COLOR0,\r\n" + " uniform bool bTexture )\r\n" + "{\r\n" + " vColorOut = vColorIn;\r\n" + " if( bTexture )\r\n" + " vColorOut *= tex2D( DiffuseSampler, vTexCoord );\r\n" + "}\r\n" + "technique Specular\r\n" + "{\r\n" + " pass P0\r\n" + " {\r\n" + " VertexShader = compile vs_2_0 Projection(true);\r\n" + " PixelShader = compile ps_2_0 Lighting(false);\r\n" + " }\r\n" + "}\r\n" + "technique Wireframe\r\n" + "{\r\n" + " pass P0\r\n" + " {\r\n" + " VertexShader = compile vs_2_0 Basic(1.000f);\r\n" + " PixelShader = compile ps_2_0 Lighting(false);\r\n" + " }\r\n" + " pass P1\r\n" + " {\r\n" + " VertexShader = compile vs_2_0 Basic(1.002f);\r\n" + " PixelShader = compile ps_2_0 Lighting(false);\r\n" + " }\r\n" + " pass P2\r\n" + " {\r\n" + " VertexShader = compile vs_2_0 Basic(1.004f);\r\n" + " PixelShader = compile ps_2_0 Lighting(false);\r\n" + " }\r\n" + "}\r\n" + "technique NoSpecular\r\n" + "{\r\n" + " pass P0\r\n" + " {\r\n" + " VertexShader = compile vs_2_0 Projection(false);\r\n" + " PixelShader = compile ps_2_0 Lighting(false);\r\n" + " }\r\n" + "}\r\n" + "technique TexturedSpecular\r\n" + "{\r\n" + " pass P0\r\n" + " {\r\n" + " VertexShader = compile vs_2_0 Projection(true);\r\n" + " PixelShader = compile ps_2_0 Lighting(true);\r\n" + " }\r\n" + "}\r\n" + "technique TexturedNoSpecular\r\n" + "{\r\n" + " pass P0\r\n" + " {\r\n" + " VertexShader = compile vs_2_0 Projection(false);\r\n" + " PixelShader = compile ps_2_0 Lighting(true);\r\n" + " }\r\n" + "}\r\n"; + + UINT dwBufferSize = ( UINT )strlen( g_strBuffer ) + 1; + + try + { + if (D3DXCreateEffect(pDevice, g_strBuffer, dwBufferSize, NULL, NULL, D3DXFX_NOT_CLONEABLE, NULL, &m_pEffect, NULL) == D3D_OK) + { + if (m_pEffect) { + m_hAmbient = m_pEffect->GetParameterBySemantic(0, "Ambient"); + m_hDiffuse = m_pEffect->GetParameterBySemantic(0, "Diffuse"); + m_hSpecular = m_pEffect->GetParameterBySemantic(0, "Specular"); + m_hOpacity = m_pEffect->GetParameterBySemantic(0, "Opacity"); + m_hSpecularPower = m_pEffect->GetParameterBySemantic(0, "SpecularPower"); + m_hLightColor = m_pEffect->GetParameterBySemantic(0, "LightColor"); + m_hLightPosition = m_pEffect->GetParameterBySemantic(0, "LightPosition"); + m_hCameraPosition = m_pEffect->GetParameterBySemantic(0, "CameraPosition"); + m_hTexture = m_pEffect->GetParameterBySemantic(0, "Texture"); + m_hTime = m_pEffect->GetParameterBySemantic(0, "Time"); + m_hWorld = m_pEffect->GetParameterBySemantic(0, "World"); + m_hWorldViewProjection = m_pEffect->GetParameterBySemantic(0, "WorldViewProjection"); + m_hWireframeColor = m_pEffect->GetParameterBySemantic(0, "WireframeColor"); + m_hTransform = m_pEffect->GetParameterBySemantic(0, "Transform"); + } + } + } + catch ( ... ) + { + _ERROR("%s - Fatal error creating D3D Effect.", __FUNCTION__); + m_pEffect = nullptr; + } +} + +#endif \ No newline at end of file diff --git a/skee/CDXShader.h b/skee/CDXShader.h new file mode 100644 index 0000000..9405e5b --- /dev/null +++ b/skee/CDXShader.h @@ -0,0 +1,40 @@ +#ifdef FIXME + +#ifndef __CDXSHADER__ +#define __CDXSHADER__ + +#pragma once + +#include + +class CDXShader +{ +public: + CDXShader(); + void CreateEffect(LPDIRECT3DDEVICE9 pDevice); + void Release(); + + D3DXHANDLE m_hAmbient; + D3DXHANDLE m_hDiffuse; + D3DXHANDLE m_hSpecular; + D3DXHANDLE m_hOpacity; + D3DXHANDLE m_hSpecularPower; + D3DXHANDLE m_hLightColor; + D3DXHANDLE m_hLightPosition; + D3DXHANDLE m_hCameraPosition; + D3DXHANDLE m_hTexture; + D3DXHANDLE m_hTime; + D3DXHANDLE m_hWorld; + D3DXHANDLE m_hWorldViewProjection; + D3DXHANDLE m_hTransform; + D3DXHANDLE m_hWireframeColor; + + ID3DXEffect * GetEffect() { return m_pEffect; } + +protected: + ID3DXEffect * m_pEffect; +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXStroke.cpp b/skee/CDXStroke.cpp new file mode 100644 index 0000000..a3eedf0 --- /dev/null +++ b/skee/CDXStroke.cpp @@ -0,0 +1,285 @@ +#ifdef FIXME + +#include "CDXStroke.h" +#include "CDXBrush.h" + +CDXStroke::CDXStroke(CDXBrush * brush, CDXEditableMesh * mesh) +{ + m_brush = brush; + m_mesh = mesh; + m_origin = CDXVec3(0.0f, 0.0f, 0.0f); + m_mirror = false; +} + +CDXUndoCommand::UndoType CDXBasicStroke::GetUndoType() +{ + return CDXUndoCommand::kUndoType_Stroke; +} + +void CDXBasicStroke::Begin(CDXPickInfo & pickInfo) +{ + m_origin = pickInfo.origin; +} + +void CDXBasicHitStroke::Redo() +{ + CDXMeshVert* pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Do what we have now + for (auto it : m_current) + pVertices[it.first].Position += it.second; + + m_mesh->UnlockVertices(); +} + +void CDXBasicHitStroke::Undo() +{ + CDXMeshVert* pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Undo what we did + for (auto it : m_current) + pVertices[it.first].Position -= it.second; + + m_mesh->UnlockVertices(); +} + +CDXMaskAddStroke::~CDXMaskAddStroke() +{ + m_previous.clear(); + m_current.clear(); +} + +CDXStroke::StrokeType CDXMaskAddStroke::GetStrokeType() +{ + return kStrokeType_Mask_Add; +} + +void CDXMaskAddStroke::Redo() +{ + CDXMeshVert* pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Do what we have now + for (auto it : m_current) + pVertices[it.first].Color = it.second; + + m_mesh->UnlockVertices(); +} + +void CDXMaskAddStroke::Undo() +{ + CDXMeshVert* pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Undo what we did + for (auto it : m_previous) + pVertices[it.first].Color = it.second; + + m_mesh->UnlockVertices(); +} + +void CDXMaskAddStroke::Update(CDXStroke::Info * info) +{ + CDXMeshVert * pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Place the new info in the new map, update the colors + CDXColor color = COLOR_SELECTED; + auto ret = m_current.emplace(info->index, color); + if (ret.second) + m_previous.emplace(info->index, pVertices[info->index].Color); + + pVertices[info->index].Color = color; + + m_mesh->UnlockVertices(); +} + +CDXStroke::StrokeType CDXMaskSubtractStroke::GetStrokeType() +{ + return kStrokeType_Mask_Subtract; +} + +void CDXMaskSubtractStroke::Update(CDXStroke::Info * info) +{ + CDXMeshVert * pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Place the new info in the new map, update the colors + CDXColor color = COLOR_UNSELECTED; + if (pVertices[info->index].Color != color) { + auto ret = m_current.emplace(info->index, color); + if (ret.second) + m_previous.emplace(info->index, pVertices[info->index].Color); + + pVertices[info->index].Color = color; + } + m_mesh->UnlockVertices(); +} + +CDXInflateStroke::~CDXInflateStroke() +{ + m_current.clear(); +} + +CDXStroke::StrokeType CDXInflateStroke::GetStrokeType() +{ + return kStrokeType_Inflate; +} + +void CDXInflateStroke::Update(CDXStroke::Info * info) +{ + CDXMeshVert * pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + CDXVec3 vertexNormal = ((InflateInfo*)info)->normal; + CDXVec3 difference = vertexNormal * info->strength * info->falloff; + + m_current.emplace(info->index, CDXVec3(0, 0, 0)); + m_current[info->index] += difference; + pVertices[info->index].Position += difference; + m_mesh->UnlockVertices(); +} + +CDXStroke::StrokeType CDXDeflateStroke::GetStrokeType() +{ + return kStrokeType_Deflate; +} + +void CDXDeflateStroke::Update(CDXStroke::Info * info) +{ + CDXMeshVert * pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + CDXVec3 vertexNormal = ((InflateInfo*)info)->normal; + CDXVec3 difference = vertexNormal * info->strength * info->falloff; + + m_current.emplace(info->index, CDXVec3(0, 0, 0)); + m_current[info->index] -= difference; + pVertices[info->index].Position -= difference; + m_mesh->UnlockVertices(); +} + +CDXSmoothStroke::~CDXSmoothStroke() +{ + m_current.clear(); +} + +CDXStroke::StrokeType CDXSmoothStroke::GetStrokeType() +{ + return kStrokeType_Smooth; +} + +void CDXSmoothStroke::Update(CDXStroke::Info * info) +{ + CDXMeshVert * pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + CDXVec3 newPos = CDXVec3(0, 0, 0); + + UInt32 totalCount = 0; + m_mesh->VisitAdjacencies(info->index, [&](CDXMeshFace & face) + { + CDXMeshIndex m1 = face.v1; + if (m1 >= m_mesh->GetVertexCount()) + return false; + + CDXMeshIndex m2 = face.v2; + if (m2 >= m_mesh->GetVertexCount()) + return false; + + CDXMeshIndex m3 = face.v3; + if (m3 >= m_mesh->GetVertexCount()) + return false; + + CDXVec3 sum = pVertices[m1].Position + pVertices[m2].Position + pVertices[m3].Position; + newPos += sum / 3; + totalCount++; + return false; + }); + + newPos /= totalCount; + + CDXVec3 difference = (newPos - pVertices[info->index].Position) * info->strength * info->falloff; + + m_current.emplace(info->index, CDXVec3(0,0,0)); + m_current[info->index] += difference; + pVertices[info->index].Position += difference; + m_mesh->UnlockVertices(); +} + +CDXMoveStroke::~CDXMoveStroke() +{ + m_previous.clear(); + m_current.clear(); + m_hitIndices.clear(); +} + +CDXStroke::StrokeType CDXMoveStroke::GetStrokeType() +{ + return kStrokeType_Move; +} + +void CDXMoveStroke::Begin(CDXPickInfo & pickInfo) +{ + CDXBasicStroke::Begin(pickInfo); + m_rayInfo = pickInfo.ray; +} + +void CDXMoveStroke::Redo() +{ + CDXMeshVert* pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Do what we have now + for (auto it : m_current) + pVertices[it.first].Position += it.second; + + m_mesh->UnlockVertices(); +} + +void CDXMoveStroke::Undo() +{ + CDXMeshVert* pVertices = m_mesh->LockVertices(); + if (!pVertices) + return; + + // Undo what we did + for (auto it : m_current) + pVertices[it.first].Position -= it.second; + + m_mesh->UnlockVertices(); +} + +void CDXMoveStroke::Update(CDXStroke::Info * info) +{ + CDXMeshVert * pVertices = m_mesh->LockVertices(); + + m_previous.emplace(info->index, pVertices[info->index].Position); + CDXVec3 newPosition = m_previous[info->index] + ((MoveInfo*)info)->offset * info->strength * info->falloff; + CDXVec3 difference = newPosition - pVertices[info->index].Position; + + m_current.emplace(info->index, CDXVec3(0, 0, 0)); + m_current[info->index] += difference; + pVertices[info->index].Position += difference; + + m_mesh->UnlockVertices(); +} + +void CDXMoveStroke::End() +{ + m_hitIndices.clear(); +} + +#endif \ No newline at end of file diff --git a/skee/CDXStroke.h b/skee/CDXStroke.h new file mode 100644 index 0000000..80819ef --- /dev/null +++ b/skee/CDXStroke.h @@ -0,0 +1,181 @@ +#ifdef FIXME + +#ifndef __CDXSTROKE__ +#define __CDXSTROKE__ + +#pragma once + +#include "CDXUndo.h" +#include "CDXEditableMesh.h" +#include "CDXPicker.h" + +class CDXBrush; + +class CDXStroke : public CDXUndoCommand +{ +public: + CDXStroke(CDXBrush * brush, CDXEditableMesh * mesh); + + class Info + { + public: + CDXMeshIndex index; + double strength; + double falloff; + }; + + + enum StrokeType { + kStrokeType_None = 0, + kStrokeType_Mask_Add, + kStrokeType_Mask_Subtract, + kStrokeType_Inflate, + kStrokeType_Deflate, + kStrokeType_Move, + kStrokeType_Smooth + }; + + virtual UndoType GetUndoType() = 0; + virtual StrokeType GetStrokeType() = 0; + virtual void Begin(CDXPickInfo & pickInfo) = 0; + virtual void Update(Info * pickInfo) = 0; + virtual void End() = 0; + virtual void Apply(SInt32 i) = 0; + virtual UInt32 Length() = 0; + + CDXVec3 GetOrigin() { return m_origin; } + void SetMirror(bool m) { m_mirror = m; } + bool IsMirror() const { return m_mirror; } + CDXEditableMesh * GetMesh() { return m_mesh; } + +protected: + CDXVec3 m_origin; + CDXEditableMesh * m_mesh; + CDXBrush * m_brush; + bool m_mirror; +}; + +typedef std::shared_ptr CDXStrokePtr; +typedef std::vector CDXStrokeList; + +class CDXBasicStroke : public CDXStroke +{ +public: + CDXBasicStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXStroke(brush, mesh) { }; + + virtual UndoType GetUndoType(); + virtual void Begin(CDXPickInfo & pickInfo); + virtual void End() { }; + virtual void Apply(SInt32 i) { }; +}; + +class CDXBasicHitStroke : public CDXBasicStroke +{ +public: + CDXBasicHitStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXBasicStroke(brush, mesh) { }; + + virtual void Undo(); + virtual void Redo(); + virtual UInt32 Length() { return m_current.size(); } + +protected: + CDXVectorMap m_current; +}; + +class CDXMaskAddStroke : public CDXBasicStroke +{ +public: + CDXMaskAddStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXBasicStroke(brush, mesh) { } + ~CDXMaskAddStroke(); + + virtual StrokeType GetStrokeType(); + virtual void Update(Info * strokeInfo); + virtual void Undo(); + virtual void Redo(); + virtual UInt32 Length() { return m_current.size(); } + +protected: + CDXMaskMap m_previous; + CDXMaskMap m_current; +}; + +class CDXMaskSubtractStroke : public CDXMaskAddStroke +{ +public: + CDXMaskSubtractStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXMaskAddStroke(brush, mesh) { } + + virtual StrokeType GetStrokeType(); + virtual void Update(Info * strokeInfo); +}; + +class CDXInflateStroke : public CDXBasicHitStroke +{ +public: + CDXInflateStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXBasicHitStroke(brush, mesh) { } + ~CDXInflateStroke(); + + class InflateInfo : public CDXStroke::Info + { + public: + CDXVec3 normal; + }; + + virtual StrokeType GetStrokeType(); + virtual void Update(Info * strokeInfo); +}; + +class CDXDeflateStroke : public CDXInflateStroke +{ +public: + CDXDeflateStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXInflateStroke(brush, mesh) { } + + virtual StrokeType GetStrokeType(); + virtual void Update(Info * strokeInfo); +}; + +class CDXSmoothStroke : public CDXBasicHitStroke +{ +public: + CDXSmoothStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXBasicHitStroke(brush, mesh) { } + ~CDXSmoothStroke(); + + virtual StrokeType GetStrokeType(); + virtual void Update(Info * strokeInfo); +}; + + +class CDXMoveStroke : public CDXBasicStroke +{ +public: + CDXMoveStroke(CDXBrush * brush, CDXEditableMesh * mesh) : CDXBasicStroke(brush, mesh) { } + ~CDXMoveStroke(); + + class MoveInfo : public CDXStroke::Info + { + public: + CDXVec3 offset; + }; + + virtual void Begin(CDXPickInfo & pickInfo); + virtual StrokeType GetStrokeType(); + virtual void Update(Info * strokeInfo); + virtual void End(); + virtual void Undo(); + virtual void Redo(); + virtual UInt32 Length() { return m_current.size(); } + + CDXRayInfo & GetRayInfo() { return m_rayInfo; } + + void SetHitIndices(CDXHitIndexMap & indices) { m_hitIndices = indices; } + CDXHitIndexMap & GetHitIndices() { return m_hitIndices; } + +protected: + CDXRayInfo m_rayInfo; + CDXHitIndexMap m_hitIndices; + CDXVectorMap m_previous; + CDXVectorMap m_current; +}; + +#endif + +#endif \ No newline at end of file diff --git a/skee/CDXUndo.cpp b/skee/CDXUndo.cpp new file mode 100644 index 0000000..6c24da9 --- /dev/null +++ b/skee/CDXUndo.cpp @@ -0,0 +1,73 @@ +#ifdef FIXME + +#include "CDXUndo.h" + +CDXUndoStack g_undoStack; + +CDXUndoStack::CDXUndoStack() +{ + m_index = -1; + m_maxStack = 512; +} + +void CDXUndoStack::Release() +{ + m_index = -1; + clear(); +} + +SInt32 CDXUndoStack::Push(CDXUndoCommandPtr action) +{ + CDXUndoStack::iterator actionIt; + int maxState = size() - 1; + if(m_index != maxState) { // Not at the end, erase everything from now til the end + erase(begin() + (m_index + 1), end()); + m_index++; + } else if(size() == m_maxStack) { // Stack is full + erase(begin()); + } else + m_index++; + + push_back(action); + return m_index; +} + +SInt32 CDXUndoStack::Undo(bool doUpdate) +{ + if(m_index > -1) { + if(doUpdate) + at(m_index)->Undo(); + m_index--; + return m_index; + } + return -1; +} + +SInt32 CDXUndoStack::Redo(bool doUpdate) +{ + SInt32 maxState = size() - 1; + if(m_index < maxState) { + m_index++; + if(doUpdate) + at(m_index)->Redo(); + return m_index; + } + return -1; +} + +SInt32 CDXUndoStack::GoTo(SInt32 index, bool doUpdate) +{ + SInt32 result = -1; + SInt32 amount = index - m_index; + + if (amount == 0) + return m_index; + + for (UInt32 i = 0; i < abs(amount); i++) { + result = amount < 0 ? Undo(doUpdate) : Redo(doUpdate); + } + + return result; +} + +#endif \ No newline at end of file diff --git a/skee/CDXUndo.h b/skee/CDXUndo.h new file mode 100644 index 0000000..c165a95 --- /dev/null +++ b/skee/CDXUndo.h @@ -0,0 +1,56 @@ +#ifdef FIXME + +#ifndef __CDXUNDO__ +#define __CDXUNDO__ + +#pragma once + +#include +#include + +class CDXUndoCommand +{ +public: + virtual ~CDXUndoCommand() { }; + + enum UndoType + { + kUndoType_None = 0, + kUndoType_Stroke, + kUndoType_ResetMask, + kUndoType_ResetSculpt, + kUndoType_Import + }; + + virtual UndoType GetUndoType() { return kUndoType_None; } + virtual void Undo() { }; + virtual void Redo() { }; +}; + +typedef std::shared_ptr CDXUndoCommandPtr; + +class CDXUndoStack : public std::vector +{ +public: + CDXUndoStack(); + + SInt32 Undo(bool doUpdate); + SInt32 Redo(bool doUpdate); + SInt32 GoTo(SInt32 index, bool doUpdate); + SInt32 Push(CDXUndoCommandPtr action); + SInt32 GetIndex() const { return m_index; } + + UInt32 GetLimit() const { return m_maxStack; } + + void Release(); + +protected: + SInt32 m_index; + UInt32 m_maxStack; +}; + +extern CDXUndoStack g_undoStack; + +#endif + +#endif \ No newline at end of file diff --git a/skee/FileUtils.cpp b/skee/FileUtils.cpp new file mode 100644 index 0000000..442e147 --- /dev/null +++ b/skee/FileUtils.cpp @@ -0,0 +1,114 @@ +#include "FileUtils.h" + +#include "skse64/GameStreams.h" +#include "skse64/GameData.h" +#include "skse64/GameForms.h" + +// trim from start +namespace std +{ + std::string <rim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + return s; + } + + // trim from end + std::string &rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; + } + + // trim from both ends + std::string &trim(std::string &s) { + return ltrim(rtrim(s)); + } + + // return vector of delimited string + std::vector explode(const std::string& str, const char& ch) { + std::string next; + std::vector result; + + for (std::string::const_iterator it = str.begin(); it != str.end(); it++) { + if (*it == ch) { + if (!next.empty()) { + result.push_back(next); + next.clear(); + } + } + else { + next += *it; + } + } + if (!next.empty()) + result.push_back(next); + return result; + } +} + +bool BSReadLine(BSResourceNiBinaryStream* fin, std::string* str) +{ + char buf[1024]; + UInt32 ret = 0; + + ret = fin->ReadLine((char*)buf, 1024, '\n'); + if (ret > 0) { + buf[ret - 1] = '\0'; + *str = buf; + return true; + } + return false; +} + +void BSReadAll(BSResourceNiBinaryStream* fin, std::string* str) +{ + char ch; + UInt32 ret = fin->Read(&ch, 1); + while (ret > 0) { + str->push_back(ch); + ret = fin->Read(&ch, 1); + } +} + +TESRace * GetRaceByName(std::string & raceName) +{ + DataHandler * dataHandler = DataHandler::GetSingleton(); + if (dataHandler) + { + for (UInt32 i = 0; i < dataHandler->races.count; i++) + { + TESRace * race = NULL; + if (dataHandler->races.GetNthItem(i, race)) { + BSFixedString raceStrName(raceName.c_str()); + if (race->editorId == raceStrName) { + CALL_MEMBER_FN(&raceStrName, Release)(); + return race; + } + CALL_MEMBER_FN(&raceStrName, Release)(); + } + } + } + + return NULL; +} + +BGSHeadPart * GetHeadPartByName(std::string & headPartName) +{ + DataHandler * dataHandler = DataHandler::GetSingleton(); + if (dataHandler) + { + for (UInt32 i = 0; i < dataHandler->headParts.count; i++) + { + BGSHeadPart * headPart = NULL; + if (dataHandler->headParts.GetNthItem(i, headPart)) { + BSFixedString partName(headPartName.c_str()); + if (headPart->partName == partName) { + CALL_MEMBER_FN(&partName, Release)(); + return headPart; + } + CALL_MEMBER_FN(&partName, Release)(); + } + } + } + + return NULL; +} diff --git a/skee/FileUtils.h b/skee/FileUtils.h new file mode 100644 index 0000000..f540d2b --- /dev/null +++ b/skee/FileUtils.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "skse64/GameTypes.h" + +class TESRace; +class BGSHeadPart; +class BSResourceNiBinaryStream; + +namespace std { + extern inline std::string <rim(std::string &s); + extern inline std::string &rtrim(std::string &s); + extern inline std::string &trim(std::string &s); + std::vector explode(const std::string& str, const char& ch); +} + + +bool BSReadLine(BSResourceNiBinaryStream* fin, std::string* str); +void BSReadAll(BSResourceNiBinaryStream* fin, std::string* str); + +TESRace * GetRaceByName(std::string & raceName); +BGSHeadPart * GetHeadPartByName(std::string & headPartName); \ No newline at end of file diff --git a/skee/Hooks_chargen.cpp b/skee/Hooks_chargen.cpp new file mode 100644 index 0000000..6359025 --- /dev/null +++ b/skee/Hooks_chargen.cpp @@ -0,0 +1,1118 @@ +#include "Hooks.h" +#include "MorphHandler.h" +#include "PartHandler.h" + +#include "skse/SafeWrite.h" +#include "skse/NiObjects.h" +#include "skse/GameData.h" +#include "skse/GameRTTI.h" + +#include "common/IFileStream.h" +#include "skse/GameStreams.h" +#include "skse/HashUtil.h" + +#include "skse/NiGeometry.h" + +#include + +bool CacheTempTRI(UInt32 hash, const char * originalPath); + +extern MorphHandler g_morphHandler; +extern PartSet g_partSet; +extern UInt32 g_customDataMax; +extern bool g_externalHeads; +extern bool g_extendedMorphs; +extern bool g_allowAllMorphs; + +static const UInt32 kInstallRegenHeadHook_Base = 0x005A4B80 + 0x49B; +static const UInt32 kInstallRegenHeadHook_Entry_retn = kInstallRegenHeadHook_Base + 0x8; + +enum +{ + kRegenHeadHook_EntryStackOffset1 = 0x20, + kRegenHeadHook_EntryStackOffset2 = 0xA8, + + kRegenHeadHook_VarHeadPart = 0x08, + kRegenHeadHook_VarFaceGenNode = 0x04, + kRegenHeadHook_VarNPC = 0x0C +}; + +void __stdcall ApplyPreset(TESNPC * npc, BSFaceGenNiNode * headNode, BGSHeadPart * headPart) +{ + g_morphHandler.ApplyPreset(npc, headNode, headPart); +} + +__declspec(naked) void InstallRegenHeadHook_Entry(void) +{ + __asm + { + pushad + mov eax, [esp + kRegenHeadHook_EntryStackOffset1 + kRegenHeadHook_EntryStackOffset2 + kRegenHeadHook_VarHeadPart] + push eax + mov eax, [esp + kRegenHeadHook_EntryStackOffset1 + kRegenHeadHook_EntryStackOffset2 + kRegenHeadHook_VarFaceGenNode + 0x04] + push eax + mov eax, [esp + kRegenHeadHook_EntryStackOffset1 + kRegenHeadHook_EntryStackOffset2 + kRegenHeadHook_VarNPC + 0x08] + push eax + call ApplyPreset + popad + + pop edi + pop ebx + add esp, 0xA0 + jmp[kInstallRegenHeadHook_Entry_retn] + } +} + +static const UInt32 kInstallForceRegenHeadHook_Base = 0x0056AEB0 + 0x35; +static const UInt32 kInstallForceRegenHeadHook_Entry_retn = kInstallForceRegenHeadHook_Base + 0x7; // Standard execution + +enum +{ + kForceRegenHeadHook_EntryStackOffset = 0x10, +}; + +bool * g_useFaceGenPreProcessedHeads = (bool*)0x0125D280; + +bool __stdcall IsHeadGenerated(TESNPC * npc) +{ + // For some reason the NPC vanilla preset data is reset when the actor is disable/enabled + auto presetData = g_morphHandler.GetPreset(npc); + if (presetData) { + if (!npc->faceMorph) + npc->faceMorph = (TESNPC::FaceMorphs*)FormHeap_Allocate(sizeof(TESNPC::FaceMorphs)); + + UInt32 i = 0; + for (auto & preset : presetData->presets) { + npc->faceMorph->presets[i] = preset; + i++; + } + + i = 0; + for (auto & morph : presetData->morphs) { + npc->faceMorph->option[i] = morph; + i++; + } + } + return (presetData != NULL) || !(*g_useFaceGenPreProcessedHeads); +} + +__declspec(naked) void InstallForceRegenHeadHook_Entry(void) +{ + __asm + { + push esi + call IsHeadGenerated + cmp al, 1 + jmp[kInstallForceRegenHeadHook_Entry_retn] + } +} + +typedef void(*_LoadActorValues)(); +const _LoadActorValues LoadActorValues = (_LoadActorValues)0x00692FA0; + +#ifdef USE_TRICACHE +void RemoveCachedTRIFiles() +{ + std::vector files; + WIN32_FIND_DATA findFileData; + + std::string cacheDirectory("Data\\meshes\\"); + cacheDirectory.append(MORPH_CACHE_PATH); + std::string findDirectory = cacheDirectory; + findDirectory.append("*"); + + HANDLE hFindFile = FindFirstFile(findDirectory.c_str(), &findFileData); + if (hFindFile) + { + do + { + std::string filePath = findFileData.cFileName; + if (_strnicmp(filePath.substr(filePath.find_last_of(".") + 1).c_str(), "tri", 3) == 0) { + files.push_back(filePath); + } + } while (FindNextFile(hFindFile, &findFileData)); + FindClose(hFindFile); + } + + for (std::vector::iterator it = files.begin(); it != files.end(); ++it) + { + std::string filePath = cacheDirectory; + filePath.append(*it); + _DMESSAGE("Removing cached tri - %s", filePath.c_str()); + DeleteFile(filePath.c_str()); + } +} +#endif + +class ExtendedMorphCache : public MorphMap::Visitor +{ +public: + virtual bool Accept(BSFixedString morphName) + { + g_morphHandler.GetExtendedModelTri(morphName); + return false; + } +}; + +SInt32 GetGameSettingInt(const char * key) +{ + Setting * setting = (*g_gameSettingCollection)->Get(key); + if (setting && setting->GetType() == Setting::kType_Integer) + return setting->data.s32; + + return 0; +} + +void _cdecl LoadActorValues_Hook() +{ + LoadActorValues(); + +#ifdef USE_TRICACHE + RemoveCachedTRIFiles(); +#endif + + DataHandler * dataHandler = DataHandler::GetSingleton(); + if (dataHandler) + { + UInt8 modCount = dataHandler->modList.loadedModCount; + for (UInt32 i = 0; i < modCount; i++) + { + ModInfo * modInfo = dataHandler->modList.loadedMods[i]; + std::string fixedPath = "Meshes\\"; + fixedPath.append(SLIDER_MOD_DIRECTORY); + std::string modPath = modInfo->name; + modPath.append("\\"); + + g_morphHandler.ReadRaces(fixedPath, modPath, "races.ini"); + if (g_extendedMorphs) + g_morphHandler.ReadMorphs(fixedPath, modPath, "morphs.ini"); + + ReadPartReplacements(fixedPath, modPath, "replacements.ini"); + } + + + /*for(MorphMap::iterator it = g_morphHandler.m_morphMap.begin(); it != g_morphHandler.m_morphMap.end(); ++it) + { + std::string beginLine = "extension = "; + beginLine.append(it->first.data); + for(MorphSet::iterator sit = it->second.begin(); sit != it->second.end(); ++sit) + { + beginLine.append(", "); + beginLine.append(sit->data); + } + _MESSAGE("%s", beginLine.c_str()); + }*/ + + if (g_extendedMorphs) { + BGSHeadPart * part = NULL; + for (UInt32 i = 0; i < dataHandler->headParts.count; i++) + { + if (dataHandler->headParts.GetNthItem(i, part)) { + if (g_morphHandler.CacheHeadPartModel(part)) { + + BSFixedString key = part->chargenMorph.GetModelName(); + + // Cache all of the extended morphs + ExtendedMorphCache extendedCache; + g_morphHandler.VisitMorphMap(key, extendedCache); + } + } + } + } + + // Create default slider maps + TESRace * race = NULL; + for (UInt32 i = 0; i < dataHandler->races.count; i++) + { + if (dataHandler->races.GetNthItem(i, race)) { + + if (g_allowAllMorphs) { + for (UInt32 i = 0; i <= 1; i++) { + if (race->chargenData[i]) { + for (UInt32 t = 0; t < FacePresetList::kNumPresets; t++) { + const char * gameSetting = FacePresetList::GetSingleton()->presets[t].data->gameSettingName; + race->chargenData[i]->presetFlags[t][0] = 0xFFFFFFFF; + race->chargenData[i]->presetFlags[t][1] = 0xFFFFFFFF; + race->chargenData[i]->totalPresets[t] = GetGameSettingInt(gameSetting); + } + } + } + } + if ((race->data.raceFlags & TESRace::kRace_FaceGenHead) == TESRace::kRace_FaceGenHead) + g_morphHandler.m_raceMap.CreateDefaultMap(race); + } + } + /* + std::set uniqueTris; + for (UInt32 i = 0; i < dataHandler->headParts.count; i++) + { + BGSHeadPart * headPart = NULL; + if (dataHandler->headParts.GetNthItem(i, headPart)) { + BSFixedString chargenPath(headPart->chargenMorph.GetModelName()); + if (chargenPath == BSFixedString("")) + continue; + uniqueTris.insert(chargenPath); + } + } + + for (auto tri : uniqueTris) + { + std::string lowerStr(tri.data); + std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(), ::tolower); + UInt32 hash = HashUtil::CRC32(lowerStr.c_str()); + + char buffer[0x20]; + memset(buffer, 0, 0x20); + sprintf_s(buffer, 0x20, MORPH_CACHE_TEMPLATE, hash); + + std::string path(MORPH_CACHE_DIR); + path.append(buffer); + BSFixedString cachePath(path.c_str()); + + //CacheTempTRI(hash, tri.data); + + g_morphHandler.m_morphMap.AddMorph(tri, cachePath); + }*/ + } +} + +typedef void(*_ClearFaceGenCache)(); +const _ClearFaceGenCache ClearFaceGenCache = (_ClearFaceGenCache)0x00886B50; + +void _cdecl ClearFaceGenCache_Hooked() +{ + ClearFaceGenCache(); + + g_morphHandler.RevertInternals(); + g_partSet.Revert(); // Cleanup HeadPart List before loading new ones +} + +bool _cdecl CacheTRIFile_Hook(const char * filePath, BSFaceGenDB::TRI::DBTraits::MorphSet ** morphSet, UInt32 * unk1) +{ +#ifdef _DEBUG_HOOK + //_MESSAGE("Caching TRI: '%s'", filePath); +#endif + + bool ret = CacheTRIFile(filePath, morphSet, unk1); +#ifdef __DEBUG + _MESSAGE("External Cache - %s - %s - MorphSet: %08X", ret ? "Failed" : "Succeeded", filePath, morphSet); +#endif + // Add additional morphs here based on morph path instead + +#ifdef __DEBUG + BSFaceGenDB::TRI::DBTraits::MorphSet * foundSet = *morphSet; + if (foundSet) + { + BSFaceGenDB::TRI::DBTraits::MorphData morphEntry; + + _MESSAGE("Set - %s MorphCount: %d Capacity: %d", foundSet->fileName, foundSet->morphData.count, foundSet->morphData.arr.capacity); + + gLog.Indent(); + for (UInt32 n = 0; n < foundSet->morphData.count; n++) { + BSFaceGenDB::TRI::DBTraits::MorphData morphEntry; + if (foundSet->morphData.GetNthItem(n, morphEntry)) { + _MESSAGE("Morph %d - %s baseDiff: %f DiffVertNum: %d BaseVertNum: %d DiffVertPosNum: %d\t\t04 : %d\t0C : %d\t10 : %d", n, morphEntry.morphName, morphEntry.baseDiff, morphEntry.diffVertexNum, morphEntry.baseVertexNum, morphEntry.diffVertexPosNum, morphEntry.unk04, morphEntry.unk0C, morphEntry.unk10); + // for(UInt32 i = 0; i < morphEntry.baseVertexNum; i++) + // { + // _MESSAGE("Vertex %d - X: %d Y: %d Z: %d \t\t Floating Point - X: %f Y: %f Z: %f", i, morphEntry.diffData[i].x, morphEntry.diffData[i].y, morphEntry.diffData[i].z, morphEntry.diffData[i].x * morphEntry.baseDiff, morphEntry.diffData[i].y * morphEntry.baseDiff, morphEntry.diffData[i].z * morphEntry.baseDiff); + // } + } + } + gLog.Outdent(); + } +#endif + +#ifdef USE_TRICACHE + std::string pathStr(filePath); + std::transform(pathStr.begin(), pathStr.end(), pathStr.begin(), ::tolower); + if (pathStr.compare(0, sizeof(MORPH_CACHE_PATH)-1, MORPH_CACHE_PATH) == 0) { + pathStr.erase(0, sizeof(MORPH_CACHE_PATH)-1); + UInt32 hash = 0; + sscanf_s(pathStr.c_str(), "%08X.tri", &hash); + auto morphData = g_morphHandler.GetSculptFromHost(hash); + if (morphData) { + BSFaceGenDB::TRI::DBTraits::MorphSet * foundSet = *morphSet; + if (foundSet) { + g_morphHandler.WriteMorphsToDatabase(morphData, foundSet); + } + } + } +#endif + + return ret; +} + +void TESNPC_Hooked::UpdateMorphs_Hooked(void * unk1, BSFaceGenNiNode * faceNode) +{ + CALL_MEMBER_FN(this, UpdateMorphs)(unk1, faceNode); +#ifdef _DEBUG_HOOK + _DMESSAGE("UpdateMorphs_Hooked - Applying custom morphs"); +#endif + try + { + g_morphHandler.ApplyMorphs(this, faceNode); + } + catch (...) + { + _DMESSAGE("%s - Fatal error", __FUNCTION__); + } +} + +void TESNPC_Hooked::UpdateMorph_Hooked(BGSHeadPart * headPart, BSFaceGenNiNode * faceNode) +{ + CALL_MEMBER_FN(this, UpdateMorph)(headPart, faceNode); +#ifdef _DEBUG_HOOK + _DMESSAGE("UpdateMorph_Hooked - Applying single custom morph"); +#endif + try + { + g_morphHandler.ApplyMorph(this, headPart, faceNode); + } + catch (...) + { + _DMESSAGE("%s - Fatal error", __FUNCTION__); + } +} + +void FxResponseArgsList_Hooked::AddArgument_Hooked(GFxValue * value) +{ + CALL_MEMBER_FN(this, AddArgument)(value); +#ifdef _DEBUG_HOOK + _DMESSAGE("AddArgument_Hooked - Loading Category..."); +#endif + GFxValue arg; + arg.SetString("$EXTRA"); + CALL_MEMBER_FN(this, AddArgument)(&arg); + arg.SetNumber(SLIDER_CATEGORY_EXTRA); + CALL_MEMBER_FN(this, AddArgument)(&arg); + + arg.SetString("$EXPRESSIONS"); + CALL_MEMBER_FN(this, AddArgument)(&arg); + arg.SetNumber(SLIDER_CATEGORY_EXPRESSIONS); + CALL_MEMBER_FN(this, AddArgument)(&arg); +#ifdef _DEBUG_HOOK + _DMESSAGE("AddArgument_Hooked - Loaded Category."); +#endif +} + +UInt32 SliderArray::AddSlider_Hooked(RaceMenuSlider * slider) +{ + UInt32 ret = CALL_MEMBER_FN(this, AddSlider)(slider); +#ifdef _DEBUG_HOOK + _DMESSAGE("AddSlider_Hooked - Loading Sliders..."); +#endif + g_morphHandler.LoadSliders(this, slider); +#ifdef _DEBUG_HOOK + _DMESSAGE("AddSlider_Hooked - Loaded Sliders."); +#endif + return ret; +} + +/*UInt32 RaceSexMenu_Hooked::OpenMenu_Hooked(UInt32 unk1) +{ + UInt32 ret = CALL_MEMBER_FN(this, OpenMenu)(unk1); + + return ret; +}*/ + +#ifdef _DEBUG_HOOK +class DumpPartVisitor : public PartSet::Visitor +{ +public: + bool Accept(UInt32 key, BGSHeadPart * headPart) + { + _DMESSAGE("DumpPartVisitor - Key: %d Part: %s", key, headPart->partName.data); + return false; + } +}; +#endif + +void DataHandler_Hooked::GetValidPlayableHeadParts_Hooked(UInt32 unk1, void * unk2) +{ +#ifdef _DEBUG_HOOK + _DMESSAGE("Reverting Parts:"); + DumpPartVisitor dumpVisitor; + g_partSet.Visit(dumpVisitor); +#endif + + g_partSet.Revert(); // Cleanup HeadPart List before loading new ones + + CALL_MEMBER_FN(this, GetValidPlayableHeadParts)(unk1, unk2); +} + +// Pre-filtered by ValidRace and Gender +UInt8 BGSHeadPart_Hooked::IsPlayablePart_Hooked() +{ + UInt8 ret = CALL_MEMBER_FN(this, IsPlayablePart)(); + +#ifdef _DEBUG_HOOK + _DMESSAGE("IsPlayablePart_Hooked - Reading Part: %08X : %s", this->formID, this->partName.data); +#endif + + if(this->type >= BGSHeadPart::kNumTypes) { + if((this->partFlags & BGSHeadPart::kFlagExtraPart) == 0) { // Skip Extra Parts + if(strcmp(this->model.GetModelName(), "") == 0) + g_partSet.SetDefaultPart(this->type, this); + else + g_partSet.AddPart(this->type, this); + } + return false; // Prevents hanging if the HeadPart is marked as Playable + } + else if ((this->partFlags & BGSHeadPart::kFlagExtraPart) == 0 && ret) + { + // Sets the default part of this type + if (g_partSet.GetDefaultPart(this->type) == NULL) { + auto playeRace = (*g_thePlayer)->race; + if (playeRace) { + TESNPC * npc = DYNAMIC_CAST((*g_thePlayer)->baseForm, TESForm, TESNPC); + UInt8 gender = CALL_MEMBER_FN(npc, GetSex)(); + + auto chargenData = playeRace->chargenData[gender]; + if (chargenData) { + auto headParts = chargenData->headParts; + if (headParts) { + for (UInt32 i = 0; i < headParts->count; i++) { + BGSHeadPart * part; + headParts->GetNthItem(i, part); + if (part->type == this->type) + g_partSet.SetDefaultPart(part->type, part); + } + } + } + } + } + + // maps the pre-existing part to this type + g_partSet.AddPart(this->type, this); + } + + return ret; +} + +#ifdef _DEBUG +#pragma warning (push) +#pragma warning (disable : 4200) +struct RTTIType +{ + void * typeInfo; + UInt32 pad; + char name[0]; +}; + +struct RTTILocator +{ + UInt32 sig, offset, cdOffset; + RTTIType * type; +}; +#pragma warning (pop) + +const char * GetObjectClassName(void * objBase) +{ + const char * result = ""; + + __try + { + void ** obj = (void **)objBase; + RTTILocator ** vtbl = (RTTILocator **)obj[0]; + RTTILocator * rtti = vtbl[-1]; + RTTIType * type = rtti->type; + + // starts with ,? + if((type->name[0] == '.') && (type->name[1] == '?')) + { + // is at most 100 chars long + for(UInt32 i = 0; i < 100; i++) + { + if(type->name[i] == 0) + { + // remove the .?AV + result = type->name + 4; + break; + } + } + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + // return the default + } + + return result; +} + +void DumpClass(void * theClassPtr, UInt32 nIntsToDump) +{ + UInt32* basePtr = (UInt32*)theClassPtr; + _DMESSAGE("DumpClass: %X", basePtr); + + gLog.Indent(); + + if (!theClassPtr) return; + for (UInt32 ix = 0; ix < nIntsToDump; ix++ ) { + UInt32* curPtr = basePtr+ix; + const char* curPtrName = NULL; + UInt32 otherPtr = 0; + float otherFloat = 0.0; + const char* otherPtrName = NULL; + if (curPtr) { + curPtrName = GetObjectClassName((void*)curPtr); + + __try + { + otherPtr = *curPtr; + otherFloat = *(float*)(curPtr); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + // + } + + if (otherPtr) { + otherPtrName = GetObjectClassName((void*)otherPtr); + } + } + + _DMESSAGE("%3d +%03X ptr: 0x%08X: %32s *ptr: 0x%08x | %f: %32s", ix, ix*4, curPtr, curPtrName, otherPtr, otherFloat, otherPtrName); + } + + gLog.Outdent(); +} +#endif + +class MorphVisitor : public MorphMap::Visitor +{ +public: + MorphVisitor::MorphVisitor(BSFaceGenModel * model, const char ** morphName, NiAVObject ** headNode, float relative, UInt8 unk1) + { + m_model = model; + m_morphName = morphName; + m_headNode = headNode; + m_relative = relative; + m_unk1 = unk1; + } + bool Accept(BSFixedString morphName) + { + TRIModelData & morphData = g_morphHandler.GetExtendedModelTri(morphName, true); + if (morphData.morphModel && morphData.triFile) { + NiGeometry * geometry = NULL; + if (m_headNode && (*m_headNode)) + geometry = (*m_headNode)->GetAsNiGeometry(); + + if (geometry) + morphData.triFile->Apply(geometry, *m_morphName, m_relative); + } + //UInt8 ret = CALL_MEMBER_FN(m_model, ApplyMorph)(m_morphName, morphData.morphModel, m_headNode, m_relative, m_unk1); + + /*BSFaceGenDB::TRI::DBTraits::MorphSet * mset = NULL; + UInt32 unk1 = 0; + if (CacheTRIFile_Hook(newMorph->GetModelName(), &mset, &unk1) == 0) { + if (!mset) // Why? + return false; + + auto modelObj = m_model->unk08; + if (modelObj) { + auto morphData = modelObj->unk10; + if (morphData != mset->faceMorphData) { + if (morphData) + morphData->DecRef(); + + modelObj->unk10 = mset->faceMorphData; + if (modelObj->unk10) + modelObj->unk10->IncRef(); + } + morphData = modelObj->unk10; + if (modelObj) { + NiGeometry * geometry = (*m_headNode)->GetAsNiGeometry(); + NiGeometryData * geometryData = niptr_cast(geometry->m_spModelData); + if (geometryData) { + NiGeometryData::Data0 data; + data.unk00 = 0; + data.unk04 = 0; + data.unk08 = 0; + if (CALL_MEMBER_FN(geometryData, Unk1)(1) && CALL_MEMBER_FN(geometryData, Unk2)(&data)) { + CALL_MEMBER_FN(morphData, ApplyMorph)(m_morphName, geometry, m_relative, m_unk1); + CALL_MEMBER_FN(geometryData, Unk3)(1); + } + } + } + } + }*/ + + + return false; + } +private: + BSFaceGenModel * m_model; + const char ** m_morphName; + NiAVObject ** m_headNode; + float m_relative; + UInt8 m_unk1; +}; + +UInt8 BSFaceGenModel_Hooked::ApplyRaceMorph_Hooked(const char ** morphName, TESModelTri * modelMorph, NiAVObject ** headNode, float relative, UInt8 unk1) +{ + //BGSHeadPart * headPart = (BGSHeadPart *)((UInt32)modelMorph - offsetof(BGSHeadPart, raceMorph)); + UInt8 ret = CALL_MEMBER_FN(this, ApplyMorph)(morphName, modelMorph, headNode, relative, unk1); +#ifdef _DEBUG + //_MESSAGE("%08X - Applying %s from %s : %s", this, morphName[0], modelMorph->name.data, headPart->partName.data); +#endif + + try + { + MorphVisitor morphVisitor(this, morphName, headNode, relative, unk1); + g_morphHandler.VisitMorphMap(modelMorph->GetModelName(), morphVisitor); + } + catch (...) + { + _ERROR("%s - fatal error while applying morph (%s)", __FUNCTION__, *morphName); + } + + + + return ret; +} + +#ifdef USE_TRICACHE +// Writes a CustomMorph file for overwriting +bool CacheTempTRI(UInt32 hash, const char * originalPath) +{ + if(originalPath[0] == 0 || hash == 0) { + _ERROR("%s - Invalid hash or path", __FUNCTION__); + return true; + } + + std::string path("meshes\\"); + path.append(MORPH_CACHE_PATH); + + char pathBuffer[MAX_PATH]; + memset(pathBuffer, 0, 0x20); + sprintf_s(pathBuffer, 0x20, "%08X.tri", hash); + path.append(pathBuffer); + + BSFixedString newPath(path.c_str()); + + // Cached file already exists, load it + BSResourceNiBinaryStream cached(newPath.data); + if (cached.IsValid()) { +#ifdef _DEBUG + //_DMESSAGE("%s - Loaded cached TRI %s", __FUNCTION__, newPath.data); +#endif + return false; + } + + memset(pathBuffer, 0, MAX_PATH); + sprintf_s(pathBuffer, MAX_PATH, "meshes\\%s", originalPath); + BSFixedString originalFile(pathBuffer); + + memset(pathBuffer, 0, MAX_PATH); + sprintf_s(pathBuffer, MAX_PATH, "Data\\%s", newPath.data); + BSFixedString fullPath(pathBuffer); + + // Load up original file + BSResourceNiBinaryStream file(originalFile.data); + if (!file.IsValid()) { + _ERROR("%s - Couldn't open morph for reading: %s", __FUNCTION__, originalFile.data); + return true; + } + + IFileStream currentFile; + IFileStream::MakeAllDirs(fullPath.data); + + if(!currentFile.Create(fullPath.data)) + { + _ERROR("%s - Couldn't open morph for writing: %s", __FUNCTION__, fullPath.data); + return true; + } + try + { + char buffer[MAX_PATH]; + memset(buffer, 0, MAX_PATH); + + _DMESSAGE("%s - Writing from %s to %s", __FUNCTION__, originalFile.data, fullPath.data); + + UInt32 vertexNum = 0, faceNum = 0, UVNum = 0, morphNum = 0; + UInt32 baseNum = 0; + + // Header + file.Read(buffer, 0x08); + currentFile.WriteBuf(buffer, 0x08); + + // Vertices + file.Read(&vertexNum, sizeof(vertexNum)); + currentFile.Write32(vertexNum); + + // Faces + file.Read(&faceNum, sizeof(faceNum)); + currentFile.Write32(faceNum); + + // Polyquads, unknown 2, 3 + file.Read(buffer, 0x0C); + currentFile.WriteBuf(buffer, 0x0C); + + // UV + file.Read(&UVNum, sizeof(UVNum)); + currentFile.Write32(UVNum); + + // Flags + file.Read(buffer, 0x04); + currentFile.WriteBuf(buffer, 0x04); + + // Num morphs + file.Read(&morphNum, sizeof(morphNum)); + SInt64 morphOffset = currentFile.GetOffset(); + currentFile.Write32(morphNum); + + // Num modifiers + file.Read(buffer, 0x04); + currentFile.WriteBuf(buffer, 0x04); + + // Mod vertices + file.Read(buffer, 0x04); + currentFile.WriteBuf(buffer, 0x04); + + // Unknown 7-10 + file.Read(buffer, 0x04 * 4); + currentFile.WriteBuf(buffer, 0x04 * 4); + + // Base mesh + for (UInt32 i = 0; i < vertexNum; i++) + { + float x, y, z; + file.Read(&x, sizeof(x)); + currentFile.WriteFloat(x); + file.Read(&y, sizeof(y)); + currentFile.WriteFloat(y); + file.Read(&z, sizeof(z)); + currentFile.WriteFloat(z); + } + + // Faces + for (UInt32 i = 0; i < faceNum; i++) + { + UInt32 x, y, z; + file.Read(&x, sizeof(x)); + currentFile.Write32(x); + file.Read(&y, sizeof(y)); + currentFile.Write32(y); + file.Read(&z, sizeof(z)); + currentFile.Write32(z); + } + + // UV coords + for (UInt32 i = 0; i < UVNum; i++) + { + float x, y; + file.Read(&x, sizeof(x)); + currentFile.WriteFloat(x); + file.Read(&y, sizeof(y)); + currentFile.WriteFloat(y); + } + + // Texture tris + for (UInt32 i = 0; i < faceNum; i++) + { + UInt32 x, y, z; + file.Read(&x, sizeof(x)); + currentFile.Write32(x); + file.Read(&y, sizeof(y)); + currentFile.Write32(y); + file.Read(&z, sizeof(z)); + currentFile.Write32(z); + } + + // Write the morph count + morphNum = g_customDataMax; + SInt64 currentOffset = currentFile.GetOffset(); + currentFile.SetOffset(morphOffset); + currentFile.Write32(morphNum); + currentFile.SetOffset(currentOffset); + + // Write morphs + for(UInt32 i = 0; i < morphNum; i++) + { + memset(buffer, 0, MAX_PATH); + sprintf_s(buffer, MAX_PATH, "CustomMorph%d", i); + UInt32 strLength = strlen(buffer) + 1; + currentFile.Write32(strLength); + currentFile.WriteBuf(buffer, strLength); + currentFile.WriteFloat(0); // Base Diff + + // Write vertex data + for(UInt32 j = 0; j < vertexNum; j++) + { + currentFile.Write16(0); // DiffX + currentFile.Write16(0); // DiffY + currentFile.Write16(0); // DiffZ + } + } + } + catch(...) + { + _ERROR("%s - exception during tri write.", __FUNCTION__); + } + + currentFile.Close(); + return false; +} +#endif + +UInt8 BSFaceGenModel_Hooked::ApplyChargenMorph_Hooked(const char ** morphName, TESModelTri * modelMorph, NiAVObject ** headNode, float relative, UInt8 unk1) +{ +#ifdef _DEBUG + //_MESSAGE("%08X - Applying %s from %s : %s - %f", this, morphName[0], modelMorph->name.data, headPart->partName.data, relative); +#endif + + UInt8 ret = CALL_MEMBER_FN(this, ApplyMorph)(morphName, modelMorph, headNode, relative, unk1); + +#ifdef USE_TRICACHE + std::string lowerStr(modelMorph->GetModelName()); + std::transform(lowerStr.begin(), lowerStr.end(), lowerStr.begin(), ::tolower); + + UInt32 hash = HashUtil::CRC32(lowerStr.c_str()); + CacheTempTRI(hash, lowerStr.c_str()); +#endif + + try + { + MorphVisitor morphVisitor(this, morphName, headNode, relative, unk1); + g_morphHandler.VisitMorphMap(BSFixedString(modelMorph->GetModelName()), morphVisitor); + } + catch (...) + { + _ERROR("%s - fatal error while applying morph (%s)", __FUNCTION__, *morphName); + } + + + + return ret; +} + +void RaceSexMenu_Hooked::DoubleMorphCallback_Hooked(float newValue, UInt32 sliderId) +{ + RaceMenuSlider * slider = NULL; + RaceSexMenu::RaceComponent * raceData = NULL; + + UInt8 gender = 0; + PlayerCharacter * player = (*g_thePlayer); + TESNPC * actorBase = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + BSFaceGenNiNode * faceNode = player->GetFaceGenNiNode(); + + if(raceIndex < sliderData[gender].count) + raceData = &sliderData[gender][raceIndex]; + if(raceData && sliderId < raceData->sliders.count) + slider = &raceData->sliders[sliderId]; + + if(raceData && slider) { +#ifdef _DEBUG_HOOK + _DMESSAGE("Name: %s Value: %f Callback: %s Index: %d", slider->name, slider->value, slider->callback, slider->index); +#endif + if(slider->index >= SLIDER_OFFSET) { + UInt32 sliderIndex = slider->index - SLIDER_OFFSET; + SliderInternalPtr sliderInternal = g_morphHandler.GetSliderByIndex(player->race, sliderIndex); + if(!sliderInternal) + return; + + float currentValue = g_morphHandler.GetMorphValueByName(actorBase, sliderInternal->name); + float relative = newValue - currentValue; + + if(relative == 0.0 && sliderInternal->type != SliderInternal::kTypeHeadPart) { + // Nothing to morph here +#ifdef _DEBUG_HOOK + _DMESSAGE("Skipping Morph %s", sliderInternal->name.data); +#endif + return; + } + + if(sliderInternal->type == SliderInternal::kTypePreset) + { + slider->value = newValue; + + char buffer[MAX_PATH]; + slider->value = newValue; + sprintf_s(buffer, MAX_PATH, "%s%d", sliderInternal->lowerBound.data, (UInt32)currentValue); + g_morphHandler.SetMorph(actorBase, faceNode, buffer, -1.0); + memset(buffer, 0, MAX_PATH); + sprintf_s(buffer, MAX_PATH, "%s%d", sliderInternal->lowerBound.data, (UInt32)newValue); + g_morphHandler.SetMorph(actorBase, faceNode, buffer, 1.0); + + g_morphHandler.SetMorphValue(actorBase, sliderInternal->name, newValue); + return; + } + + if(sliderInternal->type == SliderInternal::kTypeHeadPart) + { + slider->value = newValue; + + UInt8 partType = sliderInternal->presetCount; + + HeadPartList * partList = g_partSet.GetPartList(partType); + if(partList) + { + if(newValue == 0.0) { + BGSHeadPart * oldPart = actorBase->GetCurrentHeadPartByType(partType); + if(oldPart) { + BGSHeadPart * defaultPart = g_partSet.GetDefaultPart(partType); + if(defaultPart && oldPart != defaultPart) { + CALL_MEMBER_FN(actorBase, ChangeHeadPart)(defaultPart); + ChangeActorHeadPart(player, oldPart, defaultPart); + } + } + return; + } + BGSHeadPart * targetPart = g_partSet.GetPartByIndex(partList, (UInt32)newValue - 1); + if(targetPart) { + BGSHeadPart * oldPart = actorBase->GetCurrentHeadPartByType(partType); + if (oldPart != targetPart) { + CALL_MEMBER_FN(actorBase, ChangeHeadPart)(targetPart); + ChangeActorHeadPart(player, oldPart, targetPart); + } + } + } + + return; + } + + + // Cross from positive to negative + if(newValue < 0.0 && currentValue > 0.0) { + // Undo the upper morph + SetRelativeMorph(actorBase, faceNode, sliderInternal->upperBound, -abs(currentValue)); +#ifdef _DEBUG_HOOK + _DMESSAGE("Undoing Upper Morph: New: %f Old: %f Relative %f Remaining %f", newValue, currentValue, relative, relative - currentValue); +#endif + relative = newValue; + } + + // Cross from negative to positive + if(newValue > 0.0 && currentValue < 0.0) { + // Undo the lower morph + SetRelativeMorph(actorBase, faceNode, sliderInternal->lowerBound, -abs(currentValue)); +#ifdef _DEBUG_HOOK + _DMESSAGE("Undoing Lower Morph: New: %f Old: %f Relative %f Remaining %f", newValue, currentValue, relative, relative - currentValue); +#endif + relative = newValue; + } + +#ifdef _DEBUG_HOOK + _DMESSAGE("CurrentValue: %f Relative: %f SavedValue: %f", currentValue, relative, slider->value); +#endif + slider->value = newValue; + + BSFixedString bound = sliderInternal->lowerBound; + if(newValue < 0.0) { + bound = sliderInternal->lowerBound; + relative = -relative; + } else if(newValue > 0.0) { + bound = sliderInternal->upperBound; + } else { + if(currentValue > 0.0) { + bound = sliderInternal->upperBound; + } else { + bound = sliderInternal->lowerBound; + relative = -relative; + } + } + +#ifdef _DEBUG_HOOK + _DMESSAGE("Morphing %d - %s Relative: %f", sliderIndex, bound.data, relative); +#endif + + SetRelativeMorph(actorBase, faceNode, bound, relative); + g_morphHandler.SetMorphValue(actorBase, sliderInternal->name, newValue); + return; + } + } + + CALL_MEMBER_FN(this, DoubleMorphCallback)(newValue, sliderId); +} + +void RaceSexMenu_Hooked::SetRelativeMorph(TESNPC * npc, BSFaceGenNiNode * faceNode, BSFixedString name, float relative) +{ + float absRel = abs(relative); + if(absRel > 1.0) { + float max = 0.0; + if(relative < 0.0) + max = -1.0; + if(relative > 0.0) + max = 1.0; + UInt32 count = (UInt32)absRel; + for(UInt32 i = 0; i < count; i++) { + g_morphHandler.SetMorph(npc, faceNode, name.data, max); + relative -= max; + } + } + g_morphHandler.SetMorph(npc, faceNode, name.data, relative); +} + +#include "CDXNifScene.h" +#include "CDXNifMesh.h" + +#include "CDXCamera.h" + +#include "skse/NiRenderer.h" +#include "skse/NiTextures.h" +#include +#pragma comment(lib, "d3dx9.lib") + + +CDXNifScene g_World; +extern CDXModelViewerCamera g_Camera; // A model viewing camera + +void RaceSexMenu_Hooked::RenderMenu_Hooked(void) +{ + CALL_MEMBER_FN(this, RenderMenu)(); + + LPDIRECT3DDEVICE9 pDevice = NiDX9Renderer::GetSingleton()->m_pkD3DDevice9; + if (!pDevice) // This shouldnt happen + return; + + if(g_World.IsVisible() && g_World.GetTextureGroup()) { + NiRenderedTexture * renderedTexture = g_World.GetTextureGroup()->renderedTexture[0]; + if(renderedTexture) { + g_World.Begin(pDevice); + LPDIRECT3DSURFACE9 oldTarget; + pDevice->GetRenderTarget(0,&oldTarget); + LPDIRECT3DSURFACE9 pRenderSurface = NULL; + LPDIRECT3DTEXTURE9 pRenderTexture = (LPDIRECT3DTEXTURE9)((NiTexture::NiDX9TextureData*)renderedTexture->rendererData)->texture; + pRenderTexture->GetSurfaceLevel(0, &pRenderSurface); + pDevice->SetRenderTarget(0, pRenderSurface); + g_World.Render(pDevice); + pDevice->SetRenderTarget(0, oldTarget); + g_World.End(pDevice); + } + } +} + +void InstallHooks() +{ + WriteRelCall(DATA_ADDR(0x00699100, 0x275), (UInt32)&LoadActorValues_Hook); // Hook for loading initial data on startup + + /*WriteRelCall(DATA_ADDR(0x005A5B70, 0x48), (UInt32)&CacheTRIFile_Hook); // Used to cache internal morph set + WriteRelCall(DATA_ADDR(0x005A5E70, 0x20C), (UInt32)&CacheTRIFile_Hook); + WriteRelCall(DATA_ADDR(0x005A93E0, 0x3A), (UInt32)&CacheTRIFile_Hook);*/ + + WriteRelCall(DATA_ADDR(0x00882290, 0x185), GetFnAddr(&DataHandler_Hooked::GetValidPlayableHeadParts_Hooked)); // Cleans up HeadPart List + WriteRelCall(DATA_ADDR(0x00886C70, 0x97), (UInt32)&ClearFaceGenCache_Hooked); // RaceMenu dtor Cleans up HeadPart List + WriteRelCall(DATA_ADDR(0x00881320, 0x67), GetFnAddr(&BGSHeadPart_Hooked::IsPlayablePart_Hooked)); // Custom head part insertion + + if (g_extendedMorphs) { + WriteRelCall(DATA_ADDR(0x005A4070, 0xBE), GetFnAddr(&BSFaceGenModel_Hooked::ApplyChargenMorph_Hooked)); // Load and apply extended morphs + WriteRelCall(DATA_ADDR(0x005A5CE0, 0x39), GetFnAddr(&BSFaceGenModel_Hooked::ApplyRaceMorph_Hooked)); // Load and apply extended morphs + } + WriteRelCall(DATA_ADDR(0x005A4AC0, 0x67), GetFnAddr(&TESNPC_Hooked::UpdateMorphs_Hooked)); // Updating all morphs when head is regenerated + WriteRelCall(DATA_ADDR(0x005AA230, 0x5C), GetFnAddr(&TESNPC_Hooked::UpdateMorph_Hooked)); // ChangeHeadPart to update morph on single part + WriteRelCall(DATA_ADDR(0x00882290, 0x2E26), GetFnAddr(&SliderArray::AddSlider_Hooked)); // Create Slider + WriteRelCall(DATA_ADDR(0x00881DD0, 0x436), GetFnAddr(&FxResponseArgsList_Hooked::AddArgument_Hooked)); // Add Slider + + WriteRelCall(DATA_ADDR(0x00882290, 0x337C), GetFnAddr(&RaceSexMenu_Hooked::DoubleMorphCallback_Hooked)); // Change Slider OnLoad + WriteRelCall(DATA_ADDR(0x0087FA50, 0x93), GetFnAddr(&RaceSexMenu_Hooked::DoubleMorphCallback_Hooked)); // Change Slider OnCallback + + SafeWrite32(DATA_ADDR(0x010E7404, 0x18), GetFnAddr(&RaceSexMenu_Hooked::RenderMenu_Hooked)); // Hooks RaceMenu renderer + + if (!g_externalHeads) { + WriteRelJump(kInstallRegenHeadHook_Base, (UInt32)&InstallRegenHeadHook_Entry); // FaceGen Regenerate HeadPart Hook + WriteRelJump(kInstallForceRegenHeadHook_Base, (UInt32)&InstallForceRegenHeadHook_Entry); // Preprocessed Head Hook + } +} \ No newline at end of file diff --git a/skee/Hooks_chargen.h b/skee/Hooks_chargen.h new file mode 100644 index 0000000..1610c94 --- /dev/null +++ b/skee/Hooks_chargen.h @@ -0,0 +1,84 @@ +#pragma once + +#include "skse/GameObjects.h" +#include "skse/GameMenus.h" +#include "skse/GameData.h" + +#define CODE_ADDR(x) x +#define DATA_ADDR(x, off) (x + off) + +void InstallHooks(); + +void _cdecl LoadActorValues_Hook(); +void _cdecl ClearFaceGenCache_Hooked(); +bool _cdecl CacheTRIFile_Hook(const char * filePath, BSFaceGenDB::TRI::DBTraits::MorphSet ** morphSet, UInt32 * unk1); + +class TESNPC_Hooked : public TESNPC +{ +public: + MEMBER_FN_PREFIX(TESNPC_Hooked); + DEFINE_MEMBER_FN(UpdateMorphs, void, CODE_ADDR(0x005640C0), void * unk1, BSFaceGenNiNode * faceNode); + DEFINE_MEMBER_FN(UpdateMorph, void, CODE_ADDR(0x00564240), BGSHeadPart * headPart, BSFaceGenNiNode * faceNode); + + void UpdateMorphs_Hooked(void * unk1, BSFaceGenNiNode * faceNode); + void UpdateMorph_Hooked(BGSHeadPart * headPart, BSFaceGenNiNode * faceNode); +}; + +class BGSHeadPart_Hooked : public BGSHeadPart +{ +public: + MEMBER_FN_PREFIX(BGSHeadPart_Hooked); + DEFINE_MEMBER_FN(IsPlayablePart, UInt8, CODE_ADDR(0x0054CD80)); + + UInt8 IsPlayablePart_Hooked(); +}; + +class BSFaceGenModel_Hooked : public BSFaceGenModel +{ +public: + UInt8 ApplyRaceMorph_Hooked(const char ** morphName, TESModelTri * chargenTri, NiAVObject ** headNode, float relative, UInt8 unk1); + UInt8 ApplyChargenMorph_Hooked(const char ** morphName, TESModelTri * chargenTri, NiAVObject ** headNode, float relative, UInt8 unk1); +}; + +class SliderArray : public tArray +{ +public: + MEMBER_FN_PREFIX(SliderArray); + DEFINE_MEMBER_FN(AddSlider, UInt32, CODE_ADDR(0x00880A70), RaceMenuSlider * slider); + + UInt32 AddSlider_Hooked(RaceMenuSlider * slider); +}; + +class FxResponseArgsList_Hooked : public FxResponseArgsList +{ +public: + MEMBER_FN_PREFIX(FxResponseArgsList_Hooked); + DEFINE_MEMBER_FN(AddArgument, void, CODE_ADDR(0x00843150), GFxValue * value); + + void AddArgument_Hooked(GFxValue * value); +}; + +class DataHandler_Hooked : public DataHandler +{ +public: + MEMBER_FN_PREFIX(DataHandler_Hooked); + DEFINE_MEMBER_FN(GetValidPlayableHeadParts, void, CODE_ADDR(0x00881320), UInt32 unk1, void * unk2); + + void GetValidPlayableHeadParts_Hooked(UInt32 unk1, void * unk2); +}; + +class RaceSexMenu_Hooked : public RaceSexMenu +{ +public: + MEMBER_FN_PREFIX(RaceSexMenu_Hooked); + DEFINE_MEMBER_FN(DoubleMorphCallback, void, CODE_ADDR(0x0087F6E0), float value, UInt32 sliderId); + DEFINE_MEMBER_FN(RenderMenu, void, CODE_ADDR(0x00847000)); + + DEFINE_MEMBER_FN(CloseMenu, UInt32, CODE_ADDR(0x00885980), UInt32 unk1, UInt32 unk2); + UInt32 CloseMenu_Hooked(UInt32 unk1, UInt32 unk2); + void RenderMenu_Hooked(void); + + void DoubleMorphCallback_Hooked(float value, UInt32 sliderId); + void SetRelativeMorph(TESNPC * npc, BSFaceGenNiNode * faceNode, BSFixedString name, float relative); + //void SetRelativeMorph(TESNPC * npc, BSFaceGenNiNode * faceNode, UInt32 sliderIndex, UInt32 sliderType, std::string sliderName, float relative); +}; \ No newline at end of file diff --git a/skee/IHashType.h b/skee/IHashType.h new file mode 100644 index 0000000..a9fc559 --- /dev/null +++ b/skee/IHashType.h @@ -0,0 +1,13 @@ +#pragma once + +#include "skse64/GameTypes.h" + +namespace std { + template <> struct hash < BSFixedString > + { + size_t operator()(const BSFixedString & x) const + { + return hash()(x.data); + } + }; +} \ No newline at end of file diff --git a/skee/IPluginInterface.cpp b/skee/IPluginInterface.cpp new file mode 100644 index 0000000..997a6b7 --- /dev/null +++ b/skee/IPluginInterface.cpp @@ -0,0 +1,61 @@ +#include "IPluginInterface.h" + +IPluginInterface::~IPluginInterface() +{ + +} + +UInt32 IPluginInterface::GetVersion() +{ + return 0; +} + +void IPluginInterface::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + +} + +bool IPluginInterface::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + return false; +} + +void IPluginInterface::Revert() +{ + +} + +IPluginInterface * IInterfaceMap::QueryInterface(const char * name) +{ + for (auto iface : *this) + { + if (_stricmp(iface.first, name) == 0) { + return iface.second; + } + } + + return NULL; +} + +bool IInterfaceMap::AddInterface(const char * name, IPluginInterface * pluginInterface) +{ + if (QueryInterface(name) != NULL) + return false; + + emplace(name, pluginInterface); + return true; +} + +IPluginInterface * IInterfaceMap::RemoveInterface(const char * name) +{ + for (auto it = begin(); it != end(); ++it) + { + if (_stricmp(it->first, name) == 0) { + IPluginInterface * foundInterface = it->second; + erase(it); + return foundInterface; + } + } + + return NULL; +} \ No newline at end of file diff --git a/skee/IPluginInterface.h b/skee/IPluginInterface.h new file mode 100644 index 0000000..3668e7f --- /dev/null +++ b/skee/IPluginInterface.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +struct SKSESerializationInterface; + +class IPluginInterface +{ +public: + IPluginInterface() { }; + virtual ~IPluginInterface(); + + virtual UInt32 GetVersion(); + virtual void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + virtual bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); + virtual void Revert(); +}; + +class IInterfaceMap : public std::map +{ +public: + virtual IPluginInterface * QueryInterface(const char * name); + virtual bool AddInterface(const char * name, IPluginInterface * pluginInterface); + virtual IPluginInterface * RemoveInterface(const char * name); +}; + +struct InterfaceExchangeMessage +{ + enum + { + kMessage_ExchangeInterface = 0x9E3779B9 + }; + + IInterfaceMap * interfaceMap = NULL; +}; \ No newline at end of file diff --git a/skee/ItemDataInterface.cpp b/skee/ItemDataInterface.cpp new file mode 100644 index 0000000..fbf3258 --- /dev/null +++ b/skee/ItemDataInterface.cpp @@ -0,0 +1,786 @@ +#include + +#include "skse64/GameRTTI.h" +#include "skse64/GameReferences.h" +#include "skse64/GameObjects.h" +#include "skse64/GameExtraData.h" + +#include "skse64/NiNodes.h" + +#include "skse64/PluginAPI.h" + +#include "skse64/PapyrusEvents.h" + +#include "ItemDataInterface.h" +#include "TintMaskInterface.h" +#include "ShaderUtilities.h" +#include "OverrideInterface.h" +#include "NifUtils.h" + +extern SKSETaskInterface * g_task; +extern SKSEMessagingInterface * g_messaging; +extern TintMaskInterface g_tintMaskInterface; +extern ItemDataInterface g_itemDataInterface; +extern OverrideInterface g_overrideInterface; + +UInt32 ItemDataInterface::GetVersion() +{ + return kCurrentPluginVersion; +} + +bool ModifiedItemFinder::Accept(InventoryEntryData* pEntryData) +{ + if (!pEntryData) + return true; + + ExtendDataList* pExtendList = pEntryData->extendDataList; + if (!pExtendList) + return true; + + SInt32 n = 0; + BaseExtraList* pExtraDataList = pExtendList->GetNthItem(n); + while (pExtraDataList) + { + bool isMatch = false; + bool isWorn = false; + + // Check if the item is worn + bool hasWorn = pExtraDataList->HasType(kExtraData_Worn); + bool hasWornLeft = pExtraDataList->HasType(kExtraData_WornLeft); + if (hasWorn || hasWornLeft) + isWorn = true; + + if ((m_identifier.type & ModifiedItemIdentifier::kTypeRank) == ModifiedItemIdentifier::kTypeRank) { + if (pExtraDataList->HasType(kExtraData_Rank)) { + ExtraRank * extraRank = static_cast(pExtraDataList->GetByType(kExtraData_Rank)); + if (m_identifier.rankId == extraRank->rank) + isMatch = true; + } + } + + if ((m_identifier.type & ModifiedItemIdentifier::kTypeUID) == ModifiedItemIdentifier::kTypeUID) { + if (pExtraDataList->HasType(kExtraData_UniqueID)) { + ExtraUniqueID * extraUID = static_cast(pExtraDataList->GetByType(kExtraData_UniqueID)); + if (m_identifier.uid == extraUID->uniqueId && m_identifier.ownerForm == extraUID->ownerFormId) + isMatch = true; + } + } + + if ((m_identifier.type & ModifiedItemIdentifier::kTypeSlot) == ModifiedItemIdentifier::kTypeSlot) { + if (isWorn) { + TESObjectARMO * armor = DYNAMIC_CAST(pEntryData->type, TESForm, TESObjectARMO); + + // It's armor and we don't have a slot mask search + if (armor && m_identifier.slotMask != 0) { + if ((armor->bipedObject.GetSlotMask() & m_identifier.slotMask) != 0) + isMatch = true; + } + // Is not an armor, we don't have a slot mask search, and it's equipped + else if (!armor && m_identifier.slotMask == 0) { + if ((m_identifier.weaponSlot == ModifiedItemIdentifier::kHandSlot_Left && hasWornLeft) || (m_identifier.weaponSlot == ModifiedItemIdentifier::kHandSlot_Right && hasWorn)) + isMatch = true; + } + } + } + + if (isMatch) { + m_found.pForm = pEntryData->type; + m_found.pExtraData = pExtraDataList; + m_found.isWorn = isWorn; + return false; + } + + n++; + pExtraDataList = pExtendList->GetNthItem(n); + } + + return true; +} + +bool ResolveModifiedIdentifier(TESObjectREFR * reference, ModifiedItemIdentifier & identifier, ModifiedItem & itemData) +{ + if (!reference) + return false; + + if (identifier.IsDirect()) { + itemData.pForm = identifier.form; + itemData.pExtraData = identifier.extraData; + if (reference->baseForm == identifier.form) + identifier.SetSelf(); + return (itemData.pForm && itemData.pExtraData); + } + + if (identifier.IsSelf()) { + itemData.pForm = reference->baseForm; + itemData.pExtraData = &reference->extraData; + return (itemData.pForm && itemData.pExtraData); + } + + ExtraContainerChanges* pContainerChanges = static_cast(reference->extraData.GetByType(kExtraData_ContainerChanges)); + if (pContainerChanges) { + ModifiedItemFinder itemFinder(identifier); + auto data = pContainerChanges->data; + if (data) { + auto objList = data->objList; + if (objList) { + objList->Visit(itemFinder); + itemData = itemFinder.Found(); + return (itemData.pForm && itemData.pExtraData); + } + } + } + return false; +} + +ItemAttributeData * ModifiedItem::GetAttributeData(TESObjectREFR * reference, bool makeUnique, bool allowNewEntry, bool isSelf, UInt32 * idOut) +{ + ExtraRank * rank = NULL; + + if (pExtraData->HasType(kExtraData_Rank)) { + rank = static_cast(pExtraData->GetByType(kExtraData_Rank)); + } else if (makeUnique) { + rank = ExtraRank::Create(); + rank->rank = 0; + pExtraData->Add(kExtraData_Rank, rank); + } + + ExtraUniqueID * uniqueId = NULL; + if (pExtraData->HasType(kExtraData_UniqueID)) { + uniqueId = static_cast(pExtraData->GetByType(kExtraData_UniqueID)); + } else if (makeUnique && !isSelf) { + ExtraContainerChanges* pContainerChanges = static_cast(reference->extraData.GetByType(kExtraData_ContainerChanges)); + if (pContainerChanges) { + auto containerData = pContainerChanges->data; + if (containerData) { + CALL_MEMBER_FN(containerData, SetUniqueID)(pExtraData, NULL, pForm); + uniqueId = static_cast(pExtraData->GetByType(kExtraData_UniqueID)); + } + } + } + else if (!uniqueId && isSelf) { + uniqueId = ExtraUniqueID::Create(); + uniqueId->ownerFormId = reference->formID; + uniqueId->uniqueId = 0; + pExtraData->Add(kExtraData_UniqueID, uniqueId); + } + + if (rank) { + auto data = g_itemDataInterface.GetData(rank->rank); + if (!data && uniqueId && allowNewEntry) { + rank->rank = g_itemDataInterface.GetNextRankID(); + data = g_itemDataInterface.CreateData(rank->rank, uniqueId ? uniqueId->uniqueId : 0, uniqueId ? uniqueId->ownerFormId : 0, pForm->formID); + g_itemDataInterface.UseRankID(); + } + if (idOut) { + *idOut = rank->rank; + } + return data; + } + + return NULL; +} + +ItemAttributeData * ItemDataInterface::GetExistingData(TESObjectREFR * reference, ModifiedItemIdentifier & identifier) +{ + ModifiedItem foundData; + if (ResolveModifiedIdentifier(reference, identifier, foundData)) { + return foundData.GetAttributeData(reference, false, false, identifier.IsSelf()); + } + + return NULL; +} + +NIOVTaskUpdateItemDye::NIOVTaskUpdateItemDye(Actor * actor, ModifiedItemIdentifier & identifier) +{ + m_formId = actor->formID; + m_identifier = identifier; +} + +void NIOVTaskUpdateItemDye::Run() +{ + TESForm * form = LookupFormByID(m_formId); + Actor * actor = DYNAMIC_CAST(form, TESForm, Actor); + if (actor) { + ModifiedItem foundData; + if (ResolveModifiedIdentifier(actor, m_identifier, foundData)) { + ColorMap * overrides = NULL; + ItemAttributeData * data = foundData.GetAttributeData(actor, false, true, m_identifier.IsSelf()); + if (!data) { + _DMESSAGE("%s - Failed to acquire item attribute data", __FUNCTION__); + return; + } + + if (data->m_tintData) + overrides = &data->m_tintData->m_colorMap; + + // Don't bother with visual update if not wearing it + if (!foundData.isWorn) + return; + + TESObjectARMO * armor = DYNAMIC_CAST(foundData.pForm, TESForm, TESObjectARMO); + if (armor) { + for (UInt32 i = 0; i < armor->armorAddons.count; i++) + { + TESObjectARMA * arma = NULL; + if (armor->armorAddons.GetNthItem(i, arma)) { + VisitArmorAddon(actor, armor, arma, [&](bool isFirstPerson, NiAVObject * rootNode, NiAVObject * parent) + { + VisitObjects(parent, [&](NiAVObject* object) + { + if (object->GetAsBSGeometry()) { + g_tintMaskInterface.ApplyMasks(actor, isFirstPerson, armor, arma, object, [&](ColorMap* colorMap) + { + if (overrides) + *colorMap = *overrides; + }); + } + return false; + }); + }); + } + } + } + } + } +} + +UInt32 ItemDataInterface::GetItemUniqueID(TESObjectREFR * reference, ModifiedItemIdentifier & identifier, bool makeUnique) +{ + ModifiedItem foundData; + if (ResolveModifiedIdentifier(reference, identifier, foundData)) { + UInt32 id = 0; + ItemAttributeData * data = foundData.GetAttributeData(reference, makeUnique, true, identifier.IsSelf(), &id); + if (data) { + return id; + } + } + + return 0; +} + +void ItemDataInterface::SetItemDyeColor(UInt32 uniqueID, SInt32 maskIndex, UInt32 color) +{ + ItemAttributeData * data = GetData(uniqueID); + if (data) { + auto tintData = data->m_tintData; + if (!tintData) { + tintData = new ItemAttributeData::TintData; + data->m_tintData = tintData; + } + + tintData->m_colorMap[maskIndex] = color; + } +} + +UInt32 ItemDataInterface::GetItemDyeColor(UInt32 uniqueID, SInt32 maskIndex) +{ + UInt32 color = 0; + ItemAttributeData * data = GetData(uniqueID); + if (data) { + auto tintData = data->m_tintData; + if (tintData) { + auto & it = tintData->m_colorMap.find(maskIndex); + color = it->second; + } + } + + return color; +} + +void ItemDataInterface::ClearItemDyeColor(UInt32 uniqueID, SInt32 maskIndex) +{ + ItemAttributeData * data = GetData(uniqueID); + if (data) { + auto tintData = data->m_tintData; + if (tintData) { + auto & it = tintData->m_colorMap.find(maskIndex); + if (it != tintData->m_colorMap.end()) + tintData->m_colorMap.erase(it); + } + } +} + +ItemAttributeData * ItemDataInterface::GetData(UInt32 rankId) +{ + SimpleLocker lock(&m_lock); + if (rankId == kInvalidRank) + return NULL; + + auto it = std::find_if(m_data.begin(), m_data.end(), [&](ItemAttribute& item) + { + if (std::get(item) == rankId) + return true; + return false; + }); + + if (it != m_data.end()) { + return std::get(*it); + } + + return NULL; +} + +TESForm * ItemDataInterface::GetFormFromUniqueID(UInt32 uniqueID) +{ + SimpleLocker lock(&m_lock); + if (uniqueID == kInvalidRank) + return NULL; + + auto it = std::find_if(m_data.begin(), m_data.end(), [&](ItemAttribute& item) + { + if (std::get(item) == uniqueID) + return true; + return false; + }); + + if (it != m_data.end()) { + TESForm * form = LookupFormByID(std::get(*it)); + return form; + } + + return NULL; +} + +TESForm * ItemDataInterface::GetOwnerOfUniqueID(UInt32 uniqueID) +{ + SimpleLocker lock(&m_lock); + if (uniqueID == kInvalidRank) + return NULL; + + auto it = std::find_if(m_data.begin(), m_data.end(), [&](ItemAttribute& item) + { + if (std::get(item) == uniqueID) + return true; + return false; + }); + + if (it != m_data.end()) { + TESForm * form = LookupFormByID(std::get(*it)); + return form; + } + + return NULL; +} + +ItemAttributeData * ItemDataInterface::CreateData(UInt32 rankId, UInt16 uid, UInt32 ownerId, UInt32 formId) +{ + EraseByRank(rankId); + + ItemAttributeData * data = new ItemAttributeData; + Lock(); + m_data.push_back(std::make_tuple(rankId, uid, ownerId, formId, data)); + Release(); + return data; +} + +bool ItemDataInterface::UpdateUIDByRank(UInt32 rankId, UInt16 uid, UInt32 formId) +{ + SimpleLocker lock(&m_lock); + + auto it = std::find_if(m_data.begin(), m_data.end(), [&](ItemAttribute& item) + { + if (std::get(item) == rankId) { + std::get(item) = uid; + std::get(item) = formId; + return true; + } + + return false; + }); + + return it != m_data.end(); +} + +bool ItemDataInterface::UpdateUID(UInt16 oldId, UInt32 oldFormId, UInt16 newId, UInt32 newFormId) +{ + SimpleLocker lock(&m_lock); + + auto it = std::find_if(m_data.begin(), m_data.end(), [&](ItemAttribute& item) + { + if (std::get(item) == oldId && std::get(item) == oldFormId) { + std::get(item) = newId; + std::get(item) = newFormId; + return true; + } + + return false; + }); + + return it != m_data.end(); +} + +bool ItemDataInterface::EraseByRank(UInt32 rankId) +{ + SimpleLocker lock(&m_lock); + + auto it = std::find_if(m_data.begin(), m_data.end(), [&](ItemAttribute& item) + { + if (std::get(item) == rankId) { + return true; + } + + return false; + }); + + if (it != m_data.end()) { + ItemAttributeData * data = std::get(*it); + if (data) + delete data; + + m_data.erase(it); + return true; + } + + return false; +} + +bool ItemDataInterface::EraseByUID(UInt32 uid, UInt32 formId) +{ + SimpleLocker lock(&m_lock); + + auto it = std::find_if(m_data.begin(), m_data.end(), [&](ItemAttribute& item) + { + if (std::get(item) == uid && std::get(item) == formId) { + return true; + } + + return false; + }); + + if (it != m_data.end()) { + ItemAttributeData * data = std::get(*it); + if (data) { + auto eventDispatcher = static_cast*>(g_messaging->GetEventDispatcher(SKSEMessagingInterface::kDispatcher_ModEvent)); + if (eventDispatcher) { + TESForm * form = LookupFormByID(std::get(*it)); + SKSEModCallbackEvent evn("NiOverride_Internal_EraseUID", "", std::get(*it), form); + eventDispatcher->SendEvent(&evn); + } + delete data; + } + + m_data.erase(it); + return true; + } + + return false; +} + +void ItemDataInterface::Revert() +{ + SimpleLocker lock(&m_lock); + for (auto item : m_data) + { + ItemAttributeData * data = std::get(item); + if (data) + delete data; + } + m_data.clear(); + m_nextRank = 1; +} + +void DyeMap::Revert() +{ + SimpleLocker lock(&m_lock); + m_data.clear(); +} + +bool DyeMap::IsValidDye(TESForm * form) +{ + SimpleLocker lock(&m_lock); + + auto & it = m_data.find(form->formID); + if (it != m_data.end()) { + return true; + } + + return false; +} + +UInt32 DyeMap::GetDyeColor(TESForm * form) +{ + SimpleLocker lock(&m_lock); + + auto & it = m_data.find(form->formID); + if (it != m_data.end()) { + return it->second; + } + + return 0; +} + +void DyeMap::RegisterDyeForm(TESForm * form, UInt32 color) +{ + SimpleLocker lock(&m_lock); + m_data[form->formID] = color; +} + +void DyeMap::UnregisterDyeForm(TESForm * form) +{ + SimpleLocker lock(&m_lock); + auto & it = m_data.find(form->formID); + if (it != m_data.end()) + m_data.erase(it); +} + +void ItemAttributeData::TintData::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 numTints = m_colorMap.size(); + intfc->WriteRecordData(&numTints, sizeof(numTints)); + + for (auto tint : m_colorMap) + { + SInt32 maskIndex = tint.first; + UInt32 maskColor = tint.second; + + intfc->WriteRecordData(&maskIndex, sizeof(maskIndex)); + intfc->WriteRecordData(&maskColor, sizeof(maskColor)); + } +} + +bool ItemAttributeData::TintData::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + bool error = false; + + UInt32 tintCount; + if (!intfc->ReadRecordData(&tintCount, sizeof(tintCount))) + { + _ERROR("%s - Error loading tint count", __FUNCTION__); + error = true; + return error; + } + + for (UInt32 i = 0; i < tintCount; i++) + { + SInt32 maskIndex; + if (!intfc->ReadRecordData(&maskIndex, sizeof(maskIndex))) + { + _ERROR("%s - Error loading mask index", __FUNCTION__); + error = true; + return error; + } + + UInt32 maskColor; + if (!intfc->ReadRecordData(&maskColor, sizeof(maskColor))) + { + _ERROR("%s - Error loading mask color", __FUNCTION__); + error = true; + return error; + } + + m_colorMap[maskIndex] = maskColor; + } + + return error; +} + +void ItemAttributeData::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + if (m_tintData) { + UInt32 numSubrecords = 1; + intfc->WriteRecordData(&numSubrecords, sizeof(numSubrecords)); + + intfc->OpenRecord('TINT', kVersion); + m_tintData->Save(intfc, kVersion); + } + else { + UInt32 numSubrecords = 0; + intfc->WriteRecordData(&numSubrecords, sizeof(numSubrecords)); + } +} + +bool ItemAttributeData::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + // Handle count + UInt32 numSubrecords = 0; + if (!intfc->ReadRecordData(&numSubrecords, sizeof(numSubrecords))) + { + _ERROR("%s - Error loading number of attribute sub records", __FUNCTION__); + error = true; + return error; + } + + for (UInt32 i = 0; i < numSubrecords; i++) + { + if (intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'TINT': + { + ItemAttributeData::TintData * tintData = new ItemAttributeData::TintData; + if (tintData->Load(intfc, kVersion)) + { + delete tintData; + tintData = NULL; + error = true; + return error; + } + m_tintData = tintData; + } + break; + default: + _ERROR("Error loading unexpected chunk type %08X (%.4s)", type, &type); + error = true; + break; + } + } + } + + return error; +} + +void ItemDataInterface::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + intfc->OpenRecord('ITEE', kVersion); + + UInt32 nextRank = m_nextRank; + intfc->WriteRecordData(&nextRank, sizeof(nextRank)); + + UInt32 numItems = m_data.size(); + intfc->WriteRecordData(&numItems, sizeof(numItems)); + + for (auto attribute : m_data) { + intfc->OpenRecord('IDAT', kVersion); + + UInt32 rankId = std::get(attribute); + UInt16 uid = std::get(attribute); + + UInt32 ownerFormId = std::get(attribute); + UInt32 itemFormId = std::get(attribute); + + TESForm * ownerForm = LookupFormByID(ownerFormId); + TESForm * itemForm = LookupFormByID(itemFormId); + + UInt64 ownerHandle = g_overrideInterface.GetHandle(ownerForm, TESForm::kTypeID); + UInt64 itemHandle = g_overrideInterface.GetHandle(itemForm, TESForm::kTypeID); + + intfc->WriteRecordData(&rankId, sizeof(rankId)); + intfc->WriteRecordData(&uid, sizeof(uid)); + intfc->WriteRecordData(&ownerHandle, sizeof(ownerHandle)); + intfc->WriteRecordData(&itemHandle, sizeof(itemHandle)); + + ItemAttributeData * data = std::get(attribute); + if (data) { + data->Save(intfc, kVersion); + } + } +} + +bool ItemDataInterface::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + UInt32 nextRank; + if (!intfc->ReadRecordData(&nextRank, sizeof(nextRank))) + { + _ERROR("%s - Error loading next rank ID", __FUNCTION__); + error = true; + return error; + } + + m_nextRank = nextRank; + + UInt32 numItems; + if (!intfc->ReadRecordData(&numItems, sizeof(numItems))) + { + _ERROR("%s - Error loading number of items", __FUNCTION__); + error = true; + return error; + } + + + for (UInt32 i = 0; i < numItems; i++) + { + if (intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'IDAT': + { + UInt32 rankId; + if (!intfc->ReadRecordData(&rankId, sizeof(rankId))) + { + _ERROR("%s - Error loading Rank ID", __FUNCTION__); + error = true; + return error; + } + + UInt16 uid; + if (!intfc->ReadRecordData(&uid, sizeof(uid))) + { + _ERROR("%s - Error loading Unique ID", __FUNCTION__); + error = true; + return error; + } + + UInt64 ownerHandle = 0; + if (!intfc->ReadRecordData(&ownerHandle, sizeof(ownerHandle))) + { + _ERROR("%s - Error loading owner handle", __FUNCTION__); + error = true; + return error; + } + + UInt64 itemHandle = 0; + if (!intfc->ReadRecordData(&itemHandle, sizeof(itemHandle))) + { + _ERROR("%s - Error loading item handle", __FUNCTION__); + error = true; + return error; + } + + UInt64 newOwnerHandle = 0; + if (!ResolveAnyHandle(intfc, ownerHandle, &newOwnerHandle)) { + return error; + } + + UInt64 newItemHandle = 0; + if (!intfc->ResolveHandle(itemHandle, &newItemHandle)) { + return error; + } + + ItemAttributeData * data = new ItemAttributeData; + if (data->Load(intfc, kVersion)) { + _ERROR("%s - Failed to load item data for owner %016llX item %016llX", __FUNCTION__, ownerHandle, itemHandle); + delete data; + data = NULL; + error = true; + return error; + } + + if (rankId != kInvalidRank) { + UInt32 ownerFormId = newOwnerHandle & 0xFFFFFFFF; + UInt32 itemFormId = newItemHandle & 0xFFFFFFFF; + + Lock(); + m_data.push_back(std::make_tuple(rankId, uid, ownerFormId, itemFormId, data)); + Release(); + + TESForm * ownerForm = LookupFormByID(ownerFormId); + TESForm * itemForm = LookupFormByID(itemFormId); + + Actor * actor = DYNAMIC_CAST(ownerForm, TESForm, Actor); + TESObjectARMO * armor = DYNAMIC_CAST(itemForm, TESForm, TESObjectARMO); + if (actor && armor) { + ModifiedItemIdentifier identifier; + identifier.SetSlotMask(armor->bipedObject.GetSlotMask()); + g_task->AddTask(new NIOVTaskUpdateItemDye(actor, identifier)); + } + } + } + break; + default: + _ERROR("Error loading unexpected chunk type %08X (%.4s)", type, &type); + error = true; + break; + } + } + } + + return error; +} \ No newline at end of file diff --git a/skee/ItemDataInterface.h b/skee/ItemDataInterface.h new file mode 100644 index 0000000..253f5f7 --- /dev/null +++ b/skee/ItemDataInterface.h @@ -0,0 +1,224 @@ +#pragma once + +struct SKSESerializationInterface; +class Actor; +class ItemAttributeData; + +#include "skse64/GameThreads.h" +#include "skse64/GameExtraData.h" + +#include "IPluginInterface.h" + +#include +#include +#include + +typedef std::map ColorMap; + +struct ModifiedItem +{ + ModifiedItem::ModifiedItem() + { + pForm = NULL; + pExtraData = NULL; + isWorn = false; + } + TESForm * pForm; + BaseExtraList * pExtraData; + bool isWorn; + + operator bool() const + { + return (pForm && pExtraData); + } + + ItemAttributeData * GetAttributeData(TESObjectREFR * reference, bool makeUnique = true, bool allowNewEntry = true, bool allowSelf = false, UInt32 * idOut = NULL); +}; + +struct ModifiedItemIdentifier +{ + enum + { + kTypeNone = 0, + kTypeRank = (1 << 0), + kTypeUID = (1 << 1), + kTypeSlot = (1 << 2), + kTypeSelf = (1 << 3), + kTypeDirect = (1 << 4) + }; + + enum + { + kHandSlot_Left = 0, + kHandSlot_Right + }; + + UInt16 type = kTypeNone; + UInt16 uid = 0; + UInt32 ownerForm = 0; + UInt32 weaponSlot = 0; + UInt32 slotMask = 0; + UInt32 rankId = 0; + TESForm * form = NULL; + BaseExtraList * extraData = NULL; + + void SetRankID(UInt32 _rank) + { + type |= kTypeRank; + rankId = _rank; + } + void SetSlotMask(UInt32 _slotMask, UInt32 _weaponSlot = 0) + { + type |= kTypeSlot; + slotMask = _slotMask; + weaponSlot = _weaponSlot; + } + void SetUniqueID(UInt16 _uid, UInt32 _ownerForm) + { + type |= kTypeUID; + uid = _uid; + ownerForm = _ownerForm; + } + void SetDirect(TESForm * _baseForm, BaseExtraList * _extraData) + { + type |= kTypeDirect; + form = _baseForm; + extraData = _extraData; + } + + bool IsDirect() + { + return (type & kTypeDirect) == kTypeDirect; + } + + bool IsSelf() + { + return (type & kTypeSelf) == kTypeSelf; + } + + void SetSelf() + { + type |= kTypeSelf; + } +}; + +class ModifiedItemFinder +{ +public: + ModifiedItemFinder(ModifiedItemIdentifier & identifier) : m_identifier(identifier) { } + bool Accept(InventoryEntryData* pEntryData); + ModifiedItem& Found() { + return m_found; + }; +private: + ModifiedItemIdentifier m_identifier; + ModifiedItem m_found; +}; + +class ItemAttributeData +{ +public: + ItemAttributeData::~ItemAttributeData() + { + if (m_tintData) { + delete m_tintData; + m_tintData = NULL; + } + } + + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); + + class TintData + { + public: + TintData::~TintData() + { + m_colorMap.clear(); + } + + ColorMap m_colorMap; + + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); + }; + + TintData * m_tintData = NULL; +}; + + +static const UInt32 ITEM_ATTRIBUTE_RANK = 0; +static const UInt32 ITEM_ATTRIBUTE_UID = 1; +static const UInt32 ITEM_ATTRIBUTE_OWNERFORM = 2; +static const UInt32 ITEM_ATTRIBUTE_FORMID = 3; +static const UInt32 ITEM_ATTRIBUTE_DATA = 4; + +typedef std::tuple ItemAttribute; + +class ItemDataInterface : public SafeDataHolder>, public IPluginInterface +{ +public: + typedef std::vector Data; + + enum + { + kCurrentPluginVersion = 1, + kSerializationVersion1 = 1, + kSerializationVersion = kSerializationVersion1 + }; + virtual UInt32 GetVersion(); + + virtual void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + virtual bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); + virtual void Revert(); + + virtual UInt32 GetItemUniqueID(TESObjectREFR * reference, ModifiedItemIdentifier & identifier, bool makeUnique); + virtual void SetItemDyeColor(UInt32 uniqueID, SInt32 maskIndex, UInt32 color); + virtual UInt32 GetItemDyeColor(UInt32 uniqueID, SInt32 maskIndex); + virtual void ClearItemDyeColor(UInt32 uniqueID, SInt32 maskIndex); + virtual TESForm * GetFormFromUniqueID(UInt32 uniqueID); + virtual TESForm * GetOwnerOfUniqueID(UInt32 uniqueID); + + ItemAttributeData * GetExistingData(TESObjectREFR * reference, ModifiedItemIdentifier & identifier); + ItemAttributeData * CreateData(UInt32 rankId, UInt16 uid, UInt32 ownerId, UInt32 formId); + ItemAttributeData * GetData(UInt32 rankId); + bool UpdateUIDByRank(UInt32 rankId, UInt16 uid, UInt32 formId); + bool UpdateUID(UInt16 oldId, UInt32 oldFormId, UInt16 newId, UInt32 newFormId); + bool EraseByRank(UInt32 rankId); + bool EraseByUID(UInt32 uid, UInt32 formId); + + enum + { + kInvalidRank = 0 + }; + + void UseRankID() { m_nextRank++; } + UInt32 GetNextRankID() const { return m_nextRank; } + UInt32 m_nextRank = 1; +}; + +class DyeMap : public SafeDataHolder> +{ +public: + typedef std::unordered_map Data; + + UInt32 GetDyeColor(TESForm * form); + bool IsValidDye(TESForm * form); + void RegisterDyeForm(TESForm * form, UInt32 color); + void UnregisterDyeForm(TESForm * form); + void Revert(); +}; + +class NIOVTaskUpdateItemDye : public TaskDelegate +{ +public: + NIOVTaskUpdateItemDye::NIOVTaskUpdateItemDye(Actor * actor, ModifiedItemIdentifier & identifier); + virtual void Run(); + virtual void Dispose() { + delete this; + }; + +private: + UInt32 m_formId; + ModifiedItemIdentifier m_identifier; +}; \ No newline at end of file diff --git a/skee/MorphHandler.cpp b/skee/MorphHandler.cpp new file mode 100644 index 0000000..c707c3d --- /dev/null +++ b/skee/MorphHandler.cpp @@ -0,0 +1,3179 @@ +#include "common/IFileStream.h" +#include "skse64_common/skse_version.h" + +#include "skse64/PluginAPI.h" + +#include "skse64/GameAPI.h" +#include "skse64/GameData.h" +#include "skse64/GameObjects.h" +#include "skse64/GameMenus.h" +#include "skse64/GameRTTI.h" +#include "skse64/GameResources.h" +#include "skse64/GameStreams.h" + +#include "skse64/NiNodes.h" +#include "skse64/NiGeometry.h" +#include "skse64/NiMaterial.h" +#include "skse64/NiProperties.h" +#include "skse64/NiExtraData.h" +#include "skse64/NiSerialization.h" + +#include "skse64/ScaleformMovie.h" + +#include "skse64/HashUtil.h" + +#include "MorphHandler.h" +#include "PartHandler.h" +#include "ScaleformFunctions.h" +#include "NifUtils.h" +#include "SKEEHooks.h" + +#include "OverrideVariant.h" +#include "OverrideInterface.h" +#include "NiTransformInterface.h" +#include "BodyMorphInterface.h" +#include "OverlayInterface.h" + +extern OverrideInterface g_overrideInterface; +extern NiTransformInterface g_transformInterface; +extern BodyMorphInterface g_bodyMorphInterface; +extern OverlayInterface g_overlayInterface; + +#include +#include + +extern float g_sliderMultiplier; +extern float g_sliderInterval; +extern PartSet g_partSet; +extern MorphHandler g_morphHandler; +extern std::string g_raceTemplate; +extern bool g_extendedMorphs; + +extern SKSEMessagingInterface * g_messaging; +extern PluginHandle g_pluginHandle; + +void RaceMap::Revert() +{ + clear(); +} + +void MorphHandler::Revert() +{ + m_valueMap.clear(); + m_sculptStorage.clear(); +} + +void MorphHandler::RevertInternals() +{ + m_internalMap.clear(); +} + +bool SliderSet::for_each_slider(std::function func) +{ + // Iterate the list of SliderSet + for (auto rit = begin(); rit != end(); ++rit) + { + // Iterate the SliderMap + for (auto it = (*rit)->begin(); it != (*rit)->end(); ++it) + { + if (func(it->second)) + return true; + } + } + + return false; +} + +SliderSetPtr RaceMap::GetSliderSet(TESRace * race) +{ + RaceMap::iterator it = find(race); + if(it != end()) + return it->second; + + return NULL; +} + +SInt32 ReadTRIVertexCount(const char * triPath) +{ + if(triPath[0] == 0) { + return -1; + } + + char filePath[MAX_PATH]; + memset(filePath, 0, MAX_PATH); + sprintf_s(filePath, MAX_PATH, "Meshes\\%s", triPath); + BSFixedString newPath(filePath); + + // Cached file already exists, load it + BSResourceNiBinaryStream file(newPath.data); + if (!file.IsValid()) { + return -1; + } + + char header[0x08]; + file.Read(header, 0x08); + if(strncmp(header, "FRTRI003", 8) != 0) + return -1; + + UInt32 vertexNum = 0; + file.Read(&vertexNum, sizeof(vertexNum)); + return vertexNum; +} + + +bool TRIFile::Load(const char * triPath) +{ + if (triPath[0] == 0) { + return -1; + } + + char filePath[MAX_PATH]; + memset(filePath, 0, MAX_PATH); + sprintf_s(filePath, MAX_PATH, "Meshes\\%s", triPath); + BSFixedString newPath(filePath); + + BSResourceNiBinaryStream file(newPath.data); + if (!file.IsValid()) { + return false; + } + + char header[0x08]; + file.Read(header, 0x08); + if (strncmp(header, "FRTRI003", 8) != 0) + return false; + + file.Read(&vertexCount, sizeof(vertexCount)); + + UInt32 polytris = 0, polyquads = 0, unk2 = 0, unk3 = 0, + uvverts = 0, flags = 0, numMorphs = 0, numMods = 0, + modVerts = 0, unk7 = 0, unk8 = 0, unk9 = 0, unk10 = 0; + + file.Read(&polytris, sizeof(polytris)); + file.Read(&polyquads, sizeof(polyquads)); + file.Read(&unk2, sizeof(unk2)); + file.Read(&unk3, sizeof(unk3)); + file.Read(&uvverts, sizeof(uvverts)); + file.Read(&flags, sizeof(flags)); + file.Read(&numMorphs, sizeof(numMorphs)); + file.Read(&numMods, sizeof(numMods)); + file.Read(&modVerts, sizeof(modVerts)); + file.Read(&unk7, sizeof(unk7)); + file.Read(&unk8, sizeof(unk8)); + file.Read(&unk9, sizeof(unk9)); + file.Read(&unk10, sizeof(unk10)); + + // Skip reference verts + file.Seek(vertexCount * 3 * sizeof(float)); + + // Skip polytris + file.Seek(polytris * 3 * sizeof(UInt32)); + + // Skip UV + if (uvverts > 0) + file.Seek(uvverts * 2 * sizeof(float)); + + // Skip text coords + file.Seek(polytris * 3 * sizeof(UInt32)); + + for (UInt32 i = 0; i < numMorphs; i++) + { + UInt32 strLen = 0; + file.Read(&strLen, sizeof(strLen)); + + char * name = new char[strLen+1]; + for (UInt32 l = 0; l < strLen; l++) + { + file.Read(&name[l], sizeof(char)); + } + name[strLen] = 0; + + float mult = 0.0f; + file.Read(&mult, sizeof(mult)); + + Morph morph; + morph.name = BSFixedString(name); + morph.multiplier = mult; + + for (UInt32 v = 0; v < vertexCount; v++) + { + Morph::Vertex vert; + file.Read(&vert, sizeof(vert)); + morph.vertices.push_back(vert); + } + + morphs.insert(std::make_pair(morph.name, morph)); + } + + return true; +} + +bool TRIFile::Apply(BSGeometry * geometry, BSFixedString morphName, float relative) +{ + BSFaceGenBaseMorphExtraData * extraData = (BSFaceGenBaseMorphExtraData *)geometry->GetExtraData("FOD"); + if (!extraData) + return false; + + // Found morph doesn't match the cached morph + if (extraData->vertexCount != vertexCount) + return false; + + // Morph name wasn't found + auto & morph = morphs.find(morphName); + if (morph == morphs.end()) + return false; + + // What? + if (extraData->vertexCount != morph->second.vertices.size()) + return false; + + UInt32 size = morph->second.vertices.size(); + for (UInt32 i = 0; i < size; i++) + { + auto & vert = morph->second.vertices.at(i); + extraData->vertexData[i].x += (float)((double)vert.x * (double)morph->second.multiplier * (double)relative); + extraData->vertexData[i].y += (float)((double)vert.y * (double)morph->second.multiplier * (double)relative); + extraData->vertexData[i].z += (float)((double)vert.z * (double)morph->second.multiplier * (double)relative); + } + + UpdateModelFace(geometry); + return true; +} + +class RacePartVisitor +{ +public: + virtual bool Accept(BGSHeadPart * headPart) = 0; +}; + +void VisitRaceParts(TESRace * race, UInt32 gender, RacePartVisitor & visitor) +{ + TESRace::CharGenData * chargenData = race->chargenData[gender]; + if(chargenData) { + tArray * headParts = chargenData->headParts; + if(headParts) { + for(UInt32 i = 0; i < headParts->count; i++) { + BGSHeadPart* headPart = NULL; + if(headParts->GetNthItem(i, headPart)) { + if(visitor.Accept(headPart)) + break; + } + } + } + } +} + +class RacePartByType : public RacePartVisitor +{ +public: + RacePartByType::RacePartByType(UInt32 partType) : m_type(partType), m_headPart(NULL) {} + virtual bool Accept(BGSHeadPart * headPart) + { + if(headPart->type == m_type) { + m_headPart = headPart; + return true; + } + + return false; + } + + UInt32 m_type; + BGSHeadPart * m_headPart; +}; + +class RacePartDefaultGen : public RacePartVisitor +{ +public: + RacePartDefaultGen::RacePartDefaultGen(TESRace * sourceRace, TESRace * targetRace, std::vector * parts, UInt32 gender) : m_sourceRace(sourceRace), m_targetRace(targetRace), m_gender(gender), m_partList(parts), m_acceptDefault(false) {} + + virtual bool Accept(BGSHeadPart * headPart) + { + BSFixedString sourceMorphPath(headPart->chargenMorph.GetModelName()); + if(sourceMorphPath == BSFixedString("")) + return false; + + auto it = std::find(m_partList->begin(), m_partList->end(), sourceMorphPath); + if (it != m_partList->end()) { // Found part tri file + if(headPart->type == BGSHeadPart::kTypeFace) + m_acceptDefault = true; + } else { + RacePartByType racePart(headPart->type); + VisitRaceParts(m_targetRace, m_gender, racePart); + + BGSHeadPart * targetPart = racePart.m_headPart; + if(targetPart) { + BSFixedString targetMorphPath(targetPart->chargenMorph.GetModelName()); + if(targetMorphPath == BSFixedString("")) { + _ERROR("%s - Could not bind default morphs for %s on %s[%d] using %s. No valid morph path.", __FUNCTION__, headPart->partName.data, m_sourceRace->editorId.data, m_gender, m_targetRace->editorId.data, sourceMorphPath.data); + return false; + } + + TRIModelData sourceData, targetData; + g_morphHandler.GetModelTri(sourceMorphPath, sourceData); + g_morphHandler.GetModelTri(targetMorphPath, targetData); + + if (sourceData.vertexCount == targetData.vertexCount && sourceData.vertexCount > 0 && targetData.vertexCount > 0) { + // Bind additional morphs here, the source and target morphs are identical + _DMESSAGE("%s - Binding default morphs for %s (%s) on %s[%d] using %s. (%s - %d | %s - %d)", __FUNCTION__, headPart->partName.data, sourceMorphPath.data, m_sourceRace->editorId.data, m_gender, m_targetRace->editorId.data, sourceMorphPath.data, sourceData.vertexCount, targetMorphPath.data, targetData.vertexCount); + auto titer = g_morphHandler.m_morphMap.find(targetMorphPath); + if(titer != g_morphHandler.m_morphMap.end()) + g_morphHandler.m_morphMap.emplace(sourceMorphPath, titer->second); + if(headPart->type == BGSHeadPart::kTypeFace) + m_acceptDefault = true; + } else if(sourceData.vertexCount == 0 || targetData.vertexCount == 0) { + _ERROR("%s - Could not bind default morphs for %s on %s[%d] using %s. Invalid vertex count (%s - %d | %s - %d).", __FUNCTION__, headPart->partName.data, m_sourceRace->editorId.data, m_gender, m_targetRace->editorId.data, sourceMorphPath.data, sourceData.vertexCount, targetMorphPath.data, targetData.vertexCount); + } else { + _ERROR("%s - Could not bind default morphs for %s on %s[%d] using %s. Vertex mismatch (%s - %d | %s - %d).", __FUNCTION__, headPart->partName.data, m_sourceRace->editorId.data, m_gender, m_targetRace->editorId.data, sourceMorphPath.data, sourceData.vertexCount, targetMorphPath.data, targetData.vertexCount); + } + } + } + + return false; + } + + UInt32 m_gender; + TESRace * m_sourceRace; + TESRace * m_targetRace; + std::vector * m_partList; + bool m_acceptDefault; +}; + +class RacePartFiles : public RacePartVisitor +{ +public: + RacePartFiles::RacePartFiles(std::vector * parts) : m_parts(parts) {} + virtual bool Accept(BGSHeadPart * headPart) + { + if (headPart->chargenMorph.name.data != BSFixedString("").data) + m_parts->push_back(headPart->chargenMorph.name); + if (headPart->raceMorph.name.data != BSFixedString("").data) + m_parts->push_back(headPart->raceMorph.name); + + return false; + } + + std::vector * m_parts; +}; + +bool RaceMap::CreateDefaultMap(TESRace * race) +{ + TESRace * templateRace = GetRaceByName(g_raceTemplate); + RaceMap::iterator rit = find(templateRace); + if(rit != end()) { // Found NordRace slider maps + + std::vector templateFiles; + for (UInt32 gender = 0; gender <= 1; gender++) { + RacePartFiles parts(&templateFiles); + VisitRaceParts(templateRace, gender, parts); + } + + bool acceptDefault = false; + for(UInt32 gender = 0; gender <= 1; gender++) { // Iterate genders + RacePartDefaultGen defaultGen(race, templateRace, &templateFiles, gender); + VisitRaceParts(race, gender, defaultGen); + acceptDefault = defaultGen.m_acceptDefault; + } + + if(acceptDefault) { + SliderSetPtr sliderMaps = rit->second; + if(sliderMaps->size() > 0) { + UInt32 addedMaps = 0; + for(auto smit = sliderMaps->begin(); smit != sliderMaps->end(); ++smit) { + if(AddSliderMap(race, *smit)) + addedMaps++; + } + if(addedMaps > 0) { + _DMESSAGE("%s - Added default slider maps for %s from %s", __FUNCTION__, race->editorId.data, rit->first->editorId.data); + return true; + } + } + } + } + + return false; +} + +bool RaceMap::AddSliderMap(TESRace * race, SliderMapPtr sliderMap) +{ + RaceMap::iterator it = find(race); + if(it != end()) { + //std::pair ret; + auto ret = it->second->insert(sliderMap); + return ret.second; + } else { + SliderSetPtr sliderMaps = std::make_shared(); + sliderMaps->insert(sliderMap); + emplace(race, sliderMaps); + return true; + } + + return false; +} + +SliderInternalPtr MorphHandler::GetSliderByIndex(TESRace * race, UInt32 index) +{ + RaceSliders::iterator it = m_internalMap.find(race); + if(it != m_internalMap.end()) { + if(index < it->second.size()) + return it->second.at(index); + } + return NULL; +} + +float ValueMap::GetMorphValueByName(TESNPC* npc, BSFixedString name) +{ + ValueMap::iterator it = find(npc); + if(it != end()) { + return it->second.GetValue(name); + } + + return 0.0; +} + +void ValueMap::SetMorphValue(TESNPC* npc, BSFixedString name, float value) +{ + ValueMap::iterator it = find(npc); + if(it != end()) { + it->second.SetValue(name, value); + } else { + ValueSet newSet; + newSet.emplace(name, value); + emplace(npc, newSet); + } +} + +void ValueSet::SetValue(BSFixedString name, float value) +{ + ValueSet::iterator val = find(name); + if (val != end()) + val->second = value; + else + emplace(name, value); +} + +void ValueSet::ClearValue(BSFixedString name) +{ + ValueSet::iterator val = find(name); + if(val != end()) + erase(val); +} + +float ValueSet::GetValue(BSFixedString name) +{ + ValueSet::iterator val = find(name); + if(val != end()) + return val->second; + + return 0.0; +} + +ValueSet * ValueMap::GetValueSet(TESNPC* npc) +{ + ValueMap::iterator it = find(npc); + if(it != end()) { + return &it->second; + } + + return NULL; +} + +void ValueMap::EraseNPC(TESNPC * npc) +{ + auto it = find(npc); + if (it != end()) + erase(it); +} + +float MorphHandler::GetMorphValueByName(TESNPC* npc, BSFixedString name) +{ + return m_valueMap.GetMorphValueByName(npc, name); +} + +void MorphHandler::SetMorphValue(TESNPC* npc, BSFixedString name, float value) +{ + return m_valueMap.SetMorphValue(npc, name, value); +} + +void MorphMap::Revert() +{ + clear(); +} + +class ExtendedMorphCache : public MorphMap::Visitor +{ +public: + virtual bool Accept(BSFixedString morphName) + { + g_morphHandler.GetExtendedModelTri(morphName); + return false; + } +}; + +void MorphHandler::LoadMods() +{ + DataHandler * dataHandler = DataHandler::GetSingleton(); + if (dataHandler) + { + UInt8 modCount = dataHandler->modList.loadedMods.count; + for (UInt32 i = 0; i < modCount; i++) + { + ModInfo * modInfo = dataHandler->modList.loadedMods[i]; + std::string fixedPath = "Meshes\\"; + fixedPath.append(SLIDER_MOD_DIRECTORY); + std::string modPath = modInfo->name; + modPath.append("\\"); + + ReadRaces(fixedPath, modPath, "races.ini"); + if (g_extendedMorphs) + ReadMorphs(fixedPath, modPath, "morphs.ini"); + + ReadPartReplacements(fixedPath, modPath, "replacements.ini"); + } + + if (g_extendedMorphs) { + BGSHeadPart * part = NULL; + for (UInt32 i = 0; i < dataHandler->headParts.count; i++) + { + if (dataHandler->headParts.GetNthItem(i, part)) { + if (CacheHeadPartModel(part)) { + + BSFixedString key = part->chargenMorph.GetModelName(); + + // Cache all of the extended morphs + ExtendedMorphCache extendedCache; + VisitMorphMap(key, extendedCache); + } + } + } + } + + // Create default slider maps + TESRace * race = NULL; + for (UInt32 i = 0; i < dataHandler->races.count; i++) + { + if (dataHandler->races.GetNthItem(i, race)) { + +#ifdef FIXME + if (g_allowAllMorphs) { + for (UInt32 i = 0; i <= 1; i++) { + if (race->chargenData[i]) { + for (UInt32 t = 0; t < FacePresetList::kNumPresets; t++) { + const char * gameSetting = FacePresetList::GetSingleton()->presets[t].data->gameSettingName; + race->chargenData[i]->presetFlags[t][0] = 0xFFFFFFFF; + race->chargenData[i]->presetFlags[t][1] = 0xFFFFFFFF; + race->chargenData[i]->totalPresets[t] = GetGameSettingInt(gameSetting); + } + } + } + } +#endif + if ((race->data.raceFlags & TESRace::kRace_FaceGenHead) == TESRace::kRace_FaceGenHead) + m_raceMap.CreateDefaultMap(race); + } + } + } +} + +bool MorphHandler::CacheHeadPartModel(BGSHeadPart * headPart, bool cacheTRI) +{ + BSFixedString modelPath = headPart->chargenMorph.GetModelName(); + if (modelPath == BSFixedString("")) + return false; + + ModelMap::iterator it = m_modelMap.find(modelPath); + if (it == m_modelMap.end()) { + TRIModelData data; + + data.morphModel = &headPart->chargenMorph; + if (!cacheTRI) { + data.vertexCount = ReadTRIVertexCount(modelPath.data); + } + else { + data.triFile = std::make_shared(); + data.triFile->Load(modelPath.data); + data.vertexCount = data.triFile->vertexCount; + } + + m_modelMap.emplace(modelPath, data); + } + else if (cacheTRI && !it->second.triFile) { + it->second.triFile = std::make_shared(); + it->second.triFile->Load(modelPath.data); + } + + return true; +} + +bool MorphHandler::GetModelTri(BSFixedString filePath, TRIModelData & modelData) +{ + ModelMap::iterator it = m_modelMap.find(filePath); + if (it != m_modelMap.end()) { + modelData = it->second; + return true; + } + + return false; +} + +TRIModelData & MorphHandler::GetExtendedModelTri(BSFixedString morphName, bool cacheTRI) +{ + std::string filePath(SLIDER_DIRECTORY); + filePath.append(morphName.data); + BSFixedString morphFile(filePath.c_str()); + ModelMap::iterator it = m_modelMap.find(morphFile); + if(it == m_modelMap.end()) { + void* memory = Heap_Allocate(sizeof(TESModelTri)); + memset(memory, 0, sizeof(TESModelTri)); + ((uintptr_t*)memory)[0] = TESModelTri_vtbl.GetUIntPtr(); + TESModelTri* xData = (TESModelTri*)memory; + xData->SetModelName(morphFile.data); + + TRIModelData data; + data.morphModel = xData; + if (!cacheTRI) { + data.vertexCount = ReadTRIVertexCount(morphFile.data); + } + else { + data.triFile = std::make_shared(); + data.triFile->Load(morphFile.data); + data.vertexCount = data.triFile->vertexCount; + } + + auto ret = m_modelMap.emplace(morphFile, data); + return ret.first->second; + } + else if(cacheTRI && !it->second.triFile) { + it->second.triFile = std::make_shared(); + it->second.triFile->Load(morphFile.data); + } + + return it->second; +} + +PresetDataPtr MorphHandler::GetPreset(TESNPC* npc) +{ + auto it = m_mappedPreset.find(npc); + if (it != m_mappedPreset.end()) + return it->second; + + return NULL; +} + +void MorphHandler::AssignPreset(TESNPC * npc, PresetDataPtr presetData) +{ + ErasePreset(npc); + m_mappedPreset.emplace(npc, presetData); +} + +bool MorphHandler::ErasePreset(TESNPC * npc) +{ + auto it = m_mappedPreset.find(npc); + if (it != m_mappedPreset.end()) { + m_mappedPreset.erase(it); + return true; + } + + return false; +} + +void MorphHandler::ClearPresets() +{ + m_mappedPreset.clear(); +} + +void MorphHandler::ApplyPreset(TESNPC * npc, BSFaceGenNiNode * faceNode, BGSHeadPart * headPart) +{ + PresetDataPtr presetData = GetPreset(npc); + if (presetData && faceNode && headPart) { + NiAVObject * object = faceNode->GetObjectByName(&headPart->partName.data); + if (object) { + BSGeometry * geometry = object->GetAsBSGeometry(); + if (geometry) { + BSShaderProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if (shaderProperty) { + if (ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) { + BSLightingShaderProperty * lightingShader = (BSLightingShaderProperty *)shaderProperty; + BSLightingShaderMaterial * material = (BSLightingShaderMaterial *)shaderProperty->material; + if (headPart->type == BGSHeadPart::kTypeFace) { + for (auto & texture : presetData->faceTextures) + material->textureSet->SetTexturePath(texture.index, texture.name.data); + material->textureSet->SetTexturePath(6, presetData->tintTexture.data); + material->ReleaseTextures(); + CALL_MEMBER_FN(lightingShader, InvalidateTextures)(0); + CALL_MEMBER_FN(lightingShader, InitializeShader)(geometry); + } + else if (material->GetShaderType() == BSLightingShaderMaterial::kShaderType_HairTint) { + BSLightingShaderMaterialHairTint * tintedMaterial = (BSLightingShaderMaterialHairTint *)material; // I don't know what this * 2.0 bullshit is. + tintedMaterial->tintColor.r = (((presetData->hairColor >> 16) & 0xFF) / 255.0) * 2.0; + tintedMaterial->tintColor.g = (((presetData->hairColor >> 8) & 0xFF) / 255.0) * 2.0; + tintedMaterial->tintColor.b = ((presetData->hairColor & 0xFF) / 255.0) * 2.0; + } + } + } + } + } + } +} + +class ValidRaceFinder : public BGSListForm::Visitor +{ +public: + ValidRaceFinder::ValidRaceFinder(TESRace * race) : m_race(race) { } + virtual bool Accept(TESForm * form) + { + if (m_race == form) + return true; + + return false; + }; + TESRace * m_race; +}; + +void MorphHandler::ApplyPresetData(Actor * actor, PresetDataPtr presetData, bool setSkinColor, ApplyTypes applyType) +{ + PlayerCharacter * player = (*g_thePlayer); + TESNPC * npc = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + TESRace * race = npc->race.race; + + // Wipe the HeadPart list and replace it with the default race list + UInt8 gender = CALL_MEMBER_FN(npc, GetSex)(); + TESRace::CharGenData * chargenData = race->chargenData[gender]; + if (chargenData) { + BGSHeadPart ** headParts = npc->headparts; + tArray * headPartList = race->chargenData[gender]->headParts; + if (headParts && headPartList) { + Heap_Free(headParts); + npc->numHeadParts = headPartList->count; + headParts = (BGSHeadPart **)Heap_Allocate(npc->numHeadParts * sizeof(BGSHeadPart*)); + for (UInt32 i = 0; i < headPartList->count; i++) + headPartList->GetNthItem(i, headParts[i]); + npc->headparts = headParts; + } + } + + // Replace the old parts with the new parts if they are the right sex + for (auto & part : presetData->headParts) { + if ((gender == 0 && (part->partFlags & BGSHeadPart::kFlagMale) == BGSHeadPart::kFlagMale) || + (gender == 1 && (part->partFlags & BGSHeadPart::kFlagFemale) == BGSHeadPart::kFlagFemale)) + { + ValidRaceFinder partFinder(race); + if (part->validRaces) { + if (part->validRaces->Visit(partFinder)) + CALL_MEMBER_FN(npc, ChangeHeadPart)(part); + } + } + } + + npc->weight = presetData->weight; + + if (!npc->faceMorph) + npc->faceMorph = (TESNPC::FaceMorphs*)Heap_Allocate(sizeof(TESNPC::FaceMorphs)); + + UInt32 i = 0; + for (auto value : presetData->presets) { + npc->faceMorph->presets[i] = value; + i++; + } + + i = 0; + for (auto value : presetData->morphs) { + npc->faceMorph->option[i] = value; + i++; + } + + for (auto & tint : presetData->tints) { + float alpha = (tint.color >> 24) / 255.0; + TintMask * tintMask = NULL; + if (player == actor && player->tintMasks.GetNthItem(tint.index, tintMask)) { + tintMask->color.red = (tint.color >> 16) & 0xFF; + tintMask->color.green = (tint.color >> 8) & 0xFF; + tintMask->color.blue = tint.color & 0xFF; + tintMask->alpha = alpha; + if (tintMask->alpha > 0) + tintMask->texture->str = tint.name; + } + + if (tint.index == 0 && setSkinColor) + { + float alpha = (tint.color >> 24) / 255.0; + TintMask tintMask; + tintMask.color.red = (tint.color >> 16) & 0xFF; + tintMask.color.green = (tint.color >> 8) & 0xFF; + tintMask.color.blue = tint.color & 0xFF; + tintMask.alpha = alpha; + tintMask.tintType = TintMask::kMaskType_SkinTone; + + NiColorA colorResult; + CALL_MEMBER_FN(npc, SetSkinFromTint)(&colorResult, &tintMask, 1, 0); + } + } + + g_morphHandler.EraseSculptData(npc); + if (presetData->sculptData) { + if (presetData->sculptData->size() > 0) { + g_morphHandler.SetSculptTarget(npc, presetData->sculptData); + } + } + + g_morphHandler.EraseMorphData(npc); + for (auto & morph : presetData->customMorphs) + g_morphHandler.SetMorphValue(npc, morph.name, morph.value); + + g_overrideInterface.RemoveAllReferenceNodeOverrides(actor); + g_overlayInterface.RevertOverlays(actor, true); + + if ((applyType & kPresetApplyOverrides) == kPresetApplyOverrides) + { + for (auto & nodes : presetData->overrideData) { + for (auto & value : nodes.second) { + g_overrideInterface.AddNodeOverride(actor, gender == 1 ? true : false, nodes.first, value); + } + } + } + + if ((applyType & kPresetApplySkinOverrides) == kPresetApplySkinOverrides) + { + for (UInt32 i = 0; i <= 1; i++) { + for (auto & slot : presetData->skinData[i]) { + for (auto & value : slot.second) { + g_overrideInterface.AddSkinOverride(actor, gender == 1 ? true : false, i == 1 ? true : false, slot.first, value); + } + } + } + } + + g_transformInterface.RemoveAllReferenceTransforms(actor); + + if ((applyType & kPresetApplyTransforms) == kPresetApplyTransforms) + { + for (UInt32 i = 0; i <= 1; i++) { + for (auto & xForms : presetData->transformData[i]) { + for (auto & key : xForms.second) { + for (auto & value : key.second) { + g_transformInterface.AddNodeTransform(actor, i == 1 ? true : false, gender == 1 ? true : false, xForms.first, key.first, value); + } + } + } + } + } + g_transformInterface.UpdateNodeAllTransforms(actor); + + + g_bodyMorphInterface.ClearMorphs(actor); + + if ((applyType & kPresetApplyBodyMorphs) == kPresetApplyBodyMorphs) + { + for (auto & morph : presetData->bodyMorphData) { + for (auto & keys : morph.second) + g_bodyMorphInterface.SetMorph(actor, morph.first, keys.first, keys.second); + } + } + + g_bodyMorphInterface.UpdateModelWeight(actor); +} + +bool MorphMap::Visit(BSFixedString key, Visitor & visitor) +{ + MorphMap::iterator it = find(key); + if(it != end()) + { +#ifdef _DEBUG_MORPHAPPLICATOR + _DMESSAGE("%s - Applying %d additional morphs to %s", __FUNCTION__, it->second.size(), key.data); + gLog.Indent(); +#endif + for(auto iter = it->second.begin(); iter != it->second.end(); ++iter) + { +#ifdef _DEBUG_MORPHAPPLICATOR + _DMESSAGE("%s - Visting %s", __FUNCTION__, (*iter).data); +#endif + if(visitor.Accept(*iter)) + break; + } +#ifdef _DEBUG_MORPHAPPLICATOR + gLog.Outdent(); +#endif + return true; + } +#ifdef _DEBUG_MORPHAPPLICATOR + else { + _DMESSAGE("%s - No additional morphs for %s", __FUNCTION__, key.data); + } +#endif + + return false; +} + + + +bool MorphHandler::VisitMorphMap(BSFixedString key, MorphMap::Visitor & visitor) +{ + //key = toLower(key); + return m_morphMap.Visit(key, visitor); +} + +void MorphMap::AddMorph(BSFixedString key, BSFixedString value) +{ + //key = toLower(key); + MorphMap::iterator it = find(key); + if(it != end()) { + if (std::find(it->second.begin(), it->second.end(), value) == it->second.end()) + it->second.push_back(value); + } else { + MorphSet firstSet; + firstSet.push_back(value); + emplace(key, firstSet); + } +} + +void MorphHandler::ReadMorphs(std::string fixedPath, std::string modName, std::string fileName) +{ + std::string fullPath = fixedPath + modName + fileName; + BSResourceNiBinaryStream file(fullPath.c_str()); + if (!file.IsValid()) { + return; + } + + UInt32 lineCount = 0; + std::string str = ""; + while(BSReadLine(&file, &str)) + { + lineCount++; + str = std::trim(str); + if(str.length() == 0) + continue; + if(str.at(0) == '#') + continue; + + std::vector side = explode(str, '='); + if(side.size() < 2) { + _ERROR("ReadMorphs Error - Line (%d) loading a morph from %s has no left-hand side.", lineCount, fullPath.c_str()); + continue; + } + + std::string lSide = std::trim(side[0]); + std::string rSide = std::trim(side[1]); + + if(_strnicmp(lSide.c_str(), "extension", 9) != 0) { + _ERROR("ReadMorphs Error - Line (%d) loading a morph from %s invalid left-hand side.", lineCount, fullPath.c_str()); + continue; + } + + std::vector params = explode(rSide, ','); + if(params.size() < 2) { + _ERROR("ReadMorphs Error - Line (%d) slider %s from %s has less than 2 parameters.", lineCount, lSide.c_str(), fullPath.c_str()); + continue; + } + + // Trim all parameters + for(UInt32 i = 0; i < params.size(); i++) + params[i] = std::trim(params[i]); + + std::string key = params[0]; + for(UInt32 i = 1; i < params.size(); i++) { +#ifdef _DEBUG_DATAREADER + _DMESSAGE("ReadMorphs Info - Line (%d) added %s morph to %s from %s.", lineCount, params[i].c_str(), key.c_str(), fullPath.c_str()); +#endif + m_morphMap.AddMorph(BSFixedString(key.c_str()), BSFixedString(params[i].c_str())); + } + } +} + +void MorphHandler::ReadRaces(std::string fixedPath, std::string modPath, std::string fileName) +{ + std::string fullPath = fixedPath + modPath + fileName; + BSResourceNiBinaryStream file(fullPath.c_str()); + if (!file.IsValid()) { + return; + } + + std::map fileMap; + + UInt32 lineCount = 0; + std::string str = ""; + while(BSReadLine(&file, &str)) + { + lineCount++; + str = std::trim(str); + if(str.length() == 0) + continue; + if(str.at(0) == '#') + continue; + + std::vector side = explode(str, '='); + if(side.size() < 2) { + _ERROR("ReadRaces Error - Line (%d) loading a race from %s has insufficient parameters.", lineCount, fullPath.c_str()); + continue; + } + + std::string lSide = std::trim(side[0]); + std::string rSide = std::trim(side[1]); + + std::vector files = explode(rSide, ','); + for(UInt32 i = 0; i < files.size(); i++) + files[i] = std::trim(files[i]); + + for(UInt32 i = 0; i < files.size(); i++) + { + std::string pathOverride = modPath; + if(files[i].at(0) == ':') { + pathOverride = ""; + files[i].erase(0, 1); + } + + SliderMapPtr sliderMap = NULL; + std::map::iterator it = fileMap.find(files[i]); + if(it != fileMap.end()) { + sliderMap = it->second; + } else { + sliderMap = ReadSliders(fixedPath, pathOverride, files[i]); + if(sliderMap) { + fileMap.emplace(files[i], sliderMap); + } else { + _ERROR("ReadRaces Error - Line (%d) failed to load slider map for %s from %s.", lineCount, lSide.c_str(), fullPath.c_str()); + } + } + + if(sliderMap) { +#ifdef _DEBUG_DATAREADER + _DMESSAGE("ReadRaces Info - Line (%d) Loaded %s for Race %s from %s.", lineCount, files[i].c_str(), lSide.c_str(), fullPath.c_str()); +#endif + + TESRace * race = GetRaceByName(lSide); + if(race) + m_raceMap.AddSliderMap(race, sliderMap); + } + } + } +} + +void SliderMap::AddSlider(BSFixedString key, UInt8 gender, SliderInternal & sliderInternal) +{ + SliderMap::iterator it = find(key); + if(it != end()) { + it->second->slider[gender] = std::make_shared(); + it->second->slider[gender]->copy(&sliderInternal); + } else { + SliderGenderPtr sliderGender = std::make_shared(); + sliderGender->slider[gender] = std::make_shared(); + sliderGender->slider[gender]->copy(&sliderInternal); + emplace(key, sliderGender); + } +} + +SliderMapPtr MorphHandler::ReadSliders(std::string fixedPath, std::string modPath, std::string fileName) +{ + SliderMapPtr sliderMap = NULL; + std::string fullPath = fixedPath + modPath + fileName; + BSResourceNiBinaryStream file(fullPath.c_str()); + if (!file.IsValid()) { + return NULL; + } + + sliderMap = std::make_shared(); + + UInt8 gender = 0; + UInt32 lineCount = 0; + std::string str = ""; + while(BSReadLine(&file, &str)) + { + lineCount++; + str = std::trim(str); + if(str.length() == 0) + continue; + if(str.at(0) == '#') + continue; + + if(str.at(0) == '[') + { + str.erase(0, 1); + if(_strnicmp(str.c_str(), "Male", 4) == 0) + gender = 0; + if(_strnicmp(str.c_str(), "Female", 6) == 0) + gender = 1; + continue; + } + + std::vector side = explode(str, '='); + if(side.size() < 2) { + _ERROR("ReadSliders Error - Line (%d) slider from %s has no left-hand side.", lineCount, fullPath.c_str()); + continue; + } + + std::string lSide = std::trim(side[0]); + std::string rSide = std::trim(side[1]); + + std::vector params = explode(rSide, ','); + if(params.size() < 3) { + _ERROR("ReadSliders Error - Line (%d) slider %s from %s has less than 3 parameters.", lineCount, lSide.c_str(), fullPath.c_str()); + continue; + } + + // Trim all parameters + for(UInt32 i = 0; i < params.size(); i++) + params[i] = std::trim(params[i]); + + BSFixedString sliderName = BSFixedString(lSide.c_str()); + SliderInternal sliderInternal; + sliderInternal.name = sliderName; + sliderInternal.category = atoi(params.at(0).c_str()); + if(sliderInternal.category == -1) + sliderInternal.category = SliderInternal::kCategoryExtra; + switch(sliderInternal.category) + { + case SliderInternal::kCategoryExpressions: + case SliderInternal::kCategoryExtra: + case SliderInternal::kCategoryBody: + case SliderInternal::kCategoryHead: + case SliderInternal::kCategoryFace: + case SliderInternal::kCategoryEyes: + case SliderInternal::kCategoryBrow: + case SliderInternal::kCategoryMouth: + case SliderInternal::kCategoryHair: + break; + default: + _ERROR("ReadSliders Error - Line (%d) loading slider %s from %s has invalid category (%d).", lineCount, lSide.c_str(), fullPath.c_str(), sliderInternal.category); + continue; + break; + } + if(_strnicmp(params[1].c_str(), "Slider", 6) == 0) { + sliderInternal.type = SliderInternal::kTypeSlider; + } else if(_strnicmp(params[1].c_str(), "Preset", 6) == 0) { + sliderInternal.type = SliderInternal::kTypePreset; + } else if(_strnicmp(params[1].c_str(), "HeadPart", 8) == 0) { + sliderInternal.type = SliderInternal::kTypeHeadPart; + } else { + _ERROR("ReadSliders Error - Line (%d) loading slider %s from %s has invalid slider type (%s).", lineCount, lSide.c_str(), fullPath.c_str(), params[1].c_str()); + continue; + } + switch(sliderInternal.type) + { + case SliderInternal::kTypeSlider: + { + // Additional morphs are disabled + if (!g_extendedMorphs) + continue; + + if(params.size() < 4) { + _ERROR("ReadSliders Error - Line (%d) slider %s from %s has less than 4 parameters.", lineCount, lSide.c_str(), fullPath.c_str()); + continue; + } + + sliderInternal.lowerBound = BSFixedString(params[2].c_str()); + sliderInternal.upperBound = BSFixedString(params[3].c_str()); + + if(sliderInternal.lowerBound == BSFixedString("None")) + sliderInternal.lowerBound = BSFixedString(""); + if(sliderInternal.upperBound == BSFixedString("None")) + sliderInternal.upperBound = BSFixedString(""); + } + break; + case SliderInternal::kTypePreset: + { + // Additional morphs are disabled + if (!g_extendedMorphs) + continue; + + if (params.size() < 4) { + _ERROR("ReadSliders Error - Line (%d) slider %s from %s has less than 4 parameters.", lineCount, lSide.c_str(), fullPath.c_str()); + continue; + } + + sliderInternal.lowerBound = BSFixedString(params[2].c_str()); + UInt32 presetCount = atoi(params[3].c_str()); + if (presetCount > 255) { + presetCount = 255; + _WARNING("ReadSliders Warning - Line (%d) loading slider %s from %s has exceeded a preset count of %d.", lineCount, lSide.c_str(), fullPath.c_str(), presetCount); + } + sliderInternal.presetCount = presetCount; + + } + break; + case SliderInternal::kTypeHeadPart: + { + sliderInternal.presetCount = atoi(params[2].c_str()); + } + break; + } +#ifdef _DEBUG_DATAREADER + _DMESSAGE("ReadSliders Info - Line (%d) Added Slider (%s, %d, %d, %d, %s, %s) to Gender %d %s from %s.", lineCount, sliderInternal.name.data, sliderInternal.category, sliderInternal.type, sliderInternal.presetCount, sliderInternal.lowerBound.data, sliderInternal.upperBound.data, gender, lSide.c_str(), fullPath.c_str()); +#endif + sliderMap->AddSlider(sliderName, gender, sliderInternal); + } + + return sliderMap; +} + +#ifdef _DEBUG_DATADUMP +void SliderMap::DumpMap() +{ + for(SliderMap::iterator it = begin(); it != end(); ++it) + { + SliderGender * gender = it->second; + if(gender->slider[0]) + _DMESSAGE("Slider - Name: %s Gender: Male Type: %d Cat: %d LowerBound: %s UpperBound: %s PresetCount: %d", it->first.data, gender->slider[0]->type, gender->slider[0]->category, gender->slider[0]->lowerBound.data, gender->slider[0]->upperBound.data, gender->slider[0]->presetCount); + if(gender->slider[1]) + _DMESSAGE("Slider - Name: %s Gender: Female Type: %d Cat: %d LowerBound: %s UpperBound: %s PresetCount: %d", it->first.data, gender->slider[1]->type, gender->slider[1]->category, gender->slider[1]->lowerBound.data, gender->slider[1]->upperBound.data, gender->slider[1]->presetCount); + } +} + +void RaceMap::DumpMap() +{ + for(RaceMap::iterator it = begin(); it != end(); ++it) + { + _DMESSAGE("Race: %08X - %s", it->first->formID, it->first->editorId.data); + gLog.Indent(); + for(SliderSet::iterator sit = it->second.begin(); sit != it->second.end(); ++sit) + { + _DMESSAGE("Map: %08X", (*sit)); + (*sit)->DumpMap(); + } + gLog.Outdent(); + } +} + +void MorphMap::DumpMap() +{ + for(MorphMap::iterator it = begin(); it != end(); ++it) + { + _DMESSAGE("Morph: %s", it->first.data); + DumpVisitor visitor; + gLog.Indent(); + Visit(it->first, visitor); + gLog.Outdent(); + } +} + +void MorphHandler::DumpAll() +{ + m_raceMap.DumpMap(); + m_morphMap.DumpMap(); +} +#endif + +SliderInternalPtr MorphHandler::GetSlider(TESRace * race, UInt8 gender, BSFixedString name) +{ + SliderSetPtr sliderMaps = m_raceMap.GetSliderSet(race); + if(sliderMaps) + { + SliderInternalPtr sliderInternal; + sliderMaps->for_each_slider([&](SliderGenderPtr genders) { + sliderInternal = genders->slider[gender]; + if (sliderInternal && sliderInternal->name == name) + return true; + return false; + }); + if (sliderInternal) + return sliderInternal; + } + + return NULL; +} + + +bool sortFixedStrings(SliderInternalPtr s1, SliderInternalPtr s2) +{ + return s1->name.data < s2->name.data; +} + +SliderList * MorphHandler::CreateSliderList(TESRace * race, UInt8 gender) +{ + // Clear the old map before building the new + RaceSliders::iterator it = m_internalMap.find(race); + if(it != m_internalMap.end()) { + it->second.clear(); + } + + SliderSetPtr sliderMaps = m_raceMap.GetSliderSet(race); + if(sliderMaps) + { + sliderMaps->for_each_slider([&](SliderGenderPtr genders) { + SliderInternalPtr sliderInternal = genders->slider[gender]; + if (sliderInternal) + AddSlider(race, sliderInternal); + return false; + }); + } + + // Return the list + it = m_internalMap.find(race); + if(it != m_internalMap.end()) { + std::sort(it->second.begin(), it->second.end(), sortFixedStrings); + it->second.resize(it->second.size()); + return &it->second; + } + + return NULL; +} + +void MorphHandler::AddSlider(TESRace * race, SliderInternalPtr & slider) +{ + RaceSliders::iterator it = m_internalMap.find(race); + if(it != m_internalMap.end()) { + it->second.push_back(slider); + } else { + SliderList newList; + newList.push_back(slider); + m_internalMap.emplace(race, newList); + } +} + +void MorphHandler::ApplyMorph(TESNPC * npc, BGSHeadPart * headPart, BSFaceGenNiNode * faceNode) +{ + char buffer[MAX_PATH]; + + auto sculptTarget = GetSculptTarget(npc, false); + if (sculptTarget) { + if (headPart) { + + NiAVObject * object = faceNode->GetObjectByName(&headPart->partName.data); + if (object) { + BSGeometry * geometry = object->GetAsBSGeometry(); + if (geometry) { + auto sculptHost = sculptTarget->GetSculptHost(SculptData::GetHostByPart(headPart), false); + if (sculptHost) { + BSFaceGenBaseMorphExtraData * extraData = (BSFaceGenBaseMorphExtraData *)geometry->GetExtraData("FOD"); + if (extraData) { + for (auto data : *sculptHost) + extraData->vertexData[data.first] += data.second; + } + } + } + } + } + } + + if (!g_extendedMorphs) + return; + + ValueSet * valueSet = m_valueMap.GetValueSet(npc); + if(!valueSet) + return; + + UInt8 gender = CALL_MEMBER_FN(npc, GetSex)(); + + for(auto it = valueSet->begin(); it != valueSet->end(); ++it) + { + SliderInternalPtr slider = GetSlider(npc->race.race, gender, it->first); + if(slider) { + if(slider->type == SliderInternal::kTypePreset) { + if(it->second != 0) { // There should be no zero morph for presets + memset(buffer, 0, MAX_PATH); + sprintf_s(buffer, MAX_PATH, "%s%d", slider->lowerBound.data, (UInt32)it->second); + BSFixedString morphName(buffer); +#ifdef _DEBUG_MORPH + _DMESSAGE("Applying Single Preset %s value %f", morphName.data, it->value); +#endif + CALL_MEMBER_FN(FaceGen::GetSingleton(), ApplyMorph)(faceNode, headPart, &morphName, 1.0); + } + } else { + BSFixedString morphName = slider->lowerBound; + if(it->second < 0) + morphName = slider->lowerBound; + if(it->second > 0) + morphName = slider->upperBound; + + float relative = abs(it->second); + if(relative > 1.0) { + UInt32 count = (UInt32)relative; + float difference = relative - count; + for(UInt32 i = 0; i < count; i++) + CALL_MEMBER_FN(FaceGen::GetSingleton(), ApplyMorph)(faceNode, headPart, &morphName, 1.0); + relative = difference; + } +#ifdef _DEBUG_MORPH + _DMESSAGE("Applying Single Slider %s value %f", morphName.data, it->value); +#endif + CALL_MEMBER_FN(FaceGen::GetSingleton(), ApplyMorph)(faceNode, headPart, &morphName, relative); + } + } + } +} + +void MorphHandler::ApplyMorphs(TESNPC * npc, BSFaceGenNiNode * faceNode) +{ + char buffer[MAX_PATH]; + + + auto sculptTarget = GetSculptTarget(npc, false); + if (sculptTarget) { + VisitObjects(faceNode, [&](NiAVObject* object) + { + if (BSGeometry * geometry = object->GetAsBSGeometry()) { + std::string headPartName = object->m_name; + BGSHeadPart * headPart = GetHeadPartByName(headPartName); + if (headPart) { + auto sculptHost = sculptTarget->GetSculptHost(SculptData::GetHostByPart(headPart), false); + if (sculptHost) { + BSFaceGenBaseMorphExtraData * extraData = (BSFaceGenBaseMorphExtraData *)geometry->GetExtraData("FOD"); + if (extraData) { + for (auto data : *sculptHost) + extraData->vertexData[data.first] += data.second; + } + } + } + } + + return false; + }); + } + + if (!g_extendedMorphs) + return; + + ValueSet * valueSet = m_valueMap.GetValueSet(npc); + if(!valueSet) + return; + + UInt8 gender = CALL_MEMBER_FN(npc, GetSex)(); + + + for(auto it = valueSet->begin(); it != valueSet->end(); ++it) + { + SliderInternalPtr slider = GetSlider(npc->race.race, gender, it->first); + if(slider) { + if(slider->type == SliderInternal::kTypePreset) { + if(it->second != 0) { // There should be no zero morph for presets + memset(buffer, 0, MAX_PATH); + sprintf_s(buffer, MAX_PATH, "%s%d", slider->lowerBound.data, (UInt32)it->second); + BSFixedString morphName(buffer); +#ifdef _DEBUG_MORPH + _DMESSAGE("Applying Full Preset %s value %f", morphName.data, it->value); +#endif + SetMorph(npc, faceNode, morphName.data, 1.0); + } + } else { + BSFixedString morphName = slider->lowerBound; + if(it->second < 0.0) + morphName = slider->lowerBound; + if(it->second > 0.0) + morphName = slider->upperBound; + + float relative = abs(it->second); + if(relative > 1.0) { + UInt32 count = (UInt32)relative; + float difference = relative - count; + for(UInt32 i = 0; i < count; i++) + SetMorph(npc, faceNode, morphName.data, 1.0); + relative = difference; + } + +#ifdef _DEBUG_MORPH + _DMESSAGE("Applying Full Slider %s value %f", morphName.data, it->value); +#endif + SetMorph(npc, faceNode, morphName.data, relative); + } + } + } +} + +void MorphHandler::SetMorph(TESNPC * npc, BSFaceGenNiNode * faceNode, const char * name, float relative) +{ +#ifdef _DEBUG_MORPH + _DMESSAGE("Applying Morph %s", name); +#endif + BSFixedString morphName(name); + FaceGenApplyMorph(FaceGen::GetSingleton(), faceNode, npc, &morphName, relative); +} + +SInt32 MorphHandler::LoadSliders(tArray * sliderArray, RaceMenuSlider * slider) +{ + PlayerCharacter * player = (*g_thePlayer); + TESNPC * npc = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + + UInt32 sliderId = sliderArray->count; + UInt32 morphIndex = SLIDER_OFFSET; + + currentList = CreateSliderList(player->race, CALL_MEMBER_FN(npc, GetSex)()); + if(!currentList) + return sliderId; + + ValueSet * valueSet = m_valueMap.GetValueSet(npc); + + // Clean up invalid morphs + if(valueSet) { + ValueSet::iterator it = valueSet->begin(); + while (it != valueSet->end()) { + bool foundMorph = false; + for(auto mit = currentList->begin(); mit != currentList->end(); ++mit) { + SliderInternalPtr slider = (*mit); + if(slider->name == it->first) { + foundMorph = true; + break; + } + } + + if (!foundMorph) { + _DMESSAGE("Erasing %s", it->first.data); + valueSet->erase(it++); + } + else + it++; + } + } + + UInt32 i = 0; + for(auto it = currentList->begin(); it != currentList->end(); ++it) + { + SliderInternalPtr slider = (*it); + std::string sliderName = "$"; + sliderName.append(slider->name.data); + + float value = valueSet ? valueSet->GetValue(slider->name) : 0.0; + + UInt32 sliderIndex = morphIndex + i; + float lowerBound = slider->lowerBound == BSFixedString("") ? 0.0 : -1.0; + float upperBound = slider->upperBound == BSFixedString("") ? 0.0 : 1.0; + float interval = g_sliderInterval; + float lowerMultiplier = g_sliderMultiplier; + float upperMultiplier = g_sliderMultiplier; + + if(slider->type == SliderInternal::kTypePreset) { + lowerBound = 0.0; + interval = 1; + lowerMultiplier = 1.0; + upperMultiplier = 1.0; + upperBound = (float)slider->presetCount; + } else if(slider->type == SliderInternal::kTypeHeadPart) { + lowerBound = 0.0; + interval = 1; + lowerMultiplier = 1.0; + upperMultiplier = 1.0; + HeadPartList * headPartList = g_partSet.GetPartList(slider->presetCount); + BGSHeadPart * headPart = npc->GetHeadPartByType(slider->presetCount); + SInt32 partIndex = -1; + if(headPart && headPartList) + partIndex = g_partSet.GetPartIndex(headPartList, headPart); + if(partIndex != -1) + value = (float)(partIndex + 1); + if(headPartList) + upperBound = (float)headPartList->size(); + else + upperBound = 0; + } + + BSFixedString morphName(sliderName.c_str()); + +#ifdef _DEBUG_SLIDER + _DMESSAGE("Adding slider: %s Morph: %s Value: %f SliderID: %d Index: %d", morphName.data, slider->name.data, value, sliderId, sliderIndex); +#endif + + RaceMenuSlider newSlider(slider->category, morphName.c_str(), "ChangeDoubleMorph", sliderId++, sliderIndex, 2, 1, lowerBound * lowerMultiplier, upperBound * upperMultiplier, value, interval, 0); + AddRaceMenuSlider(sliderArray, &newSlider); + i++; + } + + return sliderId; +} + +void MorphHandler::Save(TESNPC* npc, SKSESerializationInterface * intfc, UInt32 kVersion) +{ + auto sculptData = m_sculptStorage.GetSculptTarget(npc, false); + if (sculptData) { + if (sculptData->size() > 0) { + intfc->OpenRecord('SCDT', kVersion); + + UInt16 numValidParts = 0; + for (auto part : *sculptData) { + if (part.first.data[0] != 0 && part.second->size() > 0) { + numValidParts++; + } + } + + intfc->WriteRecordData(&numValidParts, sizeof(numValidParts)); + if (numValidParts > 0) { + for (auto part : *sculptData) { + UInt16 diffCount = part.second->size(); + if (diffCount > 0) { + UInt16 length = strlen(part.first.data); + intfc->OpenRecord('SCPT', kVersion); + intfc->WriteRecordData(&length, sizeof(length)); + intfc->WriteRecordData(part.first.data, length); + intfc->WriteRecordData(&diffCount, sizeof(diffCount)); + + for (auto diff : *part.second) { + intfc->WriteRecordData(&diff.first, sizeof(diff.first)); + intfc->WriteRecordData(&diff.second, sizeof(NiPoint3)); + } + } + } + } + } + } + + ValueSet * valueSet = m_valueMap.GetValueSet(npc); + if (valueSet) { + // Count only non-zero morphs + UInt32 numMorphs = 0; + for (auto it = valueSet->begin(); it != valueSet->end(); ++it) { + if (it->second != 0.0) + numMorphs++; + } + + if (numMorphs > 0) { + intfc->OpenRecord('MRST', kVersion); + intfc->WriteRecordData(&numMorphs, sizeof(numMorphs)); + for (auto it = valueSet->begin(); it != valueSet->end(); ++it) { + if (it->second != 0.0) { + UInt16 length = strlen(it->first.data); + intfc->OpenRecord('MRPH', kVersion); + intfc->WriteRecordData(&length, sizeof(length)); + intfc->WriteRecordData(it->first.data, length); + intfc->WriteRecordData(&it->second, sizeof(it->second)); + } + } + } + } +} + +bool MorphHandler::Load(TESNPC* npc, SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type; + UInt32 version; + UInt32 length; + bool error = false; + + while(!error && intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch(type) + { + case 'MRST': + { + if(version == kVersion) + { + UInt32 numMorphs = 0; + if(!intfc->ReadRecordData(&numMorphs, sizeof(numMorphs))) + { + _MESSAGE("Error loading morph count"); + error = true; + return true; + } + + for(UInt32 i = 0; i < numMorphs; i++) + { + char * name = NULL; + UInt32 index = 0; + float value = 0.0; + + if(intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch(type) + { + case 'MRPH': + { + UInt16 nameLength = 0; + if (!intfc->ReadRecordData(&nameLength, sizeof(nameLength))) { + _MESSAGE("Error loading morph name length"); + error = true; + return true; + } + + name = new char[nameLength + 1]; + if (!intfc->ReadRecordData(name, nameLength)) { + _MESSAGE("Error loading morph name"); + error = true; + return true; + } + name[nameLength] = 0; + + if (!intfc->ReadRecordData(&value, sizeof(value))) { + _MESSAGE("Error loading morph value"); + error = true; + return true; + } + } + break; + default: + _MESSAGE("unhandled type %08X", type); + error = true; + break; + } + } + + if(value != 0.0) { + _MESSAGE("Loaded Morph: %s - Value: %f", name, value); + m_valueMap.SetMorphValue(npc, BSFixedString(name), value); + } + } + } else { + error = true; + } + } + break; + + case 'SCDT': + { + if (version == kVersion) + { + UInt16 numParts = 0; + if (!intfc->ReadRecordData(&numParts, sizeof(numParts))) + { + _MESSAGE("Error loading sculpt part count"); + error = true; + return true; + } + + for (UInt32 i = 0; i < numParts; i++) + { + if (intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'SCPT': + { + UInt16 nameLength = 0; + if (!intfc->ReadRecordData(&nameLength, sizeof(nameLength))) { + _MESSAGE("Error loading sculpt part name length"); + error = true; + return true; + } + + char * name = new char[nameLength + 1]; + if (!intfc->ReadRecordData(name, nameLength)) { + _MESSAGE("Error loading sculpt part name"); + error = true; + return true; + } + name[nameLength] = 0; + + UInt16 totalDifferences = 0; + if (!intfc->ReadRecordData(&totalDifferences, sizeof(totalDifferences))) { + _MESSAGE("Error loading sculpt difference count"); + error = true; + return true; + } + + SculptDataPtr sculptTarget; + if (totalDifferences > 0) + sculptTarget = m_sculptStorage.GetSculptTarget(npc, true); + MappedSculptDataPtr sculptHost; + if (sculptTarget && totalDifferences > 0) + sculptHost = sculptTarget->GetSculptHost(BSFixedString(name), true); + + UInt16 index = 0; + NiPoint3 value; + for (UInt16 i = 0; i < totalDifferences; i++) + { + if (!intfc->ReadRecordData(&index, sizeof(index))) { + _MESSAGE("Error loading sculpt index"); + error = true; + return true; + } + + if (!intfc->ReadRecordData(&value, sizeof(value))) { + _MESSAGE("Error loading sculpt index"); + error = true; + return true; + } + + if (sculptTarget && sculptHost) + sculptHost->force_insert(std::make_pair(index, value)); + } + } + break; + default: + _MESSAGE("unhandled type %08X", type); + error = true; + break; + } + } + } + } + } + break; + + default: + _MESSAGE("unhandled type %08X", type); + error = true; + break; + } + } + + return error; +} + + +struct PresetHeader +{ + enum + { + kSignature = MACRO_SWAP32('SKSE'), // endian-swapping so the order matches + kVersion = 3, + + kVersion_Invalid = 0 + }; + + UInt32 signature; + UInt32 formatVersion; + UInt32 skseVersion; + UInt32 runtimeVersion; +}; + +#include + +bool MorphHandler::SaveJsonPreset(const char * filePath) +{ + Json::StyledWriter writer; + Json::Value root; + + IFileStream currentFile; + IFileStream::MakeAllDirs(filePath); + if (!currentFile.Create(filePath)) + { + _ERROR("%s: couldn't create preset file (%s) Error (%d)", __FUNCTION__, filePath, GetLastError()); + return true; + } + + Json::Value versionInfo; + versionInfo["signature"] = PresetHeader::kSignature; + versionInfo["formatVersion"] = PresetHeader::kVersion; + versionInfo["skseVersion"] = PACKED_SKSE_VERSION; + versionInfo["runtimeVersion"] = RUNTIME_VERSION_1_4_2; + + + PlayerCharacter * player = (*g_thePlayer); + TESNPC * npc = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + DataHandler * dataHandler = DataHandler::GetSingleton(); + + bool isFemale = false; + if (npc) + isFemale = CALL_MEMBER_FN(npc, GetSex)() == 1; + + std::map modList; + std::map partList; + + UInt32 numHeadParts = 0; + BGSHeadPart ** headParts = NULL; + if (CALL_MEMBER_FN(npc, HasOverlays)()) { + numHeadParts = GetNumActorBaseOverlays(npc); + headParts = GetActorBaseOverlays(npc); + } + else { + numHeadParts = npc->numHeadParts; + headParts = npc->headparts; + } + + for (UInt32 i = 0; i < numHeadParts; i++) // Acquire all unique parts + { + BGSHeadPart * headPart = headParts[i]; + if (headPart && !headPart->IsExtraPart()) { + UInt8 modIndex = headPart->formID >> 24; + ModInfo* modInfo = dataHandler->modList.modInfoList.GetNthItem(modIndex); + if (modInfo) { + modList.emplace(modInfo->modIndex, modInfo->name); + partList.emplace(i, headPart); + } + } + } + + std::map> tintList; + for (UInt32 i = 0; i < player->tintMasks.count; i++) + { + TintMask * tintMask = NULL; + if (player->tintMasks.GetNthItem(i, tintMask)) + { + UInt32 tintColor = ((UInt32)(tintMask->alpha * 255.0) << 24) | tintMask->color.red << 16 | tintMask->color.green << 8 | tintMask->color.blue; + if (tintMask->texture) + tintList.emplace(i, std::make_pair(tintColor, tintMask->texture->str.data)); + } + } + + Json::Value modInfo; + for (auto mIt = modList.begin(); mIt != modList.end(); ++mIt) + { + Json::Value mod; + mod["index"] = mIt->first; + mod["name"] = mIt->second; + modInfo.append(mod); + } + + Json::Value headPartInfo; + for (auto pIt = partList.begin(); pIt != partList.end(); ++pIt) + { + Json::Value partInfo; + partInfo["type"] = pIt->first; + partInfo["formId"] = (Json::UInt)pIt->second->formID; + headPartInfo.append(partInfo); + } + + Json::Value tintInfo; + for (auto tmIt = tintList.begin(); tmIt != tintList.end(); ++tmIt) + { + Json::Value tint; + tint["index"] = tmIt->first; + tint["color"] = (Json::UInt)tmIt->second.first; + tint["texture"] = tmIt->second.second; + tintInfo.append(tint); + } + + Json::Value morphInfo; + if (npc->faceMorph) + { + for (UInt8 p = 0; p < TESNPC::FaceMorphs::kNumPresets; p++) { + Json::Value morphValue = (Json::UInt)npc->faceMorph->presets[p]; + morphInfo["presets"].append(morphValue); + } + + for (UInt8 o = 0; o < TESNPC::FaceMorphs::kNumOptions; o++) { + Json::Value morphValue = npc->faceMorph->option[o]; + morphInfo["morphs"].append(morphValue); + } + } + + Json::Value customMorphInfo; + ValueSet * valueSet = m_valueMap.GetValueSet(npc); + if (valueSet) + { + for (auto it = valueSet->begin(); it != valueSet->end(); ++it) + { + if (it->second != 0.0) { + Json::Value morphValue; + morphValue["name"] = it->first.data; + morphValue["value"] = it->second; + customMorphInfo.append(morphValue); + } + } + } + + + Json::Value sculptData; + auto sculptTarget = GetSculptTarget(npc, false); + if (sculptTarget) { + for (UInt32 i = 0; i < numHeadParts; i++) // Acquire all unique parts + { + BGSHeadPart * headPart = headParts[i]; + if (headPart) { + BSFixedString morphPath = SculptData::GetHostByPart(headPart); + auto sculptHost = sculptTarget->GetSculptHost(morphPath, false); + if (sculptHost) { + Json::Value hostData; + hostData["host"] = morphPath.data; + + TRIModelData data; + GetModelTri(morphPath, data); + + hostData["vertices"] = (Json::UInt)data.vertexCount; + + for (auto morph : *sculptHost) { + Json::Value value; + value.append(morph.first); + value.append((Json::Int)(morph.second.x * VERTEX_MULTIPLIER)); + value.append((Json::Int)(morph.second.y * VERTEX_MULTIPLIER)); + value.append((Json::Int)(morph.second.z * VERTEX_MULTIPLIER)); + hostData["data"].append(value); + } + + sculptData.append(hostData); + } + } + } + } + + Json::Value textureInfo; + BGSHeadPart * facePart = npc->GetCurrentHeadPartByType(BGSHeadPart::kTypeFace); + if (facePart) { + BGSTextureSet * textureSet = GetTextureSetForPart(npc, facePart); + if (textureSet) { + for (UInt8 i = 0; i < BSShaderTextureSet::kNumTextures; i++) { + const char * texturePath = textureSet->textureSet.GetTexturePath(i); + if (texturePath != NULL) { + Json::Value textureValue; + textureValue["index"] = i; + textureValue["texture"] = texturePath; + textureInfo.append(textureValue); + } + } + } + } + + // Collect override data + PresetData::OverrideData overrideData; + g_overrideInterface.VisitNodes(player, [&overrideData](BSFixedString node, OverrideVariant & value) + { + overrideData[node].push_back(value); + }); + + // Collect skin data + PresetData::SkinData skinData[2]; + for (UInt32 i = 0; i <= 1; i++) { + g_overrideInterface.VisitSkin(player, isFemale, i == 1, [&i, &skinData](UInt32 slotMask, OverrideVariant & value) + { + skinData[i][slotMask].push_back(value); + return false; + }); + } + + // Collect transform data + PresetData::TransformData transformData[2]; + for (UInt32 i = 0; i <= 1; i++) { + g_transformInterface.VisitNodes(player, i == 1, isFemale, [&i, &transformData](BSFixedString node, OverrideRegistration * keys) + { + keys->Visit([&i, &node, &transformData](const BSFixedString & key, OverrideSet * set) + { + if (key == BSFixedString("internal")) + return false; + + set->Visit([&i, &node, &transformData, &key](OverrideVariant * value) + { + transformData[i][node][key].push_back(*value); + return false; + }); + return false; + }); + + return false; + }); + } + + // Collect body morph data + PresetData::BodyMorphData bodyMorphData; + g_bodyMorphInterface.VisitMorphs(player, [&](BSFixedString name, std::unordered_map * map) + { + for (auto & it : *map) + { + bodyMorphData[name][it.first] = it.second; + } + }); + + for (UInt32 i = 0; i <= 1; i++) { + for (auto & data : transformData[i]) { + Json::Value transform; + transform["firstPerson"] = (bool)(i == 1); + transform["node"] = data.first.data; + + for (auto & key : data.second) { + Json::Value transformKey; + transformKey["name"] = key.first.data; + + for (auto & value : key.second) { + Json::Value jvalue; + jvalue["key"] = value.key; + jvalue["type"] = value.type; + jvalue["index"] = value.index; + switch (value.type) { + case OverrideVariant::kType_Bool: + jvalue["data"] = value.data.b; + break; + case OverrideVariant::kType_Int: + jvalue["data"] = value.data.i; + break; + case OverrideVariant::kType_Float: + jvalue["data"] = value.data.f; + break; + case OverrideVariant::kType_String: + jvalue["data"] = value.data.GetStr()->data; + break; + } + transformKey["values"].append(jvalue); + } + transform["keys"].append(transformKey); + } + root["transforms"].append(transform); + } + } + for (auto & data : overrideData) { + Json::Value ovr; + ovr["node"] = data.first.data; + + for (auto & value : data.second) { + Json::Value jvalue; + jvalue["key"] = value.key; + jvalue["type"] = value.type; + jvalue["index"] = value.index; + switch (value.type) { + case OverrideVariant::kType_Bool: + jvalue["data"] = value.data.b; + break; + case OverrideVariant::kType_Int: + jvalue["data"] = value.data.i; + break; + case OverrideVariant::kType_Float: + jvalue["data"] = value.data.f; + break; + case OverrideVariant::kType_String: + jvalue["data"] = value.data.GetStr()->data; + break; + } + ovr["values"].append(jvalue); + } + root["overrides"].append(ovr); + } + + for (UInt32 i = 0; i <= 1; i++) { + for (auto & data : skinData[i]) { + Json::Value slot; + slot["firstPerson"] = (bool)(i == 1); + slot["slotMask"] = (Json::UInt)data.first; + + for (auto & value : data.second) { + Json::Value jvalue; + jvalue["key"] = value.key; + jvalue["type"] = value.type; + jvalue["index"] = value.index; + switch (value.type) { + case OverrideVariant::kType_Bool: + jvalue["data"] = value.data.b; + break; + case OverrideVariant::kType_Int: + jvalue["data"] = value.data.i; + break; + case OverrideVariant::kType_Float: + jvalue["data"] = value.data.f; + break; + case OverrideVariant::kType_String: + jvalue["data"] = value.data.GetStr()->data; + break; + } + slot["values"].append(jvalue); + } + + root["skinOverrides"].append(slot); + } + } + + for (auto & data : bodyMorphData) { + Json::Value bm; + bm["name"] = data.first.data; + for (auto & keys : data.second) + { + Json::Value jvalue; + jvalue["key"] = keys.first.data; + jvalue["value"] = keys.second; + bm["keys"].append(jvalue); + } + root["bodyMorphs"].append(bm); + } + + root["version"] = versionInfo; + root["mods"] = modInfo; + root["headParts"] = headPartInfo; + root["actor"]["weight"] = npc->weight; + + if (npc->headData) { + auto hairColor = npc->headData->hairColor; + if (hairColor) + root["actor"]["hairColor"] = (Json::UInt)(hairColor->color.red << 16 | hairColor->color.green << 8 | hairColor->color.blue); + } + + root["tintInfo"] = tintInfo; + root["faceTextures"] = textureInfo; + root["morphs"]["default"] = morphInfo; + root["morphs"]["custom"] = customMorphInfo; + root["morphs"]["sculptDivisor"] = VERTEX_MULTIPLIER; + root["morphs"]["sculpt"] = sculptData; + + std::string data = writer.write(root); + currentFile.WriteBuf(data.c_str(), data.length()); + currentFile.Close(); + return false; +} + +bool MorphHandler::SaveBinaryPreset(const char * filePath) +{ + IFileStream currentFile; + IFileStream::MakeAllDirs(filePath); + + _DMESSAGE("creating preset"); + if(!currentFile.Create(filePath)) + { + _ERROR("%s: couldn't create preset file (%s) Error (%d)", __FUNCTION__, filePath, GetLastError()); + return true; + } + + try + { + PresetHeader fileHeader; + fileHeader.signature = PresetHeader::kSignature; + fileHeader.formatVersion = PresetHeader::kVersion; + fileHeader.skseVersion = PACKED_SKSE_VERSION; + fileHeader.runtimeVersion = RUNTIME_VERSION_1_4_2; + + currentFile.Skip(sizeof(fileHeader)); + + PlayerCharacter * player = (*g_thePlayer); + TESNPC * npc = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + DataHandler * dataHandler = DataHandler::GetSingleton(); + + typedef std::map ModMap; + typedef std::pair ModPair; + + typedef std::map PartMap; + typedef std::pair PartPair; + + typedef std::pair TintCouple; + typedef std::map TintMap; + typedef std::pair TintPair; + + ModMap modList; + PartMap partList; + + UInt32 numHeadParts = 0; + BGSHeadPart ** headParts = NULL; + if (CALL_MEMBER_FN(npc, HasOverlays)()) { + numHeadParts = GetNumActorBaseOverlays(npc); + headParts = GetActorBaseOverlays(npc); + } else { + numHeadParts = npc->numHeadParts; + headParts = npc->headparts; + } + for (UInt32 i = 0; i < numHeadParts; i++) // Acquire all unique parts + { + BGSHeadPart * headPart = headParts[i]; + if(headPart && !headPart->IsExtraPart()) { + UInt8 modIndex = headPart->formID >> 24; + ModInfo* modInfo = dataHandler->modList.modInfoList.GetNthItem(modIndex); + if(modInfo) { + modList.emplace(modInfo->modIndex, modInfo->name); + partList.emplace(i, headPart); + } + } + } + + TintMap tintList; + for(UInt32 i = 0; i < player->tintMasks.count; i++) + { + TintMask * tintMask = NULL; + if(player->tintMasks.GetNthItem(i, tintMask)) + { + UInt32 tintColor = ((UInt32)(tintMask->alpha * 255.0) << 24) | tintMask->color.red << 16 | tintMask->color.green << 8 | tintMask->color.blue; + tintList.emplace(i, TintCouple(tintColor, tintMask->texture->str.data)); + } + } + + UInt8 modCount = modList.size(); + UInt8 partCount = partList.size(); + UInt8 tintCount = tintList.size(); + + currentFile.Write8(modCount); + for(auto mIt = modList.begin(); mIt != modList.end(); ++mIt) + { + currentFile.Write8(mIt->first); + + UInt16 strLen = strlen(mIt->second); + currentFile.Write16(strLen); + currentFile.WriteBuf(mIt->second, strLen); + } + + currentFile.Write8(partCount); + for(auto pIt = partList.begin(); pIt != partList.end(); ++pIt) + { + currentFile.Write8(pIt->first); + currentFile.Write32(pIt->second->formID); + } + + currentFile.WriteFloat(npc->weight); + + if (npc->faceMorph) + { + currentFile.Write8(TESNPC::FaceMorphs::kNumPresets); + for (UInt8 p = 0; p < TESNPC::FaceMorphs::kNumPresets; p++) + currentFile.Write8(npc->faceMorph->presets[p]); + + currentFile.Write8(TESNPC::FaceMorphs::kNumOptions); + for (UInt8 o = 0; o < TESNPC::FaceMorphs::kNumOptions; o++) + currentFile.WriteFloat(npc->faceMorph->option[o]); + } + else { + currentFile.Write8(0); + currentFile.Write8(0); + } + + UInt32 hairColor = 0; + if(npc->headData && npc->headData->hairColor) { + hairColor = npc->headData->hairColor->color.red << 16 | npc->headData->hairColor->color.green << 8 | npc->headData->hairColor->color.blue; + } + currentFile.Write32(hairColor); + + currentFile.Write8(tintCount); + for(auto tmIt = tintList.begin(); tmIt != tintList.end(); ++tmIt) + { + currentFile.Write8(tmIt->first); + currentFile.Write32(tmIt->second.first); + + UInt16 strLen = strlen(tmIt->second.second); + currentFile.Write16(strLen); + currentFile.WriteBuf(tmIt->second.second, strLen); + } + + SInt64 offset = currentFile.GetOffset(); + currentFile.Skip(sizeof(UInt8)); + UInt8 totalMorphs = 0; + + ValueSet * valueSet = m_valueMap.GetValueSet(npc); + if(valueSet) + { + for(auto it = valueSet->begin(); it != valueSet->end(); ++it) + { + if(it->second != 0.0) { + UInt16 strLen = strlen(it->first.data); + currentFile.Write16(strLen); + currentFile.WriteBuf(it->first.data, strLen); + + currentFile.WriteFloat(it->second); + totalMorphs++; + } + } + } + + SInt64 jumpBack = currentFile.GetOffset(); + currentFile.SetOffset(offset); + currentFile.Write8(totalMorphs); + + currentFile.SetOffset(jumpBack); + offset = currentFile.GetOffset(); + currentFile.Skip(sizeof(UInt8)); + UInt8 totalTextures = 0; + BGSHeadPart * facePart = npc->GetCurrentHeadPartByType(BGSHeadPart::kTypeFace); + if (facePart) { + BGSTextureSet * textureSet = GetTextureSetForPart(npc, facePart); + if (textureSet) { + for (UInt8 i = 0; i < BSShaderTextureSet::kNumTextures; i++) { + const char * texturePath = textureSet->textureSet.GetTexturePath(i); + if (texturePath != NULL) { + UInt16 strLen = strlen(texturePath); + currentFile.Write8(i); + currentFile.Write16(strLen); + currentFile.WriteBuf(texturePath, strLen); + totalTextures++; + } + } + } + } + currentFile.SetOffset(offset); + currentFile.Write8(totalTextures); + + // write header + currentFile.SetOffset(0); + currentFile.WriteBuf(&fileHeader, sizeof(fileHeader)); + + } + catch(...) + { + _ERROR("SavePreset: exception during save"); + } + + currentFile.Close(); + return false; +} + +PresetData::PresetData() +{ + weight = 0; + hairColor = 0; +} + +bool MorphHandler::LoadJsonPreset(const char * filePath, PresetDataPtr presetData) +{ + bool loadError = false; + BSResourceNiBinaryStream file(filePath); + if (!file.IsValid()) { + _ERROR("%s: File %s failed to open.", __FUNCTION__, filePath); + loadError = true; + return loadError; + } + + std::string in; + BSReadAll(&file, &in); + + Json::Features features; + features.all(); + + Json::Value root; + Json::Reader reader(features); + + bool parseSuccess = reader.parse(in, root); + if (!parseSuccess) { + _ERROR("%s: Error occured parsing json for %s.", __FUNCTION__, filePath); + loadError = true; + return loadError; + } + + Json::Value defaultValue; + Json::Value version = root["version"]; + if (version.empty()) { + _ERROR("%s: No version header.", __FUNCTION__); + loadError = true; + return loadError; + } + + UInt32 signature = version["signature"].asUInt(); + if (signature != PresetHeader::kSignature) + { + _ERROR("%s: invalid file signature (found %08X expected %08X)", __FUNCTION__, signature, PresetHeader::kSignature); + loadError = true; + return loadError; + } + + UInt32 formatVersion = version["formatVersion"].asUInt(); + if (formatVersion <= PresetHeader::kVersion_Invalid) + { + _ERROR("%s: version invalid (%08X)", __FUNCTION__, formatVersion); + loadError = true; + return loadError; + } + + Json::Value mods = root["mods"]; + if (mods.empty()) { + _ERROR("%s: No mods header.", __FUNCTION__); + loadError = true; + return loadError; + } + + DataHandler * dataHandler = DataHandler::GetSingleton(); + + std::map modList; + if (mods.type() == Json::arrayValue) { + for (auto & mod : mods) { + UInt8 modIndex = mod["index"].asUInt(); + std::string modName = mod["name"].asString(); + + modList.emplace(modIndex, modName); + presetData->modList.push_back(modName); + } + } + + Json::Value headParts = root["headParts"]; + if (!headParts.empty() && headParts.type() == Json::arrayValue) { + for (auto & part : headParts) { + + UInt8 partType = part["type"].asUInt(); + UInt32 formId = part["formId"].asUInt(); + + UInt8 modIndex = formId >> 24; + auto it = modList.find(modIndex); + if (it != modList.end()) { + UInt8 gameIndex = dataHandler->GetModIndex(it->second.c_str()); + if (gameIndex != 255) { + formId = (formId & 0x00FFFFFF) | (gameIndex << 24); + TESForm * headPartForm = LookupFormByID(formId); + if (headPartForm) { + BGSHeadPart * headPart = DYNAMIC_CAST(headPartForm, TESForm, BGSHeadPart); + if (headPart) { + presetData->headParts.push_back(headPart); + } + } + else { + _WARNING("Could not resolve part %08X", formId); + } + } + else { + _WARNING("Could not load part type %d from %s; mod not found.", partType, it->second.c_str()); + } + } + } + } + + Json::Value actor = root["actor"]; + if (!actor.empty() && actor.type() == Json::objectValue) { + presetData->weight = actor["weight"].asFloat(); + presetData->hairColor = actor["hairColor"].asUInt(); + } + + + Json::Value tintInfo = root["tintInfo"]; + if (!tintInfo.empty() && tintInfo.type() == Json::arrayValue) { + for (auto & tint : tintInfo) { + PresetData::Tint tintData; + tintData.color = tint["color"].asUInt(); + tintData.index = tint["index"].asUInt(); + tintData.name = tint["texture"].asString().c_str(); + presetData->tints.push_back(tintData); + } + } + + Json::Value faceTextures = root["faceTextures"]; + if (!faceTextures.empty() && faceTextures.type() == Json::arrayValue) { + for (auto & faceTexture : faceTextures) { + PresetData::Texture texture; + texture.index = faceTexture["index"].asUInt(); + texture.name = faceTexture["texture"].asString().c_str(); + presetData->faceTextures.push_back(texture); + } + } + + Json::Value morphs = root["morphs"]; + if (!morphs.empty()) { + Json::Value defaultMorphs = morphs["default"]; + if (!defaultMorphs.empty()) { + Json::Value presets = defaultMorphs["presets"]; + for (auto & preset : presets) { + UInt32 presetValue = preset.asUInt(); + if (presetValue == 255) + presetValue = -1; + + presetData->presets.push_back(presetValue); + } + + Json::Value morphs = defaultMorphs["morphs"]; + for (auto & morph : morphs) { + presetData->morphs.push_back(morph.asFloat()); + } + } + Json::Value customMorphs = morphs["custom"]; + if (!customMorphs.empty()) { + for (auto & customMorph : customMorphs) { + PresetData::Morph morph; + morph.name = customMorph["name"].asString().c_str(); + morph.value = customMorph["value"].asFloat(); + presetData->customMorphs.push_back(morph); + } + } + + SInt32 multiplier = -1; + + Json::Value sculptMult = morphs["sculptDivisor"]; + if (!sculptMult.empty()) + multiplier = sculptMult.asInt(); + + Json::Value sculptData = morphs["sculpt"]; + if (!sculptData.empty()) { + presetData->sculptData = std::make_shared(); + for (auto & hostFile : sculptData) { + BSFixedString host = hostFile["host"].asString().c_str(); + Json::Value data = hostFile["data"]; + + auto sculptedData = std::make_shared(); + for (auto & morphData : data) { + UInt16 index = morphData[0].asUInt(); + NiPoint3 pt; + + if (multiplier > 0) { + pt.x = (float)morphData[1].asInt() / (float)multiplier; + pt.y = (float)morphData[2].asInt() / (float)multiplier; + pt.z = (float)morphData[3].asInt() / (float)multiplier; + } else { + pt.x = morphData[1].asFloat(); + pt.y = morphData[2].asFloat(); + pt.z = morphData[3].asFloat(); + } + + sculptedData->force_insert(std::make_pair(index, pt)); + } + + presetData->sculptData->emplace(host, sculptedData); + } + } + } + + Json::Value transforms = root["transforms"]; + if (!transforms.empty()) { + for (auto & xForm : transforms) { + bool isFirstPerson = xForm["firstPerson"].asBool(); + BSFixedString nodeName = xForm["node"].asString().c_str(); + + Json::Value keys = xForm["keys"]; + for (auto & key : keys) { + BSFixedString keyName = key["name"].asString().c_str(); + + Json::Value values = key["values"]; + for (auto & jvalue : values) { + OverrideVariant value; + value.key = jvalue["key"].asUInt(); + value.type = jvalue["type"].asInt(); + value.index = jvalue["index"].asInt(); + switch (value.type) { + case OverrideVariant::kType_Bool: + value.data.b = jvalue["data"].asBool(); + break; + case OverrideVariant::kType_Int: + value.data.i = jvalue["data"].asInt(); + break; + case OverrideVariant::kType_Float: + value.data.f = jvalue["data"].asFloat(); + break; + case OverrideVariant::kType_String: + value.data.str = BSFixedString(jvalue["data"].asString().c_str()).data; + break; + } + + presetData->transformData[isFirstPerson ? 1 : 0][nodeName][keyName].push_back(value); + } + } + } + } + + Json::Value overrides = root["overrides"]; + if (!overrides.empty()) { + for (auto & ovr : overrides) { + BSFixedString node = ovr["node"].asString().c_str(); + Json::Value values = ovr["values"]; + for (auto & jvalue : values) { + OverrideVariant value; + value.key = jvalue["key"].asUInt(); + value.type = jvalue["type"].asInt(); + value.index = jvalue["index"].asInt(); + switch (value.type) { + case OverrideVariant::kType_Bool: + value.data.b = jvalue["data"].asBool(); + break; + case OverrideVariant::kType_Int: + value.data.i = jvalue["data"].asInt(); + break; + case OverrideVariant::kType_Float: + value.data.f = jvalue["data"].asFloat(); + break; + case OverrideVariant::kType_String: + value.data.str = BSFixedString(jvalue["data"].asString().c_str()).data; + break; + } + presetData->overrideData[node].push_back(value); + } + } + } + + Json::Value skinOverrides = root["skinOverrides"]; + if (!skinOverrides.empty()) { + for (auto & skinData : skinOverrides) { + bool isFirstPerson = skinData["firstPerson"].asBool(); + UInt32 slotMask = skinData["slotMask"].asUInt(); + + Json::Value values = skinData["values"]; + for (auto & jvalue : values) { + OverrideVariant value; + value.key = jvalue["key"].asUInt(); + value.type = jvalue["type"].asInt(); + value.index = jvalue["index"].asInt(); + switch (value.type) { + case OverrideVariant::kType_Bool: + value.data.b = jvalue["data"].asBool(); + break; + case OverrideVariant::kType_Int: + value.data.i = jvalue["data"].asInt(); + break; + case OverrideVariant::kType_Float: + value.data.f = jvalue["data"].asFloat(); + break; + case OverrideVariant::kType_String: + value.data.str = BSFixedString(jvalue["data"].asString().c_str()).data; + break; + } + + presetData->skinData[isFirstPerson ? 1 : 0][slotMask].push_back(value); + } + } + } + + Json::Value bodyMorphs = root["bodyMorphs"]; + if (!bodyMorphs.empty()) { + for (auto & bm : bodyMorphs) { + BSFixedString name = bm["name"].asString().c_str(); + + // Legacy version + Json::Value keyless = bm["value"]; + if (!keyless.empty()) + { + float value = bm["value"].asFloat(); + presetData->bodyMorphData[name]["RSMLegacy"] = value; + } + + // New version + Json::Value values = bm["keys"]; + if (!values.empty()) + { + for (auto & jvalue : values) { + BSFixedString key = jvalue["key"].asString().c_str(); + float value = jvalue["value"].asFloat(); + + // If the keys were mapped by mod name, skip them if they arent in load order + std::string strKey(key.data); + BSFixedString ext(strKey.substr(strKey.find_last_of(".") + 1).c_str()); + if (ext == BSFixedString("esp") || ext == BSFixedString("esm")) + { + if (!dataHandler->LookupModByName(key.data)) + continue; + } + + presetData->bodyMorphData[name][key] = value; + } + } + } + } + + return loadError; +} + +bool MorphHandler::LoadBinaryPreset(const char * filePath, PresetDataPtr presetData) +{ + bool loadError = false; + BSResourceNiBinaryStream file(filePath); + if (!file.IsValid()) { + _ERROR("%s: File %s failed to open.", __FUNCTION__, filePath); + loadError = true; + return loadError; + } + + try + { + PresetHeader header; + file.Read(&header, sizeof(header)); + + if(header.signature != PresetHeader::kSignature) + { + _ERROR("%s: invalid file signature (found %08X expected %08X)", __FUNCTION__, header.signature, PresetHeader::kSignature); + loadError = true; + goto done; + } + + if(header.formatVersion <= PresetHeader::kVersion_Invalid) + { + _ERROR("%s: version invalid (%08X)", __FUNCTION__, header.formatVersion); + loadError = true; + goto done; + } + + if(header.formatVersion < 2) + { + _ERROR("%s: version too old (found %08X current %08X)", __FUNCTION__, header.formatVersion, PresetHeader::kVersion); + goto done; + } + + DataHandler * dataHandler = DataHandler::GetSingleton(); + + typedef std::map ModMap; + typedef std::pair ModPair; + + ModMap modList; + UInt8 modCount = 0; + file.Read(&modCount, sizeof(modCount)); + + char textBuffer[MAX_PATH]; + for(UInt8 i = 0; i < modCount; i++) + { + UInt8 modIndex; + file.Read(&modIndex, sizeof(modIndex)); + + UInt16 strLen = 0; + file.Read(&strLen, sizeof(strLen)); + + memset(textBuffer, 0, MAX_PATH); + file.Read(textBuffer, strLen); + + std::string modName(textBuffer); + + modList.emplace(modIndex, modName); + presetData->modList.push_back(modName); + } + + UInt8 partCount = 0; + file.Read(&partCount, sizeof(partCount)); + + for(UInt8 i = 0; i < partCount; i++) + { + UInt8 partType = 0; + file.Read(&partType, sizeof(partType)); + + UInt32 formId = 0; + file.Read(&formId, sizeof(formId)); + + UInt8 modIndex = formId >> 24; + auto it = modList.find(modIndex); + if(it != modList.end()) { + UInt8 gameIndex = dataHandler->GetModIndex(it->second.c_str()); + if(gameIndex != 255) { + formId = (formId & 0x00FFFFFF) | (gameIndex << 24); + TESForm * headPartForm = LookupFormByID(formId); + if(headPartForm) { + BGSHeadPart * headPart = DYNAMIC_CAST(headPartForm, TESForm, BGSHeadPart); + if(headPart) { + presetData->headParts.push_back(headPart); + } + } else { + _WARNING("Could not resolve part %08X", formId); + } + } else { + _WARNING("Could not load part type %d from %s; mod not found.", partType, it->second.c_str()); + } + } + } + + float weight = 0.0; + file.Read(&weight, sizeof(weight)); + + presetData->weight = weight; + + UInt8 presetCount = 0; + file.Read(&presetCount, sizeof(presetCount)); + + for(UInt8 i = 0; i < presetCount; i++) + { + SInt32 preset = 0; + file.Read(&preset, sizeof(UInt8)); + + if(preset == 255) + preset = -1; + + presetData->presets.push_back(preset); + } + + UInt8 optionCount = 0; + file.Read(&optionCount, sizeof(optionCount)); + + for(UInt8 i = 0; i < optionCount; i++) + { + float option = 0.0; + file.Read(&option, sizeof(option)); + + presetData->morphs.push_back(option); + } + + UInt32 hairColor = 0; + file.Read(&hairColor, sizeof(hairColor)); + + presetData->hairColor = hairColor; + + UInt8 tintCount = 0; + file.Read(&tintCount, sizeof(tintCount)); + + for(UInt8 i = 0; i < tintCount; i++) + { + UInt8 tintIndex = 0; + file.Read(&tintIndex, sizeof(tintIndex)); + + UInt32 tintColor = 0; + file.Read(&tintColor, sizeof(tintColor)); + + UInt16 strLen = 0; + file.Read(&strLen, sizeof(strLen)); + + memset(textBuffer, 0, MAX_PATH); + file.Read(textBuffer, strLen); + + BSFixedString tintPath = textBuffer; + + PresetData::Tint tint; + tint.color = tintColor; + tint.index = tintIndex; + tint.name = tintPath; + presetData->tints.push_back(tint); + } + + UInt8 morphCount = 0; + file.Read(&morphCount, sizeof(morphCount)); + + for(UInt8 i = 0; i < morphCount; i++) + { + UInt16 strLen = 0; + file.Read(&strLen, sizeof(strLen)); + + memset(textBuffer, 0, MAX_PATH); + file.Read(textBuffer, strLen); + + float morphValue = 0.0; + file.Read(&morphValue, sizeof(morphValue)); + + PresetData::Morph morph; + morph.name = textBuffer; + morph.value = morphValue; + + presetData->customMorphs.push_back(morph); + } + + if(header.formatVersion >= 3) + { + UInt8 textureCount = 0; + file.Read(&textureCount, sizeof(textureCount)); + + for(UInt8 i = 0; i < textureCount; i++) + { + UInt8 textureIndex = 0; + file.Read(&textureIndex, sizeof(textureIndex)); + + UInt16 strLen = 0; + file.Read(&strLen, sizeof(strLen)); + + memset(textBuffer, 0, MAX_PATH); + file.Read(textBuffer, strLen); + + BSFixedString texturePath(textBuffer); + + PresetData::Texture texture; + texture.index = textureIndex; + texture.name = texturePath; + + presetData->faceTextures.push_back(texture); + } + } + } + catch(...) + { + _ERROR("%s: exception during load", __FUNCTION__); + loadError = true; + } + +done: + return loadError; +} + +BSFixedString SculptData::GetHostByPart(BGSHeadPart * headPart) +{ + const char * morphPath = headPart->chargenMorph.GetModelName(); + if (morphPath != NULL && morphPath[0] != NULL) + return morphPath; + + morphPath = headPart->morph.GetModelName(); + if (morphPath != NULL && morphPath[0] != NULL) + return morphPath; + + morphPath = headPart->raceMorph.GetModelName(); + if (morphPath != NULL && morphPath[0] != NULL) + return morphPath; + + return BSFixedString(""); +} + +MappedSculptDataPtr SculptData::GetSculptHost(BSFixedString host, bool create) +{ + auto it = find(host); + if (it != end()) + return it->second; + else if (create) { + auto data = std::make_shared(); + emplace(host, data); + return data; + } + + return nullptr; +} + +SculptDataPtr SculptStorage::GetSculptTarget(TESNPC * target, bool create) +{ + auto it = find(target); + if (it != end()) + return it->second; + else if (create) { + auto data = std::make_shared(); + emplace(target, data); + return data; + } + + return nullptr; +} + +void SculptStorage::SetSculptTarget(TESNPC * target, SculptDataPtr sculptData) +{ + auto it = find(target); + if (it != end()) + it->second = sculptData; + else { + emplace(target, sculptData); + } +} + +void SculptStorage::EraseNPC(TESNPC * npc) +{ + auto sculptTarget = find(npc); + if (sculptTarget != end()) { + erase(sculptTarget); + } +} + +SKSETaskApplyMorphs::SKSETaskApplyMorphs(Actor * actor) +{ + m_formId = actor->formID; +} + +void SKSETaskApplyMorphs::Run() +{ + if (!m_formId) + return; + + TESForm * form = LookupFormByID(m_formId); + Actor * actor = DYNAMIC_CAST(form, TESForm, Actor); + if (!actor) + return; + + TESNPC * actorBase = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (!actorBase) + return; + + BSFaceGenNiNode * faceNode = actor->GetFaceGenNiNode(); + if (faceNode) { + g_morphHandler.ApplyMorphs(actorBase, faceNode); + UpdateModelFace(faceNode); + } +} + +SKSETaskImportHead::SKSETaskImportHead(Actor * actor, BSFixedString nifPath) : m_nifPath(nifPath) +{ + m_formId = actor->formID; +} +/* +class TESNPC_Extn : public TESNPC +{ +public: + MEMBER_FN_PREFIX(TESNPC_Extn); + DEFINE_MEMBER_FN(UpdateHead, void, 0x560E00, NiGeometry * geometry); +}; + +class BSFaceGenNiNode_Extn : public BSFaceGenNiNode +{ +public: + MEMBER_FN_PREFIX(BSFaceGenNiNode_Extn); + DEFINE_MEMBER_FN(UpdateSkin, void, 0x5A83C0, NiAVObject * root, NiAVObject * geometry, UInt32 unk1); +}; + +class UnknownClass1 +{ +public: + static UnknownClass1 * GetSingleton(void) + { + return *((UnknownClass1 **)0x1BA7680); + } + + MEMBER_FN_PREFIX(UnknownClass1); + DEFINE_MEMBER_FN(Invalidate, void, 0xC78D80, NiAVObject * object); + DEFINE_MEMBER_FN(Invalidate2, void, 0xC77EB0, NiAVObject * object, char unk1, char unk2); +}; + +typedef UInt32(*_UpdateModelGeometryData)(NiAVObject*, UInt8 * update); +const _UpdateModelGeometryData UpdateModelGeometryData = (_UpdateModelGeometryData)0x005A9B30; + +typedef void (*_InvalidateGeometryData)(NiAVObject* object, NiAVObject* root); +const _InvalidateGeometryData InvalidateGeometryData = (_InvalidateGeometryData)0x00C6F3B0; +*/ + +void SKSETaskImportHead::Run() +{ + if (!m_formId) + return; + + TESForm * form = LookupFormByID(m_formId); + Actor * actor = DYNAMIC_CAST(form, TESForm, Actor); + if (!actor) + return; + + NiNode * root = actor->GetNiRootNode(0); + BSFaceGenNiNode * faceNode = actor->GetFaceGenNiNode(); + TESNPC * actorBase = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (!actorBase || !faceNode || !root) + return; + + BSFaceGenAnimationData * animationData = actor->GetFaceGenAnimationData(); + if (animationData) { + FaceGen::GetSingleton()->isReset = 0; + for (UInt32 t = BSFaceGenAnimationData::kKeyframeType_Expression; t <= BSFaceGenAnimationData::kKeyframeType_Phoneme; t++) + { + BSFaceGenKeyframeMultiple * keyframe = &animationData->keyFrames[t]; + for (UInt32 i = 0; i < keyframe->count; i++) + keyframe->values[i] = 0.0; + keyframe->isUpdated = 0; + } + UpdateModelFace(faceNode); + } + + UInt32 numParts = actorBase->numHeadParts; + BGSHeadPart ** headParts = actorBase->headparts; + if (CALL_MEMBER_FN(actorBase, HasOverlays)()) { + numParts = GetNumActorBaseOverlays(actorBase); + headParts = GetActorBaseOverlays(actorBase); + } + + std::unordered_map> typeMap; + for (UInt32 i = 0; i < numParts; i++) + { + BGSHeadPart * headPart = headParts[i]; + for (UInt32 p = 0; p < faceNode->m_children.m_size; p++) + { + NiAVObject * object = faceNode->m_children.m_data[p]; + if (object && BSFixedString(object->m_name) == headPart->partName) { + BSGeometry * geometry = object->GetAsBSGeometry(); + if (geometry) { + BSGeometry * otherGeometry = NULL; + geometry->IncRef(); + typeMap.emplace(SculptData::GetHostByPart(headPart), std::make_tuple(headPart, geometry, otherGeometry)); + break; + } + } + } + } + + UInt8 niStreamMemory[0x5B4]; + memset(niStreamMemory, 0, 0x5B4); + NiStream * niStream = (NiStream *)niStreamMemory; + CALL_MEMBER_FN(niStream, ctor)(); + + + BSResourceNiBinaryStream binaryStream(m_nifPath.data); + if (binaryStream.IsValid()) { + + NiNode * rootNode = NULL; + niStream->LoadStream(&binaryStream); + if (niStream->m_rootObjects.m_data) + { + if (niStream->m_rootObjects.m_data[0]) // Get the root node + rootNode = niStream->m_rootObjects.m_data[0]->GetAsNiNode(); + if (rootNode) + { + VisitObjects(rootNode, [&](NiAVObject* trishape) + { + NiNode * parent = trishape->m_parent; + if (parent && BSFixedString(parent->m_name) == BSFixedString("BSFaceGenNiNodeSkinned")) { + BSGeometry * geometry = trishape->GetAsBSGeometry(); + if (geometry) { + std::string name(trishape->m_name); + BGSHeadPart * part = GetHeadPartByName(name); + if (part) { + auto it = typeMap.find(SculptData::GetHostByPart(part)); + if (it != typeMap.end()) { + geometry->IncRef(); + std::get<2>(it->second) = geometry; + } + } + } + } + return false; + }); + + } + } + + CALL_MEMBER_FN(niStream, dtor)(); + } + + for (auto obj : typeMap) { + BGSHeadPart * part = std::get<0>(obj.second); + BSGeometry * source = std::get<1>(obj.second); + BSGeometry * target = std::get<2>(obj.second); + if (!part || !source || !target) + continue; + +#ifdef FIXME_GEOMETRY + NiGeometryData * sourceData = niptr_cast(source->m_spModelData); + NiGeometryData * targetData = niptr_cast(target->m_spModelData); + if (!sourceData || !targetData) + continue; + + if (sourceData->m_usVertices == targetData->m_usVertices) { + auto sculptHost = g_morphHandler.GetSculptTarget(actorBase); + if (sculptHost) { + auto sculptData = sculptHost->GetSculptHost(obj.first); + if (sculptData) { + BSFaceGenBaseMorphExtraData * extraData = (BSFaceGenBaseMorphExtraData *)source->GetExtraData("FOD"); + if (extraData) { + for (UInt32 i = 0; i < sourceData->m_usVertices; i++) { + NiPoint3 diff = targetData->m_pkVertex[i] - sourceData->m_pkVertex[i]; + + if (abs(diff.x) > 0.001 || abs(diff.y) > 0.001 || abs(diff.z) > 0.001) { + extraData->vertexData[i] += diff; + sculptData->force_insert(std::make_pair(i, diff)); + } + } + } + } + } + } +#endif + + if (source) + source->DecRef(); + if (target) + target->DecRef(); + } + + UpdateModelFace(faceNode); + + if (animationData) { + animationData->overrideFlag = 0; + CALL_MEMBER_FN(animationData, Reset)(1.0, 1, 1, 0, 0); + FaceGen::GetSingleton()->isReset = 1; + UpdateModelFace(faceNode); + } +} \ No newline at end of file diff --git a/skee/MorphHandler.h b/skee/MorphHandler.h new file mode 100644 index 0000000..cdd4746 --- /dev/null +++ b/skee/MorphHandler.h @@ -0,0 +1,448 @@ +#pragma once + +#include "skse64/GameTypes.h" + +#ifdef _DEBUG +//#define _DEBUG_HOOK +//#define _DEBUG_MORPHAPPLICATOR +//#define _DEBUG_DATADUMP +//#define _DEBUG_DATAREADER +//#define _DEBUG_MORPH +#endif + +class BSFaceGenNiNode; +class TESNPC; +class SliderArray; +class RaceMenuSlider; +struct SKSESerializationInterface; +class TESRace; +class BGSHeadPart; +class TESForm; +class TESModelTri; + +#define SLIDER_OFFSET 200 +#define SLIDER_CATEGORY_EXTRA 512 +#define SLIDER_CATEGORY_EXPRESSIONS 1024 + +#define SLIDER_MOD_DIRECTORY "actors\\character\\FaceGenMorphs\\" +#define SLIDER_DIRECTORY "actors\\character\\FaceGenMorphs\\morphs\\" + +#define MORPH_CACHE_TEMPLATE "%08X.tri" +#define MORPH_CACHE_DIR "cache\\" +#define MORPH_CACHE_PATH "actors\\character\\FaceGenMorphs\\morphs\\cache\\" + +#include +#include +#include +#include +#include + +#include "IHashType.h" +#include "OverrideVariant.h" + +#include "skse64/GameResources.h" +#include "skse64/GameThreads.h" + +class SliderInternal +{ +public: + SliderInternal::SliderInternal() + { + category = -1; + name = ""; + lowerBound = ""; + upperBound = ""; + type = 0; + presetCount = 0; + } + + void SliderInternal::copy(SliderInternal * slider) + { + category = slider->category; + name = slider->name; + lowerBound = slider->lowerBound; + upperBound = slider->upperBound; + type = slider->type; + presetCount = slider->presetCount; + } + + enum + { + kCategoryExpressions = SLIDER_CATEGORY_EXPRESSIONS, + kCategoryExtra = SLIDER_CATEGORY_EXTRA, + kCategoryBody = 4, + kCategoryHead = 8, + kCategoryFace = 16, + kCategoryEyes = 32, + kCategoryBrow = 64, + kCategoryMouth = 128, + kCategoryHair = 256 + }; + + enum + { + kTypeSlider = 0, + kTypePreset = 1, + kTypeHeadPart = 2 + }; + + SInt32 category; + BSFixedString name; + BSFixedString lowerBound; + BSFixedString upperBound; + UInt8 type; + UInt8 presetCount; +}; + +typedef std::shared_ptr SliderInternalPtr; + +class SliderGender +{ +public: + SliderGender::SliderGender() + { + slider[0] = NULL; + slider[1] = NULL; + } + + SliderInternalPtr slider[2]; +}; +typedef std::shared_ptr SliderGenderPtr; +typedef std::vector SliderList; +typedef std::map RaceSliders; +typedef std::vector MorphSet; + +class MorphMap : public std::map +{ +public: + class Visitor + { + public: + virtual bool Accept(BSFixedString morphName) { return false; }; + }; + + void AddMorph(BSFixedString key, BSFixedString value); + bool Visit(BSFixedString key, Visitor & visitor); + void Revert(); + +#ifdef _DEBUG_DATADUMP + void DumpMap(); + class DumpVisitor : public Visitor + { + public: + virtual bool Accept(BSFixedString morphName) + { + _MESSAGE("Extra Morph: %s", morphName.data); + return false; + }; + }; +#endif +}; + +class SliderMap : public std::map +{ +public: + SliderMap::SliderMap() : std::map(){ } + + void AddSlider(BSFixedString key, UInt8 gender, SliderInternal & slider); + +#ifdef _DEBUG_DATADUMP + void DumpMap(); +#endif +}; + +typedef std::shared_ptr SliderMapPtr; + + +class SliderSet : public std::set +{ +public: + bool for_each_slider(std::function func); +}; + +typedef std::shared_ptr SliderSetPtr; + +class RaceMap : public std::map +{ +public: + SliderSetPtr GetSliderSet(TESRace * race); + bool AddSliderMap(TESRace * race, SliderMapPtr sliderMap); + bool CreateDefaultMap(TESRace * race); + + void Revert(); + +#ifdef _DEBUG_DATADUMP + void DumpMap(); +#endif +}; + +class ValueSet : public std::unordered_map +{ +public: + void SetValue(BSFixedString name, float value); + void ClearValue(BSFixedString name); + float GetValue(BSFixedString name); +}; + +class ValueMap : public std::unordered_map +{ +public: + ValueSet * GetValueSet(TESNPC* npc); + void EraseNPC(TESNPC * npc); + + float GetMorphValueByName(TESNPC* npc, BSFixedString name); + void SetMorphValue(TESNPC* npc, BSFixedString name, float value); +}; + +#define VERTEX_THRESHOLD 0.00001 +#define VERTEX_MULTIPLIER 10000 + +class MappedSculptData : public std::unordered_map +{ +public: + void force_insert(value_type const & v) + { + if (abs(v.second.x) < VERTEX_THRESHOLD && abs(v.second.y) < VERTEX_THRESHOLD && abs(v.second.z) < VERTEX_THRESHOLD) + return; + + auto res = insert(v); + if (!res.second) + (*res.first).second = v.second; + } + + void add(value_type const & v) + { + auto res = insert(v); + if (!res.second) + (*res.first).second += v.second; + + if (abs((*res.first).second.x) < VERTEX_THRESHOLD && abs((*res.first).second.y) < VERTEX_THRESHOLD && abs((*res.first).second.z) < VERTEX_THRESHOLD) + erase(res.first); + } +}; +typedef std::shared_ptr MappedSculptDataPtr; + +class SculptData : public std::unordered_map < BSFixedString, MappedSculptDataPtr > +{ +public: + MappedSculptDataPtr GetSculptHost(BSFixedString, bool create = true); + + static BSFixedString GetHostByPart(BGSHeadPart * headPart); +}; +typedef std::shared_ptr SculptDataPtr; + +class SculptStorage : public std::unordered_map < TESNPC*, SculptDataPtr > +{ +public: + void SetSculptTarget(TESNPC * npc, SculptDataPtr sculptData); + SculptDataPtr GetSculptTarget(TESNPC* npc, bool create = true); + void EraseNPC(TESNPC * npc); +}; + + +class PresetData +{ +public: + PresetData(); + + struct Tint + { + UInt32 index; + UInt32 color; + BSFixedString name; + }; + + struct Morph + { + float value; + BSFixedString name; + }; + + struct Texture + { + UInt8 index; + BSFixedString name; + }; + + float weight; + UInt32 hairColor; + std::vector modList; + std::vector headParts; + std::vector presets; + std::vector morphs; + std::vector tints; + std::vector customMorphs; + std::vector faceTextures; + BSFixedString tintTexture; + typedef std::map> OverrideData; + OverrideData overrideData; + typedef std::map> SkinData; + SkinData skinData[2]; + typedef std::map>> TransformData; + TransformData transformData[2]; + SculptDataPtr sculptData; + typedef std::unordered_map> BodyMorphData; + BodyMorphData bodyMorphData; +}; +typedef std::shared_ptr PresetDataPtr; + +class TRIFile +{ +public: + TRIFile() + { + vertexCount = -1; + } + + bool Load(const char * triPath); + bool Apply(BSGeometry * geometry, BSFixedString morph, float relative); + + struct Morph + { + BSFixedString name; + float multiplier; + + struct Vertex + { + SInt16 x, y, z; + }; + + std::vector vertices; + }; + + SInt32 vertexCount; + std::unordered_map morphs; +}; + +class TRIModelData +{ +public: + TRIModelData() + { + vertexCount = -1; + morphModel = NULL; + } + SInt32 vertexCount; + std::shared_ptr triFile; + TESModelTri * morphModel; +}; + +typedef std::unordered_map ModelMap; +typedef std::unordered_map PresetMap; + +class MorphHandler +{ +public: + void Save(TESNPC* npc, SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(TESNPC* npc, SKSESerializationInterface * intfc, UInt32 kVersion); + void Revert(); + void RevertInternals(); + + void LoadMods(); + + float GetMorphValueByName(TESNPC* npc, BSFixedString name); + void SetMorphValue(TESNPC* npc, BSFixedString name, float value); + + void SetMorph(TESNPC * npc, BSFaceGenNiNode * faceNode, const char * name, float relative); + + void ApplyMorph(TESNPC * npc, BGSHeadPart * headPart, BSFaceGenNiNode * faceNode); + void ApplyMorphs(TESNPC * npc, BSFaceGenNiNode * faceNode); + + SInt32 LoadSliders(tArray * sliderArray, RaceMenuSlider * slider); + + void ReadMorphs(std::string fixedPath, std::string modName, std::string fileName); + void ReadRaces(std::string fixedPath, std::string modName, std::string fileName); + SliderMapPtr ReadSliders(std::string fixedPath, std::string modName, std::string fileName); + + SliderInternalPtr GetSlider(TESRace * race, UInt8 gender, BSFixedString name); + SliderInternalPtr GetSliderByIndex(TESRace * race, UInt32 index); + + SliderList * CreateSliderList(TESRace * race, UInt8 gender); + void AddSlider(TESRace * race, SliderInternalPtr & slider); + + bool VisitMorphMap(BSFixedString key, MorphMap::Visitor & visitor); + + bool CacheHeadPartModel(BGSHeadPart * headPart, bool cacheTRI = false); + bool GetModelTri(BSFixedString filePath, TRIModelData & modelData); + TRIModelData & GetExtendedModelTri(BSFixedString morphName, bool cacheTRI = false); + + SliderList * currentList; + RaceSliders m_internalMap; + RaceMap m_raceMap; + MorphMap m_morphMap; + ValueMap m_valueMap; + ModelMap m_modelMap; + PresetMap m_mappedPreset; + + SculptStorage m_sculptStorage; + inline SculptDataPtr GetSculptTarget(TESNPC * npc, bool create = true) + { + return m_sculptStorage.GetSculptTarget(npc, create); + } + inline void SetSculptTarget(TESNPC * npc, const SculptDataPtr & data) + { + return m_sculptStorage.SetSculptTarget(npc, data); + } + inline void EraseSculptData(TESNPC * npc) + { + m_sculptStorage.EraseNPC(npc); + } + inline void EraseMorphData(TESNPC * npc) + { + m_valueMap.EraseNPC(npc); + } + + PresetDataPtr GetPreset(TESNPC* npc); + void AssignPreset(TESNPC * npc, PresetDataPtr presetData); + void ApplyPreset(TESNPC * npc, BSFaceGenNiNode * faceNode, BGSHeadPart * headPart); + bool ErasePreset(TESNPC * npc); + void ClearPresets(); + + bool SaveBinaryPreset(const char * filePath); + //bool LoadPreset(const char * filePath, GFxMovieView * movieView, GFxValue * rootObject); + bool LoadBinaryPreset(const char * filePath, PresetDataPtr presetData); + + enum ApplyTypes + { + kPresetApplyFace = (0 << 0), + kPresetApplyOverrides = (1 << 0), + kPresetApplyBodyMorphs = (1 << 1), + kPresetApplyTransforms = (1 << 2), + kPresetApplySkinOverrides = (1 << 3), + kPresetApplyAll = kPresetApplyFace | kPresetApplyOverrides | kPresetApplyBodyMorphs | kPresetApplyTransforms | kPresetApplySkinOverrides + }; + + void ApplyPresetData(Actor * actor, PresetDataPtr presetData, bool setSkinColor = false, ApplyTypes applyType = kPresetApplyAll); + + bool SaveJsonPreset(const char * filePath); + bool LoadJsonPreset(const char * filePath, PresetDataPtr presetData); + +#ifdef _DEBUG_DATADUMP + void DumpAll(); +#endif +}; + +class SKSETaskImportHead : public TaskDelegate +{ +public: + virtual void Run(); + virtual void Dispose() { delete this; } + + SKSETaskImportHead(Actor * actor, BSFixedString nifPath); + +private: + UInt32 m_formId; + BSFixedString m_nifPath; +}; + +class SKSETaskApplyMorphs : public TaskDelegate +{ +public: + virtual void Run(); + virtual void Dispose() { delete this; } + + SKSETaskApplyMorphs(Actor * actor); + +private: + UInt32 m_formId; +}; \ No newline at end of file diff --git a/skee/NiTransformInterface.cpp b/skee/NiTransformInterface.cpp new file mode 100644 index 0000000..07cea5c --- /dev/null +++ b/skee/NiTransformInterface.cpp @@ -0,0 +1,937 @@ +#include "NiTransformInterface.h" +#include "OverrideInterface.h" +#include "ShaderUtilities.h" +#include "SkeletonExtender.h" +#include "StringTable.h" +#include "NifUtils.h" + +#include "skse64/PluginAPI.h" +#include "skse64/GameReferences.h" +#include "skse64/GameRTTI.h" +#include "skse64/GameData.h" + +#include "skse64/GameStreams.h" + +#include "skse64/NiNodes.h" +#include "skse64/NiSerialization.h" +#include "skse64/NiExtraData.h" + +#include + +extern OverrideInterface g_overrideInterface; +extern SKSETaskInterface * g_task; +extern StringTable g_stringTable; +extern bool g_enableEquippableTransforms; +extern UInt16 g_scaleMode; + +UInt32 NiTransformInterface::GetVersion() +{ + return kCurrentPluginVersion; +} + +void NodeTransformKeys::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 numNodes = this->size(); + intfc->WriteRecordData(&numNodes, sizeof(numNodes)); + + for (NodeTransformKeys::iterator it = this->begin(); it != this->end(); ++it) + { + intfc->OpenRecord('NOTM', kVersion); + + // Key + WriteKey(intfc, it->first, kVersion); + + // Value + it->second.Save(intfc, kVersion); + } +} + +bool NodeTransformKeys::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + // Handle count + UInt32 numRegs = 0; + if (!intfc->ReadRecordData(&numRegs, sizeof(numRegs))) + { + _ERROR("%s - Error loading override registration count", __FUNCTION__); + error = true; + return error; + } + + for (UInt32 i = 0; i < numRegs; i++) + { + if (intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'NOTM': + { + BSFixedString key; + if (ReadKey(intfc, key, kVersion)) { + _ERROR("%s - Error loading node entry key", __FUNCTION__); + error = true; + return error; + } + + // operator[] not working for some odd reason + bool loadError = false; + NodeTransformKeys::iterator iter = this->find(key); // Find existing first + if (iter != this->end()) { + error = iter->second.Load(intfc, version); + } + else { // No existing, create + OverrideRegistration set; + error = set.Load(intfc, version); + emplace(key, set); + } + if (loadError) + { + _ERROR("%s - Error loading node overrides", __FUNCTION__); + error = true; + return error; + } + break; + } + default: + { + _ERROR("%s - Error loading unexpected chunk type %08X (%.4s)", __FUNCTION__, type, &type); + error = true; + return error; + } + } + } + } + + return error; +} + +void NodeTransformRegistrationMapHolder::Save(SKSESerializationInterface* intfc, UInt32 kVersion) +{ + for (NodeTransformRegistrationMapHolder::RegMap::iterator it = m_data.begin(); it != m_data.end(); ++it) { + intfc->OpenRecord('ACTM', kVersion); + + // Key + UInt64 handle = it->first; + intfc->WriteRecordData(&handle, sizeof(handle)); + +#ifdef _DEBUG + _MESSAGE("%s - Saving Handle %016llX", __FUNCTION__, handle); +#endif + + // Value + it->second.Save(intfc, kVersion); + } +} + +bool NodeTransformRegistrationMapHolder::Load(SKSESerializationInterface* intfc, UInt32 kVersion, UInt64 * outHandle) +{ + bool error = false; + + UInt64 handle = 0; + // Key + if (!intfc->ReadRecordData(&handle, sizeof(handle))) + { + _ERROR("%s - Error loading reg key", __FUNCTION__); + error = true; + return error; + } + + MultiRegistration,2> reg; + if (reg.Load(intfc, kVersion)) + { + _ERROR("%s - Error loading transform gender registrations", __FUNCTION__); + error = true; + return error; + } + + UInt64 newHandle = 0; + if (!ResolveAnyHandle(intfc, handle, &newHandle)) { + *outHandle = 0; + return error; + } + + // Invalid handle + TESObjectREFR * refr = (TESObjectREFR *)g_overrideInterface.GetObject(handle, TESObjectREFR::kTypeID); + if (!refr) { + *outHandle = 0; + return error; + } + + if (reg.empty()) { + *outHandle = 0; + return error; + } + + *outHandle = newHandle; + + Lock(); + m_data[newHandle] = reg; + Release(); + +#ifdef _DEBUG + _MESSAGE("%s - Loaded Handle %016llX", __FUNCTION__, newHandle); +#endif + + //SetHandleProperties(newHandle, false); + return error; +} + +class NIOVTaskUpdateReference : public TaskDelegate +{ +public: + NIOVTaskUpdateReference(UInt64 handle, NiTransformInterface * xFormInterface) + { + m_handle = handle; + m_interface = xFormInterface; + } + virtual void Run() + { + m_interface->SetHandleNodeTransforms(m_handle, true); + } + virtual void Dispose() + { + delete this; + } + + UInt64 m_handle; + NiTransformInterface * m_interface; +}; + +void NiTransformInterface::VisitStrings(std::function functor) +{ + for (auto & i1 : transformData.m_data) { + for (UInt8 gender = 0; gender <= 1; gender++) { + for (UInt8 fp = 0; fp <= 1; fp++) { + for (auto & i2 : i1.second[gender][fp]) { + functor(i2.first); + for (auto & i3 : i2.second) { + functor(i3.first); + for (auto & i4 : i3.second) { + if (i4.type == OverrideVariant::kType_String) { + BSFixedString str(i4.data.str); + functor(str); + } + } + } + } + } + } + } +} + +void NiTransformInterface::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + transformData.Save(intfc, kVersion); +} +bool NiTransformInterface::Load(SKSESerializationInterface* intfc, UInt32 kVersion) +{ + UInt64 handle = 0; + if (!transformData.Load(intfc, kVersion, &handle)) + { + RemoveInvalidTransforms(handle); + RemoveNamedTransforms(handle, "internal"); + + NIOVTaskUpdateReference * updateTask = new NIOVTaskUpdateReference(handle, this); + if (g_task) { + g_task->AddTask(updateTask); + } + else { + updateTask->Run(); + updateTask->Dispose(); + } + } + + return false; +} + +bool NiTransformInterface::AddNodeTransform(TESObjectREFR * refr, bool firstPerson, bool isFemale, BSFixedString node, BSFixedString name, OverrideVariant & value) +{ + SimpleLocker lock(&transformData.m_lock); + + UInt64 handle = g_overrideInterface.GetHandle(refr, refr->formType); + transformData.m_data[handle][isFemale ? 1 : 0][firstPerson ? 1 : 0][node][name].erase(value); + transformData.m_data[handle][isFemale ? 1 : 0][firstPerson ? 1 : 0][node][name].insert(value); + return true; +} + + +bool NiTransformInterface::RemoveNodeTransform(TESObjectREFR * refr, bool firstPerson, bool isFemale, BSFixedString node, BSFixedString name) +{ + SimpleLocker lock(&transformData.m_lock); + + UInt8 gender = isFemale ? 1 : 0; + UInt8 fp = firstPerson ? 1 : 0; + UInt64 handle = g_overrideInterface.GetHandle(refr, refr->formType); + + auto & it = transformData.m_data.find(handle); + if (it != transformData.m_data.end()) + { + auto & ait = it->second[gender][fp].find(node); + if (ait != it->second[gender][fp].end()) + { + auto & oit = ait->second.find(name); + if (oit != ait->second.end()) + { + ait->second.erase(oit); + return true; + } + } + } + + return false; +} + +void NiTransformInterface::RemoveInvalidTransforms(UInt64 handle) +{ + auto & it = transformData.m_data.find(handle); + if (it != transformData.m_data.end()) + { + for (UInt8 gender = 0; gender <= 1; gender++) + { + for (UInt8 fp = 0; fp <= 1; fp++) + { + for (auto & ait : it->second[gender][fp]) + { + for (auto it = ait.second.begin(); it != ait.second.end();) + { + std::string strKey(it->first.data); + BSFixedString ext(strKey.substr(strKey.find_last_of(".") + 1).c_str()); + if (ext == BSFixedString("esp") || ext == BSFixedString("esm")) + { + it = ait.second.erase(it); + } + else + ++it; + } + } + } + } + } +} + +void NiTransformInterface::RemoveNamedTransforms(UInt64 handle, BSFixedString name) +{ + SimpleLocker lock(&transformData.m_lock); + + auto & it = transformData.m_data.find(handle); + if (it != transformData.m_data.end()) + { + for (UInt8 gender = 0; gender <= 1; gender++) + { + for (UInt8 fp = 0; fp <= 1; fp++) + { + for (auto & ait : it->second[gender][fp]) + { + auto & oit = ait.second.find(name); + if (oit != ait.second.end()) + { + ait.second.erase(oit); + } + } + } + } + } +} + +void NiTransformInterface::Revert() +{ + // Revert all transforms to their base data + for (auto & it : transformData.m_data) { + SetHandleNodeTransforms(it.first, false, true); + } + + SimpleLocker lock(&transformData.m_lock); + transformData.m_data.clear(); +} + +void NiTransformInterface::RemoveAllReferenceTransforms(TESObjectREFR * refr) +{ + SimpleLocker lock(&transformData.m_lock); + + UInt64 handle = g_overrideInterface.GetHandle(refr, refr->formType); + auto & it = transformData.m_data.find(handle); + if (it != transformData.m_data.end()) + { + transformData.m_data.erase(it); + } +} + +bool NiTransformInterface::RemoveNodeTransformComponent(TESObjectREFR * refr, bool firstPerson, bool isFemale, BSFixedString node, BSFixedString name, UInt16 key, UInt16 index) +{ + SimpleLocker lock(&transformData.m_lock); + + UInt8 gender = isFemale ? 1 : 0; + UInt8 fp = firstPerson ? 1 : 0; + UInt64 handle = g_overrideInterface.GetHandle(refr, refr->formType); + auto & it = transformData.m_data.find(handle); + if (it != transformData.m_data.end()) + { + auto & ait = it->second[gender][fp].find(node); + if (ait != it->second[gender][fp].end()) + { + auto & oit = ait->second.find(name); + if (oit != ait->second.end()) + { + OverrideVariant ovr; + ovr.key = key; + ovr.index = index; + auto & ost = oit->second.find(ovr); + if (ost != oit->second.end()) + { + oit->second.erase(ost); + return true; + } + } + } + } + + return false; +} + +void NiTransformInterface::VisitNodes(TESObjectREFR * refr, bool firstPerson, bool isFemale, std::function * value)> functor) +{ + SimpleLocker lock(&transformData.m_lock); + + UInt8 gender = isFemale ? 1 : 0; + UInt8 fp = firstPerson ? 1 : 0; + UInt64 handle = g_overrideInterface.GetHandle(refr, refr->formType); + + auto & it = transformData.m_data.find(handle); // Find ActorHandle + if (it != transformData.m_data.end()) + { + for (auto node : it->second[gender][fp]) { + if (functor(node.first, &node.second)) + break; + } + } +} + +bool NiTransformInterface::VisitNodeTransforms(TESObjectREFR * refr, bool firstPerson, bool isFemale, BSFixedString node, std::function*)> each_key, std::function finalize) +{ + SimpleLocker lock(&transformData.m_lock); + + bool ret = false; + UInt8 gender = isFemale ? 1 : 0; + UInt8 fp = firstPerson ? 1 : 0; + UInt64 handle = g_overrideInterface.GetHandle(refr, refr->formType); + auto & it = transformData.m_data.find(handle); // Find ActorHandle + if (it != transformData.m_data.end()) + { + NiNode * root = refr->GetNiRootNode(fp); + if (root) { + NiAutoRefCounter rc(root); + BSFixedString skeleton = GetRootModelPath(refr, firstPerson, isFemale); + NiAVObject * foundNode = root->GetObjectByName(&node.data); + if (foundNode) { + NiAutoRefCounter rc(foundNode); + NiTransform * baseTransform = transformCache.GetBaseTransform(skeleton, node, true); + if (!baseTransform) { + // Look at extensions + VisitObjects(root, [&](NiAVObject * root) + { + NiExtraData * extraData = root->GetExtraData(BSFixedString("EXTN").data); + if (extraData) { + NiAutoRefCounter rc(extraData); + NiStringsExtraData * extraSkeletons = ni_cast(extraData, NiStringsExtraData); + if (extraSkeletons && (extraSkeletons->m_size % 3) == 0) { + for (UInt32 i = 0; i < extraSkeletons->m_size; i+= 3) { + BSFixedString extnSkeleton = extraSkeletons->m_data[i+2]; + baseTransform = transformCache.GetBaseTransform(extnSkeleton, node, false); + if (baseTransform) + return true; + } + } + } + + return false; + }); + } + + if (baseTransform) { + auto & nodeIt = it->second[gender][fp].find(node); + if (nodeIt != it->second[gender][fp].end()) + if (each_key(&nodeIt->second)) + ret = true; + + if (finalize) + finalize(root, foundNode, baseTransform); + } + } + } + } + + return ret; +} + +void NiTransformInterface::UpdateNodeTransforms(TESObjectREFR * ref, bool firstPerson, bool isFemale, BSFixedString node) +{ + BSFixedString target(""); + NiTransform transformResult; + VisitNodeTransforms(ref, firstPerson, isFemale, node, + [&](OverrideRegistration* keys) + { + for (auto dit = keys->begin(); dit != keys->end(); ++dit) {// Loop Keys + NiTransform localTransform; + GetOverrideTransform(&dit->second, OverrideVariant::kParam_NodeTransformPosition, &localTransform); + GetOverrideTransform(&dit->second, OverrideVariant::kParam_NodeTransformScale, &localTransform); + GetOverrideTransform(&dit->second, OverrideVariant::kParam_NodeTransformRotation, &localTransform); + transformResult = localTransform * transformResult; + + OverrideVariant value; + value.key = OverrideVariant::kParam_NodeDestination; + auto & it = dit->second.find(value); + if (it != dit->second.end()) { + target = BSFixedString(it->data.str); + } + } + return false; + }, + [&](NiNode * root, NiAVObject * foundNode, NiTransform * baseTransform) + { + // Process Node Movement + bool noTarget = target == BSFixedString(""); + if (!noTarget) { + NiAVObject * targetNode = root->GetObjectByName(&target.data); + if (targetNode) { + NiNode * parentNode = targetNode->GetAsNiNode(); + if (parentNode) { + if (g_task) + g_task->AddTask(new NIOVTaskMoveNode(parentNode, foundNode)); + } + } + } + + // Process Transform + foundNode->m_localTransform = (*baseTransform) * transformResult; + if (g_task) + g_task->AddTask(new NIOVTaskUpdateWorldData(foundNode)); + }); +} + +OverrideVariant NiTransformInterface::GetOverrideNodeValue(TESObjectREFR * refr, bool firstPerson, bool isFemale, BSFixedString node, BSFixedString name, UInt16 key, SInt8 index) +{ + OverrideVariant foundValue; + VisitNodeTransforms(refr, firstPerson, isFemale, node, + [&](OverrideRegistration* keys) + { + if (name == BSFixedString("")) { + return true; + } + else { + auto & it = keys->find(name); + if (it != keys->end()) { + OverrideVariant searchValue; + searchValue.key = key; + searchValue.index = index; + auto & sit = it->second.find(searchValue); + if (sit != it->second.end()) + foundValue = *sit; + return true; + } + } + + return false; + }, + std::function()); + return foundValue; +} + +bool NiTransformInterface::GetOverrideNodeTransform(TESObjectREFR * refr, bool firstPerson, bool isFemale, BSFixedString node, BSFixedString name, UInt16 key, NiTransform * result) +{ + return VisitNodeTransforms(refr, firstPerson, isFemale, node, + [&](OverrideRegistration* keys) + { + if (name == BSFixedString("")) { + return true; + } else { + auto it = keys->find(name); + if (it != keys->end()) { + GetOverrideTransform(&it->second, key, result); + return true; + } + } + + return false; + }, + [&](NiNode * root, NiAVObject * foundNode, NiTransform * baseTransform) + { + if (name == BSFixedString("")) + *result = *baseTransform; + }); +} + +void NiTransformInterface::UpdateNodeAllTransforms(TESObjectREFR * refr) +{ + UInt64 handle = g_overrideInterface.GetHandle(refr, refr->formType); + SetHandleNodeTransforms(handle); +} + +void NiTransformInterface::SetHandleNodeTransforms(UInt64 handle, bool immediate, bool reset) +{ + SimpleLocker lock(&transformData.m_lock); + + TESObjectREFR * refr = (TESObjectREFR *)g_overrideInterface.GetObject(handle, TESObjectREFR::kTypeID); + if (!refr) { + return; + } + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if (actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + auto & it = transformData.m_data.find(handle); // Find ActorHandle + if (it != transformData.m_data.end()) + { + std::unordered_map nodeMovement; + NiNode * lastNode = NULL; + for (UInt8 i = 0; i <= 1; i++) + { + NiNode * root = refr->GetNiRootNode(i); + if (root == lastNode) // First and Third are the same, skip + continue; + + BSFixedString skeleton = GetRootModelPath(refr, i >= 1 ? true : false, gender >= 1 ? true : false); + if (root) + { + NiAutoRefCounter rc(root); + // Gather up skeleton extensions + std::vector additionalSkeletons; + std::set modified, changed; + VisitObjects(root, [&](NiAVObject * root) + { + NiExtraData * extraData = root->GetExtraData(BSFixedString("EXTN").data); + if (extraData) { + NiAutoRefCounter rc(extraData); + NiStringsExtraData * extraSkeletons = ni_cast(extraData, NiStringsExtraData); + if (extraSkeletons && (extraSkeletons->m_size % 3) == 0) { + for (UInt32 i = 0; i < extraSkeletons->m_size; i += 3) { + BSFixedString extnSkeleton = extraSkeletons->m_data[i+2]; + additionalSkeletons.push_back(extnSkeleton); + } + } + } + if (g_enableEquippableTransforms) + { + NiStringExtraData * stringData = ni_cast(root->GetExtraData(BSFixedString("SDTA").data), NiStringExtraData); + if (stringData) + { + SkeletonExtender::ReadTransforms(refr, stringData, i >= 1 ? true : false, gender >= 1 ? true : false, modified, changed); + } + } + + return false; + }); + + if (g_enableEquippableTransforms) + { + NiStringsExtraData * globalData = ni_cast(FindExtraData(root, "BNDT"), NiStringsExtraData); + if (globalData) + { + if (modified.size() > 0) + { + std::vector newNodes; + for (auto & node : modified) + { + newNodes.push_back(node); + } + + globalData->SetData(&newNodes.at(0), newNodes.size()); + } + } + } + + for (auto & ait = it->second[gender][i].begin(); ait != it->second[gender][i].end(); ++ait) // Loop Nodes + { + NiTransform * baseTransform = transformCache.GetBaseTransform(skeleton, ait->first, true); + if (!baseTransform) { // Not found in base skeleton, search additional skeletons + for (auto & secondaryPath : additionalSkeletons) { + baseTransform = transformCache.GetBaseTransform(secondaryPath, ait->first, false); + if (baseTransform) + break; + } + } + + if (baseTransform) + { + BSFixedString target(""); + float fScaleValue = 1.0; + NiTransform combinedTransform; + if (!reset) { + for (auto dit = ait->second.begin(); dit != ait->second.end(); ++dit) {// Loop Keys + NiTransform localTransform; + GetOverrideTransform(&dit->second, OverrideVariant::kParam_NodeTransformPosition, &localTransform); + GetOverrideTransform(&dit->second, OverrideVariant::kParam_NodeTransformScale, &localTransform); + GetOverrideTransform(&dit->second, OverrideVariant::kParam_NodeTransformRotation, &localTransform); + combinedTransform = combinedTransform * localTransform; + + if (g_scaleMode == 1 || g_scaleMode == 2) + { + fScaleValue += localTransform.scale; + } + if (g_scaleMode == 3 && localTransform.scale > fScaleValue) + { + fScaleValue = localTransform.scale; + } + + // Find node movement + OverrideVariant value; + value.key = OverrideVariant::kParam_NodeDestination; + auto & it = dit->second.find(value); + if (it != dit->second.end()) { + target = BSFixedString(it->data.str); + } + } + if (g_scaleMode == 1) + { + combinedTransform.scale = fScaleValue / (float)(ait->second.size() + 1); + } + if (g_scaleMode == 2 || g_scaleMode == 3) + { + combinedTransform.scale = fScaleValue; + } + } + BSFixedString nodeName = ait->first; + NiAVObject * transformable = root->GetObjectByName(&nodeName.data); + if (transformable) { + NiAutoRefCounter rc(transformable); + transformable->m_localTransform = (*baseTransform) * combinedTransform; + + // Collect Node Movements + bool noTarget = target == BSFixedString(""); + if (!noTarget) { + NiAVObject * targetNode = root->GetObjectByName(&target.data); + if (targetNode) { + NiAutoRefCounter rc(targetNode); + NiNode * parentNode = targetNode->GetAsNiNode(); + if (parentNode) { + nodeMovement.insert_or_assign(transformable, parentNode); + } + } + } + } + } + } + } + + lastNode = root; + + for (auto & nodePair : nodeMovement) + { + NiAutoRefCounter rc(nodePair.second); + NIOVTaskMoveNode * newTask = new NIOVTaskMoveNode(nodePair.second, nodePair.first); + if (g_task && !immediate) { + g_task->AddTask(newTask); + } + else { + newTask->Run(); + newTask->Dispose(); + } + } + + NIOVTaskUpdateWorldData * newTask = new NIOVTaskUpdateWorldData(root); + if (g_task && !immediate) { + g_task->AddTask(newTask); + } + else { + newTask->Run(); + newTask->Dispose(); + } + } + } +} + +void NiTransformInterface::GetOverrideTransform(OverrideSet * set, UInt16 key, NiTransform * result) +{ + OverrideVariant value; + OverrideSet::iterator it; + switch (key) { + case OverrideVariant::kParam_NodeTransformPosition: + { + value.key = OverrideVariant::kParam_NodeTransformPosition; + value.index = 0; + it = set->find(value); + if (it != set->end()) { + result->pos.x = it->data.f; + } + value.index = 1; + it = set->find(value); + if (it != set->end()) { + result->pos.y = it->data.f; + } + value.index = 2; + it = set->find(value); + if (it != set->end()) { + result->pos.z = it->data.f; + } + break; + } + case OverrideVariant::kParam_NodeTransformScale: + { + value.key = OverrideVariant::kParam_NodeTransformScale; + value.index = 0; + it = set->find(value); + if (it != set->end()) { + result->scale = it->data.f; + } + } + break; + case OverrideVariant::kParam_NodeTransformRotation: + { + value.key = OverrideVariant::kParam_NodeTransformRotation; + value.index = 0; + it = set->find(value); + if (it != set->end()) { + result->rot.data[0][0] = it->data.f; + } + value.index = 1; + it = set->find(value); + if (it != set->end()) { + result->rot.data[0][1] = it->data.f; + } + value.index = 2; + it = set->find(value); + if (it != set->end()) { + result->rot.data[0][2] = it->data.f; + } + value.index = 3; + it = set->find(value); + if (it != set->end()) { + result->rot.data[1][0] = it->data.f; + } + value.index = 4; + it = set->find(value); + if (it != set->end()) { + result->rot.data[1][1] = it->data.f; + } + value.index = 5; + it = set->find(value); + if (it != set->end()) { + result->rot.data[1][2] = it->data.f; + } + value.index = 6; + it = set->find(value); + if (it != set->end()) { + result->rot.data[2][0] = it->data.f; + } + value.index = 7; + it = set->find(value); + if (it != set->end()) { + result->rot.data[2][1] = it->data.f; + } + value.index = 8; + it = set->find(value); + if (it != set->end()) { + result->rot.data[2][2] = it->data.f; + } + } + break; + } +} + + +NiTransform * NodeTransformCache::GetBaseTransform(BSFixedString rootModel, BSFixedString nodeName, bool relative) +{ + SimpleLocker lock(&m_lock); + + auto & it = m_data.find(rootModel); + if (it != m_data.end()) { + auto & nodeIt = it->second.find(nodeName); + if (nodeIt != it->second.end()) { + return &nodeIt->second; + } + else + return NULL; + } + + char pathBuffer[MAX_PATH]; + BSFixedString newPath = rootModel; + if (relative) { + memset(pathBuffer, 0, MAX_PATH); + sprintf_s(pathBuffer, MAX_PATH, "meshes\\%s", rootModel.data); + newPath = pathBuffer; + } + + // No skeleton path found, why is this? + BSResourceNiBinaryStream binaryStream(newPath.data); + if (!binaryStream.IsValid()) { + _ERROR("%s - Failed to acquire skeleton at \"%s\".", __FUNCTION__, newPath.data); + return NULL; + } + + NodeMap transformMap; + NiTransform * foundTransform = NULL; + + UInt8 niStreamMemory[sizeof(NiStream)]; + memset(niStreamMemory, 0, sizeof(NiStream)); + NiStream * niStream = (NiStream *)niStreamMemory; + CALL_MEMBER_FN(niStream, ctor)(); + + niStream->LoadStream(&binaryStream); + if (niStream->m_rootObjects.m_data) + { + for (UInt32 i = 0; i < niStream->m_rootObjects.m_emptyRunStart; i++) { + NiObject * object = niStream->m_rootObjects.m_data[i]; + if (object) { + NiAVObject * node = ni_cast(object, NiAVObject); + if (node) { + VisitObjects(node, [&](NiAVObject* child) + { + if (child->m_name == NULL) + return false; + + BSFixedString localName(child->m_name); + if (strlen(localName.data) == 0) + return false; + + transformMap.insert_or_assign(localName, child->m_localTransform); + return false; + }); + } + } + } + } + + CALL_MEMBER_FN(niStream, dtor)(); + auto modelIt = m_data.insert_or_assign(rootModel, transformMap); + if (modelIt.second) { + auto & nodeIt = modelIt.first->second.find(nodeName); + if (nodeIt != modelIt.first->second.end()) { + return &nodeIt->second; + } + } + + return NULL; +} + +BSFixedString NiTransformInterface::GetRootModelPath(TESObjectREFR * refr, bool firstPerson, bool isFemale) +{ + TESModel * model = NULL; + Character * character = DYNAMIC_CAST(refr, TESObjectREFR, Character); + if (character) { + if (firstPerson) { + Setting * setting = (*g_gameSettingCollection)->Get("sFirstPersonSkeleton"); + if (setting && setting->GetType() == Setting::kType_String) + return BSFixedString(setting->data.s); + } + + TESRace * race = character->race; + if (!race) { + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if (actorBase) + race = actorBase->race.race; + } + + if (race) + model = &race->models[isFemale ? 1 : 0]; + } + else + model = DYNAMIC_CAST(refr->baseForm, TESForm, TESModel); + + if (model) + return BSFixedString(model->GetModelName()); + + return BSFixedString(""); +} \ No newline at end of file diff --git a/skee/NiTransformInterface.h b/skee/NiTransformInterface.h new file mode 100644 index 0000000..a234f9f --- /dev/null +++ b/skee/NiTransformInterface.h @@ -0,0 +1,86 @@ +#pragma once + +#include "IPluginInterface.h" + +#include +#include "skse64/GameTypes.h" + +#include "OverrideInterface.h" +#include "OverrideVariant.h" + +class TESObjectREFR; +struct SKSESerializationInterface; +class NiNode; +class NiAVObject; +class NiTransform; + +class NodeTransformKeys : public std::unordered_map> +{ +public: + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); +}; + +class NodeTransformRegistrationMapHolder : public SafeDataHolder,2>>> +{ + friend class NiTransformInterface; +public: + typedef std::unordered_map,2>> RegMap; + + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion, UInt64 * outHandle); +}; + +// Node names are hashed here due to some case where the node "NPC" gets overwritten for some unknown reason +class NodeTransformCache : public SafeDataHolder>> +{ + friend class NiTransformInterface; +public: + typedef std::unordered_map NodeMap; + typedef std::unordered_map RegMap; + + NiTransform * GetBaseTransform(BSFixedString rootModel, BSFixedString nodeName, bool relative); +}; + +class NiTransformInterface : public IPluginInterface +{ +public: + enum + { + kCurrentPluginVersion = 2, + kSerializationVersion1 = 1, + kSerializationVersion2 = 2, + kSerializationVersion = kSerializationVersion2 + }; + virtual UInt32 GetVersion(); + + virtual void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + virtual bool Load(SKSESerializationInterface* intfc, UInt32 kVersion); + virtual void Revert(); + + virtual bool AddNodeTransform(TESObjectREFR * ref, bool firstPerson, bool isFemale, BSFixedString node, BSFixedString name, OverrideVariant & value); + virtual bool RemoveNodeTransformComponent(TESObjectREFR * ref, bool firstPerson, bool isFemale, BSFixedString node, BSFixedString name, UInt16 key, UInt16 index); + virtual bool RemoveNodeTransform(TESObjectREFR * refr, bool firstPerson, bool isFemale, BSFixedString node, BSFixedString name); + virtual void RemoveAllReferenceTransforms(TESObjectREFR * refr); + + virtual OverrideVariant GetOverrideNodeValue(TESObjectREFR * refr, bool firstPerson, bool isFemale, BSFixedString node, BSFixedString name, UInt16 key, SInt8 index); + virtual bool GetOverrideNodeTransform(TESObjectREFR * refr, bool firstPerson, bool isFemale, BSFixedString node, BSFixedString name, UInt16 key, NiTransform * result); + + virtual void GetOverrideTransform(OverrideSet * set, UInt16 key, NiTransform * result); + virtual BSFixedString GetRootModelPath(TESObjectREFR * refr, bool firstPerson, bool isFemale); + + virtual void UpdateNodeAllTransforms(TESObjectREFR * ref); + + virtual void VisitNodes(TESObjectREFR * refr, bool firstPerson, bool isFemale, std::function*)> functor); + virtual bool VisitNodeTransforms(TESObjectREFR * refr, bool firstPerson, bool isFemale, BSFixedString node, std::function*)> each_key, std::function finalize); + virtual void UpdateNodeTransforms(TESObjectREFR * ref, bool firstPerson, bool isFemale, BSFixedString node); + + virtual void VisitStrings(std::function functor); + + void RemoveInvalidTransforms(UInt64 handle); + void RemoveNamedTransforms(UInt64 handle, BSFixedString name); + void SetHandleNodeTransforms(UInt64 handle, bool immediate = false, bool reset = false); + + NodeTransformRegistrationMapHolder transformData; + NodeTransformCache transformCache; +}; \ No newline at end of file diff --git a/skee/NifUtils.cpp b/skee/NifUtils.cpp new file mode 100644 index 0000000..28d26f4 --- /dev/null +++ b/skee/NifUtils.cpp @@ -0,0 +1,510 @@ +#include "NifUtils.h" + +#include + +#include "common/IFileStream.h" + +#include "skse64/GameRTTI.h" +#include "skse64/GameReferences.h" +#include "skse64/GameObjects.h" +#include "skse64/GameData.h" +#include "skse64/GameStreams.h" + +#include "skse64/NiRTTI.h" +#include "skse64/NiObjects.h" +#include "skse64/NiNodes.h" +#include "skse64/NiSerialization.h" +#include "skse64/NiGeometry.h" +#include "skse64/NiRenderer.h" +#include "skse64/NiTextures.h" +#include "skse64/NiExtraData.h" +#include "skse64/NiAllocator.h" + +#ifdef FIXME +#include +#pragma comment(lib, "d3dx9.lib") + +typedef NiSourceTexture * (* _NiRenderedTextureToNiSourceTexture)(NiRenderedTexture * texture, bool bRGBA); +const _NiRenderedTextureToNiSourceTexture NiRenderedTextureToNiSourceTexture = (_NiRenderedTextureToNiSourceTexture)0x00C8CD60; +UInt8 * g_bAlwaysCreateRendererData = (UInt8 *)0x012C5D48; // This is set as 1 in 0x00C6DBF0 (SetupRenderer) + +void SaveSourceDDS(NiSourceTexture * pkSrcTexture, const char * pcFileName) +{ + if(!pkSrcTexture) + return; + + LPDIRECT3DTEXTURE9 pkD3DTexture = NULL; + D3DLOCKED_RECT pkLockedTexture; + + UInt32 uiWidth = pkSrcTexture->GetWidth(); + UInt32 uiHeight = pkSrcTexture->GetHeight(); + + LPDIRECT3DDEVICE9 pkD3DDevice9 = (LPDIRECT3DDEVICE9)NiDX9Renderer::GetSingleton()->m_pkD3DDevice9; + if (D3DXCreateTexture(pkD3DDevice9, uiWidth, uiHeight, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &pkD3DTexture) != -1) + { + if(!pkD3DTexture) { + // Failed to create texture + return; + } + + if (pkSrcTexture) + { + NiPixelData * pkPixelData = pkSrcTexture->pixelData; + if(pkPixelData) + { + if (pkD3DTexture->LockRect(0, &pkLockedTexture, 0, 0) != -1) + { + UInt8 * pucSrc = pkPixelData->GetPixels(); + UInt8 * pucDest = (UInt8 *)pkLockedTexture.pBits; + + for (UInt32 y = 0; y < uiHeight; y++) + { + for (UInt32 x = 0; x < uiWidth; x++) + { + pucDest[0] = pucSrc[0]; // A + pucDest[1] = pucSrc[1]; // R + pucDest[2] = pucSrc[2]; // G + pucDest[3] = pucSrc[3]; // B + + pucDest+=4; + pucSrc+=4; + } + } + } + + pkD3DTexture->UnlockRect(0); + D3DXSaveTextureToFile(pcFileName, D3DXIFF_DDS, pkD3DTexture, 0); + } + } + + pkD3DTexture->Release(); + } +} + +void SaveRenderedDDS(NiRenderedTexture * pkTexture, const char * pcFileName) +{ + UInt8 oldVal = *g_bAlwaysCreateRendererData; + *g_bAlwaysCreateRendererData = 0; // Need to set this to 0 because NiSourceTexture is inited with flag DestroyAppData = 0x04, and CreateRenderData checks this + NiSourceTexture * pkSrcTexture = NiRenderedTextureToNiSourceTexture(pkTexture, 1); + *g_bAlwaysCreateRendererData = oldVal; + + SaveSourceDDS(pkSrcTexture, pcFileName); +} +#endif + +BSGeometry * GetHeadGeometry(Actor * actor, UInt32 partType) +{ + BSFaceGenNiNode * faceNode = actor->GetFaceGenNiNode(); + TESNPC * actorBase = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + + if(faceNode && actorBase) { + BGSHeadPart * facePart = actorBase->GetCurrentHeadPartByType(partType); + if(facePart) { + NiAVObject * headNode = faceNode->GetObjectByName(&facePart->partName.data); + if(headNode) { + BSGeometry * geometry = headNode->GetAsBSGeometry(); + if(geometry) + return geometry; + } + } + } + + return NULL; +} + +BSGeometry * GetGeometryByHeadPart(BSFaceGenNiNode * faceNode, BGSHeadPart * headPart) +{ + for (UInt32 p = 0; p < faceNode->m_children.m_size; p++) + { + NiAVObject * object = faceNode->m_children.m_data[p]; + if (object && BSFixedString(object->m_name) == headPart->partName) { + BSGeometry * geometry = object->GetAsBSGeometry(); + if (geometry) { + return geometry; + } + } + } + + return NULL; +} + +SKSETaskRefreshTintMask::SKSETaskRefreshTintMask(Actor * actor, BSFixedString ddsPath) : m_ddsPath(ddsPath) +{ + m_formId = actor->formID; +} + +void SKSETaskRefreshTintMask::Run() +{ + TESForm * form = LookupFormByID(m_formId); + Actor * actor = DYNAMIC_CAST(form, TESForm, Actor); + if (!actor) + return; + + BSGeometry * geometry = GetHeadGeometry(actor, BGSHeadPart::kTypeFace); + if (geometry) { + BSShaderProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if (shaderProperty) { + if (ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) { + BSLightingShaderProperty * lightingShader = static_cast(shaderProperty); + BSLightingShaderMaterial * material = static_cast(shaderProperty->material); + if (material) { + material->textureSet->SetTexturePath(6, m_ddsPath.data); + material->ReleaseTextures(); + CALL_MEMBER_FN(lightingShader, InvalidateTextures)(0); + CALL_MEMBER_FN(lightingShader, InitializeShader)(geometry); + } + } + } + } +} + +void ExportTintMaskDDS(Actor * actor, BSFixedString filePath) +{ + BSGeometry * geometry = GetHeadGeometry(actor, BGSHeadPart::kTypeFace); + if(geometry) + { + BSShaderProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if(ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) + { + BSLightingShaderProperty * lightingShader = static_cast(shaderProperty); + BSLightingShaderMaterial * material = static_cast(shaderProperty->material); + if (material) + { + if (material->GetShaderType() == BSShaderMaterial::kShaderType_FaceGen) + { + BSLightingShaderMaterialFacegen * maskedMaterial = (BSLightingShaderMaterialFacegen *)material; +#ifdef FIXME + SaveRenderedDDS(niptr_cast(maskedMaterial->renderedTexture), filePath.data); +#endif + } + } + } + } +} + +BGSTextureSet * GetTextureSetForPart(TESNPC * npc, BGSHeadPart * headPart) +{ + BGSTextureSet * textureSet = NULL; + if (headPart->type == BGSHeadPart::kTypeFace) { + if (npc->headData) + textureSet = npc->headData->headTexture; + } + if (!textureSet) + textureSet = headPart->textureSet; + + return textureSet; +} + +std::pair GetTextureSetForPartByName(TESNPC * npc, BSFixedString partName) +{ + UInt32 numHeadParts = 0; + BGSHeadPart ** headParts = NULL; + if (CALL_MEMBER_FN(npc, HasOverlays)()) { + numHeadParts = GetNumActorBaseOverlays(npc); + headParts = GetActorBaseOverlays(npc); + } + else { + numHeadParts = npc->numHeadParts; + headParts = npc->headparts; + } + for (UInt32 i = 0; i < numHeadParts; i++) // Acquire all parts + { + BGSHeadPart * headPart = headParts[i]; + if (headPart && headPart->partName == partName) { + BGSTextureSet * textureSet = nullptr; + textureSet = GetTextureSetForPart(npc, headPart); + return std::make_pair(textureSet, headPart); + } + } + + return std::make_pair(NULL, NULL); +} + +SKSETaskExportHead::SKSETaskExportHead(Actor * actor, BSFixedString nifPath, BSFixedString ddsPath) : m_nifPath(nifPath), m_ddsPath(ddsPath) +{ + m_formId = actor->formID; +} + +void SKSETaskExportHead::Run() +{ + if (!m_formId) + return; + + TESForm * form = LookupFormByID(m_formId); + Actor * actor = DYNAMIC_CAST(form, TESForm, Actor); + if (!actor) + return; + + BSFaceGenNiNode * faceNode = actor->GetFaceGenNiNode(); + TESNPC * actorBase = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (!actorBase || !faceNode) + return; + + BSFaceGenAnimationData * animationData = actor->GetFaceGenAnimationData(); + if (animationData) { + FaceGen::GetSingleton()->isReset = 0; + for (UInt32 t = BSFaceGenAnimationData::kKeyframeType_Expression; t <= BSFaceGenAnimationData::kKeyframeType_Phoneme; t++) + { + BSFaceGenKeyframeMultiple * keyframe = &animationData->keyFrames[t]; + for (UInt32 i = 0; i < keyframe->count; i++) + keyframe->values[i] = 0.0; + keyframe->isUpdated = 0; + } + UpdateModelFace(faceNode); + } + + IFileStream::MakeAllDirs(m_nifPath.data); + + BSFadeNode * rootNode = BSFadeNode::Create(); + rootNode->IncRef(); + NiNode * skinnedNode = NiNode::Create(0); + skinnedNode->m_name = BSFixedString("BSFaceGenNiNodeSkinned").data; + + std::map boneMap; + + for (UInt32 i = 0; i < faceNode->m_children.m_size; i++) + { + NiAVObject * object = faceNode->m_children.m_data[i]; + if (!object) + continue; + + if (BSGeometry * geometry = object->GetAsBSGeometry()) { +#ifdef FIXME_GEOMETRY + NiGeometryData * geometryData = niptr_cast(geometry->m_spModelData); + NiGeometryData * newGeometryData = NULL; + if (geometryData) + CALL_MEMBER_FN(geometryData, DeepCopy)((NiObject **)&newGeometryData); +#endif + + NiProperty * trishapeEffect = niptr_cast(geometry->m_spEffectState); + NiProperty * newTrishapeEffect = NULL; + if (trishapeEffect) + CALL_MEMBER_FN(trishapeEffect, DeepCopy)((NiObject **)&newTrishapeEffect); + + NiProperty * trishapeProperty = niptr_cast(geometry->m_spPropertyState); + NiProperty * newTrishapeProperty = NULL; + if (trishapeProperty) + CALL_MEMBER_FN(trishapeProperty, DeepCopy)((NiObject **)&newTrishapeProperty); + + NiSkinInstance * skinInstance = niptr_cast(geometry->m_spSkinInstance); + NiSkinInstance * newSkinInstance = NULL; + if (skinInstance) { + newSkinInstance = skinInstance->Clone(false); + newSkinInstance->m_pkRootParent = skinnedNode; + + UInt32 numBones = 0; + NiSkinData * skinData = niptr_cast(skinInstance->m_spSkinData); + NiSkinData * newSkinData = NULL; + if (skinData) { + numBones = skinData->m_uiBones; + CALL_MEMBER_FN(skinData, DeepCopy)((NiObject **)&newSkinData); + } + + NiSkinPartition * skinPartition = niptr_cast(skinInstance->m_spSkinPartition); + NiSkinPartition * newSkinPartition = NULL; + if (skinPartition) + CALL_MEMBER_FN(skinPartition, DeepCopy)((NiObject **)&newSkinPartition); + + newSkinInstance->m_spSkinData = newSkinData; + newSkinData->DecRef(); + + newSkinInstance->m_spSkinPartition = newSkinPartition; + newSkinPartition->DecRef(); + + // Remap the bones to new NiNode instances + if (numBones > 0) + { + newSkinInstance->m_ppkBones = (NiAVObject**)NiAllocate(numBones * sizeof(NiAVObject*)); + for (UInt32 i = 0; i < numBones; i++) + { + NiAVObject * bone = skinInstance->m_ppkBones[i]; + if (bone) + { + auto it = boneMap.find(bone); + if (it == boneMap.end()) { + NiNode * newBone = NiNode::Create(); + newBone->m_name = bone->m_name; + newBone->m_flags = bone->m_flags; + boneMap.insert(std::make_pair(bone, newBone)); + newSkinInstance->m_ppkBones[i] = newBone; + } + else + newSkinInstance->m_ppkBones[i] = it->second; + } + else + { + newSkinInstance->m_ppkBones[i] = nullptr; + } + } + } + } + + BSGeometry * newGeometry = NULL; + +#ifdef FIXME_GEOMETRY + if (NiTriShape * trishape = geometry->GetAsNiTriShape()) { + NiTriShape * newTrishape = NiTriShape::Create(static_cast(newGeometryData)); + newGeometryData->DecRef(); + newTrishape->m_localTransform = geometry->m_localTransform; + newTrishape->m_name = geometry->m_name; +#ifdef FIXME + memcpy(&newTrishape->unk88, &geometry->unk88, 0x1F); +#endif + newTrishape->m_spEffectState = newTrishapeEffect; + newTrishape->m_spPropertyState = newTrishapeProperty; + newTrishape->m_spSkinInstance = newSkinInstance; + newGeometry = newTrishape; + } + else if (NiTriStrips * tristrips = geometry->GetAsNiTriStrips()) { + NiTriStrips * newTristrips = NiTriStrips::Create(static_cast(newGeometryData)); + newGeometryData->DecRef(); + newTristrips->m_localTransform = geometry->m_localTransform; + newTristrips->m_name = geometry->m_name; +#ifdef FIXME + memcpy(&newTristrips->unk88, &geometry->unk88, 0x1F); +#endif + newTristrips->m_spEffectState = newTrishapeEffect; + newTristrips->m_spPropertyState = newTrishapeProperty; + newTristrips->m_spSkinInstance = newSkinInstance; + newGeometry = newTristrips; + } +#endif + + if (newGeometry) + { + auto textureData = GetTextureSetForPartByName(actorBase, newGeometry->m_name); + if (textureData.first && textureData.second) { + BSShaderProperty * shaderProperty = niptr_cast(newGeometry->m_spEffectState); + if (shaderProperty) { + if (ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) { + BSLightingShaderProperty * lightingShader = static_cast(shaderProperty); + BSLightingShaderMaterial * material = static_cast(shaderProperty->material); + if (material && material->textureSet) { + for (UInt32 i = 0; i < BGSTextureSet::kNumTextures; i++) + material->textureSet->SetTexturePath(i, textureData.first->textureSet.GetTexturePath(i)); + + if (textureData.second->type == BGSHeadPart::kTypeFace) + material->textureSet->SetTexturePath(6, m_ddsPath.data); + } + } + } + + // Save the previous tint mask + BSShaderProperty * originalShaderProperty = niptr_cast(geometry->m_spEffectState); + if (originalShaderProperty) { + if (ni_is_type(originalShaderProperty->GetRTTI(), BSLightingShaderProperty)) { + BSLightingShaderProperty * lightingShader = static_cast(originalShaderProperty); + BSLightingShaderMaterial * material = static_cast(originalShaderProperty->material); + if (material) { + if (material->GetShaderType() == BSShaderMaterial::kShaderType_FaceGen) { + BSLightingShaderMaterialFacegen * maskedMaterial = static_cast(material); +#ifdef FIXME + SaveRenderedDDS(niptr_cast(maskedMaterial->renderedTexture), m_ddsPath.data); +#endif + } + } + } + } + } + + skinnedNode->AttachChild(newGeometry, true); + } + } + } + + for (auto & bones : boneMap) + rootNode->AttachChild(bones.second, true); + + rootNode->AttachChild(skinnedNode, true); + + UInt8 niStreamMemory[0x5B4]; + memset(niStreamMemory, 0, 0x5B4); + NiStream * niStream = (NiStream *)niStreamMemory; + CALL_MEMBER_FN(niStream, ctor)(); + CALL_MEMBER_FN(niStream, AddObject)(rootNode); + niStream->SavePath(m_nifPath.data); + CALL_MEMBER_FN(niStream, dtor)(); + + rootNode->DecRef(); + + if (animationData) { + animationData->overrideFlag = 0; + CALL_MEMBER_FN(animationData, Reset)(1.0, 1, 1, 0, 0); + FaceGen::GetSingleton()->isReset = 1; + UpdateModelFace(faceNode); + } +} + +bool VisitObjects(NiAVObject * parent, std::function functor) +{ + NiNode * node = parent->GetAsNiNode(); + if (node) { + if (functor(parent)) + return true; + + for (UInt32 i = 0; i < node->m_children.m_emptyRunStart; i++) { + NiAVObject * object = node->m_children.m_data[i]; + if (object) { + if (VisitObjects(object, functor)) + return true; + } + } + } + else if (functor(parent)) + return true; + + return false; +} + +NiTransform GetGeometryTransform(BSGeometry * geometry) +{ + NiTransform transform = geometry->m_localTransform; + NiSkinInstance * dstSkin = niptr_cast(geometry->m_spSkinInstance); + if (dstSkin) { + NiSkinData * skinData = dstSkin->m_spSkinData; + if (skinData) { + transform = transform * skinData->m_kRootParentToSkin; + + for (UInt32 i = 0; i < skinData->m_uiBones; i++) { + NiAVObject * bone = dstSkin->m_ppkBones[i]; + if (bone->m_name == BSFixedString("NPC Head [Head]").data) { + transform = transform * skinData->m_pkBoneData[i].m_kSkinToBone; + break; + } + } + } + } + + return transform; +} + +UInt16 GetStripLengthSum(NiTriStripsData * strips) +{ + return strips->m_usTriangles + 2 * strips->m_usStrips; +} + +void GetTriangleIndices(NiTriStripsData * strips, UInt16 i, UInt16 v0, UInt16 v1, UInt16 v2) +{ + UInt16 usTriangles; + UInt16 usStrip = 0; + + UInt16* pusStripLists = strips->m_pusStripLists; + while (i >= (usTriangles = strips->m_pusStripLengths[usStrip] - 2)) + { + i = (UInt16)(i - usTriangles); + pusStripLists += strips->m_pusStripLengths[usStrip++]; + } + + if (i & 1) + { + v0 = pusStripLists[i + 1]; + v1 = pusStripLists[i]; + } + else + { + v0 = pusStripLists[i]; + v1 = pusStripLists[i + 1]; + } + + v2 = pusStripLists[i + 2]; +} \ No newline at end of file diff --git a/skee/NifUtils.h b/skee/NifUtils.h new file mode 100644 index 0000000..5c4762b --- /dev/null +++ b/skee/NifUtils.h @@ -0,0 +1,58 @@ +#pragma once + +#include "skse64/GameThreads.h" +#include "skse64/GameTypes.h" + +#include "skse64/NiTypes.h" + +#include + +class NiSourceTexture; +class NiRenderedTexture; +class NiTriBasedGeom; +class TESNPC; +class BGSHeadPart; +class BSFaceGenNiNode; +class BSGeometry; +class NiTriStripsData; + +class SKSETaskExportHead : public TaskDelegate +{ + virtual void Run(); + virtual void Dispose() { delete this; } + +public: + SKSETaskExportHead::SKSETaskExportHead(Actor * actor, BSFixedString nifPath, BSFixedString ddsPath); + + UInt32 m_formId; + BSFixedString m_nifPath; + BSFixedString m_ddsPath; +}; + +class SKSETaskRefreshTintMask : public TaskDelegate +{ + virtual void Run(); + virtual void Dispose() { delete this; }; + +public: + SKSETaskRefreshTintMask::SKSETaskRefreshTintMask(Actor * actor, BSFixedString ddsPath); + + UInt32 m_formId; + BSFixedString m_ddsPath; +}; + +BGSTextureSet * GetTextureSetForPart(TESNPC * npc, BGSHeadPart * headPart); +std::pair GetTextureSetForPartByName(TESNPC * npc, BSFixedString partName); +BSGeometry * GetHeadGeometry(Actor * actor, UInt32 partType); +void ExportTintMaskDDS(Actor * actor, BSFixedString filePath); +BSGeometry * GetGeometryByHeadPart(BSFaceGenNiNode * faceNode, BGSHeadPart * headPart); + +void SaveSourceDDS(NiSourceTexture * pkSrcTexture, const char * pcFileName); +void SaveRenderedDDS(NiRenderedTexture * pkTexture, const char * pcFileName); + +bool VisitObjects(NiAVObject * parent, std::function functor); + +NiTransform GetGeometryTransform(BSGeometry * geometry); + +UInt16 GetStripLengthSum(NiTriStripsData * strips); +void GetTriangleIndices(NiTriStripsData * strips, UInt16 i, UInt16 v0, UInt16 v1, UInt16 v2); diff --git a/skee/OverlayInterface.cpp b/skee/OverlayInterface.cpp new file mode 100644 index 0000000..3380710 --- /dev/null +++ b/skee/OverlayInterface.cpp @@ -0,0 +1,1046 @@ +#include "skse64/PluginAPI.h" +#include "skse64_common/skse_version.h" + +#include "skse64/GameAPI.h" +#include "skse64/GameObjects.h" +#include "skse64/GameRTTI.h" +#include "skse64/GameData.h" +#include "skse64/GameReferences.h" +#include "skse64/GameFormComponents.h" +#include "skse64/GameStreams.h" + +#include "skse64/NiMaterial.h" +#include "skse64/NiGeometry.h" +#include "skse64/NiRTTI.h" +#include "skse64/NiNodes.h" +#include "skse64/NiExtraData.h" +#include "skse64/NiSerialization.h" + +#include "OverlayInterface.h" +#include "OverrideInterface.h" +#include "BodyMorphInterface.h" +#include "OverrideVariant.h" +#include "ShaderUtilities.h" + +extern OverlayInterface g_overlayInterface; +extern OverrideInterface g_overrideInterface; +extern BodyMorphInterface g_bodyMorphInterface; + +extern SKSETaskInterface * g_task; + +extern UInt32 g_numBodyOverlays; +extern UInt32 g_numHandOverlays; +extern UInt32 g_numFeetOverlays; +extern UInt32 g_numFaceOverlays; + +extern bool g_playerOnly; +extern UInt32 g_numSpellBodyOverlays; +extern UInt32 g_numSpellHandOverlays; +extern UInt32 g_numSpellFeetOverlays; +extern UInt32 g_numSpellFaceOverlays; + +extern bool g_alphaOverride; +extern UInt16 g_alphaFlags; +extern UInt16 g_alphaThreshold; +extern bool g_immediateArmor; + +UInt32 OverlayInterface::GetVersion() +{ + return kCurrentPluginVersion; +} + +void OverlayInterface::UninstallOverlay(const char * nodeName, TESObjectREFR * refr, NiNode * parent) +{ + if(!parent) + return; + + // Remove all overlay instances + BSFixedString overlayName(nodeName); + NiAVObject * foundObject = parent->GetObjectByName(&overlayName.data); + while(foundObject) + { + BSGeometry * foundGeometry = foundObject->GetAsBSGeometry(); + if(foundGeometry) + { +#ifdef FIXME_GEOMETRY + foundGeometry->SetModelData(NULL); + foundGeometry->SetSkinInstance(NULL); +#endif + } + + parent->RemoveChild(foundObject); + foundObject = parent->GetObjectByName(&overlayName.data); + } +} + +void OverlayInterface::InstallOverlay(const char * nodeName, const char * path, TESObjectREFR * refr, BSGeometry * source, NiNode * destination, BGSTextureSet * textureSet) +{ + NiNode * rootNode = NULL; + BSGeometry * newShape = NULL; + + UInt8 niStreamMemory[0x5B4]; + memset(niStreamMemory, 0, 0x5B4); + NiStream * niStream = (NiStream *)niStreamMemory; + CALL_MEMBER_FN(niStream, ctor)(); + + BSFixedString overlayName(nodeName); + NiAVObject * foundGeometry = destination->GetObjectByName(&overlayName.data); + if (foundGeometry) + newShape = foundGeometry->GetAsBSGeometry(); + + bool attachNew = false; + if(!newShape) + { + BSResourceNiBinaryStream binaryStream(path); + if(!binaryStream.IsValid()) { + goto destroy_stream; + } + + niStream->LoadStream(&binaryStream); + + if(niStream->m_rootObjects.m_data) + { + if(niStream->m_rootObjects.m_data[0]) // Get the root node + rootNode = niStream->m_rootObjects.m_data[0]->GetAsNiNode(); + if(rootNode) { + if(rootNode->m_children.m_data) { + if(rootNode->m_children.m_data[0]) { // Get first child of root + newShape = rootNode->m_children.m_data[0]->GetAsBSGeometry(); + attachNew = true; + } + } + } + if(newShape) + newShape->m_name = overlayName.data; + } + } + + if(newShape) + { + newShape->m_localTransform = source->m_localTransform; + +#ifdef FIXME_GEOMETRY + newShape->SetModelData(source->m_spModelData); + newShape->SetSkinInstance(source->m_spSkinInstance); +#endif + + NiProperty * newProperty = niptr_cast(newShape->m_spEffectState); + NiProperty * sourceProperty = niptr_cast(source->m_spEffectState); + + BSLightingShaderProperty * shaderProperty = ni_cast(newProperty, BSLightingShaderProperty); + BSLightingShaderProperty * sourceShader = ni_cast(sourceProperty, BSLightingShaderProperty); + if(sourceShader && shaderProperty) { + if((sourceShader->shaderFlags2 & BSLightingShaderProperty::kSLSF2_Vertex_Colors) == BSLightingShaderProperty::kSLSF2_Vertex_Colors) + shaderProperty->shaderFlags2 |= BSLightingShaderProperty::kSLSF2_Vertex_Colors; + else + shaderProperty->shaderFlags2 &= ~BSLightingShaderProperty::kSLSF2_Vertex_Colors; + + if(ni_is_type(sourceShader->GetRTTI(), BSLightingShaderProperty) && ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) + { + BSLightingShaderMaterial * sourceMaterial = (BSLightingShaderMaterial *)sourceShader->material; + BSLightingShaderMaterial * targetMaterial = (BSLightingShaderMaterial *)shaderProperty->material; + + if(sourceMaterial && targetMaterial) + { + // Copy the remaining textures + if(!textureSet) + { + for(UInt32 i = 1; i < BSTextureSet::kNumTextures; i++) + { + const char * texturePath = sourceMaterial->textureSet->GetTexturePath(i); + targetMaterial->textureSet->SetTexturePath(i, texturePath); + } + } + else + { + for(UInt32 i = 1; i < BSTextureSet::kNumTextures; i++) + targetMaterial->textureSet->SetTexturePath(i, textureSet->textureSet.GetTexturePath(i)); + } + + targetMaterial->ReleaseTextures(); + CALL_MEMBER_FN(shaderProperty, InvalidateTextures)(0); + CALL_MEMBER_FN(shaderProperty, InitializeShader)(newShape); + } + } + } + + if(g_alphaOverride) { + NiAlphaProperty * alphaProperty = niptr_cast(newShape->m_spPropertyState); + if(alphaProperty) { + alphaProperty->alphaFlags = g_alphaFlags; + alphaProperty->alphaThreshold = g_alphaThreshold; + } + } + + g_overrideInterface.ApplyNodeOverrides(refr, newShape, true); + + if(attachNew) { + NiNode * parent = newShape->m_parent; + destination->AttachChild(newShape, false); + newShape->IncRef(); + if(parent) { + parent->RemoveChild(newShape); + newShape->DecRef(); + } + +#ifdef _DEBUG + _DMESSAGE("%s - Successfully installed overlay %s to actor: %08X", __FUNCTION__, newShape->m_name, refr->formID); +#endif + } + } + +destroy_stream: + CALL_MEMBER_FN(niStream, dtor)(); +} + +void OverlayInterface::ResetOverlay(const char * nodeName, TESObjectREFR * refr, BSGeometry * source, NiNode * destination, BGSTextureSet * textureSet, bool resetDiffuse) +{ + NiNode * rootNode = NULL; + BSGeometry * foundGeometry = NULL; + + BSFixedString overlayName(nodeName); + NiAVObject * foundNode = destination->GetObjectByName(&overlayName.data); + if(foundNode) + foundGeometry = foundNode->GetAsBSGeometry(); + + if(foundGeometry) + { + NiProperty * foundProperty = niptr_cast(foundGeometry->m_spEffectState); + NiProperty * sourceProperty = niptr_cast(source->m_spEffectState); + + BSLightingShaderProperty * shaderProperty = ni_cast(foundProperty, BSLightingShaderProperty); + BSLightingShaderProperty * sourceShader = ni_cast(sourceProperty, BSLightingShaderProperty); + if(sourceShader && shaderProperty) + { + if(ni_is_type(sourceShader->GetRTTI(), BSLightingShaderProperty) && ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) + { + BSLightingShaderMaterial * sourceMaterial = (BSLightingShaderMaterial *)sourceShader->material; + BSLightingShaderMaterial * targetMaterial = (BSLightingShaderMaterial *)shaderProperty->material; + + if(sourceMaterial && targetMaterial) + { + /*NiColor color; + color.r = 0; + color.g = 0; + color.b = 0; + OverrideVariant defaultValue; + defaultValue.SetColor(OverrideVariant::kParam_ShaderEmissiveColor, -1, color); + SetShaderProperty(foundGeometry, &defaultValue, true); + defaultValue.SetFloat(OverrideVariant::kParam_ShaderEmissiveMultiple, -1, 1.0); + SetShaderProperty(foundGeometry, &defaultValue, true); + defaultValue.SetFloat(OverrideVariant::kParam_ShaderAlpha, -1, 1.0); + SetShaderProperty(foundGeometry, &defaultValue, true); + defaultValue.SetFloat(OverrideVariant::kParam_ShaderGlossiness, -1, 30.0); + SetShaderProperty(foundGeometry, &defaultValue, true); + defaultValue.SetFloat(OverrideVariant::kParam_ShaderSpecularStrength, -1, 3.0); + SetShaderProperty(foundGeometry, &defaultValue, true); + defaultValue.SetFloat(OverrideVariant::kParam_ShaderLightingEffect1, -1, 0.4); + SetShaderProperty(foundGeometry, &defaultValue, true); + defaultValue.SetFloat(OverrideVariant::kParam_ShaderLightingEffect2, -1, 2.0); + SetShaderProperty(foundGeometry, &defaultValue, true);*/ + + // Copy the remaining textures + if(!textureSet) + { + if (resetDiffuse) + targetMaterial->textureSet->SetTexturePath(0, BSFixedString(GetDefaultTexture().c_str())); + + for(UInt32 i = 1; i < BSTextureSet::kNumTextures; i++) + { + const char * texturePath = sourceMaterial->textureSet->GetTexturePath(i); + targetMaterial->textureSet->SetTexturePath(i, texturePath); + } + } + else + { + if (resetDiffuse) + targetMaterial->textureSet->SetTexturePath(0, BSFixedString(GetDefaultTexture().c_str())); + + for(UInt32 i = 1; i < BSTextureSet::kNumTextures; i++) + targetMaterial->textureSet->SetTexturePath(i, textureSet->textureSet.GetTexturePath(i)); + } + targetMaterial->ReleaseTextures(); + CALL_MEMBER_FN(shaderProperty, InvalidateTextures)(0); + CALL_MEMBER_FN(shaderProperty, InitializeShader)(foundGeometry); + } + } + } + } +} + +void OverlayInterface::RelinkOverlay(const char * nodeName, TESObjectREFR * refr, BSGeometry * source, NiNode * skeleton) +{ + NiNode * rootNode = NULL; + BSGeometry * foundGeometry = NULL; + + BSFixedString overlayName(nodeName); + NiAVObject * foundNode = skeleton->GetObjectByName(&overlayName.data); + if (foundNode) + foundGeometry = foundNode->GetAsBSGeometry(); + + if (source && foundGeometry) + { +#ifdef FIXME_GEOMETRY + NiGeometryData * sourceGeometryData = niptr_cast(source->m_spModelData); + NiSkinInstance * sourceSkin = niptr_cast(source->m_spSkinInstance); + + if (sourceGeometryData && sourceSkin) { + foundGeometry->SetModelData(sourceGeometryData); + foundGeometry->SetSkinInstance(sourceSkin); + } +#endif + } +} + +TESObjectARMA * GetArmorAddonByMask(TESRace * race, TESObjectARMO * armor, UInt32 mask) +{ + TESObjectARMA * currentAddon = NULL; + for(UInt32 i = 0; i < armor->armorAddons.count; i++) + { + armor->armorAddons.GetNthItem(i, currentAddon); + if(currentAddon->isValidRace(race) && (currentAddon->biped.GetSlotMask() & mask) == mask) { + return currentAddon; + } + } + + return NULL; +} + +SKSETaskRevertOverlay::SKSETaskRevertOverlay(TESObjectREFR * refr, BSFixedString nodeName, UInt32 armorMask, UInt32 addonMask, bool resetDiffuse) +{ + m_formId = refr->formID; + m_nodeName = nodeName; + m_armorMask = armorMask; + m_addonMask = addonMask; + m_resetDiffuse = resetDiffuse; +} + +void SKSETaskRevertOverlay::Run() +{ + TESForm * form = LookupFormByID(m_formId); + TESObjectREFR * reference = DYNAMIC_CAST(form, TESForm, TESObjectREFR); + if (reference && g_overlayInterface.HasOverlays(reference)) + { + Actor * actor = DYNAMIC_CAST(reference, TESObjectREFR, Actor); + if(actor) + { + TESForm * form = GetSkinForm(actor, m_armorMask); + if(TESObjectARMO * armor = DYNAMIC_CAST(form, TESForm, TESObjectARMO)) + { + TESObjectARMA * foundAddon = GetArmorAddonByMask(actor->race, armor, m_addonMask); + if(foundAddon) + { + VisitArmorAddon(actor, armor, foundAddon, [&](bool isFP, NiNode * rootNode, NiAVObject * armorNode) + { + BSGeometry * firstSkin = GetFirstShaderType(armorNode, BSShaderMaterial::kShaderType_FaceGenRGBTint); + if (firstSkin) + { + g_overlayInterface.ResetOverlay(m_nodeName.data, actor, firstSkin, rootNode, NULL, m_resetDiffuse); + } + }); + } + } + } + } +} + +void SKSETaskRevertOverlay::Dispose() +{ + delete this; +} + + +SKSETaskRevertFaceOverlay::SKSETaskRevertFaceOverlay(TESObjectREFR * refr, BSFixedString nodeName, UInt32 partType, UInt32 shaderType, bool resetDiffuse) +{ + m_formId = refr->formID; + m_nodeName = nodeName; + m_partType = partType; + m_shaderType = shaderType; + m_resetDiffuse = resetDiffuse; +} + +void SKSETaskRevertFaceOverlay::Run() +{ + TESForm * form = LookupFormByID(m_formId); + TESObjectREFR * reference = DYNAMIC_CAST(form, TESForm, TESObjectREFR); + if (reference && g_overlayInterface.HasOverlays(reference)) + { + Actor * actor = DYNAMIC_CAST(reference, TESObjectREFR, Actor); + if(actor) + { + BSFaceGenNiNode * faceNode = reference->GetFaceGenNiNode(); + TESNPC * actorBase = DYNAMIC_CAST(reference->baseForm, TESForm, TESNPC); + BGSHeadPart * headPart = actorBase->GetCurrentHeadPartByType(m_partType); + BSFixedString rootName("NPC Root [Root]"); + NiNode * skeletonRoot = actor->GetNiRootNode(0); + BGSTextureSet * textureSet = NULL; + if(actorBase->headData) + textureSet = actorBase->headData->headTexture; + + if(skeletonRoot && faceNode && headPart) + { + NiAVObject * root = skeletonRoot->GetObjectByName(&rootName.data); + if(root) + { + NiNode * rootNode = root->GetAsNiNode(); + if(rootNode) + { + NiAVObject * headNode = faceNode->GetObjectByName(&headPart->partName.data); + if(headNode) + { + BSGeometry * firstFace = GetFirstShaderType(headNode, m_shaderType); + if(firstFace) + { + g_overlayInterface.ResetOverlay(m_nodeName.data, actor, firstFace, rootNode, textureSet, m_resetDiffuse); + } + } + } + } + } + } + } +} + +void SKSETaskRevertFaceOverlay::Dispose() +{ + delete this; +} + +SKSETaskInstallOverlay::SKSETaskInstallOverlay(TESObjectREFR * refr, BSFixedString nodeName, BSFixedString overlayPath, UInt32 armorMask, UInt32 addonMask) +{ + m_formId = refr->formID; + m_nodeName = nodeName; + m_overlayPath = overlayPath; + m_armorMask = armorMask; + m_addonMask = addonMask; +} + +void SKSETaskInstallOverlay::Run() +{ + TESForm * form = LookupFormByID(m_formId); + TESObjectREFR * reference = DYNAMIC_CAST(form, TESForm, TESObjectREFR); + if (reference && g_overlayInterface.HasOverlays(reference)) + { + Actor * actor = DYNAMIC_CAST(reference, TESObjectREFR, Actor); + if(actor) + { +#ifdef _DEBUG + _DMESSAGE("%s - Installing Overlay %s from %s to actor: %08X", __FUNCTION__, m_nodeName.data, m_overlayPath.data, actor->formID); +#endif + TESForm * form = GetSkinForm(actor, m_armorMask); + if(TESObjectARMO * armor = DYNAMIC_CAST(form, TESForm, TESObjectARMO)) + { +#ifdef _DEBUG + _DMESSAGE("%s - Installing Overlay to Armor: %08X on Actor: %08X", __FUNCTION__, armor->formID, actor->formID); +#endif + TESObjectARMA * foundAddon = GetArmorAddonByMask(actor->race, armor, m_addonMask); + if(foundAddon) + { + VisitArmorAddon(actor, armor, foundAddon, [&](bool isFirstPerson, NiNode* rootNode, NiAVObject* armorNode) + { + BSGeometry * firstSkin = GetFirstShaderType(armorNode, BSShaderMaterial::kShaderType_FaceGenRGBTint); + if (firstSkin) + { +#ifdef _DEBUG + _DMESSAGE("%s - Installing Overlay %s to %08X on skeleton %08X", __FUNCTION__, m_nodeName.data, actor->formID, rootNode); +#endif + g_overlayInterface.InstallOverlay(m_nodeName.data, m_overlayPath.data, actor, firstSkin, rootNode); + } + }); + } +#ifdef _DEBUG + else { + _DMESSAGE("%s - Failed to locate addon by mask %d for Armor: %08X on Actor: %08X", __FUNCTION__, m_addonMask, armor->formID, actor->formID); + } +#endif + } + } + } +} + +void SKSETaskInstallOverlay::Dispose() +{ + delete this; +} + + + +SKSETaskInstallFaceOverlay::SKSETaskInstallFaceOverlay(TESObjectREFR * refr, BSFixedString nodeName, BSFixedString overlayPath, UInt32 partType, UInt32 shaderType) +{ + m_formId = refr->formID; + m_nodeName = nodeName; + m_overlayPath = overlayPath; + m_partType = partType; + m_shaderType = shaderType; +} + +void SKSETaskInstallFaceOverlay::Run() +{ + TESForm * form = LookupFormByID(m_formId); + TESObjectREFR * reference = DYNAMIC_CAST(form, TESForm, TESObjectREFR); + if (reference && g_overlayInterface.HasOverlays(reference)) + { + Actor * actor = DYNAMIC_CAST(reference, TESObjectREFR, Actor); + if(actor) + { + BSFaceGenNiNode * faceNode = reference->GetFaceGenNiNode(); + TESNPC * actorBase = DYNAMIC_CAST(reference->baseForm, TESForm, TESNPC); + BGSHeadPart * headPart = actorBase->GetCurrentHeadPartByType(m_partType); + BSFixedString rootName("NPC Root [Root]"); + NiNode * skeletonRoot = actor->GetNiRootNode(0); + BGSTextureSet * textureSet = NULL; + if(actorBase->headData) + textureSet = actorBase->headData->headTexture; + +#ifdef _DEBUG + _DMESSAGE("%s - Installing Face Overlay %s from %s to actor: %08X - Face %08X Skeleton %08X HeadPart %08X", __FUNCTION__, m_nodeName.data, m_overlayPath.data, actor->formID, faceNode, skeletonRoot, headPart); +#endif + if(skeletonRoot && faceNode && headPart) + { + NiAVObject * root = skeletonRoot->GetObjectByName(&rootName.data); + if(root) + { + NiNode * rootNode = root->GetAsNiNode(); + if(rootNode) + { + // Already installed, update its visibility + if(NiAVObject * foundOverlay = root->GetObjectByName(&m_nodeName.data)) + { + bool hiddenFlag = (faceNode->m_flags & 0x01) == 0x01; + if(hiddenFlag) + foundOverlay->m_flags |= 0x01; + else + foundOverlay->m_flags &= ~0x01; +#ifdef _DEBUG + _MESSAGE("%s - Toggling Face Overlay %s to %08X on skeleton", __FUNCTION__, m_nodeName.data, actor->formID); +#endif + return; + } + + NiAVObject * headNode = faceNode->GetObjectByName(&headPart->partName.data); + if(headNode) + { + BSGeometry * firstFace = GetFirstShaderType(headNode, m_shaderType); + if(firstFace) + { +#ifdef _DEBUG + _MESSAGE("%s - Installing Face Overlay %s to %08X on skeleton", __FUNCTION__, m_nodeName.data, actor->formID); +#endif + g_overlayInterface.InstallOverlay(m_nodeName.data, m_overlayPath.data, actor, firstFace, rootNode, textureSet); + } + } + } + } + } + } + } +} + +void SKSETaskInstallFaceOverlay::Dispose() +{ + delete this; +} + + +SKSETaskModifyOverlay::SKSETaskModifyOverlay(TESObjectREFR * refr, BSFixedString nodeName) +{ + m_formId = refr->formID; + m_nodeName = nodeName; +} + + +void SKSETaskModifyOverlay::Run() +{ + TESForm * form = LookupFormByID(m_formId); + TESObjectREFR * reference = DYNAMIC_CAST(form, TESForm, TESObjectREFR); + if (reference && g_overlayInterface.HasOverlays(reference)) + { + Actor * actor = DYNAMIC_CAST(reference, TESObjectREFR, Actor); + if(actor) + { + BSFixedString rootName("NPC Root [Root]"); + + NiNode * skeletonRoot[2]; + skeletonRoot[0] = actor->GetNiRootNode(0); + skeletonRoot[1] = actor->GetNiRootNode(1); + + // Skip second skeleton, it's the same as the first + if(skeletonRoot[1] == skeletonRoot[0]) + skeletonRoot[1] = NULL; + + for(UInt32 i = 0; i <= 1; i++) + { + if(skeletonRoot[i]) + { + NiAVObject * root = skeletonRoot[i]->GetObjectByName(&rootName.data); + if(root) + { + NiNode * rootNode = root->GetAsNiNode(); + if(rootNode) + { + NiAVObject * overlayNode = rootNode->GetObjectByName(&m_nodeName.data); + if(overlayNode) + { +#ifdef _DEBUG + _MESSAGE("%s - Modifying Overlay %s from %08X on skeleton %d", __FUNCTION__, m_nodeName.data, actor->formID, i); +#endif + Modify(actor, overlayNode, rootNode); + } + } + } + } + } + } + } +} + +void SKSETaskModifyOverlay::Dispose() +{ + delete this; +} + +void SKSETaskUninstallOverlay::Modify(TESObjectREFR * reference, NiAVObject * targetNode, NiNode * parent) +{ + g_overlayInterface.UninstallOverlay(targetNode->m_name, reference, parent); +} + +void OverlayInterface::SetupOverlay(UInt32 primaryCount, const char * primaryPath, const char * primaryNode, UInt32 secondaryCount, const char * secondaryPath, const char * secondaryNode, TESObjectREFR * refr, NiNode * boneTree, NiAVObject * resultNode) +{ + BSGeometry * skin = GetFirstShaderType(resultNode, BSShaderMaterial::kShaderType_FaceGenRGBTint); + if(skin) + { +#ifdef _DEBUG + _MESSAGE("%s - Installing body overlay for %s on %08X", __FUNCTION__, skin->m_name, refr->formID); +#endif + char buff[MAX_PATH]; + for(UInt32 i = 0; i < primaryCount; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, primaryNode, i); + InstallOverlay(buff, primaryPath, refr, skin, boneTree); + } + for(UInt32 i = 0; i < secondaryCount; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, secondaryNode, i); + InstallOverlay(buff, secondaryPath, refr, skin, boneTree); + } + } + else + { +#ifdef _DEBUG + _MESSAGE("%s - Uninstalling body overlay on %08X", __FUNCTION__, refr->formID); +#endif + char buff[MAX_PATH]; + for(UInt32 i = 0; i < primaryCount; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, primaryNode, i); + UninstallOverlay(buff, refr, boneTree); + } + + for(UInt32 i = 0; i < secondaryCount; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, secondaryNode, i); + UninstallOverlay(buff, refr, boneTree); + } + } +} + +void OverlayInterface::RelinkOverlays(UInt32 primaryCount, const char * primaryNode, UInt32 secondaryCount, const char * secondaryNode, TESObjectREFR * refr, NiNode * boneTree, NiAVObject * resultNode) +{ + BSGeometry * skin = GetFirstShaderType(resultNode, BSShaderMaterial::kShaderType_FaceGenRGBTint); + if (skin) + { + char buff[MAX_PATH]; + for (UInt32 i = 0; i < primaryCount; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, primaryNode, i); + RelinkOverlay(buff, refr, skin, boneTree); + } + for (UInt32 i = 0; i < secondaryCount; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, secondaryNode, i); + RelinkOverlay(buff, refr, skin, boneTree); + } + } +} + +void OverlayInterface::BuildOverlays(UInt32 armorMask, UInt32 addonMask, TESObjectREFR * refr, NiNode * boneTree, NiAVObject * resultNode) +{ + if ((armorMask & BGSBipedObjectForm::kPart_Body) == BGSBipedObjectForm::kPart_Body && (addonMask & BGSBipedObjectForm::kPart_Body) == BGSBipedObjectForm::kPart_Body) + { + SetupOverlay(g_numBodyOverlays, BODY_MESH, BODY_NODE, g_numSpellBodyOverlays, BODY_MAGIC_MESH, BODY_NODE_SPELL, refr, boneTree, resultNode); + } + if ((armorMask & BGSBipedObjectForm::kPart_Hands) == BGSBipedObjectForm::kPart_Hands && (addonMask & BGSBipedObjectForm::kPart_Hands) == BGSBipedObjectForm::kPart_Hands) + { + SetupOverlay(g_numHandOverlays, HAND_MESH, HAND_NODE, g_numSpellHandOverlays, HAND_MAGIC_MESH, HAND_NODE_SPELL, refr, boneTree, resultNode); + } + if ((armorMask & BGSBipedObjectForm::kPart_Feet) == BGSBipedObjectForm::kPart_Feet && (addonMask & BGSBipedObjectForm::kPart_Feet) == BGSBipedObjectForm::kPart_Feet) + { + SetupOverlay(g_numFeetOverlays, FEET_MESH, FEET_NODE, g_numSpellFeetOverlays, FEET_MAGIC_MESH, FEET_NODE_SPELL, refr, boneTree, resultNode); + } +} + +void OverlayInterface::RebuildOverlays(UInt32 armorMask, UInt32 addonMask, TESObjectREFR * refr, NiNode * boneTree, NiAVObject * resultNode) +{ + if ((armorMask & BGSBipedObjectForm::kPart_Body) == BGSBipedObjectForm::kPart_Body && (addonMask & BGSBipedObjectForm::kPart_Body) == BGSBipedObjectForm::kPart_Body) + { + RelinkOverlays(g_numBodyOverlays, BODY_NODE, g_numSpellBodyOverlays, BODY_NODE_SPELL, refr, boneTree, resultNode); + } + if ((armorMask & BGSBipedObjectForm::kPart_Hands) == BGSBipedObjectForm::kPart_Hands && (addonMask & BGSBipedObjectForm::kPart_Hands) == BGSBipedObjectForm::kPart_Hands) + { + RelinkOverlays(g_numHandOverlays, HAND_NODE, g_numSpellHandOverlays, HAND_NODE_SPELL, refr, boneTree, resultNode); + } + if ((armorMask & BGSBipedObjectForm::kPart_Feet) == BGSBipedObjectForm::kPart_Feet && (addonMask & BGSBipedObjectForm::kPart_Feet) == BGSBipedObjectForm::kPart_Feet) + { + RelinkOverlays(g_numFeetOverlays, FEET_NODE, g_numSpellFeetOverlays, FEET_NODE_SPELL, refr, boneTree, resultNode); + } +} + +void OverlayInterface::RevertOverlay(TESObjectREFR * reference, BSFixedString nodeName, UInt32 armorMask, UInt32 addonMask, bool resetDiffuse) +{ + if(!reference) + return; + + g_task->AddTask(new SKSETaskRevertOverlay(reference, nodeName, armorMask, addonMask, resetDiffuse)); +} + +void OverlayInterface::RevertOverlays(TESObjectREFR * reference, bool resetDiffuse) +{ + if(!reference) + return; + + // Body + char buff[MAX_PATH]; + for(UInt32 i = 0; i < g_numBodyOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, BODY_NODE, i); + g_task->AddTask(new SKSETaskRevertOverlay(reference, buff, BGSBipedObjectForm::kPart_Body, BGSBipedObjectForm::kPart_Body, resetDiffuse)); + } + for(UInt32 i = 0; i < g_numSpellBodyOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, BODY_NODE_SPELL, i); + g_task->AddTask(new SKSETaskRevertOverlay(reference, buff, BGSBipedObjectForm::kPart_Body, BGSBipedObjectForm::kPart_Body, resetDiffuse)); + } + + // Hand + for(UInt32 i = 0; i < g_numHandOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, HAND_NODE, i); + g_task->AddTask(new SKSETaskRevertOverlay(reference, buff, BGSBipedObjectForm::kPart_Hands, BGSBipedObjectForm::kPart_Hands, resetDiffuse)); + } + for(UInt32 i = 0; i < g_numSpellHandOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, HAND_NODE_SPELL, i); + g_task->AddTask(new SKSETaskRevertOverlay(reference, buff, BGSBipedObjectForm::kPart_Hands, BGSBipedObjectForm::kPart_Hands, resetDiffuse)); + } + + // Feet + for(UInt32 i = 0; i < g_numFeetOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FEET_NODE, i); + g_task->AddTask(new SKSETaskRevertOverlay(reference, buff, BGSBipedObjectForm::kPart_Feet, BGSBipedObjectForm::kPart_Feet, resetDiffuse)); + } + for(UInt32 i = 0; i < g_numSpellFeetOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FEET_NODE_SPELL, i); + g_task->AddTask(new SKSETaskRevertOverlay(reference, buff, BGSBipedObjectForm::kPart_Feet, BGSBipedObjectForm::kPart_Feet, resetDiffuse)); + } + + // Face + for(UInt32 i = 0; i < g_numFaceOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FEET_NODE, i); + g_task->AddTask(new SKSETaskRevertFaceOverlay(reference, buff, BGSHeadPart::kTypeFace, BSShaderMaterial::kShaderType_FaceGen, resetDiffuse)); + } + for(UInt32 i = 0; i < g_numSpellFaceOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FEET_NODE_SPELL, i); + g_task->AddTask(new SKSETaskRevertFaceOverlay(reference, buff, BGSHeadPart::kTypeFace, BSShaderMaterial::kShaderType_FaceGen, resetDiffuse)); + } +} + + +void OverlayInterface::RevertHeadOverlay(TESObjectREFR * reference, BSFixedString nodeName, UInt32 partType, UInt32 shaderType, bool resetDiffuse) +{ + if(!reference) + return; + + g_task->AddTask(new SKSETaskRevertFaceOverlay(reference, nodeName, partType, shaderType, resetDiffuse)); +} + +void OverlayInterface::RevertHeadOverlays(TESObjectREFR * reference, bool resetDiffuse) +{ + if(!reference) + return; + + // Face + char buff[MAX_PATH]; + for(UInt32 i = 0; i < g_numFaceOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FACE_NODE, i); + g_task->AddTask(new SKSETaskRevertFaceOverlay(reference, buff, BGSHeadPart::kTypeFace, BSShaderMaterial::kShaderType_FaceGen, resetDiffuse)); + } + for(UInt32 i = 0; i < g_numSpellFaceOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FACE_NODE_SPELL, i); + g_task->AddTask(new SKSETaskRevertFaceOverlay(reference, buff, BGSHeadPart::kTypeFace, BSShaderMaterial::kShaderType_FaceGen, resetDiffuse)); + } +} + +bool OverlayInterface::HasOverlays(TESObjectREFR * reference) +{ + if(reference == (*g_thePlayer)) // Always true for the player + return true; + + UInt64 handle = g_overrideInterface.GetHandle(reference, reference->formType); + auto & it = overlays.find(handle); + if(it != overlays.end()) + return true; + + return false; +} + +void OverlayInterface::RemoveOverlays(TESObjectREFR * reference) +{ + if(!reference || reference == (*g_thePlayer)) // Cannot remove from player + return; + + // Body + char buff[MAX_PATH]; + for(UInt32 i = 0; i < g_numBodyOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, BODY_NODE, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + } + for(UInt32 i = 0; i < g_numSpellBodyOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, BODY_NODE_SPELL, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + } + + // Hand + for(UInt32 i = 0; i < g_numHandOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, HAND_NODE, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + } + for(UInt32 i = 0; i < g_numSpellHandOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, HAND_NODE_SPELL, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + } + + // Feet + for(UInt32 i = 0; i < g_numFeetOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FEET_NODE, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + } + for(UInt32 i = 0; i < g_numSpellFeetOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FEET_NODE_SPELL, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + } + + // Face + for(UInt32 i = 0; i < g_numFaceOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FACE_NODE, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + } + for(UInt32 i = 0; i < g_numSpellFaceOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FACE_NODE_SPELL, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + } + + UInt64 handle = g_overrideInterface.GetHandle(reference, TESObjectREFR::kTypeID); + overlays.erase(handle); +} + +void OverlayInterface::AddOverlays(TESObjectREFR * reference) +{ + if(!reference || reference == (*g_thePlayer)) // Cannot add to player, already exists + return; + +#ifdef _DEBUG + _DMESSAGE("%s Installing Overlays to %08X", __FUNCTION__, reference->formID); +#endif + + UInt64 handle = g_overrideInterface.GetHandle(reference, TESObjectREFR::kTypeID); + overlays.insert(handle); + + char buff[MAX_PATH]; + // Body + for(UInt32 i = 0; i < g_numBodyOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, BODY_NODE, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + g_task->AddTask(new SKSETaskInstallOverlay(reference, buff, BODY_MESH, BGSBipedObjectForm::kPart_Body, BGSBipedObjectForm::kPart_Body)); + } + for(UInt32 i = 0; i < g_numSpellBodyOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, BODY_NODE_SPELL, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + g_task->AddTask(new SKSETaskInstallOverlay(reference, buff, BODY_MAGIC_MESH, BGSBipedObjectForm::kPart_Body, BGSBipedObjectForm::kPart_Body)); + } + + // Hand + for(UInt32 i = 0; i < g_numHandOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, HAND_NODE, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + g_task->AddTask(new SKSETaskInstallOverlay(reference, buff, HAND_MESH, BGSBipedObjectForm::kPart_Hands, BGSBipedObjectForm::kPart_Hands)); + } + for(UInt32 i = 0; i < g_numSpellHandOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, HAND_NODE_SPELL, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + g_task->AddTask(new SKSETaskInstallOverlay(reference, buff, HAND_MAGIC_MESH, BGSBipedObjectForm::kPart_Hands, BGSBipedObjectForm::kPart_Hands)); + } + + // Feet + for(UInt32 i = 0; i < g_numFeetOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FEET_NODE, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + g_task->AddTask(new SKSETaskInstallOverlay(reference, buff, FEET_MESH, BGSBipedObjectForm::kPart_Feet, BGSBipedObjectForm::kPart_Feet)); + } + for(UInt32 i = 0; i < g_numSpellFeetOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FEET_NODE_SPELL, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + g_task->AddTask(new SKSETaskInstallOverlay(reference, buff, FEET_MAGIC_MESH, BGSBipedObjectForm::kPart_Feet, BGSBipedObjectForm::kPart_Feet)); + } + + // Face + for(UInt32 i = 0; i < g_numFaceOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FACE_NODE, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + g_task->AddTask(new SKSETaskInstallFaceOverlay(reference, buff, FACE_MESH, BGSHeadPart::kTypeFace, BSShaderMaterial::kShaderType_FaceGen)); + } + for(UInt32 i = 0; i < g_numSpellFaceOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FACE_NODE_SPELL, i); + g_task->AddTask(new SKSETaskUninstallOverlay(reference, buff)); + g_task->AddTask(new SKSETaskInstallFaceOverlay(reference, buff, FACE_MAGIC_MESH, BGSHeadPart::kTypeFace, BSShaderMaterial::kShaderType_FaceGen)); + } +} + +void OverlayInterface::Revert() +{ + for (auto & handle : overlays) { + TESObjectREFR * reference = static_cast(g_overrideInterface.GetObject(handle, TESObjectREFR::kTypeID)); + if (reference) { + RevertOverlays(reference, true); + } + } + overlays.clear(); +} + +std::string & OverlayInterface::GetDefaultTexture() +{ + return defaultTexture; +} + +void OverlayInterface::SetDefaultTexture(const std::string & newTexture) +{ + defaultTexture = newTexture; +} + +void OverlayHolder::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + for(OverlayHolder::iterator it = begin(); it != end(); ++it) + { + intfc->OpenRecord('AOVL', kVersion); + + UInt64 handle = (*it); + intfc->WriteRecordData(&handle, sizeof(handle)); + } +} + +bool OverlayHolder::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + bool error = false; + + UInt64 handle = 0; + // Key + if (!intfc->ReadRecordData(&handle, sizeof(handle))) + { + _ERROR("%s - Error loading overlay actor", __FUNCTION__); + error = true; + return error; + } + + UInt64 newHandle = 0; + // Skip if handle is no longer valid. + if (!ResolveAnyHandle(intfc, handle, &newHandle)) { + return error; + } + +#ifdef _DEBUG + _DMESSAGE("%s - Loading overlay for %016llX", __FUNCTION__, newHandle); +#endif + + TESObjectREFR * refr = static_cast(g_overrideInterface.GetObject(newHandle, TESObjectREFR::kTypeID)); + if(refr) + g_overlayInterface.AddOverlays(refr); + + return error; +} + +void OverlayInterface::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + overlays.Save(intfc, kVersion); +} + +bool OverlayInterface::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ +#ifdef _DEBUG + _DMESSAGE("%s Loading Overlays...", __FUNCTION__); +#endif + return overlays.Load(intfc, kVersion); +} + +#ifdef _DEBUG +void OverlayInterface::DumpMap() +{ + _DMESSAGE("Dumping Overlays"); + for(OverlayHolder::iterator it = overlays.begin(); it != overlays.end(); ++it) + { + _DMESSAGE("Overlay for handle: %016llX", (*it)); + } +} +#endif \ No newline at end of file diff --git a/skee/OverlayInterface.h b/skee/OverlayInterface.h new file mode 100644 index 0000000..8860355 --- /dev/null +++ b/skee/OverlayInterface.h @@ -0,0 +1,197 @@ +#pragma once + +#include "IPluginInterface.h" + +class TESObjectARMO; +class TESObjectARMA; +class TESModelTextureSwap; +class BGSTextureSet; +class TESObjectREFR; +class BSFaceGenNiNode; + +struct SKSESerializationInterface; + +class NiNode; +class BSGeometry; + +#include "skse64/GameThreads.h" +#include "skse64/GameTypes.h" + +#include + +#define FACE_NODE "Face [Ovl%d]" +#define FACE_NODE_SPELL "Face [SOvl%d]" +#define FACE_MESH "meshes\\actors\\character\\character assets\\face_overlay.nif" +#define FACE_MAGIC_MESH "meshes\\actors\\character\\character assets\\face_magicoverlay.nif" + +#define HAIR_NODE "Hair [Ovl%d]" +#define HAIR_NODE_SPELL "Hair [SOvl%d]" +#define HAIR_MESH "meshes\\actors\\character\\character assets\\hair_overlay.nif" +#define HAIR_MAGIC_MESH "meshes\\actors\\character\\character assets\\hair_magicoverlay.nif" + +#define BODY_NODE "Body [Ovl%d]" +#define BODY_NODE_SPELL "Body [SOvl%d]" +#define BODY_MESH "meshes\\actors\\character\\character assets\\body_overlay.nif" +#define BODY_MAGIC_MESH "meshes\\actors\\character\\character assets\\body_magicoverlay.nif" + +#define HAND_NODE "Hands [Ovl%d]" +#define HAND_NODE_SPELL "Hands [SOvl%d]" +#define HAND_MESH "meshes\\actors\\character\\character assets\\hands_overlay.nif" +#define HAND_MAGIC_MESH "meshes\\actors\\character\\character assets\\hands_magicoverlay.nif" + +#define FEET_NODE "Feet [Ovl%d]" +#define FEET_NODE_SPELL "Feet [SOvl%d]" +#define FEET_MESH "meshes\\actors\\character\\character assets\\feet_overlay.nif" +#define FEET_MAGIC_MESH "meshes\\actors\\character\\character assets\\feet_magicoverlay.nif" + +class SKSETaskRevertOverlay : public TaskDelegate +{ +public: + virtual void Run(); + virtual void Dispose(); + + SKSETaskRevertOverlay(TESObjectREFR * refr, BSFixedString nodeName, UInt32 armorMask, UInt32 addonMask, bool resetDiffuse); + + UInt32 m_formId; + BSFixedString m_nodeName; + UInt32 m_armorMask; + UInt32 m_addonMask; + bool m_resetDiffuse; +}; + +class SKSETaskRevertFaceOverlay : public TaskDelegate +{ +public: + virtual void Run(); + virtual void Dispose(); + + SKSETaskRevertFaceOverlay(TESObjectREFR * refr, BSFixedString nodeName, UInt32 partType, UInt32 shaderType, bool resetDiffuse); + + UInt32 m_formId; + BSFixedString m_nodeName; + UInt32 m_partType; + UInt32 m_shaderType; + bool m_resetDiffuse; +}; + +class SKSETaskInstallFaceOverlay : public TaskDelegate +{ +public: + virtual void Run(); + virtual void Dispose(); + + SKSETaskInstallFaceOverlay(TESObjectREFR * refr, BSFixedString nodeName, BSFixedString overlayPath, UInt32 partType, UInt32 shaderType); + + UInt32 m_formId; + BSFixedString m_nodeName; + BSFixedString m_overlayPath; + UInt32 m_partType; + UInt32 m_shaderType; +}; + +class SKSETaskInstallOverlay : public TaskDelegate +{ +public: + virtual void Run(); + virtual void Dispose(); + + SKSETaskInstallOverlay(TESObjectREFR * refr, BSFixedString nodeName, BSFixedString overlayPath, UInt32 armorMask, UInt32 addonMask); + + UInt32 m_formId; + BSFixedString m_nodeName; + BSFixedString m_overlayPath; + UInt32 m_armorMask; + UInt32 m_addonMask; +}; + +class SKSETaskModifyOverlay : public TaskDelegate +{ +public: + virtual void Run(); + virtual void Dispose(); + virtual void Modify(TESObjectREFR * reference, NiAVObject * targetNode, NiNode * parent) = 0; + + SKSETaskModifyOverlay(TESObjectREFR * refr, BSFixedString nodeName); + + UInt32 m_formId; + BSFixedString m_nodeName; +}; + +class SKSETaskUninstallOverlay : public SKSETaskModifyOverlay +{ +public: + virtual void Modify(TESObjectREFR * reference, NiAVObject * targetNode, NiNode * parent); + + SKSETaskUninstallOverlay(TESObjectREFR * refr, BSFixedString nodeName) : SKSETaskModifyOverlay(refr, nodeName){}; +}; + +class SKSETaskToggleOverlay : public SKSETaskModifyOverlay +{ +public: + virtual void Modify(TESObjectREFR * reference, NiAVObject * targetNode, NiNode * parent); + + SKSETaskToggleOverlay(TESObjectREFR * refr, BSFixedString nodeName, bool toggle); + + bool m_toggle; +}; + +class OverlayHolder : public std::set +{ +public: + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); +}; + +class OverlayInterface : public IPluginInterface +{ +public: + enum + { + kCurrentPluginVersion = 1, + kSerializationVersion1 = 1, + kSerializationVersion = kSerializationVersion1 + }; + virtual UInt32 GetVersion(); + + virtual void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + virtual bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); + virtual void Revert(); + + virtual bool HasOverlays(TESObjectREFR * reference); + virtual void AddOverlays(TESObjectREFR * reference); + virtual void RemoveOverlays(TESObjectREFR * reference); + virtual void RevertOverlays(TESObjectREFR * reference, bool resetDiffuse); + virtual void RevertOverlay(TESObjectREFR * reference, BSFixedString nodeName, UInt32 armorMask, UInt32 addonMask, bool resetDiffuse); + + virtual void RevertHeadOverlays(TESObjectREFR * reference, bool resetDiffuse); + virtual void RevertHeadOverlay(TESObjectREFR * reference, BSFixedString nodeName, UInt32 partType, UInt32 shaderType, bool resetDiffuse); + + virtual void SetupOverlay(UInt32 primaryCount, const char * primaryPath, const char * primaryNode, UInt32 secondaryCount, const char * secondaryPath, const char * secondaryNode, TESObjectREFR * refr, NiNode * boneTree, NiAVObject * resultNode); + + virtual void UninstallOverlay(const char * nodeName, TESObjectREFR * refr, NiNode * parent); + virtual void InstallOverlay(const char * nodeName, const char * path, TESObjectREFR * refr, BSGeometry * source, NiNode * destination, BGSTextureSet * textureSet = NULL); + virtual void ResetOverlay(const char * nodeName, TESObjectREFR * refr, BSGeometry * source, NiNode * destination, BGSTextureSet * textureSet = NULL, bool resetDiffuse = false); // Re-applies the skin's textures + + virtual std::string & GetDefaultTexture(); + virtual void SetDefaultTexture(const std::string & newTexture); + + // Relinks an overlay node by name to the new source + virtual void RelinkOverlay(const char * nodeName, TESObjectREFR * refr, BSGeometry * source, NiNode * skeleton); + + // Relinks structured name of overlays to new source + virtual void RelinkOverlays(UInt32 primaryCount, const char * primaryNode, UInt32 secondaryCount, const char * secondaryNode, TESObjectREFR * refr, NiNode * boneTree, NiAVObject * resultNode); + + // Builds default overlays + virtual void BuildOverlays(UInt32 armorMask, UInt32 addonMask, TESObjectREFR * refr, NiNode * boneTree, NiAVObject * resultNode); + + // Relinks default overlays + virtual void RebuildOverlays(UInt32 armorMask, UInt32 addonMask, TESObjectREFR * refr, NiNode * boneTree, NiAVObject * resultNode); + +#ifdef _DEBUG + void DumpMap(); +#endif + +private: + std::string defaultTexture; + OverlayHolder overlays; +}; diff --git a/skee/OverrideInterface.cpp b/skee/OverrideInterface.cpp new file mode 100644 index 0000000..4a26b70 --- /dev/null +++ b/skee/OverrideInterface.cpp @@ -0,0 +1,2654 @@ +#include "OverrideInterface.h" +#include "skse64/PluginAPI.h" +#include "skse64/PapyrusVM.h" + +#include "ShaderUtilities.h" +#include "OverrideVariant.h" +#include "StringTable.h" +#include "NifUtils.h" + +#include "skse64/GameReferences.h" +#include "skse64/GameObjects.h" +#include "skse64/GameRTTI.h" +#include "skse64/GameTypes.h" + +#include "skse64/NiGeometry.h" + +extern OverrideInterface g_overrideInterface; +extern SKSETaskInterface * g_task; +extern StringTable g_stringTable; +extern UInt16 g_loadMode; +extern bool g_firstLoad; + +UInt32 OverrideInterface::GetVersion() +{ + return kCurrentPluginVersion; +} + +void OverrideInterface::AddRawOverride(UInt64 handle, bool isFemale, UInt64 armorHandle, UInt64 addonHandle, BSFixedString nodeName, OverrideVariant & value) +{ + armorData.Lock(); + armorData.m_data[handle][isFemale ? 1 : 0][armorHandle][addonHandle][nodeName].erase(value); + armorData.m_data[handle][isFemale ? 1 : 0][armorHandle][addonHandle][nodeName].insert(value); + armorData.Release(); +} + +void OverrideInterface::AddOverride(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, OverrideVariant & value) +{ + UInt64 handle = GetHandle(refr, refr->formType); + UInt64 armorHandle = GetHandle(armor, armor->formType); + UInt64 addonHandle = GetHandle(addon, addon->formType); + armorData.Lock(); + armorData.m_data[handle][isFemale ? 1 : 0][armorHandle][addonHandle][nodeName].erase(value); + armorData.m_data[handle][isFemale ? 1 : 0][armorHandle][addonHandle][nodeName].insert(value); + armorData.Release(); +} + +void OverrideInterface::AddRawNodeOverride(UInt64 handle, bool isFemale, BSFixedString nodeName, OverrideVariant & value) +{ + nodeData.Lock(); + nodeData.m_data[handle][isFemale ? 1 : 0][nodeName].erase(value); + nodeData.m_data[handle][isFemale ? 1 : 0][nodeName].insert(value); + nodeData.Release(); +} + +void OverrideInterface::AddNodeOverride(TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, OverrideVariant & value) +{ + UInt64 handle = GetHandle(refr, refr->formType); + nodeData.Lock(); + nodeData.m_data[handle][isFemale ? 1 : 0][nodeName].erase(value); + nodeData.m_data[handle][isFemale ? 1 : 0][nodeName].insert(value); + nodeData.Release(); +} + +void OverrideInterface::AddRawWeaponOverride(UInt64 handle, bool isFemale, bool firstPerson, UInt64 weaponHandle, BSFixedString nodeName, OverrideVariant & value) +{ + weaponData.Lock(); + weaponData.m_data[handle][isFemale ? 1 : 0][firstPerson ? 1 : 0][weaponHandle][nodeName].erase(value); + weaponData.m_data[handle][isFemale ? 1 : 0][firstPerson ? 1 : 0][weaponHandle][nodeName].insert(value); + weaponData.Release(); +} + +void OverrideInterface::AddWeaponOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, OverrideVariant & value) +{ + UInt64 handle = GetHandle(refr, refr->formType); + UInt64 weaponHandle = GetHandle(weapon, weapon->formType); + weaponData.Lock(); + weaponData.m_data[handle][isFemale ? 1 : 0][firstPerson ? 1 : 0][weaponHandle][nodeName].erase(value); + weaponData.m_data[handle][isFemale ? 1 : 0][firstPerson ? 1 : 0][weaponHandle][nodeName].insert(value); + weaponData.Release(); +} + +void OverrideInterface::AddRawSkinOverride(UInt64 handle, bool isFemale, bool firstPerson, UInt32 slotMask, OverrideVariant & value) +{ + skinData.Lock(); + skinData.m_data[handle][isFemale ? 1 : 0][firstPerson ? 1 : 0][slotMask].erase(value); + skinData.m_data[handle][isFemale ? 1 : 0][firstPerson ? 1 : 0][slotMask].insert(value); + skinData.Release(); +} + +void OverrideInterface::AddSkinOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, OverrideVariant & value) +{ + UInt64 handle = GetHandle(refr, refr->formType); + AddRawSkinOverride(handle, isFemale, firstPerson, slotMask, value); +} + +OverrideVariant * OverrideInterface::GetOverride(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt16 key, UInt8 index) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&armorData.m_lock); + auto & it = armorData.m_data.find(handle); + if(it != armorData.m_data.end()) + { + UInt64 armorHandle = GetHandle(armor, armor->formType); + auto & ait = it->second[gender].find(armorHandle); + if(ait != it->second[gender].end()) + { + UInt64 addonHandle = GetHandle(addon, addon->formType); + auto & dit = ait->second.find(addonHandle); + if(dit != ait->second.end()) + { + auto & oit = dit->second.find(nodeName); + if(oit != dit->second.end()) + { + OverrideVariant ovr; + ovr.key = key; + ovr.index = index; + auto & ost = oit->second.find(ovr); + if(ost != oit->second.end()) + { + return const_cast(&(*ost)); + } + } + } + } + } + + return NULL; +} + +OverrideVariant * OverrideInterface::GetNodeOverride(TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt16 key, UInt8 index) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&nodeData.m_lock); + auto & it = nodeData.m_data.find(handle); + if(it != nodeData.m_data.end()) + { + auto & oit = it->second[gender].find(nodeName); + if(oit != it->second[gender].end()) + { + OverrideVariant ovr; + ovr.key = key; + ovr.index = index; + auto & ost = oit->second.find(ovr); + if(ost != oit->second.end()) + { + return const_cast(&(*ost)); + } + } + } + + return NULL; +} + +OverrideVariant * OverrideInterface::GetWeaponOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt16 key, UInt8 index) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&weaponData.m_lock); + auto & it = weaponData.m_data.find(handle); + if (it != weaponData.m_data.end()) + { + UInt64 weaponHandle = GetHandle(weapon, weapon->formType); + auto & ait = it->second[gender][firstPerson].find(weaponHandle); + if (ait != it->second[gender][firstPerson].end()) + { + auto & oit = ait->second.find(nodeName); + if (oit != ait->second.end()) + { + OverrideVariant ovr; + ovr.key = key; + ovr.index = index; + auto & ost = oit->second.find(ovr); + if (ost != oit->second.end()) + { + return const_cast(&(*ost)); + } + } + } + } + + return NULL; +} + +OverrideVariant * OverrideInterface::GetSkinOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, UInt16 key, UInt8 index) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&skinData.m_lock); + auto & it = skinData.m_data.find(handle); + if (it != skinData.m_data.end()) + { + auto & slot = it->second[gender][firstPerson].find(slotMask); + if(slot != it->second[gender][firstPerson].end()) + { + OverrideVariant ovr; + ovr.key = key; + ovr.index = index; + auto & ost = slot->second.find(ovr); + if (ost != slot->second.end()) + { + return const_cast(&(*ost)); + } + } + } + + return NULL; +} + +UInt64 OverrideInterface::GetHandle(void * src, UInt32 typeID) +{ + VMClassRegistry * registry = (*g_skyrimVM)->GetClassRegistry(); + IObjectHandlePolicy * policy = registry->GetHandlePolicy(); + + return policy->Create(typeID, (void*)src); +} + +void * OverrideInterface::GetObject(UInt64 handle, UInt32 typeID) +{ + VMClassRegistry * registry = (*g_skyrimVM)->GetClassRegistry(); + IObjectHandlePolicy * policy = registry->GetHandlePolicy(); + + if(handle == policy->GetInvalidHandle()) { + return NULL; + } + + return policy->Resolve(typeID, handle); +} + +void OverrideInterface::RemoveAllReferenceOverrides(TESObjectREFR * refr) +{ + UInt64 handle = GetHandle(refr, TESObjectREFR::kTypeID); + RemoveAllReferenceOverrides(handle); +} + +void OverrideInterface::RemoveAllReferenceNodeOverrides(TESObjectREFR * refr) +{ + UInt64 handle = GetHandle(refr, TESObjectREFR::kTypeID); + RemoveAllReferenceNodeOverrides(handle); +} + +void OverrideInterface::RemoveAllReferenceWeaponOverrides(TESObjectREFR * refr) +{ + UInt64 handle = GetHandle(refr, TESObjectREFR::kTypeID); + RemoveAllReferenceWeaponOverrides(handle); +} + +void OverrideInterface::RemoveAllReferenceSkinOverrides(TESObjectREFR * refr) +{ + UInt64 handle = GetHandle(refr, TESObjectREFR::kTypeID); + RemoveAllReferenceSkinOverrides(handle); +} + +void OverrideInterface::RemoveAllReferenceOverrides(UInt64 handle) +{ + armorData.Lock(); + armorData.m_data.erase(handle); + armorData.Release(); +} + +void OverrideInterface::RemoveAllReferenceNodeOverrides(UInt64 handle) +{ + nodeData.Lock(); + nodeData.m_data.erase(handle); + nodeData.Release(); +} + +void OverrideInterface::RemoveAllReferenceWeaponOverrides(UInt64 handle) +{ + weaponData.Lock(); + weaponData.m_data.erase(handle); + weaponData.Release(); +} + +void OverrideInterface::RemoveAllReferenceSkinOverrides(UInt64 handle) +{ + skinData.Lock(); + skinData.m_data.erase(handle); + skinData.Release(); +} + +void OverrideInterface::RemoveAllArmorOverrides(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&armorData.m_lock); + auto & it = armorData.m_data.find(handle); + if(it != armorData.m_data.end()) + { + UInt64 armorHandle = GetHandle(armor, armor->formType); + auto & nit = it->second[gender].find(armorHandle); + if(nit != it->second[gender].end()) { + nit->second.clear(); + } + } +} + +void OverrideInterface::RemoveAllArmorAddonOverrides(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&armorData.m_lock); + auto & it = armorData.m_data.find(handle); + if(it != armorData.m_data.end()) + { + UInt64 armorHandle = GetHandle(armor, armor->formType); + auto & ait = it->second[gender].find(armorHandle); + if(ait != it->second[gender].end()) + { + UInt64 addonHandle = GetHandle(addon, addon->formType); + auto & dit = ait->second.find(addonHandle); + if(dit != ait->second.end()) + { + ait->second.erase(dit); + } + } + } +} + +void OverrideInterface::RemoveAllArmorAddonNodeOverrides(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&armorData.m_lock); + auto & it = armorData.m_data.find(handle); + if(it != armorData.m_data.end()) + { + UInt64 armorHandle = GetHandle(armor, armor->formType); + auto & ait = it->second[gender].find(armorHandle); + if(ait != it->second[gender].end()) + { + UInt64 addonHandle = GetHandle(addon, addon->formType); + auto & dit = ait->second.find(addonHandle); + if(dit != ait->second.end()) + { + auto & oit = dit->second.find(nodeName); + if(oit != dit->second.end()) + { + dit->second.erase(oit); + } + } + } + } +} + +void OverrideInterface::RemoveArmorAddonOverride(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt16 key, UInt8 index) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&armorData.m_lock); + auto & it = armorData.m_data.find(handle); + if(it != armorData.m_data.end()) + { + UInt64 armorHandle = GetHandle(armor, armor->formType); + auto & ait = it->second[gender].find(armorHandle); + if(ait != it->second[gender].end()) + { + UInt64 addonHandle = GetHandle(addon, addon->formType); + auto & dit = ait->second.find(addonHandle); + if(dit != ait->second.end()) + { + auto & oit = dit->second.find(nodeName); + if(oit != dit->second.end()) + { + OverrideVariant ovr; + ovr.key = key; + ovr.index = index; + auto & ost = oit->second.find(ovr); + if(ost != oit->second.end()) + { + oit->second.erase(ost); + } + } + } + } + } +} + +void OverrideInterface::RemoveAllNodeNameOverrides(TESObjectREFR * refr, bool isFemale, BSFixedString nodeName) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&nodeData.m_lock); + auto & it = nodeData.m_data.find(handle); + if(it != nodeData.m_data.end()) + { + auto & oit = it->second[gender].find(nodeName); + if(oit != it->second[gender].end()) + { + it->second[gender].erase(oit); + } + } +} + +void OverrideInterface::RemoveNodeOverride(TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt16 key, UInt8 index) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&nodeData.m_lock); + auto & it = nodeData.m_data.find(handle); + if(it != nodeData.m_data.end()) + { + auto & oit = it->second[gender].find(nodeName); + if(oit != it->second[gender].end()) + { + OverrideVariant ovr; + ovr.key = key; + ovr.index = index; + auto & ost = oit->second.find(ovr); + if(ost != oit->second.end()) + { + oit->second.erase(ost); + } + } + } +} + +void OverrideInterface::RemoveAllWeaponOverrides(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&weaponData.m_lock); + auto & it = weaponData.m_data.find(handle); + if(it != weaponData.m_data.end()) + { + UInt64 weaponHandle = GetHandle(weapon, weapon->formType); + WeaponRegistration::iterator ait = it->second[gender][firstPerson ? 1 : 0].find(weaponHandle); + if(ait != it->second[gender][firstPerson ? 1 : 0].end()) + { + it->second[gender][firstPerson ? 1 : 0].erase(ait); + } + } +} + +void OverrideInterface::RemoveAllWeaponNodeOverrides(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt8 fPerson = firstPerson ? 1 : 0; + + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&weaponData.m_lock); + auto & it = weaponData.m_data.find(handle); + if(it != weaponData.m_data.end()) + { + UInt64 weaponHandle = GetHandle(weapon, weapon->formType); + WeaponRegistration::iterator ait = it->second[gender][fPerson].find(weaponHandle); + if(ait != it->second[gender][firstPerson].end()) + { + OverrideRegistration::iterator oit = ait->second.find(nodeName); + if(oit != ait->second.end()) + { + ait->second.erase(oit); + } + } + } +} + +void OverrideInterface::RemoveWeaponOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt16 key, UInt8 index) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt8 fPerson = firstPerson ? 1 : 0; + + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&weaponData.m_lock); + auto & it = weaponData.m_data.find(handle); + if(it != weaponData.m_data.end()) + { + UInt64 weaponHandle = GetHandle(weapon, weapon->formType); + WeaponRegistration::iterator ait = it->second[gender][fPerson].find(weaponHandle); + if(ait != it->second[gender][firstPerson].end()) + { + OverrideRegistration::iterator oit = ait->second.find(nodeName); + if(oit != ait->second.end()) + { + OverrideVariant ovr; + ovr.key = key; + ovr.index = index; + OverrideSet::iterator ost = oit->second.find(ovr); + if(ost != oit->second.end()) + { + oit->second.erase(ost); + } + } + } + } +} + +void OverrideInterface::RemoveAllSkinOverrides(TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&skinData.m_lock); + auto & it = skinData.m_data.find(handle); + if (it != skinData.m_data.end()) + { + auto & slot = it->second[gender][firstPerson].find(slotMask); + if (slot != it->second[gender][firstPerson].end()) + { + it->second[gender][firstPerson].erase(slot); + } + } +} + +void OverrideInterface::RemoveSkinOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, UInt16 key, UInt8 index) +{ + UInt8 gender = isFemale ? 1 : 0; + UInt8 fPerson = firstPerson ? 1 : 0; + + UInt64 handle = GetHandle(refr, refr->formType); + SimpleLocker locker(&skinData.m_lock); + auto & it = skinData.m_data.find(handle); + if (it != skinData.m_data.end()) + { + auto & slot = it->second[gender][firstPerson].find(slotMask); + if (slot != it->second[gender][firstPerson].end()) + { + OverrideVariant ovr; + ovr.key = key; + ovr.index = index; + OverrideSet::iterator ost = slot->second.find(ovr); + if (ost != slot->second.end()) + { + slot->second.erase(ost); + } + } + } +} + +void OverrideInterface::SetArmorAddonProperty(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, OverrideVariant * value, bool immediate) +{ + Actor * actor = DYNAMIC_CAST(refr, TESObjectREFR, Actor); + if (actor) { + VisitArmorAddon(actor, armor, addon, [&](bool isFP, NiNode * rootNode, NiAVObject * armorNode) + { + bool isRoot = nodeName == BSFixedString(""); + if (!armorNode->GetAsNiNode() && !isRoot) { + _WARNING("%s - Warning, override for Armor %08X Addon %08X has no children, use an empty string for the node name to access the root instead.", __FUNCTION__, armor->formID, addon->formID); + } + NiAVObject * foundNode = isRoot ? armorNode : armorNode->GetObjectByName(&nodeName.data); + if (foundNode) { + SetShaderProperty(foundNode, value, immediate); + } + }); + } +} + +void OverrideInterface::GetArmorAddonProperty(TESObjectREFR * refr, bool firstPerson, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, OverrideVariant * value) +{ + Actor * actor = DYNAMIC_CAST(refr, TESObjectREFR, Actor); + if (actor) { + VisitArmorAddon(actor, armor, addon, [&](bool isFP, NiNode * rootNode, NiAVObject * armorNode) + { + if (firstPerson == isFP) { + NiAVObject * foundNode = nodeName == BSFixedString("") ? armorNode : armorNode->GetObjectByName(&nodeName.data); + if (foundNode) { + GetShaderProperty(foundNode, value); + } + } + }); + } +} + +bool OverrideInterface::HasArmorAddonNode(TESObjectREFR * refr, bool firstPerson, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, bool debug) +{ + if(!refr) { + if(debug) + _DMESSAGE("%s - No reference", __FUNCTION__); + return false; + } + bool found = false; + Actor * actor = DYNAMIC_CAST(refr, TESObjectREFR, Actor); + if (actor) { + VisitArmorAddon(actor, armor, addon, [&](bool isFP, NiNode * rootNode, NiAVObject * armorNode) + { + if (firstPerson == isFP) { + NiAVObject * foundNode = nodeName == BSFixedString("") ? armorNode : armorNode->GetObjectByName(&nodeName.data); + if (foundNode) { + if (debug) + _DMESSAGE("%s - Success, found node name '%s' for Armor %08X, Addon %08X.", __FUNCTION__, nodeName.data, armor->formID, addon->formID); + + found = true; + } + else if (debug) + _DMESSAGE("%s - Failed to find node name '%s' for Armor %08X, Addon %08X.", __FUNCTION__, nodeName.data, armor->formID, addon->formID); + } + }); + } + + return found; +} + +void OverrideInterface::SetWeaponProperty(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, OverrideVariant * value, bool immediate) +{ + char weaponString[MAX_PATH]; + + UInt8 gender = isFemale ? 1 : 0; + + memset(weaponString, 0, MAX_PATH); + weapon->GetNodeName(weaponString); + + NiNode * root = refr->GetNiRootNode(firstPerson ? 1 : 0); // Apply to third and first person + if(root) { + root->IncRef(); + BSFixedString weaponName(weaponString); // Find the Armor name from the root + NiAVObject * weaponNode = root->GetObjectByName(&weaponName.data); + if(weaponNode) { + bool isRoot = nodeName == BSFixedString(""); + if(!weaponNode->GetAsNiNode() && !isRoot) { + _WARNING("%s - Warning, override for Weapon %08X has no children, use an empty string for the node name to access the root instead.", __FUNCTION__, weapon->formID); + } + NiAVObject * foundNode = isRoot ? weaponNode : weaponNode->GetObjectByName(&nodeName.data); + if(foundNode) { + SetShaderProperty(foundNode, value, immediate); + } + } + root->DecRef(); + } +} + +void OverrideInterface::GetWeaponProperty(TESObjectREFR * refr, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, OverrideVariant * value) +{ + char weaponString[MAX_PATH]; + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + memset(weaponString, 0, MAX_PATH); + weapon->GetNodeName(weaponString); + + NiNode * root = refr->GetNiRootNode(firstPerson ? 1 : 0); // Apply to third and first person + if(root) { + root->IncRef(); + BSFixedString weaponName(weaponString); // Find the Armor name from the root + NiAVObject * weaponNode = root->GetObjectByName(&weaponName.data); + if(weaponNode) { + NiAVObject * foundNode = nodeName == BSFixedString("") ? weaponNode : weaponNode->GetObjectByName(&nodeName.data); + if(foundNode) { + GetShaderProperty(foundNode, value); + } + } + root->DecRef(); + } +} + +bool OverrideInterface::HasWeaponNode(TESObjectREFR * refr, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, bool debug) +{ + if(!refr) { + if(debug) + _DMESSAGE("%s - No reference", __FUNCTION__); + return false; + } + char weaponString[MAX_PATH]; + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + memset(weaponString, 0, MAX_PATH); + weapon->GetNodeName(weaponString); + + NiNode * root = refr->GetNiRootNode(firstPerson ? 1 : 0); // Apply to third and first person + if(root) { + root->IncRef(); + BSFixedString weaponName(weaponString); // Find the Armor name from the root + NiAVObject * weaponNode = root->GetObjectByName(&weaponName.data); + if(weaponNode) { + NiAVObject * foundNode = nodeName == BSFixedString("") ? weaponNode : weaponNode->GetObjectByName(&nodeName.data); + if(foundNode) { + if(debug) _DMESSAGE("%s - Success, found node name '%s' for Weapon %08X.", __FUNCTION__, nodeName.data, weapon->formID); + return true; + } else if(debug) + _DMESSAGE("%s - Failed to find node name '%s' for Weapon %08X.", __FUNCTION__, nodeName.data, weapon->formID); + } else if(debug) + _DMESSAGE("%s - Failed to acquire weapon node '%s' for Weapon %08X.", __FUNCTION__, weaponName.data, weapon->formID); + root->DecRef(); + } else if(debug) + _DMESSAGE("%s - Failed to acquire skeleton for Reference %08X", __FUNCTION__, refr->formID); + + return false; +} + +void OverrideInterface::SetSkinProperty(TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, OverrideVariant * value, bool immediate) +{ + Actor * actor = DYNAMIC_CAST(refr, TESObjectREFR, Actor); + if (actor) { + TESForm * pForm = GetSkinForm(actor, slotMask); + TESObjectARMO * armor = DYNAMIC_CAST(pForm, TESForm, TESObjectARMO); + if (armor) { + for (UInt32 i = 0; i < armor->armorAddons.count; i++) { + TESObjectARMA * addon = NULL; + if (armor->armorAddons.GetNthItem(i, addon)) { + VisitArmorAddon(actor, armor, addon, [&](bool isFP, NiNode * rootNode, NiAVObject * armorNode) + { + if (firstPerson == isFP) + { + VisitObjects(armorNode, [&](NiAVObject* object) + { + BSGeometry * geometry = object->GetAsBSGeometry(); + if (geometry) + { + BSShaderProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if (shaderProperty && ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) + { + BSLightingShaderMaterial * material = (BSLightingShaderMaterial *)shaderProperty->material; + if (material && material->GetShaderType() == BSLightingShaderMaterial::kShaderType_FaceGenRGBTint) + { + SetShaderProperty(geometry, value, immediate); + } + } + } + return false; + }); + } + }); + } + } + } + } +} + +void OverrideInterface::GetSkinProperty(TESObjectREFR * refr, bool firstPerson, UInt32 slotMask, OverrideVariant * value) +{ + Actor * actor = DYNAMIC_CAST(refr, TESObjectREFR, Actor); + if (actor) { + TESForm * pForm = GetSkinForm(actor, slotMask); + TESObjectARMO * armor = DYNAMIC_CAST(pForm, TESForm, TESObjectARMO); + if (armor) { + for (UInt32 i = 0; i < armor->armorAddons.count; i++) { + TESObjectARMA * addon = NULL; + if (armor->armorAddons.GetNthItem(i, addon)) { + + VisitArmorAddon(actor, armor, addon, [&](bool isFP, NiNode * rootNode, NiAVObject * armorNode) + { + if (firstPerson == isFP) + { + VisitObjects(armorNode, [&](NiAVObject* object) + { + BSGeometry * geometry = object->GetAsBSGeometry(); + if (geometry) + { + BSShaderProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if (shaderProperty && ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) + { + BSLightingShaderMaterial * material = (BSLightingShaderMaterial *)shaderProperty->material; + if (material && material->GetShaderType() == BSLightingShaderMaterial::kShaderType_FaceGenRGBTint) + { + GetShaderProperty(geometry, value); + } + } + } + return false; + }); + } + }); + } + } + } + } +} + +void OverrideInterface::SetHandleProperties(UInt64 handle, bool immediate) +{ + TESObjectREFR * refr = (TESObjectREFR *)GetObject(handle, TESObjectREFR::kTypeID); + if(!refr) { + return; + } + + Actor * actor = DYNAMIC_CAST(refr, TESForm, Actor); + if (!actor) { + return; + } + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + SimpleLocker locker(&armorData.m_lock); + auto & it = armorData.m_data.find(handle); // Find ActorHandle + if(it != armorData.m_data.end()) + { + for(ArmorRegistration::iterator ait = it->second[gender].begin(); ait != it->second[gender].end(); ++ait) // Loop Armors + { + TESObjectARMO * armor = (TESObjectARMO *)GetObject(ait->first, TESObjectARMO::kTypeID); + if(!armor) + continue; + + for(AddonRegistration::iterator dit = ait->second.begin(); dit != ait->second.end(); ++dit) // Loop Addons + { + TESObjectARMA * addon = (TESObjectARMA *)GetObject(dit->first, TESObjectARMA::kTypeID); + if(!addon) + continue; + + VisitArmorAddon(actor, armor, addon, [&](bool isFP, NiNode * rootNode, NiAVObject * armorNode) + { + dit->second.Visit([&](const BSFixedString & key, OverrideSet * set) + { + BSFixedString nodeName(key.data); + NiAVObject * foundNode = key == BSFixedString("") ? armorNode : armorNode->GetObjectByName(&nodeName.data); + if (foundNode) { + set->Visit([&](OverrideVariant * value) + { + SetShaderProperty(foundNode, value, immediate); + return false; + }); + } + + return false; + }); + }); + } + } + } +} + +void OverrideInterface::SetNodeProperty(TESObjectREFR * refr, BSFixedString nodeName, OverrideVariant * value, bool immediate) +{ + NiNode * lastRoot = NULL; + for(UInt32 i = 0; i <= 1; i++) + { + NiNode * root = refr->GetNiRootNode(i); // Apply to third and first person + if(root == lastRoot) // First and Third are the same, skip + continue; + + if(root) { + root->IncRef(); + NiAVObject * foundNode = root->GetObjectByName(&nodeName.data); + if(foundNode) { + SetShaderProperty(foundNode, value, immediate); + } + root->DecRef(); + } + + lastRoot = root; + } +} + +void OverrideInterface::GetNodeProperty(TESObjectREFR * refr, bool firstPerson, BSFixedString nodeName, OverrideVariant * value) +{ + NiNode * root = refr->GetNiRootNode(firstPerson ? 1 : 0); // Apply to third and first person + if(root) { + root->IncRef(); + NiAVObject * foundNode = root->GetObjectByName(&nodeName.data); + if(foundNode) { + GetShaderProperty(foundNode, value); + } + root->DecRef(); + } +} + +void OverrideInterface::SetHandleNodeProperties(UInt64 handle, bool immediate) +{ + TESObjectREFR * refr = (TESObjectREFR *)GetObject(handle, TESObjectREFR::kTypeID); + if(!refr) { + return; + } + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + SimpleLocker locker(&nodeData.m_lock); + auto & nit = nodeData.m_data.find(handle); // Find ActorHandle + if(nit != nodeData.m_data.end()) + { + NiNode * lastRoot = NULL; + for(UInt8 i = 0; i <= 1; i++) + { + NiNode * root = refr->GetNiRootNode(i); + if(root == lastRoot) // First and third are the same, skip + continue; + + if(root) + { + root->IncRef(); + nit->second[gender].Visit([&](const BSFixedString & key, OverrideSet * set) + { + BSFixedString nodeName(key.data); + NiAVObject * foundNode = key == BSFixedString("") ? root : root->GetObjectByName(&nodeName.data); + if (foundNode) { + set->Visit([&](OverrideVariant * value) + { + SetShaderProperty(foundNode, value, immediate); + return false; + }); + } + + return false; + }); + root->DecRef(); + } + + lastRoot = root; + } + } +} + +void OverrideInterface::SetHandleWeaponProperties(UInt64 handle, bool immediate) +{ + TESObjectREFR * refr = (TESObjectREFR *)GetObject(handle, TESObjectREFR::kTypeID); + if (!refr) { + return; + } + + char weaponString[MAX_PATH]; + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if (actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + SimpleLocker locker(&weaponData.m_lock); + auto & it = weaponData.m_data.find(handle); // Find ActorHandle + if (it != weaponData.m_data.end()) + { + for (UInt8 i = 0; i <= 1; i++) + { + for (WeaponRegistration::iterator ait = it->second[gender][i].begin(); ait != it->second[gender][i].end(); ++ait) // Loop Armors + { + TESObjectWEAP * weapon = (TESObjectWEAP *)GetObject(ait->first, TESObjectWEAP::kTypeID); + if (!weapon) + continue; + + memset(weaponString, 0, MAX_PATH); + weapon->GetNodeName(weaponString); + + NiNode * lastNode = NULL; + BSFixedString weaponName(weaponString); + + NiNode * root = refr->GetNiRootNode(i); + if (root == lastNode) // First and Third are the same, skip + continue; + + if (root) + { + root->IncRef(); + // Find the Armor node + NiAVObject * weaponNode = root->GetObjectByName(&weaponName.data); + if (weaponNode) { + ait->second.Visit([&](const BSFixedString & key, OverrideSet * set) + { + BSFixedString nodeName(key.data); + NiAVObject * foundNode = key == BSFixedString("") ? weaponNode : weaponNode->GetObjectByName(&nodeName.data); + if (foundNode) { + set->Visit([&](OverrideVariant * value) + { + SetShaderProperty(foundNode, value, immediate); + return false; + }); + } + + return false; + }); + } + root->DecRef(); + } + + lastNode = root; + } + } + } +} + +void OverrideInterface::SetHandleSkinProperties(UInt64 handle, bool immediate) +{ + TESObjectREFR * refr = (TESObjectREFR *)GetObject(handle, TESObjectREFR::kTypeID); + if (!refr) { + return; + } + + Actor * actor = DYNAMIC_CAST(refr, TESObjectREFR, Actor); + if (!actor) { + return; + } + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if (actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + SimpleLocker locker(&skinData.m_lock); + auto & it = skinData.m_data.find(handle); // Find ActorHandle + if (it != skinData.m_data.end()) + { + for (UInt8 fp = 0; fp <= 1; fp++) + { + for (SkinRegistration::iterator ait = it->second[gender][fp].begin(); ait != it->second[gender][fp].end(); ++ait) // Loop Armors + { + NiNode * lastNode = NULL; + NiNode * root = refr->GetNiRootNode(fp); + if (root == lastNode) // First and Third are the same, skip + continue; + + if (root) + { + NiAutoRefCounter rc(root); + TESForm * pForm = GetSkinForm(actor, ait->first); + TESObjectARMO * armor = DYNAMIC_CAST(pForm, TESForm, TESObjectARMO); + if (armor) { + for (UInt32 i = 0; i < armor->armorAddons.count; i++) { + TESObjectARMA * arma = NULL; + if (armor->armorAddons.GetNthItem(i, arma)) { + VisitArmorAddon(actor, armor, arma, [&](bool isFirstPerson, NiAVObject * rootNode, NiAVObject * parent) + { + if ((fp == 0 && isFirstPerson) || (fp == 1 && !isFirstPerson)) + { + VisitObjects(parent, [&](NiAVObject* object) + { + BSGeometry * geometry = object->GetAsBSGeometry(); + if (geometry) + { + BSShaderProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if (shaderProperty && ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) + { + BSLightingShaderMaterial * material = (BSLightingShaderMaterial *)shaderProperty->material; + if (material && material->GetShaderType() == BSLightingShaderMaterial::kShaderType_FaceGenRGBTint) + { + ait->second.Visit([&](OverrideVariant * value) + { + SetShaderProperty(object, value, immediate); + return false; + }); + } + } + } + return false; + }); + } + }); + } + } + } + } + lastNode = root; + } + } + } +} + +void OverrideInterface::VisitNodes(TESObjectREFR * refr, std::function functor) +{ + UInt64 handle = GetHandle(refr, refr->formType); + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if (actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + SimpleLocker locker(&nodeData.m_lock); + auto & nit = nodeData.m_data.find(handle); // Find ActorHandle + if (nit != nodeData.m_data.end()) + { + for (auto & ovr : nit->second[gender]) // Loop Overrides + { + for (auto prop : ovr.second) { + functor(ovr.first, prop); + } + } + } +} + +void OverrideInterface::VisitSkin(TESObjectREFR * refr, bool isFemale, bool firstPerson, std::function functor) +{ + UInt64 handle = GetHandle(refr, refr->formType); + + UInt8 fp = firstPerson ? 1 : 0; + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if (actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + SimpleLocker locker(&skinData.m_lock); + auto & it = skinData.m_data.find(handle); + if (it != skinData.m_data.end()) + { + for (auto & ovr : it->second[gender][fp]) + { + for (auto prop : ovr.second) { + functor(ovr.first, prop); + } + } + } +} + +/* +void OverrideInterface::SetHandleArmorAddonProperties(UInt64 handle, UInt64 armorHandle, UInt64 addonHandle, bool immediate) +{ + TESObjectREFR * refr = (TESObjectREFR *)GetObject(handle, TESObjectREFR::kTypeID); + if(!refr) { + armorData.Lock(); + armorData.m_data.erase(handle); + armorData.Release(); + _MESSAGE("Error applying override: No such reference %016llX", handle); + return; + } + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + ActorRegistrationMapHolder::RegMap::iterator it = armorData.m_data.find(handle); // Find ActorHandle + if(it != armorData.m_data.end()) + { + TESObjectARMO * armor = (TESObjectARMO *)GetObject(armorHandle, TESObjectARMO::kTypeID); + if(!armor) { + armorData.Lock(); + armorData.m_data[handle][gender].erase(armorHandle); + armorData.Release(); + _MESSAGE("Error applying override: No such armor %016llX", armorHandle); + return; + } + ArmorRegistration::iterator ait = it->second[gender].find(armorHandle); // Find ArmorHandle + if(ait != it->second[gender].end()) + { + TESObjectARMA * addon = (TESObjectARMA *)GetObject(addonHandle, TESObjectARMA::kTypeID); + if(!addon) { + armorData.Lock(); + armorData.m_data[handle][gender][armorHandle].erase(addonHandle); + armorData.Release(); + _MESSAGE("Error applying override: No such addon %016llX", addonHandle); + return; + } + + AddonRegistration::iterator dit = ait->second.find(addonHandle); // Find AddonHandle + if(dit != ait->second.end()) + { + char addonString[MAX_PATH]; + addon->GetNodeName(addonString, refr, armor, -1); + + NiNode * lastRoot = NULL; + BSFixedString addonName(addonString); + for(UInt8 i = 0; i <= 1; i++) + { + NiNode * root = refr->GetNiRootNode(i); + if(root == lastRoot) // First and third are the same, skip + continue; + + if(root) + { + NiAVObject * armorNode = root->GetObjectByName(&addonName.data); + if(armorNode) { + OverrideRegistration::SetVisitor visitor(armorNode, immediate); + dit->second.Visit(&visitor); + } + } + + lastRoot = root; + } + } + } + } +}*/ + +class NodeOverrideApplicator : public GeometryVisitor +{ +public: + NodeOverrideApplicator::NodeOverrideApplicator(OverrideRegistration * overrides, bool immediate) : m_overrides(overrides), m_immediate(immediate) {} + + virtual bool Accept(BSGeometry * geometry) + { + BSFixedString nodeName(geometry->m_name); + OverrideRegistration::iterator nit = m_overrides->find(nodeName); + if(nit != m_overrides->end()) + { + nit->second.Visit([&](OverrideVariant * value) + { + SetShaderProperty(geometry, value, m_immediate); + return false; + }); + } + return false; + } + + OverrideRegistration * m_overrides; + bool m_immediate; +}; + +class OverrideApplicator : public GeometryVisitor +{ +public: + OverrideApplicator::OverrideApplicator(OverrideRegistration * overrides, bool immediate) : m_overrides(overrides), m_immediate(immediate) {} + + virtual bool Accept(BSGeometry * geometry) + { + m_geometryList.push_back(geometry); + return false; + } + + void Apply() + { + for(auto & geometry : m_geometryList) + { + BSFixedString objectName(m_geometryList.size() == 1 ? "" : geometry->m_name); + OverrideRegistration::iterator nit = m_overrides->find(objectName); + if(nit != m_overrides->end()) + { + nit->second.Visit([&](OverrideVariant* value) + { + SetShaderProperty(geometry, value, m_immediate); + return false; + }); + } + } + } + + std::vector m_geometryList; + OverrideRegistration * m_overrides; + bool m_immediate; +}; + + +class SkinOverrideApplicator : public GeometryVisitor +{ +public: + SkinOverrideApplicator::SkinOverrideApplicator(TESObjectARMO * armor, TESObjectARMA * addon, UInt32 slotMask, OverrideSet * overrides, bool immediate) : m_armor(armor), m_addon(addon), m_overrides(overrides), m_slotMask(slotMask), m_immediate(immediate) {} + + virtual bool Accept(BSGeometry * geometry) + { + UInt32 armorMask = m_armor->bipedObject.GetSlotMask(); + UInt32 addonMask = m_addon->biped.GetSlotMask(); + + if ((armorMask & m_slotMask) == m_slotMask && (addonMask & m_slotMask) == m_slotMask) + { + m_geometryList.push_back(geometry); + } + + return false; + } + + void Apply() + { + for (auto & geometry : m_geometryList) + { + BSShaderProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if (shaderProperty && ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) + { + BSLightingShaderMaterial * material = (BSLightingShaderMaterial *)shaderProperty->material; + if (material && material->GetShaderType() == BSLightingShaderMaterial::kShaderType_FaceGenRGBTint) + { + if (m_overrides) { + m_overrides->Visit([&](OverrideVariant* value) + { + SetShaderProperty(geometry, value, m_immediate); + return false; + }); + } + } + } + } + } + + std::vector m_geometryList; + TESObjectARMO * m_armor; + TESObjectARMA * m_addon; + OverrideSet * m_overrides; + UInt32 m_slotMask; + bool m_immediate; +}; +/* +void OverrideInterface::GetGeometryCount(NiAVObject * object, SInt32 * count) +{ + NiGeometry * geometry = object->GetAsNiGeometry(); + if(geometry) { + (*count)++; + return; + } + + NiNode * niNode = object->GetAsNiNode(); + if(niNode) { + if(niNode->m_children.m_emptyRunStart > 0 && niNode->m_children.m_data) { + for(int i = 0; i < niNode->m_children.m_emptyRunStart; i++) + { + NiAVObject * object = niNode->m_children.m_data[i]; + if(object) + GetGeometryCount(object, count); + } + } + } +} + +template<> +void OverrideInterface::SetOverrides(NiGeometry * geometry, OverrideRegistration * overrides, bool immediate, SInt32 geoCount) +{ + BSFixedString objectName(geoCount == 1 ? "" : geometry->m_name); + OverrideRegistration::iterator nit = overrides->find(objectName); + if(nit != overrides->end()) + { + OverrideSet::PropertyVisitor visitor(geometry, immediate); + nit->second.Visit(&visitor); + } +} + +template +void OverrideInterface::SetOverridesRecursive(NiAVObject * node, OverrideRegistration * overrides, bool immediate, SInt32 geoCount) +{ + NiNode * niNode = node->GetAsNiNode(); + NiGeometry * geometry = node->GetAsNiGeometry(); + if(geometry) + SetOverrides(geometry, overrides, immediate, geoCount); + else if(niNode && niNode->m_children.m_emptyRunStart > 0) + { + for(int i = 0; i < niNode->m_children.m_emptyRunStart; i++) + { + NiAVObject * object = niNode->m_children.m_data[i]; + if(object) + SetOverridesRecursive(object, overrides, immediate, geoCount); + } + } +} +*/ + +void OverrideInterface::ApplyNodeOverrides(TESObjectREFR * refr, NiAVObject * object, bool immediate) +{ + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + SimpleLocker locker(&nodeData.m_lock); + UInt64 handle = GetHandle(refr, refr->formType); + auto & nit = nodeData.m_data.find(handle); + if(nit != nodeData.m_data.end()) { + NodeOverrideApplicator applicator(&nit->second[gender], immediate); + VisitGeometry(object, &applicator); + } +} + +void OverrideInterface::ApplyOverrides(TESObjectREFR * refr, TESObjectARMO * armor, TESObjectARMA * addon, NiAVObject * object, bool immediate) +{ + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + SimpleLocker locker(&armorData.m_lock); + UInt64 handle = GetHandle(refr, refr->formType); + auto & it = armorData.m_data.find(handle); // Find ActorHandle + if(it != armorData.m_data.end()) + { + UInt64 armorHandle = GetHandle(armor, armor->formType); + auto ait = it->second[gender].find(armorHandle); // Find ArmorHandle + if(ait != it->second[gender].end()) + { + UInt64 addonHandle = GetHandle(addon, addon->formType); + auto dit = ait->second.find(addonHandle); // Find AddonHandle + if(dit != ait->second.end()) + { + OverrideApplicator applicator(&dit->second, immediate); + VisitGeometry(object, &applicator); + applicator.Apply(); + } + } + } +} + +void OverrideInterface::ApplyWeaponOverrides(TESObjectREFR * refr, bool firstPerson, TESObjectWEAP * weapon, NiAVObject * object, bool immediate) +{ + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + SimpleLocker locker(&weaponData.m_lock); + UInt64 handle = GetHandle(refr, refr->formType); + auto it = weaponData.m_data.find(handle); // Find ActorHandle + if(it != weaponData.m_data.end()) + { + UInt64 weaponHandle = GetHandle(weapon, weapon->formType); + auto ait = it->second[gender][firstPerson ? 1 : 0].find(weaponHandle); // Find WeaponHandle + if(ait != it->second[gender][firstPerson ? 1 : 0].end()) + { + OverrideApplicator applicator(&ait->second, immediate); + VisitGeometry(object, &applicator); + applicator.Apply(); + } + } +} + +void OverrideInterface::ApplySkinOverrides(TESObjectREFR * refr, bool firstPerson, TESObjectARMO * armor, TESObjectARMA * addon, UInt32 slotMask, NiAVObject * object, bool immediate) +{ + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if (actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + SimpleLocker locker(&skinData.m_lock); + UInt64 handle = GetHandle(refr, refr->formType); + auto it = skinData.m_data.find(handle); // Find ActorHandle + if (it != skinData.m_data.end()) + { + auto ait = it->second[gender][firstPerson ? 1 : 0].find(slotMask); // Find WeaponHandle + if (ait != it->second[gender][firstPerson ? 1 : 0].end()) + { + SkinOverrideApplicator applicator(armor, addon, slotMask, &ait->second, immediate); + VisitGeometry(object, &applicator); + applicator.Apply(); + } + } +} + +void OverrideSet::Visit(std::function functor) +{ + for(auto it = begin(); it != end(); ++it) { + if(functor(const_cast(&(*it)))) + break; + } +} + +template +void OverrideRegistration::Visit(std::function functor) +{ + for(auto it = begin(); it != end(); ++it) { + if(functor(it->first, &it->second)) + break; + } +} + +void OverrideInterface::Revert() +{ + armorData.Lock(); + armorData.m_data.clear(); + armorData.Release(); + + nodeData.Lock(); + nodeData.m_data.clear(); + nodeData.Release(); + + weaponData.Lock(); + weaponData.m_data.clear(); + weaponData.Release(); +} + +void OverrideInterface::RemoveAllOverrides() +{ + armorData.Lock(); + armorData.m_data.clear(); + armorData.Release(); +} + +void OverrideInterface::RemoveAllNodeOverrides() +{ + nodeData.Lock(); + nodeData.m_data.clear(); + nodeData.Release(); +} + +void OverrideInterface::RemoveAllWeaponBasedOverrides() +{ + weaponData.Lock(); + weaponData.m_data.clear(); + weaponData.Release(); +} + +void OverrideInterface::RemoveAllSkinBasedOverrides() +{ + skinData.Lock(); + skinData.m_data.clear(); + skinData.Release(); +} + +// OverrideVariant +void OverrideVariant::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + intfc->OpenRecord('OVRV', kVersion); + // Key + intfc->WriteRecordData(&key, sizeof(key)); + + intfc->WriteRecordData(&type, sizeof(type)); + + if(IsIndexValid(key)) + intfc->WriteRecordData(&index, sizeof(index)); + + switch(type) { + case kType_Int: + intfc->WriteRecordData(&data.u, sizeof(data.u)); + break; + case kType_Float: + intfc->WriteRecordData(&data.f, sizeof(data.f)); + break; + case kType_Bool: + intfc->WriteRecordData(&data.b, sizeof(data.b)); + break; + case kType_String: + { + g_stringTable.WriteString(intfc, data.str, kVersion); + } + break; + case kType_Identifier: + { + VMClassRegistry * registry = (*g_skyrimVM)->GetClassRegistry(); + IObjectHandlePolicy * policy = registry->GetHandlePolicy(); + UInt64 handle = policy->Create(BGSTextureSet::kTypeID, data.p); + intfc->WriteRecordData(&handle, sizeof(handle)); + } + break; + } + +#ifdef _DEBUG + _MESSAGE("Saving %d %d %X %f", key, type, data.u, data.f); +#endif +} + +bool OverrideVariant::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + SetNone(); + + if(intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'OVRV': + { + UInt16 keyValue; + // Key + if (!intfc->ReadRecordData(&keyValue, sizeof(keyValue))) + { + _ERROR("%s - Error loading override value key", __FUNCTION__); + error = true; + return error; + } + + this->key = keyValue; + + if (!intfc->ReadRecordData(&this->type, sizeof(this->type))) + { + _ERROR("%s - Error loading override value type", __FUNCTION__); + error = true; + return error; + } + + if(IsIndexValid(this->key)) + { + if (!intfc->ReadRecordData(&this->index, sizeof(this->index))) + { + _ERROR("%s - Error loading override value index", __FUNCTION__); + error = true; + return error; + } + } + + switch(this->type) + { + case kType_Int: + { + if (!intfc->ReadRecordData(&data.u, sizeof(data.u))) { + _ERROR("%s - Error loading override value data", __FUNCTION__); + error = true; + return error; + } + } + break; + case kType_Float: + { + if (!intfc->ReadRecordData(&data.f, sizeof(data.f))) { + _ERROR("%s - Error loading override value data", __FUNCTION__); + error = true; + return error; + } + } + break; + case kType_Bool: + { + if (!intfc->ReadRecordData(&data.b, sizeof(data.b))) { + _ERROR("%s - Error loading override value data", __FUNCTION__); + error = true; + return error; + } + } + break; + case kType_String: + { + BSFixedString str = g_stringTable.ReadString(intfc, kVersion); + this->data.str = str.data; + } + break; + case kType_Identifier: + { + UInt64 handle; + if (!intfc->ReadRecordData(&handle, sizeof(handle))) + { + _ERROR("%s - Error loading override value key handle", __FUNCTION__); + error = true; + return error; + } + + UInt64 newHandle = 0; + if (intfc->ResolveHandle(handle, &newHandle)) + { + VMClassRegistry * registry = (*g_skyrimVM)->GetClassRegistry(); + IObjectHandlePolicy * policy = registry->GetHandlePolicy(); + + if(policy->IsType(BGSTextureSet::kTypeID, newHandle)) + data.p = (void*)policy->Resolve(BGSTextureSet::kTypeID, newHandle); + else + SetNone(); + } + else + SetNone(); + } + break; + } + + break; + } + default: + { + _ERROR("%s - Error loading unexpected chunk type %08X (%.4s)", __FUNCTION__, type, &type); + error = true; + return error; + } + } + } + + return error; +} + +void OverrideInterface::VisitStrings(std::function functor) +{ + for (auto & i1 : armorData.m_data){ + for (UInt8 gender = 0; gender <= 1; gender++) { + for (UInt8 fp = 0; fp <= 1; fp++) { + for (auto & i2 : i1.second[gender][fp]) { + for (auto & i3 : i2.second) { + functor(i3.first); + for (auto & i4 : i3.second){ + if (i4.type == OverrideVariant::kType_String) { + BSFixedString str(i4.data.str); + functor(str); + } + } + } + } + } + } + } + + for (auto & i1 : weaponData.m_data) { + for (UInt8 gender = 0; gender <= 1; gender++) { + for (UInt8 fp = 0; fp <= 1; fp++) { + for (auto & i2 : i1.second[gender][fp]) { + for (auto & i3 : i2.second) { + functor(i3.first); + for (auto & i4 : i3.second) { + if (i4.type == OverrideVariant::kType_String) { + BSFixedString str(i4.data.str); + functor(str); + } + } + } + } + } + } + } + + for (auto & i1 : nodeData.m_data) { + for (UInt8 fp = 0; fp <= 1; fp++) { + for (auto & i2 : i1.second[fp]) { + functor(i2.first); + for (auto & i3 : i2.second) { + if (i3.type == OverrideVariant::kType_String) { + BSFixedString str(i3.data.str); + functor(str); + } + } + } + } + } + + for (auto & i1 : skinData.m_data) { + for (UInt8 gender = 0; gender <= 1; gender++) { + for (UInt8 fp = 0; fp <= 1; fp++) { + for (auto & i2 : i1.second[gender][fp]) { + for (auto & i3 : i2.second) { + if (i3.type == OverrideVariant::kType_String) { + BSFixedString str(i3.data.str); + functor(str); + } + } + } + } + } + } +} + +// ValueSet +void OverrideSet::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + intfc->OpenRecord('OVST', kVersion); + + UInt32 numOverrides = this->size(); + intfc->WriteRecordData(&numOverrides, sizeof(numOverrides)); + +#ifdef _DEBUG + _MESSAGE("Saving %d values", numOverrides); +#endif + + for(auto it = this->begin(); it != this->end(); ++it) + const_cast((*it)).Save(intfc, kVersion); +} + +bool OverrideSet::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + if(intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'OVST': + { + // Override Count + UInt32 numOverrides = 0; + if (!intfc->ReadRecordData(&numOverrides, sizeof(numOverrides))) + { + _MESSAGE("%s - Error loading override count", __FUNCTION__); + error = true; + return error; + } + + for (UInt32 i = 0; i < numOverrides; i++) + { + OverrideVariant value; + if (!value.Load(intfc, version)) + { + if(value.type == OverrideVariant::kType_None) + continue; + +#ifdef _DEBUG + if (value.type != OverrideVariant::kType_String) + _MESSAGE("Loaded override value %d %X", value.key, value.data.u); + else + _MESSAGE("Loaded override value %d %s", value.key, value.data.GetStr()->data); +#endif + + this->insert(value); + } + else + { + _MESSAGE("%s - Error loading override value", __FUNCTION__); + error = true; + return error; + } + } + + break; + } + default: + { + _MESSAGE("%s - Error loading unexpected chunk type %08X (%.4s)", __FUNCTION__, type, &type); + error = true; + return error; + } + } + } + + return error; +} + +// OverrideRegistration +template<> +bool ReadKey(SKSESerializationInterface * intfc, BSFixedString & key, UInt32 kVersion) +{ + key = g_stringTable.ReadString(intfc, kVersion); + return false; +} + +template<> +bool ReadKey(SKSESerializationInterface * intfc, UInt32 & key, UInt32 kVersion) +{ + if(!intfc->ReadRecordData(&key, sizeof(key))) { + return true; + } + + return false; +} + +template<> +void WriteKey(SKSESerializationInterface * intfc, const BSFixedString key, UInt32 kVersion) +{ + g_stringTable.WriteString(intfc, key, kVersion); + +#ifdef _DEBUG + _MESSAGE("Saving Key %s", key.data); +#endif +} + +template<> +void WriteKey(SKSESerializationInterface * intfc, const UInt32 key, UInt32 kVersion) +{ + intfc->WriteRecordData(&key, sizeof(key)); +#ifdef _DEBUG + _MESSAGE("Saving Key %d", key); +#endif +} + +template +void OverrideRegistration::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 numNodes = this->size(); + intfc->WriteRecordData(&numNodes, sizeof(numNodes)); + + for(auto it = this->begin(); it != this->end(); ++it) + { + intfc->OpenRecord('NOEN', kVersion); + + // Key + WriteKey(intfc, it->first, kVersion); + + // Value + it->second.Save(intfc, kVersion); + } +} + +template +bool OverrideRegistration::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + // Handle count + UInt32 numRegs = 0; + if (!intfc->ReadRecordData(&numRegs, sizeof(numRegs))) + { + _MESSAGE("%s - Error loading override registration count", __FUNCTION__); + error = true; + return error; + } + + for(UInt32 i = 0; i < numRegs; i++) + { + if(intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'NOEN': + { + T key; + if(ReadKey(intfc, key, kVersion)) { + _MESSAGE("%s - Error loading node entry key", __FUNCTION__); + error = true; + return error; + } + + // operator[] not working for some odd reason + bool loadError = false; + auto iter = this->find(key); // Find existing first + if(iter != this->end()) { + error = iter->second.Load(intfc, version); + } else { // No existing, create + OverrideSet set; + error = set.Load(intfc, version); + emplace(key, set); + } + if(loadError) + { + _MESSAGE("%s - Error loading node overrides", __FUNCTION__); + error = true; + return error; + } + break; + } + default: + { + _MESSAGE("%s - Error loading unexpected chunk type %08X (%.4s)", __FUNCTION__, type, &type); + error = true; + return error; + } + } + } + } + + return error; +} + + +// AddonRegistration +void AddonRegistration::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 numRegs = this->size(); + intfc->WriteRecordData(&numRegs, sizeof(numRegs)); + + for(auto it = this->begin(); it != this->end(); ++it) { + intfc->OpenRecord('AAEN', kVersion); + + // Key + UInt64 handle = it->first; + intfc->WriteRecordData(&handle, sizeof(handle)); + +#ifdef _DEBUG + _MESSAGE("Saving ArmorAddon Handle %016llX", handle); +#endif + + // Value + it->second.Save(intfc, kVersion); + } +} + +bool AddonRegistration::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + // Handle count + UInt32 numRegs = 0; + if (!intfc->ReadRecordData(&numRegs, sizeof(numRegs))) + { + _MESSAGE("%s - Error loading Addon Registration count", __FUNCTION__); + error = true; + return error; + } + + for(UInt32 i = 0; i < numRegs; i++) + { + if(intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'AAEN': + { + UInt64 handle; + // Key + if (!intfc->ReadRecordData(&handle, sizeof(handle))) + { + _MESSAGE("%s - Error loading ArmorAddon key", __FUNCTION__); + error = true; + return error; + } + + OverrideRegistration overrideRegistration; + if (overrideRegistration.Load(intfc, version)) + { + _MESSAGE("%s - Error loading ArmorAddon override registrations", __FUNCTION__); + error = true; + return error; + } + + UInt64 newHandle = 0; + + // Skip if handle is no longer valid. + if (! intfc->ResolveHandle(handle, &newHandle)) + return false; + + if(overrideRegistration.empty()) + return false; + + emplace(newHandle, overrideRegistration); + #ifdef _DEBUG + _MESSAGE("Loaded ArmorAddon Handle %016llX", newHandle); + #endif + break; + } + default: + { + _MESSAGE("%s - Error loading unexpected chunk type %08X (%.4s)", __FUNCTION__, type, &type); + error = true; + return error; + } + } + } + } + + return error; +} + +// ArmorRegistration +void ArmorRegistration::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 numRegs = this->size(); + intfc->WriteRecordData(&numRegs, sizeof(numRegs)); + + for(auto it = this->begin(); it != this->end(); ++it) { + intfc->OpenRecord('AREN', kVersion); + + // Key + UInt64 handle = it->first; + intfc->WriteRecordData(&handle, sizeof(handle)); + +#ifdef _DEBUG + _MESSAGE("Saving Armor Handle %016llX", handle); +#endif + + // Value + it->second.Save(intfc, kVersion); + } +} + +bool ArmorRegistration::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + if(intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'AREN': + { + UInt64 handle; + // Key + if (!intfc->ReadRecordData(&handle, sizeof(handle))) + { + _MESSAGE("%s - Error loading Armor key", __FUNCTION__); + error = true; + return error; + } + + AddonRegistration addonRegistration; + if (addonRegistration.Load(intfc, version)) + { + _MESSAGE("%s - Error loading ArmorAddon registrations", __FUNCTION__); + error = true; + return error; + } + + UInt64 newHandle = 0; + + // Skip if handle is no longer valid. + if (! intfc->ResolveHandle(handle, &newHandle)) + return false; + + if(addonRegistration.empty()) + return false; + + emplace(newHandle, addonRegistration); +#ifdef _DEBUG + _MESSAGE("Loaded Armor Handle %016llX", newHandle); +#endif + + break; + } + default: + { + _MESSAGE("%s - Error loading unexpected chunk type %08X (%.4s)", __FUNCTION__, type, &type); + error = true; + return error; + } + } + } + + return error; +} + +// WeaponRegistration +void WeaponRegistration::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 numRegs = this->size(); + intfc->WriteRecordData(&numRegs, sizeof(numRegs)); + + for(auto it = this->begin(); it != this->end(); ++it) { + intfc->OpenRecord('WAEN', kVersion); + + // Key + UInt64 handle = it->first; + intfc->WriteRecordData(&handle, sizeof(handle)); + +#ifdef _DEBUG + _MESSAGE("Saving Weapon Handle %016llX", handle); +#endif + + // Value + it->second.Save(intfc, kVersion); + } +} + +bool WeaponRegistration::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + // Handle count + UInt32 numRegs = 0; + if (!intfc->ReadRecordData(&numRegs, sizeof(numRegs))) + { + _MESSAGE("%s - Error loading Weapon registration count", __FUNCTION__); + error = true; + return error; + } + + for(UInt32 i = 0; i < numRegs; i++) + { + if(intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'WAEN': + { + UInt64 handle; + // Key + if (!intfc->ReadRecordData(&handle, sizeof(handle))) + { + _MESSAGE("%s - Error loading Weapon key", __FUNCTION__); + error = true; + return error; + } + + OverrideRegistration overrideRegistration; + if (overrideRegistration.Load(intfc, version)) + { + _MESSAGE("%s - Error loading Weapon override registrations", __FUNCTION__); + error = true; + return error; + } + + UInt64 newHandle = 0; + + // Skip if handle is no longer valid. + if (! intfc->ResolveHandle(handle, &newHandle)) + return false; + + if(overrideRegistration.empty()) + return false; + + emplace(newHandle, overrideRegistration); + #ifdef _DEBUG + _MESSAGE("%s - Loaded Weapon Handle %016llX", __FUNCTION__, newHandle); + #endif + break; + } + default: + { + _MESSAGE("%s - Error loading unexpected chunk type %08X (%.4s)", __FUNCTION__, type, &type); + error = true; + return error; + } + } + } + } + + return error; +} + +// WeaponRegistration +void SkinRegistration::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 numRegs = this->size(); + intfc->WriteRecordData(&numRegs, sizeof(numRegs)); + + for (auto it = this->begin(); it != this->end(); ++it) { + intfc->OpenRecord('SKND', kVersion); + + // Key + UInt64 handle = it->first; + intfc->WriteRecordData(&handle, sizeof(handle)); + +#ifdef _DEBUG + _MESSAGE("Saving Skin Handle %016llX", handle); +#endif + + // Value + it->second.Save(intfc, kVersion); + } +} + +bool SkinRegistration::Load(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + UInt32 type, length, version; + bool error = false; + + // Handle count + UInt32 numRegs = 0; + if (!intfc->ReadRecordData(&numRegs, sizeof(numRegs))) + { + _MESSAGE("%s - Error loading skin registration count", __FUNCTION__); + error = true; + return error; + } + + for (UInt32 i = 0; i < numRegs; i++) + { + if (intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'SKND': + { + UInt32 slotMask; + // Key + if (!intfc->ReadRecordData(&slotMask, sizeof(slotMask))) + { + _MESSAGE("%s - Error loading skin slotMask", __FUNCTION__); + error = true; + return error; + } + + OverrideSet overrideSet; + if (overrideSet.Load(intfc, version)) + { + _MESSAGE("%s - Error loading skin override set", __FUNCTION__); + error = true; + return error; + } + + if (overrideSet.empty()) + return false; + + insert_or_assign(slotMask, overrideSet); +#ifdef _DEBUG + _MESSAGE("%s - Loaded Skin SlotMask %08X", __FUNCTION__, slotMask); +#endif + break; + } + default: + { + _MESSAGE("%s - Error loading unexpected chunk type %08X (%.4s)", __FUNCTION__, type, &type); + error = true; + return error; + } + } + } + } + + return error; +} + +bool NodeRegistrationMapHolder::Load(SKSESerializationInterface * intfc, UInt32 kVersion, UInt64 * outHandle) +{ + bool error = false; + + UInt64 handle = 0; + if (!intfc->ReadRecordData(&handle, sizeof(handle))) + { + _MESSAGE("%s - Error loading reg key", __FUNCTION__); + error = true; + return error; + } + + MultiRegistration,2> reg; + if (reg.Load(intfc, kVersion)) + { + _MESSAGE("%s - Error loading override gender registrations", __FUNCTION__); + error = true; + return error; + } + + UInt64 newHandle = 0; + + // Skip if handle is no longer valid. + if (!ResolveAnyHandle(intfc, handle, &newHandle)) { + *outHandle = 0; + return error; + } + + // Invalid handle + TESObjectREFR * refr = (TESObjectREFR *)g_overrideInterface.GetObject(handle, TESObjectREFR::kTypeID); + if (!refr) { + *outHandle = 0; + return error; + } + + if(reg.empty()) { + *outHandle = 0; + return error; + } + + *outHandle = newHandle; + + Lock(); + m_data[newHandle] = reg; + Release(); + return error; +} + +void NodeRegistrationMapHolder::Save(SKSESerializationInterface* intfc, UInt32 kVersion) +{ + for(auto it = m_data.begin(); it != m_data.end(); ++it) { + intfc->OpenRecord('NDEN', kVersion); + + // Key + UInt64 handle = it->first; + intfc->WriteRecordData(&handle, sizeof(handle)); + +#ifdef _DEBUG + _MESSAGE("%s - Saving Handle %016llX", __FUNCTION__, handle); +#endif + + // Value + it->second.Save(intfc, kVersion); + } +} + +void ActorRegistrationMapHolder::Save(SKSESerializationInterface* intfc, UInt32 kVersion) +{ + for(auto it = m_data.begin(); it != m_data.end(); ++it) { + intfc->OpenRecord('ACEN', kVersion); + + // Key + UInt64 handle = it->first; + intfc->WriteRecordData(&handle, sizeof(handle)); + +#ifdef _DEBUG + _MESSAGE("%s - Saving Handle %016llX", __FUNCTION__, handle); +#endif + + // Value + it->second.Save(intfc, kVersion); + } +} + +bool ActorRegistrationMapHolder::Load(SKSESerializationInterface* intfc, UInt32 kVersion, UInt64 * outHandle) +{ + bool error = false; + + UInt64 handle = 0; + // Key + if (!intfc->ReadRecordData(&handle, sizeof(handle))) + { + _MESSAGE("%s - Error loading reg key", __FUNCTION__); + error = true; + return error; + } + + MultiRegistration reg; + if (reg.Load(intfc, kVersion)) + { + _MESSAGE("%s - Error loading armor gender registrations", __FUNCTION__); + error = true; + return error; + } + + UInt64 newHandle = 0; + + // Skip if handle is no longer valid. + if (!ResolveAnyHandle(intfc, handle, &newHandle)) { + *outHandle = 0; + return error; + } + + // Invalid handle + TESObjectREFR * refr = (TESObjectREFR *)g_overrideInterface.GetObject(handle, TESObjectREFR::kTypeID); + if (!refr) { + *outHandle = 0; + return error; + } + + if(reg.empty()) { + *outHandle = 0; + return error; + } + + *outHandle = newHandle; + + Lock(); + m_data[newHandle] = reg; + Release(); + +#ifdef _DEBUG + _MESSAGE("%s - Loaded Handle %016llX", __FUNCTION__, newHandle); +#endif + + //SetHandleProperties(newHandle, false); + return error; +} + + +void WeaponRegistrationMapHolder::Save(SKSESerializationInterface* intfc, UInt32 kVersion) +{ + for(auto it = m_data.begin(); it != m_data.end(); ++it) { + intfc->OpenRecord('WPEN', kVersion); + + // Key + UInt64 handle = it->first; + intfc->WriteRecordData(&handle, sizeof(handle)); + +#ifdef _DEBUG + _MESSAGE("%s - Saving Handle %016llX", __FUNCTION__, handle); +#endif + + // Value + it->second.Save(intfc, kVersion); + } +} + +bool WeaponRegistrationMapHolder::Load(SKSESerializationInterface* intfc, UInt32 kVersion, UInt64 * outHandle) +{ + bool error = false; + + UInt64 handle = 0; + // Key + if (!intfc->ReadRecordData(&handle, sizeof(handle))) + { + _MESSAGE("%s - Error loading reg key", __FUNCTION__); + error = true; + return error; + } + + MultiRegistration,2> reg; + if (reg.Load(intfc, kVersion)) + { + _MESSAGE("%s - Error loading weapon registrations", __FUNCTION__); + error = true; + return error; + } + + UInt64 newHandle = 0; + + // Skip if handle is no longer valid. + if (!ResolveAnyHandle(intfc, handle, &newHandle)) { + *outHandle = 0; + return error; + } + + // Invalid handle + TESObjectREFR * refr = (TESObjectREFR *)g_overrideInterface.GetObject(handle, TESObjectREFR::kTypeID); + if (!refr) { + *outHandle = 0; + return error; + } + + if(reg.empty()) { + *outHandle = 0; + return error; + } + + *outHandle = newHandle; + + Lock(); + m_data[newHandle] = reg; + Release(); + +#ifdef _DEBUG + _MESSAGE("%s - Loaded Handle %016llX", __FUNCTION__, newHandle); +#endif + + //SetHandleProperties(newHandle, false); + return error; +} + + +void SkinRegistrationMapHolder::Save(SKSESerializationInterface* intfc, UInt32 kVersion) +{ + for (auto it = m_data.begin(); it != m_data.end(); ++it) { + intfc->OpenRecord('SKNR', kVersion); + + // Key + UInt64 handle = it->first; + intfc->WriteRecordData(&handle, sizeof(handle)); + +#ifdef _DEBUG + _MESSAGE("%s - Saving Handle %016llX", __FUNCTION__, handle); +#endif + + // Value + it->second.Save(intfc, kVersion); + } +} + +bool SkinRegistrationMapHolder::Load(SKSESerializationInterface* intfc, UInt32 kVersion, UInt64 * outHandle) +{ + bool error = false; + + UInt64 handle = 0; + // Key + if (!intfc->ReadRecordData(&handle, sizeof(handle))) + { + _MESSAGE("%s - Error loading reg key", __FUNCTION__); + error = true; + return error; + } + + MultiRegistration, 2> reg; + if (reg.Load(intfc, kVersion)) + { + _MESSAGE("%s - Error loading skin registrations", __FUNCTION__); + error = true; + return error; + } + + UInt64 newHandle = 0; + + // Skip if handle is no longer valid. + if (!ResolveAnyHandle(intfc, handle, &newHandle)) { + *outHandle = 0; + return error; + } + + // Invalid handle + TESObjectREFR * refr = (TESObjectREFR *)g_overrideInterface.GetObject(handle, TESObjectREFR::kTypeID); + if (!refr) { + *outHandle = 0; + return error; + } + + if (reg.empty()) { + *outHandle = 0; + return error; + } + + *outHandle = newHandle; + + Lock(); + m_data[newHandle] = reg; + Release(); + +#ifdef _DEBUG + _MESSAGE("%s - Loaded Handle %016llX", __FUNCTION__, newHandle); +#endif + + //SetHandleProperties(newHandle, false); + return error; +} + +// ActorRegistration +void OverrideInterface::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + armorData.Save(intfc, kVersion); + nodeData.Save(intfc, kVersion); + weaponData.Save(intfc, kVersion); + skinData.Save(intfc, kVersion); +} + +bool OverrideInterface::LoadWeaponOverrides(SKSESerializationInterface* intfc, UInt32 kVersion) +{ +#ifdef _DEBUG + _MESSAGE("%s - Loading Weapon Overrides...", __FUNCTION__); +#endif + bool immediate = (g_loadMode == 1) || (g_firstLoad && g_loadMode == 0); + + UInt64 handle = 0; + if(!weaponData.Load(intfc, kVersion, &handle)) + { + if(handle != 0) + SetHandleWeaponProperties(handle, immediate); + } + + return false; +} + +bool OverrideInterface::LoadOverrides(SKSESerializationInterface* intfc, UInt32 kVersion) +{ +#ifdef _DEBUG + _MESSAGE("%s - Loading Overrides...", __FUNCTION__); +#endif + bool immediate = (g_loadMode == 1) || (g_firstLoad && g_loadMode == 0); + + UInt64 handle = 0; + if(!armorData.Load(intfc, kVersion, &handle)) + { + if(handle != 0) + SetHandleProperties(handle, immediate); + } + + return false; +} + +bool OverrideInterface::LoadNodeOverrides(SKSESerializationInterface* intfc, UInt32 kVersion) +{ +#ifdef _DEBUG + _MESSAGE("%s - Loading Node Overrides...", __FUNCTION__); +#endif + bool immediate = (g_loadMode == 1) || (g_firstLoad && g_loadMode == 0); + + UInt64 handle = 0; + if(!nodeData.Load(intfc, kVersion, &handle)) + { + if(handle != 0) + SetHandleNodeProperties(handle, immediate); + } + + return false; +} + +bool OverrideInterface::LoadSkinOverrides(SKSESerializationInterface* intfc, UInt32 kVersion) +{ +#ifdef _DEBUG + _MESSAGE("%s - Loading Skin Overrides...", __FUNCTION__); +#endif + bool immediate = (g_loadMode == 1) || (g_firstLoad && g_loadMode == 0); + + UInt64 handle = 0; + if (!skinData.Load(intfc, kVersion, &handle)) + { + if (handle != 0) + SetHandleSkinProperties(handle, immediate); + } + + return false; +} + +#ifdef _DEBUG +void OverrideInterface::DumpMap() +{ + _MESSAGE("Dumping Overrides"); + for(ActorRegistrationMapHolder::RegMap::iterator it = armorData.m_data.begin(); it != armorData.m_data.end(); ++it) + { + for(UInt8 gender = 0; gender < 2; gender++) + { + _MESSAGE("Actor Handle: %016llX children %d", it->first, it->second[gender].size()); + for(ArmorRegistration::iterator ait = it->second[gender].begin(); ait != it->second[gender].end(); ++ait) // Loop Armors + { + _MESSAGE("Armor Handle: %016llX children %d", ait->first, ait->second.size()); + for(AddonRegistration::iterator dit = ait->second.begin(); dit != ait->second.end(); ++dit) // Loop Addons + { + _MESSAGE("Addon Handle: %016llX children %d", dit->first, dit->second.size()); + for(OverrideRegistration::iterator nit = dit->second.begin(); nit != dit->second.end(); ++nit) // Loop Overrides + { + _MESSAGE("Override Node: %s children %d", nit->first.data, nit->second.size()); + for(OverrideSet::iterator iter = nit->second.begin(); iter != nit->second.end(); ++iter) + { + switch((*iter).type) + { + case OverrideVariant::kType_String: + _MESSAGE("Override: Key %d Value %s", (*iter).key, (*iter).data.str); + break; + case OverrideVariant::kType_Float: + _MESSAGE("Override: Key %d Value %f", (*iter).key, (*iter).data.f); + break; + default: + _MESSAGE("Override: Key %d Value %X", (*iter).key, (*iter).data.u); + break; + } + } + } + } + } + } + } + for(NodeRegistrationMapHolder::RegMap::iterator nit = nodeData.m_data.begin(); nit != nodeData.m_data.end(); ++nit) + { + for(UInt8 gender = 0; gender < 2; gender++) + { + _MESSAGE("Node Handle: %016llX children %d", nit->first, nit->second[gender].size()); + for(OverrideRegistration::iterator oit = nit->second[gender].begin(); oit != nit->second[gender].end(); ++oit) // Loop Overrides + { + _MESSAGE("Override Node: %s children %d", oit->first.data, oit->second.size()); + for(OverrideSet::iterator iter = oit->second.begin(); iter != oit->second.end(); ++iter) + { + switch((*iter).type) + { + case OverrideVariant::kType_String: + _MESSAGE("Override: Key %d Value %s", (*iter).key, (*iter).data.str); + break; + case OverrideVariant::kType_Float: + _MESSAGE("Override: Key %d Value %f", (*iter).key, (*iter).data.f); + break; + default: + _MESSAGE("Override: Key %d Value %X", (*iter).key, (*iter).data.u); + break; + } + } + } + } + } +} +#endif \ No newline at end of file diff --git a/skee/OverrideInterface.h b/skee/OverrideInterface.h new file mode 100644 index 0000000..6ad2e46 --- /dev/null +++ b/skee/OverrideInterface.h @@ -0,0 +1,345 @@ +#pragma once + +#include "IPluginInterface.h" +#include "IHashType.h" + +#include "skse64/GameTypes.h" +#include "skse64/NiTypes.h" +#include +#include +#include +#include + +class TESObjectREFR; +class TESObjectARMO; +class TESObjectARMA; +class TESObjectWEAP; +class NiAVObject; +struct SKSESerializationInterface; +class NiGeometry; +class BGSTextureSet; +class OverrideVariant; + +class OverrideSet : public std::set +{ +public: + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); + + virtual void Visit(std::function functor); +}; + +template +bool ReadKey(SKSESerializationInterface * intfc, T & key, UInt32 kVersion); + +template +void WriteKey(SKSESerializationInterface * intfc, const T key, UInt32 kVersion); + +template +class OverrideRegistration : public std::unordered_map +{ +public: + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); + + virtual void Visit(std::function functor); +}; + +class AddonRegistration : public std::unordered_map> +{ +public: + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); +}; + +class ArmorRegistration : public std::unordered_map +{ +public: + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); +}; + +class WeaponRegistration : public std::unordered_map> +{ +public: + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); +}; + +class SkinRegistration : public std::unordered_map +{ +public: + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); +}; + +template +class MultiRegistration +{ +public: + // Serialization + + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion) + { + bool error = false; + + UInt8 size = 0; + if (!intfc->ReadRecordData(&size, sizeof(size))) + { + _MESSAGE("%s - Error loading multi-registrations", __FUNCTION__); + error = true; + return error; + } + + for (UInt32 i = 0; i < size; i++) + { + UInt8 index = 0; + if (!intfc->ReadRecordData(&index, sizeof(index))) + { + _MESSAGE("%s - Error loading multi-registration index (%d/%d)", __FUNCTION__, i + 1, size); + error = true; + return error; + } + + T regs; + if (regs.Load(intfc, kVersion)) + { + _MESSAGE("%s - Error loading multi-registrations (%d/%d)", __FUNCTION__, i + 1, size); + error = true; + return error; + } + + table[index] = regs; + +#ifdef _DEBUG + _MESSAGE("%s - Loaded multi-reg (%d)", __FUNCTION__, index); +#endif + } + + return error; + } + + void Save(SKSESerializationInterface * intfc, UInt32 kVersion) + { + UInt8 size = 0; + for (UInt8 i = 0; i < N; i++) + { + if (!table[i].empty()) + size++; + } + + intfc->WriteRecordData(&size, sizeof(size)); + + for (UInt8 i = 0; i < N; i++) + { + if (!table[i].empty()) + { +#ifdef _DEBUG + _MESSAGE("%s - Saving Multi-Reg %d", __FUNCTION__, i); +#endif + intfc->WriteRecordData(&i, sizeof(i)); + table[i].Save(intfc, kVersion); + } + } + } + + T& operator[] (const int index) + { + if(index > N-1) + return table[0]; + + return table[index]; + } + + bool empty() + { + UInt8 emptyCount = 0; + for(UInt8 i = 0; i < N; i++) + { + if(table[i].empty()) + emptyCount++; + } + return emptyCount == N; + } + + T table[N]; +}; + +class ActorRegistrationMapHolder : public SafeDataHolder>> +{ +public: + typedef std::unordered_map> RegMap; + + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion, UInt64 * outHandle); + + friend class OverrideInterface; +}; + +class NodeRegistrationMapHolder : public SafeDataHolder, 2>>> +{ +public: + typedef std::unordered_map, 2>> RegMap; + + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion, UInt64 * outHandle); + + friend class OverrideInterface; +}; + +class WeaponRegistrationMapHolder : public SafeDataHolder, 2>>> +{ +public: + typedef std::unordered_map, 2>> RegMap; + + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion, UInt64 * outHandle); + + friend class OverrideInterface; +}; + +class SkinRegistrationMapHolder : public SafeDataHolder, 2>>> +{ +public: + typedef std::unordered_map, 2>> RegMap; + + // Serialization + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion, UInt64 * outHandle); + + friend class OverrideInterface; +}; + +class OverrideInterface : public IPluginInterface +{ +public: + enum + { + kCurrentPluginVersion = 1, + kSerializationVersion1 = 1, + kSerializationVersion2 = 2, + kSerializationVersion = kSerializationVersion2 + }; + virtual UInt32 GetVersion(); + + virtual void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + virtual bool Load(SKSESerializationInterface * intfc, UInt32 kVersion) { return false; }; + virtual void Revert(); + + virtual bool LoadOverrides(SKSESerializationInterface* intfc, UInt32 kVersion); + virtual bool LoadNodeOverrides(SKSESerializationInterface* intfc, UInt32 kVersion); + virtual bool LoadWeaponOverrides(SKSESerializationInterface* intfc, UInt32 kVersion); + + // Specific overrides + virtual void AddRawOverride(UInt64 handle, bool isFemale, UInt64 armorHandle, UInt64 addonHandle, BSFixedString nodeName, OverrideVariant & value); + virtual void AddOverride(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, OverrideVariant & value); + + // Non-specific overrides + virtual void AddRawNodeOverride(UInt64 handle, bool isFemale, BSFixedString nodeName, OverrideVariant & value); + virtual void AddNodeOverride(TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, OverrideVariant & value); + + UInt64 GetHandle(void * src, UInt32 typeID); + void * GetObject(UInt64 handle, UInt32 typeID); + + // Applies all properties for a handle + void SetHandleProperties(UInt64 handle, bool immediate); + + // Applies all properties for an armor + //void SetHandleArmorAddonProperties(UInt64 handle, UInt64 armorHandle, UInt64 addonHandle, bool immediate); + + // Applies node properties for a handle + void SetHandleNodeProperties(UInt64 handle, bool immediate); + + // Set/Get a single property + virtual void SetArmorAddonProperty(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, OverrideVariant * value, bool immediate); + virtual void GetArmorAddonProperty(TESObjectREFR * refr, bool firstPerson, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, OverrideVariant * value); + + // Applies a single node property + virtual void SetNodeProperty(TESObjectREFR * refr, BSFixedString nodeName, OverrideVariant * value, bool immediate); + virtual void GetNodeProperty(TESObjectREFR * refr, bool firstPerson, BSFixedString nodeName, OverrideVariant * value); + + // Determines whether the node could be found + virtual bool HasArmorAddonNode(TESObjectREFR * refr, bool firstPerson, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, bool debug); + + // Applies all node overrides to a particular node + virtual void ApplyNodeOverrides(TESObjectREFR * refr, NiAVObject * object, bool immediate); + + // Applies all armor overrides to a particular armor + virtual void ApplyOverrides(TESObjectREFR * refr, TESObjectARMO * armor, TESObjectARMA * addon, NiAVObject * object, bool immediate); + + virtual void RemoveAllOverrides(); + virtual void RemoveAllReferenceOverrides(TESObjectREFR * reference); + void RemoveAllReferenceOverrides(UInt64 handle); + + virtual void RemoveAllArmorOverrides(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor); + virtual void RemoveAllArmorAddonOverrides(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon); + virtual void RemoveAllArmorAddonNodeOverrides(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName); + virtual void RemoveArmorAddonOverride(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt16 key, UInt8 index); + + virtual void RemoveAllNodeOverrides(); + virtual void RemoveAllReferenceNodeOverrides(TESObjectREFR * reference); + void RemoveAllReferenceNodeOverrides(UInt64 handle); + + virtual void RemoveAllNodeNameOverrides(TESObjectREFR * refr, bool isFemale, BSFixedString nodeName); + virtual void RemoveNodeOverride(TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt16 key, UInt8 index); + + virtual OverrideVariant * GetOverride(TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt16 key, UInt8 index); + virtual OverrideVariant * GetNodeOverride(TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt16 key, UInt8 index); + + virtual void AddRawWeaponOverride(UInt64 handle, bool isFemale, bool firstPerson, UInt64 weaponHandle, BSFixedString nodeName, OverrideVariant & value); + virtual void AddWeaponOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, OverrideVariant & value); + virtual OverrideVariant * GetWeaponOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt16 key, UInt8 index); + virtual void ApplyWeaponOverrides(TESObjectREFR * refr, bool firstPerson, TESObjectWEAP * weapon, NiAVObject * object, bool immediate); + + virtual void RemoveAllWeaponBasedOverrides(); + virtual void RemoveAllReferenceWeaponOverrides(TESObjectREFR * reference); + void RemoveAllReferenceWeaponOverrides(UInt64 handle); + + virtual void RemoveAllWeaponOverrides(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon); + virtual void RemoveAllWeaponNodeOverrides(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName); + virtual void RemoveWeaponOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt16 key, UInt8 index); + + virtual bool HasWeaponNode(TESObjectREFR * refr, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, bool debug); + virtual void SetHandleWeaponProperties(UInt64 handle, bool immediate); + + virtual void SetWeaponProperty(TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, OverrideVariant * value, bool immediate); + virtual void GetWeaponProperty(TESObjectREFR * refr, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, OverrideVariant * value); + + // Skin API + virtual bool LoadSkinOverrides(SKSESerializationInterface* intfc, UInt32 kVersion); + virtual void AddRawSkinOverride(UInt64 handle, bool isFemale, bool firstPerson, UInt32 slotMask, OverrideVariant & value); + virtual void AddSkinOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, OverrideVariant & value); + virtual OverrideVariant * GetSkinOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, UInt16 key, UInt8 index); + virtual void ApplySkinOverrides(TESObjectREFR * refr, bool firstPerson, TESObjectARMO * armor, TESObjectARMA * addon, UInt32 slotMask, NiAVObject * object, bool immediate); + virtual void RemoveAllSkinOverrides(TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask); + virtual void RemoveSkinOverride(TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, UInt16 key, UInt8 index); + virtual void SetHandleSkinProperties(UInt64 handle, bool immediate); + virtual void RemoveAllSkinBasedOverrides(); + virtual void RemoveAllReferenceSkinOverrides(TESObjectREFR * reference); + void RemoveAllReferenceSkinOverrides(UInt64 handle); + virtual void SetSkinProperty(TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, OverrideVariant * value, bool immediate); + virtual void GetSkinProperty(TESObjectREFR * refr, bool firstPerson, UInt32 slotMask, OverrideVariant * value); + + + virtual void VisitNodes(TESObjectREFR * refr, std::function functor); + virtual void VisitSkin(TESObjectREFR * refr, bool isFemale, bool firstPerson, std::function functor); + virtual void VisitStrings(std::function functor); + +#ifdef _DEBUG + void DumpMap(); +#endif +private: + ActorRegistrationMapHolder armorData; + NodeRegistrationMapHolder nodeData; + WeaponRegistrationMapHolder weaponData; + SkinRegistrationMapHolder skinData; +}; \ No newline at end of file diff --git a/skee/OverrideVariant.cpp b/skee/OverrideVariant.cpp new file mode 100644 index 0000000..b891146 --- /dev/null +++ b/skee/OverrideVariant.cpp @@ -0,0 +1,267 @@ +#include "OverrideVariant.h" + +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, float * src) +{ + switch (key) + { + case OverrideVariant::kParam_ShaderEmissiveMultiple: + case OverrideVariant::kParam_ShaderGlossiness: + case OverrideVariant::kParam_ShaderSpecularStrength: + case OverrideVariant::kParam_ShaderLightingEffect1: + case OverrideVariant::kParam_ShaderLightingEffect2: + case OverrideVariant::kParam_ShaderAlpha: + case OverrideVariant::kParam_ControllerStartStop: + case OverrideVariant::kParam_ControllerStartTime: + case OverrideVariant::kParam_ControllerStopTime: + case OverrideVariant::kParam_ControllerFrequency: + case OverrideVariant::kParam_ControllerPhase: + case OverrideVariant::kParam_NodeTransformPosition: + case OverrideVariant::kParam_NodeTransformRotation: + case OverrideVariant::kParam_NodeTransformScale: + dst->SetFloat(key, index, *src); + break; + default: + dst->SetNone(); + break; + } +} +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, UInt32 * src) +{ + switch (key) + { + case OverrideVariant::kParam_ShaderEmissiveColor: + case OverrideVariant::kParam_ShaderTintColor: + dst->SetInt(key, index, *src); + break; + default: + dst->SetNone(); + break; + } +} +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, SInt32 * src) +{ + switch (key) + { + case OverrideVariant::kParam_ShaderEmissiveColor: + case OverrideVariant::kParam_ShaderTintColor: + dst->SetInt(key, index, *src); + break; + default: + dst->SetNone(); + break; + } +} +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, bool * src) +{ + dst->SetNone(); + //dst->SetBool(key, index, *src); +} +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, BSFixedString * src) +{ + switch (key) + { + case OverrideVariant::kParam_ShaderTexture: + dst->SetString(key, index, src->data); + break; + case OverrideVariant::kParam_NodeDestination: + dst->SetString(key, index, src->data); + break; + default: + dst->SetNone(); + break; + } +} +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, NiColor * src) +{ + switch (key) + { + case OverrideVariant::kParam_ShaderEmissiveColor: + case OverrideVariant::kParam_ShaderTintColor: + dst->SetColor(key, index, *src); + break; + default: + dst->SetNone(); + break; + } +} +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, NiColorA * src) +{ + switch (key) + { + case OverrideVariant::kParam_ShaderEmissiveColor: + dst->SetColorA(key, index, *src); + break; + default: + dst->SetNone(); + break; + } +} +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, BGSTextureSet ** src) +{ + switch (key) + { + case OverrideVariant::kParam_ShaderTextureSet: + dst->SetIdentifier(key, index, (void*)*src); + break; + default: + dst->SetNone(); + break; + } +} + +template <> void UnpackValue (float * dst, OverrideVariant * src) +{ + switch (src->type) + { + case OverrideVariant::kType_Int: + *dst = src->data.i; + break; + + case OverrideVariant::kType_Float: + *dst = src->data.f; + break; + + case OverrideVariant::kType_Bool: + *dst = src->data.b; + break; + + default: + *dst = 0; + break; + } +} + +template <> void UnpackValue (UInt32 * dst, OverrideVariant * src) +{ + switch (src->type) + { + case OverrideVariant::kType_Int: + *dst = src->data.u; + break; + + case OverrideVariant::kType_Float: + *dst = src->data.f; + break; + + case OverrideVariant::kType_Bool: + *dst = src->data.b; + break; + + default: + *dst = 0; + break; + } +} + +template <> void UnpackValue (SInt32 * dst, OverrideVariant * src) +{ + switch (src->type) + { + case OverrideVariant::kType_Int: + *dst = src->data.u; + break; + + case OverrideVariant::kType_Float: + *dst = src->data.f; + break; + + case OverrideVariant::kType_Bool: + *dst = src->data.b; + break; + + default: + *dst = 0; + break; + } +} + +template <> void UnpackValue (bool * dst, OverrideVariant * src) +{ + switch (src->type) + { + case OverrideVariant::kType_Int: + *dst = src->data.u != 0; + break; + + case OverrideVariant::kType_Float: + *dst = src->data.f != 0; + break; + + case OverrideVariant::kType_Bool: + *dst = src->data.b; + break; + + default: + *dst = 0; + break; + } +} + +template <> void UnpackValue (BSFixedString * dst, OverrideVariant * src) +{ + switch (src->type) + { + case OverrideVariant::kType_String: + CALL_MEMBER_FN(dst, Set)(src->data.str); + break; + default: + break; + } +} + +template <> void UnpackValue (NiColor * dst, OverrideVariant * src) +{ + switch (src->type) + { + case OverrideVariant::kType_Int: + dst->r = ((src->data.u >> 16) & 0xFF) / 255.0; + dst->g = ((src->data.u >> 8) & 0xFF) / 255.0; + dst->b = ((src->data.u) & 0xFF) / 255.0; + break; + + default: + dst->r = 0; + dst->g = 0; + dst->b = 0; + break; + } +} + +template <> void UnpackValue (NiColorA * dst, OverrideVariant * src) +{ + switch (src->type) + { + case OverrideVariant::kType_Int: + dst->a = ((src->data.u >> 24) & 0xFF) / 255.0; + dst->r = ((src->data.u >> 16) & 0xFF) / 255.0; + dst->g = ((src->data.u >> 8) & 0xFF) / 255.0; + dst->b = ((src->data.u) & 0xFF) / 255.0; + break; + + default: + dst->r = 0; + dst->g = 0; + dst->b = 0; + dst->a = 0; + break; + } +} + +template <> void UnpackValue (BGSTextureSet ** dst, OverrideVariant * src) +{ + switch (src->type) + { + case OverrideVariant::kType_Identifier: + *dst = (BGSTextureSet*)src->data.p; + break; + + default: + *dst = NULL; + break; + } +} + +bool OverrideVariant::IsIndexValid(UInt16 key) +{ + return (key >= OverrideVariant::kParam_ControllersStart && key <= OverrideVariant::kParam_ControllersEnd) || key == OverrideVariant::kParam_ShaderTexture || (key >= OverrideVariant::kParam_NodeTransformStart && key <= OverrideVariant::kParam_NodeTransformEnd); +} \ No newline at end of file diff --git a/skee/OverrideVariant.h b/skee/OverrideVariant.h new file mode 100644 index 0000000..0cd9669 --- /dev/null +++ b/skee/OverrideVariant.h @@ -0,0 +1,167 @@ +#pragma once + +#include "skse64/GameTypes.h" +#include "skse64/NiTypes.h" + +struct SKSESerializationInterface; +class BGSTextureSet; + +class OverrideVariant +{ +public: + OverrideVariant() : type(kType_None), index(-1) { }; + ~OverrideVariant() { }; + + bool operator<(const OverrideVariant & rhs) const { return key < rhs.key || (key == rhs.key && index < rhs.index); } + bool operator==(const OverrideVariant & rhs) const { return key == rhs.key && index == rhs.index; } + + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface * intfc, UInt32 kVersion); + + UInt16 key; + enum + { + kKeyMax = 0xFFFF, + kIndexMax = 0xFF + }; + enum + { + kParam_ShaderEmissiveColor = 0, + kParam_ShaderEmissiveMultiple, + kParam_ShaderGlossiness, + kParam_ShaderSpecularStrength, + kParam_ShaderLightingEffect1, + kParam_ShaderLightingEffect2, + kParam_ShaderTextureSet, + kParam_ShaderTintColor, + kParam_ShaderAlpha, + kParam_ShaderTexture, + + kParam_ControllersStart = 20, + kParam_ControllerStartStop = kParam_ControllersStart, + kParam_ControllerStartTime, + kParam_ControllerStopTime, + kParam_ControllerFrequency, + kParam_ControllerPhase, + kParam_ControllersEnd = kParam_ControllerPhase, + + kParam_NodeTransformStart = 30, + kParam_NodeTransformScale = kParam_NodeTransformStart, + kParam_NodeTransformPosition, + kParam_NodeTransformRotation, + kParam_NodeTransformEnd = kParam_NodeTransformRotation, + + kParam_NodeDestination = 40 + }; + enum + { + kType_None = 0, + kType_Identifier = 1, + kType_String = 2, + kType_Int = 3, + kType_Float = 4, + kType_Bool = 5 + }; + + UInt8 type; + SInt8 index; + union + { + SInt32 i; + UInt32 u; + float f; + bool b; + void * p; + const char * str; // BSFixedString + BSFixedString * GetStr(void) { return (BSFixedString *)(&str); } + } data; + + void SetNone(void) + { + type = kType_None; + index = -1; + data.u = 0; + } + + void SetInt(UInt16 paramKey, SInt8 controllerIndex, SInt32 i) + { + key = paramKey; + type = kType_Int; + index = controllerIndex; + data.i = i; + } + + void SetFloat(UInt16 paramKey, SInt8 controllerIndex, float f) + { + key = paramKey; + type = kType_Float; + index = controllerIndex; + data.f = f; + } + + void SetBool(UInt16 paramKey, SInt8 controllerIndex, bool b) + { + key = paramKey; + type = kType_Bool; + index = controllerIndex; + data.b = b; + } + + void SetString(UInt16 paramKey, SInt8 controllerIndex, BSFixedString str) + { + key = paramKey; + type = kType_String; + index = controllerIndex; + data.str = str.data; + } + + void SetColor(UInt16 paramKey, SInt8 controllerIndex, NiColor color) + { + key = paramKey; + type = kType_Int; + index = controllerIndex; + data.u = (UInt8)(color.r * 255) << 16 | (UInt8)(color.g * 255) << 8 | (UInt8)(color.b * 255); + } + + void SetColorA(UInt16 paramKey, SInt8 controllerIndex, NiColorA color) + { + key = paramKey; + type = kType_Int; + index = controllerIndex; + data.u = (UInt8)(color.a * 255) << 24 | (UInt8)(color.r * 255) << 16 | (UInt8)(color.g * 255) << 8 | (UInt8)(color.b * 255); + } + + void SetIdentifier(UInt16 paramKey, SInt8 controllerIndex, void * ptr) + { + key = paramKey; + type = kType_Identifier; + index = controllerIndex; + data.p = ptr; + } + + static bool IsIndexValid(UInt16 key); +}; + +template +void UnpackValue(T * dst, OverrideVariant * src); + +template <> void UnpackValue (float * dst, OverrideVariant * src); +template <> void UnpackValue (UInt32 * dst, OverrideVariant * src); +template <> void UnpackValue (SInt32 * dst, OverrideVariant * src); +template <> void UnpackValue (bool * dst, OverrideVariant * src); +template <> void UnpackValue (BSFixedString * dst, OverrideVariant * src); +template <> void UnpackValue (NiColor * dst, OverrideVariant * src); +template <> void UnpackValue (NiColorA * dst, OverrideVariant * src); +template <> void UnpackValue (BGSTextureSet ** dst, OverrideVariant * src); + +template +void PackValue(OverrideVariant * dst, UInt16 key, UInt8 index, T * src); + +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, float * src); +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, UInt32 * src); +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, SInt32 * src); +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, bool * src); +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, BSFixedString * src); +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, NiColor * src); +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, NiColorA * src); +template <> void PackValue (OverrideVariant * dst, UInt16 key, UInt8 index, BGSTextureSet ** src); \ No newline at end of file diff --git a/skee/PapyrusCharGen.cpp b/skee/PapyrusCharGen.cpp new file mode 100644 index 0000000..29191f1 --- /dev/null +++ b/skee/PapyrusCharGen.cpp @@ -0,0 +1,551 @@ +#include "PapyrusCharGen.h" +#include "MorphHandler.h" +#include "NifUtils.h" + +#include "common/IFileStream.h" + +#include "skse64/PluginAPI.h" + +#include "skse64/GameFormComponents.h" +#include "skse64/GameData.h" +#include "skse64/GameRTTI.h" +#include "skse64/GameForms.h" +#include "skse64/GameObjects.h" +#include "skse64/GameReferences.h" + +#include "skse64/NiGeometry.h" +#include "skse64/NiNodes.h" +#include "skse64/NiMaterial.h" +#include "skse64/NiProperties.h" + +#include "skse64/PapyrusVM.h" +#include "skse64/PapyrusNativeFunctions.h" + +#include "common/ICriticalSection.h" + +#include "OverrideVariant.h" +#include "OverrideInterface.h" +#include "NiTransformInterface.h" +#include "BodyMorphInterface.h" +#include "OverlayInterface.h" + +extern MorphHandler g_morphHandler; +extern bool g_externalHeads; + +extern SKSETaskInterface * g_task; +extern OverrideInterface g_overrideInterface; +extern NiTransformInterface g_transformInterface; +extern BodyMorphInterface g_bodyMorphInterface; +extern OverlayInterface g_overlayInterface; + +class SKSETaskExportTintMask : public TaskDelegate +{ + virtual void Run() + { + BSFaceGenNiNode * faceNode = (*g_thePlayer)->GetFaceGenNiNode(); + if(faceNode) { + // Save the mesh + std::string ddsPath(m_filePath.data); + IFileStream::MakeAllDirs(ddsPath.c_str()); + ddsPath.append(m_fileName.data); + ddsPath.append(".dds"); + + PlayerCharacter * player = (*g_thePlayer); + ExportTintMaskDDS(player, ddsPath.c_str()); + } + } + + virtual void Dispose() + { + delete this; + } + +public: + SKSETaskExportTintMask::SKSETaskExportTintMask(BSFixedString filePath, BSFixedString fileName) : m_filePath(filePath), m_fileName(fileName) {}; + + BSFixedString m_filePath; + BSFixedString m_fileName; +}; + +void ApplyPreset(Actor * actor, TESRace * race, TESNPC * npc, PresetDataPtr presetData, MorphHandler::ApplyTypes applyType) +{ + CALL_MEMBER_FN(actor, SetRace)(race, actor == (*g_thePlayer)); + + npc->overlayRace = NULL; + npc->weight = presetData->weight; + + if (!npc->faceMorph) + npc->faceMorph = (TESNPC::FaceMorphs*)Heap_Allocate(sizeof(TESNPC::FaceMorphs)); + + UInt32 i = 0; + for (auto & preset : presetData->presets) { + npc->faceMorph->presets[i] = preset; + i++; + } + + i = 0; + for (auto & morph : presetData->morphs) { + npc->faceMorph->option[i] = morph; + i++; + } + + // Sculpt data loaded here + g_morphHandler.EraseSculptData(npc); + if (presetData->sculptData) { + if (presetData->sculptData->size() > 0) + g_morphHandler.SetSculptTarget(npc, presetData->sculptData); + } + + // Assign custom morphs here (values only) + g_morphHandler.EraseMorphData(npc); + for (auto & it : presetData->customMorphs) + g_morphHandler.SetMorphValue(npc, it.name, it.value); + + // Wipe the HeadPart list and replace it with the default race list + UInt8 gender = CALL_MEMBER_FN(npc, GetSex)(); + TESRace::CharGenData * chargenData = race->chargenData[gender]; + if (chargenData) { + BGSHeadPart ** headParts = npc->headparts; + tArray * headPartList = race->chargenData[gender]->headParts; + if (headParts && headPartList) { + Heap_Free(headParts); + npc->numHeadParts = headPartList->count; + headParts = (BGSHeadPart **)Heap_Allocate(npc->numHeadParts * sizeof(BGSHeadPart*)); + for (UInt32 i = 0; i < headPartList->count; i++) + headPartList->GetNthItem(i, headParts[i]); + npc->headparts = headParts; + } + } + + // Force the remaining parts to change to that of the preset + for (auto part : presetData->headParts) + CALL_MEMBER_FN(npc, ChangeHeadPart)(part); + + //npc->MarkChanged(0x2000800); // Save FaceData and Race + npc->MarkChanged(0x800); // Save FaceData + + // Grab the skin tint and convert it from RGBA to RGB on NPC + if (presetData->tints.size() > 0) { + PresetData::Tint & tint = presetData->tints.at(0); + float alpha = (tint.color >> 24) / 255.0; + TintMask tintMask; + tintMask.color.red = (tint.color >> 16) & 0xFF; + tintMask.color.green = (tint.color >> 8) & 0xFF; + tintMask.color.blue = tint.color & 0xFF; + tintMask.alpha = alpha; + tintMask.tintType = TintMask::kMaskType_SkinTone; + + NiColorA colorResult; + CALL_MEMBER_FN(npc, SetSkinFromTint)(&colorResult, &tintMask, 1, 0); + } + + // Queue a node update + CALL_MEMBER_FN(actor, QueueNiNodeUpdate)(true); + + if ((applyType & MorphHandler::ApplyTypes::kPresetApplyOverrides) == MorphHandler::ApplyTypes::kPresetApplyOverrides) { + g_overrideInterface.RemoveAllReferenceNodeOverrides(actor); + + g_overlayInterface.RevertOverlays(actor, true); + + if (!g_overlayInterface.HasOverlays(actor) && presetData->overrideData.size() > 0) + g_overlayInterface.AddOverlays(actor); + + for (auto & nodes : presetData->overrideData) { + for (auto & value : nodes.second) { + g_overrideInterface.AddNodeOverride(actor, gender == 1 ? true : false, nodes.first, value); + } + } + } + + if ((applyType & MorphHandler::ApplyTypes::kPresetApplySkinOverrides) == MorphHandler::ApplyTypes::kPresetApplySkinOverrides) + { + for (UInt32 i = 0; i <= 1; i++) { + for (auto & slot : presetData->skinData[i]) { + for (auto & value : slot.second) { + g_overrideInterface.AddSkinOverride(actor, gender == 1 ? true : false, i == 1 ? true : false, slot.first, value); + } + } + } + } + + if ((applyType & MorphHandler::ApplyTypes::kPresetApplyTransforms) == MorphHandler::ApplyTypes::kPresetApplyTransforms) { + g_transformInterface.RemoveAllReferenceTransforms(actor); + for (UInt32 i = 0; i <= 1; i++) { + for (auto & xForms : presetData->transformData[i]) { + for (auto & key : xForms.second) { + for (auto & value : key.second) { + g_transformInterface.AddNodeTransform(actor, i == 1 ? true : false, gender == 1 ? true : false, xForms.first, key.first, value); + } + } + } + } + g_transformInterface.UpdateNodeAllTransforms(actor); + } + + if ((applyType & MorphHandler::ApplyTypes::kPresetApplyBodyMorphs) == MorphHandler::ApplyTypes::kPresetApplyBodyMorphs) { + g_bodyMorphInterface.ClearMorphs(actor); + + for (auto & morph : presetData->bodyMorphData) { + for (auto & keys : morph.second) + g_bodyMorphInterface.SetMorph(actor, morph.first, keys.first, keys.second); + } + + g_bodyMorphInterface.UpdateModelWeight(actor); + } +} + +namespace papyrusCharGen +{ + void SaveCharacter(StaticFunctionTag*, BSFixedString fileName) + { + char slotPath[MAX_PATH]; + sprintf_s(slotPath, "Data\\SKSE\\Plugins\\CharGen\\Exported\\%s.jslot", fileName.data); + char tintPath[MAX_PATH]; + sprintf_s(tintPath, "Data\\Textures\\CharGen\\Exported\\"); + + g_morphHandler.SaveJsonPreset(slotPath); + g_task->AddTask(new SKSETaskExportTintMask(tintPath, fileName.data)); + } + + void DeleteCharacter(StaticFunctionTag*, BSFixedString fileName) + { + char tempPath[MAX_PATH]; + sprintf_s(tempPath, "Data\\SKSE\\Plugins\\CharGen\\Exported\\%s.slot", fileName.data); + if (!DeleteFile(tempPath)) { + UInt32 lastError = GetLastError(); + switch (lastError) { + case ERROR_ACCESS_DENIED: + _ERROR("%s - access denied could not delete %s", __FUNCTION__, tempPath); + break; + } + } + sprintf_s(tempPath, "Data\\SKSE\\Plugins\\CharGen\\Exported\\%s.jslot", fileName.data); + if (!DeleteFile(tempPath)) { + UInt32 lastError = GetLastError(); + switch (lastError) { + case ERROR_ACCESS_DENIED: + _ERROR("%s - access denied could not delete %s", __FUNCTION__, tempPath); + break; + } + } + sprintf_s(tempPath, "Data\\Textures\\CharGen\\Exported\\%s.dds", fileName.data); + if (!DeleteFile(tempPath)) { + UInt32 lastError = GetLastError(); + switch (lastError) { + case ERROR_ACCESS_DENIED: + _ERROR("%s - access denied could not delete %s", __FUNCTION__, tempPath); + break; + } + } + sprintf_s(tempPath, "Data\\Meshes\\CharGen\\Exported\\%s.nif", fileName.data); + if (!DeleteFile(tempPath)) { + UInt32 lastError = GetLastError(); + switch (lastError) { + case ERROR_ACCESS_DENIED: + _ERROR("%s - access denied could not delete %s", __FUNCTION__, tempPath); + break; + } + } + } + + SInt32 DeleteFaceGenData(StaticFunctionTag*, TESNPC * npc) + { + SInt32 ret = 0; + if (!npc) { + _ERROR("%s - invalid actorbase.", __FUNCTION__); + return -1; + } + + char * modName = NULL; + UInt8 modIndex = npc->formID >> 24; + UInt32 modForm = (npc->formID & 0xFFFFFF); + DataHandler * dataHandler = DataHandler::GetSingleton(); + if (dataHandler) { + ModInfo * modInfo = dataHandler->modList.modInfoList.GetNthItem(modIndex); + if (modInfo) + modName = modInfo->name; + } + + enum + { + kReturnDeletedNif = 1, + kReturnDeletedDDS = 2 + }; + + char tempPath[MAX_PATH]; + sprintf_s(tempPath, "Data\\Meshes\\Actors\\Character\\FaceGenData\\FaceGeom\\%s\\%08X.nif", modName, modForm); + if (!DeleteFile(tempPath)) { + UInt32 lastError = GetLastError(); + switch (lastError) { + case ERROR_FILE_NOT_FOUND: // We don't need to display a message for this + break; + case ERROR_ACCESS_DENIED: + _ERROR("%s - access denied could not delete %s", __FUNCTION__, tempPath); + break; + default: + _ERROR("%s - error deleting file %s (Error %d)", __FUNCTION__, tempPath, lastError); + break; + } + } + else + ret |= kReturnDeletedNif; + + sprintf_s(tempPath, "Data\\Textures\\Actors\\Character\\FaceGenData\\FaceTint\\%s\\%08X.dds", modName, modForm); + if (!DeleteFile(tempPath)) { + UInt32 lastError = GetLastError(); + switch (lastError) { + case ERROR_FILE_NOT_FOUND: // We don't need to display a message for this + break; + case ERROR_ACCESS_DENIED: + _ERROR("%s - access denied could not delete %s", __FUNCTION__, tempPath); + break; + default: + _ERROR("%s - error deleting file %s (Error %d)", __FUNCTION__, tempPath, lastError); + break; + } + } + else + ret |= kReturnDeletedDDS; + + return ret; + } + + bool LoadCharacterEx(StaticFunctionTag*, Actor * actor, TESRace * race, BSFixedString fileName, UInt32 flags) + { + if (!actor) { + _ERROR("%s - No actor found.", __FUNCTION__); + return false; + } + if (!race) { + _ERROR("%s - No race found.", __FUNCTION__); + return false; + } + TESNPC * npc = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (!npc) { + _ERROR("%s - failed acquire ActorBase.", __FUNCTION__); + return false; + } + + char slotPath[MAX_PATH]; + sprintf_s(slotPath, "SKSE\\Plugins\\CharGen\\Exported\\%s.jslot", fileName.data); + char tintPath[MAX_PATH]; + sprintf_s(tintPath, "Textures\\CharGen\\Exported\\%s.dds", fileName.data); + + auto presetData = std::make_shared(); + bool loadError = g_morphHandler.LoadJsonPreset(slotPath, presetData); + if (loadError) { + sprintf_s(slotPath, "SKSE\\Plugins\\CharGen\\Exported\\%s.slot", fileName.data); + loadError = g_morphHandler.LoadBinaryPreset(slotPath, presetData); + } + + if (loadError) { + _ERROR("%s - failed to load preset.", __FUNCTION__); + return false; + } + + presetData->tintTexture = tintPath; + g_morphHandler.AssignPreset(npc, presetData); + + ApplyPreset(actor, race, npc, presetData, (MorphHandler::ApplyTypes)flags); + return true; + } + + void SaveExternalCharacter(StaticFunctionTag*, BSFixedString fileName) + { + char slotPath[MAX_PATH]; + sprintf_s(slotPath, "Data\\SKSE\\Plugins\\CharGen\\Exported\\%s.jslot", fileName.data); + char nifPath[MAX_PATH]; + sprintf_s(nifPath, "Data\\Meshes\\CharGen\\Exported\\%s.nif", fileName.data); + char tintPath[MAX_PATH]; + sprintf_s(tintPath, "Data\\Textures\\CharGen\\Exported\\%s.dds", fileName.data); + + g_morphHandler.SaveJsonPreset(slotPath); + g_task->AddTask(new SKSETaskExportHead((*g_thePlayer), nifPath, tintPath)); + } + + bool LoadExternalCharacterEx(StaticFunctionTag*, Actor * actor, TESRace * race, BSFixedString fileName, UInt32 flags) + { + if (!actor) { + _ERROR("%s - No actor found.", __FUNCTION__); + return false; + } + if (!race) { + _ERROR("%s - No race found.", __FUNCTION__); + return false; + } + TESNPC * npc = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (!npc) { + _ERROR("%s - failed acquire ActorBase.", __FUNCTION__); + return false; + } + + g_morphHandler.ErasePreset(npc); + + char slotPath[MAX_PATH]; + sprintf_s(slotPath, "SKSE\\Plugins\\CharGen\\Exported\\%s.jslot", fileName.data); + + auto presetData = std::make_shared(); + bool loadError = g_morphHandler.LoadJsonPreset(slotPath, presetData); + if (loadError) { + sprintf_s(slotPath, "SKSE\\Plugins\\CharGen\\Exported\\%s.slot", fileName.data); + loadError = g_morphHandler.LoadBinaryPreset(slotPath, presetData); + } + + if (loadError) { + _ERROR("%s - failed to load preset.", __FUNCTION__); + return false; + } + + char * modName = NULL; + UInt8 modIndex = npc->formID >> 24; + UInt32 modForm = (npc->formID & 0xFFFFFF); + DataHandler * dataHandler = DataHandler::GetSingleton(); + if (dataHandler) { + ModInfo * modInfo = dataHandler->modList.modInfoList.GetNthItem(modIndex); + if (modInfo) + modName = modInfo->name; + } + + char sourcePath[MAX_PATH]; + char destPath[MAX_PATH]; + sprintf_s(sourcePath, "Data\\Meshes\\CharGen\\Exported\\%s.nif", fileName.data); + sprintf_s(destPath, "Data\\Meshes\\Actors\\Character\\FaceGenData\\FaceGeom\\%s\\%08X.nif", modName, modForm); + CopyFile(sourcePath, destPath, false); + sprintf_s(sourcePath, "Data\\Textures\\CharGen\\Exported\\%s.dds", fileName.data); + sprintf_s(destPath, "Data\\Textures\\Actors\\Character\\FaceGenData\\FaceTint\\%s\\%08X.dds", modName, modForm); + CopyFile(sourcePath, destPath, false); + + ApplyPreset(actor, race, npc, presetData, (MorphHandler::ApplyTypes)flags); + g_task->AddTask(new SKSETaskRefreshTintMask(actor, sourcePath)); + return true; + } + + bool LoadCharacterPresetEx(StaticFunctionTag*, Actor * actor, BSFixedString fileName, BGSColorForm * hairColor, UInt32 flags) + { + if (!actor) { + _ERROR("%s - No actor found.", __FUNCTION__); + return false; + } + TESNPC * npc = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (!npc) { + _ERROR("%s - failed acquire ActorBase.", __FUNCTION__); + return false; + } + + char slotPath[MAX_PATH]; + sprintf_s(slotPath, "SKSE\\Plugins\\CharGen\\Presets\\%s.jslot", fileName.data); + + auto presetData = std::make_shared(); + bool loadError = g_morphHandler.LoadJsonPreset(slotPath, presetData); + if (loadError) { + sprintf_s(slotPath, "SKSE\\Plugins\\CharGen\\Presets\\%s.slot", fileName.data); + loadError = g_morphHandler.LoadBinaryPreset(slotPath, presetData); + } + + if (loadError) { + _ERROR("%s - failed to load preset.", __FUNCTION__); + return false; + } + + if (hairColor) + { + hairColor->color.red = (presetData->hairColor >> 16) & 0xFF; + hairColor->color.green = (presetData->hairColor >> 8) & 0xFF; + hairColor->color.blue = presetData->hairColor & 0xFF; + + npc->SetHairColor(hairColor); + } + + g_morphHandler.ApplyPresetData(actor, presetData, true, (MorphHandler::ApplyTypes)flags); + + // Queue a node update + CALL_MEMBER_FN(actor, QueueNiNodeUpdate)(true); + return true; + } + + void SaveCharacterPreset(StaticFunctionTag*, Actor * actor, BSFixedString fileName) + { + char slotPath[MAX_PATH]; + sprintf_s(slotPath, "Data\\SKSE\\Plugins\\CharGen\\Presets\\%s.jslot", fileName.data); + g_morphHandler.SaveJsonPreset(slotPath); + } + + bool IsExternalEnabled(StaticFunctionTag*) + { + return g_externalHeads; + } + + bool ClearPreset(StaticFunctionTag*, TESNPC * npc) + { + if (!npc) { + _ERROR("%s - failed acquire ActorBase.", __FUNCTION__); + return false; + } + + return g_morphHandler.ErasePreset(npc); + } + + void ClearPresets(StaticFunctionTag*) + { + g_morphHandler.ClearPresets(); + } + + void ExportHead(StaticFunctionTag*, BSFixedString fileName) + { + char nifPath[MAX_PATH]; + sprintf_s(nifPath, "Data\\SKSE\\Plugins\\CharGen\\%s.nif", fileName.data); + char tintPath[MAX_PATH]; + sprintf_s(tintPath, "Data\\SKSE\\Plugins\\CharGen\\%s.dds", fileName.data); + + g_task->AddTask(new SKSETaskExportHead((*g_thePlayer), nifPath, tintPath)); + } + + void ExportSlot(StaticFunctionTag*, BSFixedString fileName) + { + char slotPath[MAX_PATH]; + sprintf_s(slotPath, "Data\\SKSE\\Plugins\\CharGen\\Exported\\%s.jslot", fileName.data); + g_morphHandler.SaveJsonPreset(slotPath); + } +}; + +void papyrusCharGen::RegisterFuncs(VMClassRegistry* registry) +{ + registry->RegisterFunction( + new NativeFunction1("SaveCharacter", "CharGen", papyrusCharGen::SaveCharacter, registry)); + + registry->RegisterFunction( + new NativeFunction4("LoadCharacterEx", "CharGen", papyrusCharGen::LoadCharacterEx, registry)); + + registry->RegisterFunction( + new NativeFunction1("DeleteCharacter", "CharGen", papyrusCharGen::DeleteCharacter, registry)); + + registry->RegisterFunction( + new NativeFunction1("DeleteFaceGenData", "CharGen", papyrusCharGen::DeleteFaceGenData, registry)); + + registry->RegisterFunction( + new NativeFunction1("SaveExternalCharacter", "CharGen", papyrusCharGen::SaveExternalCharacter, registry)); + + registry->RegisterFunction( + new NativeFunction4("LoadExternalCharacterEx", "CharGen", papyrusCharGen::LoadExternalCharacterEx, registry)); + + registry->RegisterFunction( + new NativeFunction1("ClearPreset", "CharGen", papyrusCharGen::ClearPreset, registry)); + + registry->RegisterFunction( + new NativeFunction0("ClearPresets", "CharGen", papyrusCharGen::ClearPresets, registry)); + + registry->RegisterFunction( + new NativeFunction0("IsExternalEnabled", "CharGen", papyrusCharGen::IsExternalEnabled, registry)); + + registry->RegisterFunction( + new NativeFunction1("ExportHead", "CharGen", papyrusCharGen::ExportHead, registry)); + + registry->RegisterFunction( + new NativeFunction1("ExportSlot", "CharGen", papyrusCharGen::ExportSlot, registry)); + + registry->RegisterFunction( + new NativeFunction4("LoadCharacterPresetEx", "CharGen", papyrusCharGen::LoadCharacterPresetEx, registry)); + + registry->RegisterFunction( + new NativeFunction2("SaveCharacterPreset", "CharGen", papyrusCharGen::SaveCharacterPreset, registry)); +} diff --git a/skee/PapyrusCharGen.h b/skee/PapyrusCharGen.h new file mode 100644 index 0000000..9e3a11e --- /dev/null +++ b/skee/PapyrusCharGen.h @@ -0,0 +1,12 @@ +#pragma once + +class VMClassRegistry; +struct StaticFunctionTag; + +#include "skse64/GameTypes.h" + +namespace papyrusCharGen +{ + void RegisterFuncs(VMClassRegistry* registry); + +} \ No newline at end of file diff --git a/skee/PapyrusNiOverride.cpp b/skee/PapyrusNiOverride.cpp new file mode 100644 index 0000000..ca543db --- /dev/null +++ b/skee/PapyrusNiOverride.cpp @@ -0,0 +1,2117 @@ +#include "skse64/PluginAPI.h" + +#include "skse64/GameRTTI.h" +#include "skse64/GameObjects.h" +#include "skse64/GameReferences.h" + +#include "skse64/NiExtraData.h" + +#include "skse64/PapyrusArgs.h" + +#include "PapyrusNiOverride.h" + +#include "OverrideInterface.h" +#include "OverlayInterface.h" +#include "BodyMorphInterface.h" +#include "ItemDataInterface.h" +#include "TintMaskInterface.h" +#include "NiTransformInterface.h" + +#include "ShaderUtilities.h" + +extern SKSETaskInterface * g_task; +extern OverrideInterface g_overrideInterface; +extern OverlayInterface g_overlayInterface; +extern BodyMorphInterface g_bodyMorphInterface; +extern ItemDataInterface g_itemDataInterface; +extern TintMaskInterface g_tintMaskInterface; +extern DyeMap g_dyeMap; +extern NiTransformInterface g_transformInterface; + +extern UInt32 g_numBodyOverlays; +extern UInt32 g_numHandOverlays; +extern UInt32 g_numFeetOverlays; +extern UInt32 g_numFaceOverlays; + +extern bool g_playerOnly; +extern UInt32 g_numSpellBodyOverlays; +extern UInt32 g_numSpellHandOverlays; +extern UInt32 g_numSpellFeetOverlays; +extern UInt32 g_numSpellFaceOverlays; + +class MorpedReferenceEventFunctor : public IFunctionArguments +{ +public: + MorpedReferenceEventFunctor(const BSFixedString & a_eventName, TESForm * receiver, TESObjectREFR * actor) + : m_eventName(a_eventName.data), m_actor(actor), m_receiver(receiver) {} + + virtual bool Copy(Output * dst) + { + VMClassRegistry * registry = (*g_skyrimVM)->GetClassRegistry(); + + dst->Resize(1); + PackValue(dst->Get(0), &m_actor, registry); + + return true; + } + + void Run() + { + VMClassRegistry * registry = (*g_skyrimVM)->GetClassRegistry(); + IObjectHandlePolicy * policy = registry->GetHandlePolicy(); + UInt64 handle = policy->Create(m_receiver->formType, m_receiver); + registry->QueueEvent(handle, &m_eventName, this); + } + +private: + BSFixedString m_eventName; + TESForm * m_receiver; + TESObjectREFR * m_actor; +}; + +namespace papyrusNiOverride +{ + void AddOverlays(StaticFunctionTag* base, TESObjectREFR * refr) + { + if(refr) + g_overlayInterface.AddOverlays(refr); + } + + bool HasOverlays(StaticFunctionTag* base, TESObjectREFR * refr) + { + if(!refr) + return false; + + return g_overlayInterface.HasOverlays(refr); + } + + void RemoveOverlays(StaticFunctionTag* base, TESObjectREFR * refr) + { + if(refr) { + g_overlayInterface.RemoveOverlays(refr); + } + } + + void RevertOverlays(StaticFunctionTag* base, TESObjectREFR * refr) + { + if(refr) { + g_overlayInterface.RevertOverlays(refr, false); + } + } + + void RevertOverlay(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString nodeName, UInt32 armorMask, UInt32 addonMask) + { + if(refr) { + g_overlayInterface.RevertOverlay(refr, nodeName, armorMask, addonMask, false); + } + } + + void RevertHeadOverlays(StaticFunctionTag* base, TESObjectREFR * refr) + { + if(refr) { + g_overlayInterface.RevertHeadOverlays(refr, false); + } + } + + void RevertHeadOverlay(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString nodeName, UInt32 partType, UInt32 shaderType) + { + if(refr) { + g_overlayInterface.RevertHeadOverlay(refr, nodeName, partType, shaderType, false); + } + } + + bool HasOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(key > OverrideVariant::kKeyMax) + return false; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + return g_overrideInterface.GetOverride(refr, isFemale, armor, addon, nodeName, key, index) != NULL; + } + + bool HasArmorAddonNode(StaticFunctionTag* base, TESObjectREFR * refr, bool firstPerson, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, bool debug) + { + return g_overrideInterface.HasArmorAddonNode(refr, firstPerson, armor, addon, nodeName, debug); + } + + void ApplyOverrides(StaticFunctionTag* base, TESObjectREFR * refr) + { + if(!refr) + return; + + UInt64 handle = g_overrideInterface.GetHandle(refr, TESObjectREFR::kTypeID); + g_overrideInterface.SetHandleProperties(handle, false); + } + + template + void AddOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt32 key, UInt32 index, T dataType, bool persist) + { + if(!refr || key > OverrideVariant::kKeyMax) + return; + + if(!armor) { + _ERROR("%s - Failed to add override for \"%s\" node key: %d. No Armor specified.", __FUNCTION__, nodeName.data, key); + return; + } + + if(!addon) { + _ERROR("%s - Failed to add override for \"%s\" node key: %d. No ArmorAddon specified.", __FUNCTION__, nodeName.data, key); + return; + } + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + OverrideVariant value; + PackValue(&value, key, index, &dataType); + + if(value.type == OverrideVariant::kType_None) { + _ERROR("%s - Failed to pack value for \"%s\" node key: %d. Most likely invalid key for type", __FUNCTION__, nodeName.data, key); + return; + } + + // Adds the property to the map + if(persist) + g_overrideInterface.AddOverride(refr, isFemale, armor, addon, nodeName, value); + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + // Applies the properties visually, only if the current gender matches + if(isFemale == (gender == 1)) + g_overrideInterface.SetArmorAddonProperty(refr, gender == 1, armor, addon, nodeName, &value, false); + } + + template + T GetOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(!refr || key > OverrideVariant::kKeyMax || !armor || !addon) + return 0; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + + + T dest = 0; + OverrideVariant * value = g_overrideInterface.GetOverride(refr, isFemale, armor, addon, nodeName, key, index); + if(value) { + UnpackValue(&dest, value); + } + + return dest; + } + + template + T GetArmorAddonProperty(StaticFunctionTag* base, TESObjectREFR * refr, bool firstPerson, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(!refr || key > OverrideVariant::kKeyMax || !armor || !addon) + return 0; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + OverrideVariant value; + value.key = key; + value.index = index; + + g_overrideInterface.GetArmorAddonProperty(refr, firstPerson, armor, addon, nodeName, &value); + + T dest; + UnpackValue(&dest, &value); + return dest; + } + + void ApplyNodeOverrides(StaticFunctionTag* base, TESObjectREFR * refr) + { + UInt64 handle = g_overrideInterface.GetHandle(refr, TESObjectREFR::kTypeID); + return g_overrideInterface.SetHandleNodeProperties(handle, false); + } + + bool HasNodeOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(!refr || key > OverrideVariant::kKeyMax) + return false; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + return g_overrideInterface.GetNodeOverride(refr, isFemale, nodeName, key, index) != NULL; + } + + template + void AddNodeOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt32 key, UInt32 index, T dataType, bool persist) + { + if(!refr || key > OverrideVariant::kKeyMax) + return; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + OverrideVariant value; + PackValue(&value, key, index, &dataType); + + if(value.type == OverrideVariant::kType_None) { + _ERROR("%s - Failed to pack value for \"%s\" node key: %d. Most likely invalid key for type", __FUNCTION__, nodeName.data, key); + return; + } + + // Adds the property to the map + if(persist) + g_overrideInterface.AddNodeOverride(refr, isFemale, nodeName, value); + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + // Applies the properties visually, only if the current gender matches + if(isFemale == (gender == 1)) + g_overrideInterface.SetNodeProperty(refr, nodeName, &value, false); + } + + template + T GetNodeOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(!refr || key > OverrideVariant::kKeyMax) + return 0; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + T dest = 0; + OverrideVariant * value = g_overrideInterface.GetNodeOverride(refr, isFemale, nodeName, key, index); + if(value) { + UnpackValue(&dest, value); + } + + return dest; + } + + template + T GetNodeProperty(StaticFunctionTag* base, TESObjectREFR * refr, bool firstPerson, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(!refr || key > OverrideVariant::kKeyMax) + return 0; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + OverrideVariant value; + value.key = key; + value.index = index; + + g_overrideInterface.GetNodeProperty(refr, firstPerson, nodeName, &value); + + T dest; + UnpackValue(&dest, &value); + return dest; + } + + bool HasWeaponOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(key > OverrideVariant::kKeyMax) + return false; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + return g_overrideInterface.GetWeaponOverride(refr, isFemale, firstPerson, weapon, nodeName, key, index) != NULL; + } + + bool HasWeaponNode(StaticFunctionTag* base, TESObjectREFR * refr, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, bool debug) + { + return g_overrideInterface.HasWeaponNode(refr, firstPerson, weapon, nodeName, debug); + } + + void ApplyWeaponOverrides(StaticFunctionTag* base, TESObjectREFR * refr) + { + if(!refr) + return; + + UInt64 handle = g_overrideInterface.GetHandle(refr, TESObjectREFR::kTypeID); + g_overrideInterface.SetHandleWeaponProperties(handle, false); + } + + template + void AddWeaponOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt32 key, UInt32 index, T dataType, bool persist) + { + if(!refr || key > OverrideVariant::kKeyMax) + return; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + if(!weapon) { + _ERROR("%s - Failed to add override for \"%s\" node key: %d. No weapon specified.", __FUNCTION__, nodeName.data, key); + return; + } + + OverrideVariant value; + PackValue(&value, key, index, &dataType); + + if(value.type == OverrideVariant::kType_None) { + _ERROR("%s - Failed to pack value for \"%s\" node key: %d. Most likely invalid key for type", __FUNCTION__, nodeName.data, key); + return; + } + + // Adds the property to the map + if(persist) + g_overrideInterface.AddWeaponOverride(refr, isFemale, firstPerson, weapon, nodeName, value); + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + // Applies the properties visually, only if the current gender matches + if(isFemale == (gender == 1)) + g_overrideInterface.SetWeaponProperty(refr, gender == 1, firstPerson, weapon, nodeName, &value, false); + } + + template + T GetWeaponOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(!refr || key > OverrideVariant::kKeyMax || !weapon) + return 0; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + T dest = 0; + OverrideVariant * value = g_overrideInterface.GetWeaponOverride(refr, isFemale, firstPerson, weapon, nodeName, key, index); + if(value) { + UnpackValue(&dest, value); + } + + return dest; + } + + template + T GetWeaponProperty(StaticFunctionTag* base, TESObjectREFR * refr, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(!refr || key > OverrideVariant::kKeyMax || !weapon) + return 0; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + OverrideVariant value; + value.key = key; + value.index = index; + + g_overrideInterface.GetWeaponProperty(refr, firstPerson, weapon, nodeName, &value); + + T dest; + UnpackValue(&dest, &value); + return dest; + } + + + + bool HasSkinOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, UInt32 key, UInt32 index) + { + if (key > OverrideVariant::kKeyMax) + return false; + + if (!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if (index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + return g_overrideInterface.GetSkinOverride(refr, isFemale, firstPerson, slotMask, key, index) != NULL; + } + + void ApplySkinOverrides(StaticFunctionTag* base, TESObjectREFR * refr) + { + if (!refr) + return; + + UInt64 handle = g_overrideInterface.GetHandle(refr, TESObjectREFR::kTypeID); + g_overrideInterface.SetHandleSkinProperties(handle, false); + } + + template + void AddSkinOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, UInt32 key, UInt32 index, T dataType, bool persist) + { + if (!refr || key > OverrideVariant::kKeyMax) + return; + + if (!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if (index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + if (!slotMask) { + _ERROR("%s - Failed to add override for \"%d\" slot key: %d. No weapon specified.", __FUNCTION__, slotMask, key); + return; + } + + OverrideVariant value; + PackValue(&value, key, index, &dataType); + + if (value.type == OverrideVariant::kType_None) { + _ERROR("%s - Failed to pack value for \"%d\" slot key: %d. Most likely invalid key for type", __FUNCTION__, slotMask, key); + return; + } + + // Adds the property to the map + if (persist) + g_overrideInterface.AddSkinOverride(refr, isFemale, firstPerson, slotMask, value); + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if (actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + // Applies the properties visually, only if the current gender matches + if (isFemale == (gender == 1)) + g_overrideInterface.SetSkinProperty(refr, gender == 1, firstPerson, slotMask, &value, false); + } + + template + T GetSkinOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, UInt32 key, UInt32 index) + { + if (!refr || key > OverrideVariant::kKeyMax) + return 0; + + if (!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if (index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + T dest = 0; + OverrideVariant * value = g_overrideInterface.GetSkinOverride(refr, isFemale, firstPerson, slotMask, key, index); + if (value) { + UnpackValue(&dest, value); + } + + return dest; + } + + template + T GetSkinProperty(StaticFunctionTag* base, TESObjectREFR * refr, bool firstPerson, UInt32 slotMask, UInt32 key, UInt32 index) + { + if (!refr || key > OverrideVariant::kKeyMax) + return 0; + + if (!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if (index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + OverrideVariant value; + value.key = key; + value.index = index; + + g_overrideInterface.GetSkinProperty(refr, firstPerson, slotMask, &value); + + T dest; + UnpackValue(&dest, &value); + return dest; + } + + void RemoveAllOverrides(StaticFunctionTag* base) + { + g_overrideInterface.RemoveAllOverrides(); + } + + void RemoveAllReferenceOverrides(StaticFunctionTag* base, TESObjectREFR * refr) + { + if(!refr) + return; + + g_overrideInterface.RemoveAllReferenceOverrides(refr); + } + + void RemoveAllArmorOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor) + { + g_overrideInterface.RemoveAllArmorOverrides(refr, isFemale, armor); + } + + void RemoveAllArmorAddonOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon) + { + g_overrideInterface.RemoveAllArmorAddonOverrides(refr, isFemale, armor, addon); + } + + void RemoveAllArmorAddonNodeOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName) + { + g_overrideInterface.RemoveAllArmorAddonNodeOverrides(refr, isFemale, armor, addon, nodeName); + } + + void RemoveArmorAddonOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(!refr || key > OverrideVariant::kKeyMax) + return; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + g_overrideInterface.RemoveArmorAddonOverride(refr, isFemale, armor, addon, nodeName, key, index); + } + + void RemoveAllNodeOverrides(StaticFunctionTag* base) + { + g_overrideInterface.RemoveAllNodeOverrides(); + } + + void RemoveAllReferenceNodeOverrides(StaticFunctionTag* base, TESObjectREFR * refr) + { + g_overrideInterface.RemoveAllReferenceNodeOverrides(refr); + } + + void RemoveAllNodeNameOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, BSFixedString nodeName) + { + g_overrideInterface.RemoveAllNodeNameOverrides(refr, isFemale, nodeName); + } + + void RemoveNodeOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(!refr || key > OverrideVariant::kKeyMax) + return; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + g_overrideInterface.RemoveNodeOverride(refr, isFemale, nodeName, key, index); + } + + void RemoveAllWeaponBasedOverrides(StaticFunctionTag* base) + { + g_overrideInterface.RemoveAllWeaponBasedOverrides(); + } + + void RemoveAllReferenceWeaponOverrides(StaticFunctionTag* base, TESObjectREFR * refr) + { + if(!refr) + return; + + g_overrideInterface.RemoveAllReferenceWeaponOverrides(refr); + } + + void RemoveAllWeaponOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon) + { + g_overrideInterface.RemoveAllWeaponOverrides(refr, isFemale, firstPerson, weapon); + } + + void RemoveAllWeaponNodeOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName) + { + g_overrideInterface.RemoveAllWeaponNodeOverrides(refr, isFemale, firstPerson, weapon, nodeName); + } + + void RemoveWeaponOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt32 key, UInt32 index) + { + if(!refr || key > OverrideVariant::kKeyMax) + return; + + if(!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if(index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + g_overrideInterface.RemoveWeaponOverride(refr, isFemale, firstPerson, weapon, nodeName, key, index); + } + + void RemoveAllSkinBasedOverrides(StaticFunctionTag* base) + { + g_overrideInterface.RemoveAllSkinBasedOverrides(); + } + + void RemoveAllReferenceSkinOverrides(StaticFunctionTag* base, TESObjectREFR * refr) + { + if (!refr) + return; + + g_overrideInterface.RemoveAllReferenceSkinOverrides(refr); + } + + void RemoveAllSkinOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask) + { + g_overrideInterface.RemoveAllSkinOverrides(refr, isFemale, firstPerson, slotMask); + } + + void RemoveSkinOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, UInt32 slotMask, UInt32 key, UInt32 index) + { + if (!refr || key > OverrideVariant::kKeyMax) + return; + + if (!OverrideVariant::IsIndexValid(key)) + index = OverrideVariant::kIndexMax; + if (index > OverrideVariant::kIndexMax) + index = OverrideVariant::kIndexMax; + + g_overrideInterface.RemoveSkinOverride(refr, isFemale, firstPerson, slotMask, key, index); + } + + UInt32 GetNumBodyOverlays(StaticFunctionTag* base) + { + return g_numBodyOverlays; + } + + UInt32 GetNumHandOverlays(StaticFunctionTag* base) + { + return g_numHandOverlays; + } + + UInt32 GetNumFeetOverlays(StaticFunctionTag* base) + { + return g_numFeetOverlays; + } + + UInt32 GetNumFaceOverlays(StaticFunctionTag* base) + { + return g_numFaceOverlays; + } + + UInt32 GetNumSpellBodyOverlays(StaticFunctionTag* base) + { + return g_numSpellBodyOverlays; + } + + UInt32 GetNumSpellHandOverlays(StaticFunctionTag* base) + { + return g_numSpellHandOverlays; + } + + UInt32 GetNumSpellFeetOverlays(StaticFunctionTag* base) + { + return g_numSpellFeetOverlays; + } + + UInt32 GetNumSpellFaceOverlays(StaticFunctionTag* base) + { + return g_numSpellFaceOverlays; + } + + bool HasBodyMorph(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString morphName, BSFixedString keyName) + { + return g_bodyMorphInterface.HasBodyMorph(refr, morphName, keyName); + } + + void SetBodyMorph(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString morphName, BSFixedString keyName, float value) + { + g_bodyMorphInterface.SetMorph(refr, morphName, keyName, value); + } + + float GetBodyMorph(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString morphName, BSFixedString keyName) + { + return g_bodyMorphInterface.GetMorph(refr, morphName, keyName); + } + + void ClearBodyMorph(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString morphName, BSFixedString keyName) + { + g_bodyMorphInterface.ClearMorph(refr, morphName, keyName); + } + + bool HasBodyMorphKey(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString keyName) + { + return g_bodyMorphInterface.HasBodyMorphKey(refr, keyName); + } + + void ClearBodyMorphKeys(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString keyName) + { + g_bodyMorphInterface.ClearBodyMorphKeys(refr, keyName); + } + + bool HasBodyMorphName(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString morphName) + { + return g_bodyMorphInterface.HasBodyMorphName(refr, morphName); + } + + void ClearBodyMorphNames(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString morphName) + { + g_bodyMorphInterface.ClearBodyMorphNames(refr, morphName); + } + + void ClearMorphs(StaticFunctionTag* base, TESObjectREFR * refr) + { + g_bodyMorphInterface.ClearMorphs(refr); + } + + VMResultArray GetMorphedReferences(StaticFunctionTag* base) + { + VMResultArray results; + g_bodyMorphInterface.VisitActors([&](TESObjectREFR* refr) + { + results.push_back(refr); + }); + + return results; + } + + void ForEachMorphedReference(StaticFunctionTag* base, BSFixedString eventName, TESForm * receiver) + { + g_bodyMorphInterface.VisitActors([&](TESObjectREFR* refr) + { + MorpedReferenceEventFunctor fn(eventName, receiver, refr); + fn.Run(); + }); + } + + void UpdateModelWeight(StaticFunctionTag* base, TESObjectREFR * refr) + { + g_bodyMorphInterface.UpdateModelWeight(refr); + } + + void EnableTintTextureCache(StaticFunctionTag* base) + { + g_tintMaskInterface.ManageTints(); + } + + void ReleaseTintTextureCache(StaticFunctionTag* base) + { + g_tintMaskInterface.ReleaseTints(); + } + + UInt32 GetItemUniqueID(StaticFunctionTag* base, TESObjectREFR * refr, UInt32 weaponSlot, UInt32 slotMask, bool makeUnique) + { + if (!refr) + return 0; + + ModifiedItemIdentifier identifier; + identifier.SetSlotMask(slotMask, weaponSlot); + return g_itemDataInterface.GetItemUniqueID(refr, identifier, makeUnique); + } + + UInt32 GetObjectUniqueID(StaticFunctionTag* base, TESObjectREFR * refr, bool makeUnique) + { + if (!refr) + return 0; + + if (Actor * actor = DYNAMIC_CAST(refr, TESForm, Actor)) { + _WARNING("%s - cannot be called on an actor", __FUNCTION__); + return 0; + } + + ModifiedItemIdentifier identifier; + identifier.SetSelf(); + return g_itemDataInterface.GetItemUniqueID(refr, identifier, makeUnique); + } + + TESForm * GetFormFromUniqueID(StaticFunctionTag* base, UInt32 uniqueID) + { + return g_itemDataInterface.GetFormFromUniqueID(uniqueID); + } + + TESForm * GetOwnerOfUniqueID(StaticFunctionTag* base, UInt32 uniqueID) + { + return g_itemDataInterface.GetOwnerOfUniqueID(uniqueID); + } + + void SetItemDyeColor(StaticFunctionTag* base, UInt32 uniqueID, SInt32 maskIndex, UInt32 color) + { + g_itemDataInterface.SetItemDyeColor(uniqueID, maskIndex, color); + } + + UInt32 GetItemDyeColor(StaticFunctionTag* base, UInt32 uniqueID, SInt32 maskIndex) + { + return g_itemDataInterface.GetItemDyeColor(uniqueID, maskIndex); + } + + void ClearItemDyeColor(StaticFunctionTag* base, UInt32 uniqueID, SInt32 maskIndex) + { + g_itemDataInterface.ClearItemDyeColor(uniqueID, maskIndex); + } + + void UpdateItemDyeColor(StaticFunctionTag* base, TESObjectREFR * refr, UInt32 uniqueID) + { + if (Actor * actor = DYNAMIC_CAST(refr, TESForm, Actor)) { + ModifiedItemIdentifier identifier; + identifier.SetRankID(uniqueID); + g_task->AddTask(new NIOVTaskUpdateItemDye(actor, identifier)); + } + } + + bool IsFormDye(StaticFunctionTag* base, TESForm * form) + { + return form ? g_dyeMap.IsValidDye(form) : false; + } + + UInt32 GetFormDyeColor(StaticFunctionTag* base, TESForm * form) + { + return form ? g_dyeMap.GetDyeColor(form) : 0; + } + + void RegisterFormDyeColor(StaticFunctionTag* base, TESForm * form, UInt32 color) + { + if (form) { + g_dyeMap.RegisterDyeForm(form, color); + } + } + + void UnregisterFormDyeColor(StaticFunctionTag* base, TESForm * form) + { + if (form) { + g_dyeMap.UnregisterDyeForm(form); + } + } + + + + bool HasNodeTransformPosition(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name) + { + if (!refr) + return false; + + NiTransform transform; + return g_transformInterface.GetOverrideNodeTransform(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformPosition, &transform); + } + + void AddNodeTransformPosition(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name, VMArray dataType) + { + if (!refr) + return; + + if (name == BSFixedString("")) { + _ERROR("%s - Cannot add empty key for node \"%s\".", __FUNCTION__, node.data); + return; + } + + if (dataType.Length() != 3) { + _ERROR("%s - Failed to unpack array value for \"%s\". Invalid array size must be 3", __FUNCTION__, node.data); + return; + } + + float pos[3]; + OverrideVariant posV[3]; + for (UInt32 i = 0; i < 3; i++) { + dataType.Get(&pos[i], i); + PackValue(&posV[i], OverrideVariant::kParam_NodeTransformPosition, i, &pos[i]); + g_transformInterface.AddNodeTransform(refr, isFirstPerson, isFemale, node, name, posV[i]); + } + } + + + VMResultArray GetNodeTransformPosition(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name) + { + VMResultArray position; + if (!refr) + return position; + + position.resize(3, 0.0); + NiTransform transform; + bool ret = g_transformInterface.GetOverrideNodeTransform(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformPosition, &transform); + position[0] = transform.pos.x; + position[1] = transform.pos.y; + position[2] = transform.pos.z; + return position; + } + + bool RemoveNodeTransformPosition(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name) + { + if (!refr) + return false; + + bool ret = false; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformPosition, 0)) + ret = true; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformPosition, 1)) + ret = true; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformPosition, 2)) + ret = true; + return ret; + } + + bool HasNodeTransformScale(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name) + { + if (!refr) + return false; + + NiTransform transform; + return g_transformInterface.GetOverrideNodeTransform(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformScale, &transform); + } + + void AddNodeTransformScale(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name, float fScale) + { + if (!refr) + return; + + if (name == BSFixedString("")) { + _ERROR("%s - Cannot add empty key for node \"%s\".", __FUNCTION__, node.data); + return; + } + + OverrideVariant scale; + PackValue(&scale, OverrideVariant::kParam_NodeTransformScale, 0, &fScale); + g_transformInterface.AddNodeTransform(refr, isFirstPerson, isFemale, node, name, scale); + } + + float GetNodeTransformScale(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name) + { + if (!refr) + return 0; + + NiTransform transform; + g_transformInterface.GetOverrideNodeTransform(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformScale, &transform); + return transform.scale; + } + + bool RemoveNodeTransformScale(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name) + { + if (!refr) + return false; + + return g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformScale, 0); + } + + + bool HasNodeTransformRotation(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name) + { + if (!refr) + return false; + + NiTransform transform; + return g_transformInterface.GetOverrideNodeTransform(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformRotation, &transform); + } + + void AddNodeTransformRotation(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name, VMArray dataType) + { + if (!refr) + return; + + NiMatrix33 rotation; + if (dataType.Length() == 3) { + float heading, attitude, bank; + + dataType.Get(&heading, 0); + dataType.Get(&attitude, 1); + dataType.Get(&bank, 2); + + heading *= MATH_PI / 180; + attitude *= MATH_PI / 180; + bank *= MATH_PI / 180; + + rotation.SetEulerAngles(heading, attitude, bank); + } + else if (dataType.Length() == 9) { + for (UInt32 i = 0; i < 9; i++) + dataType.Get(&rotation.arr[i], i); + } + else { + _ERROR("%s - Failed to unpack array value for \"%s\". Invalid array size must be 3 or 9", __FUNCTION__, node.data); + return; + } + + OverrideVariant rotV[9]; + for (UInt32 i = 0; i < 9; i++) { + PackValue(&rotV[i], OverrideVariant::kParam_NodeTransformRotation, i, &rotation.arr[i]); + g_transformInterface.AddNodeTransform(refr, isFirstPerson, isFemale, node, name, rotV[i]); + } + } + + + VMResultArray GetNodeTransformRotation(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name, UInt32 type) + { + VMResultArray rotation; + if (!refr) + return rotation; + + NiTransform transform; + bool ret = g_transformInterface.GetOverrideNodeTransform(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformRotation, &transform); + + switch (type) { + case 0: + { + rotation.resize(3, 0.0); + float heading, attitude, bank; + transform.rot.GetEulerAngles(&heading, &attitude, &bank); + + // Radians to degrees + heading *= 180 / MATH_PI; + attitude *= 180 / MATH_PI; + bank *= 180 / MATH_PI; + + rotation[0] = heading; + rotation[1] = attitude; + rotation[2] = bank; + break; + } + case 1: + { + rotation.resize(9, 0.0); + for (UInt32 i = 0; i < 9; i++) { + rotation[i] = ((float*)transform.rot.data)[i]; + } + break; + } + default: + { + _ERROR("%s - Failed to create array value for \"%s\". Invalid type %d", __FUNCTION__, node.data, type); + break; + } + } + + + return rotation; + } + + bool RemoveNodeTransformRotation(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString name) + { + if (!refr) + return false; + + bool ret = false; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformRotation, 0)) + ret = true; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformRotation, 1)) + ret = true; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformRotation, 2)) + ret = true; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformRotation, 3)) + ret = true; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformRotation, 4)) + ret = true; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformRotation, 5)) + ret = true; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformRotation, 6)) + ret = true; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformRotation, 7)) + ret = true; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, name, OverrideVariant::kParam_NodeTransformRotation, 8)) + ret = true; + + return ret; + } + + void UpdateAllReferenceTransforms(StaticFunctionTag* base, TESObjectREFR * refr) + { + if (!refr) + return; + + g_transformInterface.UpdateNodeAllTransforms(refr); + } + + void RemoveAllTransforms(StaticFunctionTag* base) + { + g_transformInterface.Revert(); + } + + void RemoveAllReferenceTransforms(StaticFunctionTag* base, TESObjectREFR * refr) + { + if (!refr) + return; + + g_transformInterface.RemoveAllReferenceTransforms(refr); + } + + void UpdateNodeTransform(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node) + { + if (!refr) + return; + + UInt8 gender = isFemale ? 1 : 0; + UInt8 realGender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if (actorBase) + realGender = CALL_MEMBER_FN(actorBase, GetSex)(); + + if (gender != realGender) + return; + + g_transformInterface.UpdateNodeTransforms(refr, isFirstPerson, isFemale, node); + } + + void SetNodeDestination(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node, BSFixedString destination) + { + if (!refr) + return; + + OverrideVariant value; + PackValue(&value, OverrideVariant::kParam_NodeDestination, -1, &destination); + g_transformInterface.AddNodeTransform(refr, isFirstPerson, isFemale, node, "NodeDestination", value); + } + + BSFixedString GetNodeDestination(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node) + { + BSFixedString ret(""); + if (!refr) + return ret; + + OverrideVariant value = g_transformInterface.GetOverrideNodeValue(refr, isFirstPerson, isFemale, node, "NodeDestination", OverrideVariant::kParam_NodeDestination, -1); + UnpackValue(&ret, &value); + return ret; + } + + bool RemoveNodeDestination(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node) + { + if (!refr) + return false; + + bool ret = false; + if (g_transformInterface.RemoveNodeTransformComponent(refr, isFirstPerson, isFemale, node, "NodeDestination", OverrideVariant::kParam_NodeDestination, -1)) + ret = true; + return ret; + } + + float GetInverseTransform(StaticFunctionTag* base, VMArray position, VMArray rotation, float scale) + { + NiTransform transform, ixForm; + transform.scale = scale; + if (position.Length() == 3) { + position.Get(&transform.pos.x, 0); + position.Get(&transform.pos.y, 1); + position.Get(&transform.pos.z, 2); + } + if (rotation.Length() == 9) { + for (UInt32 i = 0; i < 9; i++) + rotation.Get((float*)transform.rot.data[i], i); + } + if (rotation.Length() == 3) { + float heading, attitude, bank; + rotation.Get(&heading, 0); + rotation.Get(&attitude, 1); + rotation.Get(&bank, 2); + + transform.rot.SetEulerAngles(heading, attitude, bank); + } + transform.Invert(ixForm); + if (position.Length() == 3) { + position.Set(&ixForm.pos.x, 0); + position.Set(&ixForm.pos.y, 1); + position.Set(&ixForm.pos.z, 2); + } + if (rotation.Length() == 9) { + for (UInt32 i = 0; i < 9; i++) + rotation.Set((float*)ixForm.rot.data[i], i); + } + if (rotation.Length() == 3) { + float heading, attitude, bank; + + ixForm.rot.GetEulerAngles(&heading, &attitude, &bank); + + // Radians to degrees + heading *= 180 / MATH_PI; + attitude *= 180 / MATH_PI; + bank *= 180 / MATH_PI; + + rotation.Set(&heading, 0); + rotation.Set(&attitude, 1); + rotation.Set(&bank, 2); + } + + return ixForm.scale; + } + + VMResultArray GetNodeTransformNames(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale) + { + VMResultArray result; + if (!refr) + return result; + + g_transformInterface.VisitNodes(refr, isFirstPerson, isFemale, [&](BSFixedString key, OverrideRegistration*value) + { + result.push_back(key); + return false; + }); + + return result; + } + + VMResultArray GetNodeTransformKeys(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, bool isFemale, BSFixedString node) + { + VMResultArray result; + if (!refr) + return result; + + g_transformInterface.VisitNodes(refr, isFirstPerson, isFemale, [&](BSFixedString nodeName, OverrideRegistration*value) + { + if (nodeName == node) { + for (auto key : *value) + result.push_back(key.first); + return true; + } + + return false; + }); + + return result; + } + + VMResultArray GetMorphNames(StaticFunctionTag* base, TESObjectREFR * refr) + { + VMResultArray result; + if (!refr) + return result; + + g_bodyMorphInterface.VisitMorphs(refr, [&](BSFixedString name, std::unordered_map * map) + { + result.push_back(name); + }); + + return result; + } + + VMResultArray GetMorphKeys(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString morphName) + { + VMResultArray result; + if (!refr) + return result; + + g_bodyMorphInterface.VisitKeys(refr, morphName, [&](BSFixedString name, float value) + { + result.push_back(name); + }); + + return result; + } + + template + void GetBaseExtraData(NiExtraData * extraData, T & data) + { + // No implementation + } + + template + void ExtraDataInitializer(T & data) + { + + } + + template<> void ExtraDataInitializer(float & data) + { + data = 0.0; + } + + template<> void ExtraDataInitializer(bool & data) + { + data = false; + } + + template<> void ExtraDataInitializer(SInt32 & data) + { + data = 0; + } + + template<> void ExtraDataInitializer(BSFixedString & data) + { + data = BSFixedString(""); + } + + template<> void GetBaseExtraData(NiExtraData * extraData, float & data) + { + NiFloatExtraData * pExtraData = DYNAMIC_CAST(extraData, NiExtraData, NiFloatExtraData); + if (pExtraData) { + data = pExtraData->m_data; + } + } + + template<> void GetBaseExtraData(NiExtraData * extraData, VMResultArray & data) + { + NiFloatsExtraData * pExtraData = DYNAMIC_CAST(extraData, NiExtraData, NiFloatsExtraData); + if (pExtraData) { + for (UInt32 i = 0; i < pExtraData->m_size; i++) + data.push_back(pExtraData->m_data[i]); + } + } + + template<> void GetBaseExtraData(NiExtraData * extraData, SInt32 & data) + { + NiIntegerExtraData * pExtraData = DYNAMIC_CAST(extraData, NiExtraData, NiIntegerExtraData); + if (pExtraData) { + data = pExtraData->m_data; + } + } + + template<> void GetBaseExtraData(NiExtraData * extraData, VMResultArray & data) + { + NiIntegersExtraData * pExtraData = DYNAMIC_CAST(extraData, NiExtraData, NiIntegersExtraData); + if (pExtraData) { + for (UInt32 i = 0; i < pExtraData->m_size; i++) + data.push_back(pExtraData->m_data[i]); + } + } + + template<> void GetBaseExtraData(NiExtraData * extraData, BSFixedString & data) + { + NiStringExtraData * pExtraData = DYNAMIC_CAST(extraData, NiExtraData, NiStringExtraData); + if (pExtraData) { + data = pExtraData->m_pString; + } + } + + template<> void GetBaseExtraData(NiExtraData * extraData, VMResultArray & data) + { + NiStringsExtraData * pExtraData = DYNAMIC_CAST(extraData, NiExtraData, NiStringsExtraData); + if (pExtraData) { + for (UInt32 i = 0; i < pExtraData->m_size; i++) + data.push_back(pExtraData->m_data[i]); + } + } + + + template + T GetExtraData(StaticFunctionTag* base, TESObjectREFR * refr, bool isFirstPerson, BSFixedString node, BSFixedString dataName) + { + T value; + ExtraDataInitializer(value); + if (!refr) + return value; + + NiNode * skeleton = refr->GetNiRootNode(isFirstPerson ? 1 : 0); + if (skeleton) { + skeleton->IncRef(); + + NiAVObject * object = skeleton->GetObjectByName(&node.data); + if (object) { + object->IncRef(); + + NiExtraData * extraData = object->GetExtraData(dataName); + if (extraData) { + extraData->IncRef(); + GetBaseExtraData(extraData, value); + extraData->DecRef(); + } + + object->DecRef(); + } + + skeleton->DecRef(); + } + + return value; + } +} + +#include "skse64/PapyrusVM.h" +#include "skse64/PapyrusNativeFunctions.h" + +void papyrusNiOverride::RegisterFuncs(VMClassRegistry* registry) +{ + // Overlay Data + registry->RegisterFunction( + new NativeFunction0("GetNumBodyOverlays", "NiOverride", papyrusNiOverride::GetNumBodyOverlays, registry)); + + registry->RegisterFunction( + new NativeFunction0("GetNumHandOverlays", "NiOverride", papyrusNiOverride::GetNumHandOverlays, registry)); + + registry->RegisterFunction( + new NativeFunction0("GetNumFeetOverlays", "NiOverride", papyrusNiOverride::GetNumFeetOverlays, registry)); + + registry->RegisterFunction( + new NativeFunction0("GetNumFaceOverlays", "NiOverride", papyrusNiOverride::GetNumFaceOverlays, registry)); + + // Spell Overlays Enabled + registry->RegisterFunction( + new NativeFunction0("GetNumSpellBodyOverlays", "NiOverride", papyrusNiOverride::GetNumSpellBodyOverlays, registry)); + + registry->RegisterFunction( + new NativeFunction0("GetNumSpellHandOverlays", "NiOverride", papyrusNiOverride::GetNumSpellHandOverlays, registry)); + + registry->RegisterFunction( + new NativeFunction0("GetNumSpellFeetOverlays", "NiOverride", papyrusNiOverride::GetNumSpellFeetOverlays, registry)); + + registry->RegisterFunction( + new NativeFunction0("GetNumSpellFaceOverlays", "NiOverride", papyrusNiOverride::GetNumSpellFaceOverlays, registry)); + + + // Overlays + registry->RegisterFunction( + new NativeFunction1("AddOverlays", "NiOverride", papyrusNiOverride::AddOverlays, registry)); + + registry->RegisterFunction( + new NativeFunction1("HasOverlays", "NiOverride", papyrusNiOverride::HasOverlays, registry)); + + registry->RegisterFunction( + new NativeFunction1("RemoveOverlays", "NiOverride", papyrusNiOverride::RemoveOverlays, registry)); + + registry->RegisterFunction( + new NativeFunction1("RevertOverlays", "NiOverride", papyrusNiOverride::RevertOverlays, registry)); + + registry->RegisterFunction( + new NativeFunction4("RevertOverlay", "NiOverride", papyrusNiOverride::RevertOverlay, registry)); + + registry->RegisterFunction( + new NativeFunction1("RevertHeadOverlays", "NiOverride", papyrusNiOverride::RevertHeadOverlays, registry)); + + registry->RegisterFunction( + new NativeFunction4("RevertHeadOverlay", "NiOverride", papyrusNiOverride::RevertHeadOverlay, registry)); + + + // Armor Overrides + registry->RegisterFunction( + new NativeFunction7("HasOverride", "NiOverride", papyrusNiOverride::HasOverride, registry)); + + registry->RegisterFunction( + new NativeFunction9("AddOverrideFloat", "NiOverride", papyrusNiOverride::AddOverride, registry)); + + registry->RegisterFunction( + new NativeFunction9("AddOverrideInt", "NiOverride", papyrusNiOverride::AddOverride, registry)); + + registry->RegisterFunction( + new NativeFunction9("AddOverrideBool", "NiOverride", papyrusNiOverride::AddOverride, registry)); + + registry->RegisterFunction( + new NativeFunction9("AddOverrideString", "NiOverride", papyrusNiOverride::AddOverride, registry)); + + registry->RegisterFunction( + new NativeFunction9("AddOverrideTextureSet", "NiOverride", papyrusNiOverride::AddOverride, registry)); + + registry->RegisterFunction( + new NativeFunction1("ApplyOverrides", "NiOverride", papyrusNiOverride::ApplyOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction6("HasArmorAddonNode", "NiOverride", papyrusNiOverride::HasArmorAddonNode, registry)); + + // Get Armor Overrides + registry->RegisterFunction( + new NativeFunction7("GetOverrideFloat", "NiOverride", papyrusNiOverride::GetOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("GetOverrideInt", "NiOverride", papyrusNiOverride::GetOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("GetOverrideBool", "NiOverride", papyrusNiOverride::GetOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("GetOverrideString", "NiOverride", papyrusNiOverride::GetOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("GetOverrideTextureSet", "NiOverride", papyrusNiOverride::GetOverride, registry)); + + // Get Armor Properties + registry->RegisterFunction( + new NativeFunction7("GetPropertyFloat", "NiOverride", papyrusNiOverride::GetArmorAddonProperty, registry)); + + registry->RegisterFunction( + new NativeFunction7("GetPropertyInt", "NiOverride", papyrusNiOverride::GetArmorAddonProperty, registry)); + + registry->RegisterFunction( + new NativeFunction7("GetPropertyBool", "NiOverride", papyrusNiOverride::GetArmorAddonProperty, registry)); + + registry->RegisterFunction( + new NativeFunction7("GetPropertyString", "NiOverride", papyrusNiOverride::GetArmorAddonProperty, registry)); + + // Node Overrides + registry->RegisterFunction( + new NativeFunction5("HasNodeOverride", "NiOverride", papyrusNiOverride::HasNodeOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("AddNodeOverrideFloat", "NiOverride", papyrusNiOverride::AddNodeOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("AddNodeOverrideInt", "NiOverride", papyrusNiOverride::AddNodeOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("AddNodeOverrideBool", "NiOverride", papyrusNiOverride::AddNodeOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("AddNodeOverrideString", "NiOverride", papyrusNiOverride::AddNodeOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("AddNodeOverrideTextureSet", "NiOverride", papyrusNiOverride::AddNodeOverride, registry)); + + registry->RegisterFunction( + new NativeFunction1("ApplyNodeOverrides", "NiOverride", papyrusNiOverride::ApplyNodeOverrides, registry)); + + // Get Node Overrides + registry->RegisterFunction( + new NativeFunction5("GetNodeOverrideFloat", "NiOverride", papyrusNiOverride::GetNodeOverride, registry)); + + registry->RegisterFunction( + new NativeFunction5("GetNodeOverrideInt", "NiOverride", papyrusNiOverride::GetNodeOverride, registry)); + + registry->RegisterFunction( + new NativeFunction5("GetNodeOverrideBool", "NiOverride", papyrusNiOverride::GetNodeOverride, registry)); + + registry->RegisterFunction( + new NativeFunction5("GetNodeOverrideString", "NiOverride", papyrusNiOverride::GetNodeOverride, registry)); + + registry->RegisterFunction( + new NativeFunction5("GetNodeOverrideTextureSet", "NiOverride", papyrusNiOverride::GetNodeOverride, registry)); + + // Get Node Properties + registry->RegisterFunction( + new NativeFunction5("GetNodePropertyFloat", "NiOverride", papyrusNiOverride::GetNodeProperty, registry)); + + registry->RegisterFunction( + new NativeFunction5("GetNodePropertyInt", "NiOverride", papyrusNiOverride::GetNodeProperty, registry)); + + registry->RegisterFunction( + new NativeFunction5("GetNodePropertyBool", "NiOverride", papyrusNiOverride::GetNodeProperty, registry)); + + registry->RegisterFunction( + new NativeFunction5("GetNodePropertyString", "NiOverride", papyrusNiOverride::GetNodeProperty, registry)); + + + // Weapon Overrides + registry->RegisterFunction( + new NativeFunction7("HasWeaponOverride", "NiOverride", papyrusNiOverride::HasWeaponOverride, registry)); + + registry->RegisterFunction( + new NativeFunction9("AddWeaponOverrideFloat", "NiOverride", papyrusNiOverride::AddWeaponOverride, registry)); + + registry->RegisterFunction( + new NativeFunction9("AddWeaponOverrideInt", "NiOverride", papyrusNiOverride::AddWeaponOverride, registry)); + + registry->RegisterFunction( + new NativeFunction9("AddWeaponOverrideBool", "NiOverride", papyrusNiOverride::AddWeaponOverride, registry)); + + registry->RegisterFunction( + new NativeFunction9("AddWeaponOverrideString", "NiOverride", papyrusNiOverride::AddWeaponOverride, registry)); + + registry->RegisterFunction( + new NativeFunction9("AddWeaponOverrideTextureSet", "NiOverride", papyrusNiOverride::AddWeaponOverride, registry)); + + registry->RegisterFunction( + new NativeFunction1("ApplyWeaponOverrides", "NiOverride", papyrusNiOverride::ApplyWeaponOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction5("HasWeaponNode", "NiOverride", papyrusNiOverride::HasWeaponNode, registry)); + + // Get Weapon Overrides + registry->RegisterFunction( + new NativeFunction7("GetWeaponOverrideFloat", "NiOverride", papyrusNiOverride::GetWeaponOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("GetWeaponOverrideInt", "NiOverride", papyrusNiOverride::GetWeaponOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("GetWeaponOverrideBool", "NiOverride", papyrusNiOverride::GetWeaponOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("GetWeaponOverrideString", "NiOverride", papyrusNiOverride::GetWeaponOverride, registry)); + + registry->RegisterFunction( + new NativeFunction7("GetWeaponOverrideTextureSet", "NiOverride", papyrusNiOverride::GetWeaponOverride, registry)); + + // Get Weapon Properties + registry->RegisterFunction( + new NativeFunction6("GetWeaponPropertyFloat", "NiOverride", papyrusNiOverride::GetWeaponProperty, registry)); + + registry->RegisterFunction( + new NativeFunction6("GetWeaponPropertyInt", "NiOverride", papyrusNiOverride::GetWeaponProperty, registry)); + + registry->RegisterFunction( + new NativeFunction6("GetWeaponPropertyBool", "NiOverride", papyrusNiOverride::GetWeaponProperty, registry)); + + registry->RegisterFunction( + new NativeFunction6("GetWeaponPropertyString", "NiOverride", papyrusNiOverride::GetWeaponProperty, registry)); + + + // Skin Overrides + registry->RegisterFunction( + new NativeFunction6("HasSkinOverride", "NiOverride", papyrusNiOverride::HasSkinOverride, registry)); + + registry->RegisterFunction( + new NativeFunction8("AddSkinOverrideFloat", "NiOverride", papyrusNiOverride::AddSkinOverride, registry)); + + registry->RegisterFunction( + new NativeFunction8("AddSkinOverrideInt", "NiOverride", papyrusNiOverride::AddSkinOverride, registry)); + + registry->RegisterFunction( + new NativeFunction8("AddSkinOverrideBool", "NiOverride", papyrusNiOverride::AddSkinOverride, registry)); + + registry->RegisterFunction( + new NativeFunction8("AddSkinOverrideString", "NiOverride", papyrusNiOverride::AddSkinOverride, registry)); + + registry->RegisterFunction( + new NativeFunction8("AddSkinOverrideTextureSet", "NiOverride", papyrusNiOverride::AddSkinOverride, registry)); + + registry->RegisterFunction( + new NativeFunction1("ApplySkinOverrides", "NiOverride", papyrusNiOverride::ApplySkinOverrides, registry)); + + + // Get Skin Overrides + registry->RegisterFunction( + new NativeFunction6("GetSkinOverrideFloat", "NiOverride", papyrusNiOverride::GetSkinOverride, registry)); + + registry->RegisterFunction( + new NativeFunction6("GetSkinOverrideInt", "NiOverride", papyrusNiOverride::GetSkinOverride, registry)); + + registry->RegisterFunction( + new NativeFunction6("GetSkinOverrideBool", "NiOverride", papyrusNiOverride::GetSkinOverride, registry)); + + registry->RegisterFunction( + new NativeFunction6("GetSkinOverrideString", "NiOverride", papyrusNiOverride::GetSkinOverride, registry)); + + registry->RegisterFunction( + new NativeFunction6("GetSkinOverrideTextureSet", "NiOverride", papyrusNiOverride::GetSkinOverride, registry)); + + // Get Skin Properties + registry->RegisterFunction( + new NativeFunction5("GetSkinPropertyFloat", "NiOverride", papyrusNiOverride::GetSkinProperty, registry)); + + registry->RegisterFunction( + new NativeFunction5("GetSkinPropertyInt", "NiOverride", papyrusNiOverride::GetSkinProperty, registry)); + + registry->RegisterFunction( + new NativeFunction5("GetSkinPropertyBool", "NiOverride", papyrusNiOverride::GetSkinProperty, registry)); + + registry->RegisterFunction( + new NativeFunction5("GetSkinPropertyString", "NiOverride", papyrusNiOverride::GetSkinProperty, registry)); + + + + // Remove functions + registry->RegisterFunction( + new NativeFunction0("RemoveAllOverrides", "NiOverride", papyrusNiOverride::RemoveAllOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction1("RemoveAllReferenceOverrides", "NiOverride", papyrusNiOverride::RemoveAllReferenceOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction3("RemoveAllArmorOverrides", "NiOverride", papyrusNiOverride::RemoveAllArmorOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction4("RemoveAllArmorAddonOverrides", "NiOverride", papyrusNiOverride::RemoveAllArmorAddonOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction5("RemoveAllArmorAddonNodeOverrides", "NiOverride", papyrusNiOverride::RemoveAllArmorAddonNodeOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction7("RemoveOverride", "NiOverride", papyrusNiOverride::RemoveArmorAddonOverride, registry)); + + // Node Remove functions + registry->RegisterFunction( + new NativeFunction0("RemoveAllNodeOverrides", "NiOverride", papyrusNiOverride::RemoveAllNodeOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction1("RemoveAllReferenceNodeOverrides", "NiOverride", papyrusNiOverride::RemoveAllReferenceNodeOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction3("RemoveAllNodeNameOverrides", "NiOverride", papyrusNiOverride::RemoveAllNodeNameOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction5("RemoveNodeOverride", "NiOverride", papyrusNiOverride::RemoveNodeOverride, registry)); + + // Remove Weapon functions + registry->RegisterFunction( + new NativeFunction0("RemoveAllWeaponBasedOverrides", "NiOverride", papyrusNiOverride::RemoveAllWeaponBasedOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction1("RemoveAllReferenceWeaponOverrides", "NiOverride", papyrusNiOverride::RemoveAllReferenceWeaponOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction4("RemoveAllWeaponOverrides", "NiOverride", papyrusNiOverride::RemoveAllWeaponOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction5("RemoveAllWeaponNodeOverrides", "NiOverride", papyrusNiOverride::RemoveAllWeaponNodeOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction7("RemoveWeaponOverride", "NiOverride", papyrusNiOverride::RemoveWeaponOverride, registry)); + + + // Remove Skin functions + registry->RegisterFunction( + new NativeFunction0("RemoveAllSkinBasedOverrides", "NiOverride", papyrusNiOverride::RemoveAllSkinBasedOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction1("RemoveAllReferenceSkinOverrides", "NiOverride", papyrusNiOverride::RemoveAllReferenceSkinOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction4("RemoveAllSkinOverrides", "NiOverride", papyrusNiOverride::RemoveAllSkinOverrides, registry)); + + registry->RegisterFunction( + new NativeFunction6("RemoveSkinOverride", "NiOverride", papyrusNiOverride::RemoveSkinOverride, registry)); + + + // Body Morph Manipulation + registry->RegisterFunction( + new NativeFunction3("HasBodyMorph", "NiOverride", papyrusNiOverride::HasBodyMorph, registry)); + + registry->RegisterFunction( + new NativeFunction4("SetBodyMorph", "NiOverride", papyrusNiOverride::SetBodyMorph, registry)); + + registry->RegisterFunction( + new NativeFunction3("GetBodyMorph", "NiOverride", papyrusNiOverride::GetBodyMorph, registry)); + + registry->RegisterFunction( + new NativeFunction3("ClearBodyMorph", "NiOverride", papyrusNiOverride::ClearBodyMorph, registry)); + + registry->RegisterFunction( + new NativeFunction2("HasBodyMorphKey", "NiOverride", papyrusNiOverride::HasBodyMorphKey, registry)); + + registry->RegisterFunction( + new NativeFunction2("ClearBodyMorphKeys", "NiOverride", papyrusNiOverride::ClearBodyMorphKeys, registry)); + + registry->RegisterFunction( + new NativeFunction2("HasBodyMorphName", "NiOverride", papyrusNiOverride::HasBodyMorphName, registry)); + + registry->RegisterFunction( + new NativeFunction2("ClearBodyMorphNames", "NiOverride", papyrusNiOverride::ClearBodyMorphNames, registry)); + + registry->RegisterFunction( + new NativeFunction1("ClearMorphs", "NiOverride", papyrusNiOverride::ClearMorphs, registry)); + + registry->RegisterFunction( + new NativeFunction1("UpdateModelWeight", "NiOverride", papyrusNiOverride::UpdateModelWeight, registry)); + + registry->RegisterFunction( + new NativeFunction1, TESObjectREFR*>("GetMorphNames", "NiOverride", papyrusNiOverride::GetMorphNames, registry)); + + registry->RegisterFunction( + new NativeFunction2, TESObjectREFR*, BSFixedString>("GetMorphKeys", "NiOverride", papyrusNiOverride::GetMorphKeys, registry)); + + registry->RegisterFunction( + new NativeFunction0>("GetMorphedReferences", "NiOverride", papyrusNiOverride::GetMorphedReferences, registry)); + + registry->RegisterFunction( + new NativeFunction2("ForEachMorphedReference", "NiOverride", papyrusNiOverride::ForEachMorphedReference, registry)); + + + // Unique Item manipulation + registry->RegisterFunction( + new NativeFunction4("GetItemUniqueID", "NiOverride", papyrusNiOverride::GetItemUniqueID, registry)); + + registry->RegisterFunction( + new NativeFunction2("GetObjectUniqueID", "NiOverride", papyrusNiOverride::GetObjectUniqueID, registry)); + + registry->RegisterFunction( + new NativeFunction1("GetFormFromUniqueID", "NiOverride", papyrusNiOverride::GetFormFromUniqueID, registry)); + + registry->RegisterFunction( + new NativeFunction1("GetOwnerOfUniqueID", "NiOverride", papyrusNiOverride::GetOwnerOfUniqueID, registry)); + + registry->RegisterFunction( + new NativeFunction3("SetItemDyeColor", "NiOverride", papyrusNiOverride::SetItemDyeColor, registry)); + + registry->RegisterFunction( + new NativeFunction2("GetItemDyeColor", "NiOverride", papyrusNiOverride::GetItemDyeColor, registry)); + + registry->RegisterFunction( + new NativeFunction2("ClearItemDyeColor", "NiOverride", papyrusNiOverride::ClearItemDyeColor, registry)); + + registry->RegisterFunction( + new NativeFunction2("UpdateItemDyeColor", "NiOverride", papyrusNiOverride::UpdateItemDyeColor, registry)); + + registry->RegisterFunction( + new NativeFunction0("EnableTintTextureCache", "NiOverride", papyrusNiOverride::EnableTintTextureCache, registry)); + + registry->RegisterFunction( + new NativeFunction0("ReleaseTintTextureCache", "NiOverride", papyrusNiOverride::ReleaseTintTextureCache, registry)); + + registry->RegisterFunction( + new NativeFunction1("IsFormDye", "NiOverride", papyrusNiOverride::IsFormDye, registry)); + + registry->RegisterFunction( + new NativeFunction1("GetFormDyeColor", "NiOverride", papyrusNiOverride::GetFormDyeColor, registry)); + + registry->RegisterFunction( + new NativeFunction2("RegisterFormDyeColor", "NiOverride", papyrusNiOverride::RegisterFormDyeColor, registry)); + + registry->RegisterFunction( + new NativeFunction1("UnregisterFormDyeColor", "NiOverride", papyrusNiOverride::UnregisterFormDyeColor, registry)); + + // Position Transforms + registry->RegisterFunction( + new NativeFunction5("HasNodeTransformPosition", "NiOverride", papyrusNiOverride::HasNodeTransformPosition, registry)); + + registry->RegisterFunction( + new NativeFunction6>("AddNodeTransformPosition", "NiOverride", papyrusNiOverride::AddNodeTransformPosition, registry)); + + registry->RegisterFunction( + new NativeFunction5, TESObjectREFR *, bool, bool, BSFixedString, BSFixedString>("GetNodeTransformPosition", "NiOverride", papyrusNiOverride::GetNodeTransformPosition, registry)); + + registry->RegisterFunction( + new NativeFunction5("RemoveNodeTransformPosition", "NiOverride", papyrusNiOverride::RemoveNodeTransformPosition, registry)); + + // Scale Transforms + registry->RegisterFunction( + new NativeFunction5("HasNodeTransformScale", "NiOverride", papyrusNiOverride::HasNodeTransformScale, registry)); + + registry->RegisterFunction( + new NativeFunction6("AddNodeTransformScale", "NiOverride", papyrusNiOverride::AddNodeTransformScale, registry)); + + registry->RegisterFunction( + new NativeFunction5("GetNodeTransformScale", "NiOverride", papyrusNiOverride::GetNodeTransformScale, registry)); + + registry->RegisterFunction( + new NativeFunction5("RemoveNodeTransformScale", "NiOverride", papyrusNiOverride::RemoveNodeTransformScale, registry)); + + registry->RegisterFunction( + new NativeFunction5("HasNodeTransformRotation", "NiOverride", papyrusNiOverride::HasNodeTransformRotation, registry)); + + // Rotation Transforms + registry->RegisterFunction( + new NativeFunction6>("AddNodeTransformRotation", "NiOverride", papyrusNiOverride::AddNodeTransformRotation, registry)); + + registry->RegisterFunction( + new NativeFunction6, TESObjectREFR *, bool, bool, BSFixedString, BSFixedString, UInt32>("GetNodeTransformRotation", "NiOverride", papyrusNiOverride::GetNodeTransformRotation, registry)); + + registry->RegisterFunction( + new NativeFunction5("RemoveNodeTransformRotation", "NiOverride", papyrusNiOverride::RemoveNodeTransformRotation, registry)); + + registry->RegisterFunction( + new NativeFunction1("UpdateAllReferenceTransforms", "NiOverride", papyrusNiOverride::UpdateAllReferenceTransforms, registry)); + + registry->RegisterFunction( + new NativeFunction4("UpdateNodeTransform", "NiOverride", papyrusNiOverride::UpdateNodeTransform, registry)); + + registry->RegisterFunction( + new NativeFunction1("RemoveAllReferenceTransforms", "NiOverride", papyrusNiOverride::RemoveAllReferenceTransforms, registry)); + + registry->RegisterFunction( + new NativeFunction0("RemoveAllTransforms", "NiOverride", papyrusNiOverride::RemoveAllTransforms, registry)); + + registry->RegisterFunction( + new NativeFunction3, VMArray, float>("GetInverseTransform", "NiOverride", papyrusNiOverride::GetInverseTransform, registry)); + + registry->RegisterFunction( + new NativeFunction5("SetNodeDestination", "NiOverride", papyrusNiOverride::SetNodeDestination, registry)); + + registry->RegisterFunction( + new NativeFunction4("RemoveNodeDestination", "NiOverride", papyrusNiOverride::RemoveNodeDestination, registry)); + + registry->RegisterFunction( + new NativeFunction4("GetNodeDestination", "NiOverride", papyrusNiOverride::GetNodeDestination, registry)); + + registry->RegisterFunction( + new NativeFunction3, TESObjectREFR *, bool, bool>("GetNodeTransformNames", "NiOverride", papyrusNiOverride::GetNodeTransformNames, registry)); + + registry->RegisterFunction( + new NativeFunction4, TESObjectREFR *, bool, bool, BSFixedString>("GetNodeTransformKeys", "NiOverride", papyrusNiOverride::GetNodeTransformKeys, registry)); + + + // Extra Data Testers + registry->RegisterFunction( + new NativeFunction4("GetBooleanExtraData", "NiOverride", papyrusNiOverride::GetExtraData, registry)); + + registry->RegisterFunction( + new NativeFunction4("GetIntegerExtraData", "NiOverride", papyrusNiOverride::GetExtraData, registry)); + + registry->RegisterFunction( + new NativeFunction4, TESObjectREFR *, bool, BSFixedString, BSFixedString>("GetIntegersExtraData", "NiOverride", papyrusNiOverride::GetExtraData>, registry)); + + registry->RegisterFunction( + new NativeFunction4("GetFloatExtraData", "NiOverride", papyrusNiOverride::GetExtraData, registry)); + + registry->RegisterFunction( + new NativeFunction4, TESObjectREFR *, bool, BSFixedString, BSFixedString>("GetFloatsExtraData", "NiOverride", papyrusNiOverride::GetExtraData>, registry)); + + registry->RegisterFunction( + new NativeFunction4("GetStringExtraData", "NiOverride", papyrusNiOverride::GetExtraData, registry)); + + registry->RegisterFunction( + new NativeFunction4, TESObjectREFR *, bool, BSFixedString, BSFixedString>("GetStringsExtraData", "NiOverride", papyrusNiOverride::GetExtraData>, registry)); + + // Extra data manipulation + registry->SetFunctionFlags("NiOverride", "GetBooleanExtraData", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetIntegerExtraData", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetIntegersExtraData", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetFloatExtraData", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetFloatsExtraData", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetStringExtraData", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetStringsExtraData", VMClassRegistry::kFunctionFlag_NoWait); + + // Overlay numerics + registry->SetFunctionFlags("NiOverride", "GetNumBodyOverlays", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNumHandOverlays", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNumFeetOverlays", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNumFaceOverlays", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "GetNumSpellBodyOverlays", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNumSpellHandOverlays", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNumSpellFeetOverlays", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNumSpellFaceOverlays", VMClassRegistry::kFunctionFlag_NoWait); + + // Armor based overrides + registry->SetFunctionFlags("NiOverride", "HasOverride", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "AddOverrideFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddOverrideInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddOverrideBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddOverrideString", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddOverrideTextureSet", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "GetOverrideFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetOverrideInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetOverrideBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetOverrideString", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetOverrideTextureSet", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "ApplyOverrides", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "GetPropertyFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetPropertyInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetPropertyBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetPropertyString", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "HasArmorAddonNode", VMClassRegistry::kFunctionFlag_NoWait); + + // Node based overrides + registry->SetFunctionFlags("NiOverride", "HasNodeOverride", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "AddNodeOverrideFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddNodeOverrideInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddNodeOverrideBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddNodeOverrideString", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddNodeOverrideTextureSet", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "GetNodeOverrideFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNodeOverrideInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNodeOverrideBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNodeOverrideString", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNodeOverrideTextureSet", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "GetNodePropertyFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNodePropertyInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNodePropertyBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNodePropertyString", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "ApplyNodeOverrides", VMClassRegistry::kFunctionFlag_NoWait); + + // Weapon based overrides + registry->SetFunctionFlags("NiOverride", "HasWeaponOverride", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "AddWeaponOverrideFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddWeaponOverrideInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddWeaponOverrideBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddWeaponOverrideString", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddWeaponOverrideTextureSet", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "GetWeaponOverrideFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetWeaponOverrideInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetWeaponOverrideBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetWeaponOverrideString", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetWeaponOverrideTextureSet", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "ApplyWeaponOverrides", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "GetWeaponPropertyFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetWeaponPropertyInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetWeaponPropertyBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetWeaponPropertyString", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "HasWeaponNode", VMClassRegistry::kFunctionFlag_NoWait); + + // Skin based overrides + registry->SetFunctionFlags("NiOverride", "HasSkinOverride", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "AddSkinOverrideFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddSkinOverrideInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddSkinOverrideBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddSkinOverrideString", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddSkinOverrideTextureSet", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "GetSkinOverrideFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetSkinOverrideInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetSkinOverrideBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetSkinOverrideString", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetSkinOverrideTextureSet", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "ApplySkinOverrides", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "GetSkinPropertyFloat", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetSkinPropertyInt", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetSkinPropertyBool", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetSkinPropertyString", VMClassRegistry::kFunctionFlag_NoWait); + + // Armor based overrides + registry->SetFunctionFlags("NiOverride", "RemoveAllOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllReferenceOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllArmorOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllArmorAddonOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllArmorAddonNodeOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveOverride", VMClassRegistry::kFunctionFlag_NoWait); + + // Node based overrides + registry->SetFunctionFlags("NiOverride", "RemoveAllNodeOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllReferenceNodeOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllNodeNameOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveNodeOverride", VMClassRegistry::kFunctionFlag_NoWait); + + // Weapon based overrides + registry->SetFunctionFlags("NiOverride", "RemoveAllWeaponBasedOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllReferenceWeaponOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllWeaponOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllWeaponNodeOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveWeaponOverride", VMClassRegistry::kFunctionFlag_NoWait); + + // Skin based overrides + registry->SetFunctionFlags("NiOverride", "RemoveAllSkinBasedOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllReferenceSkinOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllSkinOverrides", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveSkinOverride", VMClassRegistry::kFunctionFlag_NoWait); + + + registry->SetFunctionFlags("NiOverride", "AddOverlays", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveOverlays", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "HasOverlays", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RevertOverlays", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RevertOverlay", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RevertHeadOverlays", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RevertHeadOverlay", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "HasBodyMorph", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "SetBodyMorph", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetBodyMorph", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "ClearBodyMorph", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "HasBodyMorphName", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "ClearBodyMorphNames", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "HasBodyMorphKey", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "ClearBodyMorphKeys", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "ClearMorphs", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "UpdateModelWeight", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "EnableTintTextureCache", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "ReleaseTintTextureCache", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "SetItemDyeColor", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetItemDyeColor", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "ClearItemDyeColor", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "UpdateItemDyeColor", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "IsFormDye", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetFormDyeColor", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RegisterFormDyeColor", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "UnregisterFormDyeColor", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "HasNodeTransformPosition", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddNodeTransformPosition", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNodeTransformPosition", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveNodeTransformPosition", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "HasNodeTransformScale", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddNodeTransformScale", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNodeTransformScale", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveNodeTransformScale", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "HasNodeTransformRotation", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "AddNodeTransformRotation", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNodeTransformRotation", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveNodeTransformRotation", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "GetNodeDestination", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveNodeDestination", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "GetNodeTransformNames", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetNodeTransformKeys", VMClassRegistry::kFunctionFlag_NoWait); + + registry->SetFunctionFlags("NiOverride", "UpdateAllReferenceTransforms", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllReferenceTransforms", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "RemoveAllTransforms", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "UpdateNodeTransform", VMClassRegistry::kFunctionFlag_NoWait); + registry->SetFunctionFlags("NiOverride", "GetInverseTransform", VMClassRegistry::kFunctionFlag_NoWait); +} \ No newline at end of file diff --git a/skee/PapyrusNiOverride.h b/skee/PapyrusNiOverride.h new file mode 100644 index 0000000..a4f294e --- /dev/null +++ b/skee/PapyrusNiOverride.h @@ -0,0 +1,93 @@ +#pragma once + +class VMClassRegistry; +struct StaticFunctionTag; +class TESObjectREFR; +class TESObjectARMO; +class TESObjectARMA; +class TESObjectWEAP; + +#include "skse64/GameTypes.h" + +namespace papyrusNiOverride +{ + void RegisterFuncs(VMClassRegistry* registry); + + // Armor Overrides + bool HasOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt32 key, UInt32 index); + + template + void AddOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt32 key, UInt32 index, T dataType, bool persist); + + template + T GetOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt32 key, UInt32 index); + + template + T GetArmorAddonProperty(StaticFunctionTag* base, TESObjectREFR * refr, bool firstPerson, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt32 key, UInt32 index); + + + // Node Overrides + bool HasNodeOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt32 key, UInt32 index); + + template + void AddNodeOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt32 key, UInt32 index, T dataType, bool persist); + + template + T GetNodeOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt32 key, UInt32 index); + + template + T GetNodeProperty(StaticFunctionTag* base, TESObjectREFR * refr, bool firstPerson, BSFixedString nodeName, UInt32 key, UInt32 index); + + + // Weapon overrides + bool HasWeaponOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt32 key, UInt32 index); + + template + void AddWeaponOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt32 key, UInt32 index, T dataType, bool persist); + + template + T GetWeaponOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt32 key, UInt32 index); + + template + T GetWeaponProperty(StaticFunctionTag* base, TESObjectREFR * refr, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt32 key, UInt32 index); + + + void RemoveAllOverrides(StaticFunctionTag* base); + void RemoveAllReferenceOverrides(StaticFunctionTag* base, TESObjectREFR * refr); + void RemoveAllArmorOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor); + void RemoveAllArmorAddonOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon); + void RemoveAllArmorAddonNodeOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName); + void RemoveArmorAddonOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, TESObjectARMO * armor, TESObjectARMA * addon, BSFixedString nodeName, UInt32 key, UInt32 index); + + void RemoveAllNodeOverrides(StaticFunctionTag* base); + void RemoveAllReferenceNodeOverrides(StaticFunctionTag* base, TESObjectREFR * refr); + void RemoveAllNodeNameOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, BSFixedString nodeName); + void RemoveNodeOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, BSFixedString nodeName, UInt32 key, UInt32 index); + + void RemoveAllWeaponBasedOverrides(StaticFunctionTag* base); + void RemoveAllReferenceWeaponOverrides(StaticFunctionTag* base, TESObjectREFR * refr); + void RemoveAllWeaponOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon); + void RemoveAllWeaponNodeOverrides(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName); + void RemoveWeaponOverride(StaticFunctionTag* base, TESObjectREFR * refr, bool isFemale, bool firstPerson, TESObjectWEAP * weapon, BSFixedString nodeName, UInt32 key, UInt32 index); + + + UInt32 GetNumBodyOverlays(StaticFunctionTag* base); + UInt32 GetNumHandOverlays(StaticFunctionTag* base); + UInt32 GetNumFeetOverlays(StaticFunctionTag* base); + UInt32 GetNumFaceOverlays(StaticFunctionTag* base); + + UInt32 GetNumSpellBodyOverlays(StaticFunctionTag* base); + UInt32 GetNumSpellHandOverlays(StaticFunctionTag* base); + UInt32 GetNumSpellFeetOverlays(StaticFunctionTag* base); + UInt32 GetNumSpellFaceOverlays(StaticFunctionTag* base); + + void AddOverlays(StaticFunctionTag* base, TESObjectREFR * refr); + bool HasOverlays(StaticFunctionTag* base, TESObjectREFR * refr); + void RemoveOverlays(StaticFunctionTag* base, TESObjectREFR * refr); + void RevertOverlays(StaticFunctionTag* base, TESObjectREFR * refr); + void RevertOverlay(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString nodeName, UInt32 armorMask, UInt32 addonMask); + + void SetBodyMorph(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString morphName, BSFixedString keyName, float value); + float GetBodyMorph(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString morphName, BSFixedString keyName); + void ClearBodyMorph(StaticFunctionTag* base, TESObjectREFR * refr, BSFixedString morphName, BSFixedString keyName); +} diff --git a/skee/PartHandler.cpp b/skee/PartHandler.cpp new file mode 100644 index 0000000..ca52eda --- /dev/null +++ b/skee/PartHandler.cpp @@ -0,0 +1,169 @@ +#include "PartHandler.h" +#include "FileUtils.h" + +#include "skse64/GameReferences.h" +#include "skse64/GameObjects.h" +#include "skse64/GameForms.h" +#include "skse64/GameRTTI.h" + +#include "skse64/GameStreams.h" + +void PartSet::AddPart(UInt32 key, BGSHeadPart* part) +{ + iterator it = find(key); + if(it != end()) { + it->second.partList.push_back(part); + } else { + PartEntry partEntry; + partEntry.partList.push_back(part); + insert(std::make_pair(key, partEntry)); + } +} + +void PartSet::SetDefaultPart(UInt32 key, BGSHeadPart* part) +{ + iterator it = find(key); + if(it != end()) { + it->second.defaultPart = part; + } else { + PartEntry partEntry; + partEntry.defaultPart = part; + insert(std::make_pair(key, partEntry)); + } +} + +void PartSet::Visit(Visitor & visitor) +{ + for (iterator it = begin(); it != end(); ++it) + { + for(HeadPartList::iterator pit = it->second.partList.begin(); pit != it->second.partList.end(); ++pit) + { + if(visitor.Accept(it->first, *pit)) + return; + } + } +} + +HeadPartList * PartSet::GetPartList(UInt32 key) +{ + iterator it = find(key); + if(it != end()) { + return &it->second.partList; + } + + return NULL; +} + +BGSHeadPart * PartSet::GetDefaultPart(UInt32 key) +{ + iterator it = find(key); + if(it != end()) { + return it->second.defaultPart; + } + + return NULL; +} + +BGSHeadPart * PartSet::GetPartByIndex(HeadPartList * headPartList, UInt32 index) +{ + if(index < headPartList->size()) + return headPartList->at(index); + + return NULL; +} + +SInt32 PartSet::GetPartIndex(HeadPartList * headPartList, BGSHeadPart * headPart) +{ + SInt32 partIndex = -1; + for(UInt32 p = 0; p < headPartList->size(); p++) + { + BGSHeadPart * partMatch = headPartList->at(p); + if(partMatch->partName == headPart->partName) { + partIndex = p; + break; + } + } + + return partIndex; +} + +void PartSet::Revert() +{ + for (iterator it = begin(); it != end(); ++it) + { + it->second.partList.clear(); + } + + clear(); +} + + +void ReadPartReplacements(std::string fixedPath, std::string modPath, std::string fileName) +{ + std::string fullPath = fixedPath + modPath + fileName; + BSResourceNiBinaryStream file(fullPath.c_str()); + if (!file.IsValid()) { + return; + } + + UInt32 lineCount = 0; + UInt8 gender = 0; + std::string str = ""; + while (BSReadLine(&file, &str)) + { + lineCount++; + str = std::trim(str); + if (str.length() == 0) + continue; + if (str.at(0) == '#') + continue; + + if (str.at(0) == '[') + { + str.erase(0, 1); + if (_strnicmp(str.c_str(), "Male", 4) == 0) + gender = 0; + if (_strnicmp(str.c_str(), "Female", 6) == 0) + gender = 1; + continue; + } + + std::vector side = std::explode(str, '='); + if (side.size() < 2) { + _ERROR("%s Error - Line (%d) race from %s has no left-hand side.", __FUNCTION__, lineCount, fullPath.c_str()); + continue; + } + + std::string lSide = std::trim(side[0]); + std::string rSide = std::trim(side[1]); + + BGSHeadPart * facePart = GetHeadPartByName(rSide); + TESRace * race = GetRaceByName(lSide); + if (!race) { + _WARNING("%s Warning - Line (%d) race %s from %s is not a valid race.", __FUNCTION__, lineCount, lSide.c_str(), fullPath.c_str()); + continue; + } + + if (!facePart) { + _WARNING("%s Warning - Line (%d) head part %s from %s is not a valid head part.", __FUNCTION__, lineCount, rSide.c_str(), fullPath.c_str()); + continue; + } + + auto charGenData = race->chargenData[gender]; + if (!charGenData) { + _ERROR("%s Error - Line (%d) race %s from %s has no CharGen data.", __FUNCTION__, lineCount, lSide.c_str(), fullPath.c_str()); + continue; + } + + if (charGenData->headParts) { + for (UInt32 i = 0; i < charGenData->headParts->count; i++) { + BGSHeadPart * headPart = NULL; + if (charGenData->headParts->GetNthItem(i, headPart)) { + if (headPart->type == facePart->type) { + charGenData->headParts->entries[i] = facePart; + } + } + } + } + } +} \ No newline at end of file diff --git a/skee/PartHandler.h b/skee/PartHandler.h new file mode 100644 index 0000000..d660be1 --- /dev/null +++ b/skee/PartHandler.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#include "FileUtils.h" + +class BGSHeadPart; +class SliderArray; +class RaceMenuSlider; + +typedef std::vector HeadPartList; + +class PartEntry +{ +public: + BGSHeadPart * defaultPart; + HeadPartList partList; +}; + +class PartSet : public std::unordered_map +{ +public: + class Visitor + { + public: + virtual bool Accept(UInt32 key, BGSHeadPart * part) { return false; }; + }; + + void LoadSliders(SliderArray * sliderArray, RaceMenuSlider * slider); + + void AddPart(UInt32 key, BGSHeadPart* part); + void Visit(Visitor & visitor); + + HeadPartList * GetPartList(UInt32 key); + BGSHeadPart * GetDefaultPart(UInt32 key); + void SetDefaultPart(UInt32 key, BGSHeadPart* part); + SInt32 GetPartIndex(HeadPartList * headPartList, BGSHeadPart * headPart); + BGSHeadPart * GetPartByIndex(HeadPartList * headPartList, UInt32 index); + + void Revert(); +}; + + +void ReadPartReplacements(std::string fixedPath, std::string modPath, std::string fileName); \ No newline at end of file diff --git a/skee/Resource.rc b/skee/Resource.rc new file mode 100644 index 0000000..cdf9535 Binary files /dev/null and b/skee/Resource.rc differ diff --git a/skee/SKEEHooks.cpp b/skee/SKEEHooks.cpp new file mode 100644 index 0000000..f89123a --- /dev/null +++ b/skee/SKEEHooks.cpp @@ -0,0 +1,1174 @@ +#include "skse64_common/SafeWrite.h" +#include "skse64/PluginAPI.h" +#include "skse64/PapyrusVM.h" + +#include "skse64/GameData.h" +#include "skse64/GameRTTI.h" + +#include "skse64/NiExtraData.h" +#include "skse64/NiNodes.h" +#include "skse64/NiRTTI.h" +#include "skse64/NiNodes.h" +#include "skse64/NiMaterial.h" +#include "skse64/NiProperties.h" + +#include "SKEEHooks.h" +#include "PapyrusNiOverride.h" + +#include "OverlayInterface.h" +#include "OverrideInterface.h" +#include "BodyMorphInterface.h" +#include "TintMaskInterface.h" +#include "ItemDataInterface.h" + +#include "MorphHandler.h" +#include "PartHandler.h" + +#include "SkeletonExtender.h" +#include "ShaderUtilities.h" + +#include + +#include "skse64_common/Relocation.h" +#include "skse64_common/BranchTrampoline.h" +#include "skse64_common/SafeWrite.h" +#include "xbyak/xbyak.h" + +extern SKSETaskInterface * g_task; + +extern ItemDataInterface g_itemDataInterface; +extern TintMaskInterface g_tintMaskInterface; +extern BodyMorphInterface g_bodyMorphInterface; +extern OverlayInterface g_overlayInterface; +extern OverrideInterface g_overrideInterface; + +extern bool g_enableFaceOverlays; +extern UInt32 g_numFaceOverlays; + +extern bool g_playerOnly; +extern UInt32 g_numSpellFaceOverlays; + +extern bool g_immediateArmor; +extern bool g_immediateFace; + +extern bool g_enableEquippableTransforms; + +Actor * g_weaponHookActor = NULL; +TESObjectWEAP * g_weaponHookWeapon = NULL; +UInt32 g_firstPerson = 0; + +typedef NiAVObject * (*_CreateWeaponNode)(UInt32 * unk1, UInt32 unk2, Actor * actor, UInt32 ** unk4, UInt32 * unk5); +extern const _CreateWeaponNode CreateWeaponNode = (_CreateWeaponNode)0x0046F530; + +void __stdcall InstallWeaponHook(Actor * actor, TESObjectWEAP * weapon, NiAVObject * resultNode1, NiAVObject * resultNode2, UInt32 firstPerson) +{ + if (!actor) { +#ifdef _DEBUG + _MESSAGE("%s - Error no reference found skipping overrides.", __FUNCTION__); +#endif + return; + } + if (!weapon) { +#ifdef _DEBUG + _MESSAGE("%s - Error no weapon found skipping overrides.", __FUNCTION__); +#endif + return; + } + + std::vector flattenedWeapons; + flattenedWeapons.push_back(weapon); + TESObjectWEAP * templateWeapon = weapon->templateForm; + while (templateWeapon) { + flattenedWeapons.push_back(templateWeapon); + templateWeapon = templateWeapon->templateForm; + } + + // Apply top-most parent properties first + for (std::vector::reverse_iterator it = flattenedWeapons.rbegin(); it != flattenedWeapons.rend(); ++it) + { + if (resultNode1) + g_overrideInterface.ApplyWeaponOverrides(actor, firstPerson == 1 ? true : false, weapon, resultNode1, true); + if (resultNode2) + g_overrideInterface.ApplyWeaponOverrides(actor, firstPerson == 1 ? true : false, weapon, resultNode2, true); + } +} + +#ifdef FIXME +// Store stack values here, they would otherwise be lost +enum +{ + kWeaponHook_EntryStackOffset1 = 0x40, + kWeaponHook_EntryStackOffset2 = 0x20, + kWeaponHook_VarObj = 0x04 +}; + +static const UInt32 kInstallWeaponFPHook_Base = 0x0046F870 + 0x143; +static const UInt32 kInstallWeaponFPHook_Entry_retn = kInstallWeaponFPHook_Base + 0x5; + +__declspec(naked) void CreateWeaponNodeFPHook_Entry(void) +{ + __asm + { + pushad + mov eax, [esp + kWeaponHook_EntryStackOffset1 + kWeaponHook_VarObj + kWeaponHook_EntryStackOffset2] + mov g_weaponHookWeapon, eax + mov g_weaponHookActor, edi + mov g_firstPerson, 1 + popad + + call[CreateWeaponNode] + + mov g_weaponHookWeapon, NULL + mov g_weaponHookActor, NULL + + jmp[kInstallWeaponFPHook_Entry_retn] + } +} + +static const UInt32 kInstallWeapon3PHook_Base = 0x0046F870 + 0x17E; +static const UInt32 kInstallWeapon3PHook_Entry_retn = kInstallWeapon3PHook_Base + 0x5; + +__declspec(naked) void CreateWeaponNode3PHook_Entry(void) +{ + __asm + { + pushad + mov eax, [esp + kWeaponHook_EntryStackOffset1 + kWeaponHook_VarObj + kWeaponHook_EntryStackOffset2] + mov g_weaponHookWeapon, eax + mov g_weaponHookActor, edi + mov g_firstPerson, 0 + popad + + call[CreateWeaponNode] + + mov g_weaponHookWeapon, NULL + mov g_weaponHookActor, NULL + + jmp[kInstallWeapon3PHook_Entry_retn] + } +} + +// Recall stack values here +static const UInt32 kInstallWeaponHook_Base = 0x0046F530 + 0x28A; +static const UInt32 kInstallWeaponHook_Entry_retn = kInstallWeaponHook_Base + 0x5; + +__declspec(naked) void InstallWeaponNodeHook_Entry(void) +{ + __asm + { + pushad + mov eax, g_firstPerson + push eax + push ebp + push edx + mov eax, g_weaponHookWeapon + push eax + mov eax, g_weaponHookActor + push eax + call InstallWeaponHook + popad + + push ebx + push ecx + push edx + push ebp + push esi + + jmp[kInstallWeaponHook_Entry_retn] + } +} + +UInt8 * g_unk1 = (UInt8*)0x001240DE8; +UInt32 * g_unk2 = (UInt32*)0x001310588; + +#endif + +NiAVObject * ArmorAddonTree::CreateArmorNode(AddonTreeParameters * params, NiNode * unk1, UInt32 unk2, UInt32 unk3, UInt32 unk4, UInt32 unk5) +{ + NiNode * boneTree = this->unk04; + NiAVObject * retVal = CALL_MEMBER_FN(this, CreateArmorNode)(unk1, unk2, unk3, unk4, unk5); + + try { + TESObjectREFR * reference = nullptr; + UInt32 handle = this->unkA88; + LookupREFRByHandle(&handle, &reference); + if(reference) + InstallArmorAddonHook(reference, params, boneTree, retVal); + } + catch (...) { + _MESSAGE("%s - unexpected error occured when installing body overlays.", __FUNCTION__); + } + + return retVal; +} + +#ifdef FIXME +static const UInt32 kCreateArmorNode = GetFnAddr(&ArmorAddonTree::CreateArmorNode); +static const UInt32 kInstallArmorHook_Base = 0x00470050 + 0x902; +static const UInt32 kInstallArmorHook_Entry_retn = kInstallArmorHook_Base + 0x5; + +enum +{ + kArmorHook_EntryStackOffset = 0x04, + kArmorHook_VarObj = 0x14 +}; + +__declspec(naked) void InstallArmorNodeHook_Entry(void) +{ + __asm + { + lea eax, [ebx] + push eax + call[kCreateArmorNode] + + jmp[kInstallArmorHook_Entry_retn] + } +} +#endif + + +void __stdcall InstallArmorAddonHook(TESObjectREFR * refr, AddonTreeParameters * params, NiNode * boneTree, NiAVObject * resultNode) +{ + if (!refr) { +#ifdef _DEBUG + _ERROR("%s - Error no reference found skipping overlays.", __FUNCTION__); +#endif + return; + } + if (!params) { +#ifdef _DEBUG + _ERROR("%s - Error no armor parameters found, skipping overlays.", __FUNCTION__); +#endif + return; + } + if (!params->armor || !params->addon) { +#ifdef _DEBUG + _ERROR("%s - Armor or ArmorAddon found, skipping overlays.", __FUNCTION__); +#endif + return; + } + if (!boneTree) { +#ifdef _DEBUG + _ERROR("%s - Error no bone tree found, skipping overlays.", __FUNCTION__); +#endif + return; + } + if (!resultNode) { +#ifdef _DEBUG + UInt32 addonFormid = params->addon ? params->addon->formID : 0; + UInt32 armorFormid = params->armor ? params->armor->formID : 0; + _ERROR("%s - Error no node found on Reference (%08X) while attaching ArmorAddon (%08X) of Armor (%08X)", __FUNCTION__, refr->formID, addonFormid, armorFormid); +#endif + return; + } + + NiNode * node3P = refr->GetNiRootNode(0); + NiNode * node1P = refr->GetNiRootNode(1); + + // Go up to the root and see which one it is + NiNode * rootNode = nullptr; + NiNode * parent = boneTree->m_parent; + do + { + if (parent == node1P) + rootNode = node1P; + if (parent == node3P) + rootNode = node3P; + parent = parent->m_parent; + } while (parent); + + bool isFirstPerson = (rootNode == node1P); + if (node1P == node3P) { // Theres only one node, theyre the same, no 1st person + isFirstPerson = false; + } + + if (rootNode != node1P && rootNode != node3P) { +#ifdef _DEBUG + _DMESSAGE("%s - Mismatching root nodes, bone tree not for this reference (%08X)", __FUNCTION__, refr->formID); +#endif + return; + } + + SkeletonExtender::Attach(refr, boneTree, resultNode); + +#ifdef _DEBUG + _ERROR("%s - Applying Vertex Diffs on Reference (%08X) ArmorAddon (%08X) of Armor (%08X)", __FUNCTION__, refr->formID, params->addon->formID, params->armor->formID); +#endif + + // Apply no v-diffs if theres no morphs at all + if (g_bodyMorphInterface.HasMorphs(refr)) { + NiAutoRefCounter rf(resultNode); + g_bodyMorphInterface.ApplyVertexDiff(refr, resultNode, true); + } + + + if (g_enableEquippableTransforms) + { + NiAutoRefCounter rf(resultNode); + SkeletonExtender::AddTransforms(refr, isFirstPerson, isFirstPerson ? node1P : node3P, resultNode); + } + + if ((refr == (*g_thePlayer) && g_playerOnly) || !g_playerOnly || g_overlayInterface.HasOverlays(refr)) + { + UInt32 armorMask = params->armor->bipedObject.GetSlotMask(); + UInt32 addonMask = params->addon->biped.GetSlotMask(); + g_overlayInterface.BuildOverlays(armorMask, addonMask, refr, boneTree, resultNode); + } + + { + NiAutoRefCounter rf(resultNode); + g_overrideInterface.ApplyOverrides(refr, params->armor, params->addon, resultNode, g_immediateArmor); + } + + { + UInt32 armorMask = params->armor->bipedObject.GetSlotMask(); + UInt32 addonMask = params->addon->biped.GetSlotMask(); + NiAutoRefCounter rf(resultNode); + g_overrideInterface.ApplySkinOverrides(refr, isFirstPerson, params->armor, params->addon, armorMask & addonMask, resultNode, g_immediateArmor); + } + + UInt32 armorMask = params->armor->bipedObject.GetSlotMask(); + + std::function overrideFunc = [=](ColorMap* colorMap) + { + Actor * actor = DYNAMIC_CAST(refr, TESForm, Actor); + if (actor) { + ModifiedItemIdentifier identifier; + identifier.SetSlotMask(armorMask); + ItemAttributeData * data = g_itemDataInterface.GetExistingData(actor, identifier); + if (data) { + if (data->m_tintData) { + *colorMap = data->m_tintData->m_colorMap; + } + } + } + }; + + g_task->AddTask(new NIOVTaskDeferredMask(refr, isFirstPerson, params->armor, params->addon, resultNode, overrideFunc)); +} + +void __stdcall InstallFaceOverlayHook(TESObjectREFR* refr, bool attemptUninstall, bool immediate) +{ + if (!refr) { +#ifdef _DEBUG + _DMESSAGE("%s - Warning no reference found skipping overlay", __FUNCTION__); +#endif + return; + } + + if (!refr->GetFaceGenNiNode()) { +#ifdef _DEBUG + _DMESSAGE("%s - Warning no head node for %08X skipping overlay", __FUNCTION__, refr->formID); +#endif + return; + } + +#ifdef _DEBUG + _DMESSAGE("%s - Attempting to install face overlay to %08X - Flags %08X", __FUNCTION__, refr->formID, refr->GetFaceGenNiNode()->m_flags); +#endif + + if ((refr == (*g_thePlayer) && g_playerOnly) || !g_playerOnly || g_overlayInterface.HasOverlays(refr)) + { + char buff[MAX_PATH]; + // Face + for (UInt32 i = 0; i < g_numFaceOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FACE_NODE, i); + if (attemptUninstall) { + SKSETaskUninstallOverlay * task = new SKSETaskUninstallOverlay(refr, buff); + if (immediate) { + task->Run(); + task->Dispose(); + } + else { + g_task->AddTask(task); + } + } + SKSETaskInstallFaceOverlay * task = new SKSETaskInstallFaceOverlay(refr, buff, FACE_MESH, BGSHeadPart::kTypeFace, BSShaderMaterial::kShaderType_FaceGen); + if (immediate) { + task->Run(); + task->Dispose(); + } + else { + g_task->AddTask(task); + } + } + for (UInt32 i = 0; i < g_numSpellFaceOverlays; i++) + { + memset(buff, 0, MAX_PATH); + sprintf_s(buff, MAX_PATH, FACE_NODE_SPELL, i); + if (attemptUninstall) { + SKSETaskUninstallOverlay * task = new SKSETaskUninstallOverlay(refr, buff); + if (immediate) { + task->Run(); + task->Dispose(); + } + else { + g_task->AddTask(task); + } + } + SKSETaskInstallFaceOverlay * task = new SKSETaskInstallFaceOverlay(refr, buff, FACE_MAGIC_MESH, BGSHeadPart::kTypeFace, BSShaderMaterial::kShaderType_FaceGen); + if (immediate) { + task->Run(); + task->Dispose(); + } + else { + g_task->AddTask(task); + } + } + } +} + +void ExtraContainerChangesData_Hooked::TransferItemUID_Hooked(BaseExtraList * extraList, TESForm * oldForm, TESForm * newForm, UInt32 unk1) +{ + CALL_MEMBER_FN(this, TransferItemUID)(extraList, oldForm, newForm, unk1); + + if (extraList) { + if (extraList->HasType(kExtraData_Rank) && !extraList->HasType(kExtraData_UniqueID)) { + CALL_MEMBER_FN(this, SetUniqueID)(extraList, oldForm, newForm); + ExtraRank * rank = static_cast(extraList->GetByType(kExtraData_Rank)); + ExtraUniqueID * uniqueId = static_cast(extraList->GetByType(kExtraData_UniqueID)); + if (rank && uniqueId) { + // Re-assign mapping + g_itemDataInterface.UpdateUIDByRank(rank->rank, uniqueId->uniqueId, uniqueId->ownerFormId); + } + } + } +} + +bool BSLightingShaderProperty_Hooked::HasFlags_Hooked(UInt32 flags) +{ + bool ret = HasFlags(flags); + if (material) { + if (material->GetShaderType() == BSShaderMaterial::kShaderType_FaceGen) { + NiExtraData * tintData = GetExtraData("TINT"); + if (tintData) { + NiBooleanExtraData * boolData = ni_cast(tintData, NiBooleanExtraData); + if (boolData) { + return boolData->m_data; + } + } + + return false; + } + } + + return ret; +} + +#ifdef FIXME +SInt32 TESNPC_Hooked::CreateHeadState_Hooked(Actor * actor, UInt32 unk1) +{ + SInt32 ret = CALL_MEMBER_FN(this, UpdateHeadState)(actor, unk1); + try { + __asm { + pushad + mov al, g_immediateFace + push eax + push 1 + mov eax, actor + push eax + call InstallFaceOverlayHook + popad + } + } + catch (...) { + if (actor) + _MESSAGE("%s - unexpected error while updating face overlay for actor %08X", __FUNCTION__, actor->formID); + } + return ret; +} + +SInt32 TESNPC_Hooked::UpdateHeadState_Hooked(Actor * actor, UInt32 unk1) +{ + SInt32 ret = CALL_MEMBER_FN(this, UpdateHeadState)(actor, unk1); + try { + __asm { + pushad + mov al, g_immediateFace + push eax + push 0 + mov eax, actor + push eax + call InstallFaceOverlayHook + popad + } + } + catch (...) { + if (actor) + _MESSAGE("%s - unexpected error while updating face overlay for actor %08X", __FUNCTION__, actor->formID); + } + return ret; +} +#endif + +class UniqueIDEventHandler : public BSTEventSink +{ +public: + virtual EventResult ReceiveEvent(TESUniqueIDChangeEvent * evn, EventDispatcher * dispatcher) + { + if (evn->oldOwnerFormId != 0) { + g_itemDataInterface.UpdateUID(evn->oldUniqueId, evn->oldOwnerFormId, evn->newUniqueId, evn->newOwnerFormId); + } + if (evn->newOwnerFormId == 0) { + g_itemDataInterface.EraseByUID(evn->oldUniqueId, evn->oldOwnerFormId); + } + return EventResult::kEvent_Continue; + } +}; + +UniqueIDEventHandler g_uniqueIdEventSink; + +#ifdef FIXME +void SkyrimVM_Hooked::RegisterEventSinks_Hooked() +{ + RegisterPapyrusFunctions(GetClassRegistry()); + + CALL_MEMBER_FN(this, RegisterEventSinks)(); + + if (g_changeUniqueIDEventDispatcher) + g_changeUniqueIDEventDispatcher->AddEventSink(&g_uniqueIdEventSink); + + g_tintMaskInterface.ReadTintData("Data\\SKSE\\Plugins\\NiOverride\\TintData\\", "*.xml"); +} +#endif + +#ifdef FIXME +#include "CDXNifScene.h" +#include "CDXNifMesh.h" + +#include "CDXCamera.h" + +#include "skse64/NiRenderer.h" +#include "skse64/NiTextures.h" +#include +#pragma comment(lib, "d3dx9.lib") + + +CDXNifScene g_World; +extern CDXModelViewerCamera g_Camera; // A model viewing camera + +void RaceSexMenu_Hooked::RenderMenu_Hooked(void) +{ + CALL_MEMBER_FN(this, RenderMenu)(); + + LPDIRECT3DDEVICE9 pDevice = NiDX9Renderer::GetSingleton()->m_pkD3DDevice9; + if (!pDevice) // This shouldnt happen + return; + + if (g_World.IsVisible() && g_World.GetTextureGroup()) { + NiRenderedTexture * renderedTexture = g_World.GetTextureGroup()->renderedTexture[0]; + if (renderedTexture) { + g_World.Begin(pDevice); + LPDIRECT3DSURFACE9 oldTarget; + pDevice->GetRenderTarget(0, &oldTarget); + LPDIRECT3DSURFACE9 pRenderSurface = NULL; + LPDIRECT3DTEXTURE9 pRenderTexture = (LPDIRECT3DTEXTURE9)((NiTexture::NiDX9TextureData*)renderedTexture->rendererData)->texture; + pRenderTexture->GetSurfaceLevel(0, &pRenderSurface); + pDevice->SetRenderTarget(0, pRenderSurface); + g_World.Render(pDevice); + pDevice->SetRenderTarget(0, oldTarget); + g_World.End(pDevice); + } + } +} +#endif + +bool CacheTempTRI(UInt32 hash, const char * originalPath); + +extern MorphHandler g_morphHandler; +extern PartSet g_partSet; +extern UInt32 g_customDataMax; +extern bool g_externalHeads; +extern bool g_extendedMorphs; +extern bool g_allowAllMorphs; + +static const UInt32 kInstallRegenHeadHook_Base = 0x005A4B80 + 0x49B; +static const UInt32 kInstallRegenHeadHook_Entry_retn = kInstallRegenHeadHook_Base + 0x8; + +enum +{ + kRegenHeadHook_EntryStackOffset1 = 0x20, + kRegenHeadHook_EntryStackOffset2 = 0xA8, + + kRegenHeadHook_VarHeadPart = 0x08, + kRegenHeadHook_VarFaceGenNode = 0x04, + kRegenHeadHook_VarNPC = 0x0C +}; + +void __stdcall ApplyPreset(TESNPC * npc, BSFaceGenNiNode * headNode, BGSHeadPart * headPart) +{ + g_morphHandler.ApplyPreset(npc, headNode, headPart); +} + +#ifdef FIXME +__declspec(naked) void InstallRegenHeadHook_Entry(void) +{ + __asm + { + pushad + mov eax, [esp + kRegenHeadHook_EntryStackOffset1 + kRegenHeadHook_EntryStackOffset2 + kRegenHeadHook_VarHeadPart] + push eax + mov eax, [esp + kRegenHeadHook_EntryStackOffset1 + kRegenHeadHook_EntryStackOffset2 + kRegenHeadHook_VarFaceGenNode + 0x04] + push eax + mov eax, [esp + kRegenHeadHook_EntryStackOffset1 + kRegenHeadHook_EntryStackOffset2 + kRegenHeadHook_VarNPC + 0x08] + push eax + call ApplyPreset + popad + + pop edi + pop ebx + add esp, 0xA0 + jmp[kInstallRegenHeadHook_Entry_retn] + } +} +#endif + +static const UInt32 kInstallForceRegenHeadHook_Base = 0x0056AEB0 + 0x35; +static const UInt32 kInstallForceRegenHeadHook_Entry_retn = kInstallForceRegenHeadHook_Base + 0x7; // Standard execution + +enum +{ + kForceRegenHeadHook_EntryStackOffset = 0x10, +}; + +bool * g_useFaceGenPreProcessedHeads = (bool*)0x0125D280; + +bool __stdcall IsHeadGenerated(TESNPC * npc) +{ + // For some reason the NPC vanilla preset data is reset when the actor is disable/enabled + auto presetData = g_morphHandler.GetPreset(npc); + if (presetData) { + if (!npc->faceMorph) + npc->faceMorph = (TESNPC::FaceMorphs*)Heap_Allocate(sizeof(TESNPC::FaceMorphs)); + + UInt32 i = 0; + for (auto & preset : presetData->presets) { + npc->faceMorph->presets[i] = preset; + i++; + } + + i = 0; + for (auto & morph : presetData->morphs) { + npc->faceMorph->option[i] = morph; + i++; + } + } + return (presetData != NULL) || !(*g_useFaceGenPreProcessedHeads); +} + +#ifdef FIXME +__declspec(naked) void InstallForceRegenHeadHook_Entry(void) +{ + __asm + { + push esi + call IsHeadGenerated + cmp al, 1 + jmp[kInstallForceRegenHeadHook_Entry_retn] + } +} +#endif + +SInt32 GetGameSettingInt(const char * key) +{ + Setting * setting = (*g_gameSettingCollection)->Get(key); + if (setting && setting->GetType() == Setting::kType_Integer) + return setting->data.s32; + + return 0; +} + +typedef void(*_ClearFaceGenCache)(); +const _ClearFaceGenCache ClearFaceGenCache = (_ClearFaceGenCache)0x00886B50; + +void _cdecl ClearFaceGenCache_Hooked() +{ + ClearFaceGenCache(); + + g_morphHandler.RevertInternals(); + g_partSet.Revert(); // Cleanup HeadPart List before loading new ones +} + +void UpdateMorphs_Hooked(TESNPC * npc, void * unk1, BSFaceGenNiNode * faceNode) +{ + UpdateNPCMorphs(npc, unk1, faceNode); +#ifdef _DEBUG_HOOK + _DMESSAGE("UpdateMorphs_Hooked - Applying custom morphs"); +#endif + try + { + g_morphHandler.ApplyMorphs(npc, faceNode); + } + catch (...) + { + _DMESSAGE("%s - Fatal error", __FUNCTION__); + } +} + +void UpdateMorph_Hooked(TESNPC * npc, BGSHeadPart * headPart, BSFaceGenNiNode * faceNode) +{ + UpdateNPCMorph(npc, headPart, faceNode); +#ifdef _DEBUG_HOOK + _DMESSAGE("UpdateMorph_Hooked - Applying single custom morph"); +#endif + try + { + g_morphHandler.ApplyMorph(npc, headPart, faceNode); + } + catch (...) + { + _DMESSAGE("%s - Fatal error", __FUNCTION__); + } +} + +#ifdef _DEBUG_HOOK +class DumpPartVisitor : public PartSet::Visitor +{ +public: + bool Accept(UInt32 key, BGSHeadPart * headPart) + { + _DMESSAGE("DumpPartVisitor - Key: %d Part: %s", key, headPart->partName.data); + return false; + } +}; +#endif + +void DataHandler_Hooked::GetValidPlayableHeadParts_Hooked(UInt32 unk1, void * unk2) +{ +#ifdef _DEBUG_HOOK + _DMESSAGE("Reverting Parts:"); + DumpPartVisitor dumpVisitor; + g_partSet.Visit(dumpVisitor); +#endif + + g_partSet.Revert(); // Cleanup HeadPart List before loading new ones + + CALL_MEMBER_FN(this, GetValidPlayableHeadParts)(unk1, unk2); +} + +// Pre-filtered by ValidRace and Gender +UInt8 BGSHeadPart_Hooked::IsPlayablePart_Hooked() +{ + UInt8 ret = CALL_MEMBER_FN(this, IsPlayablePart)(); + +#ifdef _DEBUG_HOOK + _DMESSAGE("IsPlayablePart_Hooked - Reading Part: %08X : %s", this->formID, this->partName.data); +#endif + + if (this->type >= BGSHeadPart::kNumTypes) { + if ((this->partFlags & BGSHeadPart::kFlagExtraPart) == 0) { // Skip Extra Parts + if (strcmp(this->model.GetModelName(), "") == 0) + g_partSet.SetDefaultPart(this->type, this); + else + g_partSet.AddPart(this->type, this); + } + return false; // Prevents hanging if the HeadPart is marked as Playable + } + else if ((this->partFlags & BGSHeadPart::kFlagExtraPart) == 0 && ret) + { + // Sets the default part of this type + if (g_partSet.GetDefaultPart(this->type) == NULL) { + auto playeRace = (*g_thePlayer)->race; + if (playeRace) { + TESNPC * npc = DYNAMIC_CAST((*g_thePlayer)->baseForm, TESForm, TESNPC); + UInt8 gender = CALL_MEMBER_FN(npc, GetSex)(); + + auto chargenData = playeRace->chargenData[gender]; + if (chargenData) { + auto headParts = chargenData->headParts; + if (headParts) { + for (UInt32 i = 0; i < headParts->count; i++) { + BGSHeadPart * part; + headParts->GetNthItem(i, part); + if (part->type == this->type) + g_partSet.SetDefaultPart(part->type, part); + } + } + } + } + } + + // maps the pre-existing part to this type + g_partSet.AddPart(this->type, this); + } + + return ret; +} + +class MorphVisitor : public MorphMap::Visitor +{ +public: + MorphVisitor::MorphVisitor(BSFaceGenModel * model, BSFixedString * morphName, NiAVObject ** headNode, float relative, UInt8 unk1) + { + m_model = model; + m_morphName = morphName; + m_headNode = headNode; + m_relative = relative; + m_unk1 = unk1; + } + bool Accept(BSFixedString morphName) + { + TRIModelData & morphData = g_morphHandler.GetExtendedModelTri(morphName, true); + if (morphData.morphModel && morphData.triFile) { + BSGeometry * geometry = NULL; + if (m_headNode && (*m_headNode)) + geometry = (*m_headNode)->GetAsBSGeometry(); + + if (geometry) + morphData.triFile->Apply(geometry, *m_morphName, m_relative); + } + + return false; + } +private: + BSFaceGenModel * m_model; + BSFixedString * m_morphName; + NiAVObject ** m_headNode; + float m_relative; + UInt8 m_unk1; +}; + +UInt8 ApplyRaceMorph_Hooked(BSFaceGenModel * model, BSFixedString * morphName, TESModelTri * modelMorph, NiAVObject ** headNode, float relative, UInt8 unk1) +{ + //BGSHeadPart * headPart = (BGSHeadPart *)((UInt32)modelMorph - offsetof(BGSHeadPart, raceMorph)); + UInt8 ret = CALL_MEMBER_FN(model, ApplyMorph)(morphName, modelMorph, headNode, relative, unk1); +#ifdef _DEBUG + //_MESSAGE("%08X - Applying %s from %s : %s", this, morphName[0], modelMorph->name.data, headPart->partName.data); +#endif + + try + { + MorphVisitor morphVisitor(model, morphName, headNode, relative, unk1); + g_morphHandler.VisitMorphMap(modelMorph->GetModelName(), morphVisitor); + } + catch (...) + { + _ERROR("%s - fatal error while applying morph (%s)", __FUNCTION__, *morphName); + } + + + + return ret; +} + +UInt8 ApplyChargenMorph_Hooked(BSFaceGenModel * model, BSFixedString * morphName, TESModelTri * modelMorph, NiAVObject ** headNode, float relative, UInt8 unk1) +{ +#ifdef _DEBUG + //_MESSAGE("%08X - Applying %s from %s : %s - %f", this, morphName[0], modelMorph->name.data, headPart->partName.data, relative); +#endif + + UInt8 ret = CALL_MEMBER_FN(model, ApplyMorph)(morphName, modelMorph, headNode, relative, unk1); + + try + { + MorphVisitor morphVisitor(model, morphName, headNode, relative, unk1); + g_morphHandler.VisitMorphMap(BSFixedString(modelMorph->GetModelName()), morphVisitor); + } + catch (...) + { + _ERROR("%s - fatal error while applying morph (%s)", __FUNCTION__, *morphName); + } + + + + return ret; +} + +void SetRelativeMorph(TESNPC * npc, BSFaceGenNiNode * faceNode, BSFixedString name, float relative) +{ + float absRel = abs(relative); + if (absRel > 1.0) { + float max = 0.0; + if (relative < 0.0) + max = -1.0; + if (relative > 0.0) + max = 1.0; + UInt32 count = (UInt32)absRel; + for (UInt32 i = 0; i < count; i++) { + g_morphHandler.SetMorph(npc, faceNode, name.data, max); + relative -= max; + } + } + g_morphHandler.SetMorph(npc, faceNode, name.data, relative); +} + + +void InvokeCategoryList_Hook(GFxMovieView * movie, const char * fnName, FxResponseArgsList * arguments) +{ + UInt64 idx = arguments->args.size; + AddGFXArgument(&arguments->args, &arguments->args, idx + 1); // 17 elements + memset(&arguments->args.values[idx], 0, sizeof(GFxValue)); + arguments->args.values[idx].SetString("$EXTRA"); idx++; + AddGFXArgument(&arguments->args, &arguments->args, idx + 1); + memset(&arguments->args.values[idx], 0, sizeof(GFxValue)); + arguments->args.values[idx].SetNumber(SLIDER_CATEGORY_EXTRA); idx++; + AddGFXArgument(&arguments->args, &arguments->args, idx + 1); + memset(&arguments->args.values[idx], 0, sizeof(GFxValue)); + arguments->args.values[idx].SetString("$EXPRESSIONS"); idx++; + AddGFXArgument(&arguments->args, &arguments->args, idx + 1); + memset(&arguments->args.values[idx], 0, sizeof(GFxValue)); + arguments->args.values[idx].SetNumber(SLIDER_CATEGORY_EXPRESSIONS); + InvokeFunction(movie, fnName, arguments); +} + + +SInt32 AddSlider_Hook(tArray * sliders, RaceMenuSlider * slider) +{ + SInt32 totalSliders = AddRaceMenuSlider(sliders, slider); + totalSliders = g_morphHandler.LoadSliders(sliders, slider); + return totalSliders; +} + +void DoubleMorphCallback_Hook(RaceSexMenu * menu, float newValue, UInt32 sliderId) +{ + RaceMenuSlider * slider = NULL; + RaceSexMenu::RaceComponent * raceData = NULL; + + UInt8 gender = 0; + PlayerCharacter * player = (*g_thePlayer); + TESNPC * actorBase = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + if (actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + BSFaceGenNiNode * faceNode = player->GetFaceGenNiNode(); + + if (menu->raceIndex < menu->sliderData[gender].count) + raceData = &menu->sliderData[gender][menu->raceIndex]; + if (raceData && sliderId < raceData->sliders.count) + slider = &raceData->sliders[sliderId]; + + if (raceData && slider) { +#ifdef _DEBUG_HOOK + _DMESSAGE("Name: %s Value: %f Callback: %s Index: %d", slider->name, slider->value, slider->callback, slider->index); +#endif + if (slider->index >= SLIDER_OFFSET) { + UInt32 sliderIndex = slider->index - SLIDER_OFFSET; + SliderInternalPtr sliderInternal = g_morphHandler.GetSliderByIndex(player->race, sliderIndex); + if (!sliderInternal) + return; + + float currentValue = g_morphHandler.GetMorphValueByName(actorBase, sliderInternal->name); + float relative = newValue - currentValue; + + if (relative == 0.0 && sliderInternal->type != SliderInternal::kTypeHeadPart) { + // Nothing to morph here +#ifdef _DEBUG_HOOK + _DMESSAGE("Skipping Morph %s", sliderInternal->name.data); +#endif + return; + } + + if (sliderInternal->type == SliderInternal::kTypePreset) + { + slider->value = newValue; + + char buffer[MAX_PATH]; + slider->value = newValue; + sprintf_s(buffer, MAX_PATH, "%s%d", sliderInternal->lowerBound.data, (UInt32)currentValue); + g_morphHandler.SetMorph(actorBase, faceNode, buffer, -1.0); + memset(buffer, 0, MAX_PATH); + sprintf_s(buffer, MAX_PATH, "%s%d", sliderInternal->lowerBound.data, (UInt32)newValue); + g_morphHandler.SetMorph(actorBase, faceNode, buffer, 1.0); + + g_morphHandler.SetMorphValue(actorBase, sliderInternal->name, newValue); + return; + } + + if (sliderInternal->type == SliderInternal::kTypeHeadPart) + { + slider->value = newValue; + + UInt8 partType = sliderInternal->presetCount; + + HeadPartList * partList = g_partSet.GetPartList(partType); + if (partList) + { + if (newValue == 0.0) { + BGSHeadPart * oldPart = actorBase->GetCurrentHeadPartByType(partType); + if (oldPart) { + BGSHeadPart * defaultPart = g_partSet.GetDefaultPart(partType); + if (defaultPart && oldPart != defaultPart) { + CALL_MEMBER_FN(actorBase, ChangeHeadPart)(defaultPart); + ChangeActorHeadPart(player, oldPart, defaultPart); + } + } + return; + } + BGSHeadPart * targetPart = g_partSet.GetPartByIndex(partList, (UInt32)newValue - 1); + if (targetPart) { + BGSHeadPart * oldPart = actorBase->GetCurrentHeadPartByType(partType); + if (oldPart != targetPart) { + CALL_MEMBER_FN(actorBase, ChangeHeadPart)(targetPart); + ChangeActorHeadPart(player, oldPart, targetPart); + } + } + } + + return; + } + + + // Cross from positive to negative + if (newValue < 0.0 && currentValue > 0.0) { + // Undo the upper morph + SetRelativeMorph(actorBase, faceNode, sliderInternal->upperBound, -abs(currentValue)); +#ifdef _DEBUG_HOOK + _DMESSAGE("Undoing Upper Morph: New: %f Old: %f Relative %f Remaining %f", newValue, currentValue, relative, relative - currentValue); +#endif + relative = newValue; + } + + // Cross from negative to positive + if (newValue > 0.0 && currentValue < 0.0) { + // Undo the lower morph + SetRelativeMorph(actorBase, faceNode, sliderInternal->lowerBound, -abs(currentValue)); +#ifdef _DEBUG_HOOK + _DMESSAGE("Undoing Lower Morph: New: %f Old: %f Relative %f Remaining %f", newValue, currentValue, relative, relative - currentValue); +#endif + relative = newValue; + } + +#ifdef _DEBUG_HOOK + _DMESSAGE("CurrentValue: %f Relative: %f SavedValue: %f", currentValue, relative, slider->value); +#endif + slider->value = newValue; + + BSFixedString bound = sliderInternal->lowerBound; + if (newValue < 0.0) { + bound = sliderInternal->lowerBound; + relative = -relative; + } + else if (newValue > 0.0) { + bound = sliderInternal->upperBound; + } + else { + if (currentValue > 0.0) { + bound = sliderInternal->upperBound; + } + else { + bound = sliderInternal->lowerBound; + relative = -relative; + } + } + +#ifdef _DEBUG_HOOK + _DMESSAGE("Morphing %d - %s Relative: %f", sliderIndex, bound.data, relative); +#endif + + SetRelativeMorph(actorBase, faceNode, bound, relative); + g_morphHandler.SetMorphValue(actorBase, sliderInternal->name, newValue); + return; + } + } + + DoubleMorphCallback(menu, newValue, sliderId); +} + +RelocAddr TESModelTri_vtbl(0x015A8828); +RelocAddr<_AddGFXArgument> AddGFXArgument(0x008554B0); +RelocAddr<_FaceGenApplyMorph> FaceGenApplyMorph(0x003D23D0); +RelocAddr<_AddRaceMenuSlider> AddRaceMenuSlider(0x008BAED0); +RelocAddr<_DoubleMorphCallback> DoubleMorphCallback(0x008B2F90); + +RelocAddr<_UpdateNPCMorphs> UpdateNPCMorphs(0x00360910); +RelocAddr<_UpdateNPCMorph> UpdateNPCMorph(0x00360B00); + +bool InstallSKEEHooks() +{ +#ifdef FIXME + WriteRelJump(kInstallArmorHook_Base, (UInt32)&InstallArmorNodeHook_Entry); + + if (g_enableFaceOverlays) { + WriteRelJump(0x00569990 + 0x16E, GetFnAddr(&TESNPC_Hooked::CreateHeadState_Hooked)); // Creates new head + WriteRelCall(0x0056AFC0 + 0x2E5, GetFnAddr(&TESNPC_Hooked::UpdateHeadState_Hooked)); // Updates head state + } + + WriteRelCall(0x0046C310 + 0x110, GetFnAddr(&BSLightingShaderProperty_Hooked::HasFlags_Hooked)); + + // Make the ExtraRank item unique + WriteRelCall(0x00482120 + 0x5DD, GetFnAddr(&ExtraContainerChangesData_Hooked::TransferItemUID_Hooked)); + + // Closest and easiest hook to Payprus Native Function Registry without a detour + WriteRelCall(0x008D7A40 + 0x995, GetFnAddr(&SkyrimVM_Hooked::RegisterEventSinks_Hooked)); + + WriteRelJump(kInstallWeaponFPHook_Base, (UInt32)&CreateWeaponNodeFPHook_Entry); + WriteRelJump(kInstallWeapon3PHook_Base, (UInt32)&CreateWeaponNode3PHook_Entry); + WriteRelJump(kInstallWeaponHook_Base, (UInt32)&InstallWeaponNodeHook_Entry); + + + + WriteRelCall(DATA_ADDR(0x00699100, 0x275), (UInt32)&LoadActorValues_Hook); // Hook for loading initial data on startup + + WriteRelCall(DATA_ADDR(0x00882290, 0x185), GetFnAddr(&DataHandler_Hooked::GetValidPlayableHeadParts_Hooked)); // Cleans up HeadPart List + WriteRelCall(DATA_ADDR(0x00886C70, 0x97), (UInt32)&ClearFaceGenCache_Hooked); // RaceMenu dtor Cleans up HeadPart List + WriteRelCall(DATA_ADDR(0x00881320, 0x67), GetFnAddr(&BGSHeadPart_Hooked::IsPlayablePart_Hooked)); // Custom head part insertion + + if (g_extendedMorphs) { + WriteRelCall(DATA_ADDR(0x005A4070, 0xBE), GetFnAddr(&BSFaceGenModel_Hooked::ApplyChargenMorph_Hooked)); // Load and apply extended morphs + WriteRelCall(DATA_ADDR(0x005A5CE0, 0x39), GetFnAddr(&BSFaceGenModel_Hooked::ApplyRaceMorph_Hooked)); // Load and apply extended morphs + } + WriteRelCall(DATA_ADDR(0x005A4AC0, 0x67), GetFnAddr(&TESNPC_Hooked::UpdateMorphs_Hooked)); // Updating all morphs when head is regenerated + WriteRelCall(DATA_ADDR(0x005AA230, 0x5C), GetFnAddr(&TESNPC_Hooked::UpdateMorph_Hooked)); // ChangeHeadPart to update morph on single part + WriteRelCall(DATA_ADDR(0x00882290, 0x2E26), GetFnAddr(&SliderArray::AddSlider_Hooked)); // Create Slider + WriteRelCall(DATA_ADDR(0x00881DD0, 0x436), GetFnAddr(&FxResponseArgsList_Hooked::AddArgument_Hooked)); // Add Slider + + WriteRelCall(DATA_ADDR(0x00882290, 0x337C), GetFnAddr(&RaceSexMenu_Hooked::DoubleMorphCallback_Hooked)); // Change Slider OnLoad + WriteRelCall(DATA_ADDR(0x0087FA50, 0x93), GetFnAddr(&RaceSexMenu_Hooked::DoubleMorphCallback_Hooked)); // Change Slider OnCallback + + SafeWrite32(DATA_ADDR(0x010E7404, 0x18), GetFnAddr(&RaceSexMenu_Hooked::RenderMenu_Hooked)); // Hooks RaceMenu renderer + + if (!g_externalHeads) { + WriteRelJump(kInstallRegenHeadHook_Base, (UInt32)&InstallRegenHeadHook_Entry); // FaceGen Regenerate HeadPart Hook + WriteRelJump(kInstallForceRegenHeadHook_Base, (UInt32)&InstallForceRegenHeadHook_Entry); // Preprocessed Head Hook + } +#endif + // ApplyChargenMorph - 1403D2530+F3 + // ApplyRaceMorph - 1403D4760+56 + + // UpdateMorphs Hook - 1403D26A0+C7 + // UpdateMorph - 1403DC370+79 + + // AddSlider - 1408B4590+37E4 - call 1408BAED0 + // AddArgument - 1408B39A0+97A - call 1408554B0 + + // DoubleMorphCallback - 1408B4590+3CD5 + + if (!g_branchTrampoline.Create(1024 * 64)) + { + _ERROR("couldn't create branch trampoline. this is fatal. skipping remainder of init process."); + return false; + } + + if (!g_localTrampoline.Create(1024 * 64, nullptr)) + { + _ERROR("couldn't create codegen buffer. this is fatal. skipping remainder of init process."); + return false; + } + + RelocAddr InvokeCategoriesList_Target(0x008B39A0 + 0x9FB); + g_branchTrampoline.Write5Call(InvokeCategoriesList_Target.GetUIntPtr(), (uintptr_t)InvokeCategoryList_Hook); + + RelocAddr AddSlider_Target(0x008B4590 + 0x37E4); + g_branchTrampoline.Write5Call(AddSlider_Target.GetUIntPtr(), (uintptr_t)AddSlider_Hook); + + RelocAddr DoubleMorphCallback1_Target(0x008B4590 + 0x3CD5); + g_branchTrampoline.Write5Call(DoubleMorphCallback1_Target.GetUIntPtr(), (uintptr_t)DoubleMorphCallback_Hook); + + RelocAddr DoubleMorphCallback2_Target(0x008B0330 + 0x4F); + g_branchTrampoline.Write5Call(DoubleMorphCallback2_Target.GetUIntPtr(), (uintptr_t)DoubleMorphCallback_Hook); + + RelocAddr ApplyChargenMorph_Target(0x003D2530 + 0xF3); + g_branchTrampoline.Write5Call(ApplyChargenMorph_Target.GetUIntPtr(), (uintptr_t)ApplyChargenMorph_Hooked); + + RelocAddr ApplyRaceMorph_Target(0x003D4760 + 0x56); + g_branchTrampoline.Write5Call(ApplyRaceMorph_Target.GetUIntPtr(), (uintptr_t)ApplyRaceMorph_Hooked); + + RelocAddr UpdateMorphs_Target(0x003D26A0 + 0xC7); + g_branchTrampoline.Write5Call(UpdateMorphs_Target.GetUIntPtr(), (uintptr_t)UpdateMorphs_Hooked); + + RelocAddr UpdateMorph_Target(0x003DC370 + 0x79); + g_branchTrampoline.Write5Call(UpdateMorph_Target.GetUIntPtr(), (uintptr_t)UpdateMorph_Hooked); + + return true; +} \ No newline at end of file diff --git a/skee/SKEEHooks.h b/skee/SKEEHooks.h new file mode 100644 index 0000000..ebe8ed4 --- /dev/null +++ b/skee/SKEEHooks.h @@ -0,0 +1,131 @@ +#pragma once + +class TESObjectARMO; +class TESObjectARMA; +class TESModelTextureSwap; +class BGSTextureSet; +class BSFaceGenNiNode; + +#include "common/IErrors.h" +#include "skse64/NiProperties.h" +#include "skse64/GameTypes.h" +#include "skse64/GameData.h" +#include "skse64/GameExtraData.h" +#include "skse64/GameMenus.h" + +extern RelocAddr TESModelTri_vtbl; +typedef SInt32 (*_AddRaceMenuSlider)(tArray * sliders, RaceMenuSlider * slider); +extern RelocAddr<_AddRaceMenuSlider> AddRaceMenuSlider; + +typedef void (*_DoubleMorphCallback)(RaceSexMenu * menu, float value, UInt32 sliderId); +extern RelocAddr<_DoubleMorphCallback> DoubleMorphCallback; + +typedef void(*_FaceGenApplyMorph)(FaceGen * faceGen, BSFaceGenNiNode * node, TESNPC * npc, BSFixedString * morph, float relative); +extern RelocAddr<_FaceGenApplyMorph> FaceGenApplyMorph; + +typedef void(*_AddGFXArgument)(GArray * arr, GArray * arr2, UInt64 idx); +extern RelocAddr<_AddGFXArgument> AddGFXArgument; + +typedef void(*_UpdateNPCMorphs)(TESNPC * npc, void * unk1, BSFaceGenNiNode * faceNode); +extern RelocAddr<_UpdateNPCMorphs> UpdateNPCMorphs; + +typedef void(*_UpdateNPCMorph)(TESNPC * npc, BGSHeadPart * headPart, BSFaceGenNiNode * faceNode); +extern RelocAddr<_UpdateNPCMorph> UpdateNPCMorph; + + +class AddonTreeParameters +{ +public: + UInt32 unk00; // 00 - ? + UInt32 unk04; // 04 - inited to zero + TESObjectARMO * armor; // 08 + TESObjectARMA * addon; // 0C + TESModelTextureSwap * model; // 10 + BGSTextureSet * textureSet; // 14 +}; + +class ArmorAddonTree +{ +public: + MEMBER_FN_PREFIX(ArmorAddonTree); + DEFINE_MEMBER_FN(CreateArmorNode, NiAVObject *, 0x0046F0B0, NiNode * unk1, UInt32 unk2, UInt8 unk3, UInt32 unk4, UInt32 unk5); + + UInt32 unk00; // 00 refcount? + NiNode * unk04; // 04 FlattenedBoneTree + TESObjectARMO * skin; // 08 + UInt32 unk0C[(0xA88 - 0x0C) >> 2]; // 0C + UInt32 unkA88; // A88 + + //NiAVObject * CreateArmorNode_Hooked(NiNode * unk1, UInt32 unk2, UInt32 unk3, UInt32 unk4, UInt32 unk5); + NiAVObject * CreateArmorNode(AddonTreeParameters * params, NiNode * unk1, UInt32 unk2, UInt32 unk3, UInt32 unk4, UInt32 unk5); +}; + +#ifdef FIXME +class SkyrimVM_Hooked : public SkyrimVM +{ +public: + MEMBER_FN_PREFIX(SkyrimVM_Hooked); + DEFINE_MEMBER_FN(RegisterEventSinks, void, 0x008D2120); + + void RegisterEventSinks_Hooked(); +}; +#endif + +class TESNPC_Hooked : public TESNPC +{ +public: + MEMBER_FN_PREFIX(TESNPC_Hooked); + DEFINE_MEMBER_FN(BindHead, void, 0x00000000, Actor*, BSFaceGenNiNode**); + //DEFINE_MEMBER_FN(GetFaceGenHead, UInt32, 0x0056AEB0, UInt32 unk1, UInt32 unk2); + DEFINE_MEMBER_FN(UpdateHeadState, SInt32, 0x00000000, Actor *, UInt32 unk1); + DEFINE_MEMBER_FN(UpdateMorphs, void, 0x00000000, void * unk1, BSFaceGenNiNode * faceNode); + DEFINE_MEMBER_FN(UpdateMorph, void, 0x00000000, BGSHeadPart * headPart, BSFaceGenNiNode * faceNode); + + void BindHead_Hooked(Actor* actor, BSFaceGenNiNode** headNode); + //UInt32 GetFaceGenHead_Hooked(TESObjectREFR* refr, UInt32 unk1, UInt32 unk2); + SInt32 CreateHeadState_Hooked(Actor *, UInt32 unk1); + SInt32 UpdateHeadState_Hooked(Actor *, UInt32 unk1); + + void UpdateMorphs_Hooked(void * unk1, BSFaceGenNiNode * faceNode); + void UpdateMorph_Hooked(BGSHeadPart * headPart, BSFaceGenNiNode * faceNode); +}; + +class BGSHeadPart_Hooked : public BGSHeadPart +{ +public: + MEMBER_FN_PREFIX(BGSHeadPart_Hooked); + DEFINE_MEMBER_FN(IsPlayablePart, UInt8, 0x00000000); + + UInt8 IsPlayablePart_Hooked(); +}; + +class ExtraContainerChangesData_Hooked : public ExtraContainerChanges::Data +{ +public: + MEMBER_FN_PREFIX(ExtraContainerChangesData_Hooked); + DEFINE_MEMBER_FN(TransferItemUID, void, 0x00000000, BaseExtraList * extraList, TESForm * oldForm, TESForm * newForm, UInt32 unk1); + + void TransferItemUID_Hooked(BaseExtraList * extraList, TESForm * oldForm, TESForm * newForm, UInt32 unk1); +}; + +class BSLightingShaderProperty_Hooked : public BSLightingShaderProperty +{ +public: + MEMBER_FN_PREFIX(BSLightingShaderProperty_Hooked); + + bool HasFlags_Hooked(UInt32 flags); +}; + +class DataHandler_Hooked : public DataHandler +{ +public: + MEMBER_FN_PREFIX(DataHandler_Hooked); + DEFINE_MEMBER_FN(GetValidPlayableHeadParts, void, 0x00000000, UInt32 unk1, void * unk2); + + void GetValidPlayableHeadParts_Hooked(UInt32 unk1, void * unk2); +}; + +void __stdcall InstallArmorAddonHook(TESObjectREFR * refr, AddonTreeParameters * params, NiNode * boneTree, NiAVObject * resultNode); +void __stdcall InstallFaceOverlayHook(TESObjectREFR* refr, bool attemptUninstall, bool immediate); + +bool InstallSKEEHooks(); \ No newline at end of file diff --git a/skee/ScaleformCharGenFunctions.cpp b/skee/ScaleformCharGenFunctions.cpp new file mode 100644 index 0000000..3bbc89c --- /dev/null +++ b/skee/ScaleformCharGenFunctions.cpp @@ -0,0 +1,1354 @@ +#include "common/IFileStream.h" + +#include "skse64/PluginAPI.h" +#include "skse64_common/skse_version.h" + +#include "ScaleformCharGenFunctions.h" +#include "MorphHandler.h" +#include "PartHandler.h" + +#include "skse64/GameAPI.h" +#include "skse64/GameData.h" +#include "skse64/GameObjects.h" +#include "skse64/GameRTTI.h" +#include "skse64/GameStreams.h" +#include "skse64/GameMenus.h" + +#include "NifUtils.h" + +#include "skse64/NiRTTI.h" +#include "skse64/NiObjects.h" +#include "skse64/NiNodes.h" +#include "skse64/NiGeometry.h" +#include "skse64/NiSerialization.h" + +#include "skse64/ScaleformMovie.h" +#include "skse64/ScaleformLoader.h" + +#include "OverrideVariant.h" +#include "OverrideInterface.h" +#include "OverlayInterface.h" +#include "NiTransformInterface.h" +#include "BodyMorphInterface.h" + +#include "ScaleformUtils.h" + +extern OverrideInterface * g_overrideInterface; +extern NiTransformInterface * g_transformInterface; +extern OverlayInterface * g_overlayInterface; +extern BodyMorphInterface * g_bodyMorphInterface; + +extern MorphHandler g_morphHandler; +extern PartSet g_partSet; + +extern SKSETaskInterface * g_task; + +#include "skse64/NiRenderer.h" +#include "skse64/NiExtraData.h" + +#include "CDXCamera.h" +#include "CDXNifScene.h" +#include "CDXNifMesh.h" +#include "CDXBrush.h" +#include "CDXUndo.h" +#include "CDXNifCommands.h" + +UInt32 colors[] = { + 0xffffff, 0xff0000, 0x0000ff, 0x00ff00, + 0xff00ff, 0xffff00, 0x00ffff, 0x79f2f2, + 0xe58473, 0xe673da, 0x57d936, 0xcc3d00, + 0x5233cc, 0xcc9466, 0xbf001d, 0xb8bf30, + 0x8c007e, 0x466d8c, 0x287300, 0x397359, + 0x453973, 0x662e00, 0x050066, 0x665e1a, + 0x663342, 0x59332d, 0x4c000b, 0x40103b, + 0x33240d, 0x20330d, 0x0d1633, 0x1a332f +}; + +#ifdef FIXME +extern CDXModelViewerCamera g_Camera; +extern CDXNifScene g_World; +extern float g_panSpeed; +extern float g_cameraFOV; +#endif + +void SKSEScaleform_SavePreset::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + ASSERT(args->args[1].GetType() == GFxValue::kType_Bool); + + bool saveJson = args->args[1].GetBool(); + const char * strData = args->args[0].GetString(); + + if (saveJson) + args->result->SetBool(g_morphHandler.SaveJsonPreset(strData)); + else + args->result->SetBool(g_morphHandler.SaveBinaryPreset(strData)); +} + +void SKSEScaleform_LoadPreset::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + ASSERT(args->args[1].GetType() == GFxValue::kType_Object); + ASSERT(args->args[2].GetType() == GFxValue::kType_Bool); + + bool loadJson = args->args[2].GetBool(); + + const char * strData = args->args[0].GetString(); + + GFxValue * object = NULL; + if (args->numArgs >= 2) + object = &args->args[1]; + + auto presetData = std::make_shared(); + bool loadError = loadJson ? g_morphHandler.LoadJsonPreset(strData, presetData) : g_morphHandler.LoadBinaryPreset(strData, presetData);//g_morphHandler.LoadPreset(strData, args->movie, object); + if (!loadError) { + g_morphHandler.ApplyPresetData(*g_thePlayer, presetData); + + RegisterNumber(object, "hairColor", presetData->hairColor); + + GFxValue tintArray; + args->movie->CreateArray(&tintArray); + + for(auto & tint : presetData->tints) { + GFxValue tintObject; + args->movie->CreateObject(&tintObject); + RegisterNumber(&tintObject, "color", tint.color); + RegisterNumber(&tintObject, "index", tint.index); + RegisterString(&tintObject, args->movie, "texture", tint.name.data); + tintArray.PushBack(&tintObject); + } + + object->SetMember("tints", &tintArray); + } + + args->result->SetBool(loadError); +} + +const char * GetGameSettingString(const char * key) +{ + Setting * setting = (*g_gameSettingCollection)->Get(key); + if(setting && setting->GetType() == Setting::kType_String) + return setting->data.s; + + return NULL; +} + +void SKSEScaleform_ReadPreset::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + ASSERT(args->args[1].GetType() == GFxValue::kType_Object); + ASSERT(args->args[2].GetType() == GFxValue::kType_Bool); + + bool loadJson = args->args[2].GetBool(); + const char * strData = args->args[0].GetString(); + + + GFxValue * object = NULL; + if (args->numArgs >= 2) + object = &args->args[1]; + + DataHandler * dataHandler = DataHandler::GetSingleton(); + auto presetData = std::make_shared(); + bool loadError = loadJson ? g_morphHandler.LoadJsonPreset(strData, presetData) : g_morphHandler.LoadBinaryPreset(strData, presetData);//g_morphHandler.LoadPreset(strData, args->movie, object); + if(!loadError) { + PlayerCharacter * player = (*g_thePlayer); + TESNPC * npc = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + + GFxValue modArray; + args->movie->CreateArray(&modArray); + for(std::vector::iterator it = presetData->modList.begin(); it != presetData->modList.end(); ++it) { + GFxValue modObject; + args->movie->CreateObject(&modObject); + RegisterString(&modObject, args->movie, "name", (*it).c_str()); + RegisterNumber(&modObject, "loadedIndex", dataHandler->GetModIndex((*it).c_str())); + modArray.PushBack(&modObject); + } + object->SetMember("mods", &modArray); + + GFxValue partArray; + args->movie->CreateArray(&partArray); + for(std::vector::iterator it = presetData->headParts.begin(); it != presetData->headParts.end(); ++it) { + GFxValue partObject; + args->movie->CreateString(&partObject, (*it)->partName.data); + partArray.PushBack(&partObject); + } + object->SetMember("headParts", &partArray); + + GFxValue weightObject; + args->movie->CreateObject(&weightObject); + RegisterUnmanagedString(&weightObject, "name", GetGameSettingString("sRSMWeight")); + RegisterNumber(&weightObject, "value", presetData->weight); + object->SetMember("weight", &weightObject); + + GFxValue hairObject; + args->movie->CreateObject(&hairObject); + RegisterUnmanagedString(&hairObject, "name", GetGameSettingString("sRSMHairColorPresets")); + RegisterNumber(&hairObject, "value", presetData->hairColor); + object->SetMember("hair", &hairObject); + + GFxValue tintArray; + args->movie->CreateArray(&tintArray); + for(std::vector::iterator it = presetData->tints.begin(); it != presetData->tints.end(); ++it) { + PresetData::Tint & tint = (*it); + GFxValue tintObject; + args->movie->CreateObject(&tintObject); + RegisterNumber(&tintObject, "color", tint.color); + RegisterNumber(&tintObject, "index", tint.index); + RegisterString(&tintObject, args->movie, "texture", tint.name.data); + tintArray.PushBack(&tintObject); + } + object->SetMember("tints", &tintArray); + + GFxValue morphArray; + args->movie->CreateArray(&morphArray); + + const char * presetNames[FacePresetList::kNumPresets]; + presetNames[FacePresetList::kPreset_NoseType] = GetGameSettingString("sRSMNoseTypes"); + presetNames[FacePresetList::kPreset_BrowType] = GetGameSettingString("sRSMBrowTypes"); + presetNames[FacePresetList::kPreset_EyesType] = GetGameSettingString("sRSMEyeTypes"); + presetNames[FacePresetList::kPreset_LipType] = GetGameSettingString("sRSMMouthTypes"); + + const char * morphNames[FaceMorphList::kNumMorphs]; + morphNames[FaceMorphList::kMorph_NoseShortLong] = GetGameSettingString("sRSMNoseLength"); + morphNames[FaceMorphList::kMorph_NoseDownUp] = GetGameSettingString("sRSMNoseHeight"); + morphNames[FaceMorphList::kMorph_JawUpDown] = GetGameSettingString("sRSMJawHeight"); + morphNames[FaceMorphList::kMorph_JawNarrowWide] = GetGameSettingString("sRSMJawWidth"); + morphNames[FaceMorphList::kMorph_JawBackForward] = GetGameSettingString("sRSMJawForward"); + morphNames[FaceMorphList::kMorph_CheeksDownUp] = GetGameSettingString("sRSMCheekboneHeight"); + morphNames[FaceMorphList::kMorph_CheeksInOut] = GetGameSettingString("sRSMCheekboneWidth"); + morphNames[FaceMorphList::kMorph_EyesMoveDownUp] = GetGameSettingString("sRSMEyeHeight"); + morphNames[FaceMorphList::kMorph_EyesMoveInOut] = GetGameSettingString("sRSMEyeDepth"); + morphNames[FaceMorphList::kMorph_BrowDownUp] = GetGameSettingString("sRSMBrowHeight"); + morphNames[FaceMorphList::kMorph_BrowInOut] = GetGameSettingString("sRSMBrowWidth"); + morphNames[FaceMorphList::kMorph_BrowBackForward] = GetGameSettingString("sRSMBrowForward"); + morphNames[FaceMorphList::kMorph_LipMoveDownUp] = GetGameSettingString("sRSMMouthHeight"); + morphNames[FaceMorphList::kMorph_LipMoveInOut] = GetGameSettingString("sRSMMouthForward"); + morphNames[FaceMorphList::kMorph_ChinThinWide] = GetGameSettingString("sRSMChinWidth"); + morphNames[FaceMorphList::kMorph_ChinMoveUpDown] = GetGameSettingString("sRSMChinLength"); + morphNames[FaceMorphList::kMorph_OverbiteUnderbite] = GetGameSettingString("sRSMChinForward"); + morphNames[FaceMorphList::kMorph_EyesBackForward] = GetGameSettingString("sRSMEyeDepth"); + morphNames[FaceMorphList::kMorph_Vampire] = NULL; + + UInt32 i = 0; + for(std::vector::iterator it = presetData->presets.begin(); it != presetData->presets.end(); ++it) { + GFxValue presetObject; + args->movie->CreateObject(&presetObject); + if(presetNames[i]) + RegisterUnmanagedString(&presetObject, "name", presetNames[i]); + RegisterNumber(&presetObject, "value", *it); + RegisterNumber(&presetObject, "type", 0); + RegisterNumber(&presetObject, "index", i); + morphArray.PushBack(&presetObject); + i++; + } + + i = 0; + for(auto & it : presetData->morphs) { + GFxValue presetObject; + args->movie->CreateObject(&presetObject); + if (i < FaceMorphList::kNumMorphs && morphNames[i]) + RegisterUnmanagedString(&presetObject, "name", morphNames[i]); + RegisterNumber(&presetObject, "value", it); + RegisterNumber(&presetObject, "type", 1); + RegisterNumber(&presetObject, "index", i); + morphArray.PushBack(&presetObject); + i++; + } + + i = 0; + for(auto & it : presetData->customMorphs) { + std::string morphName = "$"; + morphName.append(it.name.data); + GFxValue customObject; + args->movie->CreateObject(&customObject); + RegisterString(&customObject, args->movie, "name", morphName.c_str()); + RegisterNumber(&customObject, "value", it.value); + RegisterNumber(&customObject, "type", 2); + RegisterNumber(&customObject, "index", i); + morphArray.PushBack(&customObject); + i++; + } + i = 0; + for (auto & it : presetData->bodyMorphData) { + GFxValue customObject; + args->movie->CreateObject(&customObject); + RegisterString(&customObject, args->movie, "name", it.first.data); + + float morphSum = 0; + for (auto & keys : it.second) + morphSum += keys.second; + + RegisterNumber(&customObject, "value", morphSum); + RegisterNumber(&customObject, "type", 3); + RegisterNumber(&customObject, "index", i); + morphArray.PushBack(&customObject); + i++; + } + object->SetMember("morphs", &morphArray); + } + + args->result->SetBool(loadError); +} + +void SKSEScaleform_ReloadSliders::Invoke(Args * args) +{ + MenuManager * mm = MenuManager::GetSingleton(); + if (mm) { + BSFixedString t("RaceSex Menu"); + RaceSexMenu* raceMenu = (RaceSexMenu*)mm->GetMenu(&t); + if(raceMenu) { + PlayerCharacter * player = (*g_thePlayer); + CALL_MEMBER_FN(raceMenu, LoadSliders)((UInt64)player->baseForm, 0); + //CALL_MEMBER_FN(raceMenu, UpdatePlayer)(); + CALL_MEMBER_FN((*g_thePlayer), QueueNiNodeUpdate)(true); + } + } +} + +void SKSEScaleform_GetSliderData::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + + UInt32 sliderId = (UInt32)args->args[0].GetNumber(); + double value = args->args[1].GetNumber(); + + MenuManager * mm = MenuManager::GetSingleton(); + if(mm) + { + BSFixedString t("RaceSex Menu"); + RaceSexMenu * raceMenu = (RaceSexMenu *)mm->GetMenu(&t); + if(raceMenu) + { + RaceMenuSlider * slider = NULL; + RaceSexMenu::RaceComponent * raceData = NULL; + + UInt8 gender = 0; + PlayerCharacter * player = (*g_thePlayer); + TESNPC * actorBase = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + if(raceMenu->raceIndex < raceMenu->sliderData[gender].count) + raceData = &raceMenu->sliderData[gender][raceMenu->raceIndex]; + if(raceData && sliderId < raceData->sliders.count) + slider = &raceData->sliders[sliderId]; + + if(raceData && slider) + { + args->movie->CreateObject(args->result); + RegisterNumber(args->result, "type", slider->type); + RegisterNumber(args->result, "index", slider->index); + + switch(slider->type) + { + case RaceMenuSlider::kTypeHeadPart: + { + if(slider->index < RaceSexMenu::kNumHeadPartLists) + { + BGSHeadPart * headPart = NULL; + raceMenu->headParts[slider->index].GetNthItem((UInt32)value, headPart); + if(headPart) { + RegisterNumber(args->result, "formId", headPart->formID); + RegisterString(args->result, args->movie, "partName", headPart->partName.data); + } + } + } + break; + case RaceMenuSlider::kTypeDoubleMorph: + { + // Provide case for custom parts + if(slider->index >= SLIDER_OFFSET) { + UInt32 sliderIndex = slider->index - SLIDER_OFFSET; + SliderInternalPtr sliderInternal = g_morphHandler.GetSliderByIndex(player->race, sliderIndex); + if(sliderInternal) { + RegisterNumber(args->result, "subType", sliderInternal->type); + switch (sliderInternal->type) + { + // Only acquire part information for actual part sliders + case SliderInternal::kTypeHeadPart: + { + UInt8 partType = sliderInternal->presetCount; + HeadPartList * partList = g_partSet.GetPartList(partType); + if (partList) + { + BGSHeadPart * targetPart = g_partSet.GetPartByIndex(partList, (UInt32)value - 1); + if (targetPart) { + RegisterNumber(args->result, "formId", targetPart->formID); + RegisterString(args->result, args->movie, "partName", targetPart->partName.data); + } + } + } + break; + } + } + } + } + break; + } + } + } + } +} + + +void SKSEScaleform_GetModName::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + + UInt32 modIndex = (UInt32)args->args[0].GetNumber(); + + DataHandler* pDataHandler = DataHandler::GetSingleton(); + ModInfo* modInfo = pDataHandler->modList.modInfoList.GetNthItem(modIndex); + if(modInfo) { + args->movie->CreateString(args->result, modInfo->name); + } +} + +void SKSEScaleform_ExportHead::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + + const char * strData = args->args[0].GetString(); + +#ifdef FIXME + // Get the Editor's working actor + Actor * actor = g_World.GetWorkingActor(); + if (!actor) + return; + + std::string nifPath = strData; + nifPath.append(".nif"); + std::string ddsPath = strData; + ddsPath.append(".dds"); + + g_task->AddTask(new SKSETaskExportHead(actor, nifPath.c_str(), ddsPath.c_str())); +#endif +} + +void SKSEScaleform_ImportHead::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + + const char * strData = args->args[0].GetString(); + +#ifdef FIXME + // Release the previous import just in case + g_World.ReleaseImport(); + + // Get the Editor's working actor + Actor * actor = g_World.GetWorkingActor(); + if (!actor) + return; + + BSFaceGenNiNode * faceNode = actor->GetFaceGenNiNode(); + TESNPC * actorBase = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (!actorBase || !faceNode) + return; + + UInt8 niStreamMemory[sizeof(NiStream)]; + memset(niStreamMemory, 0, sizeof(NiStream)); + NiStream * niStream = (NiStream *)niStreamMemory; + CALL_MEMBER_FN(niStream, ctor)(); + + NiNode * rootNode = NULL; + BSResourceNiBinaryStream binaryStream(strData); + if (binaryStream.IsValid()) + { + niStream->LoadStream(&binaryStream); + if (niStream->m_rootObjects.m_data) + { + if (niStream->m_rootObjects.m_data[0]) // Get the root node + rootNode = niStream->m_rootObjects.m_data[0]->GetAsNiNode(); + if (rootNode) + { + args->movie->CreateArray(args->result); + /*args->movie->CreateObject(args->result); + + GFxValue source; + args->movie->CreateArray(&source); + + + + GFxValue destination; + args->movie->CreateArray(&destination); + + UInt32 numParts = actorBase->numHeadParts; + BGSHeadPart ** headParts = actorBase->headparts; + if (CALL_MEMBER_FN(actorBase, HasOverlays)()) { + numParts = GetNumActorBaseOverlays(actorBase); + headParts = GetActorBaseOverlays(actorBase); + } + + for (UInt32 i = 0; i < numParts; i++) + { + BGSHeadPart * headPart = headParts[i]; + if (!headPart) + continue; + + NiTriBasedGeom * geometry = GetTriBasedGeomByHeadPart(faceNode, headPart); + if (!geometry) + continue; + + NiGeometryData * geometryData = niptr_cast(geometry->m_spModelData); + if (!geometryData) + continue; + + bool morphable = geometry->GetExtraData("FOD") != NULL; + + GFxValue gfxPart; + args->movie->CreateObject(&gfxPart); + RegisterString(&gfxPart, args->movie, "name", geometry->m_name); + RegisterNumber(&gfxPart, "vertices", geometryData->m_usVertices); + RegisterBool(&gfxPart, "morphable", morphable); + destination.PushBack(&gfxPart); + } + + */ + + SInt32 gIndex = 0; + VisitObjects(rootNode, [&gIndex, &args](NiAVObject* trishape) + { + NiNode * parent = trishape->m_parent; + if (parent && BSFixedString(parent->m_name) == BSFixedString("BSFaceGenNiNodeSkinned")) { + NiTriBasedGeom * geometry = trishape->GetAsNiTriBasedGeom(); + if (!geometry) + return false; + + NiGeometryData * geometryData = niptr_cast(geometry->m_spModelData); + if (!geometryData) + return false; + + GFxValue gfxGeom; + args->movie->CreateObject(&gfxGeom); + RegisterString(&gfxGeom, args->movie, "name", geometry->m_name); + RegisterNumber(&gfxGeom, "vertices", geometryData->m_usVertices); + RegisterNumber(&gfxGeom, "gIndex", gIndex); + args->result->PushBack(&gfxGeom); + gIndex++; + } + + return false; + }); + + //args->result->SetMember("source", &source); + //args->result->SetMember("destination", &destination); + } + } + } + + // Add the Root node to the Editor + if (rootNode) { + rootNode->IncRef(); + g_World.SetImportRoot(rootNode); + } + + // Release the created NiStream + CALL_MEMBER_FN(niStream, dtor)(); +#endif +} + +void SKSEScaleform_ReleaseImportedHead::Invoke(Args * args) +{ +#ifdef FIXME + g_World.ReleaseImport(); +#endif +} + +void SKSEScaleform_LoadImportedHead::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Array); + +#ifdef FIXME + + UInt32 meshLength = min(g_World.GetNumMeshes(), args->args[0].GetArraySize()); + + for (UInt32 i = 0; i < meshLength; i++) + { + CDXNifMesh * mesh = static_cast(g_World.GetNthMesh(i)); + if (mesh) { + NiNode * importRoot = g_World.GetImportRoot(); + if (importRoot) { + + SInt32 searchIndex = -1; + + GFxValue gfxIndex; + args->args[0].GetElement(i, &gfxIndex); + searchIndex = gfxIndex.GetNumber(); + + NiGeometry * sourceGeometry = NULL; + SInt32 gIndex = 0; + VisitObjects(importRoot, [&gIndex, &args, &searchIndex, &sourceGeometry](NiAVObject* trishape) + { + NiNode * parent = trishape->m_parent; + if (parent && BSFixedString(parent->m_name) == BSFixedString("BSFaceGenNiNodeSkinned")) { + NiTriBasedGeom * geometry = trishape->GetAsNiTriBasedGeom(); + if (!geometry) + return false; + + NiGeometryData * geometryData = niptr_cast(geometry->m_spModelData); + if (!geometryData) + return false; + + if (searchIndex == gIndex) { + sourceGeometry = geometry; + return true; + } + + gIndex++; + } + + return false; + }); + + if (sourceGeometry) { + std::shared_ptr importGeometry = std::make_shared(mesh, sourceGeometry); + if (importGeometry->Length() > 0) + importGeometry->Apply(g_undoStack.Push(importGeometry)); + } + } + } + } +#endif +} + + +void SKSEScaleform_ClearSculptData::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Array); + +#ifdef FIXME + + UInt32 meshLength = args->args[0].GetArraySize(); + + for (UInt32 i = 0; i < meshLength; i++) + { + GFxValue gfxIndex; + args->args[0].GetElement(i, &gfxIndex); + SInt32 meshIndex = gfxIndex.GetNumber(); + + CDXNifMesh * mesh = static_cast(g_World.GetNthMesh(meshIndex)); + if (mesh) { + std::shared_ptr resetGeometry = std::make_shared(mesh); + if (resetGeometry->Length() > 0) + resetGeometry->Apply(g_undoStack.Push(resetGeometry)); + } + } +#endif +} + + +void SKSEScaleform_GetHeadParts::Invoke(Args * args) +{ + args->movie->CreateObject(args->result); + + GFxValue partList; + args->movie->CreateArray(&partList); + + PlayerCharacter * player = (*g_thePlayer); + TESNPC * actorBase = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + for(UInt32 i = 0; i < actorBase->numHeadParts; i++) + { + GFxValue partData; + args->movie->CreateObject(&partData); + + BGSHeadPart * headPart = actorBase->headparts[i]; + + GFxValue headPartData; + args->movie->CreateObject(&headPartData); + RegisterString(&headPartData, args->movie, "partName", headPart->partName.data); + RegisterNumber(&headPartData, "partFlags", headPart->partFlags); + RegisterNumber(&headPartData, "partType", headPart->type); + RegisterString(&headPartData, args->movie, "modelPath", headPart->model.GetModelName()); + RegisterString(&headPartData, args->movie, "chargenMorphPath", headPart->chargenMorph.GetModelName()); + RegisterString(&headPartData, args->movie, "raceMorphPath", headPart->raceMorph.GetModelName()); + partData.SetMember("base", &headPartData); + + // Get the overlay, if there is one + if(CALL_MEMBER_FN(actorBase, HasOverlays)()) { + BGSHeadPart * overlayPart = actorBase->GetHeadPartOverlayByType(headPart->type); + if(overlayPart) { + GFxValue overlayPartData; + args->movie->CreateObject(&overlayPartData); + RegisterString(&overlayPartData, args->movie, "partName", overlayPart->partName.data); + RegisterNumber(&overlayPartData, "partFlags", overlayPart->partFlags); + RegisterNumber(&overlayPartData, "partType", overlayPart->type); + RegisterString(&overlayPartData, args->movie, "modelPath", overlayPart->model.GetModelName()); + RegisterString(&overlayPartData, args->movie, "chargenMorphPath", overlayPart->chargenMorph.GetModelName()); + RegisterString(&overlayPartData, args->movie, "raceMorphPath", overlayPart->raceMorph.GetModelName()); + partData.SetMember("overlay", &overlayPartData); + } + } + + partList.PushBack(&partData); + } + + args->result->SetMember("parts", &partList); +} + +void SKSEScaleform_GetPlayerPosition::Invoke(Args * args) +{ + PlayerCharacter * player = (*g_thePlayer); + NiNode * root = player->GetNiRootNode(0); + if(root) { + args->movie->CreateObject(args->result); + GFxValue x; + x.SetNumber(root->m_localTransform.pos.x); + args->result->SetMember("x", &x); + GFxValue y; + y.SetNumber(root->m_localTransform.pos.y); + args->result->SetMember("y", &y); + GFxValue z; + z.SetNumber(root->m_localTransform.pos.z); + args->result->SetMember("z", &z); + } +} + +void SKSEScaleform_GetPlayerRotation::Invoke(Args * args) +{ + PlayerCharacter * player = (*g_thePlayer); + NiNode * root = player->GetNiRootNode(0); + + args->movie->CreateArray(args->result); + for(UInt32 i = 0; i < 3 * 3; i++) + { + GFxValue index; + index.SetNumber(((float*)(root->m_localTransform.rot.data))[i]); + args->result->PushBack(&index); + } +} + +void SKSEScaleform_SetPlayerRotation::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Array); + ASSERT(args->args[0].GetArraySize() == 9); + + PlayerCharacter * player = (*g_thePlayer); + NiNode * root = player->GetNiRootNode(0); + + for(UInt32 i = 0; i < 3 * 3; i++) + { + GFxValue val; + args->args[0].GetElement(i, &val); + if(val.GetType() != GFxValue::kType_Number) + break; + + ((float*)root->m_localTransform.rot.data)[i] = val.GetNumber(); + } + + NiAVObject::ControllerUpdateContext ctx; + root->UpdateWorldData(&ctx); +} + +void SKSEScaleform_GetRaceSexCameraRot::Invoke(Args * args) +{ + RaceSexMenu * raceMenu = DYNAMIC_CAST(MenuManager::GetSingleton()->GetMenu(&UIStringHolder::GetSingleton()->raceSexMenu), IMenu, RaceSexMenu); + if(raceMenu) { + NiNode * raceCamera = raceMenu->camera.cameraNode; + args->movie->CreateArray(args->result); + for(UInt32 i = 0; i < 3 * 3; i++) + { + GFxValue index; + index.SetNumber(((float*)raceCamera->m_localTransform.rot.data)[i]); + args->result->PushBack(&index); + } + } +} + +void SKSEScaleform_GetRaceSexCameraPos::Invoke(Args * args) +{ + RaceSexMenu * raceMenu = DYNAMIC_CAST(MenuManager::GetSingleton()->GetMenu(&UIStringHolder::GetSingleton()->raceSexMenu), IMenu, RaceSexMenu); + if(raceMenu) { + NiNode * raceCamera = raceMenu->camera.cameraNode; + args->movie->CreateObject(args->result); + GFxValue x; + x.SetNumber(raceCamera->m_localTransform.pos.x); + args->result->SetMember("x", &x); + GFxValue y; + y.SetNumber(raceCamera->m_localTransform.pos.y); + args->result->SetMember("y", &y); + GFxValue z; + z.SetNumber(raceCamera->m_localTransform.pos.z); + args->result->SetMember("z", &z); + } +} + +void SKSEScaleform_SetRaceSexCameraPos::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Object); + + RaceSexMenu * raceMenu = DYNAMIC_CAST(MenuManager::GetSingleton()->GetMenu(&UIStringHolder::GetSingleton()->raceSexMenu), IMenu, RaceSexMenu); + if(raceMenu) { + NiNode * raceCamera = raceMenu->camera.cameraNode; + + GFxValue val; + args->args[0].GetMember("x", &val); + if(val.GetType() == GFxValue::kType_Number) + raceCamera->m_localTransform.pos.x = val.GetNumber(); + + args->args[0].GetMember("y", &val); + if(val.GetType() == GFxValue::kType_Number) + raceCamera->m_localTransform.pos.y = val.GetNumber(); + + args->args[0].GetMember("z", &val); + if(val.GetType() == GFxValue::kType_Number) + raceCamera->m_localTransform.pos.z = val.GetNumber(); + + NiAVObject::ControllerUpdateContext ctx; + raceCamera->UpdateWorldData(&ctx); + } +} + +void SKSEScaleform_CreateMorphEditor::Invoke(Args * args) +{ +#ifdef FIXME + LPDIRECT3DDEVICE9 pDevice = NiDX9Renderer::GetSingleton()->m_pkD3DDevice9; + if (!pDevice) { + _ERROR("%s - Failed to acquire DirectX device.", __FUNCTION__); + return; + } + + PlayerCharacter * player = (*g_thePlayer); + g_Camera.SetProjParams(g_cameraFOV * (D3DX_PI / 180.0f), 1.0f, 1.0f, 1000.0f); + g_Camera.SetPanSpeed(g_panSpeed); + g_World.SetWorkingActor(player); + g_World.Setup(pDevice); + + Actor * actor = g_World.GetWorkingActor(); + if (!actor) { + _ERROR("%s - Invalid working actor.", __FUNCTION__); + return; + } + + TESNPC * actorBase = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (!actorBase) { + _ERROR("%s - No actor base.", __FUNCTION__); + return; + } + + NiNode * rootFaceGen = actor->GetFaceGenNiNode(); + if (!rootFaceGen) { + _ERROR("%s - No FaceGen node.", __FUNCTION__); + return; + } + + BSFaceGenAnimationData * animationData = actor->GetFaceGenAnimationData(); + if (animationData) { + FaceGen::GetSingleton()->isReset = 0; + for (UInt32 t = BSFaceGenAnimationData::kKeyframeType_Expression; t <= BSFaceGenAnimationData::kKeyframeType_Phoneme; t++) + { + BSFaceGenKeyframeMultiple * keyframe = &animationData->keyFrames[t]; + for (UInt32 i = 0; i < keyframe->count; i++) + keyframe->values[i] = 0.0; + keyframe->isUpdated = 0; + } + UpdateModelFace(rootFaceGen); + } + + UInt32 numHeadParts = actorBase->numHeadParts; + BGSHeadPart ** headParts = actorBase->headparts; + if (CALL_MEMBER_FN(actorBase, HasOverlays)()) { + numHeadParts = GetNumActorBaseOverlays(actorBase); + headParts = GetActorBaseOverlays(actorBase); + } + + // What?? + if (!headParts) { + _ERROR("%s - No head parts found.", __FUNCTION__); + return; + } + + for(UInt32 i = 0; i < rootFaceGen->m_children.m_emptyRunStart; i++) + { + std::set extraParts; // Collect extra hair parts + BGSHeadPart * hairPart = actorBase->GetCurrentHeadPartByType(BGSHeadPart::kTypeHair); + if(hairPart) { + BGSHeadPart * extraPart = NULL; + for(UInt32 p = 0; p < hairPart->extraParts.count; p++) { + if(hairPart->extraParts.GetNthItem(p, extraPart)) + extraParts.insert(extraPart); + } + } + + for(UInt32 h = 0; h < actorBase->numHeadParts; h++) { + BGSHeadPart * headPart = headParts[h]; + if (!headPart) + continue; + + NiAVObject * object = rootFaceGen->m_children.m_data[i]; + if (!object) + continue; + + if(headPart->partName == BSFixedString(object->m_name)) { + CDXNifMesh * mesh = CDXNifMesh::Create(pDevice, object->GetAsNiGeometry()); + if (!mesh) + continue; + + if (extraParts.find(headPart) != extraParts.end()) // Is one of the hair parts toggle visibility + mesh->SetVisible(false); + + CDXMaterial * material = mesh->GetMaterial(); + if (material) + material->SetWireframeColor(CDXVec3(((colors[i] >> 16) & 0xFF) / 255.0, ((colors[i] >> 8) & 0xFF) / 255.0, (colors[i] & 0xFF) / 255.0)); + + if (headPart->type != BGSHeadPart::kTypeFace) + mesh->SetLocked(true); + + g_World.AddMesh(mesh); + break; + } + } + } + + if (animationData) { + animationData->overrideFlag = 0; + CALL_MEMBER_FN(animationData, Reset)(1.0, 1, 1, 0, 0); + FaceGen::GetSingleton()->isReset = 1; + UpdateModelFace(rootFaceGen); + } + + args->movie->CreateObject(args->result); + RegisterNumber(args->result, "width", g_World.GetWidth()); + RegisterNumber(args->result, "height", g_World.GetHeight()); +#endif +} + +void SKSEScaleform_ReleaseMorphEditor::Invoke(Args * args) +{ +#ifdef FIXME + g_World.Release(); +#endif +} + +void SKSEScaleform_BeginRotateMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + +#ifdef FIXME + g_Camera.OnRotateBegin(args->args[0].GetNumber(), args->args[1].GetNumber()); +#endif +}; + +void SKSEScaleform_DoRotateMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + +#ifdef FIXME + g_Camera.OnRotate(args->args[0].GetNumber(), args->args[1].GetNumber()); +#endif +}; + +void SKSEScaleform_EndRotateMesh::Invoke(Args * args) +{ +#ifdef FIXME + g_Camera.OnRotateEnd(); +#endif +}; + +void SKSEScaleform_BeginPanMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + +#ifdef FIXME + g_Camera.OnMoveBegin(args->args[0].GetNumber(), args->args[1].GetNumber()); +#endif +}; + +void SKSEScaleform_DoPanMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + +#ifdef FIXME + g_Camera.OnMove(args->args[0].GetNumber(), args->args[1].GetNumber()); +#endif +}; + +void SKSEScaleform_EndPanMesh::Invoke(Args * args) +{ +#ifdef FIXME + g_Camera.OnMoveEnd(); +#endif +}; + +void SKSEScaleform_BeginPaintMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + + SInt32 x = args->args[0].GetNumber(); + SInt32 y = args->args[1].GetNumber(); + + bool hitMesh = false; +#ifdef FIXME + CDXBrush * brush = g_World.GetCurrentBrush(); + if (brush) { + CDXBrushPickerBegin brushStroke(brush); + brushStroke.SetMirror(brush->IsMirror()); + if (g_World.Pick(x, y, brushStroke)) + hitMesh = true; + } +#endif + + args->result->SetBool(hitMesh); +}; + +void SKSEScaleform_DoPaintMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + + SInt32 x = args->args[0].GetNumber(); + SInt32 y = args->args[1].GetNumber(); + +#ifdef FIXME + + CDXBrush * brush = g_World.GetCurrentBrush(); + if (brush) { + CDXBrushPickerUpdate brushStroke(brush); + brushStroke.SetMirror(brush->IsMirror()); + g_World.Pick(x, y, brushStroke); + } +#endif +}; + +void SKSEScaleform_EndPaintMesh::Invoke(Args * args) +{ +#ifdef FIXME + CDXBrush * brush = g_World.GetCurrentBrush(); + if(brush) + brush->EndStroke(); +#endif +}; + + +void SKSEScaleform_GetCurrentBrush::Invoke(Args * args) +{ +#ifdef FIXME + CDXBrush * brush = g_World.GetCurrentBrush(); + if (brush) + args->result->SetNumber(brush->GetType()); + else + args->result->SetNull(); +#endif +} + +void SKSEScaleform_SetCurrentBrush::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + +#ifdef FIXME + CDXBrush::BrushType brushType = (CDXBrush::BrushType)(UInt32)args->args[0].GetNumber(); + CDXBrush * brush = g_World.GetBrush(brushType); + if (brush) + g_World.SetCurrentBrush(brushType); + + args->result->SetBool(brush != NULL); +#endif +} + +void SKSEScaleform_GetBrushes::Invoke(Args * args) +{ + args->movie->CreateArray(args->result); + +#ifdef FIXME + for (auto brush : g_World.GetBrushes()) { + GFxValue object; + args->movie->CreateObject(&object); + RegisterNumber(&object, "type", brush->GetType()); + RegisterNumber(&object, "radius", brush->GetProperty(CDXBrush::kBrushProperty_Radius, CDXBrush::kBrushPropertyValue_Value)); + RegisterNumber(&object, "radiusMin", brush->GetProperty(CDXBrush::kBrushProperty_Radius, CDXBrush::kBrushPropertyValue_Min)); + RegisterNumber(&object, "radiusMax", brush->GetProperty(CDXBrush::kBrushProperty_Radius, CDXBrush::kBrushPropertyValue_Max)); + RegisterNumber(&object, "radiusInterval", brush->GetProperty(CDXBrush::kBrushProperty_Radius, CDXBrush::kBrushPropertyValue_Interval)); + RegisterNumber(&object, "strength", brush->GetProperty(CDXBrush::kBrushProperty_Strength, CDXBrush::kBrushPropertyValue_Value)); + RegisterNumber(&object, "strengthMin", brush->GetProperty(CDXBrush::kBrushProperty_Strength, CDXBrush::kBrushPropertyValue_Min)); + RegisterNumber(&object, "strengthMax", brush->GetProperty(CDXBrush::kBrushProperty_Strength, CDXBrush::kBrushPropertyValue_Max)); + RegisterNumber(&object, "strengthInterval", brush->GetProperty(CDXBrush::kBrushProperty_Strength, CDXBrush::kBrushPropertyValue_Interval)); + RegisterNumber(&object, "falloff", brush->GetProperty(CDXBrush::kBrushProperty_Falloff, CDXBrush::kBrushPropertyValue_Value)); + RegisterNumber(&object, "falloffMin", brush->GetProperty(CDXBrush::kBrushProperty_Falloff, CDXBrush::kBrushPropertyValue_Min)); + RegisterNumber(&object, "falloffMax", brush->GetProperty(CDXBrush::kBrushProperty_Falloff, CDXBrush::kBrushPropertyValue_Max)); + RegisterNumber(&object, "falloffInterval", brush->GetProperty(CDXBrush::kBrushProperty_Falloff, CDXBrush::kBrushPropertyValue_Interval)); + RegisterNumber(&object, "mirror", brush->IsMirror() ? 1.0 : 0.0); + args->result->PushBack(&object); + } +#endif +} + +void SKSEScaleform_SetBrushData::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Object); + +#ifdef FIXME + CDXBrush::BrushType brushType = (CDXBrush::BrushType)(UInt32)args->args[0].GetNumber(); + CDXBrush * brush = g_World.GetBrush(brushType); + if (brush) { + GFxValue radius; + args->args[1].GetMember("radius", &radius); + GFxValue strength; + args->args[1].GetMember("strength", &strength); + GFxValue falloff; + args->args[1].GetMember("falloff", &falloff); + GFxValue mirror; + args->args[1].GetMember("mirror", &mirror); + + brush->SetProperty(CDXBrush::kBrushProperty_Radius, CDXBrush::kBrushPropertyValue_Value, radius.GetNumber()); + brush->SetProperty(CDXBrush::kBrushProperty_Strength, CDXBrush::kBrushPropertyValue_Value, strength.GetNumber()); + brush->SetProperty(CDXBrush::kBrushProperty_Falloff, CDXBrush::kBrushPropertyValue_Value, falloff.GetNumber()); + brush->SetMirror(mirror.GetNumber() > 0.0 ? true : false); + + args->result->SetBool(true); + } + else { + args->result->SetBool(false); + } +#endif +} + +void SKSEScaleform_GetMeshes::Invoke(Args * args) +{ + args->movie->CreateArray(args->result); + +#ifdef FIXME + for (UInt32 i = 0; i < g_World.GetNumMeshes(); i++) { + CDXNifMesh * mesh = static_cast(g_World.GetNthMesh(i)); + if (mesh) { + NiGeometry * geometry = mesh->GetNifGeometry(); + if (geometry) { + GFxValue object; + args->movie->CreateObject(&object); + RegisterString(&object, args->movie, "name", geometry->m_name); + RegisterNumber(&object, "meshIndex", i); + RegisterBool(&object, "wireframe", mesh->ShowWireframe()); + RegisterBool(&object, "locked", mesh->IsLocked()); + RegisterBool(&object, "visible", mesh->IsVisible()); + RegisterBool(&object, "morphable", mesh->IsMorphable()); + RegisterNumber(&object, "vertices", mesh->GetVertexCount()); + + CDXMaterial * material = mesh->GetMaterial(); + if (material) { + CDXVec3 fColor = material->GetWireframeColor(); + UInt32 color = 0xFF000000 | (UInt32)(fColor.x * 255) << 16 | (UInt32)(fColor.y * 255) << 8 | (UInt32)(fColor.z * 255); + RegisterNumber(&object, "wfColor", color); + } + + args->result->PushBack(&object); + } + } + } +#endif +} + +void SKSEScaleform_SetMeshData::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Object); + + UInt32 i = (UInt32)args->args[0].GetNumber(); + +#ifdef FIXME + CDXNifMesh * mesh = static_cast(g_World.GetNthMesh(i)); + if (mesh) { + GFxValue wireframe; + args->args[1].GetMember("wireframe", &wireframe); + GFxValue locked; + args->args[1].GetMember("locked", &locked); + GFxValue visible; + args->args[1].GetMember("visible", &visible); + GFxValue wfColor; + args->args[1].GetMember("wfColor", &wfColor); + + mesh->SetLocked(locked.GetBool()); + mesh->SetShowWireframe(wireframe.GetBool()); + mesh->SetVisible(visible.GetBool()); + CDXMaterial * material = mesh->GetMaterial(); + if (material) { + UInt32 color = wfColor.GetNumber(); + material->SetWireframeColor(CDXVec3(((color >> 16) & 0xFF) / 255.0, ((color >> 8) & 0xFF) / 255.0, (color & 0xFF) / 255.0)); + } + + args->result->SetBool(true); + } + else { + args->result->SetBool(false); + } +#endif +} + +void SKSEScaleform_GetActionLimit::Invoke(Args * args) +{ +#ifdef FIXME + args->result->SetNumber(g_undoStack.GetLimit()); +#endif +} + +void SKSEScaleform_UndoAction::Invoke(Args * args) +{ +#ifdef FIXME + args->result->SetNumber(g_undoStack.Undo(true)); +#endif +} + +void SKSEScaleform_RedoAction::Invoke(Args * args) +{ +#ifdef FIXME + args->result->SetNumber(g_undoStack.Redo(true)); +#endif +} + +void SKSEScaleform_GoToAction::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + +#ifdef FIXME + SInt32 previous = g_undoStack.GetIndex(); + SInt32 result = g_undoStack.GoTo(args->args[0].GetNumber(), true); + + if (result != previous) { + Actor * actor = g_World.GetWorkingActor(); + if (actor) { + NiNode * rootFaceGen = actor->GetFaceGenNiNode(); + UpdateModelFace(rootFaceGen); + } + } + + args->result->SetNumber(result); +#endif +} + +void SKSEScaleform_GetMeshCameraRadius::Invoke(Args * args) +{ +#ifdef FIXME + args->result->SetNumber(g_Camera.GetRadius()); +#endif +} + +void SKSEScaleform_SetMeshCameraRadius::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + +#ifdef FIXME + g_Camera.SetRadius(args->args[0].GetNumber()); + g_Camera.Update(); +#endif +} + +#include + +void ReadFileDirectory(const char * lpFolder, const char ** lpFilePattern, UInt32 numPatterns, std::function file) +{ + char szFullPattern[MAX_PATH]; + WIN32_FIND_DATA FindFileData; + HANDLE hFindFile; + // first we are going to process any subdirectories + PathCombine(szFullPattern, lpFolder, "*"); + hFindFile = FindFirstFile(szFullPattern, &FindFileData); + if (hFindFile != INVALID_HANDLE_VALUE) + { + do + { + if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + // found a subdirectory; recurse into it + PathCombine(szFullPattern, lpFolder, FindFileData.cFileName); + if (FindFileData.cFileName[0] == '.') + continue; + + file(szFullPattern, FindFileData, true); + } + } while (FindNextFile(hFindFile, &FindFileData)); + FindClose(hFindFile); + } + // now we are going to look for the matching files + for (UInt32 i = 0; i < numPatterns; i++) + { + PathCombine(szFullPattern, lpFolder, lpFilePattern[i]); + hFindFile = FindFirstFile(szFullPattern, &FindFileData); + if (hFindFile != INVALID_HANDLE_VALUE) + { + do + { + if (!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + // found a file; do something with it + PathCombine(szFullPattern, lpFolder, FindFileData.cFileName); + file(szFullPattern, FindFileData, false); + } + } while (FindNextFile(hFindFile, &FindFileData)); + FindClose(hFindFile); + } + } +} + +void SKSEScaleform_GetExternalFiles::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + ASSERT(args->args[1].GetType() == GFxValue::kType_Array); + + const char * path = args->args[0].GetString(); + + UInt32 numPatterns = args->args[1].GetArraySize(); + + const char ** patterns = (const char **)ScaleformHeap_Allocate(numPatterns * sizeof(const char*)); + for (UInt32 i = 0; i < numPatterns; i++) { + GFxValue str; + args->args[1].GetElement(i, &str); + patterns[i] = str.GetString(); + } + + args->movie->CreateArray(args->result); + + ReadFileDirectory(path, patterns, numPatterns, [args](char* dirPath, WIN32_FIND_DATA & fileData, bool dir) + { + GFxValue fileInfo; + args->movie->CreateObject(&fileInfo); + RegisterString(&fileInfo, args->movie, "path", dirPath); + RegisterString(&fileInfo, args->movie, "name", fileData.cFileName); + UInt64 fileSize = (UInt64)fileData.nFileSizeHigh << 32 | fileData.nFileSizeLow; + RegisterNumber(&fileInfo, "size", fileSize); + SYSTEMTIME sysTime; + FileTimeToSystemTime(&fileData.ftLastWriteTime, &sysTime); + GFxValue date; + GFxValue params[7]; + params[0].SetNumber(sysTime.wYear); + params[1].SetNumber(sysTime.wMonth - 1); // Flash Month is 0-11, System time is 1-12 + params[2].SetNumber(sysTime.wDay); + params[3].SetNumber(sysTime.wHour); + params[4].SetNumber(sysTime.wMinute); + params[5].SetNumber(sysTime.wSecond); + params[6].SetNumber(sysTime.wMilliseconds); + args->movie->CreateObject(&date, "Date", params, 7); + fileInfo.SetMember("lastModified", &date); + RegisterBool(&fileInfo, "directory", dir); + args->result->PushBack(&fileInfo); + }); + + ScaleformHeap_Free(patterns); +} + diff --git a/skee/ScaleformCharGenFunctions.h b/skee/ScaleformCharGenFunctions.h new file mode 100644 index 0000000..3c9d831 --- /dev/null +++ b/skee/ScaleformCharGenFunctions.h @@ -0,0 +1,263 @@ +#pragma once + +#include "skse64/ScaleformCallbacks.h" +#include "skse64/GameThreads.h" +#include "skse64/GameTypes.h" + +class GFxValue; +class GFxMovieView; + +void RegisterNumber(GFxValue * dst, const char * name, double value); +void RegisterString(GFxValue * dst, GFxMovieView * view, const char * name, const char * str); + +class SKSEScaleform_GetHeadParts : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetModName : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetSliderData : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ReloadSliders : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_LoadPreset : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SavePreset : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ReadPreset : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ImportHead : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_LoadImportedHead : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ClearSculptData : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ReleaseImportedHead : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ExportHead : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetPlayerPosition : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetPlayerRotation : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetPlayerRotation : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetRaceSexCameraRot : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetRaceSexCameraPos : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetRaceSexCameraPos : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_CreateMorphEditor : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ReleaseMorphEditor : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_BeginRotateMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_DoRotateMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_EndRotateMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_BeginPanMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_DoPanMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_EndPanMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_BeginPaintMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_DoPaintMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_EndPaintMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetCurrentBrush : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetCurrentBrush : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetBrushes : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetBrushData : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetMeshes : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetMeshData : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetActionLimit : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_UndoAction : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_RedoAction : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GoToAction : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetMeshCameraRadius : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetMeshCameraRadius : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetExternalFiles : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; diff --git a/skee/ScaleformFunctions.cpp b/skee/ScaleformFunctions.cpp new file mode 100644 index 0000000..b2f579c --- /dev/null +++ b/skee/ScaleformFunctions.cpp @@ -0,0 +1,363 @@ +#include + +#include "ItemDataInterface.h" + +#include "skse64/PluginAPI.h" +#include "skse64/ScaleformMovie.h" + +#include "skse64/GameForms.h" +#include "skse64/GameRTTI.h" +#include "skse64/GameReferences.h" +#include "skse64/GameExtraData.h" + +#include "ScaleformFunctions.h" + +extern SKSETaskInterface * g_task; +extern ItemDataInterface g_itemDataInterface; +extern DyeMap g_dyeMap; + +class DyeableItemCollector +{ +public: + typedef std::vector FoundItems; + + DyeableItemCollector() {} + + bool Accept(InventoryEntryData* pEntryData) + { + if (!pEntryData) + return true; + + if (pEntryData->countDelta < 1) + return true; + + ExtendDataList* pExtendList = pEntryData->extendDataList; + if (!pExtendList) + return true; + + SInt32 n = 0; + BaseExtraList* pExtraDataList = pExtendList->GetNthItem(n); + while (pExtraDataList) + { + // Only armor right now + if (TESObjectARMO * armor = DYNAMIC_CAST(pEntryData->type, TESForm, TESObjectARMO)) { + ModifiedItemIdentifier itemData; + if (ExtraRank * extraRank = static_cast(pExtraDataList->GetByType(kExtraData_Rank))) + { + itemData.type |= ModifiedItemIdentifier::kTypeRank; + itemData.rankId = extraRank->rank; + } + if (ExtraUniqueID * extraUID = static_cast(pExtraDataList->GetByType(kExtraData_UniqueID))) + { + itemData.type |= ModifiedItemIdentifier::kTypeUID; + itemData.uid = extraUID->uniqueId; + itemData.ownerForm = extraUID->ownerFormId; + } + if (pExtraDataList->HasType(kExtraData_Worn) || pExtraDataList->HasType(kExtraData_WornLeft)) + { + itemData.type |= ModifiedItemIdentifier::kTypeSlot; + itemData.slotMask = armor->bipedObject.GetSlotMask(); + } + + if (itemData.type != ModifiedItemIdentifier::kTypeNone) { + itemData.form = pEntryData->type; + itemData.extraData = pExtraDataList; + m_found.push_back(itemData); + } + } + + n++; + pExtraDataList = pExtendList->GetNthItem(n); + } + + return true; + } + + FoundItems& Found() + { + return m_found; + } +private: + FoundItems m_found; +}; + +class DyeItemCollector +{ +public: + struct FoundData + { + TESForm * form; + SInt32 count; + std::vector colors; + }; + typedef std::vector FoundItems; + + DyeItemCollector() {} + + bool Accept(InventoryEntryData* pEntryData) + { + if (!pEntryData) + return true; + + if (pEntryData->countDelta < 1) + return true; + + if (pEntryData->type->formType == AlchemyItem::kTypeID) { + AlchemyItem * potion = DYNAMIC_CAST(pEntryData->type, TESForm, AlchemyItem); + if (potion) { + FoundData found; + found.form = NULL; + found.count = 0; + AlchemyItem::EffectItem * effect = NULL; + for (UInt32 i = 0; i < potion->effectItemList.count; i++) { + if (potion->effectItemList.GetNthItem(i, effect)) { + if (g_dyeMap.IsValidDye(effect->mgef)) { + found.form = pEntryData->type; + found.count = pEntryData->countDelta; + found.colors.push_back(g_dyeMap.GetDyeColor(effect->mgef)); + } + } + } + + if (g_dyeMap.IsValidDye(potion)) { + found.form = potion; + found.count = pEntryData->countDelta; + found.colors.clear(); + found.colors.push_back(g_dyeMap.GetDyeColor(potion)); + } + + if (found.form) + m_found.push_back(found); + } + } else if (g_dyeMap.IsValidDye(pEntryData->type)) { + FoundData found; + found.form = pEntryData->type; + found.count = pEntryData->countDelta; + found.colors.push_back(g_dyeMap.GetDyeColor(found.form)); + m_found.push_back(found); + } + return true; + } + + FoundItems& Found() + { + return m_found; + } +private: + FoundItems m_found; +}; + +void SKSEScaleform_GetDyeableItems::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + + UInt32 formidArg = 0; + TESForm * formArg = NULL; + + if (args->numArgs >= 1) { + formidArg = (UInt32)args->args[0].GetNumber(); + if (formidArg > 0) + formArg = LookupFormByID(formidArg); + } + + Actor * actor = DYNAMIC_CAST(formArg, TESForm, Actor); + if (!actor) { + _MESSAGE("%s - Invalid form type (%X)", __FUNCTION__, formidArg); + return; + } + + ExtraContainerChanges * extraContainer = static_cast(actor->extraData.GetByType(kExtraData_ContainerChanges)); + if (extraContainer) { + DyeableItemCollector::FoundItems foundData; + if (extraContainer->data && extraContainer->data->objList) { + DyeableItemCollector dyeFinder; + extraContainer->data->objList->Visit(dyeFinder); + foundData = dyeFinder.Found(); + + if (!foundData.empty()) { + args->movie->CreateArray(args->result); + + for (auto & item : foundData) { + GFxValue itm; + args->movie->CreateObject(&itm); + RegisterNumber(&itm, "type", item.type); + RegisterNumber(&itm, "uid", item.uid); + RegisterNumber(&itm, "owner", item.ownerForm); + RegisterNumber(&itm, "rankId", item.rankId); + RegisterNumber(&itm, "slotMask", item.slotMask); + RegisterNumber(&itm, "weaponSlot", item.weaponSlot); + + const char * itemName = NULL; + if (item.form && item.extraData) { + itemName = item.extraData->GetDisplayName(item.form); + if (!itemName) { + TESFullName* pFullName = DYNAMIC_CAST(item.form, TESForm, TESFullName); + if (pFullName) + itemName = pFullName->name.data; + } + + if (itemName) + RegisterString(&itm, args->movie, "name", itemName); + } + + ItemAttributeData * itemData = NULL; + if ((item.type & ModifiedItemIdentifier::kTypeRank) == ModifiedItemIdentifier::kTypeRank) + itemData = g_itemDataInterface.GetData(item.rankId); + + GFxValue colorArray; + args->movie->CreateArray(&colorArray); + for (UInt32 i = 0; i < 15; i++) { + UInt32 color = 0; + if (itemData) { + auto tintData = itemData->m_tintData; + if (tintData) { + auto it = tintData->m_colorMap.find(i); + if (it != tintData->m_colorMap.end()) + color = it->second; + } + } + + GFxValue colorValue; + colorValue.SetNumber(color); + colorArray.PushBack(&colorValue); + } + + itm.SetMember("colors", &colorArray); + args->result->PushBack(&itm); + } + } + } + } +} + +void SKSEScaleform_GetDyeItems::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + + UInt32 formidArg = 0; + TESForm * formArg = NULL; + + if (args->numArgs >= 1) { + formidArg = (UInt32)args->args[0].GetNumber(); + if (formidArg > 0) + formArg = LookupFormByID(formidArg); + } + + TESObjectREFR * reference = DYNAMIC_CAST(formArg, TESForm, Actor); + if (!reference) { + _MESSAGE("%s - Invalid form type (%X)", __FUNCTION__, formidArg); + return; + } + + ExtraContainerChanges * extraContainer = static_cast(reference->extraData.GetByType(kExtraData_ContainerChanges)); + if (extraContainer) { + DyeItemCollector::FoundItems foundData; + if (extraContainer->data && extraContainer->data->objList) { + DyeItemCollector dyeFinder; + extraContainer->data->objList->Visit(dyeFinder); + foundData = dyeFinder.Found(); + + if (!foundData.empty()) { + args->movie->CreateArray(args->result); + + for (auto & item : foundData) { + GFxValue itm; + args->movie->CreateObject(&itm); + RegisterNumber(&itm, "formId", item.form->formID); + RegisterNumber(&itm, "count", item.count); + + GFxValue colorArray; + args->movie->CreateArray(&colorArray); + for (auto color : item.colors) { + GFxValue itemColor; + itemColor.SetNumber(color); + colorArray.PushBack(&itemColor); + } + itm.SetMember("colors", &colorArray); + + const char * itemName = NULL; + if (item.form) { + TESFullName* pFullName = DYNAMIC_CAST(item.form, TESForm, TESFullName); + if (pFullName) + RegisterString(&itm, args->movie, "name", pFullName->name.data); + } + + args->result->PushBack(&itm); + } + } + } + } +} + +void SKSEScaleform_SetItemDyeColor::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Object); + ASSERT(args->args[2].GetType() == GFxValue::kType_Number); + + UInt32 formidArg = 0; + TESForm * formArg = NULL; + UInt32 maskIndex = args->args[2].GetNumber(); + UInt32 color = 0; + bool clear = false; + + if (args->numArgs >= 3) { + if (args->args[3].GetType() == GFxValue::kType_Undefined || args->args[3].GetType() == GFxValue::kType_Null) + clear = true; + else + color = args->args[3].GetNumber(); + } else { + clear = true; + } + + if (args->numArgs >= 1) { + formidArg = (UInt32)args->args[0].GetNumber(); + if (formidArg > 0) + formArg = LookupFormByID(formidArg); + } + + Actor * actor = DYNAMIC_CAST(formArg, TESForm, Actor); + if (!actor) { + _MESSAGE("%s - Invalid form type (%X)", __FUNCTION__, formidArg); + return; + } + + ModifiedItemIdentifier identifier; + GFxValue param[6]; + + if (args->args[1].HasMember("type")) { + args->args[1].GetMember("type", ¶m[0]); + identifier.type = param[0].GetNumber(); + } + if (args->args[1].HasMember("uid")) { + args->args[1].GetMember("uid", ¶m[1]); + identifier.uid = param[1].GetNumber(); + } + if (args->args[1].HasMember("owner")) { + args->args[1].GetMember("owner", ¶m[2]); + identifier.ownerForm = param[2].GetNumber(); + } + if (args->args[1].HasMember("rankId")) { + args->args[1].GetMember("rankId", ¶m[3]); + identifier.rankId = param[3].GetNumber(); + } + if (args->args[1].HasMember("slotMask")) { + args->args[1].GetMember("slotMask", ¶m[4]); + identifier.slotMask = param[4].GetNumber(); + } + if (args->args[1].HasMember("weaponSlot")) { + args->args[1].GetMember("weaponSlot", ¶m[5]); + identifier.weaponSlot = param[5].GetNumber(); + } + + UInt32 uniqueId = g_itemDataInterface.GetItemUniqueID(actor, identifier, true); + if (clear) + g_itemDataInterface.ClearItemDyeColor(uniqueId, maskIndex); + else + g_itemDataInterface.SetItemDyeColor(uniqueId, maskIndex, color); + + g_task->AddTask(new NIOVTaskUpdateItemDye(actor, identifier)); +} \ No newline at end of file diff --git a/skee/ScaleformFunctions.h b/skee/ScaleformFunctions.h new file mode 100644 index 0000000..25ee228 --- /dev/null +++ b/skee/ScaleformFunctions.h @@ -0,0 +1,30 @@ +#pragma once + +#include "skse64/ScaleformCallbacks.h" +#include "skse64/GameThreads.h" +#include "skse64/GameTypes.h" + +class GFxValue; +class GFxMovieView; + +void RegisterNumber(GFxValue * dst, const char * name, double value); +void RegisterString(GFxValue * dst, GFxMovieView * view, const char * name, const char * str); +void RegisterBool(GFxValue * dst, const char * name, bool value); + +class SKSEScaleform_GetDyeableItems : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetDyeItems : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetItemDyeColor : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; \ No newline at end of file diff --git a/skee/ScaleformFunctions_chargen.cpp b/skee/ScaleformFunctions_chargen.cpp new file mode 100644 index 0000000..1ccbdcf --- /dev/null +++ b/skee/ScaleformFunctions_chargen.cpp @@ -0,0 +1,1317 @@ +#include "common/IFileStream.h" + +#include "skse/PluginAPI.h" +#include "skse/skse_version.h" + +#include "ScaleformFunctions.h" +#include "MorphHandler.h" +#include "PartHandler.h" + +#include "skse/GameAPI.h" +#include "skse/GameData.h" +#include "skse/GameObjects.h" +#include "skse/GameRTTI.h" +#include "skse/GameStreams.h" +#include "skse/GameMenus.h" + +#include "NifUtils.h" + +#include "skse/NiRTTI.h" +#include "skse/NiObjects.h" +#include "skse/NiNodes.h" +#include "skse/NiGeometry.h" +#include "skse/NiSerialization.h" + +#include "skse/ScaleformMovie.h" +#include "skse/ScaleformLoader.h" + +#include "interfaces/OverrideVariant.h" +#include "interfaces/OverrideInterface.h" +#include "interfaces/OverlayInterface.h" +#include "interfaces/NiTransformInterface.h" +#include "interfaces/BodyMorphInterface.h" + +extern OverrideInterface * g_overrideInterface; +extern NiTransformInterface * g_transformInterface; +extern OverlayInterface * g_overlayInterface; +extern BodyMorphInterface * g_bodyMorphInterface; + +extern MorphHandler g_morphHandler; +extern PartSet g_partSet; + +extern SKSETaskInterface * g_task; + +#include "CDXCamera.h" +#include "skse/NiRenderer.h" + +#include "CDXNifScene.h" +#include "CDXNifMesh.h" +#include "CDXBrush.h" +#include "CDXUndo.h" +#include "CDXNifCommands.h" + +#include "skse/NiExtraData.h" + +UInt32 colors[] = { + 0xffffff, 0xff0000, 0x0000ff, 0x00ff00, + 0xff00ff, 0xffff00, 0x00ffff, 0x79f2f2, + 0xe58473, 0xe673da, 0x57d936, 0xcc3d00, + 0x5233cc, 0xcc9466, 0xbf001d, 0xb8bf30, + 0x8c007e, 0x466d8c, 0x287300, 0x397359, + 0x453973, 0x662e00, 0x050066, 0x665e1a, + 0x663342, 0x59332d, 0x4c000b, 0x40103b, + 0x33240d, 0x20330d, 0x0d1633, 0x1a332f +}; + +extern CDXModelViewerCamera g_Camera; +extern CDXNifScene g_World; +extern float g_panSpeed; +extern float g_cameraFOV; + +void RegisterNumber(GFxValue * dst, const char * name, double value) +{ + GFxValue fxValue; + fxValue.SetNumber(value); + dst->SetMember(name, &fxValue); +} + +void RegisterBool(GFxValue * dst, const char * name, bool value) +{ + GFxValue fxValue; + fxValue.SetBool(value); + dst->SetMember(name, &fxValue); +} + +void RegisterUnmanagedString(GFxValue * dst, const char * name, const char * str) +{ + GFxValue fxValue; + fxValue.SetString(str); + dst->SetMember(name, &fxValue); +} + +void RegisterString(GFxValue * dst, GFxMovieView * view, const char * name, const char * str) +{ + GFxValue fxValue; + view->CreateString(&fxValue, str); + dst->SetMember(name, &fxValue); +} + +void SKSEScaleform_SavePreset::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + ASSERT(args->args[1].GetType() == GFxValue::kType_Bool); + + bool saveJson = args->args[1].GetBool(); + const char * strData = args->args[0].GetString(); + + if (saveJson) + args->result->SetBool(g_morphHandler.SaveJsonPreset(strData)); + else + args->result->SetBool(g_morphHandler.SaveBinaryPreset(strData)); +} + +void SKSEScaleform_LoadPreset::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + ASSERT(args->args[1].GetType() == GFxValue::kType_Object); + ASSERT(args->args[2].GetType() == GFxValue::kType_Bool); + + bool loadJson = args->args[2].GetBool(); + + const char * strData = args->args[0].GetString(); + + GFxValue * object = NULL; + if (args->numArgs >= 2) + object = &args->args[1]; + + auto presetData = std::make_shared(); + bool loadError = loadJson ? g_morphHandler.LoadJsonPreset(strData, presetData) : g_morphHandler.LoadBinaryPreset(strData, presetData);//g_morphHandler.LoadPreset(strData, args->movie, object); + if (!loadError) { + g_morphHandler.ApplyPresetData(*g_thePlayer, presetData); + + RegisterNumber(object, "hairColor", presetData->hairColor); + + GFxValue tintArray; + args->movie->CreateArray(&tintArray); + + for(auto & tint : presetData->tints) { + GFxValue tintObject; + args->movie->CreateObject(&tintObject); + RegisterNumber(&tintObject, "color", tint.color); + RegisterNumber(&tintObject, "index", tint.index); + RegisterString(&tintObject, args->movie, "texture", tint.name.data); + tintArray.PushBack(&tintObject); + } + + object->SetMember("tints", &tintArray); + } + + args->result->SetBool(loadError); +} + +const char * GetGameSettingString(const char * key) +{ + Setting * setting = (*g_gameSettingCollection)->Get(key); + if(setting && setting->GetType() == Setting::kType_String) + return setting->data.s; + + return NULL; +} + +void SKSEScaleform_ReadPreset::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + ASSERT(args->args[1].GetType() == GFxValue::kType_Object); + ASSERT(args->args[2].GetType() == GFxValue::kType_Bool); + + bool loadJson = args->args[2].GetBool(); + const char * strData = args->args[0].GetString(); + + + GFxValue * object = NULL; + if (args->numArgs >= 2) + object = &args->args[1]; + + DataHandler * dataHandler = DataHandler::GetSingleton(); + auto presetData = std::make_shared(); + bool loadError = loadJson ? g_morphHandler.LoadJsonPreset(strData, presetData) : g_morphHandler.LoadBinaryPreset(strData, presetData);//g_morphHandler.LoadPreset(strData, args->movie, object); + if(!loadError) { + PlayerCharacter * player = (*g_thePlayer); + TESNPC * npc = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + + GFxValue modArray; + args->movie->CreateArray(&modArray); + for(std::vector::iterator it = presetData->modList.begin(); it != presetData->modList.end(); ++it) { + GFxValue modObject; + args->movie->CreateObject(&modObject); + RegisterString(&modObject, args->movie, "name", (*it).c_str()); + RegisterNumber(&modObject, "loadedIndex", dataHandler->GetModIndex((*it).c_str())); + modArray.PushBack(&modObject); + } + object->SetMember("mods", &modArray); + + GFxValue partArray; + args->movie->CreateArray(&partArray); + for(std::vector::iterator it = presetData->headParts.begin(); it != presetData->headParts.end(); ++it) { + GFxValue partObject; + args->movie->CreateString(&partObject, (*it)->partName.data); + partArray.PushBack(&partObject); + } + object->SetMember("headParts", &partArray); + + GFxValue weightObject; + args->movie->CreateObject(&weightObject); + RegisterUnmanagedString(&weightObject, "name", GetGameSettingString("sRSMWeight")); + RegisterNumber(&weightObject, "value", presetData->weight); + object->SetMember("weight", &weightObject); + + GFxValue hairObject; + args->movie->CreateObject(&hairObject); + RegisterUnmanagedString(&hairObject, "name", GetGameSettingString("sRSMHairColorPresets")); + RegisterNumber(&hairObject, "value", presetData->hairColor); + object->SetMember("hair", &hairObject); + + GFxValue tintArray; + args->movie->CreateArray(&tintArray); + for(std::vector::iterator it = presetData->tints.begin(); it != presetData->tints.end(); ++it) { + PresetData::Tint & tint = (*it); + GFxValue tintObject; + args->movie->CreateObject(&tintObject); + RegisterNumber(&tintObject, "color", tint.color); + RegisterNumber(&tintObject, "index", tint.index); + RegisterString(&tintObject, args->movie, "texture", tint.name.data); + tintArray.PushBack(&tintObject); + } + object->SetMember("tints", &tintArray); + + GFxValue morphArray; + args->movie->CreateArray(&morphArray); + + const char * presetNames[FacePresetList::kNumPresets]; + presetNames[FacePresetList::kPreset_NoseType] = GetGameSettingString("sRSMNoseTypes"); + presetNames[FacePresetList::kPreset_BrowType] = GetGameSettingString("sRSMBrowTypes"); + presetNames[FacePresetList::kPreset_EyesType] = GetGameSettingString("sRSMEyeTypes"); + presetNames[FacePresetList::kPreset_LipType] = GetGameSettingString("sRSMMouthTypes"); + + const char * morphNames[FaceMorphList::kNumMorphs]; + morphNames[FaceMorphList::kMorph_NoseShortLong] = GetGameSettingString("sRSMNoseLength"); + morphNames[FaceMorphList::kMorph_NoseDownUp] = GetGameSettingString("sRSMNoseHeight"); + morphNames[FaceMorphList::kMorph_JawUpDown] = GetGameSettingString("sRSMJawHeight"); + morphNames[FaceMorphList::kMorph_JawNarrowWide] = GetGameSettingString("sRSMJawWidth"); + morphNames[FaceMorphList::kMorph_JawBackForward] = GetGameSettingString("sRSMJawForward"); + morphNames[FaceMorphList::kMorph_CheeksDownUp] = GetGameSettingString("sRSMCheekboneHeight"); + morphNames[FaceMorphList::kMorph_CheeksInOut] = GetGameSettingString("sRSMCheekboneWidth"); + morphNames[FaceMorphList::kMorph_EyesMoveDownUp] = GetGameSettingString("sRSMEyeHeight"); + morphNames[FaceMorphList::kMorph_EyesMoveInOut] = GetGameSettingString("sRSMEyeDepth"); + morphNames[FaceMorphList::kMorph_BrowDownUp] = GetGameSettingString("sRSMBrowHeight"); + morphNames[FaceMorphList::kMorph_BrowInOut] = GetGameSettingString("sRSMBrowWidth"); + morphNames[FaceMorphList::kMorph_BrowBackForward] = GetGameSettingString("sRSMBrowForward"); + morphNames[FaceMorphList::kMorph_LipMoveDownUp] = GetGameSettingString("sRSMMouthHeight"); + morphNames[FaceMorphList::kMorph_LipMoveInOut] = GetGameSettingString("sRSMMouthForward"); + morphNames[FaceMorphList::kMorph_ChinThinWide] = GetGameSettingString("sRSMChinWidth"); + morphNames[FaceMorphList::kMorph_ChinMoveUpDown] = GetGameSettingString("sRSMChinLength"); + morphNames[FaceMorphList::kMorph_OverbiteUnderbite] = GetGameSettingString("sRSMChinForward"); + morphNames[FaceMorphList::kMorph_EyesBackForward] = GetGameSettingString("sRSMEyeDepth"); + morphNames[FaceMorphList::kMorph_Vampire] = NULL; + + UInt32 i = 0; + for(std::vector::iterator it = presetData->presets.begin(); it != presetData->presets.end(); ++it) { + GFxValue presetObject; + args->movie->CreateObject(&presetObject); + if(presetNames[i]) + RegisterUnmanagedString(&presetObject, "name", presetNames[i]); + RegisterNumber(&presetObject, "value", *it); + RegisterNumber(&presetObject, "type", 0); + RegisterNumber(&presetObject, "index", i); + morphArray.PushBack(&presetObject); + i++; + } + + i = 0; + for(auto & it : presetData->morphs) { + GFxValue presetObject; + args->movie->CreateObject(&presetObject); + if (i < FaceMorphList::kNumMorphs && morphNames[i]) + RegisterUnmanagedString(&presetObject, "name", morphNames[i]); + RegisterNumber(&presetObject, "value", it); + RegisterNumber(&presetObject, "type", 1); + RegisterNumber(&presetObject, "index", i); + morphArray.PushBack(&presetObject); + i++; + } + + i = 0; + for(auto & it : presetData->customMorphs) { + std::string morphName = "$"; + morphName.append(it.name.data); + GFxValue customObject; + args->movie->CreateObject(&customObject); + RegisterString(&customObject, args->movie, "name", morphName.c_str()); + RegisterNumber(&customObject, "value", it.value); + RegisterNumber(&customObject, "type", 2); + RegisterNumber(&customObject, "index", i); + morphArray.PushBack(&customObject); + i++; + } + i = 0; + for (auto & it : presetData->bodyMorphData) { + GFxValue customObject; + args->movie->CreateObject(&customObject); + RegisterString(&customObject, args->movie, "name", it.first.data); + + float morphSum = 0; + for (auto & keys : it.second) + morphSum += keys.second; + + RegisterNumber(&customObject, "value", morphSum); + RegisterNumber(&customObject, "type", 3); + RegisterNumber(&customObject, "index", i); + morphArray.PushBack(&customObject); + i++; + } + object->SetMember("morphs", &morphArray); + } + + args->result->SetBool(loadError); +} + +void SKSEScaleform_ReloadSliders::Invoke(Args * args) +{ + MenuManager * mm = MenuManager::GetSingleton(); + if (mm) { + BSFixedString t("RaceSex Menu"); + RaceSexMenu* raceMenu = (RaceSexMenu*)mm->GetMenu(&t); + if(raceMenu) { + PlayerCharacter * player = (*g_thePlayer); + CALL_MEMBER_FN(raceMenu, LoadSliders)((UInt32)player->baseForm, 0); + //CALL_MEMBER_FN(raceMenu, UpdatePlayer)(); + CALL_MEMBER_FN((*g_thePlayer), QueueNiNodeUpdate)(true); + } + } +} + +void SKSEScaleform_GetSliderData::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + + UInt32 sliderId = (UInt32)args->args[0].GetNumber(); + double value = args->args[1].GetNumber(); + + MenuManager * mm = MenuManager::GetSingleton(); + if(mm) + { + BSFixedString t("RaceSex Menu"); + RaceSexMenu * raceMenu = (RaceSexMenu *)mm->GetMenu(&t); + if(raceMenu) + { + RaceMenuSlider * slider = NULL; + RaceSexMenu::RaceComponent * raceData = NULL; + + UInt8 gender = 0; + PlayerCharacter * player = (*g_thePlayer); + TESNPC * actorBase = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + if(actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + if(raceMenu->raceIndex < raceMenu->sliderData[gender].count) + raceData = &raceMenu->sliderData[gender][raceMenu->raceIndex]; + if(raceData && sliderId < raceData->sliders.count) + slider = &raceData->sliders[sliderId]; + + if(raceData && slider) + { + args->movie->CreateObject(args->result); + RegisterNumber(args->result, "type", slider->type); + RegisterNumber(args->result, "index", slider->index); + + switch(slider->type) + { + case RaceMenuSlider::kTypeHeadPart: + { + if(slider->index < RaceSexMenu::kNumHeadPartLists) + { + BGSHeadPart * headPart = NULL; + raceMenu->headParts[slider->index].GetNthItem((UInt32)value, headPart); + if(headPart) { + RegisterNumber(args->result, "formId", headPart->formID); + RegisterString(args->result, args->movie, "partName", headPart->partName.data); + } + } + } + break; + case RaceMenuSlider::kTypeDoubleMorph: + { + // Provide case for custom parts + if(slider->index >= SLIDER_OFFSET) { + UInt32 sliderIndex = slider->index - SLIDER_OFFSET; + SliderInternalPtr sliderInternal = g_morphHandler.GetSliderByIndex(player->race, sliderIndex); + if(sliderInternal) { + RegisterNumber(args->result, "subType", sliderInternal->type); + switch (sliderInternal->type) + { + // Only acquire part information for actual part sliders + case SliderInternal::kTypeHeadPart: + { + UInt8 partType = sliderInternal->presetCount; + HeadPartList * partList = g_partSet.GetPartList(partType); + if (partList) + { + BGSHeadPart * targetPart = g_partSet.GetPartByIndex(partList, (UInt32)value - 1); + if (targetPart) { + RegisterNumber(args->result, "formId", targetPart->formID); + RegisterString(args->result, args->movie, "partName", targetPart->partName.data); + } + } + } + break; + } + } + } + } + break; + } + } + } + } +} + + +void SKSEScaleform_GetModName::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + + UInt32 modIndex = (UInt32)args->args[0].GetNumber(); + + DataHandler* pDataHandler = DataHandler::GetSingleton(); + ModInfo* modInfo = pDataHandler->modList.modInfoList.GetNthItem(modIndex); + if(modInfo) { + args->movie->CreateString(args->result, modInfo->name); + } +} + +void SKSEScaleform_ExportHead::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + + const char * strData = args->args[0].GetString(); + + // Get the Editor's working actor + Actor * actor = g_World.GetWorkingActor(); + if (!actor) + return; + + std::string nifPath = strData; + nifPath.append(".nif"); + std::string ddsPath = strData; + ddsPath.append(".dds"); + + g_task->AddTask(new SKSETaskExportHead(actor, nifPath.c_str(), ddsPath.c_str())); +} + +void SKSEScaleform_ImportHead::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + + const char * strData = args->args[0].GetString(); + + // Release the previous import just in case + g_World.ReleaseImport(); + + // Get the Editor's working actor + Actor * actor = g_World.GetWorkingActor(); + if (!actor) + return; + + BSFaceGenNiNode * faceNode = actor->GetFaceGenNiNode(); + TESNPC * actorBase = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (!actorBase || !faceNode) + return; + + UInt8 niStreamMemory[0x5B4]; + memset(niStreamMemory, 0, 0x5B4); + NiStream * niStream = (NiStream *)niStreamMemory; + CALL_MEMBER_FN(niStream, ctor)(); + + NiNode * rootNode = NULL; + BSResourceNiBinaryStream binaryStream(strData); + if (binaryStream.IsValid()) + { + niStream->LoadStream(&binaryStream); + if (niStream->m_rootObjects.m_data) + { + if (niStream->m_rootObjects.m_data[0]) // Get the root node + rootNode = niStream->m_rootObjects.m_data[0]->GetAsNiNode(); + if (rootNode) + { + args->movie->CreateArray(args->result); + /*args->movie->CreateObject(args->result); + + GFxValue source; + args->movie->CreateArray(&source); + + + + GFxValue destination; + args->movie->CreateArray(&destination); + + UInt32 numParts = actorBase->numHeadParts; + BGSHeadPart ** headParts = actorBase->headparts; + if (CALL_MEMBER_FN(actorBase, HasOverlays)()) { + numParts = GetNumActorBaseOverlays(actorBase); + headParts = GetActorBaseOverlays(actorBase); + } + + for (UInt32 i = 0; i < numParts; i++) + { + BGSHeadPart * headPart = headParts[i]; + if (!headPart) + continue; + + NiTriBasedGeom * geometry = GetTriBasedGeomByHeadPart(faceNode, headPart); + if (!geometry) + continue; + + NiGeometryData * geometryData = niptr_cast(geometry->m_spModelData); + if (!geometryData) + continue; + + bool morphable = geometry->GetExtraData("FOD") != NULL; + + GFxValue gfxPart; + args->movie->CreateObject(&gfxPart); + RegisterString(&gfxPart, args->movie, "name", geometry->m_name); + RegisterNumber(&gfxPart, "vertices", geometryData->m_usVertices); + RegisterBool(&gfxPart, "morphable", morphable); + destination.PushBack(&gfxPart); + } + + */ + + SInt32 gIndex = 0; + VisitObjects(rootNode, [&gIndex, &args](NiAVObject* trishape) + { + NiNode * parent = trishape->m_parent; + if (parent && BSFixedString(parent->m_name) == BSFixedString("BSFaceGenNiNodeSkinned")) { + NiTriBasedGeom * geometry = trishape->GetAsNiTriBasedGeom(); + if (!geometry) + return false; + + NiGeometryData * geometryData = niptr_cast(geometry->m_spModelData); + if (!geometryData) + return false; + + GFxValue gfxGeom; + args->movie->CreateObject(&gfxGeom); + RegisterString(&gfxGeom, args->movie, "name", geometry->m_name); + RegisterNumber(&gfxGeom, "vertices", geometryData->m_usVertices); + RegisterNumber(&gfxGeom, "gIndex", gIndex); + args->result->PushBack(&gfxGeom); + gIndex++; + } + + return false; + }); + + //args->result->SetMember("source", &source); + //args->result->SetMember("destination", &destination); + } + } + } + + // Add the Root node to the Editor + if (rootNode) { + rootNode->IncRef(); + g_World.SetImportRoot(rootNode); + } + + // Release the created NiStream + CALL_MEMBER_FN(niStream, dtor)(); +} + +void SKSEScaleform_ReleaseImportedHead::Invoke(Args * args) +{ + g_World.ReleaseImport(); +} + +void SKSEScaleform_LoadImportedHead::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Array); + + UInt32 meshLength = min(g_World.GetNumMeshes(), args->args[0].GetArraySize()); + + for (UInt32 i = 0; i < meshLength; i++) + { + CDXNifMesh * mesh = static_cast(g_World.GetNthMesh(i)); + if (mesh) { + NiNode * importRoot = g_World.GetImportRoot(); + if (importRoot) { + + SInt32 searchIndex = -1; + + GFxValue gfxIndex; + args->args[0].GetElement(i, &gfxIndex); + searchIndex = gfxIndex.GetNumber(); + + NiGeometry * sourceGeometry = NULL; + SInt32 gIndex = 0; + VisitObjects(importRoot, [&gIndex, &args, &searchIndex, &sourceGeometry](NiAVObject* trishape) + { + NiNode * parent = trishape->m_parent; + if (parent && BSFixedString(parent->m_name) == BSFixedString("BSFaceGenNiNodeSkinned")) { + NiTriBasedGeom * geometry = trishape->GetAsNiTriBasedGeom(); + if (!geometry) + return false; + + NiGeometryData * geometryData = niptr_cast(geometry->m_spModelData); + if (!geometryData) + return false; + + if (searchIndex == gIndex) { + sourceGeometry = geometry; + return true; + } + + gIndex++; + } + + return false; + }); + + if (sourceGeometry) { + std::shared_ptr importGeometry = std::make_shared(mesh, sourceGeometry); + if (importGeometry->Length() > 0) + importGeometry->Apply(g_undoStack.Push(importGeometry)); + } + } + } + } +} + + +void SKSEScaleform_ClearSculptData::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Array); + + UInt32 meshLength = args->args[0].GetArraySize(); + + for (UInt32 i = 0; i < meshLength; i++) + { + GFxValue gfxIndex; + args->args[0].GetElement(i, &gfxIndex); + SInt32 meshIndex = gfxIndex.GetNumber(); + + CDXNifMesh * mesh = static_cast(g_World.GetNthMesh(meshIndex)); + if (mesh) { + std::shared_ptr resetGeometry = std::make_shared(mesh); + if (resetGeometry->Length() > 0) + resetGeometry->Apply(g_undoStack.Push(resetGeometry)); + } + } +} + + +void SKSEScaleform_GetHeadParts::Invoke(Args * args) +{ + args->movie->CreateObject(args->result); + + GFxValue partList; + args->movie->CreateArray(&partList); + + PlayerCharacter * player = (*g_thePlayer); + TESNPC * actorBase = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + for(UInt32 i = 0; i < actorBase->numHeadParts; i++) + { + GFxValue partData; + args->movie->CreateObject(&partData); + + BGSHeadPart * headPart = actorBase->headparts[i]; + + GFxValue headPartData; + args->movie->CreateObject(&headPartData); + RegisterString(&headPartData, args->movie, "partName", headPart->partName.data); + RegisterNumber(&headPartData, "partFlags", headPart->partFlags); + RegisterNumber(&headPartData, "partType", headPart->type); + RegisterString(&headPartData, args->movie, "modelPath", headPart->model.GetModelName()); + RegisterString(&headPartData, args->movie, "chargenMorphPath", headPart->chargenMorph.GetModelName()); + RegisterString(&headPartData, args->movie, "raceMorphPath", headPart->raceMorph.GetModelName()); + partData.SetMember("base", &headPartData); + + // Get the overlay, if there is one + if(CALL_MEMBER_FN(actorBase, HasOverlays)()) { + BGSHeadPart * overlayPart = actorBase->GetHeadPartOverlayByType(headPart->type); + if(overlayPart) { + GFxValue overlayPartData; + args->movie->CreateObject(&overlayPartData); + RegisterString(&overlayPartData, args->movie, "partName", overlayPart->partName.data); + RegisterNumber(&overlayPartData, "partFlags", overlayPart->partFlags); + RegisterNumber(&overlayPartData, "partType", overlayPart->type); + RegisterString(&overlayPartData, args->movie, "modelPath", overlayPart->model.GetModelName()); + RegisterString(&overlayPartData, args->movie, "chargenMorphPath", overlayPart->chargenMorph.GetModelName()); + RegisterString(&overlayPartData, args->movie, "raceMorphPath", overlayPart->raceMorph.GetModelName()); + partData.SetMember("overlay", &overlayPartData); + } + } + + partList.PushBack(&partData); + } + + args->result->SetMember("parts", &partList); +} + +void SKSEScaleform_GetPlayerPosition::Invoke(Args * args) +{ + PlayerCharacter * player = (*g_thePlayer); + NiNode * root = player->GetNiRootNode(0); + if(root) { + args->movie->CreateObject(args->result); + GFxValue x; + x.SetNumber(root->m_localTransform.pos.x); + args->result->SetMember("x", &x); + GFxValue y; + y.SetNumber(root->m_localTransform.pos.y); + args->result->SetMember("y", &y); + GFxValue z; + z.SetNumber(root->m_localTransform.pos.z); + args->result->SetMember("z", &z); + } +} + +void SKSEScaleform_GetPlayerRotation::Invoke(Args * args) +{ + PlayerCharacter * player = (*g_thePlayer); + NiNode * root = player->GetNiRootNode(0); + + args->movie->CreateArray(args->result); + for(UInt32 i = 0; i < 3 * 3; i++) + { + GFxValue index; + index.SetNumber(((float*)(root->m_localTransform.rot.data))[i]); + args->result->PushBack(&index); + } +} + +void SKSEScaleform_SetPlayerRotation::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Array); + ASSERT(args->args[0].GetArraySize() == 9); + + PlayerCharacter * player = (*g_thePlayer); + NiNode * root = player->GetNiRootNode(0); + + for(UInt32 i = 0; i < 3 * 3; i++) + { + GFxValue val; + args->args[0].GetElement(i, &val); + if(val.GetType() != GFxValue::kType_Number) + break; + + ((float*)root->m_localTransform.rot.data)[i] = val.GetNumber(); + } + + NiAVObject::ControllerUpdateContext ctx; + root->UpdateWorldData(&ctx); +} + +void SKSEScaleform_GetRaceSexCameraRot::Invoke(Args * args) +{ + RaceSexMenu * raceMenu = DYNAMIC_CAST(MenuManager::GetSingleton()->GetMenu(&UIStringHolder::GetSingleton()->raceSexMenu), IMenu, RaceSexMenu); + if(raceMenu) { + NiNode * raceCamera = raceMenu->camera.cameraNode; + args->movie->CreateArray(args->result); + for(UInt32 i = 0; i < 3 * 3; i++) + { + GFxValue index; + index.SetNumber(((float*)raceCamera->m_localTransform.rot.data)[i]); + args->result->PushBack(&index); + } + } +} + +void SKSEScaleform_GetRaceSexCameraPos::Invoke(Args * args) +{ + RaceSexMenu * raceMenu = DYNAMIC_CAST(MenuManager::GetSingleton()->GetMenu(&UIStringHolder::GetSingleton()->raceSexMenu), IMenu, RaceSexMenu); + if(raceMenu) { + NiNode * raceCamera = raceMenu->camera.cameraNode; + args->movie->CreateObject(args->result); + GFxValue x; + x.SetNumber(raceCamera->m_localTransform.pos.x); + args->result->SetMember("x", &x); + GFxValue y; + y.SetNumber(raceCamera->m_localTransform.pos.y); + args->result->SetMember("y", &y); + GFxValue z; + z.SetNumber(raceCamera->m_localTransform.pos.z); + args->result->SetMember("z", &z); + } +} + +void SKSEScaleform_SetRaceSexCameraPos::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Object); + + RaceSexMenu * raceMenu = DYNAMIC_CAST(MenuManager::GetSingleton()->GetMenu(&UIStringHolder::GetSingleton()->raceSexMenu), IMenu, RaceSexMenu); + if(raceMenu) { + NiNode * raceCamera = raceMenu->camera.cameraNode; + + GFxValue val; + args->args[0].GetMember("x", &val); + if(val.GetType() == GFxValue::kType_Number) + raceCamera->m_localTransform.pos.x = val.GetNumber(); + + args->args[0].GetMember("y", &val); + if(val.GetType() == GFxValue::kType_Number) + raceCamera->m_localTransform.pos.y = val.GetNumber(); + + args->args[0].GetMember("z", &val); + if(val.GetType() == GFxValue::kType_Number) + raceCamera->m_localTransform.pos.z = val.GetNumber(); + + NiAVObject::ControllerUpdateContext ctx; + raceCamera->UpdateWorldData(&ctx); + } +} + +void SKSEScaleform_CreateMorphEditor::Invoke(Args * args) +{ + LPDIRECT3DDEVICE9 pDevice = NiDX9Renderer::GetSingleton()->m_pkD3DDevice9; + if (!pDevice) { + _ERROR("%s - Failed to acquire DirectX device.", __FUNCTION__); + return; + } + + PlayerCharacter * player = (*g_thePlayer); + g_Camera.SetProjParams(g_cameraFOV * (D3DX_PI / 180.0f), 1.0f, 1.0f, 1000.0f); + g_Camera.SetPanSpeed(g_panSpeed); + g_World.SetWorkingActor(player); + g_World.Setup(pDevice); + + Actor * actor = g_World.GetWorkingActor(); + if (!actor) { + _ERROR("%s - Invalid working actor.", __FUNCTION__); + return; + } + + TESNPC * actorBase = DYNAMIC_CAST(actor->baseForm, TESForm, TESNPC); + if (!actorBase) { + _ERROR("%s - No actor base.", __FUNCTION__); + return; + } + + NiNode * rootFaceGen = actor->GetFaceGenNiNode(); + if (!rootFaceGen) { + _ERROR("%s - No FaceGen node.", __FUNCTION__); + return; + } + + BSFaceGenAnimationData * animationData = actor->GetFaceGenAnimationData(); + if (animationData) { + FaceGen::GetSingleton()->isReset = 0; + for (UInt32 t = BSFaceGenAnimationData::kKeyframeType_Expression; t <= BSFaceGenAnimationData::kKeyframeType_Phoneme; t++) + { + BSFaceGenKeyframeMultiple * keyframe = &animationData->keyFrames[t]; + for (UInt32 i = 0; i < keyframe->count; i++) + keyframe->values[i] = 0.0; + keyframe->isUpdated = 0; + } + UpdateModelFace(rootFaceGen); + } + + UInt32 numHeadParts = actorBase->numHeadParts; + BGSHeadPart ** headParts = actorBase->headparts; + if (CALL_MEMBER_FN(actorBase, HasOverlays)()) { + numHeadParts = GetNumActorBaseOverlays(actorBase); + headParts = GetActorBaseOverlays(actorBase); + } + + // What?? + if (!headParts) { + _ERROR("%s - No head parts found.", __FUNCTION__); + return; + } + + for(UInt32 i = 0; i < rootFaceGen->m_children.m_emptyRunStart; i++) + { + std::set extraParts; // Collect extra hair parts + BGSHeadPart * hairPart = actorBase->GetCurrentHeadPartByType(BGSHeadPart::kTypeHair); + if(hairPart) { + BGSHeadPart * extraPart = NULL; + for(UInt32 p = 0; p < hairPart->extraParts.count; p++) { + if(hairPart->extraParts.GetNthItem(p, extraPart)) + extraParts.insert(extraPart); + } + } + + for(UInt32 h = 0; h < actorBase->numHeadParts; h++) { + BGSHeadPart * headPart = headParts[h]; + if (!headPart) + continue; + + NiAVObject * object = rootFaceGen->m_children.m_data[i]; + if (!object) + continue; + + if(headPart->partName == BSFixedString(object->m_name)) { + CDXNifMesh * mesh = CDXNifMesh::Create(pDevice, object->GetAsNiGeometry()); + if (!mesh) + continue; + + if (extraParts.find(headPart) != extraParts.end()) // Is one of the hair parts toggle visibility + mesh->SetVisible(false); + + CDXMaterial * material = mesh->GetMaterial(); + if (material) + material->SetWireframeColor(CDXVec3(((colors[i] >> 16) & 0xFF) / 255.0, ((colors[i] >> 8) & 0xFF) / 255.0, (colors[i] & 0xFF) / 255.0)); + + if (headPart->type != BGSHeadPart::kTypeFace) + mesh->SetLocked(true); + + g_World.AddMesh(mesh); + break; + } + } + } + + if (animationData) { + animationData->overrideFlag = 0; + CALL_MEMBER_FN(animationData, Reset)(1.0, 1, 1, 0, 0); + FaceGen::GetSingleton()->isReset = 1; + UpdateModelFace(rootFaceGen); + } + + args->movie->CreateObject(args->result); + RegisterNumber(args->result, "width", g_World.GetWidth()); + RegisterNumber(args->result, "height", g_World.GetHeight()); +} + +void SKSEScaleform_ReleaseMorphEditor::Invoke(Args * args) +{ + g_World.Release(); +} + +void SKSEScaleform_BeginRotateMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + + g_Camera.OnRotateBegin(args->args[0].GetNumber(), args->args[1].GetNumber()); +}; + +void SKSEScaleform_DoRotateMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + + g_Camera.OnRotate(args->args[0].GetNumber(), args->args[1].GetNumber()); +}; + +void SKSEScaleform_EndRotateMesh::Invoke(Args * args) +{ + g_Camera.OnRotateEnd(); +}; + +void SKSEScaleform_BeginPanMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + + g_Camera.OnMoveBegin(args->args[0].GetNumber(), args->args[1].GetNumber()); +}; + +void SKSEScaleform_DoPanMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + + g_Camera.OnMove(args->args[0].GetNumber(), args->args[1].GetNumber()); +}; + +void SKSEScaleform_EndPanMesh::Invoke(Args * args) +{ + g_Camera.OnMoveEnd(); +}; + +void SKSEScaleform_BeginPaintMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + + SInt32 x = args->args[0].GetNumber(); + SInt32 y = args->args[1].GetNumber(); + + bool hitMesh = false; + CDXBrush * brush = g_World.GetCurrentBrush(); + if (brush) { + CDXBrushPickerBegin brushStroke(brush); + brushStroke.SetMirror(brush->IsMirror()); + if (g_World.Pick(x, y, brushStroke)) + hitMesh = true; + } + + args->result->SetBool(hitMesh); +}; + +void SKSEScaleform_DoPaintMesh::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Number); + + SInt32 x = args->args[0].GetNumber(); + SInt32 y = args->args[1].GetNumber(); + + CDXBrush * brush = g_World.GetCurrentBrush(); + if (brush) { + CDXBrushPickerUpdate brushStroke(brush); + brushStroke.SetMirror(brush->IsMirror()); + g_World.Pick(x, y, brushStroke); + } +}; + +void SKSEScaleform_EndPaintMesh::Invoke(Args * args) +{ + CDXBrush * brush = g_World.GetCurrentBrush(); + if(brush) + brush->EndStroke(); +}; + + +void SKSEScaleform_GetCurrentBrush::Invoke(Args * args) +{ + CDXBrush * brush = g_World.GetCurrentBrush(); + if (brush) + args->result->SetNumber(brush->GetType()); + else + args->result->SetNull(); +} + +void SKSEScaleform_SetCurrentBrush::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + + CDXBrush::BrushType brushType = (CDXBrush::BrushType)(UInt32)args->args[0].GetNumber(); + CDXBrush * brush = g_World.GetBrush(brushType); + if (brush) + g_World.SetCurrentBrush(brushType); + + args->result->SetBool(brush != NULL); +} + +void SKSEScaleform_GetBrushes::Invoke(Args * args) +{ + args->movie->CreateArray(args->result); + for (auto brush : g_World.GetBrushes()) { + GFxValue object; + args->movie->CreateObject(&object); + RegisterNumber(&object, "type", brush->GetType()); + RegisterNumber(&object, "radius", brush->GetProperty(CDXBrush::kBrushProperty_Radius, CDXBrush::kBrushPropertyValue_Value)); + RegisterNumber(&object, "radiusMin", brush->GetProperty(CDXBrush::kBrushProperty_Radius, CDXBrush::kBrushPropertyValue_Min)); + RegisterNumber(&object, "radiusMax", brush->GetProperty(CDXBrush::kBrushProperty_Radius, CDXBrush::kBrushPropertyValue_Max)); + RegisterNumber(&object, "radiusInterval", brush->GetProperty(CDXBrush::kBrushProperty_Radius, CDXBrush::kBrushPropertyValue_Interval)); + RegisterNumber(&object, "strength", brush->GetProperty(CDXBrush::kBrushProperty_Strength, CDXBrush::kBrushPropertyValue_Value)); + RegisterNumber(&object, "strengthMin", brush->GetProperty(CDXBrush::kBrushProperty_Strength, CDXBrush::kBrushPropertyValue_Min)); + RegisterNumber(&object, "strengthMax", brush->GetProperty(CDXBrush::kBrushProperty_Strength, CDXBrush::kBrushPropertyValue_Max)); + RegisterNumber(&object, "strengthInterval", brush->GetProperty(CDXBrush::kBrushProperty_Strength, CDXBrush::kBrushPropertyValue_Interval)); + RegisterNumber(&object, "falloff", brush->GetProperty(CDXBrush::kBrushProperty_Falloff, CDXBrush::kBrushPropertyValue_Value)); + RegisterNumber(&object, "falloffMin", brush->GetProperty(CDXBrush::kBrushProperty_Falloff, CDXBrush::kBrushPropertyValue_Min)); + RegisterNumber(&object, "falloffMax", brush->GetProperty(CDXBrush::kBrushProperty_Falloff, CDXBrush::kBrushPropertyValue_Max)); + RegisterNumber(&object, "falloffInterval", brush->GetProperty(CDXBrush::kBrushProperty_Falloff, CDXBrush::kBrushPropertyValue_Interval)); + RegisterNumber(&object, "mirror", brush->IsMirror() ? 1.0 : 0.0); + args->result->PushBack(&object); + } +} + +void SKSEScaleform_SetBrushData::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Object); + + CDXBrush::BrushType brushType = (CDXBrush::BrushType)(UInt32)args->args[0].GetNumber(); + CDXBrush * brush = g_World.GetBrush(brushType); + if (brush) { + GFxValue radius; + args->args[1].GetMember("radius", &radius); + GFxValue strength; + args->args[1].GetMember("strength", &strength); + GFxValue falloff; + args->args[1].GetMember("falloff", &falloff); + GFxValue mirror; + args->args[1].GetMember("mirror", &mirror); + + brush->SetProperty(CDXBrush::kBrushProperty_Radius, CDXBrush::kBrushPropertyValue_Value, radius.GetNumber()); + brush->SetProperty(CDXBrush::kBrushProperty_Strength, CDXBrush::kBrushPropertyValue_Value, strength.GetNumber()); + brush->SetProperty(CDXBrush::kBrushProperty_Falloff, CDXBrush::kBrushPropertyValue_Value, falloff.GetNumber()); + brush->SetMirror(mirror.GetNumber() > 0.0 ? true : false); + + args->result->SetBool(true); + } + else { + args->result->SetBool(false); + } +} + +void SKSEScaleform_GetMeshes::Invoke(Args * args) +{ + args->movie->CreateArray(args->result); + for (UInt32 i = 0; i < g_World.GetNumMeshes(); i++) { + CDXNifMesh * mesh = static_cast(g_World.GetNthMesh(i)); + if (mesh) { + NiGeometry * geometry = mesh->GetNifGeometry(); + if (geometry) { + GFxValue object; + args->movie->CreateObject(&object); + RegisterString(&object, args->movie, "name", geometry->m_name); + RegisterNumber(&object, "meshIndex", i); + RegisterBool(&object, "wireframe", mesh->ShowWireframe()); + RegisterBool(&object, "locked", mesh->IsLocked()); + RegisterBool(&object, "visible", mesh->IsVisible()); + RegisterBool(&object, "morphable", mesh->IsMorphable()); + RegisterNumber(&object, "vertices", mesh->GetVertexCount()); + + CDXMaterial * material = mesh->GetMaterial(); + if (material) { + CDXVec3 fColor = material->GetWireframeColor(); + UInt32 color = 0xFF000000 | (UInt32)(fColor.x * 255) << 16 | (UInt32)(fColor.y * 255) << 8 | (UInt32)(fColor.z * 255); + RegisterNumber(&object, "wfColor", color); + } + + args->result->PushBack(&object); + } + } + } +} + +void SKSEScaleform_SetMeshData::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 2); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + ASSERT(args->args[1].GetType() == GFxValue::kType_Object); + + UInt32 i = (UInt32)args->args[0].GetNumber(); + CDXNifMesh * mesh = static_cast(g_World.GetNthMesh(i)); + if (mesh) { + GFxValue wireframe; + args->args[1].GetMember("wireframe", &wireframe); + GFxValue locked; + args->args[1].GetMember("locked", &locked); + GFxValue visible; + args->args[1].GetMember("visible", &visible); + GFxValue wfColor; + args->args[1].GetMember("wfColor", &wfColor); + + mesh->SetLocked(locked.GetBool()); + mesh->SetShowWireframe(wireframe.GetBool()); + mesh->SetVisible(visible.GetBool()); + CDXMaterial * material = mesh->GetMaterial(); + if (material) { + UInt32 color = wfColor.GetNumber(); + material->SetWireframeColor(CDXVec3(((color >> 16) & 0xFF) / 255.0, ((color >> 8) & 0xFF) / 255.0, (color & 0xFF) / 255.0)); + } + + args->result->SetBool(true); + } + else { + args->result->SetBool(false); + } +} + +void SKSEScaleform_GetActionLimit::Invoke(Args * args) +{ + args->result->SetNumber(g_undoStack.GetLimit()); +} + +void SKSEScaleform_UndoAction::Invoke(Args * args) +{ + args->result->SetNumber(g_undoStack.Undo(true)); +} + +void SKSEScaleform_RedoAction::Invoke(Args * args) +{ + args->result->SetNumber(g_undoStack.Redo(true)); +} + +void SKSEScaleform_GoToAction::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + + SInt32 previous = g_undoStack.GetIndex(); + SInt32 result = g_undoStack.GoTo(args->args[0].GetNumber(), true); + + if (result != previous) { + Actor * actor = g_World.GetWorkingActor(); + if (actor) { + NiNode * rootFaceGen = actor->GetFaceGenNiNode(); + UpdateModelFace(rootFaceGen); + } + } + + args->result->SetNumber(result); +} + +void SKSEScaleform_GetMeshCameraRadius::Invoke(Args * args) +{ + args->result->SetNumber(g_Camera.GetRadius()); +} + +void SKSEScaleform_SetMeshCameraRadius::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_Number); + + g_Camera.SetRadius(args->args[0].GetNumber()); + g_Camera.Update(); +} + +#include + +void ReadFileDirectory(const char * lpFolder, const char ** lpFilePattern, UInt32 numPatterns, std::function file) +{ + char szFullPattern[MAX_PATH]; + WIN32_FIND_DATA FindFileData; + HANDLE hFindFile; + // first we are going to process any subdirectories + PathCombine(szFullPattern, lpFolder, "*"); + hFindFile = FindFirstFile(szFullPattern, &FindFileData); + if (hFindFile != INVALID_HANDLE_VALUE) + { + do + { + if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + // found a subdirectory; recurse into it + PathCombine(szFullPattern, lpFolder, FindFileData.cFileName); + if (FindFileData.cFileName[0] == '.') + continue; + + file(szFullPattern, FindFileData, true); + } + } while (FindNextFile(hFindFile, &FindFileData)); + FindClose(hFindFile); + } + // now we are going to look for the matching files + for (UInt32 i = 0; i < numPatterns; i++) + { + PathCombine(szFullPattern, lpFolder, lpFilePattern[i]); + hFindFile = FindFirstFile(szFullPattern, &FindFileData); + if (hFindFile != INVALID_HANDLE_VALUE) + { + do + { + if (!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + // found a file; do something with it + PathCombine(szFullPattern, lpFolder, FindFileData.cFileName); + file(szFullPattern, FindFileData, false); + } + } while (FindNextFile(hFindFile, &FindFileData)); + FindClose(hFindFile); + } + } +} + +void SKSEScaleform_GetExternalFiles::Invoke(Args * args) +{ + ASSERT(args->numArgs >= 1); + ASSERT(args->args[0].GetType() == GFxValue::kType_String); + ASSERT(args->args[1].GetType() == GFxValue::kType_Array); + + const char * path = args->args[0].GetString(); + + UInt32 numPatterns = args->args[1].GetArraySize(); + + const char ** patterns = (const char **)ScaleformHeap_Allocate(numPatterns * sizeof(const char*)); + for (UInt32 i = 0; i < numPatterns; i++) { + GFxValue str; + args->args[1].GetElement(i, &str); + patterns[i] = str.GetString(); + } + + args->movie->CreateArray(args->result); + + ReadFileDirectory(path, patterns, numPatterns, [args](char* dirPath, WIN32_FIND_DATA & fileData, bool dir) + { + GFxValue fileInfo; + args->movie->CreateObject(&fileInfo); + RegisterString(&fileInfo, args->movie, "path", dirPath); + RegisterString(&fileInfo, args->movie, "name", fileData.cFileName); + UInt64 fileSize = (UInt64)fileData.nFileSizeHigh << 32 | fileData.nFileSizeLow; + RegisterNumber(&fileInfo, "size", fileSize); + SYSTEMTIME sysTime; + FileTimeToSystemTime(&fileData.ftLastWriteTime, &sysTime); + GFxValue date; + GFxValue params[7]; + params[0].SetNumber(sysTime.wYear); + params[1].SetNumber(sysTime.wMonth - 1); // Flash Month is 0-11, System time is 1-12 + params[2].SetNumber(sysTime.wDay); + params[3].SetNumber(sysTime.wHour); + params[4].SetNumber(sysTime.wMinute); + params[5].SetNumber(sysTime.wSecond); + params[6].SetNumber(sysTime.wMilliseconds); + args->movie->CreateObject(&date, "Date", params, 7); + fileInfo.SetMember("lastModified", &date); + RegisterBool(&fileInfo, "directory", dir); + args->result->PushBack(&fileInfo); + }); + + ScaleformHeap_Free(patterns); +} + diff --git a/skee/ScaleformFunctions_chargen.h b/skee/ScaleformFunctions_chargen.h new file mode 100644 index 0000000..1343457 --- /dev/null +++ b/skee/ScaleformFunctions_chargen.h @@ -0,0 +1,263 @@ +#pragma once + +#include "skse/ScaleformCallbacks.h" +#include "skse/GameThreads.h" +#include "skse/GameTypes.h" + +class GFxValue; +class GFxMovieView; + +void RegisterNumber(GFxValue * dst, const char * name, double value); +void RegisterString(GFxValue * dst, GFxMovieView * view, const char * name, const char * str); + +class SKSEScaleform_GetHeadParts : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetModName : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetSliderData : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ReloadSliders : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_LoadPreset : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SavePreset : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ReadPreset : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ImportHead : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_LoadImportedHead : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ClearSculptData : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ReleaseImportedHead : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ExportHead : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetPlayerPosition : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetPlayerRotation : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetPlayerRotation : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetRaceSexCameraRot : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetRaceSexCameraPos : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetRaceSexCameraPos : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_CreateMorphEditor : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_ReleaseMorphEditor : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_BeginRotateMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_DoRotateMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_EndRotateMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_BeginPanMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_DoPanMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_EndPanMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_BeginPaintMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_DoPaintMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_EndPaintMesh : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetCurrentBrush : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetCurrentBrush : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetBrushes : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetBrushData : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetMeshes : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetMeshData : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetActionLimit : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_UndoAction : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_RedoAction : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GoToAction : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetMeshCameraRadius : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_SetMeshCameraRadius : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; + +class SKSEScaleform_GetExternalFiles : public GFxFunctionHandler +{ +public: + virtual void Invoke(Args * args); +}; diff --git a/skee/ScaleformLoader.cpp b/skee/ScaleformLoader.cpp new file mode 100644 index 0000000..513a7dc --- /dev/null +++ b/skee/ScaleformLoader.cpp @@ -0,0 +1,10 @@ +#include "ScaleformLoader.h" + +#include "skse64_common/Relocation.h" + +GFxLoader * GFxLoader::GetSingleton() +{ + // 4E9F39D1066653EF254B38406212E476F80A6C9B+AE + RelocPtr g_GFxLoader(0x02EC72B0); + return *g_GFxLoader; +} diff --git a/skee/ScaleformLoader.h b/skee/ScaleformLoader.h new file mode 100644 index 0000000..6f0f526 --- /dev/null +++ b/skee/ScaleformLoader.h @@ -0,0 +1,46 @@ +#pragma once + +#include "skse64/ScaleformState.h" + +class NiTexture; +class IMenu; +class GImageInfoBase; + +extern bool g_logScaleform; + +class GFxImageLoader : public GFxState +{ +public: + virtual GImageInfoBase* LoadImage(const char * url) = 0; +}; + +class BSScaleformImageLoader : public GFxImageLoader +{ +public: + virtual ~BSScaleformImageLoader(); + virtual GImageInfoBase* LoadImage(const char * url); + + MEMBER_FN_PREFIX(BSScaleformImageLoader); + DEFINE_MEMBER_FN(AddVirtualImage, UInt8, 0, NiTexture ** texture); + DEFINE_MEMBER_FN(ReleaseVirtualImage, UInt8, 0, NiTexture ** texture); +}; + +class GFxLoader +{ +public: + UInt32 unk00; // 00 + GFxStateBag * stateBag; // 04 + UInt32 unk08; // 08 + UInt32 unk0C; // 0C + BSScaleformImageLoader * imageLoader; // 10 + + static GFxLoader * GetSingleton(); + + + + MEMBER_FN_PREFIX(GFxLoader); + DEFINE_MEMBER_FN(ctor, GFxLoader *, 0x00EA7830); + + // Note: Probably in subclass + DEFINE_MEMBER_FN(LoadMovie, bool, 0x00EA7EF0, IMenu* menu, GFxMovieView** viewOut, const char* name, int arg4, float arg5); +}; diff --git a/skee/ScaleformUtils.cpp b/skee/ScaleformUtils.cpp new file mode 100644 index 0000000..30deb46 --- /dev/null +++ b/skee/ScaleformUtils.cpp @@ -0,0 +1,31 @@ +#include "ScaleformUtils.h" +#include "skse64/ScaleformMovie.h" +#include "skse64/ScaleformValue.h" + +void RegisterNumber(GFxValue * dst, const char * name, double value) +{ + GFxValue fxValue; + fxValue.SetNumber(value); + dst->SetMember(name, &fxValue); +} + +void RegisterBool(GFxValue * dst, const char * name, bool value) +{ + GFxValue fxValue; + fxValue.SetBool(value); + dst->SetMember(name, &fxValue); +} + +void RegisterUnmanagedString(GFxValue * dst, const char * name, const char * str) +{ + GFxValue fxValue; + fxValue.SetString(str); + dst->SetMember(name, &fxValue); +} + +void RegisterString(GFxValue * dst, GFxMovieView * view, const char * name, const char * str) +{ + GFxValue fxValue; + view->CreateString(&fxValue, str); + dst->SetMember(name, &fxValue); +} \ No newline at end of file diff --git a/skee/ScaleformUtils.h b/skee/ScaleformUtils.h new file mode 100644 index 0000000..718affc --- /dev/null +++ b/skee/ScaleformUtils.h @@ -0,0 +1,9 @@ +#pragma once + +class GFxValue; +class GFxMovieView; + +void RegisterNumber(GFxValue * dst, const char * name, double value); +void RegisterBool(GFxValue * dst, const char * name, bool value); +void RegisterUnmanagedString(GFxValue * dst, const char * name, const char * str); +void RegisterString(GFxValue * dst, GFxMovieView * view, const char * name, const char * str); \ No newline at end of file diff --git a/skee/ShaderUtilities.cpp b/skee/ShaderUtilities.cpp new file mode 100644 index 0000000..be7e8cc --- /dev/null +++ b/skee/ShaderUtilities.cpp @@ -0,0 +1,624 @@ +#include "ShaderUtilities.h" +#include "OverrideVariant.h" +#include "NifUtils.h" + +#include "skse64/PluginAPI.h" + +#include "skse64/GameThreads.h" +#include "skse64/GameReferences.h" +#include "skse64/GameObjects.h" +#include "skse64/GameRTTI.h" +#include "skse64/GameExtraData.h" + +#include "skse64/NiProperties.h" +#include "skse64/NiMaterial.h" +#include "skse64/NiGeometry.h" +#include "skse64/NiRTTI.h" +#include "skse64/NiControllers.h" +#include "skse64/NiExtraData.h" + +extern SKSETaskInterface * g_task; + +void GetShaderProperty(NiAVObject * node, OverrideVariant * value) +{ + bool shaderError = false; + BSGeometry * geometry = node->GetAsBSGeometry(); + if(geometry) + { + BSShaderProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if(!shaderProperty) { + _MESSAGE("Shader does not exist for %s", node->m_name); + shaderError = true; + return; + } + if(value->key >= OverrideVariant::kParam_ControllersStart && value->key <= OverrideVariant::kParam_ControllersEnd) + { + SInt8 currentIndex = 0; + SInt8 controllerIndex = value->index; + if(controllerIndex != -1) + { + NiTimeController * foundController = NULL; + NiTimeController * controller = ni_cast(shaderProperty->m_controller, NiTimeController); + while(controller) + { + if(currentIndex == controllerIndex) { + foundController = controller; + break; + } + + controller = ni_cast(controller->next, NiTimeController); + currentIndex++; + } + + if(foundController) + { + switch(value->key) + { + case OverrideVariant::kParam_ControllerFrequency: PackValue(value, value->key, value->index, &foundController->m_fFrequency); break; + case OverrideVariant::kParam_ControllerPhase: PackValue(value, value->key, value->index, &foundController->m_fPhase); break; + case OverrideVariant::kParam_ControllerStartTime: PackValue(value, value->key, value->index, &foundController->m_fLoKeyTime); break; + case OverrideVariant::kParam_ControllerStopTime: PackValue(value, value->key, value->index, &foundController->m_fHiKeyTime); break; + + // Special cases + case OverrideVariant::kParam_ControllerStartStop: + { + float val = 0.0; + PackValue(value, value->key, value->index, &val); break; + } + break; + default: + _MESSAGE("Unknown controller key %d %s", value->key, node->m_name); + shaderError = true; + break; + } + } + } + + return; // Only working on controller properties + } + if(ni_is_type(shaderProperty->GetRTTI(), BSEffectShaderProperty)) + { + BSEffectShaderMaterial * material = (BSEffectShaderMaterial*)shaderProperty->material; + switch(value->key) + { + case OverrideVariant::kParam_ShaderEmissiveColor: PackValue(value, value->key, value->index, &material->emissiveColor); break; + case OverrideVariant::kParam_ShaderEmissiveMultiple: PackValue(value, value->key, value->index, &material->emissiveMultiple); break; + default: + _MESSAGE("Unknown shader key %d %s", value->key, node->m_name); + break; + } +#ifdef _DEBUG + _MESSAGE("Applied EffectShader property %d %X to %s", value->key, value->data.u, node->m_name); +#endif + } + else if(ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) + { + BSLightingShaderProperty * lightingShader = (BSLightingShaderProperty *)shaderProperty; + BSLightingShaderMaterial * material = (BSLightingShaderMaterial *)shaderProperty->material; + switch(value->key) + { + case OverrideVariant::kParam_ShaderEmissiveColor: PackValue(value, value->key, value->index, lightingShader->emissiveColor); break; + case OverrideVariant::kParam_ShaderEmissiveMultiple: PackValue(value, value->key, value->index, &lightingShader->emissiveMultiple); break; + case OverrideVariant::kParam_ShaderAlpha: PackValue(value, value->key, value->index, &material->alpha); break; + case OverrideVariant::kParam_ShaderGlossiness: PackValue(value, value->key, value->index, &material->glossiness); break; + case OverrideVariant::kParam_ShaderSpecularStrength: PackValue(value, value->key, value->index, &material->specularStrength); break; + case OverrideVariant::kParam_ShaderLightingEffect1: PackValue(value, value->key, value->index, &material->lightingEffect1); break; + case OverrideVariant::kParam_ShaderLightingEffect2: PackValue(value, value->key, value->index, &material->lightingEffect2); break; + + // Special cases + case OverrideVariant::kParam_ShaderTexture: + { + if(value->index < BSTextureSet::kNumTextures) + { + BSFixedString texture = material->textureSet->GetTexturePath(value->index); + PackValue(value, value->key, value->index, &texture); + } + } + break; + case OverrideVariant::kParam_ShaderTextureSet: + { + PackValue(value, value->key, value->index, NULL); + } + break; + case OverrideVariant::kParam_ShaderTintColor: + { + if(material->GetShaderType() == BSShaderMaterial::kShaderType_FaceGenRGBTint || material->GetShaderType() == BSShaderMaterial::kShaderType_HairTint) { + BSLightingShaderMaterialFacegenTint * tintedMaterial = (BSLightingShaderMaterialFacegenTint *)material; + PackValue(value, value->key, value->index, &tintedMaterial->tintColor); + } + } + break; + default: + _MESSAGE("Unknown lighting shader key %d %s", value->key, node->m_name); + shaderError = true; + break; + } +#ifdef _DEBUG + _MESSAGE("Applied LightingShader property %d %X to %s", value->key, value->data.u, node->m_name); +#endif + } + } else { + _MESSAGE("%s - Failed to cast %s to geometry", __FUNCTION__, node->m_name); + shaderError = true; + } + + if(shaderError) { + UInt32 def = 0; + PackValue(value, value->key, -1, &def); + } +} + +NIOVTaskUpdateTexture::NIOVTaskUpdateTexture(BSGeometry * geometry, UInt32 index, BSFixedString texture) +{ + m_geometry = geometry; + if (m_geometry) + m_geometry->IncRef(); + + m_index = index; + m_texture = texture; +} + +void NIOVTaskUpdateTexture::Run() +{ + if(m_geometry) + { + BSShaderProperty * shaderProperty = niptr_cast(m_geometry->m_spEffectState); + if(!shaderProperty) { + _MESSAGE("Shader does not exist for %s", m_geometry->m_name); + return; + } + + BSLightingShaderProperty * lightingShader = ni_cast(shaderProperty, BSLightingShaderProperty); + if(lightingShader) + { + BSLightingShaderMaterial * material = (BSLightingShaderMaterial *)shaderProperty->material; + if(m_index < BSTextureSet::kNumTextures) { + BSShaderTextureSet * newTextureSet = BSShaderTextureSet::Create(); + for(UInt32 i = 0; i < BSTextureSet::kNumTextures; i++) + { + const char * texturePath = material->textureSet->GetTexturePath(i); + newTextureSet->SetTexturePath(i, texturePath); + } + newTextureSet->SetTexturePath(m_index, m_texture.data); + material->ReleaseTextures(); + material->SetTextureSet(newTextureSet); + CALL_MEMBER_FN(lightingShader, InvalidateTextures)(0); + CALL_MEMBER_FN(lightingShader, InitializeShader)(m_geometry); + } + } + } +} + +void NIOVTaskUpdateTexture::Dispose() +{ + if (m_geometry) + m_geometry->DecRef(); + delete this; +} + +void SetShaderProperty(NiAVObject * node, OverrideVariant * value, bool immediate) +{ + BSGeometry * geometry = node->GetAsBSGeometry(); + if(geometry) + { + BSShaderProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if(!shaderProperty) { + _MESSAGE("Shader does not exist for %s", geometry->m_name); + return; + } + + if(value->key >= OverrideVariant::kParam_ControllersStart && value->key <= OverrideVariant::kParam_ControllersEnd) + { + SInt8 currentIndex = 0; + SInt8 controllerIndex = value->index; + if(controllerIndex != -1) + { + NiTimeController * foundController = NULL; + NiTimeController * controller = ni_cast(shaderProperty->m_controller, NiTimeController); + while(controller) + { + if(currentIndex == controllerIndex) { + foundController = controller; + break; + } + + controller = ni_cast(controller->next, NiTimeController); + currentIndex++; + } + + if(foundController) + { + switch(value->key) + { + case OverrideVariant::kParam_ControllerFrequency: UnpackValue(&foundController->m_fFrequency, value); return; break; + case OverrideVariant::kParam_ControllerPhase: UnpackValue(&foundController->m_fPhase, value); return; break; + case OverrideVariant::kParam_ControllerStartTime: UnpackValue(&foundController->m_fLoKeyTime, value); return; break; + case OverrideVariant::kParam_ControllerStopTime: UnpackValue(&foundController->m_fHiKeyTime, value); return; break; + + // Special cases + case OverrideVariant::kParam_ControllerStartStop: + { + float fValue; + UnpackValue(&fValue, value); + if(fValue < 0.0) + { + foundController->Start(0); + foundController->Stop(); + } + else { + foundController->Start(fValue); + } + return; + } + break; + default: + _MESSAGE("Unknown controller key %d %s", value->key, node->m_name); + return; + break; + } + } + } + + return; // Only working on controller properties + } + + BSEffectShaderProperty * effectShader = ni_cast(shaderProperty, BSEffectShaderProperty); + if(effectShader) + { + BSEffectShaderMaterial * material = (BSEffectShaderMaterial*)shaderProperty->material; + switch(value->key) + { + case OverrideVariant::kParam_ShaderEmissiveColor: UnpackValue(&material->emissiveColor, value); return; break; + case OverrideVariant::kParam_ShaderEmissiveMultiple: UnpackValue(&material->emissiveMultiple, value); return; break; + default: + _MESSAGE("Unknown shader key %d %s", value->key, node->m_name); + return; + break; + } +#ifdef _DEBUG + _MESSAGE("Applied EffectShader property %d %X to %s", value->key, value->data.u, node->m_name); +#endif + } + + BSLightingShaderProperty * lightingShader = ni_cast(shaderProperty, BSLightingShaderProperty); + if(lightingShader) + { + BSLightingShaderMaterial * material = (BSLightingShaderMaterial *)lightingShader->material; + switch(value->key) + { + case OverrideVariant::kParam_ShaderEmissiveColor: UnpackValue(lightingShader->emissiveColor, value); return; break; + case OverrideVariant::kParam_ShaderEmissiveMultiple: UnpackValue(&lightingShader->emissiveMultiple, value); return; break; + case OverrideVariant::kParam_ShaderAlpha: UnpackValue(&material->alpha, value); return; break; + case OverrideVariant::kParam_ShaderGlossiness: UnpackValue(&material->glossiness, value); return; break; + case OverrideVariant::kParam_ShaderSpecularStrength: UnpackValue(&material->specularStrength, value); return; break; + case OverrideVariant::kParam_ShaderLightingEffect1: UnpackValue(&material->lightingEffect1, value); return; break; + case OverrideVariant::kParam_ShaderLightingEffect2: UnpackValue(&material->lightingEffect2, value); return; break; + + // Special cases + case OverrideVariant::kParam_ShaderTexture: + { + BSFixedString texture; + UnpackValue(&texture, value); + + if(immediate) + { + if(value->index >= 0 && value->index < BSTextureSet::kNumTextures) { + BSShaderTextureSet * newTextureSet = BSShaderTextureSet::Create(); + for(UInt32 i = 0; i < BSTextureSet::kNumTextures; i++) + { + const char * texturePath = material->textureSet->GetTexturePath(i); + newTextureSet->SetTexturePath(i, texturePath); + } + newTextureSet->SetTexturePath(value->index, texture.data); + material->ReleaseTextures(); + material->SetTextureSet(newTextureSet); + CALL_MEMBER_FN(lightingShader, InvalidateTextures)(0); + CALL_MEMBER_FN(lightingShader, InitializeShader)(geometry); + } + } else { + g_task->AddTask(new NIOVTaskUpdateTexture(geometry, value->index, texture)); + } + return; + } + break; + case OverrideVariant::kParam_ShaderTextureSet: + { + BGSTextureSet * textureSet = NULL; + UnpackValue(&textureSet, value); + if(textureSet) + { + if(immediate) + { + BSShaderTextureSet * newTextureSet = BSShaderTextureSet::Create(); + for(UInt32 i = 0; i < BSTextureSet::kNumTextures; i++) + { + const char * texturePath = textureSet->textureSet.GetTexturePath(i); + newTextureSet->SetTexturePath(i, texturePath); + } + material->ReleaseTextures(); + material->SetTextureSet(newTextureSet); + CALL_MEMBER_FN(lightingShader, InvalidateTextures)(0); + CALL_MEMBER_FN(lightingShader, InitializeShader)(geometry); + } + else + CALL_MEMBER_FN(BSTaskPool::GetSingleton(), SetNiGeometryTexture)(geometry, textureSet); + } + return; + } + break; + case OverrideVariant::kParam_ShaderTintColor: + { + // Convert the shaderType to support tints + if(material->GetShaderType() != BSShaderMaterial::kShaderType_FaceGenRGBTint && material->GetShaderType() != BSShaderMaterial::kShaderType_HairTint)//if(CALL_MEMBER_FN(lightingShader, HasFlags)(0x0A)) + { + BSLightingShaderMaterialFacegenTint * tintedMaterial = (BSLightingShaderMaterialFacegenTint *)CreateShaderMaterial(BSShaderMaterial::kShaderType_HairTint); + CALL_MEMBER_FN(tintedMaterial, CopyFrom)(material); + CALL_MEMBER_FN(lightingShader, SetFlags)(0x0A, false); + CALL_MEMBER_FN(lightingShader, SetFlags)(0x15, true); + CALL_MEMBER_FN(lightingShader, SetMaterial)(tintedMaterial, 1); + CALL_MEMBER_FN(lightingShader, InitializeShader)(geometry); + } + + material = (BSLightingShaderMaterial *)shaderProperty->material; + if(material->GetShaderType() == BSShaderMaterial::kShaderType_FaceGenRGBTint || material->GetShaderType() == BSShaderMaterial::kShaderType_HairTint) { + BSLightingShaderMaterialFacegenTint * tintedMaterial = (BSLightingShaderMaterialFacegenTint *)material; + UnpackValue(&tintedMaterial->tintColor, value); + } + return; + } + break; + default: + _ERROR("Unknown lighting shader key %d %s", value->key, node->m_name); + return; + break; + } +#ifdef _DEBUG + _DMESSAGE("Applied LightingShader property %d %X to %s", value->key, value->data.u, node->m_name); +#endif + } + } else { + _ERROR("Failed to cast %s to geometry", node->m_name); + } +} + +class MatchBySlot : public FormMatcher +{ + UInt32 m_mask; +public: + MatchBySlot(UInt32 slot) : + m_mask(slot) + { + + } + + bool Matches(TESForm* pForm) const { + if (pForm) { + BGSBipedObjectForm* pBip = DYNAMIC_CAST(pForm, TESForm, BGSBipedObjectForm); + if (pBip) { + return (pBip->data.parts & m_mask) != 0; + } + } + return false; + } +}; + +TESForm* GetSkinForm(Actor* thisActor, UInt32 mask) +{ + TESForm * equipped = GetWornForm(thisActor, mask); // Check equipped item + if(!equipped) { + TESNPC * actorBase = DYNAMIC_CAST(thisActor->baseForm, TESForm, TESNPC); + if(actorBase) { + equipped = actorBase->skinForm.skin; // Check ActorBase + } + TESRace * race = actorBase->race.race; + if(!equipped && race) { + equipped = race->skin.skin; // Check Race + } + } + + return equipped; +} + +TESForm* GetWornForm(Actor* thisActor, UInt32 mask) +{ + MatchBySlot matcher(mask); + ExtraContainerChanges* pContainerChanges = static_cast(thisActor->extraData.GetByType(kExtraData_ContainerChanges)); + if (pContainerChanges) { + EquipData eqD = pContainerChanges->FindEquipped(matcher); + return eqD.pForm; + } + return NULL; +} + +BSGeometry * GetFirstShaderType(NiAVObject * object, UInt32 shaderType) +{ + NiNode * node = object->GetAsNiNode(); + if(node) + { + for(UInt32 i = 0; i < node->m_children.m_emptyRunStart; i++) + { + NiAVObject * object = node->m_children.m_data[i]; + if(object) { + BSGeometry * skin = GetFirstShaderType(object, shaderType); + if(skin) + return skin; + } + } + } + else + { + BSGeometry * geometry = object->GetAsBSGeometry(); + if(geometry) + { + BSShaderProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if(shaderProperty && ni_is_type(shaderProperty->GetRTTI(), BSLightingShaderProperty)) + { + // Find first geometry if the type is any + if(shaderType == 0xFFFF) + return geometry; + + BSLightingShaderMaterial * material = (BSLightingShaderMaterial *)shaderProperty->material; + if(material && material->GetShaderType() == shaderType) + { + return geometry; + } + } + } + } + + return NULL; +} + +void VisitGeometry(NiAVObject * parent, GeometryVisitor * visitor) +{ + NiNode * node = parent->GetAsNiNode(); + if(node) + { + for(UInt32 i = 0; i < node->m_children.m_emptyRunStart; i++) + { + NiAVObject * object = node->m_children.m_data[i]; + if(object) { + VisitGeometry(object, visitor); + } + } + } + else + { + BSGeometry * geometry = parent->GetAsBSGeometry(); + if(geometry) + visitor->Accept(geometry); + } +} + +bool NiExtraDataFinder::Accept(NiAVObject * object) +{ + m_data = object->GetExtraData(m_name.data); + if(m_data) + return true; + + return false; +}; + +NiExtraData * FindExtraData(NiAVObject * object, BSFixedString name) +{ + if (!object) + return NULL; + + NiExtraData * extraData = NULL; + VisitObjects(object, [&](NiAVObject*object) + { + extraData = object->GetExtraData(name.data); + if (extraData) + return true; + + return false; + }); + + return extraData; +} + +void VisitArmorAddon(Actor * actor, TESObjectARMO * armor, TESObjectARMA * arma, std::function functor) +{ + char addonString[MAX_PATH]; + memset(addonString, 0, MAX_PATH); + arma->GetNodeName(addonString, actor, armor, -1); + + BSFixedString rootName("NPC Root [Root]"); + + NiNode * skeletonRoot[2]; + skeletonRoot[0] = actor->GetNiRootNode(0); + skeletonRoot[1] = actor->GetNiRootNode(1); + + // Skip second skeleton, it's the same as the first + if (skeletonRoot[1] == skeletonRoot[0]) + skeletonRoot[1] = NULL; + + for (UInt32 i = 0; i <= 1; i++) + { + if (skeletonRoot[i]) + { + NiAVObject * root = skeletonRoot[i]->GetObjectByName(&rootName.data); + if (root) + { + NiNode * rootNode = root->GetAsNiNode(); + if (rootNode) + { + BSFixedString addonName(addonString); // Find the Armor name from the root + NiAVObject * armorNode = skeletonRoot[i]->GetObjectByName(&addonName.data); + if (functor && armorNode) + functor(i == 1, rootNode, armorNode); + } +#ifdef _DEBUG + else { + _DMESSAGE("%s - Failed to locate addon node for Armor: %08X on Actor: %08X", __FUNCTION__, armor->formID, actor->formID); + } +#endif + } + } + } +} + +bool ResolveAnyHandle(SKSESerializationInterface * intfc, UInt64 handle, UInt64 * newHandle) +{ + if (((handle & 0xFF000000) >> 24) != 0xFF) { + // Skip if handle is no longer valid. + if (!intfc->ResolveHandle(handle, newHandle)) { + return false; + } + } + else { + TESForm * formCheck = LookupFormByID(handle & 0xFFFFFFFF); + if (!formCheck) { + return false; + } + TESObjectREFR * refr = DYNAMIC_CAST(formCheck, TESForm, TESObjectREFR); + if (!refr || (refr && (refr->flags & TESForm::kFlagIsDeleted) == TESForm::kFlagIsDeleted)) { + return false; + } + *newHandle = handle; + } + + return true; +} + +#ifdef _DEBUG +void DumpNodeChildren(NiAVObject * node) +{ + _MESSAGE("{%s} {%s} {%X}", node->GetRTTI()->name, node->m_name, node); + if (node->m_extraDataLen > 0) { + gLog.Indent(); + for (UInt16 i = 0; i < node->m_extraDataLen; i++) { + _MESSAGE("{%s} {%s} {%X}", node->m_extraData[i]->GetRTTI()->name, node->m_extraData[i]->m_pcName, node); + } + gLog.Outdent(); + } + + NiNode * niNode = node->GetAsNiNode(); + if (niNode && niNode->m_children.m_emptyRunStart > 0) + { + gLog.Indent(); + for (int i = 0; i < niNode->m_children.m_emptyRunStart; i++) + { + NiAVObject * object = niNode->m_children.m_data[i]; + if (object) { + NiNode * childNode = object->GetAsNiNode(); + NiGeometry * geometry = object->GetAsNiGeometry(); + if (geometry) { + NiGeometryData * geometryData = niptr_cast(geometry->m_spModelData); + if (geometryData) + _MESSAGE("{%s} {%s} {%X} {%s} {%X}", object->GetRTTI()->name, object->m_name, object, geometryData->GetRTTI()->name, geometryData); + else + _MESSAGE("{%s} {%s} {%X}", object->GetRTTI()->name, object->m_name, object); + } + else if (childNode) { + DumpNodeChildren(childNode); + } + else { + _MESSAGE("{%s} {%s} {%X}", object->GetRTTI()->name, object->m_name, object); + } + } + } + gLog.Outdent(); + } +} +#endif \ No newline at end of file diff --git a/skee/ShaderUtilities.h b/skee/ShaderUtilities.h new file mode 100644 index 0000000..0db5742 --- /dev/null +++ b/skee/ShaderUtilities.h @@ -0,0 +1,142 @@ +#pragma once + +#include "skse64/GameTypes.h" +#include "skse64/GameThreads.h" +#include "skse64/GameObjects.h" + +#include "skse64/NiNodes.h" +#include "skse64/NiTypes.h" + +#include + +class NiExtraData; +class BSGeometry; +class OverrideVariant; + +struct SKSESerializationInterface; + +class NIOVTaskUpdateTexture : public TaskDelegate +{ +public: + NIOVTaskUpdateTexture(BSGeometry * geometry, UInt32 index, BSFixedString texture); + + virtual void Run(); + virtual void Dispose(); + + BSGeometry * m_geometry; + UInt32 m_index; + BSFixedString m_texture; +}; + +class NIOVTaskUpdateWorldData : public TaskDelegate +{ +public: + NIOVTaskUpdateWorldData(NiAVObject * object) + { + object->IncRef(); + m_object = object; + } + + virtual void Run() + { + NiAVObject::ControllerUpdateContext ctx; + ctx.flags = 0; + ctx.delta = 0; + //m_object->UpdateWorldData(&ctx); + CALL_MEMBER_FN(m_object, UpdateNode)(&ctx); + } + virtual void Dispose() + { + m_object->DecRef(); + delete this; + } + + NiAVObject * m_object; +}; + + +class NIOVTaskMoveNode : public TaskDelegate +{ +public: + NIOVTaskMoveNode(NiNode * destination, NiAVObject * object) + { + object->IncRef(); + m_object = object; + m_destination = destination; + } + + virtual void Run() + { + NiNode * currentParent = m_object->m_parent; + if (currentParent) + currentParent->RemoveChild(m_object); + if (m_destination) + m_destination->AttachChild(m_object, true); + } + virtual void Dispose() + { + m_object->DecRef(); + delete this; + } + + NiAVObject * m_object; + NiNode * m_destination; +}; + +void GetShaderProperty(NiAVObject * node, OverrideVariant * value); +void SetShaderProperty(NiAVObject * node, OverrideVariant * value, bool immediate); + +TESForm* GetWornForm(Actor* thisActor, UInt32 mask); +TESForm* GetSkinForm(Actor* thisActor, UInt32 mask); +BSGeometry * GetFirstShaderType(NiAVObject * object, UInt32 shaderType); + +class GeometryVisitor +{ +public: + virtual bool Accept(BSGeometry * geometry) = 0; +}; + +void VisitGeometry(NiAVObject * object, GeometryVisitor * visitor); + +class NiAVObjectVisitor +{ +public: + virtual bool Accept(NiAVObject * object) = 0; +}; + +class NiExtraDataFinder : public NiAVObjectVisitor +{ +public: + NiExtraDataFinder::NiExtraDataFinder(BSFixedString name) : m_name(name), m_data(NULL) { } + + virtual bool Accept(NiAVObject * object); + + NiExtraData * m_data; + BSFixedString m_name; +}; + +void VisitArmorAddon(Actor * actor, TESObjectARMO * armor, TESObjectARMA * arma, std::function functor); +NiExtraData * FindExtraData(NiAVObject * object, BSFixedString name); + +bool ResolveAnyHandle(SKSESerializationInterface * intfc, UInt64 handle, UInt64 * newHandle); + +class NiAutoRefCounter +{ +public: + NiAutoRefCounter(NiObject * object) + { + m_object = object; + m_object->IncRef(); + } + ~NiAutoRefCounter() + { + m_object->DecRef(); + } + +private: + NiObject * m_object; +}; + +#ifdef _DEBUG +void DumpNodeChildren(NiAVObject * node); +#endif \ No newline at end of file diff --git a/skee/SkeletonExtender.cpp b/skee/SkeletonExtender.cpp new file mode 100644 index 0000000..33e8f9f --- /dev/null +++ b/skee/SkeletonExtender.cpp @@ -0,0 +1,296 @@ +#include "SkeletonExtender.h" +#include "ShaderUtilities.h" +#include "NifUtils.h" + +#include "skse64/NiNodes.h" +#include "skse64/NiObjects.h" +#include "skse64/NiExtraData.h" +#include "skse64/NiSerialization.h" + +#include "skse64/GameReferences.h" +#include "skse64/GameStreams.h" + +void SkeletonExtender::Attach(TESObjectREFR * refr, NiNode * skeleton, NiAVObject * objectRoot) +{ + NiStringsExtraData * extraData = ni_cast(FindExtraData(objectRoot, "EXTN"), NiStringsExtraData); + if(extraData) + { + if(extraData->m_size % 3 != 0) { + #ifdef _DEBUG + _ERROR("%s - Error attaching additional skeleton info to %08X invalid entry count, must be divisible by 3.", __FUNCTION__, refr->formID); + #endif + return; + } + + for(UInt32 i = 0; i < extraData->m_size; i += 3) + { + BSFixedString targetNodeName = extraData->m_data[i]; + BSFixedString sourceNodeName = extraData->m_data[i+1]; + BSFixedString templatePath = extraData->m_data[i+2]; + + NiAVObject * targetNode = skeleton->GetObjectByName(&targetNodeName.data); + if(targetNode) { + NiAVObject * sourceNode = targetNode->GetObjectByName(&sourceNodeName.data); + if(!sourceNode) { // Make sure the source node doesn't exist + NiNode * targetNiNode = targetNode->GetAsNiNode(); + if(targetNiNode) { + if(!LoadTemplate(targetNiNode, templatePath.data)) + _ERROR("%s - Error attaching additional skeleton info to %08X failed to load target path %s onto %s.", __FUNCTION__, refr->formID, templatePath.data, targetNodeName.data); + } + } + } else { + #ifdef _DEBUG + _ERROR("%s - Error attaching additional skeleton info to %08X target node %s does not exist.", __FUNCTION__, refr->formID, targetNodeName.data); + #endif + } + } + } +} + +NiNode * SkeletonExtender::LoadTemplate(NiNode * parent, const char * path) +{ + NiNode * rootNode = NULL; + UInt8 niStreamMemory[0x5B4]; + memset(niStreamMemory, 0, 0x5B4); + NiStream * niStream = (NiStream *)niStreamMemory; + CALL_MEMBER_FN(niStream, ctor)(); + + BSResourceNiBinaryStream binaryStream(path); + if(!binaryStream.IsValid()) { + goto destroy_stream; + } + + niStream->LoadStream(&binaryStream); + if(niStream->m_rootObjects.m_data) + { + if(niStream->m_rootObjects.m_data[0]) // Get the root node + rootNode = niStream->m_rootObjects.m_data[0]->GetAsNiNode(); + if(rootNode) + parent->AttachChild(rootNode, false); + } + +destroy_stream: + CALL_MEMBER_FN(niStream, dtor)(); + return rootNode; +} + +#include "json\json.h" +#include +#include +#include +#include "NiTransformInterface.h" +#include "skse64/GameRTTI.h" + +extern NiTransformInterface g_transformInterface; + +void SkeletonExtender::AddTransforms(TESObjectREFR * refr, bool isFirstPerson, NiNode * skeleton, NiAVObject * objectRoot) +{ + std::set current_nodes, previous_nodes, diffs, changes, update; + + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if (actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + NiStringsExtraData * globalData = ni_cast(FindExtraData(skeleton, "BNDT"), NiStringsExtraData); + if (globalData) + { + for (int i = 0; i < globalData->m_size; i++) + { + BSFixedString node(globalData->m_data[i]); + previous_nodes.insert(node); + } + } + + VisitObjects(skeleton, [&](NiAVObject*object) + { + NiStringExtraData * stringData = ni_cast(object->GetExtraData("SDTA"), NiStringExtraData); + if (stringData) + { + try + { + Json::Features features; + features.all(); + + Json::Value root; + Json::Reader reader(features); + + bool parseSuccess = reader.parse(stringData->m_pString, root); + if (parseSuccess) + { + for (auto & objects : root) + { + BSFixedString node = objects["name"].asCString(); + current_nodes.insert(node); + } + } + } + catch (...) + { + _ERROR("%s - Error - Failed to parse skeleton transform data", __FUNCTION__); + } + } + + return false; + }); + + // Differences here means we lost nodes + std::set_symmetric_difference(current_nodes.begin(), current_nodes.end(), + previous_nodes.begin(), previous_nodes.end(), + std::inserter(diffs, diffs.begin())); + + for (auto & node : diffs) + { + g_transformInterface.RemoveNodeTransform(refr, isFirstPerson, gender == 1, node, "internal"); + } + + diffs.clear(); + + NiStringExtraData * stringData = ni_cast(FindExtraData(objectRoot, "SDTA"), NiStringExtraData); + ReadTransforms(refr, stringData, isFirstPerson, gender == 1, current_nodes, changes); + + std::set_symmetric_difference(current_nodes.begin(), current_nodes.end(), + previous_nodes.begin(), previous_nodes.end(), + std::inserter(diffs, diffs.begin())); + + std::set_union(diffs.begin(), diffs.end(), + changes.begin(), changes.end(), + std::inserter(update, update.begin())); + + for (auto & node : update) + { + g_transformInterface.UpdateNodeTransforms(refr, isFirstPerson, gender == 1, node); + } + + std::vector newNodes; + for (auto & node : current_nodes) + { + newNodes.push_back(node); + } + + // Was already there, set the new current nodes + if (globalData) + { + if (newNodes.size() == 0) + { + globalData->SetData(nullptr, 0); + } + else + { + globalData->SetData(&newNodes.at(0), newNodes.size()); + } + + } + + // No previous nodes, and we have new nodes + if (!globalData && current_nodes.size() > 0) + { + NiStringsExtraData * strData = NiStringsExtraData::Create("BNDT", &newNodes.at(0), newNodes.size()); + skeleton->AddExtraData(strData); + } +} + +void SkeletonExtender::ReadTransforms(TESObjectREFR * refr, NiStringExtraData * stringData, bool isFirstPerson, bool isFemale, std::set & nodes, std::set & changes) +{ + if (stringData) + { + try + { + Json::Features features; + features.all(); + + Json::Value root; + Json::Reader reader(features); + + bool parseSuccess = reader.parse(stringData->m_pString, root); + if (parseSuccess) + { + + bool changed = false; + for (auto & objects : root) + { + BSFixedString node = objects["name"].asCString(); + nodes.insert(node); + + Json::Value pos = objects["pos"]; + if (pos.isArray() && pos.size() == 3) + { + float position[3]; + position[0] = pos[0].asFloat(); + position[1] = pos[1].asFloat(); + position[2] = pos[2].asFloat(); + + OverrideVariant posV[3]; + float oldPosition[3]; + for (UInt32 i = 0; i < 3; i++) + { + OverrideVariant posOld = g_transformInterface.GetOverrideNodeValue(refr, isFirstPerson, isFemale, node, "internal", OverrideVariant::kParam_NodeTransformPosition, i); + UnpackValue(&oldPosition[i], &posOld); + + if (position[i] != oldPosition[i]) + changed = true; + } + + for (UInt32 i = 0; i < 3; i++) + { + PackValue(&posV[i], OverrideVariant::kParam_NodeTransformPosition, i, &position[i]); + g_transformInterface.AddNodeTransform(refr, isFirstPerson, isFemale, node, "internal", posV[i]); + } + } + + Json::Value rot = objects["rot"]; + if (pos.isArray() && pos.size() == 3) + { + NiMatrix33 rotation; + float rotationEuler[3]; + rotationEuler[0] = rot[0].asFloat() * MATH_PI / 180; + rotationEuler[1] = rot[1].asFloat() * MATH_PI / 180; + rotationEuler[2] = rot[2].asFloat() * MATH_PI / 180; + rotation.SetEulerAngles(rotationEuler[0], rotationEuler[1], rotationEuler[2]); + + float oldRotation[9]; + for (UInt32 i = 0; i < 9; i++) + { + OverrideVariant potOld = g_transformInterface.GetOverrideNodeValue(refr, isFirstPerson, isFemale, node, "internal", OverrideVariant::kParam_NodeTransformRotation, i); + UnpackValue(&oldRotation[i], &potOld); + + if (rotation.arr[i] != oldRotation[i]) + changed = true; + } + + OverrideVariant rotV[9]; + for (UInt32 i = 0; i < 9; i++) { + PackValue(&rotV[i], OverrideVariant::kParam_NodeTransformRotation, i, &rotation.arr[i]); + g_transformInterface.AddNodeTransform(refr, isFirstPerson, isFemale, node, "internal", rotV[i]); + } + } + + Json::Value scale = objects["scale"]; + if (scale.isDouble() && scale.asFloat() > 0) + { + float oldScale = 0; + OverrideVariant scaleOld = g_transformInterface.GetOverrideNodeValue(refr, isFirstPerson, isFemale, node, "internal", OverrideVariant::kParam_NodeTransformScale, 0); + UnpackValue(&oldScale, &scaleOld); + + float fScale = scale.asFloat(); + OverrideVariant scaleV; + PackValue(&scaleV, OverrideVariant::kParam_NodeTransformScale, 0, &fScale); + g_transformInterface.AddNodeTransform(refr, isFirstPerson, isFemale, node, "internal", scaleV); + + if (fScale != oldScale) + changed = true; + } + + if (changed) + { + changes.insert(node); + } + } + } + } + catch (...) + { + _ERROR("%s - Error - Failed to parse skeleton transform data", __FUNCTION__); + } + } +} \ No newline at end of file diff --git a/skee/SkeletonExtender.h b/skee/SkeletonExtender.h new file mode 100644 index 0000000..a1bee6e --- /dev/null +++ b/skee/SkeletonExtender.h @@ -0,0 +1,19 @@ +#pragma once + +class TESObjectREFR; +class NiNode; +class NiAVObject; +class NiStringsExtraData; +class NiStringExtraData; + +#include +#include "skse64\GameTypes.h" + +class SkeletonExtender +{ +public: + static void Attach(TESObjectREFR * refr, NiNode * skeleton, NiAVObject * objectRoot); + static NiNode * LoadTemplate(NiNode * parent, const char * path); + static void AddTransforms(TESObjectREFR * refr, bool isFirstPerson, NiNode * skeleton, NiAVObject * objectRoot); + static void ReadTransforms(TESObjectREFR * refr, NiStringExtraData * stringData, bool isFirstPerson, bool isFemale, std::set & nodes, std::set & changes); +}; \ No newline at end of file diff --git a/skee/StringTable.cpp b/skee/StringTable.cpp new file mode 100644 index 0000000..a2c04d8 --- /dev/null +++ b/skee/StringTable.cpp @@ -0,0 +1,87 @@ +#include "StringTable.h" + +#include "skse64/PluginAPI.h" + + +void StringTable::Save(SKSESerializationInterface * intfc, UInt32 kVersion) +{ + intfc->OpenRecord('STTB', kVersion); + + UInt32 totalStrings = m_stringToId.size(); + intfc->WriteRecordData(&totalStrings, sizeof(totalStrings)); + + for (auto & str : m_stringToId) + { + UInt16 length = strlen(str.first.data); + UInt32 strId = str.second; + intfc->WriteRecordData(&length, sizeof(length)); + intfc->WriteRecordData(str.first.data, length); + intfc->WriteRecordData(&strId, sizeof(strId)); + } +} + +bool StringTable::Load(SKSESerializationInterface* intfc, UInt32 kVersion) +{ + bool error = false; + UInt32 totalStrings = 0; + + if (!intfc->ReadRecordData(&totalStrings, sizeof(totalStrings))) + { + _ERROR("%s - Error loading total strings from table", __FUNCTION__); + error = true; + return error; + } + + for (UInt32 i = 0; i < totalStrings; i++) + { + char * stringName = NULL; + UInt16 stringLength = 0; + if (!intfc->ReadRecordData(&stringLength, sizeof(stringLength))) + { + _ERROR("%s - Error loading string length", __FUNCTION__); + error = true; + return error; + } + + stringName = new char[stringLength + 1]; + + if (stringLength > 0 && !intfc->ReadRecordData(stringName, stringLength)) { + _ERROR("%s - Error loading string of length %d", __FUNCTION__, stringLength); + error = true; + return error; + } + + stringName[stringLength] = 0; + + BSFixedString str(stringName); + delete[] stringName; + + UInt32 stringId = 0; + if (!intfc->ReadRecordData(&stringId, sizeof(stringId))) + { + _ERROR("%s - Error loading string id", __FUNCTION__); + error = true; + return error; + } + + m_idToString.insert_or_assign(stringId, str); + } + + return error; +} + +void StringTable::StringToId(const BSFixedString & str, const UInt32 & id) +{ + m_stringToId.insert_or_assign(str, id); +} + +void StringTable::IdToString(const UInt32 & id, const BSFixedString & str) +{ + m_idToString.insert_or_assign(id, str); +} + +void StringTable::Clear() +{ + m_stringToId.clear(); + m_idToString.clear(); +} diff --git a/skee/StringTable.h b/skee/StringTable.h new file mode 100644 index 0000000..05df42d --- /dev/null +++ b/skee/StringTable.h @@ -0,0 +1,101 @@ +#pragma once + +#include "skse64/GameTypes.h" + +struct SKSESerializationInterface; + +class StringTable +{ +public: + enum + { + kSerializationVersion1 = 1, + kSerializationVersion2 = 2, + kSerializationVersion = kSerializationVersion2 + }; + + void Save(SKSESerializationInterface * intfc, UInt32 kVersion); + bool Load(SKSESerializationInterface* intfc, UInt32 kVersion); + + void Clear(); + + void StringToId(const BSFixedString & str, const UInt32 & id); + void IdToString(const UInt32 & id, const BSFixedString & str); + + template + BSFixedString ReadString(SKSESerializationInterface* intfc, UInt32 kVersion) + { + BSFixedString str(""); + if (kVersion >= kSerializationVersion2) + { + UInt32 stringId = 0; + if (!intfc->ReadRecordData(&stringId, sizeof(stringId))) + { + _ERROR("%s - Error loading string id", __FUNCTION__); + return BSFixedString(""); + } + + auto & it = m_idToString.find(stringId); + if (it != m_idToString.end()) + { + str = it->second; + } + else + { + _ERROR("%s - Error string lookup failure (%08X)", __FUNCTION__, stringId); + return BSFixedString(""); + } + } + else + { + char * stringName = NULL; + T stringLength = 0; + if (!intfc->ReadRecordData(&stringLength, sizeof(stringLength))) + { + _ERROR("%s - Error loading string length", __FUNCTION__); + return BSFixedString(""); + } + + stringName = new char[stringLength + 1]; + if (!intfc->ReadRecordData(stringName, stringLength)) { + _ERROR("%s - Error loading string of length %d", __FUNCTION__, stringLength); + return BSFixedString(""); + } + stringName[stringLength] = 0; + + str = stringName; + delete[] stringName; + } + return str; + } + + template + void WriteString(SKSESerializationInterface* intfc, const BSFixedString & str, UInt32 kVersion) + { + if (kVersion >= kSerializationVersion2) + { + auto & it = m_stringToId.find(str); + if (it != m_stringToId.end()) + { + UInt32 stringId = it->second; + intfc->WriteRecordData(&stringId, sizeof(stringId)); + } + else + { + _ERROR("%s - Error mapping string %s to id", __FUNCTION__, str.data); + UInt32 stringId = (std::numeric_limits::max)(); + intfc->WriteRecordData(&stringId, sizeof(stringId)); + } + } + else + { + T length = strlen(str.data); + intfc->WriteRecordData(&length, sizeof(length)); + intfc->WriteRecordData(str.data, length); + } + } + +private: + std::map m_stringToId; + std::map m_idToString; +}; \ No newline at end of file diff --git a/skee/TintMaskInterface.cpp b/skee/TintMaskInterface.cpp new file mode 100644 index 0000000..371e9f8 --- /dev/null +++ b/skee/TintMaskInterface.cpp @@ -0,0 +1,491 @@ +#include "TintMaskInterface.h" +#include "ShaderUtilities.h" +#include "NifUtils.h" +#include "FileUtils.h" + +#include "skse64/GameData.h" +#include "skse64/GameReferences.h" +#include "skse64/GameObjects.h" +#include "skse64/GameRTTI.h" + +#include "skse64/GameStreams.h" + +#include "skse64/NiGeometry.h" +#include "skse64/NiExtraData.h" +#include "skse64/NiRTTI.h" +#include "skse64/NiProperties.h" +#include "skse64/NiMaterial.h" +#include "skse64/NiRenderer.h" +#include "skse64/NiTextures.h" + +#include + +#include "tinyxml2.h" + +#include +#include + +extern TintMaskInterface g_tintMaskInterface; + +RelocAddr TESTexture_vtbl(0x015506C8); + +UInt32 TintMaskInterface::GetVersion() +{ + return kCurrentPluginVersion; +} + +void TintMaskInterface::CreateTintsFromData(tArray & masks, UInt32 size, const char ** textureData, SInt32 * colorData, float * alphaData, ColorMap & overrides) +{ + masks.Allocate(size); + for(UInt32 m = 0; m < masks.count; m++) { + TintMask * tintMask = new TintMask; + void* memory = Heap_Allocate(sizeof(TESTexture)); + memset(memory, 0, sizeof(TESTexture)); + ((uintptr_t*)memory)[0] = TESTexture_vtbl.GetUIntPtr(); + TESTexture* newTexture = (TESTexture*)memory; + newTexture->Init(); + newTexture->str = textureData[m]; + tintMask->texture = newTexture; + + UInt32 color = colorData[m]; + float alpha = alphaData[m]; + + auto & it = overrides.find(m); + if (it != overrides.end()) { + color = it->second & 0xFFFFFF; + alpha = (it->second >> 24) / 255.0; + } + + tintMask->color.red = (color >> 16) & 0xFF; + tintMask->color.green = (color >> 8) & 0xFF; + tintMask->color.blue = color & 0xFF; + tintMask->color.alpha = 0; + tintMask->tintType = TintMask::kMaskType_SkinTone; + tintMask->alpha = alpha; + + masks.entries[m] = tintMask; + } +} + +void TintMaskInterface::ReleaseTintsFromData(tArray & masks) +{ + // Cleanup tint array + for(UInt32 m = 0; m < masks.count; m++) { + TintMask * tintMask = NULL; + if (masks.GetNthItem(m, tintMask)) { + TESTexture* texture = tintMask->texture; + if (texture) + Heap_Free(texture); + delete tintMask; + } + } + Heap_Free(masks.entries); +} + +NIOVTaskDeferredMask::NIOVTaskDeferredMask(TESObjectREFR * refr, bool isFirstPerson, TESObjectARMO * armor, TESObjectARMA * addon, NiAVObject * object, std::function overrides) +{ + m_firstPerson = isFirstPerson; + m_formId = refr->formID; + m_armorId = armor->formID; + m_addonId = addon->formID; + m_object = object; + m_overrides = overrides; + m_object->IncRef(); +} + +void NIOVTaskDeferredMask::Dispose() +{ + m_object->DecRef(); +} + +void NIOVTaskDeferredMask::Run() +{ + TESForm * refrForm = LookupFormByID(m_formId); + TESForm * armorForm = LookupFormByID(m_armorId); + TESForm * addonForm = LookupFormByID(m_addonId); + if (refrForm && armorForm && addonForm) { + TESObjectREFR * refr = DYNAMIC_CAST(refrForm, TESForm, TESObjectREFR); + TESObjectARMO * armor = DYNAMIC_CAST(armorForm, TESForm, TESObjectARMO); + TESObjectARMA * addon = DYNAMIC_CAST(addonForm, TESForm, TESObjectARMA); + + if (refr && armor && addon) { + g_tintMaskInterface.ApplyMasks(refr, m_firstPerson, armor, addon, m_object, m_overrides); + } + } +} + +void TintMaskInterface::ApplyMasks(TESObjectREFR * refr, bool isFirstPerson, TESObjectARMO * armor, TESObjectARMA * addon, NiAVObject * rootNode, std::function overrides) +{ + MaskList maskList; + VisitObjects(rootNode, [&](NiAVObject* object) + { + ObjectMask mask; + if (mask.object = object->GetAsBSGeometry()) { + auto textureData = ni_cast(object->GetExtraData(BSFixedString("MASKT").data), NiStringsExtraData); + if (textureData) { + mask.layerCount = textureData->m_size; + mask.textureData = (const char**)textureData->m_data; + } + auto colorData = ni_cast(object->GetExtraData(BSFixedString("MASKC").data), NiIntegersExtraData); + if (colorData) { + mask.colorData = colorData->m_data; + if (mask.layerCount != colorData->m_size) + mask.layerCount = 0; + } + + auto alphaData = ni_cast(object->GetExtraData(BSFixedString("MASKA").data), NiFloatsExtraData); + if (alphaData) { + mask.alphaData = alphaData->m_data; + if (mask.layerCount != alphaData->m_size) + mask.layerCount = 0; + } + + auto resolutionWData = ni_cast(object->GetExtraData(BSFixedString("MASKR").data), NiIntegerExtraData); + if (resolutionWData) { + mask.resolutionWData = resolutionWData->m_data; + mask.resolutionHData = resolutionWData->m_data; + } + else { + auto resolutionWData = ni_cast(object->GetExtraData(BSFixedString("MASKW").data), NiIntegerExtraData); + if (resolutionWData) + mask.resolutionWData = resolutionWData->m_data; + + auto resolutionHData = ni_cast(object->GetExtraData(BSFixedString("MASKH").data), NiIntegerExtraData); + if (resolutionHData) + mask.resolutionHData = resolutionHData->m_data; + } + + + if (mask.object && mask.layerCount > 0) + maskList.push_back(mask); + } + + return false; + }); + + m_modelMap.ApplyLayers(refr, isFirstPerson, addon, rootNode, [&](BSGeometry* geom, MaskLayerTuple* mask) + { + ObjectMask obj; + obj.object = geom; + obj.resolutionWData = std::get<0>(*mask); + obj.resolutionHData = std::get<1>(*mask); + obj.layerCount = std::get<2>(*mask).size(); + obj.textureData = &std::get<2>(*mask)[0]; + obj.colorData = &std::get<3>(*mask)[0]; + obj.alphaData = &std::get<4>(*mask)[0]; + maskList.push_back(obj); + }); + + ColorMap overrideMap; + if (overrides && !maskList.empty()) { + overrides(&overrideMap); + } + + for (auto & mask : maskList) + { + if(mask.object && mask.textureData && mask.colorData && mask.alphaData) + { + BSShaderProperty * shaderProperty = niptr_cast(mask.object->m_spEffectState); + if(shaderProperty) { + shaderProperty->IncRef(); + BSLightingShaderProperty * lightingShader = ni_cast(shaderProperty, BSLightingShaderProperty); + if(lightingShader) { + BSLightingShaderMaterial * material = (BSLightingShaderMaterial *)lightingShader->material; + + NiTexture * newTarget = NULL; + if (m_maskMap.IsCaching()) + newTarget = m_maskMap.GetRenderTarget(lightingShader); + if (!newTarget) { + UInt32 width = 0; + UInt32 height = 0; + if (mask.resolutionWData) + width = mask.resolutionWData; + if (mask.resolutionHData) + height = mask.resolutionHData; + else + height = mask.resolutionWData; + + newTarget = CreateSourceTexture("TintMask"); + newTarget->rendererData = CALL_MEMBER_FN(g_renderManager, CreateRenderTexture)(width, height); + + if (newTarget && m_maskMap.IsCaching()) { + m_maskMap.AddRenderTargetGroup(lightingShader, newTarget); + } + } + if(newTarget) { + tArray tintMasks; + CreateTintsFromData(tintMasks, mask.layerCount, mask.textureData, mask.colorData, mask.alphaData, overrideMap); + + newTarget->IncRef(); + if (ApplyMasksToRenderTarget(&tintMasks, newTarget)) { + BSLightingShaderMaterialFacegen * tintedMaterial = static_cast(CreateShaderMaterial(BSLightingShaderMaterialFacegen::kShaderType_FaceGen)); + CALL_MEMBER_FN(tintedMaterial, CopyFrom)(material); + tintedMaterial->renderedTexture = newTarget; + CALL_MEMBER_FN(lightingShader, SetFlags)(0x0A, true); // Enable detailmap + CALL_MEMBER_FN(lightingShader, SetFlags)(0x15, false); // Disable FaceGen_RGB + //material->ReleaseTextures(); + CALL_MEMBER_FN(lightingShader, SetMaterial)(tintedMaterial, 1); // New material takes texture ownership + if (newTarget) // Let the material now take ownership since the old target is destroyed now + newTarget->DecRef(); + CALL_MEMBER_FN(lightingShader, InitializeShader)(mask.object); + } + + newTarget->DecRef(); + + ReleaseTintsFromData(tintMasks); + } + } + shaderProperty->DecRef(); + } + } + } +} + +void TintMaskMap::ManageRenderTargetGroups() +{ + SimpleLocker locker(&m_lock); + m_caching = true; +} + +NiTexture * TintMaskMap::GetRenderTarget(BSLightingShaderProperty* key) +{ + auto & it = m_data.find(key); + if (it != m_data.end()) + return it->second; + + return NULL; +} + +void TintMaskMap::AddRenderTargetGroup(BSLightingShaderProperty* key, NiTexture * value) +{ + SimpleLocker locker(&m_lock); + auto & it = m_data.emplace(key, value); + if (it.second == true) { + key->IncRef(); + value->IncRef(); + } +} + +void TintMaskMap::ReleaseRenderTargetGroups() +{ + SimpleLocker locker(&m_lock); + for (auto & it : m_data) { + it.first->DecRef(); + it.second->DecRef(); + } + m_data.clear(); + m_caching = false; +} + +MaskLayerTuple * MaskDiffuseMap::GetMaskLayers(BSFixedString texture) +{ + auto & it = find(texture.data); + if (it != end()) { + return &it->second; + } + + return NULL; +} + +MaskDiffuseMap * MaskTriShapeMap::GetDiffuseMap(BSFixedString triShape) +{ + auto & it = find(triShape.data); + if (it != end()) { + return &it->second; + } + + return NULL; +} + +MaskLayerTuple * MaskModelMap::GetMask(BSFixedString nif, BSFixedString trishape, BSFixedString diffuse) +{ + return &m_data[nif.data][trishape.data][diffuse.data]; +} + +MaskTriShapeMap * MaskModelMap::GetTriShapeMap(BSFixedString nifPath) +{ + auto & it = m_data.find(nifPath.data); + if (it != m_data.end()) { + return &it->second; + } + + return NULL; +} + +bool ApplyMaskData(MaskTriShapeMap * triShapeMap, NiAVObject * object, const char * nameOverride, std::function functor) +{ + if (BSGeometry * geometry = object->GetAsBSGeometry()) { + geometry->IncRef(); + auto textureMap = triShapeMap->GetDiffuseMap(nameOverride ? BSFixedString(nameOverride) : object->m_name); + if (textureMap) { + NiProperty * shaderProperty = niptr_cast(geometry->m_spEffectState); + if (shaderProperty) { + shaderProperty->IncRef(); + BSLightingShaderProperty * lightingShader = ni_cast(shaderProperty, BSLightingShaderProperty); + if (lightingShader) { + BSLightingShaderMaterial * material = (BSLightingShaderMaterial *)lightingShader->material; + if (material->textureSet) { + auto layerList = textureMap->GetMaskLayers(material->textureSet->GetTexturePath(0)); + if (layerList) { + functor(geometry, layerList); + } + } + } + shaderProperty->DecRef(); + } + + return true; + } + geometry->DecRef(); + } + + return false; +} + +void MaskModelMap::ApplyLayers(TESObjectREFR * refr, bool isFirstPerson, TESObjectARMA * arma, NiAVObject * node, std::function functor) +{ + UInt8 gender = 0; + TESNPC * actorBase = DYNAMIC_CAST(refr->baseForm, TESForm, TESNPC); + if (actorBase) + gender = CALL_MEMBER_FN(actorBase, GetSex)(); + + SimpleLocker locker(&m_lock); + + auto triShapeMap = GetTriShapeMap(arma->models[isFirstPerson == true ? 1 : 0][gender].GetModelName()); + if (!triShapeMap) { + triShapeMap = GetTriShapeMap(arma->models[isFirstPerson == true ? 1 : 0][gender].GetModelName()); + if (!triShapeMap) { + return; + } + } + + UInt32 count = 0; + VisitObjects(node, [&](NiAVObject* object) + { + if (ApplyMaskData(triShapeMap, object, NULL, functor)) + count++; + + return false; + }); + + if (count == 0) + ApplyMaskData(triShapeMap, node, "", functor); +} + +void TintMaskInterface::ReadTintData(LPCTSTR lpFolder, LPCTSTR lpFilePattern) +{ + TCHAR szFullPattern[MAX_PATH]; + WIN32_FIND_DATA FindFileData; + HANDLE hFindFile; + // first we are going to process any subdirectories + PathCombine(szFullPattern, lpFolder, "*"); + hFindFile = FindFirstFile(szFullPattern, &FindFileData); + if (hFindFile != INVALID_HANDLE_VALUE) + { + do + { + if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + // found a subdirectory; recurse into it + PathCombine(szFullPattern, lpFolder, FindFileData.cFileName); + if (FindFileData.cFileName[0] == '.') + continue; + ReadTintData(szFullPattern, lpFilePattern); + } + } while (FindNextFile(hFindFile, &FindFileData)); + FindClose(hFindFile); + } + // now we are going to look for the matching files + PathCombine(szFullPattern, lpFolder, lpFilePattern); + hFindFile = FindFirstFile(szFullPattern, &FindFileData); + if (hFindFile != INVALID_HANDLE_VALUE) + { + do + { + if (!(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + // found a file; do something with it + PathCombine(szFullPattern, lpFolder, FindFileData.cFileName); + ParseTintData(szFullPattern); + } + } while (FindNextFile(hFindFile, &FindFileData)); + FindClose(hFindFile); + } +} + +void TintMaskInterface::ParseTintData(LPCTSTR filePath) +{ + std::string path(filePath); + path.erase(0, 5); + + BSResourceNiBinaryStream bStream(path.c_str()); + std::string data; + BSReadAll(&bStream, &data); + + tinyxml2::XMLDocument tintDoc; + tintDoc.Parse(data.c_str(), data.size()); + + if (tintDoc.Error()) { + _ERROR("%s", tintDoc.GetErrorStr1()); + return; + } + + auto element = tintDoc.FirstChildElement("tintmasks"); + if (element) { + auto object = element->FirstChildElement("object"); + while (object) + { + auto objectPath = BSFixedString(object->Attribute("path")); + auto child = object->FirstChildElement("geometry"); + while (child) + { + auto trishapeName = child->Attribute("name"); + auto trishape = BSFixedString(trishapeName ? trishapeName : ""); + auto diffuse = BSFixedString(child->Attribute("diffuse")); + auto width = child->IntAttribute("width"); + auto height = child->IntAttribute("height"); + auto priority = child->IntAttribute("priority"); + + if (width && !height) + height = width; + if (height && !width) + width = height; + + auto tuple = m_modelMap.GetMask(objectPath, trishape, diffuse); + + // If the current entry is of higher priorty, skip the new entry + auto previousPriority = std::get<5>(*tuple); + if (previousPriority > 0 && priority < previousPriority) { + child = child->NextSiblingElement("geometry"); + continue; + } + + std::get<0>(*tuple) = width; + std::get<1>(*tuple) = height; + std::get<2>(*tuple).clear(); + std::get<3>(*tuple).clear(); + std::get<4>(*tuple).clear(); + std::get<5>(*tuple) = priority; + + auto mask = child->FirstChildElement("mask"); + while (mask) { + auto maskPath = BSFixedString(mask->Attribute("path")); + auto color = mask->Attribute("color"); + UInt32 colorValue; + sscanf_s(color, "%x", &colorValue); + auto alpha = mask->DoubleAttribute("alpha"); + + std::get<2>(*tuple).push_back(maskPath.data); + std::get<3>(*tuple).push_back(colorValue); + std::get<4>(*tuple).push_back(alpha); + + mask = mask->NextSiblingElement("mask"); + } + + child = child->NextSiblingElement("geometry"); + } + + object = object->NextSiblingElement("object"); + } + } +} \ No newline at end of file diff --git a/skee/TintMaskInterface.h b/skee/TintMaskInterface.h new file mode 100644 index 0000000..1e9c258 --- /dev/null +++ b/skee/TintMaskInterface.h @@ -0,0 +1,279 @@ +#pragma once + +#include "IPluginInterface.h" +#include "IHashType.h" +#include "ItemDataInterface.h" + +struct SKSESerializationInterface; + +class TESObjectREFR; +class TESObjectARMO; +class TESObjectARMA; +class NiAVObject; +class TintMask; + +class NiStringsExtraData; +class NiIntegersExtraData; +class NiFloatsExtraData; +class NiIntegerExtraData; + +class BSLightingShaderProperty; +class BSRenderTargetGroup; + +#include "skse64/NiMaterial.h" +#include "skse64/GameTypes.h" + +#include +#include + +/* +MAKE_NI_POINTER(NiRenderedTexture); +MAKE_NI_POINTER(BSRenderTargetGroup); + +class TintMaskShaderMaterial +{ +public: + TintMaskShaderMaterial::TintMaskShaderMaterial() + { + unk04 = 0; + unk08 = 0; + unk10 = 0; + unk14 = 0; + unk28 = -1; + unk18 = 1.0f; + unk1C = 1.0f; + unk20 = 1.0f; + unk24 = 1.0f; + unk30 = 1.0f; + unk34 = 1.0f; + unk38 = 1.0f; + diffuse = NULL; + normalMap = NULL; + heightMap = NULL; + specular = NULL; + unk4C = 3; + alpha = 1.0f; + unk58 = 0.0f; + glossiness = 1.0f; + specularStrength = 1.0f; + lightingEffect1 = 0.0f; + lightingEffect2 = 0.0f; + unk6C = 0; + renderedTexture = NULL; + unk74 = NULL; + unk78 = NULL; + + // Custom Param + renderTarget = NULL; + }; + virtual ~TintMaskShaderMaterial() + { + CALL_MEMBER_FN(this, dtor)(); + }; + virtual TintMaskShaderMaterial * Create(void) { return new TintMaskShaderMaterial; }; + virtual void Copy(TintMaskShaderMaterial * source) + { + CALL_MEMBER_FN(this, FnCopy)(source); + }; + + virtual bool Unk_03(void * unk1) { return CALL_MEMBER_FN(this, Fn03)(unk1); }; + virtual SInt32 Unk_04(void * unk1) { return CALL_MEMBER_FN(this, Fn04)(unk1); }; + virtual void * Unk_05(void) { return CALL_MEMBER_FN(this, Fn05)(); }; + virtual UInt32 GetShaderType(void) { return BSMaskedShaderMaterial::kShaderType_FaceTint; }; + virtual UInt32 Unk_07(void) { return 2; }; // Always seems to be 2 + virtual void SetTexture(UInt32 index, BSTextureSet * texture, SInt32 unk1) + { + CALL_MEMBER_FN(this, FnSetTexture)(index, texture, unk1); + }; + virtual void ReleaseTextures(void) + { + CALL_MEMBER_FN(this, FnReleaseTextures)(); + }; + virtual void Unk_0A(UInt8 unk1, UInt8 unk2, UInt8 unk3, UInt8 unk4, UInt8 unk5, UInt32 unk6, UInt32 unk7) { CALL_MEMBER_FN(this, Fn0A)(unk1, unk2, unk3, unk4, unk5, unk6, unk7); }; // AddRefs + virtual void Unk_0B(void * unk1, UInt32 unk2) { CALL_MEMBER_FN(this, Fn0B)(unk1, unk2); }; + virtual void * Unk_0C(void * unk1) { return CALL_MEMBER_FN(this, Fn0C)(unk1); }; + virtual void * Unk_0D(void * unk1) { return CALL_MEMBER_FN(this, Fn0D)(unk1); }; + + MEMBER_FN_PREFIX(TintMaskShaderMaterial); + DEFINE_MEMBER_FN(dtor, void, 0x00C987E0); + DEFINE_MEMBER_FN(FnCopy, void, 0x00C98900, TintMaskShaderMaterial * other); + DEFINE_MEMBER_FN(Fn03, bool, 0x00C96A30, void * unk1); + DEFINE_MEMBER_FN(Fn04, SInt32, 0x00C973C0, void * unk1); + DEFINE_MEMBER_FN(Fn05, void *, 0x00C96950); + DEFINE_MEMBER_FN(FnSetTexture, void *, 0x00C9A050, UInt32 index, BSTextureSet * texture, SInt32 unk1); + DEFINE_MEMBER_FN(FnReleaseTextures, void, 0x00C97460); + DEFINE_MEMBER_FN(Fn0A, void, 0x00C989C0, UInt8 unk1, UInt8 unk2, UInt8 unk3, UInt8 unk4, UInt8 unk5, UInt32 unk6, UInt32 unk7); + DEFINE_MEMBER_FN(Fn0B, void, 0x00C974E0, void * unk1, UInt32 unk2); + DEFINE_MEMBER_FN(Fn0C, void *, 0x00C99990, void * unk1); + DEFINE_MEMBER_FN(Fn0D, void *, 0x00C99A90, void * unk1); + + // redirect to formheap + static void * operator new(std::size_t size) + { + return FormHeap_Allocate(size); + } + + static void * operator new(std::size_t size, const std::nothrow_t &) + { + return FormHeap_Allocate(size); + } + + // placement new + static void * operator new(std::size_t size, void * ptr) + { + return ptr; + } + + static void operator delete(void * ptr) + { + FormHeap_Free(ptr); + } + + static void operator delete(void * ptr, const std::nothrow_t &) + { + FormHeap_Free(ptr); + } + + static void operator delete(void *, void *) + { + // placement delete + } + + UInt32 unk04; // 04 BSIntrusiveRefCounted? + UInt32 unk08; // 08 inited to 0 + UInt32 unk0C; // 0C inited to 0 + UInt32 unk10; // 10 inited to 0 + UInt32 unk14; // 14 inited to 0 + float unk18; // 18 inited to 1.0 + float unk1C; // 1C inited to 1.0 + float unk20; // 20 inited to 1.0 + float unk24; // 24 inited to 1.0 + SInt32 unk28; // 28 inited to -1 flags? + UInt32 unk2C; // 2C flags? + float unk30; + float unk34; + float unk38; + NiSourceTexture * diffuse; // 3C inited to 0 + NiSourceTexture * normalMap; // 40 inited to 0 + NiSourceTexture * heightMap; // 44 inited to 0 + NiSourceTexture * specular; // 48 inited to 0 + UInt32 unk4C; // 4C inited to 3 + BSTextureSetPtr textureSet; // 50 inited to 0 + float alpha; // 54 inited to 1.0 + float unk58; // 58 inited to 0 + float glossiness; // 5C inited to 1.0 + float specularStrength; // 60 inited to 1.0 + float lightingEffect1; // 64 inited to 0 + float lightingEffect2; // 68 inited to 0 + UInt32 unk6C; // 6C inited to 0 + NiRenderedTexturePtr renderedTexture; // 70 inited to 0 + NiSourceTexture * unk74; // 74 inited to 0 + NiSourceTexture * unk78; // 78 inited to 0 + + // Custom Param + BSRenderTargetGroupPtr renderTarget; // 7C +};*/ + +typedef std::unordered_map TintMaskCacheMap; + +class TintMaskMap : public SafeDataHolder +{ +public: + void ManageRenderTargetGroups(); + NiTexture * GetRenderTarget(BSLightingShaderProperty* key); + void AddRenderTargetGroup(BSLightingShaderProperty* key, NiTexture* value); + void ReleaseRenderTargetGroups(); + + bool IsCaching() const { return m_caching; } + +private: + bool m_caching; +}; + +typedef std::vector MaskTextureList; +typedef std::vector MaskColorList; +typedef std::vector MaskAlphaList; + +typedef std::tuple MaskLayerTuple; + +// maps diffuse names to layer data +class MaskDiffuseMap : public std::unordered_map +{ +public: + MaskLayerTuple * GetMaskLayers(BSFixedString texture); +}; + +// maps trishape names to diffuse names +class MaskTriShapeMap : public std::unordered_map +{ +public: + MaskDiffuseMap * GetDiffuseMap(BSFixedString triShape); +}; + + +typedef std::unordered_map MaskModelContainer; + +// Maps model names to trishape names +class MaskModelMap : public SafeDataHolder +{ +public: + MaskLayerTuple * GetMask(BSFixedString nif, BSFixedString trishape, BSFixedString diffuse); + + void ApplyLayers(TESObjectREFR * refr, bool isFirstPerson, TESObjectARMA * arma, NiAVObject * node, std::function functor); + MaskTriShapeMap * GetTriShapeMap(BSFixedString nifPath); +}; + +class TintMaskInterface : public IPluginInterface +{ +public: + enum + { + kCurrentPluginVersion = 0, + kSerializationVersion1 = 1, + kSerializationVersion = kSerializationVersion1 + }; + virtual UInt32 GetVersion(); + + struct ObjectMask + { + BSGeometry * object = NULL; + UInt32 layerCount = 0; + const char ** textureData = NULL; + SInt32 * colorData = NULL; + float * alphaData = NULL; + UInt32 resolutionWData = NULL; + UInt32 resolutionHData = NULL; + }; + typedef std::vector MaskList; + + virtual void ApplyMasks(TESObjectREFR * refr, bool isFirstPerson, TESObjectARMO * armor, TESObjectARMA * addon, NiAVObject * object, std::function overrides); + virtual void ManageTints() { m_maskMap.ManageRenderTargetGroups(); } + virtual void ReleaseTints() { m_maskMap.ReleaseRenderTargetGroups(); } + + void CreateTintsFromData(tArray & masks, UInt32 size, const char ** textureData, SInt32 * colorData, float * alphaData, ColorMap & overrides); + void ReleaseTintsFromData(tArray & masks); + + //void ReadTintData(); + void ReadTintData(LPCTSTR lpFolder, LPCTSTR lpFilePattern); + void ParseTintData(LPCTSTR filePath); + + MaskModelMap m_modelMap; + TintMaskMap m_maskMap; +}; + +class NIOVTaskDeferredMask : public TaskDelegate +{ +public: + NIOVTaskDeferredMask::NIOVTaskDeferredMask(TESObjectREFR * refr, bool isFirstPerson, TESObjectARMO * armor, TESObjectARMA * addon, NiAVObject * object, std::function overrides); + + virtual void Run(); + virtual void Dispose(); + +private: + bool m_firstPerson; + UInt32 m_formId; + UInt32 m_armorId; + UInt32 m_addonId; + NiAVObject * m_object; + std::function m_overrides; +}; \ No newline at end of file diff --git a/skee/exports.def b/skee/exports.def new file mode 100644 index 0000000..b337adb --- /dev/null +++ b/skee/exports.def @@ -0,0 +1,4 @@ +LIBRARY "skee64" +EXPORTS +SKSEPlugin_Query +SKSEPlugin_Load \ No newline at end of file diff --git a/skee/main.cpp b/skee/main.cpp new file mode 100644 index 0000000..7d64801 --- /dev/null +++ b/skee/main.cpp @@ -0,0 +1,865 @@ +#include "skse64/PluginAPI.h" +#include "skse64_common/skse_version.h" +#include "skse64_common/SafeWrite.h" + +#include "skse64/GameAPI.h" +#include "skse64/GameObjects.h" +#include "skse64/GameRTTI.h" +#include "skse64/GameData.h" +#include "skse64/GameEvents.h" +#include "skse64/GameExtraData.h" + +#include "skse64/PapyrusVM.h" + +#include "skse64/NiRTTI.h" +#include "skse64/NiNodes.h" +#include "skse64/NiMaterial.h" +#include "skse64/NiProperties.h" + +#include "skse64/ScaleformCallbacks.h" +#include "skse64/ScaleformMovie.h" + +#include "IPluginInterface.h" +#include "OverrideInterface.h" +#include "OverlayInterface.h" +#include "BodyMorphInterface.h" +#include "ItemDataInterface.h" +#include "TintMaskInterface.h" +#include "NiTransformInterface.h" + +#include "MorphHandler.h" +#include "PartHandler.h" + +#include "ShaderUtilities.h" +#include "ScaleformFunctions.h" +#include "ScaleformCharGenFunctions.h" +#include "StringTable.h" + +#include +#include +#include + +#include "PapyrusNiOverride.h" +#include "PapyrusCharGen.h" +#include "SKEEHooks.h" + +IDebugLog gLog; + +PluginHandle g_pluginHandle = kPluginHandle_Invalid; +const UInt32 kSerializationDataVersion = 1; + +// Interfaces +SKSESerializationInterface * g_serialization = nullptr; +SKSEScaleformInterface * g_scaleform = nullptr; +SKSETaskInterface * g_task = nullptr; +SKSEMessagingInterface * g_messaging = nullptr; +SKSEPapyrusInterface * g_papyrus = nullptr; + +// Handlers +IInterfaceMap g_interfaceMap; +DyeMap g_dyeMap; +OverrideInterface g_overrideInterface; +TintMaskInterface g_tintMaskInterface; +OverlayInterface g_overlayInterface; +BodyMorphInterface g_bodyMorphInterface; +ItemDataInterface g_itemDataInterface; +NiTransformInterface g_transformInterface; + +MorphHandler g_morphHandler; +PartSet g_partSet; + +StringTable g_stringTable; + +UInt32 g_numBodyOverlays = 3; +UInt32 g_numHandOverlays = 3; +UInt32 g_numFeetOverlays = 3; +UInt32 g_numFaceOverlays = 3; + +bool g_playerOnly = true; +UInt32 g_numSpellBodyOverlays = 1; +UInt32 g_numSpellHandOverlays = 1; +UInt32 g_numSpellFeetOverlays = 1; +UInt32 g_numSpellFaceOverlays = 1; + +bool g_alphaOverride = true; +UInt16 g_alphaFlags = 4845; +UInt16 g_alphaThreshold = 0; + +UInt16 g_loadMode = 0; +bool g_enableAutoTransforms = true; +bool g_enableBodyGen = true; +bool g_enableBodyInit = true; +bool g_firstLoad = false; +bool g_immediateArmor = true; +bool g_enableFaceOverlays = true; +bool g_immediateFace = false; +bool g_enableEquippableTransforms = true; +bool g_parallelMorphing = true; +UInt16 g_scaleMode = 0; +UInt16 g_bodyMorphMode = 0; + +// Chargen Start +#include "CDXBrush.h" + +bool g_externalHeads = false; +bool g_extendedMorphs = true; +bool g_allowAllMorphs = true; +bool g_disableFaceGenCache = true; +float g_sliderMultiplier = 1.0f; +float g_sliderInterval = 0.01f; +float g_panSpeed = 0.01f; +float g_cameraFOV = 45.0f; +UInt32 g_numPresets = 10; +UInt32 g_customDataMax = 10; +std::string g_raceTemplate = "NordRace"; + +#ifdef FIXME +extern double g_brushProperties[CDXBrush::kBrushTypes][CDXBrush::kBrushProperties][CDXBrush::kBrushPropertyValues]; +#endif + +#define MIN_SERIALIZATION_VERSION 1 +#define MIN_TASK_VERSION 1 +#define MIN_SCALEFORM_VERSION 1 +#define MIN_PAPYRUS_VERSION 1 + + +const std::string & F4EEGetRuntimeDirectory(void) +{ + static std::string s_runtimeDirectory; + + if (s_runtimeDirectory.empty()) + { + // can't determine how many bytes we'll need, hope it's not more than MAX_PATH + char runtimePathBuf[MAX_PATH]; + UInt32 runtimePathLength = GetModuleFileName(GetModuleHandle(NULL), runtimePathBuf, sizeof(runtimePathBuf)); + + if (runtimePathLength && (runtimePathLength < sizeof(runtimePathBuf))) + { + std::string runtimePath(runtimePathBuf, runtimePathLength); + + // truncate at last slash + std::string::size_type lastSlash = runtimePath.rfind('\\'); + if (lastSlash != std::string::npos) // if we don't find a slash something is VERY WRONG + { + s_runtimeDirectory = runtimePath.substr(0, lastSlash + 1); + + _DMESSAGE("runtime root = %s", s_runtimeDirectory.c_str()); + } + else + { + _WARNING("no slash in runtime path? (%s)", runtimePath.c_str()); + } + } + else + { + _WARNING("couldn't find runtime path (len = %d, err = %08X)", runtimePathLength, GetLastError()); + } + } + + return s_runtimeDirectory; +} + +const std::string & SKEE64GetConfigPath(bool custom = false) +{ + static std::string s_configPath; + static std::string s_configPathCustom; + + if (s_configPath.empty()) + { + std::string runtimePath = F4EEGetRuntimeDirectory(); + if (!runtimePath.empty()) + { + s_configPath = runtimePath + "Data\\SKSE\\Plugins\\skee64.ini"; + + _MESSAGE("default config path = %s", s_configPath.c_str()); + } + } + if (s_configPathCustom.empty()) + { + std::string runtimePath = F4EEGetRuntimeDirectory(); + if (!runtimePath.empty()) + { + s_configPathCustom = runtimePath + "Data\\SKSE\\Plugins\\skee64_custom.ini"; + + _MESSAGE("custom config path = %s", s_configPathCustom.c_str()); + } + } + + return custom ? s_configPathCustom : s_configPath; +} + +std::string SKEE64GetConfigOption(const char * section, const char * key) +{ + std::string result; + + const std::string & configPath = SKEE64GetConfigPath(); + const std::string & configPathCustom = SKEE64GetConfigPath(true); + + char resultBuf[256]; + resultBuf[0] = 0; + + if (!configPath.empty()) + { + UInt32 resultLen = GetPrivateProfileString(section, key, NULL, resultBuf, sizeof(resultBuf), configPath.c_str()); + result = resultBuf; + } + if (!configPathCustom.empty()) + { + UInt32 resultLen = GetPrivateProfileString(section, key, NULL, resultBuf, sizeof(resultBuf), configPathCustom.c_str()); + if (resultLen > 0) // Only take custom if we have it + result = resultBuf; + } + + return result; +} + +template +const char * SKEE64GetTypeFormatting(T * dataOut) +{ + return false; +} + +template<> const char * SKEE64GetTypeFormatting(float * dataOut) { return "%f"; } +template<> const char * SKEE64GetTypeFormatting(bool * dataOut) { return "%c"; } +template<> const char * SKEE64GetTypeFormatting(SInt16 * dataOut) { return "%hd"; } +template<> const char * SKEE64GetTypeFormatting(UInt16 * dataOut) { return "%hu"; } +template<> const char * SKEE64GetTypeFormatting(SInt32 * dataOut) { return "%d"; } +template<> const char * SKEE64GetTypeFormatting(UInt32 * dataOut) { return "%u"; } +template<> const char * SKEE64GetTypeFormatting(UInt64 * dataOut) { return "%I64u"; } + +template +bool SKEE64GetConfigValue(const char * section, const char * key, T * dataOut) +{ + std::string data = SKEE64GetConfigOption(section, key); + if (data.empty()) + return false; + + T tmp; + bool res = (sscanf_s(data.c_str(), SKEE64GetTypeFormatting(dataOut), &tmp) == 1); + if (res) { + *dataOut = tmp; + } + return res; +} + +template<> +bool SKEE64GetConfigValue(const char * section, const char * key, bool * dataOut) +{ + std::string data = SKEE64GetConfigOption(section, key); + if (data.empty()) + return false; + + UInt32 tmp; + bool res = (sscanf_s(data.c_str(), SKEE64GetTypeFormatting(&tmp), &tmp) == 1); + if (res) { + *dataOut = (tmp > 0); + } + return res; +} + +// This event isnt hooked up SKSE end +void SKEE64Serialization_FormDelete(UInt64 handle) +{ + g_overrideInterface.RemoveAllReferenceOverrides(handle); + g_overrideInterface.RemoveAllReferenceNodeOverrides(handle); +} + +void SKEE64Serialization_Revert(SKSESerializationInterface * intfc) +{ + _MESSAGE("Reverting..."); + + g_overlayInterface.Revert(); + g_overrideInterface.Revert(); + g_bodyMorphInterface.Revert(); + g_itemDataInterface.Revert(); + g_dyeMap.Revert(); + g_transformInterface.Revert(); + g_morphHandler.Revert(); +} + +class StopWatch +{ +public: + StopWatch() + { + + } + + void Start() + { + start = std::chrono::system_clock::now(); + } + + long long Stop() + { + end = std::chrono::system_clock::now(); + return std::chrono::duration_cast(end - start).count(); + } + +private: + std::chrono::system_clock::time_point start; + std::chrono::system_clock::time_point end; +}; + +void SKEE64Serialization_Save(SKSESerializationInterface * intfc) +{ + _MESSAGE("Saving..."); + + StopWatch sw; + UInt32 strCount = 0; + sw.Start(); + g_transformInterface.VisitStrings([&](BSFixedString str) + { + g_stringTable.StringToId(str.data, strCount); + strCount++; + }); + g_overrideInterface.VisitStrings([&](BSFixedString str) + { + g_stringTable.StringToId(str.data, strCount); + strCount++; + }); + g_bodyMorphInterface.VisitStrings([&](BSFixedString str) + { + g_stringTable.StringToId(str.data, strCount); + strCount++; + }); + + _DMESSAGE("%s - Pooled strings %dms", __FUNCTION__, sw.Stop()); + + sw.Start(); + g_stringTable.Save(intfc, StringTable::kSerializationVersion); + _DMESSAGE("%s - Serialized string table %dms", __FUNCTION__, sw.Stop()); + + sw.Start(); + g_transformInterface.Save(intfc, NiTransformInterface::kSerializationVersion); + _DMESSAGE("%s - Serialized transforms %dms", __FUNCTION__, sw.Stop()); + + sw.Start(); + g_overlayInterface.Save(intfc, OverlayInterface::kSerializationVersion); + _DMESSAGE("%s - Serialized overlays %dms", __FUNCTION__, sw.Stop()); + + sw.Start(); + g_overrideInterface.Save(intfc, OverrideInterface::kSerializationVersion); + _DMESSAGE("%s - Serialized overrides %dms", __FUNCTION__, sw.Stop()); + + sw.Start(); + g_bodyMorphInterface.Save(intfc, BodyMorphInterface::kSerializationVersion); + _DMESSAGE("%s - Serialized body morphs %dms", __FUNCTION__, sw.Stop()); + + sw.Start(); + g_itemDataInterface.Save(intfc, ItemDataInterface::kSerializationVersion); + _DMESSAGE("%s - Serialized item data %dms", __FUNCTION__, sw.Stop()); + + PlayerCharacter * player = (*g_thePlayer); + TESNPC * playerBase = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + + g_morphHandler.Save(playerBase, intfc, kSerializationDataVersion); + + g_stringTable.Clear(); +} + +void SKEE64Serialization_Load(SKSESerializationInterface * intfc) +{ + _MESSAGE("Loading..."); + + UInt32 type, length, version; + bool error = false; + + PlayerCharacter * player = (*g_thePlayer); + TESNPC * playerBase = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + + g_morphHandler.Load(playerBase, intfc, kSerializationDataVersion); + + g_task->AddTask(new SKSETaskApplyMorphs(player)); + + StopWatch sw; + sw.Start(); + while (intfc->GetNextRecordInfo(&type, &version, &length)) + { + switch (type) + { + case 'STTB': g_stringTable.Load(intfc, version); break; + case 'AOVL': g_overlayInterface.Load(intfc, version); break; + case 'ACEN': g_overrideInterface.LoadOverrides(intfc, version); break; + case 'NDEN': g_overrideInterface.LoadNodeOverrides(intfc, version); break; + case 'WPEN': g_overrideInterface.LoadWeaponOverrides(intfc, version); break; + case 'SKNR': g_overrideInterface.LoadSkinOverrides(intfc, version); break; + case 'MRPH': g_bodyMorphInterface.Load(intfc, version); break; + case 'ITEE': g_itemDataInterface.Load(intfc, version); break; + case 'ACTM': g_transformInterface.Load(intfc, version); break; + default: + _MESSAGE("unhandled type %08X (%.4s)", type, &type); + error = true; + break; + } + } + _DMESSAGE("%s - Loaded %dms", __FUNCTION__, sw.Stop()); + + g_stringTable.Clear(); + g_firstLoad = true; + +#ifdef _DEBUG + g_overrideInterface.DumpMap(); + g_overlayInterface.DumpMap(); +#endif +} + +bool RegisterNiOverrideScaleform(GFxMovieView * view, GFxValue * root) +{ + GFxValue obj; + view->CreateObject(&obj); + RegisterNumber(&obj, "iNumOverlays", g_numBodyOverlays); + RegisterNumber(&obj, "iSpellOverlays", g_numSpellBodyOverlays); + root->SetMember("body", &obj); + + obj.SetNull(); + view->CreateObject(&obj); + RegisterNumber(&obj, "iNumOverlays", g_numHandOverlays); + RegisterNumber(&obj, "iSpellOverlays", g_numSpellHandOverlays); + root->SetMember("hand", &obj); + + obj.SetNull(); + view->CreateObject(&obj); + RegisterNumber(&obj, "iNumOverlays", g_numFeetOverlays); + RegisterNumber(&obj, "iSpellOverlays", g_numSpellFeetOverlays); + root->SetMember("feet", &obj); + + obj.SetNull(); + view->CreateObject(&obj); + RegisterNumber(&obj, "iNumOverlays", g_numFaceOverlays); + RegisterNumber(&obj, "iSpellOverlays", g_numSpellFaceOverlays); + root->SetMember("face", &obj); + + RegisterBool(root, "bPlayerOnly", g_playerOnly); + + RegisterFunction (root, view, "GetDyeItems"); + RegisterFunction (root, view, "GetDyeableItems"); + RegisterFunction (root, view, "SetItemDyeColor"); + + return true; +} + +bool RegisterCharGenScaleform(GFxMovieView * view, GFxValue * root) +{ + RegisterFunction (root, view, "ImportHead"); + RegisterFunction (root, view, "ExportHead"); + RegisterFunction (root, view, "SavePreset"); + RegisterFunction (root, view, "LoadPreset"); + RegisterFunction (root, view, "ReadPreset"); + RegisterFunction (root, view, "ReloadSliders"); + RegisterFunction (root, view, "GetSliderData"); + RegisterFunction (root, view, "GetModName"); + + RegisterFunction (root, view, "GetPlayerPosition"); + RegisterFunction (root, view, "GetPlayerRotation"); + RegisterFunction (root, view, "SetPlayerRotation"); + + RegisterFunction (root, view, "GetRaceSexCameraRot"); + RegisterFunction (root, view, "GetRaceSexCameraPos"); + RegisterFunction (root, view, "SetRaceSexCameraPos"); + + RegisterFunction (root, view, "CreateMorphEditor"); + RegisterFunction (root, view, "ReleaseMorphEditor"); + + RegisterFunction (root, view, "LoadImportedHead"); + RegisterFunction (root, view, "ReleaseImportedHead"); + + RegisterFunction (root, view, "BeginRotateMesh"); + RegisterFunction (root, view, "DoRotateMesh"); + RegisterFunction (root, view, "EndRotateMesh"); + + RegisterFunction (root, view, "BeginPanMesh"); + RegisterFunction (root, view, "DoPanMesh"); + RegisterFunction (root, view, "EndPanMesh"); + + RegisterFunction (root, view, "BeginPaintMesh"); + RegisterFunction (root, view, "DoPaintMesh"); + RegisterFunction (root, view, "EndPaintMesh"); + + RegisterFunction (root, view, "GetCurrentBrush"); + RegisterFunction (root, view, "SetCurrentBrush"); + + RegisterFunction (root, view, "GetBrushes"); + RegisterFunction (root, view, "SetBrushData"); + + RegisterFunction (root, view, "GetMeshes"); + RegisterFunction (root, view, "SetMeshData"); + + RegisterFunction (root, view, "UndoAction"); + RegisterFunction (root, view, "RedoAction"); + RegisterFunction (root, view, "GoToAction"); + RegisterFunction (root, view, "GetActionLimit"); + RegisterFunction (root, view, "ClearSculptData"); + + RegisterFunction (root, view, "GetMeshCameraRadius"); + RegisterFunction (root, view, "SetMeshCameraRadius"); + + RegisterFunction (root, view, "GetExternalFiles"); + return true; +} + +bool RegisterPapyrusFunctions(VMClassRegistry * registry) +{ + papyrusNiOverride::RegisterFuncs(registry); + papyrusCharGen::RegisterFuncs(registry); + return true; +} + +class SKSEObjectLoadSink : public BSTEventSink +{ +public: + virtual ~SKSEObjectLoadSink() {} // todo? + virtual EventResult ReceiveEvent(TESObjectLoadedEvent * evn, EventDispatcher * dispatcher) + { + if (evn) { + TESForm * form = LookupFormByID(evn->formId); + if (form) { + if (g_enableBodyGen && form->formType == Character::kTypeID) { + TESObjectREFR * reference = DYNAMIC_CAST(form, TESForm, TESObjectREFR); + if (reference) { + if (!g_bodyMorphInterface.HasMorphs(reference)) { + UInt32 total = g_bodyMorphInterface.EvaluateBodyMorphs(reference); + if (total) { + _DMESSAGE("%s - Applied %d morph(s) to %s", __FUNCTION__, total, CALL_MEMBER_FN(reference, GetReferenceName)()); + g_bodyMorphInterface.UpdateModelWeight(reference); + } + } + } + } + + if (g_enableAutoTransforms) { + UInt64 handle = g_overrideInterface.GetHandle(form, TESObjectREFR::kTypeID); + g_transformInterface.SetHandleNodeTransforms(handle); + } + } + } + return kEvent_Continue; + }; +}; + +SKSEObjectLoadSink g_objectLoadSink; + +class SKSEInitScriptSink : public BSTEventSink < TESInitScriptEvent > +{ +public: + virtual ~SKSEInitScriptSink() {} // todo? + virtual EventResult ReceiveEvent(TESInitScriptEvent * evn, EventDispatcher * dispatcher) + { + if (evn) { + TESObjectREFR * reference = evn->reference; + if (reference && g_enableBodyInit) { + if (reference->formType == Character::kTypeID) { + if (!g_bodyMorphInterface.HasMorphs(reference)) { + UInt32 total = g_bodyMorphInterface.EvaluateBodyMorphs(reference); + if (total) { + _DMESSAGE("%s - Applied %d morph(s) to %s", __FUNCTION__, total, CALL_MEMBER_FN(reference, GetReferenceName)()); + } + } + } + } + } + return kEvent_Continue; + }; +}; + +SKSEInitScriptSink g_initScriptSink; + +void SKSEMessageHandler(SKSEMessagingInterface::Message * message) +{ + switch (message->type) + { + case SKSEMessagingInterface::kMessage_InputLoaded: + { + if (g_enableAutoTransforms || g_enableBodyGen) { + GetEventDispatcherList()->objectLoadedDispatcher.AddEventSink(&g_objectLoadSink); + } + } + break; + case SKSEMessagingInterface::kMessage_PreLoadGame: + g_enableBodyInit = false; + g_tintMaskInterface.ManageTints(); + break; + case SKSEMessagingInterface::kMessage_PostLoadGame: + g_enableBodyInit = true; + g_tintMaskInterface.ReleaseTints(); + break; + case SKSEMessagingInterface::kMessage_DataLoaded: + { + if (g_enableBodyGen) { + GetEventDispatcherList()->initScriptDispatcher.AddEventSink(&g_initScriptSink); + + g_bodyMorphInterface.LoadMods(); + } + + g_morphHandler.LoadMods(); + } + break; + } +} + +void InterfaceExchangeMessageHandler(SKSEMessagingInterface::Message * message) +{ + switch (message->type) + { + case InterfaceExchangeMessage::kMessage_ExchangeInterface: + { + InterfaceExchangeMessage * exchangeMessage = (InterfaceExchangeMessage*)message->data; + exchangeMessage->interfaceMap = &g_interfaceMap; + } + break; + } +} + +extern "C" +{ + +bool SKSEPlugin_Query(const SKSEInterface * skse, PluginInfo * info) +{ + SInt32 logLevel = IDebugLog::kLevel_DebugMessage; + if (SKEE64GetConfigValue("Debug", "iLogLevel", &logLevel)) + gLog.SetLogLevel((IDebugLog::LogLevel)logLevel); + + if (logLevel >= 0) + gLog.OpenRelative(CSIDL_MYDOCUMENTS, "\\My Games\\Skyrim Special Edition\\SKSE\\skee64.log"); + + _DMESSAGE("skee"); + + // populate info structure + info->infoVersion = PluginInfo::kInfoVersion; + info->name = "skee"; + info->version = 1; + + // store plugin handle so we can identify ourselves later + g_pluginHandle = skse->GetPluginHandle(); + + if(skse->isEditor) + { + _MESSAGE("loaded in editor, marking as incompatible"); + return false; + } + else if(skse->runtimeVersion != RUNTIME_VERSION_1_5_3) + { + _FATALERROR("unsupported runtime version %08X", skse->runtimeVersion); + return false; + } + + // get the serialization interface and query its version + g_serialization = (SKSESerializationInterface *)skse->QueryInterface(kInterface_Serialization); + if(!g_serialization) + { + _FATALERROR("couldn't get serialization interface"); + return false; + } + if(g_serialization->version < MIN_SERIALIZATION_VERSION)//SKSESerializationInterface::kVersion) + { + _FATALERROR("serialization interface too old (%d expected %d)", g_serialization->version, MIN_SERIALIZATION_VERSION); + return false; + } + + // get the scaleform interface and query its version + g_scaleform = (SKSEScaleformInterface *)skse->QueryInterface(kInterface_Scaleform); + if(!g_scaleform) + { + _FATALERROR("couldn't get scaleform interface"); + return false; + } + if(g_scaleform->interfaceVersion < MIN_SCALEFORM_VERSION) + { + _FATALERROR("scaleform interface too old (%d expected %d)", g_scaleform->interfaceVersion, MIN_SCALEFORM_VERSION); + return false; + } + + // get the papyrus interface and query its version + g_papyrus = (SKSEPapyrusInterface *)skse->QueryInterface(kInterface_Papyrus); + if (!g_papyrus) + { + _FATALERROR("couldn't get papyrus interface"); + return false; + } + if (g_papyrus->interfaceVersion < MIN_PAPYRUS_VERSION) + { + _FATALERROR("scaleform interface too old (%d expected %d)", g_papyrus->interfaceVersion, MIN_PAPYRUS_VERSION); + return false; + } + + // get the task interface and query its version + g_task = (SKSETaskInterface *)skse->QueryInterface(kInterface_Task); + if(!g_task) + { + _FATALERROR("couldn't get task interface"); + return false; + } + if(g_task->interfaceVersion < MIN_TASK_VERSION)//SKSETaskInterface::kInterfaceVersion) + { + _FATALERROR("task interface too old (%d expected %d)", g_task->interfaceVersion, MIN_TASK_VERSION); + return false; + } + + g_messaging = (SKSEMessagingInterface *)skse->QueryInterface(kInterface_Messaging); + if (!g_messaging) { + _ERROR("couldn't get messaging interface"); + } + + // supported runtime version + return true; +} + +bool SKSEPlugin_Load(const SKSEInterface * skse) +{ + g_interfaceMap.AddInterface("Override", &g_overrideInterface); + g_interfaceMap.AddInterface("Overlay", &g_overlayInterface); + g_interfaceMap.AddInterface("NiTransform", &g_transformInterface); + g_interfaceMap.AddInterface("BodyMorph", &g_bodyMorphInterface); + g_interfaceMap.AddInterface("ItemData", &g_itemDataInterface); + g_interfaceMap.AddInterface("TintMask", &g_tintMaskInterface); + + _DMESSAGE("NetImmerse Override Enabled"); + + SKEE64GetConfigValue("Overlays", "bPlayerOnly", &g_playerOnly); + SKEE64GetConfigValue("Overlays", "bEnableFaceOverlays", &g_enableFaceOverlays); + SKEE64GetConfigValue("Overlays", "bImmediateArmor", &g_immediateArmor); + SKEE64GetConfigValue("Overlays", "bImmediateFace", &g_immediateFace); + + SKEE64GetConfigValue("Overlays/Body", "iNumOverlays", &g_numBodyOverlays); + SKEE64GetConfigValue("Overlays/Body", "iSpellOverlays", &g_numSpellBodyOverlays); + SKEE64GetConfigValue("Overlays/Hands", "iNumOverlays", &g_numHandOverlays); + SKEE64GetConfigValue("Overlays/Hands", "iSpellOverlays", &g_numSpellHandOverlays); + SKEE64GetConfigValue("Overlays/Feet", "iNumOverlays", &g_numFeetOverlays); + SKEE64GetConfigValue("Overlays/Feet", "iSpellOverlays", &g_numSpellFeetOverlays); + SKEE64GetConfigValue("Overlays/Face", "iNumOverlays", &g_numFaceOverlays); + SKEE64GetConfigValue("Overlays/Face", "iSpellOverlays", &g_numSpellFaceOverlays); + + SKEE64GetConfigValue("Overlays/Data", "bAlphaOverride", &g_alphaOverride); + SKEE64GetConfigValue("Overlays/Data", "iAlphaFlags", &g_alphaFlags); + SKEE64GetConfigValue("Overlays/Data", "iAlphaThreshold", &g_alphaThreshold); + + std::string defaultTexture = GetConfigOption("Overlays/Data", "sDefaultTexture"); + if (defaultTexture.empty()) { + defaultTexture = "textures\\actors\\character\\overlays\\default.dds"; + } + g_overlayInterface.SetDefaultTexture(defaultTexture); + + SKEE64GetConfigValue("General", "iLoadMode", &g_loadMode); + SKEE64GetConfigValue("General", "bEnableAutoTransforms", &g_enableAutoTransforms); + SKEE64GetConfigValue("General", "bEnableEquippableTransforms", &g_enableEquippableTransforms); + SKEE64GetConfigValue("General", "bEnableBodyGen", &g_enableBodyGen); + SKEE64GetConfigValue("General", "iScaleMode", &g_scaleMode); + SKEE64GetConfigValue("General", "iBodyMorphMode", &g_bodyMorphMode); + SKEE64GetConfigValue("General", "bParallelMorphing", &g_parallelMorphing); + + UInt32 bodyMorphMemoryLimit = 256000000; + if (SKEE64GetConfigValue("General", "uBodyMorphMemoryLimit", &bodyMorphMemoryLimit)) + { + g_bodyMorphInterface.SetCacheLimit(bodyMorphMemoryLimit); + } + + if (g_numBodyOverlays > 0x7F) + g_numBodyOverlays = 0x7F; + if (g_numSpellBodyOverlays > 0x7F) + g_numSpellBodyOverlays = 0x7F; + + if (g_numHandOverlays > 0x7F) + g_numHandOverlays = 0x7F; + if (g_numSpellHandOverlays > 0x7F) + g_numSpellHandOverlays = 0x7F; + + if (g_numFeetOverlays > 0x7F) + g_numFeetOverlays = 0x7F; + if (g_numSpellFeetOverlays > 0x7F) + g_numSpellFeetOverlays = 0x7F; + + if (g_numFaceOverlays > 0x7F) + g_numFaceOverlays = 0x7F; + if (g_numSpellFaceOverlays > 0x7F) + g_numSpellFaceOverlays = 0x7F; + + if (g_alphaThreshold > 0xFF) + g_alphaThreshold = 0xFF; + + if(!g_enableFaceOverlays) { + g_numFaceOverlays = 0; + g_numSpellFaceOverlays = 0; + } + + std::string data = GetConfigOption("FaceGen", "sTemplateRace"); + if (!data.empty()) + g_raceTemplate = data; + + SKEE64GetConfigValue("FaceGen", "fSliderMultiplier", &g_sliderMultiplier); + SKEE64GetConfigValue("FaceGen", "fSliderInterval", &g_sliderInterval); + + if (g_sliderMultiplier <= 0) + g_sliderMultiplier = 0.01f; + if (g_sliderInterval <= 0) + g_sliderInterval = 0.01f; + if (g_sliderInterval > 1.0) + g_sliderInterval = 1.0; + + SKEE64GetConfigValue("FaceGen", "bDisableFaceGenCache", &g_disableFaceGenCache); + SKEE64GetConfigValue("FaceGen", "bExternalHeads", &g_externalHeads); + SKEE64GetConfigValue("FaceGen", "bExtendedMorphs", &g_extendedMorphs); + SKEE64GetConfigValue("FaceGen", "bAllowAllMorphs", &g_allowAllMorphs); + SKEE64GetConfigValue("FaceGen", "fPanSpeed", &g_panSpeed); + SKEE64GetConfigValue("FaceGen", "fFOV", &g_cameraFOV); + +#ifdef FIXME + std::string types[CDXBrush::kBrushTypes]; + types[CDXBrush::kBrushType_Mask_Add] = "Brush/MaskAdd/"; + types[CDXBrush::kBrushType_Mask_Subtract] = "Brush/MaskSubtract/"; + types[CDXBrush::kBrushType_Inflate] = "Brush/Inflate/"; + types[CDXBrush::kBrushType_Deflate] = "Brush/Deflate/"; + types[CDXBrush::kBrushType_Smooth] = "Brush/Smooth/"; + types[CDXBrush::kBrushType_Move] = "Brush/Move/"; + + std::string properties[CDXBrush::kBrushProperties]; + properties[CDXBrush::kBrushProperty_Radius] = "Radius"; + properties[CDXBrush::kBrushProperty_Strength] = "Strength"; + properties[CDXBrush::kBrushProperty_Falloff] = "Falloff"; + + std::string values[CDXBrush::kBrushPropertyValues]; + values[CDXBrush::kBrushPropertyValue_Value] = "dbDefault"; + values[CDXBrush::kBrushPropertyValue_Min] = "dbMin"; + values[CDXBrush::kBrushPropertyValue_Max] = "dbMax"; + values[CDXBrush::kBrushPropertyValue_Interval] = "dbInterval"; + + CDXBrush::InitGlobals(); + + for (UInt32 b = 0; b < CDXBrush::kBrushTypes; b++) { + for (UInt32 p = 0; p < CDXBrush::kBrushProperties; p++) { + for (UInt32 v = 0; v < CDXBrush::kBrushPropertyValues; v++) { + std::string section = types[b] + properties[p]; + double val = 0.0; + if (SKEE64GetConfigValue(section.c_str(), values[v].c_str(), &val)) + g_brushProperties[b][p][v] = val; + } + } + } +#endif + + if (g_serialization) { + g_serialization->SetUniqueID(g_pluginHandle, 'SKEE'); + g_serialization->SetRevertCallback(g_pluginHandle, SKEE64Serialization_Revert); + g_serialization->SetSaveCallback(g_pluginHandle, SKEE64Serialization_Save); + g_serialization->SetLoadCallback(g_pluginHandle, SKEE64Serialization_Load); + g_serialization->SetFormDeleteCallback(g_pluginHandle, SKEE64Serialization_FormDelete); + } + + // register scaleform callbacks + if (g_scaleform) { + g_scaleform->Register("NiOverride", RegisterNiOverrideScaleform); + g_scaleform->Register("CharGen", RegisterCharGenScaleform); + } + + if (g_papyrus) { + g_papyrus->Register(RegisterPapyrusFunctions); + } + + if (g_messaging) { + g_messaging->RegisterListener(g_pluginHandle, "SKSE", SKSEMessageHandler); + g_messaging->RegisterListener(g_pluginHandle, nullptr, InterfaceExchangeMessageHandler); + } + + return InstallSKEEHooks(); +} + +}; diff --git a/skee/main_chargen.cpp b/skee/main_chargen.cpp new file mode 100644 index 0000000..8f1e5ec --- /dev/null +++ b/skee/main_chargen.cpp @@ -0,0 +1,514 @@ +#include "skse/PluginAPI.h" +#include "skse/skse_version.h" +#include "skse/SafeWrite.h" + +#include "skse/GameRTTI.h" + +#include "skse/ScaleformMovie.h" +#include "skse/ScaleformLoader.h" + +#include "MorphHandler.h" +#include "PartHandler.h" +#include "Hooks.h" +#include "ScaleformFunctions.h" +#include "PapyrusCharGen.h" + +#include "interfaces/IPluginInterface.h" +#include "interfaces/OverrideInterface.h" +#include "interfaces/NiTransformInterface.h" +#include "interfaces/OverlayInterface.h" +#include "interfaces/BodyMorphInterface.h" + +#include "CDXBrush.h" + +#include +#include + +#define MIN_TASK_VERSION 1 +#define MIN_PAP_VERSION 1 +#define MIN_SCALEFORM_VERSION 1 +#define MIN_SERIALIZATION_VERSION 2 +#define PLUGIN_VERSION 6 + +IDebugLog gLog; +PluginHandle g_pluginHandle = kPluginHandle_Invalid; +const UInt32 kSerializationDataVersion = 2; + +// Interfaces +SKSESerializationInterface * g_serialization = NULL; +SKSEScaleformInterface * g_scaleform = NULL; +SKSETaskInterface * g_task = NULL; +SKSEPapyrusInterface * g_papyrus = NULL; +SKSEMessagingInterface * g_messaging = NULL; + +OverrideInterface * g_overrideInterface = NULL; +NiTransformInterface * g_transformInterface = NULL; +OverlayInterface * g_overlayInterface = NULL; +BodyMorphInterface * g_bodyMorphInterface = NULL; + +MorphHandler g_morphHandler; +PartSet g_partSet; +//CustomDataMap g_customDataMap; + +bool g_externalHeads = false; +bool g_extendedMorphs = true; +bool g_allowAllMorphs = true; +bool g_disableFaceGenCache = true; +float g_sliderMultiplier = 1.0f; +float g_sliderInterval = 0.01f; +float g_panSpeed = 0.01f; +float g_cameraFOV = 45.0f; +UInt32 g_numPresets = 10; +UInt32 g_customDataMax = 10; +std::string g_raceTemplate = "NordRace"; + +extern double g_brushProperties[CDXBrush::kBrushTypes][CDXBrush::kBrushProperties][CDXBrush::kBrushPropertyValues]; + +void Serialization_Revert(SKSESerializationInterface * intfc) +{ + g_morphHandler.Revert(); +} + +void Serialization_Save(SKSESerializationInterface * intfc) +{ + _DMESSAGE("Saving..."); + + PlayerCharacter * player = (*g_thePlayer); + TESNPC * playerBase = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + + g_morphHandler.Save(playerBase, intfc, kSerializationDataVersion); + + _DMESSAGE("Save Complete."); +} + +void Serialization_Load(SKSESerializationInterface * intfc) +{ + _DMESSAGE("Loading..."); + +#ifdef _DEBUG_DATADUMP + g_morphHandler.DumpAll(); +#endif + + PlayerCharacter * player = (*g_thePlayer); + TESNPC * playerBase = DYNAMIC_CAST(player->baseForm, TESForm, TESNPC); + + g_morphHandler.Load(playerBase, intfc, kSerializationDataVersion); + + g_task->AddTask(new SKSETaskApplyMorphs(player)); + + _DMESSAGE("Load Complete."); +} + +const std::string & GetRuntimeDirectory(void) +{ + static std::string s_runtimeDirectory; + + if(s_runtimeDirectory.empty()) + { + // can't determine how many bytes we'll need, hope it's not more than MAX_PATH + char runtimePathBuf[MAX_PATH]; + UInt32 runtimePathLength = GetModuleFileName(GetModuleHandle(NULL), runtimePathBuf, sizeof(runtimePathBuf)); + + if(runtimePathLength && (runtimePathLength < sizeof(runtimePathBuf))) + { + std::string runtimePath(runtimePathBuf, runtimePathLength); + + // truncate at last slash + std::string::size_type lastSlash = runtimePath.rfind('\\'); + if(lastSlash != std::string::npos) // if we don't find a slash something is VERY WRONG + { + s_runtimeDirectory = runtimePath.substr(0, lastSlash + 1); + + _DMESSAGE("runtime root = %s", s_runtimeDirectory.c_str()); + } + else + { + _WARNING("no slash in runtime path? (%s)", runtimePath.c_str()); + } + } + else + { + _WARNING("couldn't find runtime path (len = %d, err = %08X)", runtimePathLength, GetLastError()); + } + } + + return s_runtimeDirectory; +} + +const std::string & GetConfigPath(void) +{ + static std::string s_configPath; + + if(s_configPath.empty()) + { + std::string runtimePath = GetRuntimeDirectory(); + if(!runtimePath.empty()) + { + s_configPath = runtimePath + "Data\\SKSE\\Plugins\\chargen.ini"; + + _DMESSAGE("config path = %s", s_configPath.c_str()); + } + } + + return s_configPath; +} + +std::string GetConfigOption(const char * section, const char * key) +{ + std::string result; + + const std::string & configPath = GetConfigPath(); + if(!configPath.empty()) + { + char resultBuf[256]; + resultBuf[0] = 0; + + UInt32 resultLen = GetPrivateProfileString(section, key, NULL, resultBuf, sizeof(resultBuf), configPath.c_str()); + + result = resultBuf; + } + + return result; +} + +template +bool GetConfigNumber(const char * section, const char * key, T * dataOut) +{ + std::string data = GetConfigOption(section, key); + if (data.empty()) + return false; + + return (sscanf_s(data.c_str(), "%d", dataOut) == 1); +} + +template<> +bool GetConfigNumber(const char * section, const char * key, float * dataOut) +{ + std::string data = GetConfigOption(section, key); + if(data.empty()) + return false; + + return (sscanf_s(data.c_str(), "%f", dataOut) == 1); +} + +template<> +bool GetConfigNumber(const char * section, const char * key, double * dataOut) +{ + std::string data = GetConfigOption(section, key); + if (data.empty()) + return false; + + return (sscanf_s(data.c_str(), "%lf", dataOut) == 1); +} + +bool RegisterScaleform(GFxMovieView * view, GFxValue * root) +{ + RegisterFunction (root, view, "ImportHead"); + RegisterFunction (root, view, "ExportHead"); + RegisterFunction (root, view, "SavePreset"); + RegisterFunction (root, view, "LoadPreset"); + RegisterFunction (root, view, "ReadPreset"); + RegisterFunction (root, view, "ReloadSliders"); + RegisterFunction (root, view, "GetSliderData"); + RegisterFunction (root, view, "GetModName"); + + RegisterFunction (root, view, "GetPlayerPosition"); + RegisterFunction (root, view, "GetPlayerRotation"); + RegisterFunction (root, view, "SetPlayerRotation"); + + RegisterFunction (root, view, "GetRaceSexCameraRot"); + RegisterFunction (root, view, "GetRaceSexCameraPos"); + RegisterFunction (root, view, "SetRaceSexCameraPos"); + + RegisterFunction (root, view, "CreateMorphEditor"); + RegisterFunction (root, view, "ReleaseMorphEditor"); + + RegisterFunction (root, view, "LoadImportedHead"); + RegisterFunction (root, view, "ReleaseImportedHead"); + + RegisterFunction (root, view, "BeginRotateMesh"); + RegisterFunction (root, view, "DoRotateMesh"); + RegisterFunction (root, view, "EndRotateMesh"); + + RegisterFunction (root, view, "BeginPanMesh"); + RegisterFunction (root, view, "DoPanMesh"); + RegisterFunction (root, view, "EndPanMesh"); + + RegisterFunction (root, view, "BeginPaintMesh"); + RegisterFunction (root, view, "DoPaintMesh"); + RegisterFunction (root, view, "EndPaintMesh"); + + RegisterFunction (root, view, "GetCurrentBrush"); + RegisterFunction (root, view, "SetCurrentBrush"); + + RegisterFunction (root, view, "GetBrushes"); + RegisterFunction (root, view, "SetBrushData"); + + RegisterFunction (root, view, "GetMeshes"); + RegisterFunction (root, view, "SetMeshData"); + + RegisterFunction (root, view, "UndoAction"); + RegisterFunction (root, view, "RedoAction"); + RegisterFunction (root, view, "GoToAction"); + RegisterFunction (root, view, "GetActionLimit"); + RegisterFunction (root, view, "ClearSculptData"); + + RegisterFunction (root, view, "GetMeshCameraRadius"); + RegisterFunction (root, view, "SetMeshCameraRadius"); + + RegisterFunction (root, view, "GetExternalFiles"); + return true; +} + +void SKSEMessageHandler(SKSEMessagingInterface::Message * message) +{ + switch (message->type) + { + case SKSEMessagingInterface::kMessage_PostLoad: + { + InterfaceExchangeMessage message; + g_messaging->Dispatch(g_pluginHandle, InterfaceExchangeMessage::kMessage_ExchangeInterface, (void*)&message, sizeof(InterfaceExchangeMessage*), "nioverride"); + if (message.interfaceMap) { + g_overrideInterface = (OverrideInterface*)message.interfaceMap->QueryInterface("Override"); + g_transformInterface = (NiTransformInterface *)message.interfaceMap->QueryInterface("NiTransform"); + g_overlayInterface = (OverlayInterface *)message.interfaceMap->QueryInterface("Overlay"); + g_bodyMorphInterface = (BodyMorphInterface *)message.interfaceMap->QueryInterface("BodyMorph"); + } + } + break; + } +} + +extern "C" +{ + +bool SKSEPlugin_Query(const SKSEInterface * skse, PluginInfo * info) +{ + SInt32 logLevel = IDebugLog::kLevel_DebugMessage; + if (GetConfigNumber("Debug", "iLogLevel", &logLevel)) + gLog.SetLogLevel((IDebugLog::LogLevel)logLevel); + + if (logLevel >= 0) + gLog.OpenRelative(CSIDL_MYDOCUMENTS, "\\My Games\\Skyrim\\SKSE\\skse_chargen.log"); + + _DMESSAGE("skse_chargen"); + + // populate info structure + info->infoVersion = PluginInfo::kInfoVersion; + info->name = "chargen"; + info->version = PLUGIN_VERSION; + + // store plugin handle so we can identify ourselves later + g_pluginHandle = skse->GetPluginHandle(); + + if(skse->isEditor) + { + _FATALERROR("loaded in editor, marking as incompatible"); + return false; + } + else if(skse->runtimeVersion != RUNTIME_VERSION_1_9_32_0) + { + _FATALERROR("unsupported runtime version %08X", skse->runtimeVersion); + return false; + } + + // get the serialization interface and query its version + g_serialization = (SKSESerializationInterface *)skse->QueryInterface(kInterface_Serialization); + if(!g_serialization) + { + _FATALERROR("couldn't get serialization interface"); + return false; + } + + if(g_serialization->version < MIN_SERIALIZATION_VERSION) + { + _FATALERROR("serialization interface too old (%d expected %d)", g_serialization->version, MIN_SERIALIZATION_VERSION); + return false; + } + + // get the scaleform interface and query its version + g_scaleform = (SKSEScaleformInterface *)skse->QueryInterface(kInterface_Scaleform); + if(!g_scaleform) + { + _FATALERROR("couldn't get scaleform interface"); + return false; + } + if(g_scaleform->interfaceVersion < MIN_SCALEFORM_VERSION) + { + _FATALERROR("scaleform interface too old (%d expected %d)", g_scaleform->interfaceVersion, MIN_SCALEFORM_VERSION); + return false; + } + + // get the task interface and query its version + g_task = (SKSETaskInterface *)skse->QueryInterface(kInterface_Task); + if(!g_task) + { + _FATALERROR("couldn't get task interface"); + return false; + } + if(g_task->interfaceVersion < MIN_TASK_VERSION) + { + _FATALERROR("task interface too old (%d expected %d)", g_task->interfaceVersion, MIN_TASK_VERSION); + return false; + } + + // get the papyrus interface and query its version + g_papyrus = (SKSEPapyrusInterface *)skse->QueryInterface(kInterface_Papyrus); + if(!g_papyrus) + { + _WARNING("couldn't get papyrus interface"); + } + if(g_papyrus && g_papyrus->interfaceVersion < MIN_PAP_VERSION) + { + _WARNING("papyrus interface too old (%d expected %d)", g_papyrus->interfaceVersion, MIN_PAP_VERSION); + } + + g_messaging = (SKSEMessagingInterface *)skse->QueryInterface(kInterface_Messaging); + if (!g_messaging) { + _ERROR("couldn't get messaging interface"); + } + + // supported runtime version + return true; +} + +bool RegisterFuncs(VMClassRegistry * registry) +{ + papyrusCharGen::RegisterFuncs(registry); + return true; +} + +bool SKSEPlugin_Load(const SKSEInterface * skse) +{ + _MESSAGE("CharGen Morph Support Enabled."); + + std::string data = GetConfigOption("FaceGen", "sTemplateRace"); + if (!data.empty()) + g_raceTemplate = data; + + float sliderMultiplier = 1.0f; + if (GetConfigNumber("FaceGen", "fSliderMultiplier", &sliderMultiplier)) + { + g_sliderMultiplier = sliderMultiplier; + if(g_sliderMultiplier <= 0) + g_sliderMultiplier = 0.01f; + } + float sliderInterval = 0.01f; + if (GetConfigNumber("FaceGen", "fSliderInterval", &sliderMultiplier)) + { + g_sliderInterval = sliderInterval; + if(g_sliderInterval <= 0) + g_sliderInterval = 0.01f; + if(g_sliderInterval > 1.0) + g_sliderInterval = 1.0; + } + + UInt32 disableFaceGenCache = 1; + if (GetConfigNumber("FaceGen", "bDisableFaceGenCache", &disableFaceGenCache)) + { + g_disableFaceGenCache = (disableFaceGenCache > 0); + } + + UInt32 externalHeads = 0; + if (GetConfigNumber("FaceGen", "bExternalHeads", &externalHeads)) + { + g_externalHeads = (externalHeads > 0); + } + UInt32 extendedMorphs = 1; + if (GetConfigNumber("FaceGen", "bExtendedMorphs", &extendedMorphs)) + { + g_extendedMorphs = (extendedMorphs > 0); + } + UInt32 allowAllMorphs = 1; + if (GetConfigNumber("FaceGen", "bAllowAllMorphs", &allowAllMorphs)) + { + g_allowAllMorphs = (allowAllMorphs > 0); + } + + float panSpeed = 0.01f; + if (GetConfigNumber("FaceGen", "fPanSpeed", &panSpeed)) + { + g_panSpeed = panSpeed; + } + + float cameraFOV = 45.0f; + if (GetConfigNumber("FaceGen", "fFOV", &cameraFOV)) + { + g_cameraFOV = cameraFOV; + } + + std::string types[CDXBrush::kBrushTypes]; + types[CDXBrush::kBrushType_Mask_Add] = "Brush/MaskAdd/"; + types[CDXBrush::kBrushType_Mask_Subtract] = "Brush/MaskSubtract/"; + types[CDXBrush::kBrushType_Inflate] = "Brush/Inflate/"; + types[CDXBrush::kBrushType_Deflate] = "Brush/Deflate/"; + types[CDXBrush::kBrushType_Smooth] = "Brush/Smooth/"; + types[CDXBrush::kBrushType_Move] = "Brush/Move/"; + + std::string properties[CDXBrush::kBrushProperties]; + properties[CDXBrush::kBrushProperty_Radius] = "Radius"; + properties[CDXBrush::kBrushProperty_Strength] = "Strength"; + properties[CDXBrush::kBrushProperty_Falloff] = "Falloff"; + + std::string values[CDXBrush::kBrushPropertyValues]; + values[CDXBrush::kBrushPropertyValue_Value] = "dbDefault"; + values[CDXBrush::kBrushPropertyValue_Min] = "dbMin"; + values[CDXBrush::kBrushPropertyValue_Max] = "dbMax"; + values[CDXBrush::kBrushPropertyValue_Interval] = "dbInterval"; + + CDXBrush::InitGlobals(); + + for (UInt32 b = 0; b < CDXBrush::kBrushTypes; b++) { + for (UInt32 p = 0; p < CDXBrush::kBrushProperties; p++) { + for (UInt32 v = 0; v < CDXBrush::kBrushPropertyValues; v++) { + std::string section = types[b] + properties[p]; + double val = 0.0; + if (GetConfigNumber(section.c_str(), values[v].c_str(), &val)) + g_brushProperties[b][p][v] = val; + } + } + } + + InstallHooks(); + + if(g_disableFaceGenCache) { + SafeWrite8(0x008868C0, 0xC3); // Disable PrecacheCharGen + SafeWrite8(0x00886B50, 0xC3); // Disable PrecacheCharGenClear + } + + //SafeWrite8(DATA_ADDR(0x005A0AB0, 0xF5) + 2, 0xFF); + + // Patch Morph Limit + /*SafeWrite8(DATA_ADDR(0x005A7570, 0x33) + 1, 0xFF); + SafeWrite8(DATA_ADDR(0x005A7870, 0x9D) + 1, 0xFF); + SafeWrite8(DATA_ADDR(0x005A7870, 0xA4) + 1, 0xFF);*/ + + /*SafeWrite8(0x005A7280 + 0x0F + 1, 0xFF); + SafeWrite8(0x005A7240 + 0x10 + 1, 0xFF); + SafeWrite8(0x005A7200 + 0x10 + 1, 0xFF); + SafeWrite8(0x005A71C0 + 0x10 + 1, 0xFF);*/ + + if (g_serialization) { + g_serialization->SetUniqueID(g_pluginHandle, 'FCGN'); + g_serialization->SetRevertCallback(g_pluginHandle, Serialization_Revert); + g_serialization->SetSaveCallback(g_pluginHandle, Serialization_Save); + g_serialization->SetLoadCallback(g_pluginHandle, Serialization_Load); + } + + // register scaleform callbacks + if (g_scaleform) { + g_scaleform->Register("CharGen", RegisterScaleform); + } + + if (g_papyrus) { + g_papyrus->Register(RegisterFuncs); + } + + if (g_messaging) { + g_messaging->RegisterListener(g_pluginHandle, "SKSE", SKSEMessageHandler); + } + + return true; +} + +}; diff --git a/skee/resource.h b/skee/resource.h new file mode 100644 index 0000000..7ca31da --- /dev/null +++ b/skee/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Resource.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/skee/skee64.vcxproj b/skee/skee64.vcxproj new file mode 100644 index 0000000..e077eff --- /dev/null +++ b/skee/skee64.vcxproj @@ -0,0 +1,313 @@ + + + + + Debug + x64 + + + Release + x64 + + + + skee64 + {FC9A1EE6-D9DB-47F6-B0ED-8867439C43CE} + skee64 + Win32Proj + 8.1 + + + + DynamicLibrary + v140 + MultiByte + true + + + DynamicLibrary + v140 + MultiByte + + + + + + + + + + + + + <_ProjectFileVersion>12.0.21005.1 + + + true + + + false + + + + Disabled + $(SolutionDir);$(SolutionDir)\..;$(SolutionDir)\..\..;$(SolutionDir)\skse64;$(SolutionDir)\jsoncpp;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;PLUGIN_EXAMPLE_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebug + + + Level3 + ProgramDatabase + common/IPrefix.h;%(ForcedIncludeFiles) + + + exports.def + true + Windows + shlwapi.lib;%(AdditionalDependencies) + + + + + + + + + + + copy "$(TargetPath)" "$(Skyrim64Path)\Data\SKSE\Plugins\$(TargetFileName)" + + + Installing DLL... + + + + + MaxSpeed + true + $(SolutionDir);$(SolutionDir)\..;$(SolutionDir)\..\..;$(SolutionDir)\skse64;$(SolutionDir)\jsoncpp;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;PLUGIN_EXAMPLE_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + true + + + Level3 + ProgramDatabase + common/IPrefix.h;%(ForcedIncludeFiles) + + + exports.def + true + Windows + true + true + shlwapi.lib;%(AdditionalDependencies) + + + + + + + + + copy "$(TargetPath)" "$(Skyrim64Path)\Data\SKSE\Plugins\$(TargetFileName)" + + + Installing DLL... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {d4c128a1-73dc-4941-a453-ce55af239ba8} + + + {20c6411c-596f-4b85-be4e-8bc91f59d8a6} + false + false + false + true + false + + + {522ec01c-c40f-4de4-a83e-06c5b56620e6} + + + + + + + + + \ No newline at end of file diff --git a/skee/skee64.vcxproj.filters b/skee/skee64.vcxproj.filters new file mode 100644 index 0000000..c83f963 --- /dev/null +++ b/skee/skee64.vcxproj.filters @@ -0,0 +1,559 @@ + + + + + + interfaces + + + interfaces + + + interfaces + + + interfaces + + + interfaces + + + interfaces + + + interfaces + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + utils + + + utils + + + handlers + + + handlers + + + utils + + + handlers + + + utils + + + papyrus\functions + + + papyrus\functions + + + interfaces + + + api\netimmerse + + + api\netimmerse + + + scaleform\functions + + + scaleform\functions + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + scaleform\functions + + + tinyxml + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\papyrus\vm + + + api\papyrus\vm + + + api\papyrus\vm + + + api\util + + + api\papyrus\vm + + + api\game + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\papyrus\vm + + + api\netimmerse + + + api\game + + + api\game + + + hooks + + + + + + + + + + + {0e7456c6-8b31-4e3c-8c58-6bf8d11029af} + + + {fd15b1d7-af8f-47cd-9bc2-81cb5e3031b6} + + + {32f893d1-2ac9-4bf4-847d-c826765c38ec} + + + {86d73a42-5804-4bb0-ba15-15de6b668abe} + + + {8a63702f-2d8b-48c4-b433-1cee3d884756} + + + {6b24f812-a327-4f57-bf81-0eb152237deb} + + + {32b8b088-86c4-46cd-8133-1185264ab64d} + + + {46eba454-d6fb-4652-98b4-4e68a0cdc53f} + + + {a7b136cc-81b4-4ade-b474-f1d1d93d5a80} + + + {cf28e502-c371-4e65-a68f-0ffee359622a} + + + {b0362c2f-7b73-433d-a183-3324be01f92e} + + + {034a5766-0417-496f-8069-f1336a7f0e0d} + + + {dbfabd71-8642-489e-8903-b0df2d615ecd} + + + {920a578f-cc79-4581-bade-41e3f2f878e6} + + + {8c282fe0-26e1-4096-87cd-a68ea11c3efb} + + + {c785bcf4-53bb-4e4e-896c-79d61e907e21} + + + {703a9dd2-68ff-4004-9066-f9bd34abd0dd} + + + {386572e3-f3b3-4998-847b-1f680bb2d95b} + + + + + interfaces + + + interfaces + + + interfaces + + + interfaces + + + interfaces + + + interfaces + + + interfaces + + + interfaces + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + renderer + + + utils + + + utils + + + handlers + + + handlers + + + utils + + + handlers + + + utils + + + papyrus\functions + + + papyrus\functions + + + interfaces + + + api\netimmerse + + + api\netimmerse + + + scaleform\functions + + + scaleform\functions + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + api\scaleform\api + + + scaleform\functions + + + tinyxml + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\game + + + api\papyrus\vm + + + api\papyrus\vm + + + api\papyrus\vm + + + api\util + + + api\papyrus\vm + + + api\game + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\netimmerse + + + api\papyrus\vm + + + api\netimmerse + + + api\game + + + api\game + + + hooks + + + \ No newline at end of file diff --git a/skee/tinyxml2.cpp b/skee/tinyxml2.cpp new file mode 100644 index 0000000..d87ef8e --- /dev/null +++ b/skee/tinyxml2.cpp @@ -0,0 +1,2199 @@ +/* +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "tinyxml2.h" + +#include // yes, this one new style header, is in the Android SDK. +# ifdef ANDROID_NDK +# include +#else +# include +#endif + +static const char LINE_FEED = (char)0x0a; // all line endings are normalized to LF +static const char LF = LINE_FEED; +static const char CARRIAGE_RETURN = (char)0x0d; // CR gets filtered out +static const char CR = CARRIAGE_RETURN; +static const char SINGLE_QUOTE = '\''; +static const char DOUBLE_QUOTE = '\"'; + +// Bunch of unicode info at: +// http://www.unicode.org/faq/utf_bom.html +// ef bb bf (Microsoft "lead bytes") - designates UTF-8 + +static const unsigned char TIXML_UTF_LEAD_0 = 0xefU; +static const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; +static const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + + +#define DELETE_NODE( node ) { \ + if ( node ) { \ + MemPool* pool = node->_memPool; \ + node->~XMLNode(); \ + pool->Free( node ); \ + } \ + } +#define DELETE_ATTRIBUTE( attrib ) { \ + if ( attrib ) { \ + MemPool* pool = attrib->_memPool; \ + attrib->~XMLAttribute(); \ + pool->Free( attrib ); \ + } \ + } + +namespace tinyxml2 +{ + +struct Entity { + const char* pattern; + int length; + char value; +}; + +static const int NUM_ENTITIES = 5; +static const Entity entities[NUM_ENTITIES] = { + { "quot", 4, DOUBLE_QUOTE }, + { "amp", 3, '&' }, + { "apos", 4, SINGLE_QUOTE }, + { "lt", 2, '<' }, + { "gt", 2, '>' } +}; + + +StrPair::~StrPair() +{ + Reset(); +} + + +void StrPair::Reset() +{ + if ( _flags & NEEDS_DELETE ) { + delete [] _start; + } + _flags = 0; + _start = 0; + _end = 0; +} + + +void StrPair::SetStr( const char* str, int flags ) +{ + Reset(); + size_t len = strlen( str ); + _start = new char[ len+1 ]; + memcpy( _start, str, len+1 ); + _end = _start + len; + _flags = flags | NEEDS_DELETE; +} + + +char* StrPair::ParseText( char* p, const char* endTag, int strFlags ) +{ + TIXMLASSERT( endTag && *endTag ); + + char* start = p; // fixme: hides a member + char endChar = *endTag; + size_t length = strlen( endTag ); + + // Inner loop of text parsing. + while ( *p ) { + if ( *p == endChar && strncmp( p, endTag, length ) == 0 ) { + Set( start, p, strFlags ); + return p + length; + } + ++p; + } + return 0; +} + + +char* StrPair::ParseName( char* p ) +{ + char* start = p; + + if ( !start || !(*start) ) { + return 0; + } + + while( *p && ( p == start ? XMLUtil::IsNameStartChar( *p ) : XMLUtil::IsNameChar( *p ) )) { + ++p; + } + + if ( p > start ) { + Set( start, p, 0 ); + return p; + } + return 0; +} + + +void StrPair::CollapseWhitespace() +{ + // Trim leading space. + _start = XMLUtil::SkipWhiteSpace( _start ); + + if ( _start && *_start ) { + char* p = _start; // the read pointer + char* q = _start; // the write pointer + + while( *p ) { + if ( XMLUtil::IsWhiteSpace( *p )) { + p = XMLUtil::SkipWhiteSpace( p ); + if ( *p == 0 ) { + break; // don't write to q; this trims the trailing space. + } + *q = ' '; + ++q; + } + *q = *p; + ++q; + ++p; + } + *q = 0; + } +} + + +const char* StrPair::GetStr() +{ + if ( _flags & NEEDS_FLUSH ) { + *_end = 0; + _flags ^= NEEDS_FLUSH; + + if ( _flags ) { + char* p = _start; // the read pointer + char* q = _start; // the write pointer + + while( p < _end ) { + if ( (_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == CR ) { + // CR-LF pair becomes LF + // CR alone becomes LF + // LF-CR becomes LF + if ( *(p+1) == LF ) { + p += 2; + } + else { + ++p; + } + *q++ = LF; + } + else if ( (_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == LF ) { + if ( *(p+1) == CR ) { + p += 2; + } + else { + ++p; + } + *q++ = LF; + } + else if ( (_flags & NEEDS_ENTITY_PROCESSING) && *p == '&' ) { + // Entities handled by tinyXML2: + // - special entities in the entity table [in/out] + // - numeric character reference [in] + // 中 or 中 + + if ( *(p+1) == '#' ) { + char buf[10] = { 0 }; + int len; + p = const_cast( XMLUtil::GetCharacterRef( p, buf, &len ) ); + for( int i=0; i(p); + // Check for BOM: + if ( *(pu+0) == TIXML_UTF_LEAD_0 + && *(pu+1) == TIXML_UTF_LEAD_1 + && *(pu+2) == TIXML_UTF_LEAD_2 ) { + *bom = true; + p += 3; + } + return p; +} + + +void XMLUtil::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) +{ + const unsigned long BYTE_MASK = 0xBF; + const unsigned long BYTE_MARK = 0x80; + const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + if (input < 0x80) { + *length = 1; + } + else if ( input < 0x800 ) { + *length = 2; + } + else if ( input < 0x10000 ) { + *length = 3; + } + else if ( input < 0x200000 ) { + *length = 4; + } + else { + *length = 0; // This code won't covert this correctly anyway. + return; + } + + output += *length; + + // Scary scary fall throughs. + switch (*length) { + case 4: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 3: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 2: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 1: + --output; + *output = (char)(input | FIRST_BYTE_MARK[*length]); + default: + break; + } +} + + +const char* XMLUtil::GetCharacterRef( const char* p, char* value, int* length ) +{ + // Presume an entity, and pull it out. + *length = 0; + + if ( *(p+1) == '#' && *(p+2) ) { + unsigned long ucs = 0; + ptrdiff_t delta = 0; + unsigned mult = 1; + + if ( *(p+2) == 'x' ) { + // Hexadecimal. + if ( !*(p+3) ) { + return 0; + } + + const char* q = p+3; + q = strchr( q, ';' ); + + if ( !q || !*q ) { + return 0; + } + + delta = q-p; + --q; + + while ( *q != 'x' ) { + if ( *q >= '0' && *q <= '9' ) { + ucs += mult * (*q - '0'); + } + else if ( *q >= 'a' && *q <= 'f' ) { + ucs += mult * (*q - 'a' + 10); + } + else if ( *q >= 'A' && *q <= 'F' ) { + ucs += mult * (*q - 'A' + 10 ); + } + else { + return 0; + } + mult *= 16; + --q; + } + } + else { + // Decimal. + if ( !*(p+2) ) { + return 0; + } + + const char* q = p+2; + q = strchr( q, ';' ); + + if ( !q || !*q ) { + return 0; + } + + delta = q-p; + --q; + + while ( *q != '#' ) { + if ( *q >= '0' && *q <= '9' ) { + ucs += mult * (*q - '0'); + } + else { + return 0; + } + mult *= 10; + --q; + } + } + // convert the UCS to UTF-8 + ConvertUTF32ToUTF8( ucs, value, length ); + return p + delta + 1; + } + return p+1; +} + + +void XMLUtil::ToStr( int v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%d", v ); +} + + +void XMLUtil::ToStr( unsigned v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%u", v ); +} + + +void XMLUtil::ToStr( bool v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%d", v ? 1 : 0 ); +} + +/* + ToStr() of a number is a very tricky topic. + https://github.com/leethomason/tinyxml2/issues/106 +*/ +void XMLUtil::ToStr( float v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%.8g", v ); +} + + +void XMLUtil::ToStr( double v, char* buffer, int bufferSize ) +{ + TIXML_SNPRINTF( buffer, bufferSize, "%.17g", v ); +} + + +bool XMLUtil::ToInt( const char* str, int* value ) +{ + if ( TIXML_SSCANF( str, "%d", value ) == 1 ) { + return true; + } + return false; +} + +bool XMLUtil::ToUnsigned( const char* str, unsigned *value ) +{ + if ( TIXML_SSCANF( str, "%u", value ) == 1 ) { + return true; + } + return false; +} + +bool XMLUtil::ToBool( const char* str, bool* value ) +{ + int ival = 0; + if ( ToInt( str, &ival )) { + *value = (ival==0) ? false : true; + return true; + } + if ( StringEqual( str, "true" ) ) { + *value = true; + return true; + } + else if ( StringEqual( str, "false" ) ) { + *value = false; + return true; + } + return false; +} + + +bool XMLUtil::ToFloat( const char* str, float* value ) +{ + if ( TIXML_SSCANF( str, "%f", value ) == 1 ) { + return true; + } + return false; +} + +bool XMLUtil::ToDouble( const char* str, double* value ) +{ + if ( TIXML_SSCANF( str, "%lf", value ) == 1 ) { + return true; + } + return false; +} + + +char* XMLDocument::Identify( char* p, XMLNode** node ) +{ + XMLNode* returnNode = 0; + char* start = p; + p = XMLUtil::SkipWhiteSpace( p ); + if( !p || !*p ) { + return p; + } + + // What is this thing? + // These strings define the matching patters: + static const char* xmlHeader = { "_memPool = &_commentPool; + p += xmlHeaderLen; + } + else if ( XMLUtil::StringEqual( p, commentHeader, commentHeaderLen ) ) { + returnNode = new (_commentPool.Alloc()) XMLComment( this ); + returnNode->_memPool = &_commentPool; + p += commentHeaderLen; + } + else if ( XMLUtil::StringEqual( p, cdataHeader, cdataHeaderLen ) ) { + XMLText* text = new (_textPool.Alloc()) XMLText( this ); + returnNode = text; + returnNode->_memPool = &_textPool; + p += cdataHeaderLen; + text->SetCData( true ); + } + else if ( XMLUtil::StringEqual( p, dtdHeader, dtdHeaderLen ) ) { + returnNode = new (_commentPool.Alloc()) XMLUnknown( this ); + returnNode->_memPool = &_commentPool; + p += dtdHeaderLen; + } + else if ( XMLUtil::StringEqual( p, elementHeader, elementHeaderLen ) ) { + returnNode = new (_elementPool.Alloc()) XMLElement( this ); + returnNode->_memPool = &_elementPool; + p += elementHeaderLen; + } + else { + returnNode = new (_textPool.Alloc()) XMLText( this ); + returnNode->_memPool = &_textPool; + p = start; // Back it up, all the text counts. + } + + *node = returnNode; + return p; +} + + +bool XMLDocument::Accept( XMLVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this ) ) { + for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) { + if ( !node->Accept( visitor ) ) { + break; + } + } + } + return visitor->VisitExit( *this ); +} + + +// --------- XMLNode ----------- // + +XMLNode::XMLNode( XMLDocument* doc ) : + _document( doc ), + _parent( 0 ), + _firstChild( 0 ), _lastChild( 0 ), + _prev( 0 ), _next( 0 ), + _memPool( 0 ) +{ +} + + +XMLNode::~XMLNode() +{ + DeleteChildren(); + if ( _parent ) { + _parent->Unlink( this ); + } +} + +const char* XMLNode::Value() const +{ + return _value.GetStr(); +} + +void XMLNode::SetValue( const char* str, bool staticMem ) +{ + if ( staticMem ) { + _value.SetInternedStr( str ); + } + else { + _value.SetStr( str ); + } +} + + +void XMLNode::DeleteChildren() +{ + while( _firstChild ) { + XMLNode* node = _firstChild; + Unlink( node ); + + DELETE_NODE( node ); + } + _firstChild = _lastChild = 0; +} + + +void XMLNode::Unlink( XMLNode* child ) +{ + if ( child == _firstChild ) { + _firstChild = _firstChild->_next; + } + if ( child == _lastChild ) { + _lastChild = _lastChild->_prev; + } + + if ( child->_prev ) { + child->_prev->_next = child->_next; + } + if ( child->_next ) { + child->_next->_prev = child->_prev; + } + child->_parent = 0; +} + + +void XMLNode::DeleteChild( XMLNode* node ) +{ + TIXMLASSERT( node->_parent == this ); + DELETE_NODE( node ); +} + + +XMLNode* XMLNode::InsertEndChild( XMLNode* addThis ) +{ + if (addThis->_document != _document) + return 0; + + if (addThis->_parent) + addThis->_parent->Unlink( addThis ); + else + addThis->_memPool->SetTracked(); + + if ( _lastChild ) { + TIXMLASSERT( _firstChild ); + TIXMLASSERT( _lastChild->_next == 0 ); + _lastChild->_next = addThis; + addThis->_prev = _lastChild; + _lastChild = addThis; + + addThis->_next = 0; + } + else { + TIXMLASSERT( _firstChild == 0 ); + _firstChild = _lastChild = addThis; + + addThis->_prev = 0; + addThis->_next = 0; + } + addThis->_parent = this; + return addThis; +} + + +XMLNode* XMLNode::InsertFirstChild( XMLNode* addThis ) +{ + if (addThis->_document != _document) + return 0; + + if (addThis->_parent) + addThis->_parent->Unlink( addThis ); + else + addThis->_memPool->SetTracked(); + + if ( _firstChild ) { + TIXMLASSERT( _lastChild ); + TIXMLASSERT( _firstChild->_prev == 0 ); + + _firstChild->_prev = addThis; + addThis->_next = _firstChild; + _firstChild = addThis; + + addThis->_prev = 0; + } + else { + TIXMLASSERT( _lastChild == 0 ); + _firstChild = _lastChild = addThis; + + addThis->_prev = 0; + addThis->_next = 0; + } + addThis->_parent = this; + return addThis; +} + + +XMLNode* XMLNode::InsertAfterChild( XMLNode* afterThis, XMLNode* addThis ) +{ + if (addThis->_document != _document) + return 0; + + TIXMLASSERT( afterThis->_parent == this ); + + if ( afterThis->_parent != this ) { + return 0; + } + + if ( afterThis->_next == 0 ) { + // The last node or the only node. + return InsertEndChild( addThis ); + } + if (addThis->_parent) + addThis->_parent->Unlink( addThis ); + else + addThis->_memPool->SetTracked(); + addThis->_prev = afterThis; + addThis->_next = afterThis->_next; + afterThis->_next->_prev = addThis; + afterThis->_next = addThis; + addThis->_parent = this; + return addThis; +} + + + + +const XMLElement* XMLNode::FirstChildElement( const char* value ) const +{ + for( XMLNode* node=_firstChild; node; node=node->_next ) { + XMLElement* element = node->ToElement(); + if ( element ) { + if ( !value || XMLUtil::StringEqual( element->Name(), value ) ) { + return element; + } + } + } + return 0; +} + + +const XMLElement* XMLNode::LastChildElement( const char* value ) const +{ + for( XMLNode* node=_lastChild; node; node=node->_prev ) { + XMLElement* element = node->ToElement(); + if ( element ) { + if ( !value || XMLUtil::StringEqual( element->Name(), value ) ) { + return element; + } + } + } + return 0; +} + + +const XMLElement* XMLNode::NextSiblingElement( const char* value ) const +{ + for( XMLNode* element=this->_next; element; element = element->_next ) { + if ( element->ToElement() + && (!value || XMLUtil::StringEqual( value, element->Value() ))) { + return element->ToElement(); + } + } + return 0; +} + + +const XMLElement* XMLNode::PreviousSiblingElement( const char* value ) const +{ + for( XMLNode* element=_prev; element; element = element->_prev ) { + if ( element->ToElement() + && (!value || XMLUtil::StringEqual( value, element->Value() ))) { + return element->ToElement(); + } + } + return 0; +} + + +char* XMLNode::ParseDeep( char* p, StrPair* parentEnd ) +{ + // This is a recursive method, but thinking about it "at the current level" + // it is a pretty simple flat list: + // + // + // + // With a special case: + // + // + // + // + // Where the closing element (/foo) *must* be the next thing after the opening + // element, and the names must match. BUT the tricky bit is that the closing + // element will be read by the child. + // + // 'endTag' is the end tag for this node, it is returned by a call to a child. + // 'parentEnd' is the end tag for the parent, which is filled in and returned. + + while( p && *p ) { + XMLNode* node = 0; + + p = _document->Identify( p, &node ); + if ( p == 0 || node == 0 ) { + break; + } + + StrPair endTag; + p = node->ParseDeep( p, &endTag ); + if ( !p ) { + DELETE_NODE( node ); + node = 0; + if ( !_document->Error() ) { + _document->SetError( XML_ERROR_PARSING, 0, 0 ); + } + break; + } + + // We read the end tag. Return it to the parent. + if ( node->ToElement() && node->ToElement()->ClosingType() == XMLElement::CLOSING ) { + if ( parentEnd ) { + *parentEnd = static_cast(node)->_value; + } + node->_memPool->SetTracked(); // created and then immediately deleted. + DELETE_NODE( node ); + return p; + } + + // Handle an end tag returned to this level. + // And handle a bunch of annoying errors. + XMLElement* ele = node->ToElement(); + if ( ele ) { + if ( endTag.Empty() && ele->ClosingType() == XMLElement::OPEN ) { + _document->SetError( XML_ERROR_MISMATCHED_ELEMENT, node->Value(), 0 ); + p = 0; + } + else if ( !endTag.Empty() && ele->ClosingType() != XMLElement::OPEN ) { + _document->SetError( XML_ERROR_MISMATCHED_ELEMENT, node->Value(), 0 ); + p = 0; + } + else if ( !endTag.Empty() ) { + if ( !XMLUtil::StringEqual( endTag.GetStr(), node->Value() )) { + _document->SetError( XML_ERROR_MISMATCHED_ELEMENT, node->Value(), 0 ); + p = 0; + } + } + } + if ( p == 0 ) { + DELETE_NODE( node ); + node = 0; + } + if ( node ) { + this->InsertEndChild( node ); + } + } + return 0; +} + +// --------- XMLText ---------- // +char* XMLText::ParseDeep( char* p, StrPair* ) +{ + const char* start = p; + if ( this->CData() ) { + p = _value.ParseText( p, "]]>", StrPair::NEEDS_NEWLINE_NORMALIZATION ); + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_CDATA, start, 0 ); + } + return p; + } + else { + int flags = _document->ProcessEntities() ? StrPair::TEXT_ELEMENT : StrPair::TEXT_ELEMENT_LEAVE_ENTITIES; + if ( _document->WhitespaceMode() == COLLAPSE_WHITESPACE ) { + flags |= StrPair::COLLAPSE_WHITESPACE; + } + + p = _value.ParseText( p, "<", flags ); + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_TEXT, start, 0 ); + } + if ( p && *p ) { + return p-1; + } + } + return 0; +} + + +XMLNode* XMLText::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLText* text = doc->NewText( Value() ); // fixme: this will always allocate memory. Intern? + text->SetCData( this->CData() ); + return text; +} + + +bool XMLText::ShallowEqual( const XMLNode* compare ) const +{ + return ( compare->ToText() && XMLUtil::StringEqual( compare->ToText()->Value(), Value() )); +} + + +bool XMLText::Accept( XMLVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +// --------- XMLComment ---------- // + +XMLComment::XMLComment( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLComment::~XMLComment() +{ +} + + +char* XMLComment::ParseDeep( char* p, StrPair* ) +{ + // Comment parses as text. + const char* start = p; + p = _value.ParseText( p, "-->", StrPair::COMMENT ); + if ( p == 0 ) { + _document->SetError( XML_ERROR_PARSING_COMMENT, start, 0 ); + } + return p; +} + + +XMLNode* XMLComment::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLComment* comment = doc->NewComment( Value() ); // fixme: this will always allocate memory. Intern? + return comment; +} + + +bool XMLComment::ShallowEqual( const XMLNode* compare ) const +{ + return ( compare->ToComment() && XMLUtil::StringEqual( compare->ToComment()->Value(), Value() )); +} + + +bool XMLComment::Accept( XMLVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +// --------- XMLDeclaration ---------- // + +XMLDeclaration::XMLDeclaration( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLDeclaration::~XMLDeclaration() +{ + //printf( "~XMLDeclaration\n" ); +} + + +char* XMLDeclaration::ParseDeep( char* p, StrPair* ) +{ + // Declaration parses as text. + const char* start = p; + p = _value.ParseText( p, "?>", StrPair::NEEDS_NEWLINE_NORMALIZATION ); + if ( p == 0 ) { + _document->SetError( XML_ERROR_PARSING_DECLARATION, start, 0 ); + } + return p; +} + + +XMLNode* XMLDeclaration::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLDeclaration* dec = doc->NewDeclaration( Value() ); // fixme: this will always allocate memory. Intern? + return dec; +} + + +bool XMLDeclaration::ShallowEqual( const XMLNode* compare ) const +{ + return ( compare->ToDeclaration() && XMLUtil::StringEqual( compare->ToDeclaration()->Value(), Value() )); +} + + + +bool XMLDeclaration::Accept( XMLVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + +// --------- XMLUnknown ---------- // + +XMLUnknown::XMLUnknown( XMLDocument* doc ) : XMLNode( doc ) +{ +} + + +XMLUnknown::~XMLUnknown() +{ +} + + +char* XMLUnknown::ParseDeep( char* p, StrPair* ) +{ + // Unknown parses as text. + const char* start = p; + + p = _value.ParseText( p, ">", StrPair::NEEDS_NEWLINE_NORMALIZATION ); + if ( !p ) { + _document->SetError( XML_ERROR_PARSING_UNKNOWN, start, 0 ); + } + return p; +} + + +XMLNode* XMLUnknown::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLUnknown* text = doc->NewUnknown( Value() ); // fixme: this will always allocate memory. Intern? + return text; +} + + +bool XMLUnknown::ShallowEqual( const XMLNode* compare ) const +{ + return ( compare->ToUnknown() && XMLUtil::StringEqual( compare->ToUnknown()->Value(), Value() )); +} + + +bool XMLUnknown::Accept( XMLVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + +// --------- XMLAttribute ---------- // + +const char* XMLAttribute::Name() const +{ + return _name.GetStr(); +} + +const char* XMLAttribute::Value() const +{ + return _value.GetStr(); +} + +char* XMLAttribute::ParseDeep( char* p, bool processEntities ) +{ + // Parse using the name rules: bug fix, was using ParseText before + p = _name.ParseName( p ); + if ( !p || !*p ) { + return 0; + } + + // Skip white space before = + p = XMLUtil::SkipWhiteSpace( p ); + if ( !p || *p != '=' ) { + return 0; + } + + ++p; // move up to opening quote + p = XMLUtil::SkipWhiteSpace( p ); + if ( *p != '\"' && *p != '\'' ) { + return 0; + } + + char endTag[2] = { *p, 0 }; + ++p; // move past opening quote + + p = _value.ParseText( p, endTag, processEntities ? StrPair::ATTRIBUTE_VALUE : StrPair::ATTRIBUTE_VALUE_LEAVE_ENTITIES ); + return p; +} + + +void XMLAttribute::SetName( const char* n ) +{ + _name.SetStr( n ); +} + + +XMLError XMLAttribute::QueryIntValue( int* value ) const +{ + if ( XMLUtil::ToInt( Value(), value )) { + return XML_NO_ERROR; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryUnsignedValue( unsigned int* value ) const +{ + if ( XMLUtil::ToUnsigned( Value(), value )) { + return XML_NO_ERROR; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryBoolValue( bool* value ) const +{ + if ( XMLUtil::ToBool( Value(), value )) { + return XML_NO_ERROR; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryFloatValue( float* value ) const +{ + if ( XMLUtil::ToFloat( Value(), value )) { + return XML_NO_ERROR; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +XMLError XMLAttribute::QueryDoubleValue( double* value ) const +{ + if ( XMLUtil::ToDouble( Value(), value )) { + return XML_NO_ERROR; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + +void XMLAttribute::SetAttribute( const char* v ) +{ + _value.SetStr( v ); +} + + +void XMLAttribute::SetAttribute( int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +void XMLAttribute::SetAttribute( unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +void XMLAttribute::SetAttribute( bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + +void XMLAttribute::SetAttribute( double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + +void XMLAttribute::SetAttribute( float v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + _value.SetStr( buf ); +} + + +// --------- XMLElement ---------- // +XMLElement::XMLElement( XMLDocument* doc ) : XMLNode( doc ), + _closingType( 0 ), + _rootAttribute( 0 ) +{ +} + + +XMLElement::~XMLElement() +{ + while( _rootAttribute ) { + XMLAttribute* next = _rootAttribute->_next; + DELETE_ATTRIBUTE( _rootAttribute ); + _rootAttribute = next; + } +} + + +XMLAttribute* XMLElement::FindAttribute( const char* name ) +{ + XMLAttribute* a = 0; + for( a=_rootAttribute; a; a = a->_next ) { + if ( XMLUtil::StringEqual( a->Name(), name ) ) { + return a; + } + } + return 0; +} + + +const XMLAttribute* XMLElement::FindAttribute( const char* name ) const +{ + XMLAttribute* a = 0; + for( a=_rootAttribute; a; a = a->_next ) { + if ( XMLUtil::StringEqual( a->Name(), name ) ) { + return a; + } + } + return 0; +} + + +const char* XMLElement::Attribute( const char* name, const char* value ) const +{ + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return 0; + } + if ( !value || XMLUtil::StringEqual( a->Value(), value )) { + return a->Value(); + } + return 0; +} + + +const char* XMLElement::GetText() const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + return FirstChild()->ToText()->Value(); + } + return 0; +} + + +void XMLElement::SetText( const char* inText ) +{ + if ( FirstChild() && FirstChild()->ToText() ) + FirstChild()->SetValue( inText ); + else { + XMLText* theText = GetDocument()->NewText( inText ); + InsertFirstChild( theText ); + } +} + + +void XMLElement::SetText( int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( float v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +void XMLElement::SetText( double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + SetText( buf ); +} + + +XMLError XMLElement::QueryIntText( int* ival ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->ToText()->Value(); + if ( XMLUtil::ToInt( t, ival ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryUnsignedText( unsigned* uval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->ToText()->Value(); + if ( XMLUtil::ToUnsigned( t, uval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryBoolText( bool* bval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->ToText()->Value(); + if ( XMLUtil::ToBool( t, bval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryDoubleText( double* dval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->ToText()->Value(); + if ( XMLUtil::ToDouble( t, dval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + +XMLError XMLElement::QueryFloatText( float* fval ) const +{ + if ( FirstChild() && FirstChild()->ToText() ) { + const char* t = FirstChild()->ToText()->Value(); + if ( XMLUtil::ToFloat( t, fval ) ) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + + +XMLAttribute* XMLElement::FindOrCreateAttribute( const char* name ) +{ + XMLAttribute* last = 0; + XMLAttribute* attrib = 0; + for( attrib = _rootAttribute; + attrib; + last = attrib, attrib = attrib->_next ) { + if ( XMLUtil::StringEqual( attrib->Name(), name ) ) { + break; + } + } + if ( !attrib ) { + attrib = new (_document->_attributePool.Alloc() ) XMLAttribute(); + attrib->_memPool = &_document->_attributePool; + if ( last ) { + last->_next = attrib; + } + else { + _rootAttribute = attrib; + } + attrib->SetName( name ); + attrib->_memPool->SetTracked(); // always created and linked. + } + return attrib; +} + + +void XMLElement::DeleteAttribute( const char* name ) +{ + XMLAttribute* prev = 0; + for( XMLAttribute* a=_rootAttribute; a; a=a->_next ) { + if ( XMLUtil::StringEqual( name, a->Name() ) ) { + if ( prev ) { + prev->_next = a->_next; + } + else { + _rootAttribute = a->_next; + } + DELETE_ATTRIBUTE( a ); + break; + } + prev = a; + } +} + + +char* XMLElement::ParseAttributes( char* p ) +{ + const char* start = p; + XMLAttribute* prevAttribute = 0; + + // Read the attributes. + while( p ) { + p = XMLUtil::SkipWhiteSpace( p ); + if ( !p || !(*p) ) { + _document->SetError( XML_ERROR_PARSING_ELEMENT, start, Name() ); + return 0; + } + + // attribute. + if (XMLUtil::IsNameStartChar( *p ) ) { + XMLAttribute* attrib = new (_document->_attributePool.Alloc() ) XMLAttribute(); + attrib->_memPool = &_document->_attributePool; + attrib->_memPool->SetTracked(); + + p = attrib->ParseDeep( p, _document->ProcessEntities() ); + if ( !p || Attribute( attrib->Name() ) ) { + DELETE_ATTRIBUTE( attrib ); + _document->SetError( XML_ERROR_PARSING_ATTRIBUTE, start, p ); + return 0; + } + // There is a minor bug here: if the attribute in the source xml + // document is duplicated, it will not be detected and the + // attribute will be doubly added. However, tracking the 'prevAttribute' + // avoids re-scanning the attribute list. Preferring performance for + // now, may reconsider in the future. + if ( prevAttribute ) { + prevAttribute->_next = attrib; + } + else { + _rootAttribute = attrib; + } + prevAttribute = attrib; + } + // end of the tag + else if ( *p == '/' && *(p+1) == '>' ) { + _closingType = CLOSED; + return p+2; // done; sealed element. + } + // end of the tag + else if ( *p == '>' ) { + ++p; + break; + } + else { + _document->SetError( XML_ERROR_PARSING_ELEMENT, start, p ); + return 0; + } + } + return p; +} + + +// +// +// foobar +// +char* XMLElement::ParseDeep( char* p, StrPair* strPair ) +{ + // Read the element name. + p = XMLUtil::SkipWhiteSpace( p ); + if ( !p ) { + return 0; + } + + // The closing element is the form. It is + // parsed just like a regular element then deleted from + // the DOM. + if ( *p == '/' ) { + _closingType = CLOSING; + ++p; + } + + p = _value.ParseName( p ); + if ( _value.Empty() ) { + return 0; + } + + p = ParseAttributes( p ); + if ( !p || !*p || _closingType ) { + return p; + } + + p = XMLNode::ParseDeep( p, strPair ); + return p; +} + + + +XMLNode* XMLElement::ShallowClone( XMLDocument* doc ) const +{ + if ( !doc ) { + doc = _document; + } + XMLElement* element = doc->NewElement( Value() ); // fixme: this will always allocate memory. Intern? + for( const XMLAttribute* a=FirstAttribute(); a; a=a->Next() ) { + element->SetAttribute( a->Name(), a->Value() ); // fixme: this will always allocate memory. Intern? + } + return element; +} + + +bool XMLElement::ShallowEqual( const XMLNode* compare ) const +{ + const XMLElement* other = compare->ToElement(); + if ( other && XMLUtil::StringEqual( other->Value(), Value() )) { + + const XMLAttribute* a=FirstAttribute(); + const XMLAttribute* b=other->FirstAttribute(); + + while ( a && b ) { + if ( !XMLUtil::StringEqual( a->Value(), b->Value() ) ) { + return false; + } + a = a->Next(); + b = b->Next(); + } + if ( a || b ) { + // different count + return false; + } + return true; + } + return false; +} + + +bool XMLElement::Accept( XMLVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this, _rootAttribute ) ) { + for ( const XMLNode* node=FirstChild(); node; node=node->NextSibling() ) { + if ( !node->Accept( visitor ) ) { + break; + } + } + } + return visitor->VisitExit( *this ); +} + + +// --------- XMLDocument ----------- // +XMLDocument::XMLDocument( bool processEntities, Whitespace whitespace ) : + XMLNode( 0 ), + _writeBOM( false ), + _processEntities( processEntities ), + _errorID( XML_NO_ERROR ), + _whitespace( whitespace ), + _errorStr1( 0 ), + _errorStr2( 0 ), + _charBuffer( 0 ) +{ + _document = this; // avoid warning about 'this' in initializer list +} + + +XMLDocument::~XMLDocument() +{ + DeleteChildren(); + delete [] _charBuffer; + +#if 0 + _textPool.Trace( "text" ); + _elementPool.Trace( "element" ); + _commentPool.Trace( "comment" ); + _attributePool.Trace( "attribute" ); +#endif + +#ifdef DEBUG + if ( Error() == false ) { + TIXMLASSERT( _elementPool.CurrentAllocs() == _elementPool.Untracked() ); + TIXMLASSERT( _attributePool.CurrentAllocs() == _attributePool.Untracked() ); + TIXMLASSERT( _textPool.CurrentAllocs() == _textPool.Untracked() ); + TIXMLASSERT( _commentPool.CurrentAllocs() == _commentPool.Untracked() ); + } +#endif +} + + +void XMLDocument::Clear() +{ + DeleteChildren(); + + _errorID = XML_NO_ERROR; + _errorStr1 = 0; + _errorStr2 = 0; + + delete [] _charBuffer; + _charBuffer = 0; +} + + +XMLElement* XMLDocument::NewElement( const char* name ) +{ + XMLElement* ele = new (_elementPool.Alloc()) XMLElement( this ); + ele->_memPool = &_elementPool; + ele->SetName( name ); + return ele; +} + + +XMLComment* XMLDocument::NewComment( const char* str ) +{ + XMLComment* comment = new (_commentPool.Alloc()) XMLComment( this ); + comment->_memPool = &_commentPool; + comment->SetValue( str ); + return comment; +} + + +XMLText* XMLDocument::NewText( const char* str ) +{ + XMLText* text = new (_textPool.Alloc()) XMLText( this ); + text->_memPool = &_textPool; + text->SetValue( str ); + return text; +} + + +XMLDeclaration* XMLDocument::NewDeclaration( const char* str ) +{ + XMLDeclaration* dec = new (_commentPool.Alloc()) XMLDeclaration( this ); + dec->_memPool = &_commentPool; + dec->SetValue( str ? str : "xml version=\"1.0\" encoding=\"UTF-8\"" ); + return dec; +} + + +XMLUnknown* XMLDocument::NewUnknown( const char* str ) +{ + XMLUnknown* unk = new (_commentPool.Alloc()) XMLUnknown( this ); + unk->_memPool = &_commentPool; + unk->SetValue( str ); + return unk; +} + + +XMLError XMLDocument::LoadFile( const char* filename ) +{ + Clear(); + FILE* fp = 0; + +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) + errno_t err = fopen_s(&fp, filename, "rb" ); + if ( !fp || err) { +#else + fp = fopen( filename, "rb" ); + if ( !fp) { +#endif + SetError( XML_ERROR_FILE_NOT_FOUND, filename, 0 ); + return _errorID; + } + LoadFile( fp ); + fclose( fp ); + return _errorID; +} + + +XMLError XMLDocument::LoadFile( FILE* fp ) +{ + Clear(); + + fseek( fp, 0, SEEK_SET ); + if ( fgetc( fp ) == EOF && ferror( fp ) != 0 ) { + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + + fseek( fp, 0, SEEK_END ); + size_t size = ftell( fp ); + fseek( fp, 0, SEEK_SET ); + + if ( size == 0 ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return _errorID; + } + + _charBuffer = new char[size+1]; + size_t read = fread( _charBuffer, 1, size, fp ); + if ( read != size ) { + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + return _errorID; + } + + _charBuffer[size] = 0; + + const char* p = _charBuffer; + p = XMLUtil::SkipWhiteSpace( p ); + p = XMLUtil::ReadBOM( p, &_writeBOM ); + if ( !p || !*p ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return _errorID; + } + + ParseDeep( _charBuffer + (p-_charBuffer), 0 ); + return _errorID; +} + + +XMLError XMLDocument::SaveFile( const char* filename, bool compact ) +{ + FILE* fp = 0; +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) + errno_t err = fopen_s(&fp, filename, "w" ); + if ( !fp || err) { +#else + fp = fopen( filename, "w" ); + if ( !fp) { +#endif + SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, filename, 0 ); + return _errorID; + } + SaveFile(fp, compact); + fclose( fp ); + return _errorID; +} + + +XMLError XMLDocument::SaveFile( FILE* fp, bool compact ) +{ + XMLPrinter stream( fp, compact ); + Print( &stream ); + return _errorID; +} + + +XMLError XMLDocument::Parse( const char* p, size_t len ) +{ + const char* start = p; + Clear(); + + if ( len == 0 || !p || !*p ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return _errorID; + } + if ( len == (size_t)(-1) ) { + len = strlen( p ); + } + _charBuffer = new char[ len+1 ]; + memcpy( _charBuffer, p, len ); + _charBuffer[len] = 0; + + p = XMLUtil::SkipWhiteSpace( p ); + p = XMLUtil::ReadBOM( p, &_writeBOM ); + if ( !p || !*p ) { + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + return _errorID; + } + + ptrdiff_t delta = p - start; // skip initial whitespace, BOM, etc. + ParseDeep( _charBuffer+delta, 0 ); + return _errorID; +} + + +void XMLDocument::Print( XMLPrinter* streamer ) const +{ + XMLPrinter stdStreamer( stdout ); + if ( !streamer ) { + streamer = &stdStreamer; + } + Accept( streamer ); +} + + +void XMLDocument::SetError( XMLError error, const char* str1, const char* str2 ) +{ + _errorID = error; + _errorStr1 = str1; + _errorStr2 = str2; +} + + +void XMLDocument::PrintError() const +{ + if ( _errorID ) { + static const int LEN = 20; + char buf1[LEN] = { 0 }; + char buf2[LEN] = { 0 }; + + if ( _errorStr1 ) { + TIXML_SNPRINTF( buf1, LEN, "%s", _errorStr1 ); + } + if ( _errorStr2 ) { + TIXML_SNPRINTF( buf2, LEN, "%s", _errorStr2 ); + } + + printf( "XMLDocument error id=%d str1=%s str2=%s\n", + _errorID, buf1, buf2 ); + } +} + + +XMLPrinter::XMLPrinter( FILE* file, bool compact, int depth ) : + _elementJustOpened( false ), + _firstElement( true ), + _fp( file ), + _depth( depth ), + _textDepth( -1 ), + _processEntities( true ), + _compactMode( compact ) +{ + for( int i=0; i'] = true; // not required, but consistency is nice + _buffer.Push( 0 ); +} + + +void XMLPrinter::Print( const char* format, ... ) +{ + va_list va; + va_start( va, format ); + + if ( _fp ) { + vfprintf( _fp, format, va ); + } + else { +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + #if defined(WINCE) + int len = 512; + do { + len = len*2; + char* str = new char[len](); + len = _vsnprintf(str, len, format, va); + delete[] str; + }while (len < 0); + #else + int len = _vscprintf( format, va ); + #endif +#else + int len = vsnprintf( 0, 0, format, va ); +#endif + // Close out and re-start the va-args + va_end( va ); + va_start( va, format ); + char* p = _buffer.PushArr( len ) - 1; // back up over the null terminator. +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + #if defined(WINCE) + _vsnprintf( p, len+1, format, va ); + #else + vsnprintf_s( p, len+1, _TRUNCATE, format, va ); + #endif +#else + vsnprintf( p, len+1, format, va ); +#endif + } + va_end( va ); +} + + +void XMLPrinter::PrintSpace( int depth ) +{ + for( int i=0; i 0 && *q < ENTITY_RANGE ) { + // Check for entities. If one is found, flush + // the stream up until the entity, write the + // entity, and keep looking. + if ( flag[(unsigned)(*q)] ) { + while ( p < q ) { + Print( "%c", *p ); + ++p; + } + for( int i=0; i 0) ) { + Print( "%s", p ); + } +} + + +void XMLPrinter::PushHeader( bool writeBOM, bool writeDec ) +{ + if ( writeBOM ) { + static const unsigned char bom[] = { TIXML_UTF_LEAD_0, TIXML_UTF_LEAD_1, TIXML_UTF_LEAD_2, 0 }; + Print( "%s", bom ); + } + if ( writeDec ) { + PushDeclaration( "xml version=\"1.0\"" ); + } +} + + +void XMLPrinter::OpenElement( const char* name, bool compactMode ) +{ + if ( _elementJustOpened ) { + SealElement(); + } + _stack.Push( name ); + + if ( _textDepth < 0 && !_firstElement && !compactMode ) { + Print( "\n" ); + } + if ( !compactMode ) { + PrintSpace( _depth ); + } + + Print( "<%s", name ); + _elementJustOpened = true; + _firstElement = false; + ++_depth; +} + + +void XMLPrinter::PushAttribute( const char* name, const char* value ) +{ + TIXMLASSERT( _elementJustOpened ); + Print( " %s=\"", name ); + PrintString( value, false ); + Print( "\"" ); +} + + +void XMLPrinter::PushAttribute( const char* name, int v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute( const char* name, unsigned v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute( const char* name, bool v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::PushAttribute( const char* name, double v ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( v, buf, BUF_SIZE ); + PushAttribute( name, buf ); +} + + +void XMLPrinter::CloseElement( bool compactMode ) +{ + --_depth; + const char* name = _stack.Pop(); + + if ( _elementJustOpened ) { + Print( "/>" ); + } + else { + if ( _textDepth < 0 && !compactMode) { + Print( "\n" ); + PrintSpace( _depth ); + } + Print( "", name ); + } + + if ( _textDepth == _depth ) { + _textDepth = -1; + } + if ( _depth == 0 && !compactMode) { + Print( "\n" ); + } + _elementJustOpened = false; +} + + +void XMLPrinter::SealElement() +{ + _elementJustOpened = false; + Print( ">" ); +} + + +void XMLPrinter::PushText( const char* text, bool cdata ) +{ + _textDepth = _depth-1; + + if ( _elementJustOpened ) { + SealElement(); + } + if ( cdata ) { + Print( "" ); + } + else { + PrintString( text, true ); + } +} + +void XMLPrinter::PushText( int value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( unsigned value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( bool value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( float value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushText( double value ) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr( value, buf, BUF_SIZE ); + PushText( buf, false ); +} + + +void XMLPrinter::PushComment( const char* comment ) +{ + if ( _elementJustOpened ) { + SealElement(); + } + if ( _textDepth < 0 && !_firstElement && !_compactMode) { + Print( "\n" ); + PrintSpace( _depth ); + } + _firstElement = false; + Print( "", comment ); +} + + +void XMLPrinter::PushDeclaration( const char* value ) +{ + if ( _elementJustOpened ) { + SealElement(); + } + if ( _textDepth < 0 && !_firstElement && !_compactMode) { + Print( "\n" ); + PrintSpace( _depth ); + } + _firstElement = false; + Print( "", value ); +} + + +void XMLPrinter::PushUnknown( const char* value ) +{ + if ( _elementJustOpened ) { + SealElement(); + } + if ( _textDepth < 0 && !_firstElement && !_compactMode) { + Print( "\n" ); + PrintSpace( _depth ); + } + _firstElement = false; + Print( "", value ); +} + + +bool XMLPrinter::VisitEnter( const XMLDocument& doc ) +{ + _processEntities = doc.ProcessEntities(); + if ( doc.HasBOM() ) { + PushHeader( true, false ); + } + return true; +} + + +bool XMLPrinter::VisitEnter( const XMLElement& element, const XMLAttribute* attribute ) +{ + const XMLElement* parentElem = element.Parent()->ToElement(); + bool compactMode = parentElem ? CompactMode(*parentElem) : _compactMode; + OpenElement( element.Name(), compactMode ); + while ( attribute ) { + PushAttribute( attribute->Name(), attribute->Value() ); + attribute = attribute->Next(); + } + return true; +} + + +bool XMLPrinter::VisitExit( const XMLElement& element ) +{ + CloseElement( CompactMode(element) ); + return true; +} + + +bool XMLPrinter::Visit( const XMLText& text ) +{ + PushText( text.Value(), text.CData() ); + return true; +} + + +bool XMLPrinter::Visit( const XMLComment& comment ) +{ + PushComment( comment.Value() ); + return true; +} + +bool XMLPrinter::Visit( const XMLDeclaration& declaration ) +{ + PushDeclaration( declaration.Value() ); + return true; +} + + +bool XMLPrinter::Visit( const XMLUnknown& unknown ) +{ + PushUnknown( unknown.Value() ); + return true; +} + +} // namespace tinyxml2 + diff --git a/skee/tinyxml2.h b/skee/tinyxml2.h new file mode 100644 index 0000000..c07ce4b --- /dev/null +++ b/skee/tinyxml2.h @@ -0,0 +1,2079 @@ +/* +Original code by Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#ifndef TINYXML2_INCLUDED +#define TINYXML2_INCLUDED + +#if defined(ANDROID_NDK) || defined(__BORLANDC__) +# include +# include +# include +# include +# include +# include +#else +# include +# include +# include +# include +# include +# include +#endif + +/* + TODO: intern strings instead of allocation. +*/ +/* + gcc: + g++ -Wall -DDEBUG tinyxml2.cpp xmltest.cpp -o gccxmltest.exe + + Formatting, Artistic Style: + AStyle.exe --style=1tbs --indent-switches --break-closing-brackets --indent-preprocessor tinyxml2.cpp tinyxml2.h +*/ + +#if defined( _DEBUG ) || defined( DEBUG ) || defined (__DEBUG__) +# ifndef DEBUG +# define DEBUG +# endif +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4251) +#endif + +#ifdef _WIN32 +# ifdef TINYXML2_EXPORT +# define TINYXML2_LIB __declspec(dllexport) +# elif defined(TINYXML2_IMPORT) +# define TINYXML2_LIB __declspec(dllimport) +# else +# define TINYXML2_LIB +# endif +#else +# define TINYXML2_LIB +#endif + + +#if defined(DEBUG) +# if defined(_MSC_VER) +# define TIXMLASSERT( x ) if ( !(x)) { __debugbreak(); } //if ( !(x)) WinDebugBreak() +# elif defined (ANDROID_NDK) +# include +# define TIXMLASSERT( x ) if ( !(x)) { __android_log_assert( "assert", "grinliz", "ASSERT in '%s' at %d.", __FILE__, __LINE__ ); } +# else +# include +# define TIXMLASSERT assert +# endif +# else +# define TIXMLASSERT( x ) {} +#endif + + +#if defined(_MSC_VER) && (_MSC_VER >= 1400 ) && (!defined WINCE) +// Microsoft visual studio, version 2005 and higher. +/*int _snprintf_s( + char *buffer, + size_t sizeOfBuffer, + size_t count, + const char *format [, + argument] ... +);*/ +inline int TIXML_SNPRINTF( char* buffer, size_t size, const char* format, ... ) +{ + va_list va; + va_start( va, format ); + int result = vsnprintf_s( buffer, size, _TRUNCATE, format, va ); + va_end( va ); + return result; +} +#define TIXML_SSCANF sscanf_s +#elif defined WINCE +#define TIXML_SNPRINTF _snprintf +#define TIXML_SSCANF sscanf +#else +// GCC version 3 and higher +//#warning( "Using sn* functions." ) +#define TIXML_SNPRINTF snprintf +#define TIXML_SSCANF sscanf +#endif + +/* Versioning, past 1.0.14: + http://semver.org/ +*/ +static const int TIXML2_MAJOR_VERSION = 2; +static const int TIXML2_MINOR_VERSION = 1; +static const int TIXML2_PATCH_VERSION = 0; + +namespace tinyxml2 +{ +class XMLDocument; +class XMLElement; +class XMLAttribute; +class XMLComment; +class XMLText; +class XMLDeclaration; +class XMLUnknown; +class XMLPrinter; + +/* + A class that wraps strings. Normally stores the start and end + pointers into the XML file itself, and will apply normalization + and entity translation if actually read. Can also store (and memory + manage) a traditional char[] +*/ +class StrPair +{ +public: + enum { + NEEDS_ENTITY_PROCESSING = 0x01, + NEEDS_NEWLINE_NORMALIZATION = 0x02, + COLLAPSE_WHITESPACE = 0x04, + + TEXT_ELEMENT = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + TEXT_ELEMENT_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, + ATTRIBUTE_NAME = 0, + ATTRIBUTE_VALUE = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + ATTRIBUTE_VALUE_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, + COMMENT = NEEDS_NEWLINE_NORMALIZATION + }; + + StrPair() : _flags( 0 ), _start( 0 ), _end( 0 ) {} + ~StrPair(); + + void Set( char* start, char* end, int flags ) { + Reset(); + _start = start; + _end = end; + _flags = flags | NEEDS_FLUSH; + } + + const char* GetStr(); + + bool Empty() const { + return _start == _end; + } + + void SetInternedStr( const char* str ) { + Reset(); + _start = const_cast(str); + } + + void SetStr( const char* str, int flags=0 ); + + char* ParseText( char* in, const char* endTag, int strFlags ); + char* ParseName( char* in ); + +private: + void Reset(); + void CollapseWhitespace(); + + enum { + NEEDS_FLUSH = 0x100, + NEEDS_DELETE = 0x200 + }; + + // After parsing, if *_end != 0, it can be set to zero. + int _flags; + char* _start; + char* _end; +}; + + +/* + A dynamic array of Plain Old Data. Doesn't support constructors, etc. + Has a small initial memory pool, so that low or no usage will not + cause a call to new/delete +*/ +template +class DynArray +{ +public: + DynArray< T, INIT >() { + _mem = _pool; + _allocated = INIT; + _size = 0; + } + + ~DynArray() { + if ( _mem != _pool ) { + delete [] _mem; + } + } + + void Clear() { + _size = 0; + } + + void Push( T t ) { + EnsureCapacity( _size+1 ); + _mem[_size++] = t; + } + + T* PushArr( int count ) { + EnsureCapacity( _size+count ); + T* ret = &_mem[_size]; + _size += count; + return ret; + } + + T Pop() { + return _mem[--_size]; + } + + void PopArr( int count ) { + TIXMLASSERT( _size >= count ); + _size -= count; + } + + bool Empty() const { + return _size == 0; + } + + T& operator[](int i) { + TIXMLASSERT( i>= 0 && i < _size ); + return _mem[i]; + } + + const T& operator[](int i) const { + TIXMLASSERT( i>= 0 && i < _size ); + return _mem[i]; + } + + const T& PeekTop() const { + TIXMLASSERT( _size > 0 ); + return _mem[ _size - 1]; + } + + int Size() const { + return _size; + } + + int Capacity() const { + return _allocated; + } + + const T* Mem() const { + return _mem; + } + + T* Mem() { + return _mem; + } + +private: + void EnsureCapacity( int cap ) { + if ( cap > _allocated ) { + int newAllocated = cap * 2; + T* newMem = new T[newAllocated]; + memcpy( newMem, _mem, sizeof(T)*_size ); // warning: not using constructors, only works for PODs + if ( _mem != _pool ) { + delete [] _mem; + } + _mem = newMem; + _allocated = newAllocated; + } + } + + T* _mem; + T _pool[INIT]; + int _allocated; // objects allocated + int _size; // number objects in use +}; + + +/* + Parent virtual class of a pool for fast allocation + and deallocation of objects. +*/ +class MemPool +{ +public: + MemPool() {} + virtual ~MemPool() {} + + virtual int ItemSize() const = 0; + virtual void* Alloc() = 0; + virtual void Free( void* ) = 0; + virtual void SetTracked() = 0; +}; + + +/* + Template child class to create pools of the correct type. +*/ +template< int SIZE > +class MemPoolT : public MemPool +{ +public: + MemPoolT() : _root(0), _currentAllocs(0), _nAllocs(0), _maxAllocs(0), _nUntracked(0) {} + ~MemPoolT() { + // Delete the blocks. + for( int i=0; i<_blockPtrs.Size(); ++i ) { + delete _blockPtrs[i]; + } + } + + virtual int ItemSize() const { + return SIZE; + } + int CurrentAllocs() const { + return _currentAllocs; + } + + virtual void* Alloc() { + if ( !_root ) { + // Need a new block. + Block* block = new Block(); + _blockPtrs.Push( block ); + + for( int i=0; ichunk[i].next = &block->chunk[i+1]; + } + block->chunk[COUNT-1].next = 0; + _root = block->chunk; + } + void* result = _root; + _root = _root->next; + + ++_currentAllocs; + if ( _currentAllocs > _maxAllocs ) { + _maxAllocs = _currentAllocs; + } + _nAllocs++; + _nUntracked++; + return result; + } + virtual void Free( void* mem ) { + if ( !mem ) { + return; + } + --_currentAllocs; + Chunk* chunk = (Chunk*)mem; +#ifdef DEBUG + memset( chunk, 0xfe, sizeof(Chunk) ); +#endif + chunk->next = _root; + _root = chunk; + } + void Trace( const char* name ) { + printf( "Mempool %s watermark=%d [%dk] current=%d size=%d nAlloc=%d blocks=%d\n", + name, _maxAllocs, _maxAllocs*SIZE/1024, _currentAllocs, SIZE, _nAllocs, _blockPtrs.Size() ); + } + + void SetTracked() { + _nUntracked--; + } + + int Untracked() const { + return _nUntracked; + } + + // This number is perf sensitive. 4k seems like a good tradeoff on my machine. + // The test file is large, 170k. + // Release: VS2010 gcc(no opt) + // 1k: 4000 + // 2k: 4000 + // 4k: 3900 21000 + // 16k: 5200 + // 32k: 4300 + // 64k: 4000 21000 + enum { COUNT = (4*1024)/SIZE }; // Some compilers do not accept to use COUNT in private part if COUNT is private + +private: + union Chunk { + Chunk* next; + char mem[SIZE]; + }; + struct Block { + Chunk chunk[COUNT]; + }; + DynArray< Block*, 10 > _blockPtrs; + Chunk* _root; + + int _currentAllocs; + int _nAllocs; + int _maxAllocs; + int _nUntracked; +}; + + + +/** + Implements the interface to the "Visitor pattern" (see the Accept() method.) + If you call the Accept() method, it requires being passed a XMLVisitor + class to handle callbacks. For nodes that contain other nodes (Document, Element) + you will get called with a VisitEnter/VisitExit pair. Nodes that are always leafs + are simply called with Visit(). + + If you return 'true' from a Visit method, recursive parsing will continue. If you return + false, no children of this node or its siblings will be visited. + + All flavors of Visit methods have a default implementation that returns 'true' (continue + visiting). You need to only override methods that are interesting to you. + + Generally Accept() is called on the XMLDocument, although all nodes support visiting. + + You should never change the document from a callback. + + @sa XMLNode::Accept() +*/ +class TINYXML2_LIB XMLVisitor +{ +public: + virtual ~XMLVisitor() {} + + /// Visit a document. + virtual bool VisitEnter( const XMLDocument& /*doc*/ ) { + return true; + } + /// Visit a document. + virtual bool VisitExit( const XMLDocument& /*doc*/ ) { + return true; + } + + /// Visit an element. + virtual bool VisitEnter( const XMLElement& /*element*/, const XMLAttribute* /*firstAttribute*/ ) { + return true; + } + /// Visit an element. + virtual bool VisitExit( const XMLElement& /*element*/ ) { + return true; + } + + /// Visit a declaration. + virtual bool Visit( const XMLDeclaration& /*declaration*/ ) { + return true; + } + /// Visit a text node. + virtual bool Visit( const XMLText& /*text*/ ) { + return true; + } + /// Visit a comment node. + virtual bool Visit( const XMLComment& /*comment*/ ) { + return true; + } + /// Visit an unknown node. + virtual bool Visit( const XMLUnknown& /*unknown*/ ) { + return true; + } +}; + + +/* + Utility functionality. +*/ +class XMLUtil +{ +public: + // Anything in the high order range of UTF-8 is assumed to not be whitespace. This isn't + // correct, but simple, and usually works. + static const char* SkipWhiteSpace( const char* p ) { + while( !IsUTF8Continuation(*p) && isspace( *reinterpret_cast(p) ) ) { + ++p; + } + return p; + } + static char* SkipWhiteSpace( char* p ) { + while( !IsUTF8Continuation(*p) && isspace( *reinterpret_cast(p) ) ) { + ++p; + } + return p; + } + static bool IsWhiteSpace( char p ) { + return !IsUTF8Continuation(p) && isspace( static_cast(p) ); + } + + inline static bool IsNameStartChar( unsigned char ch ) { + return ( ( ch < 128 ) ? isalpha( ch ) : 1 ) + || ch == ':' + || ch == '_'; + } + + inline static bool IsNameChar( unsigned char ch ) { + return IsNameStartChar( ch ) + || isdigit( ch ) + || ch == '.' + || ch == '-'; + } + + inline static bool StringEqual( const char* p, const char* q, int nChar=INT_MAX ) { + int n = 0; + if ( p == q ) { + return true; + } + while( *p && *q && *p == *q && n(const_cast(this)->FirstChildElement( value )); + } + + /// Get the last child node, or null if none exists. + const XMLNode* LastChild() const { + return _lastChild; + } + + XMLNode* LastChild() { + return const_cast(const_cast(this)->LastChild() ); + } + + /** Get the last child element or optionally the last child + element with the specified name. + */ + const XMLElement* LastChildElement( const char* value=0 ) const; + + XMLElement* LastChildElement( const char* value=0 ) { + return const_cast(const_cast(this)->LastChildElement(value) ); + } + + /// Get the previous (left) sibling node of this node. + const XMLNode* PreviousSibling() const { + return _prev; + } + + XMLNode* PreviousSibling() { + return _prev; + } + + /// Get the previous (left) sibling element of this node, with an optionally supplied name. + const XMLElement* PreviousSiblingElement( const char* value=0 ) const ; + + XMLElement* PreviousSiblingElement( const char* value=0 ) { + return const_cast(const_cast(this)->PreviousSiblingElement( value ) ); + } + + /// Get the next (right) sibling node of this node. + const XMLNode* NextSibling() const { + return _next; + } + + XMLNode* NextSibling() { + return _next; + } + + /// Get the next (right) sibling element of this node, with an optionally supplied name. + const XMLElement* NextSiblingElement( const char* value=0 ) const; + + XMLElement* NextSiblingElement( const char* value=0 ) { + return const_cast(const_cast(this)->NextSiblingElement( value ) ); + } + + /** + Add a child node as the last (right) child. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the node does not + belong to the same document. + */ + XMLNode* InsertEndChild( XMLNode* addThis ); + + XMLNode* LinkEndChild( XMLNode* addThis ) { + return InsertEndChild( addThis ); + } + /** + Add a child node as the first (left) child. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the node does not + belong to the same document. + */ + XMLNode* InsertFirstChild( XMLNode* addThis ); + /** + Add a node after the specified child node. + If the child node is already part of the document, + it is moved from its old location to the new location. + Returns the addThis argument or 0 if the afterThis node + is not a child of this node, or if the node does not + belong to the same document. + */ + XMLNode* InsertAfterChild( XMLNode* afterThis, XMLNode* addThis ); + + /** + Delete all the children of this node. + */ + void DeleteChildren(); + + /** + Delete a child of this node. + */ + void DeleteChild( XMLNode* node ); + + /** + Make a copy of this node, but not its children. + You may pass in a Document pointer that will be + the owner of the new Node. If the 'document' is + null, then the node returned will be allocated + from the current Document. (this->GetDocument()) + + Note: if called on a XMLDocument, this will return null. + */ + virtual XMLNode* ShallowClone( XMLDocument* document ) const = 0; + + /** + Test if 2 nodes are the same, but don't test children. + The 2 nodes do not need to be in the same Document. + + Note: if called on a XMLDocument, this will return false. + */ + virtual bool ShallowEqual( const XMLNode* compare ) const = 0; + + /** Accept a hierarchical visit of the nodes in the TinyXML-2 DOM. Every node in the + XML tree will be conditionally visited and the host will be called back + via the XMLVisitor interface. + + This is essentially a SAX interface for TinyXML-2. (Note however it doesn't re-parse + the XML for the callbacks, so the performance of TinyXML-2 is unchanged by using this + interface versus any other.) + + The interface has been based on ideas from: + + - http://www.saxproject.org/ + - http://c2.com/cgi/wiki?HierarchicalVisitorPattern + + Which are both good references for "visiting". + + An example of using Accept(): + @verbatim + XMLPrinter printer; + tinyxmlDoc.Accept( &printer ); + const char* xmlcstr = printer.CStr(); + @endverbatim + */ + virtual bool Accept( XMLVisitor* visitor ) const = 0; + + // internal + virtual char* ParseDeep( char*, StrPair* ); + +protected: + XMLNode( XMLDocument* ); + virtual ~XMLNode(); + XMLNode( const XMLNode& ); // not supported + XMLNode& operator=( const XMLNode& ); // not supported + + XMLDocument* _document; + XMLNode* _parent; + mutable StrPair _value; + + XMLNode* _firstChild; + XMLNode* _lastChild; + + XMLNode* _prev; + XMLNode* _next; + +private: + MemPool* _memPool; + void Unlink( XMLNode* child ); +}; + + +/** XML text. + + Note that a text node can have child element nodes, for example: + @verbatim + This is bold + @endverbatim + + A text node can have 2 ways to output the next. "normal" output + and CDATA. It will default to the mode it was parsed from the XML file and + you generally want to leave it alone, but you can change the output mode with + SetCData() and query it with CData(). +*/ +class TINYXML2_LIB XMLText : public XMLNode +{ + friend class XMLBase; + friend class XMLDocument; +public: + virtual bool Accept( XMLVisitor* visitor ) const; + + virtual XMLText* ToText() { + return this; + } + virtual const XMLText* ToText() const { + return this; + } + + /// Declare whether this should be CDATA or standard text. + void SetCData( bool isCData ) { + _isCData = isCData; + } + /// Returns true if this is a CDATA text element. + bool CData() const { + return _isCData; + } + + char* ParseDeep( char*, StrPair* endTag ); + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + XMLText( XMLDocument* doc ) : XMLNode( doc ), _isCData( false ) {} + virtual ~XMLText() {} + XMLText( const XMLText& ); // not supported + XMLText& operator=( const XMLText& ); // not supported + +private: + bool _isCData; +}; + + +/** An XML Comment. */ +class TINYXML2_LIB XMLComment : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLComment* ToComment() { + return this; + } + virtual const XMLComment* ToComment() const { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const; + + char* ParseDeep( char*, StrPair* endTag ); + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + XMLComment( XMLDocument* doc ); + virtual ~XMLComment(); + XMLComment( const XMLComment& ); // not supported + XMLComment& operator=( const XMLComment& ); // not supported + +private: +}; + + +/** In correct XML the declaration is the first entry in the file. + @verbatim + + @endverbatim + + TinyXML-2 will happily read or write files without a declaration, + however. + + The text of the declaration isn't interpreted. It is parsed + and written as a string. +*/ +class TINYXML2_LIB XMLDeclaration : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLDeclaration* ToDeclaration() { + return this; + } + virtual const XMLDeclaration* ToDeclaration() const { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const; + + char* ParseDeep( char*, StrPair* endTag ); + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + XMLDeclaration( XMLDocument* doc ); + virtual ~XMLDeclaration(); + XMLDeclaration( const XMLDeclaration& ); // not supported + XMLDeclaration& operator=( const XMLDeclaration& ); // not supported +}; + + +/** Any tag that TinyXML-2 doesn't recognize is saved as an + unknown. It is a tag of text, but should not be modified. + It will be written back to the XML, unchanged, when the file + is saved. + + DTD tags get thrown into XMLUnknowns. +*/ +class TINYXML2_LIB XMLUnknown : public XMLNode +{ + friend class XMLDocument; +public: + virtual XMLUnknown* ToUnknown() { + return this; + } + virtual const XMLUnknown* ToUnknown() const { + return this; + } + + virtual bool Accept( XMLVisitor* visitor ) const; + + char* ParseDeep( char*, StrPair* endTag ); + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +protected: + XMLUnknown( XMLDocument* doc ); + virtual ~XMLUnknown(); + XMLUnknown( const XMLUnknown& ); // not supported + XMLUnknown& operator=( const XMLUnknown& ); // not supported +}; + + +enum XMLError { + XML_NO_ERROR = 0, + XML_SUCCESS = 0, + + XML_NO_ATTRIBUTE, + XML_WRONG_ATTRIBUTE_TYPE, + + XML_ERROR_FILE_NOT_FOUND, + XML_ERROR_FILE_COULD_NOT_BE_OPENED, + XML_ERROR_FILE_READ_ERROR, + XML_ERROR_ELEMENT_MISMATCH, + XML_ERROR_PARSING_ELEMENT, + XML_ERROR_PARSING_ATTRIBUTE, + XML_ERROR_IDENTIFYING_TAG, + XML_ERROR_PARSING_TEXT, + XML_ERROR_PARSING_CDATA, + XML_ERROR_PARSING_COMMENT, + XML_ERROR_PARSING_DECLARATION, + XML_ERROR_PARSING_UNKNOWN, + XML_ERROR_EMPTY_DOCUMENT, + XML_ERROR_MISMATCHED_ELEMENT, + XML_ERROR_PARSING, + + XML_CAN_NOT_CONVERT_TEXT, + XML_NO_TEXT_NODE +}; + + +/** An attribute is a name-value pair. Elements have an arbitrary + number of attributes, each with a unique name. + + @note The attributes are not XMLNodes. You may only query the + Next() attribute in a list. +*/ +class TINYXML2_LIB XMLAttribute +{ + friend class XMLElement; +public: + /// The name of the attribute. + const char* Name() const; + + /// The value of the attribute. + const char* Value() const; + + /// The next attribute in the list. + const XMLAttribute* Next() const { + return _next; + } + + /** IntValue interprets the attribute as an integer, and returns the value. + If the value isn't an integer, 0 will be returned. There is no error checking; + use QueryIntValue() if you need error checking. + */ + int IntValue() const { + int i=0; + QueryIntValue( &i ); + return i; + } + /// Query as an unsigned integer. See IntValue() + unsigned UnsignedValue() const { + unsigned i=0; + QueryUnsignedValue( &i ); + return i; + } + /// Query as a boolean. See IntValue() + bool BoolValue() const { + bool b=false; + QueryBoolValue( &b ); + return b; + } + /// Query as a double. See IntValue() + double DoubleValue() const { + double d=0; + QueryDoubleValue( &d ); + return d; + } + /// Query as a float. See IntValue() + float FloatValue() const { + float f=0; + QueryFloatValue( &f ); + return f; + } + + /** QueryIntValue interprets the attribute as an integer, and returns the value + in the provided parameter. The function will return XML_NO_ERROR on success, + and XML_WRONG_ATTRIBUTE_TYPE if the conversion is not successful. + */ + XMLError QueryIntValue( int* value ) const; + /// See QueryIntValue + XMLError QueryUnsignedValue( unsigned int* value ) const; + /// See QueryIntValue + XMLError QueryBoolValue( bool* value ) const; + /// See QueryIntValue + XMLError QueryDoubleValue( double* value ) const; + /// See QueryIntValue + XMLError QueryFloatValue( float* value ) const; + + /// Set the attribute to a string value. + void SetAttribute( const char* value ); + /// Set the attribute to value. + void SetAttribute( int value ); + /// Set the attribute to value. + void SetAttribute( unsigned value ); + /// Set the attribute to value. + void SetAttribute( bool value ); + /// Set the attribute to value. + void SetAttribute( double value ); + /// Set the attribute to value. + void SetAttribute( float value ); + +private: + enum { BUF_SIZE = 200 }; + + XMLAttribute() : _next( 0 ), _memPool( 0 ) {} + virtual ~XMLAttribute() {} + + XMLAttribute( const XMLAttribute& ); // not supported + void operator=( const XMLAttribute& ); // not supported + void SetName( const char* name ); + + char* ParseDeep( char* p, bool processEntities ); + + mutable StrPair _name; + mutable StrPair _value; + XMLAttribute* _next; + MemPool* _memPool; +}; + + +/** The element is a container class. It has a value, the element name, + and can contain other elements, text, comments, and unknowns. + Elements also contain an arbitrary number of attributes. +*/ +class TINYXML2_LIB XMLElement : public XMLNode +{ + friend class XMLBase; + friend class XMLDocument; +public: + /// Get the name of an element (which is the Value() of the node.) + const char* Name() const { + return Value(); + } + /// Set the name of the element. + void SetName( const char* str, bool staticMem=false ) { + SetValue( str, staticMem ); + } + + virtual XMLElement* ToElement() { + return this; + } + virtual const XMLElement* ToElement() const { + return this; + } + virtual bool Accept( XMLVisitor* visitor ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none + exists. For example: + + @verbatim + const char* value = ele->Attribute( "foo" ); + @endverbatim + + The 'value' parameter is normally null. However, if specified, + the attribute will only be returned if the 'name' and 'value' + match. This allow you to write code: + + @verbatim + if ( ele->Attribute( "foo", "bar" ) ) callFooIsBar(); + @endverbatim + + rather than: + @verbatim + if ( ele->Attribute( "foo" ) ) { + if ( strcmp( ele->Attribute( "foo" ), "bar" ) == 0 ) callFooIsBar(); + } + @endverbatim + */ + const char* Attribute( const char* name, const char* value=0 ) const; + + /** Given an attribute name, IntAttribute() returns the value + of the attribute interpreted as an integer. 0 will be + returned if there is an error. For a method with error + checking, see QueryIntAttribute() + */ + int IntAttribute( const char* name ) const { + int i=0; + QueryIntAttribute( name, &i ); + return i; + } + /// See IntAttribute() + unsigned UnsignedAttribute( const char* name ) const { + unsigned i=0; + QueryUnsignedAttribute( name, &i ); + return i; + } + /// See IntAttribute() + bool BoolAttribute( const char* name ) const { + bool b=false; + QueryBoolAttribute( name, &b ); + return b; + } + /// See IntAttribute() + double DoubleAttribute( const char* name ) const { + double d=0; + QueryDoubleAttribute( name, &d ); + return d; + } + /// See IntAttribute() + float FloatAttribute( const char* name ) const { + float f=0; + QueryFloatAttribute( name, &f ); + return f; + } + + /** Given an attribute name, QueryIntAttribute() returns + XML_NO_ERROR, XML_WRONG_ATTRIBUTE_TYPE if the conversion + can't be performed, or XML_NO_ATTRIBUTE if the attribute + doesn't exist. If successful, the result of the conversion + will be written to 'value'. If not successful, nothing will + be written to 'value'. This allows you to provide default + value: + + @verbatim + int value = 10; + QueryIntAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 + @endverbatim + */ + XMLError QueryIntAttribute( const char* name, int* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryIntValue( value ); + } + /// See QueryIntAttribute() + XMLError QueryUnsignedAttribute( const char* name, unsigned int* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryUnsignedValue( value ); + } + /// See QueryIntAttribute() + XMLError QueryBoolAttribute( const char* name, bool* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryBoolValue( value ); + } + /// See QueryIntAttribute() + XMLError QueryDoubleAttribute( const char* name, double* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryDoubleValue( value ); + } + /// See QueryIntAttribute() + XMLError QueryFloatAttribute( const char* name, float* value ) const { + const XMLAttribute* a = FindAttribute( name ); + if ( !a ) { + return XML_NO_ATTRIBUTE; + } + return a->QueryFloatValue( value ); + } + + + /** Given an attribute name, QueryAttribute() returns + XML_NO_ERROR, XML_WRONG_ATTRIBUTE_TYPE if the conversion + can't be performed, or XML_NO_ATTRIBUTE if the attribute + doesn't exist. It is overloaded for the primitive types, + and is a generally more convenient replacement of + QueryIntAttribute() and related functions. + + If successful, the result of the conversion + will be written to 'value'. If not successful, nothing will + be written to 'value'. This allows you to provide default + value: + + @verbatim + int value = 10; + QueryAttribute( "foo", &value ); // if "foo" isn't found, value will still be 10 + @endverbatim + */ + int QueryAttribute( const char* name, int* value ) const { + return QueryIntAttribute( name, value ); + } + + int QueryAttribute( const char* name, unsigned int* value ) const { + return QueryUnsignedAttribute( name, value ); + } + + int QueryAttribute( const char* name, bool* value ) const { + return QueryBoolAttribute( name, value ); + } + + int QueryAttribute( const char* name, double* value ) const { + return QueryDoubleAttribute( name, value ); + } + + int QueryAttribute( const char* name, float* value ) const { + return QueryFloatAttribute( name, value ); + } + + /// Sets the named attribute to value. + void SetAttribute( const char* name, const char* value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, int value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, unsigned value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, bool value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, double value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + /// Sets the named attribute to value. + void SetAttribute( const char* name, float value ) { + XMLAttribute* a = FindOrCreateAttribute( name ); + a->SetAttribute( value ); + } + + /** + Delete an attribute. + */ + void DeleteAttribute( const char* name ); + + /// Return the first attribute in the list. + const XMLAttribute* FirstAttribute() const { + return _rootAttribute; + } + /// Query a specific attribute in the list. + const XMLAttribute* FindAttribute( const char* name ) const; + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, GetText() is limited compared to getting the XMLText child + and accessing it directly. + + If the first child of 'this' is a XMLText, the GetText() + returns the character string of the Text node, else null is returned. + + This is a convenient method for getting the text of simple contained text: + @verbatim + This is text + const char* str = fooElement->GetText(); + @endverbatim + + 'str' will be a pointer to "This is text". + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then the value of str would be null. The first child node isn't a text node, it is + another element. From this XML: + @verbatim + This is text + @endverbatim + GetText() will return "This is ". + */ + const char* GetText() const; + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, SetText() is limited compared to creating an XMLText child + and mutating it directly. + + If the first child of 'this' is a XMLText, SetText() sets its value to + the given string, otherwise it will create a first child that is an XMLText. + + This is a convenient method for setting the text of simple contained text: + @verbatim + This is text + fooElement->SetText( "Hullaballoo!" ); + Hullaballoo! + @endverbatim + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + This is text + @endverbatim + + then it will not change "This is text", but rather prefix it with a text element: + @verbatim + Hullaballoo!This is text + @endverbatim + + For this XML: + @verbatim + + @endverbatim + SetText() will generate + @verbatim + Hullaballoo! + @endverbatim + */ + void SetText( const char* inText ); + /// Convenience method for setting text inside and element. See SetText() for important limitations. + void SetText( int value ); + /// Convenience method for setting text inside and element. See SetText() for important limitations. + void SetText( unsigned value ); + /// Convenience method for setting text inside and element. See SetText() for important limitations. + void SetText( bool value ); + /// Convenience method for setting text inside and element. See SetText() for important limitations. + void SetText( double value ); + /// Convenience method for setting text inside and element. See SetText() for important limitations. + void SetText( float value ); + + /** + Convenience method to query the value of a child text node. This is probably best + shown by example. Given you have a document is this form: + @verbatim + + 1 + 1.4 + + @endverbatim + + The QueryIntText() and similar functions provide a safe and easier way to get to the + "value" of x and y. + + @verbatim + int x = 0; + float y = 0; // types of x and y are contrived for example + const XMLElement* xElement = pointElement->FirstChildElement( "x" ); + const XMLElement* yElement = pointElement->FirstChildElement( "y" ); + xElement->QueryIntText( &x ); + yElement->QueryFloatText( &y ); + @endverbatim + + @returns XML_SUCCESS (0) on success, XML_CAN_NOT_CONVERT_TEXT if the text cannot be converted + to the requested type, and XML_NO_TEXT_NODE if there is no child text to query. + + */ + XMLError QueryIntText( int* ival ) const; + /// See QueryIntText() + XMLError QueryUnsignedText( unsigned* uval ) const; + /// See QueryIntText() + XMLError QueryBoolText( bool* bval ) const; + /// See QueryIntText() + XMLError QueryDoubleText( double* dval ) const; + /// See QueryIntText() + XMLError QueryFloatText( float* fval ) const; + + // internal: + enum { + OPEN, // + CLOSED, // + CLOSING // + }; + int ClosingType() const { + return _closingType; + } + char* ParseDeep( char* p, StrPair* endTag ); + virtual XMLNode* ShallowClone( XMLDocument* document ) const; + virtual bool ShallowEqual( const XMLNode* compare ) const; + +private: + XMLElement( XMLDocument* doc ); + virtual ~XMLElement(); + XMLElement( const XMLElement& ); // not supported + void operator=( const XMLElement& ); // not supported + + XMLAttribute* FindAttribute( const char* name ); + XMLAttribute* FindOrCreateAttribute( const char* name ); + //void LinkAttribute( XMLAttribute* attrib ); + char* ParseAttributes( char* p ); + + enum { BUF_SIZE = 200 }; + int _closingType; + // The attribute list is ordered; there is no 'lastAttribute' + // because the list needs to be scanned for dupes before adding + // a new attribute. + XMLAttribute* _rootAttribute; +}; + + +enum Whitespace { + PRESERVE_WHITESPACE, + COLLAPSE_WHITESPACE +}; + + +/** A Document binds together all the functionality. + It can be saved, loaded, and printed to the screen. + All Nodes are connected and allocated to a Document. + If the Document is deleted, all its Nodes are also deleted. +*/ +class TINYXML2_LIB XMLDocument : public XMLNode +{ + friend class XMLElement; +public: + /// constructor + XMLDocument( bool processEntities = true, Whitespace = PRESERVE_WHITESPACE ); + ~XMLDocument(); + + virtual XMLDocument* ToDocument() { + return this; + } + virtual const XMLDocument* ToDocument() const { + return this; + } + + /** + Parse an XML file from a character string. + Returns XML_NO_ERROR (0) on success, or + an errorID. + + You may optionally pass in the 'nBytes', which is + the number of bytes which will be parsed. If not + specified, TinyXML-2 will assume 'xml' points to a + null terminated string. + */ + XMLError Parse( const char* xml, size_t nBytes=(size_t)(-1) ); + + /** + Load an XML file from disk. + Returns XML_NO_ERROR (0) on success, or + an errorID. + */ + XMLError LoadFile( const char* filename ); + + /** + Load an XML file from disk. You are responsible + for providing and closing the FILE*. + + Returns XML_NO_ERROR (0) on success, or + an errorID. + */ + XMLError LoadFile( FILE* ); + + /** + Save the XML file to disk. + Returns XML_NO_ERROR (0) on success, or + an errorID. + */ + XMLError SaveFile( const char* filename, bool compact = false ); + + /** + Save the XML file to disk. You are responsible + for providing and closing the FILE*. + + Returns XML_NO_ERROR (0) on success, or + an errorID. + */ + XMLError SaveFile( FILE* fp, bool compact = false ); + + bool ProcessEntities() const { + return _processEntities; + } + Whitespace WhitespaceMode() const { + return _whitespace; + } + + /** + Returns true if this document has a leading Byte Order Mark of UTF8. + */ + bool HasBOM() const { + return _writeBOM; + } + /** Sets whether to write the BOM when writing the file. + */ + void SetBOM( bool useBOM ) { + _writeBOM = useBOM; + } + + /** Return the root element of DOM. Equivalent to FirstChildElement(). + To get the first node, use FirstChild(). + */ + XMLElement* RootElement() { + return FirstChildElement(); + } + const XMLElement* RootElement() const { + return FirstChildElement(); + } + + /** Print the Document. If the Printer is not provided, it will + print to stdout. If you provide Printer, this can print to a file: + @verbatim + XMLPrinter printer( fp ); + doc.Print( &printer ); + @endverbatim + + Or you can use a printer to print to memory: + @verbatim + XMLPrinter printer; + doc.Print( &printer ); + // printer.CStr() has a const char* to the XML + @endverbatim + */ + void Print( XMLPrinter* streamer=0 ) const; + virtual bool Accept( XMLVisitor* visitor ) const; + + /** + Create a new Element associated with + this Document. The memory for the Element + is managed by the Document. + */ + XMLElement* NewElement( const char* name ); + /** + Create a new Comment associated with + this Document. The memory for the Comment + is managed by the Document. + */ + XMLComment* NewComment( const char* comment ); + /** + Create a new Text associated with + this Document. The memory for the Text + is managed by the Document. + */ + XMLText* NewText( const char* text ); + /** + Create a new Declaration associated with + this Document. The memory for the object + is managed by the Document. + + If the 'text' param is null, the standard + declaration is used.: + @verbatim + + @endverbatim + */ + XMLDeclaration* NewDeclaration( const char* text=0 ); + /** + Create a new Unknown associated with + this Document. The memory for the object + is managed by the Document. + */ + XMLUnknown* NewUnknown( const char* text ); + + /** + Delete a node associated with this document. + It will be unlinked from the DOM. + */ + void DeleteNode( XMLNode* node ) { + node->_parent->DeleteChild( node ); + } + + void SetError( XMLError error, const char* str1, const char* str2 ); + + /// Return true if there was an error parsing the document. + bool Error() const { + return _errorID != XML_NO_ERROR; + } + /// Return the errorID. + XMLError ErrorID() const { + return _errorID; + } + /// Return a possibly helpful diagnostic location or string. + const char* GetErrorStr1() const { + return _errorStr1; + } + /// Return a possibly helpful secondary diagnostic location or string. + const char* GetErrorStr2() const { + return _errorStr2; + } + /// If there is an error, print it to stdout. + void PrintError() const; + + /// Clear the document, resetting it to the initial state. + void Clear(); + + // internal + char* Identify( char* p, XMLNode** node ); + + virtual XMLNode* ShallowClone( XMLDocument* /*document*/ ) const { + return 0; + } + virtual bool ShallowEqual( const XMLNode* /*compare*/ ) const { + return false; + } + +private: + XMLDocument( const XMLDocument& ); // not supported + void operator=( const XMLDocument& ); // not supported + + bool _writeBOM; + bool _processEntities; + XMLError _errorID; + Whitespace _whitespace; + const char* _errorStr1; + const char* _errorStr2; + char* _charBuffer; + + MemPoolT< sizeof(XMLElement) > _elementPool; + MemPoolT< sizeof(XMLAttribute) > _attributePool; + MemPoolT< sizeof(XMLText) > _textPool; + MemPoolT< sizeof(XMLComment) > _commentPool; +}; + + +/** + A XMLHandle is a class that wraps a node pointer with null checks; this is + an incredibly useful thing. Note that XMLHandle is not part of the TinyXML-2 + DOM structure. It is a separate utility class. + + Take an example: + @verbatim + + + + + + + @endverbatim + + Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very + easy to write a *lot* of code that looks like: + + @verbatim + XMLElement* root = document.FirstChildElement( "Document" ); + if ( root ) + { + XMLElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + XMLElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + XMLElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. + @endverbatim + + And that doesn't even cover "else" cases. XMLHandle addresses the verbosity + of such code. A XMLHandle checks for null pointers so it is perfectly safe + and correct to use: + + @verbatim + XMLHandle docHandle( &document ); + XMLElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild().NextSibling().ToElement(); + if ( child2 ) + { + // do something useful + @endverbatim + + Which is MUCH more concise and useful. + + It is also safe to copy handles - internally they are nothing more than node pointers. + @verbatim + XMLHandle handleCopy = handle; + @endverbatim + + See also XMLConstHandle, which is the same as XMLHandle, but operates on const objects. +*/ +class TINYXML2_LIB XMLHandle +{ +public: + /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. + XMLHandle( XMLNode* node ) { + _node = node; + } + /// Create a handle from a node. + XMLHandle( XMLNode& node ) { + _node = &node; + } + /// Copy constructor + XMLHandle( const XMLHandle& ref ) { + _node = ref._node; + } + /// Assignment + XMLHandle& operator=( const XMLHandle& ref ) { + _node = ref._node; + return *this; + } + + /// Get the first child of this handle. + XMLHandle FirstChild() { + return XMLHandle( _node ? _node->FirstChild() : 0 ); + } + /// Get the first child element of this handle. + XMLHandle FirstChildElement( const char* value=0 ) { + return XMLHandle( _node ? _node->FirstChildElement( value ) : 0 ); + } + /// Get the last child of this handle. + XMLHandle LastChild() { + return XMLHandle( _node ? _node->LastChild() : 0 ); + } + /// Get the last child element of this handle. + XMLHandle LastChildElement( const char* _value=0 ) { + return XMLHandle( _node ? _node->LastChildElement( _value ) : 0 ); + } + /// Get the previous sibling of this handle. + XMLHandle PreviousSibling() { + return XMLHandle( _node ? _node->PreviousSibling() : 0 ); + } + /// Get the previous sibling element of this handle. + XMLHandle PreviousSiblingElement( const char* _value=0 ) { + return XMLHandle( _node ? _node->PreviousSiblingElement( _value ) : 0 ); + } + /// Get the next sibling of this handle. + XMLHandle NextSibling() { + return XMLHandle( _node ? _node->NextSibling() : 0 ); + } + /// Get the next sibling element of this handle. + XMLHandle NextSiblingElement( const char* _value=0 ) { + return XMLHandle( _node ? _node->NextSiblingElement( _value ) : 0 ); + } + + /// Safe cast to XMLNode. This can return null. + XMLNode* ToNode() { + return _node; + } + /// Safe cast to XMLElement. This can return null. + XMLElement* ToElement() { + return ( ( _node && _node->ToElement() ) ? _node->ToElement() : 0 ); + } + /// Safe cast to XMLText. This can return null. + XMLText* ToText() { + return ( ( _node && _node->ToText() ) ? _node->ToText() : 0 ); + } + /// Safe cast to XMLUnknown. This can return null. + XMLUnknown* ToUnknown() { + return ( ( _node && _node->ToUnknown() ) ? _node->ToUnknown() : 0 ); + } + /// Safe cast to XMLDeclaration. This can return null. + XMLDeclaration* ToDeclaration() { + return ( ( _node && _node->ToDeclaration() ) ? _node->ToDeclaration() : 0 ); + } + +private: + XMLNode* _node; +}; + + +/** + A variant of the XMLHandle class for working with const XMLNodes and Documents. It is the + same in all regards, except for the 'const' qualifiers. See XMLHandle for API. +*/ +class TINYXML2_LIB XMLConstHandle +{ +public: + XMLConstHandle( const XMLNode* node ) { + _node = node; + } + XMLConstHandle( const XMLNode& node ) { + _node = &node; + } + XMLConstHandle( const XMLConstHandle& ref ) { + _node = ref._node; + } + + XMLConstHandle& operator=( const XMLConstHandle& ref ) { + _node = ref._node; + return *this; + } + + const XMLConstHandle FirstChild() const { + return XMLConstHandle( _node ? _node->FirstChild() : 0 ); + } + const XMLConstHandle FirstChildElement( const char* value=0 ) const { + return XMLConstHandle( _node ? _node->FirstChildElement( value ) : 0 ); + } + const XMLConstHandle LastChild() const { + return XMLConstHandle( _node ? _node->LastChild() : 0 ); + } + const XMLConstHandle LastChildElement( const char* _value=0 ) const { + return XMLConstHandle( _node ? _node->LastChildElement( _value ) : 0 ); + } + const XMLConstHandle PreviousSibling() const { + return XMLConstHandle( _node ? _node->PreviousSibling() : 0 ); + } + const XMLConstHandle PreviousSiblingElement( const char* _value=0 ) const { + return XMLConstHandle( _node ? _node->PreviousSiblingElement( _value ) : 0 ); + } + const XMLConstHandle NextSibling() const { + return XMLConstHandle( _node ? _node->NextSibling() : 0 ); + } + const XMLConstHandle NextSiblingElement( const char* _value=0 ) const { + return XMLConstHandle( _node ? _node->NextSiblingElement( _value ) : 0 ); + } + + + const XMLNode* ToNode() const { + return _node; + } + const XMLElement* ToElement() const { + return ( ( _node && _node->ToElement() ) ? _node->ToElement() : 0 ); + } + const XMLText* ToText() const { + return ( ( _node && _node->ToText() ) ? _node->ToText() : 0 ); + } + const XMLUnknown* ToUnknown() const { + return ( ( _node && _node->ToUnknown() ) ? _node->ToUnknown() : 0 ); + } + const XMLDeclaration* ToDeclaration() const { + return ( ( _node && _node->ToDeclaration() ) ? _node->ToDeclaration() : 0 ); + } + +private: + const XMLNode* _node; +}; + + +/** + Printing functionality. The XMLPrinter gives you more + options than the XMLDocument::Print() method. + + It can: + -# Print to memory. + -# Print to a file you provide. + -# Print XML without a XMLDocument. + + Print to Memory + + @verbatim + XMLPrinter printer; + doc.Print( &printer ); + SomeFunction( printer.CStr() ); + @endverbatim + + Print to a File + + You provide the file pointer. + @verbatim + XMLPrinter printer( fp ); + doc.Print( &printer ); + @endverbatim + + Print without a XMLDocument + + When loading, an XML parser is very useful. However, sometimes + when saving, it just gets in the way. The code is often set up + for streaming, and constructing the DOM is just overhead. + + The Printer supports the streaming case. The following code + prints out a trivially simple XML file without ever creating + an XML document. + + @verbatim + XMLPrinter printer( fp ); + printer.OpenElement( "foo" ); + printer.PushAttribute( "foo", "bar" ); + printer.CloseElement(); + @endverbatim +*/ +class TINYXML2_LIB XMLPrinter : public XMLVisitor +{ +public: + /** Construct the printer. If the FILE* is specified, + this will print to the FILE. Else it will print + to memory, and the result is available in CStr(). + If 'compact' is set to true, then output is created + with only required whitespace and newlines. + */ + XMLPrinter( FILE* file=0, bool compact = false, int depth = 0 ); + virtual ~XMLPrinter() {} + + /** If streaming, write the BOM and declaration. */ + void PushHeader( bool writeBOM, bool writeDeclaration ); + /** If streaming, start writing an element. + The element must be closed with CloseElement() + */ + void OpenElement( const char* name, bool compactMode=false ); + /// If streaming, add an attribute to an open element. + void PushAttribute( const char* name, const char* value ); + void PushAttribute( const char* name, int value ); + void PushAttribute( const char* name, unsigned value ); + void PushAttribute( const char* name, bool value ); + void PushAttribute( const char* name, double value ); + /// If streaming, close the Element. + virtual void CloseElement( bool compactMode=false ); + + /// Add a text node. + void PushText( const char* text, bool cdata=false ); + /// Add a text node from an integer. + void PushText( int value ); + /// Add a text node from an unsigned. + void PushText( unsigned value ); + /// Add a text node from a bool. + void PushText( bool value ); + /// Add a text node from a float. + void PushText( float value ); + /// Add a text node from a double. + void PushText( double value ); + + /// Add a comment + void PushComment( const char* comment ); + + void PushDeclaration( const char* value ); + void PushUnknown( const char* value ); + + virtual bool VisitEnter( const XMLDocument& /*doc*/ ); + virtual bool VisitExit( const XMLDocument& /*doc*/ ) { + return true; + } + + virtual bool VisitEnter( const XMLElement& element, const XMLAttribute* attribute ); + virtual bool VisitExit( const XMLElement& element ); + + virtual bool Visit( const XMLText& text ); + virtual bool Visit( const XMLComment& comment ); + virtual bool Visit( const XMLDeclaration& declaration ); + virtual bool Visit( const XMLUnknown& unknown ); + + /** + If in print to memory mode, return a pointer to + the XML file in memory. + */ + const char* CStr() const { + return _buffer.Mem(); + } + /** + If in print to memory mode, return the size + of the XML file in memory. (Note the size returned + includes the terminating null.) + */ + int CStrSize() const { + return _buffer.Size(); + } + /** + If in print to memory mode, reset the buffer to the + beginning. + */ + void ClearBuffer() { + _buffer.Clear(); + _buffer.Push(0); + } + +protected: + virtual bool CompactMode( const XMLElement& ) { return _compactMode; } + + /** Prints out the space before an element. You may override to change + the space and tabs used. A PrintSpace() override should call Print(). + */ + virtual void PrintSpace( int depth ); + void Print( const char* format, ... ); + + void SealElement(); + bool _elementJustOpened; + DynArray< const char*, 10 > _stack; + +private: + void PrintString( const char*, bool restrictedEntitySet ); // prints out, after detecting entities. + + bool _firstElement; + FILE* _fp; + int _depth; + int _textDepth; + bool _processEntities; + bool _compactMode; + + enum { + ENTITY_RANGE = 64, + BUF_SIZE = 200 + }; + bool _entityFlag[ENTITY_RANGE]; + bool _restrictedEntityFlag[ENTITY_RANGE]; + + DynArray< char, 20 > _buffer; +#ifdef _MSC_VER + DynArray< char, 20 > _accumulator; +#endif +}; + + +} // tinyxml2 + +#if defined(_MSC_VER) +# pragma warning(pop) +#endif + +#endif // TINYXML2_INCLUDED