Created
January 15, 2021 02:22
-
-
Save Ryan-rsm-McKenzie/985672e1bfbea53b23c5afb3e6b065c4 to your computer and use it in GitHub Desktop.
f4se papyrus back end reimplementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Papyrus | |
{ | |
#define BIND(a_method, ...) a_vm.BindNativeMethod(obj, #a_method##sv, a_method __VA_OPT__(, ) __VA_ARGS__) | |
using RE::BSScript::structure_wrapper; | |
namespace Actor | |
{ | |
std::optional<structure_wrapper<"Actor", "WornItem">> GetWornItem(const RE::Actor& a_self, RE::BIPED_OBJECT a_slotIndex, bool a_firstPerson) | |
{ | |
if (a_slotIndex >= RE::BIPED_OBJECT::kTotal) { | |
return std::nullopt; // TODO: log error? | |
} | |
const auto biped = [&]() { | |
if (a_firstPerson && a_self.IsPlayerRef()) { | |
return static_cast<const RE::PlayerCharacter&>(a_self).firstPersonBipedAnim; | |
} else { | |
return a_self.biped; | |
} | |
}(); | |
if (!biped) { | |
return std::nullopt; | |
} | |
structure_wrapper<"Actor", "WornItem"> result; | |
const auto& slot = biped->object[stl::to_underlying(a_slotIndex)]; | |
result.insert("item"sv, slot.parent.object); | |
result.insert("model"sv, slot.armorAddon); | |
if (slot.part) { | |
const auto modelName = slot.part->GetModel(); | |
result.insert<std::string_view>("modelName"sv, modelName ? modelName : ""sv); | |
if (const auto swap = slot.part->GetAsModelMaterialSwap(); swap) { | |
result.insert("materialSwap"sv, swap->swapForm); | |
} | |
} | |
result.insert("texture"sv, slot.skinTexture); | |
return result; | |
} | |
std::optional<std::vector<RE::BGSMod::Attachment::Mod*>> GetWornItemMods(const RE::Actor& a_self, RE::BIPED_OBJECT a_slotIndex) | |
{ | |
if (a_slotIndex >= RE::BIPED_OBJECT::kTotal) { | |
return std::nullopt; // TODO: log error? | |
} | |
const auto object = | |
a_self.biped ? | |
a_self.biped->object[stl::to_underlying(a_slotIndex)].parent.object : // TODO: GetCurrentBiped()? | |
nullptr; | |
const auto inv = a_self.inventoryList; | |
if (!object || !inv) { | |
return std::nullopt; | |
} | |
const RE::BSAutoReadLock l{ inv->rwLock }; | |
for (const auto& item : inv->data) { | |
for (auto stack = item.stackData.get(); stack; stack = stack->nextStack.get()) { | |
if (stack->IsEquipped() && stack->extra) { | |
std::vector<RE::BGSMod::Attachment::Mod*> mods; | |
const auto xInstance = stack->extra->GetByType<RE::BGSObjectInstanceExtra>(); | |
if (xInstance && xInstance->values) { | |
const auto data = xInstance->GetIndexData(); | |
mods.reserve(data.size()); | |
for (const auto& elem : data) { | |
const auto mod = RE::TESForm::GetFormByID<RE::BGSMod::Attachment::Mod>(elem.objectID); | |
if (mod) { | |
mods.push_back(mod); | |
} | |
} | |
} | |
return mods; | |
} | |
} | |
} | |
return std::nullopt; | |
} | |
void QueueUpdate(RE::Actor& a_self, bool a_doEquipment, std::uint32_t a_flags) | |
{ | |
a_self.Reset3D(a_doEquipment, 0, true, a_flags); | |
} | |
RE::TESObjectREFR* GetFurnitureReference(const RE::Actor& a_self) | |
{ | |
const auto proc = a_self.currentProcess; | |
const auto middleHigh = proc ? proc->middleHigh : nullptr; | |
if (middleHigh) { | |
const auto& furn = | |
a_self.interactingState != RE::INTERACTING_STATE::kNotInteracting ? | |
middleHigh->currentFurniture : | |
middleHigh->occupiedFurniture; | |
return furn.get().get(); | |
} else { | |
return nullptr; | |
} | |
} | |
void Bind(RE::BSScript::IVirtualMachine& a_vm) | |
{ | |
const auto obj = "Actor"sv; | |
BIND(GetWornItem); | |
BIND(GetWornItemMods); | |
BIND(QueueUpdate); | |
BIND(GetFurnitureReference); | |
logger::info("bound {} script"sv, obj); | |
} | |
} | |
namespace Form | |
{ | |
std::uint32_t AddSlotToMask(RE::TESForm& a_self, std::uint32_t a_slotMask) | |
{ | |
const auto biped = a_self.As<RE::BGSBipedObjectForm>(); | |
return biped ? biped->bipedModelData.bipedObjectSlots |= a_slotMask : 0; | |
} | |
std::string GetDescription(const RE::TESForm& a_self) | |
{ | |
const auto desc = a_self.As<RE::TESDescription>(); | |
if (desc) { | |
RE::BSStringT<char> buf; | |
desc->descriptionText.GetDescription(buf, nullptr); | |
return std::string(buf); | |
} else { | |
return {}; | |
} | |
} | |
RE::EnchantmentItem* GetEnchantment(const RE::TESForm& a_self) | |
{ | |
const auto ench = a_self.As<RE::TESEnchantableForm>(); | |
return ench ? ench->GetBaseEnchanting() : nullptr; | |
} | |
std::int32_t GetEnchantmentValue(const RE::TESForm& a_self) | |
{ | |
const auto ench = a_self.As<RE::TESEnchantableForm>(); | |
return ench && ench->GetBaseEnchanting() ? ench->GetBaseCharge() : 0; | |
} | |
RE::BGSEquipSlot* GetEquipType(const RE::TESForm& a_self) | |
{ | |
const auto equip = a_self.As<RE::BGSEquipType>(); | |
return equip ? equip->GetEquipSlot(nullptr) : nullptr; | |
} | |
std::string_view GetIconPath(const RE::TESForm& a_self) | |
{ | |
const auto icon = a_self.As<RE::TESIcon>(); | |
return icon ? static_cast<std::string_view>(icon->textureName) : ""sv; | |
} | |
std::vector<RE::BGSKeyword*> GetKeywords(const RE::TESForm& a_self) | |
{ | |
std::vector<RE::BGSKeyword*> result; | |
const auto keywords = a_self.As<RE::BGSKeywordForm>(); | |
if (keywords) { | |
result.resize(keywords->numKeywords, nullptr); | |
// TODO: IKeywordFormBase::CollectAllKeywords a better choice? | |
std::copy_n( | |
std::execution::unseq, | |
keywords->keywords, | |
keywords->numKeywords, | |
result.begin()); | |
} | |
return result; | |
} | |
std::uint32_t GetMaskForSlot(std::monostate, std::uint32_t a_slot) | |
{ | |
if (30 <= a_slot && a_slot <= 61) { | |
return 1 << (a_slot - 30); | |
} else { | |
return 0; | |
} | |
} | |
std::string_view GetMessageIconPath(const RE::TESForm& a_self) | |
{ | |
const auto msg = a_self.As<RE::BGSMessageIcon>(); | |
return msg ? static_cast<std::string_view>(msg->GetMessageIconTextureName()) : ""sv; | |
} | |
std::string_view GetName(const RE::TESForm& a_self) | |
{ | |
const auto full = a_self.As<RE::TESFullName>(); | |
const auto str = full ? full->GetFullName() : ""; | |
return str ? str : ""sv; | |
} | |
RE::TESRace* GetRaceForm(const RE::TESForm& a_self) | |
{ | |
const auto race = a_self.As<RE::TESRaceForm>(); | |
return race ? race->GetFormRace() : nullptr; | |
} | |
std::uint32_t GetSlotMask(const RE::TESForm& a_self) | |
{ | |
const auto biped = a_self.As<RE::BGSBipedObjectForm>(); | |
return biped ? biped->GetFilledSlots() : 0; | |
} | |
float GetWeight(const RE::TESForm& a_self) | |
{ | |
const auto weight = a_self.As<RE::TESWeightForm>(); | |
return weight ? weight->GetFormWeight() : 0.0F; | |
} | |
std::string_view GetWorldModelPath(const RE::TESForm& a_self) | |
{ | |
const auto model = a_self.As<RE::TESModel>(); | |
const auto str = model ? model->GetModel() : nullptr; | |
return str ? str : ""sv; | |
} | |
bool HasWorldModel(const RE::TESForm& a_self) | |
{ | |
return a_self.As<RE::TESModel>() != nullptr; | |
} | |
std::uint32_t RemoveSlotFromMask(RE::TESForm& a_self, std::uint32_t a_slotMask) | |
{ | |
const auto biped = a_self.As<RE::BGSBipedObjectForm>(); | |
return biped ? biped->bipedModelData.bipedObjectSlots &= ~a_slotMask : 0; | |
} | |
void SetEnchantment(RE::TESForm& a_self, RE::EnchantmentItem* a_enchantment) | |
{ | |
const auto ench = a_self.As<RE::TESEnchantableForm>(); | |
if (ench) { | |
ench->SetBaseEnchanting(a_enchantment); | |
} | |
} | |
void SetEnchantmentValue(RE::TESForm& a_self, std::uint32_t a_value) | |
{ | |
const auto ench = a_self.As<RE::TESEnchantableForm>(); | |
if (ench) { | |
ench->SetBaseCharge(static_cast<std::uint16_t>(a_value)); | |
} | |
} | |
void SetEquipType(RE::TESForm& a_self, RE::BGSEquipSlot* a_type) | |
{ | |
const auto equip = a_self.As<RE::BGSEquipType>(); | |
if (equip) { | |
equip->SetEquipSlot(a_type); | |
} | |
} | |
void SetGoldValue(RE::TESForm& a_self, std::int32_t a_value) | |
{ | |
RE::TESValueForm::SetFormValue(a_self, a_value); // divergent | |
} | |
void SetIconPath(RE::TESForm& a_self, std::string_view a_path) | |
{ | |
const auto icon = a_self.As<RE::TESIcon>(); | |
if (icon) { | |
icon->textureName = a_path; | |
} | |
} | |
void SetMessageIconPath(RE::TESForm& a_self, std::string_view a_name) | |
{ | |
const auto icon = a_self.As<RE::BGSMessageIcon>(); | |
if (icon) { | |
icon->SetMessageIconTextureName(a_name); | |
} | |
} | |
void SetName(RE::TESForm& a_self, std::string_view a_name) | |
{ | |
RE::TESFullName::SetFullName(a_self, a_name); // divergent | |
} | |
void SetRaceForm(RE::TESForm& a_self, RE::TESRace* a_newRace) | |
{ | |
const auto race = a_self.As<RE::TESRaceForm>(); | |
if (race) { | |
race->SetFormRace(a_newRace); | |
} | |
} | |
void SetSlotMask(RE::TESForm& a_self, std::uint32_t a_slotMask) | |
{ | |
const auto biped = a_self.As<RE::BGSBipedObjectForm>(); | |
if (biped) { | |
biped->bipedModelData.bipedObjectSlots = a_slotMask; | |
} | |
} | |
void SetWeight(RE::TESForm& a_self, float a_weight) | |
{ | |
const auto weight = a_self.As<RE::TESWeightForm>(); | |
if (weight) { | |
weight->SetFormWeight(a_weight); | |
} | |
} | |
void SetWorldModelPath(RE::TESForm& a_self, std::string_view a_path) | |
{ | |
const auto model = a_self.As<RE::TESModel>(); | |
if (model) { | |
model->SetModel(a_path.empty() ? "" : a_path.data()); | |
} | |
} | |
void Bind(RE::BSScript::IVirtualMachine& a_vm) | |
{ | |
const auto obj = "Form"sv; | |
BIND(AddSlotToMask); | |
BIND(GetDescription); | |
BIND(GetEnchantment); | |
BIND(GetEnchantmentValue); | |
BIND(GetEquipType); | |
BIND(GetIconPath); | |
BIND(GetKeywords); | |
BIND(GetMaskForSlot, true); | |
BIND(GetMessageIconPath); | |
BIND(GetName); | |
BIND(GetRaceForm); | |
BIND(GetSlotMask); | |
BIND(GetWeight); | |
BIND(GetWorldModelPath); | |
BIND(HasWorldModel); | |
BIND(RemoveSlotFromMask); | |
BIND(SetEnchantment); | |
BIND(SetEnchantmentValue); | |
BIND(SetEquipType); | |
BIND(SetGoldValue); | |
BIND(SetIconPath); | |
BIND(SetMessageIconPath); | |
BIND(SetName); | |
BIND(SetRaceForm); | |
BIND(SetSlotMask); | |
BIND(SetWeight); | |
BIND(SetWorldModelPath); | |
logger::info("bound {} script"sv, obj); | |
} | |
} | |
#undef BIND | |
bool F4SEAPI Bind(RE::BSScript::IVirtualMachine* a_vm) | |
{ | |
{ | |
std::vector<void*> v(1 << 11); | |
constexpr std::size_t tcount = 1 << 4; | |
std::vector<std::thread> pool; | |
pool.reserve(tcount); | |
for (std::size_t i = 0; i < tcount; ++i) { | |
pool.emplace_back( | |
[](decltype(v)::iterator a_first, decltype(v)::iterator a_last) { | |
const auto tid = WinAPI::GetCurrentThreadID(); | |
logger::trace("tid: {}", tid); | |
const auto alloc = F4SE::GetTrampolineInterface(); | |
for (; a_first != a_last; ++a_first) { | |
*a_first = alloc->AllocateFromBranchPool(1); | |
} | |
}, | |
v.begin() + i * (v.size() / tcount), | |
v.begin() + (i + 1) * (v.size() / tcount)); | |
} | |
std::ranges::for_each(pool, [](std::thread& a_thread) { a_thread.join(); }); | |
std::ranges::sort(v); | |
assert(std::ranges::adjacent_find(v) == v.end()); | |
} | |
if (!a_vm) { | |
return false; | |
} | |
Actor::Bind(*a_vm); | |
Form::Bind(*a_vm); | |
logger::info("bound all scripts"sv); | |
return true; | |
} | |
} | |
extern "C" DLLEXPORT bool F4SEAPI F4SEPlugin_Query(const F4SE::QueryInterface* a_f4se, F4SE::PluginInfo* a_info) | |
{ | |
#ifndef NDEBUG | |
auto sink = std::make_shared<logger::msvc_sink_mt>(); | |
#else | |
auto path = logger::log_directory(); | |
if (!path) { | |
return false; | |
} | |
*path /= "ExampleProject.log"sv; | |
auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path->string(), true); | |
#endif | |
auto log = std::make_shared<spdlog::logger>("global log"s, std::move(sink)); | |
#ifndef NDEBUG | |
log->set_level(spdlog::level::trace); | |
#else | |
log->set_level(spdlog::level::info); | |
log->flush_on(spdlog::level::warn); | |
#endif | |
spdlog::set_default_logger(std::move(log)); | |
spdlog::set_pattern("%g(%#): [%^%l%$] %v"s); | |
a_info->infoVersion = F4SE::PluginInfo::kVersion; | |
a_info->name = "f4se stub"; | |
a_info->version = 1; | |
if (a_f4se->IsEditor()) { | |
logger::critical("loaded in editor"sv); | |
return false; | |
} | |
const auto ver = a_f4se->RuntimeVersion(); | |
if (ver < F4SE::RUNTIME_1_10_162) { | |
logger::critical("unsupported runtime v{}"sv, ver.string()); | |
return false; | |
} | |
return true; | |
} | |
extern "C" DLLEXPORT bool F4SEAPI F4SEPlugin_Load(const F4SE::LoadInterface* a_f4se) | |
{ | |
F4SE::Init(a_f4se); | |
logger::info("loaded f4se stub v{}"sv, Version::NAME); | |
const auto papyrus = F4SE::GetPapyrusInterface(); | |
papyrus->Register(Papyrus::Bind); | |
return true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment