Skip to content

Instantly share code, notes, and snippets.

@Ryan-rsm-McKenzie
Created January 15, 2021 02:22
Show Gist options
  • Save Ryan-rsm-McKenzie/985672e1bfbea53b23c5afb3e6b065c4 to your computer and use it in GitHub Desktop.
Save Ryan-rsm-McKenzie/985672e1bfbea53b23c5afb3e6b065c4 to your computer and use it in GitHub Desktop.
f4se papyrus back end reimplementation
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