mirror of
https://github.com/Jous99/F4MP.git
synced 2026-01-12 16:50:54 +01:00
1465 lines
61 KiB
C++
1465 lines
61 KiB
C++
|
|
#define LIBRG_IMPLEMENTATION
|
||
|
|
#define LIBRG_DEBUG
|
||
|
|
#define librg_log _DMESSAGE
|
||
|
|
//#define ZPL_ASSERT_MSG _ERROR
|
||
|
|
|
||
|
|
#define ZPL_ASSERT_MSG(cond, msg, ...)\
|
||
|
|
do {\
|
||
|
|
if (!(cond)) {\
|
||
|
|
_ERROR(msg, ##__VA_ARGS__);\
|
||
|
|
}\
|
||
|
|
} while (0)
|
||
|
|
|
||
|
|
#include "f4mp.h"
|
||
|
|
#include "TopicInfoIDs.h"
|
||
|
|
#include "f4se/NiNodes.h"
|
||
|
|
#include "f4se/PapyrusDelayFunctors.h"
|
||
|
|
|
||
|
|
#include <fstream>
|
||
|
|
#include <iterator>
|
||
|
|
#include <shlobj.h> // CSIDL_MYCODUMENTS
|
||
|
|
|
||
|
|
std::vector<std::unique_ptr<f4mp::F4MP>> f4mp::F4MP::instances;
|
||
|
|
size_t f4mp::F4MP::activeInstance = 0, f4mp::F4MP::nextActiveInstance = 0;
|
||
|
|
|
||
|
|
f4mp::F4MP& f4mp::F4MP::GetInstance()
|
||
|
|
{
|
||
|
|
if (instances.size() == 0)
|
||
|
|
{
|
||
|
|
instances.push_back(std::make_unique<F4MP>());
|
||
|
|
instances.back()->player = std::make_unique<Player>();
|
||
|
|
}
|
||
|
|
|
||
|
|
return *instances[activeInstance];
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string f4mp::F4MP::GetPath()
|
||
|
|
{
|
||
|
|
std::string path(MAX_PATH, '\0');
|
||
|
|
|
||
|
|
HRESULT err = SHGetFolderPath(NULL, CSIDL_MYDOCUMENTS | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, &path[0]);
|
||
|
|
if (!SUCCEEDED(err))
|
||
|
|
{
|
||
|
|
_FATALERROR("SHGetFolderPath %08X failed (result = %08X lasterr = %08X)", CSIDL_MYDOCUMENTS, err, GetLastError());
|
||
|
|
}
|
||
|
|
ASSERT_CODE(SUCCEEDED(err), err);
|
||
|
|
|
||
|
|
return path.substr(0, path.find('\0')) + "\\My Games\\Fallout4\\F4MP\\";
|
||
|
|
}
|
||
|
|
|
||
|
|
f4mp::F4MP::F4MP() : ctx{}, port(0), handle(kPluginHandle_Invalid), messaging(nullptr), papyrus(nullptr), task(nullptr), topicInstance(nullptr)
|
||
|
|
{
|
||
|
|
ctx.tick_delay = 10.0;
|
||
|
|
ctx.mode = LIBRG_MODE_CLIENT;
|
||
|
|
|
||
|
|
librg_init(&ctx);
|
||
|
|
|
||
|
|
librg_event_add(&ctx, LIBRG_CONNECTION_REQUEST, OnConnectRequest);
|
||
|
|
librg_event_add(&ctx, LIBRG_CONNECTION_ACCEPT, OnConnectAccept);
|
||
|
|
librg_event_add(&ctx, LIBRG_CONNECTION_REFUSE, OnConnectRefuse);
|
||
|
|
librg_event_add(&ctx, LIBRG_CONNECTION_DISCONNECT, OnDisonnect);
|
||
|
|
|
||
|
|
librg_event_add(&ctx, LIBRG_ENTITY_CREATE, OnEntityCreate);
|
||
|
|
librg_event_add(&ctx, LIBRG_ENTITY_UPDATE, OnEntityUpdate);
|
||
|
|
librg_event_add(&ctx, LIBRG_ENTITY_REMOVE, OnEntityRemove);
|
||
|
|
|
||
|
|
librg_event_add(&ctx, LIBRG_CLIENT_STREAMER_UPDATE, OnClientUpdate);
|
||
|
|
|
||
|
|
librg_network_add(&ctx, MessageType::Hit, OnHit);
|
||
|
|
librg_network_add(&ctx, MessageType::FireWeapon, OnFireWeapon);
|
||
|
|
librg_network_add(&ctx, MessageType::SyncEntity, OnSyncEntity);
|
||
|
|
librg_network_add(&ctx, MessageType::SpawnBuilding, OnSpawnBuilding);
|
||
|
|
librg_network_add(&ctx, MessageType::RemoveBuilding, OnRemoveBuilding);
|
||
|
|
librg_network_add(&ctx, MessageType::Speak, OnSpeak);
|
||
|
|
|
||
|
|
const std::string path = GetPath() + "config.txt";
|
||
|
|
|
||
|
|
std::ifstream configFile(path);
|
||
|
|
if (!configFile)
|
||
|
|
{
|
||
|
|
std::ofstream newConfigFile(path);
|
||
|
|
newConfigFile << "localhost" << std::endl;
|
||
|
|
|
||
|
|
configFile = std::ifstream(path);
|
||
|
|
}
|
||
|
|
|
||
|
|
configFile >> config.hostAddress;
|
||
|
|
}
|
||
|
|
|
||
|
|
f4mp::F4MP::~F4MP()
|
||
|
|
{
|
||
|
|
librg_free(&ctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool f4mp::F4MP::Init(const F4SEInterface* f4se)
|
||
|
|
{
|
||
|
|
FILE* tmp = nullptr;
|
||
|
|
AllocConsole();
|
||
|
|
freopen_s(&tmp, "CONOUT$", "w", stdout);
|
||
|
|
|
||
|
|
printf("console opened\n");
|
||
|
|
|
||
|
|
handle = f4se->GetPluginHandle();
|
||
|
|
|
||
|
|
messaging = (F4SEMessagingInterface*)f4se->QueryInterface(kInterface_Messaging);
|
||
|
|
papyrus = (F4SEPapyrusInterface*)f4se->QueryInterface(kInterface_Papyrus);
|
||
|
|
task = (F4SETaskInterface*)f4se->QueryInterface(kInterface_Task);
|
||
|
|
object = (F4SEObjectInterface*)f4se->QueryInterface(kInterface_Object);
|
||
|
|
|
||
|
|
if (!papyrus->Register([](VirtualMachine* vm)
|
||
|
|
{
|
||
|
|
vm->RegisterFunction(new NativeFunction0<StaticFunctionTag, UInt32>("GetClientInstanceID", "F4MP", GetClientInstanceID, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction1<StaticFunctionTag, void, UInt32>("SetClient", "F4MP", SetClient, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction0<StaticFunctionTag, bool>("IsConnected", "F4MP", IsConnected, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction4<StaticFunctionTag, bool, Actor*, TESNPC*, BSFixedString, SInt32>("Connect", "F4MP", Connect, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction0<StaticFunctionTag, bool>("Disconnect", "F4MP", Disconnect, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction0<StaticFunctionTag, void>("Tick", "F4MP", Tick, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction0<StaticFunctionTag, void>("SyncWorld", "F4MP", SyncWorld, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction0<StaticFunctionTag, UInt32>("GetPlayerEntityID", "F4MP", GetPlayerEntityID, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction1<StaticFunctionTag, UInt32, TESObjectREFR*>("GetEntityID", "F4MP", GetEntityID, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction2<StaticFunctionTag, void, UInt32, TESObjectREFR*>("SetEntityRef", "F4MP", SetEntityRef, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction1<StaticFunctionTag, bool, UInt32>("IsEntityValid", "F4MP", IsEntityValid, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction1<StaticFunctionTag, VMArray<Float32>, UInt32>("GetEntityPosition", "F4MP", GetEntityPosition, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction4<StaticFunctionTag, void, UInt32, Float32, Float32, Float32>("SetEntityPosition", "F4MP", SetEntityPosition, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction3<StaticFunctionTag, void, UInt32, BSFixedString, Float32>("SetEntVarNum", "F4MP", SetEntVarNum, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction2<StaticFunctionTag, void, UInt32, BSFixedString>("SetEntVarAnim", "F4MP", SetEntVarAnim, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction2<StaticFunctionTag, Float32, UInt32, BSFixedString>("GetEntVarNum", "F4MP", GetEntVarNum, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction1<StaticFunctionTag, BSFixedString, UInt32>("GetEntVarAnim", "F4MP", GetEntVarAnim, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction1<StaticFunctionTag, VMArray<TESObjectREFR*>, TESObjectCELL*>("GetRefsInCell", "F4MP", GetRefsInCell, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction2<StaticFunctionTag, Float32, Float32, Float32>("Atan2", "F4MP", Atan2, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction3<StaticFunctionTag, BSFixedString, Float32, Float32, Float32>("GetWalkDir", "F4MP", GetWalkDir, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction1<StaticFunctionTag, BGSAction*, BSFixedString>("GetAction", "F4MP",
|
||
|
|
[](StaticFunctionTag* base, BSFixedString name) -> BGSAction*
|
||
|
|
{
|
||
|
|
auto& actions = (*g_dataHandler)->arrAACT;
|
||
|
|
for (UInt32 i = 0; i < actions.count; i++)
|
||
|
|
{
|
||
|
|
if (std::string(actions[i]->GetFullName()).compare(name.c_str()))
|
||
|
|
{
|
||
|
|
return actions[i];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return nullptr;
|
||
|
|
}, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction1 < StaticFunctionTag, void, VMArray<TESForm*>>("Inspect", "F4MP",
|
||
|
|
[](StaticFunctionTag* base, VMArray<TESForm*> forms)
|
||
|
|
{
|
||
|
|
_MESSAGE("size: %u", forms.Length());
|
||
|
|
|
||
|
|
for (UInt32 i = 0; i < forms.Length(); i++)
|
||
|
|
{
|
||
|
|
TESForm* form;
|
||
|
|
forms.Get(&form, i);
|
||
|
|
_MESSAGE("%p %u %x %s", form - RelocationManager::s_baseAddr, form->GetFormType(), form->formID, form->GetFullName());
|
||
|
|
}
|
||
|
|
}, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction1<StaticFunctionTag, bool, BSFixedString>("AnimLoops", "F4MP", AnimLoops, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction2<StaticFunctionTag, void, TESNPC*, TESNPC*>("CopyAppearance", "F4MP", CopyAppearance, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction2<StaticFunctionTag, void, Actor*, Actor*>("CopyWornItems", "F4MP", CopyWornItems, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction3<StaticFunctionTag, void, UInt32, UInt32, Float32>("PlayerHit", "F4MP", PlayerHit, vm));
|
||
|
|
vm->RegisterFunction(new NativeFunction0<StaticFunctionTag, void>("PlayerFireWeapon", "F4MP", PlayerFireWeapon, vm));
|
||
|
|
|
||
|
|
vm->RegisterFunction(new NativeFunction2<StaticFunctionTag, void, TESForm*, TESObjectREFR*>("TopicInfoBegin", "F4MP",
|
||
|
|
[](StaticFunctionTag* base, TESForm* topicInfo, TESObjectREFR* speaker)
|
||
|
|
{
|
||
|
|
/*static std::unordered_map<UInt32, UInt32> counts;
|
||
|
|
counts[topicInfo->formID]++;
|
||
|
|
|
||
|
|
for (auto& count : counts)
|
||
|
|
{
|
||
|
|
printf("%u : %u ", count.first, count.second);
|
||
|
|
}
|
||
|
|
printf("\n");
|
||
|
|
|
||
|
|
switch (speaker->formID)
|
||
|
|
{
|
||
|
|
case 0x1D882B:
|
||
|
|
case 0xE0B61:
|
||
|
|
case 0x1E2300:
|
||
|
|
case 0x1DE88E:
|
||
|
|
case 0x1D882C:
|
||
|
|
case 0x191F21:
|
||
|
|
case 0x1E2301:
|
||
|
|
case 0x1D882A:
|
||
|
|
case 0x1D206D:
|
||
|
|
case 0x1D1AED:
|
||
|
|
case 0x1DE7F6:
|
||
|
|
case 0x1AC2E5:
|
||
|
|
case 0x1D206E:
|
||
|
|
case 0x1D1AEC:
|
||
|
|
case 0x193B43:
|
||
|
|
case 0x2F27:
|
||
|
|
case 0x4338C:
|
||
|
|
case 0xED666:
|
||
|
|
case 0xF480B:
|
||
|
|
case 0x1D2864:
|
||
|
|
case 0x193C39:
|
||
|
|
case 0x248028:
|
||
|
|
case 0x2A195:
|
||
|
|
case 0x16939F:
|
||
|
|
return;
|
||
|
|
}*/
|
||
|
|
|
||
|
|
std::unordered_multiset<UInt32>& linesToSpeak = GetInstance().linesToSpeak[speaker->formID];
|
||
|
|
if (linesToSpeak.count(topicInfo->formID) > 0)
|
||
|
|
{
|
||
|
|
linesToSpeak.erase(topicInfo->formID);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
BSFixedString name = [](TESForm* baseForm, ExtraDataList* extraDataList)
|
||
|
|
{
|
||
|
|
if (baseForm)
|
||
|
|
{
|
||
|
|
if (extraDataList)
|
||
|
|
{
|
||
|
|
BSExtraData* extraData = extraDataList->GetByType(ExtraDataType::kExtraData_TextDisplayData);
|
||
|
|
if (extraData)
|
||
|
|
{
|
||
|
|
ExtraTextDisplayData* displayText = DYNAMIC_CAST(extraData, BSExtraData, ExtraTextDisplayData);
|
||
|
|
if (displayText)
|
||
|
|
{
|
||
|
|
return *CALL_MEMBER_FN(displayText, GetReferenceName)(baseForm);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
TESFullName* pFullName = DYNAMIC_CAST(baseForm, TESForm, TESFullName);
|
||
|
|
if (pFullName)
|
||
|
|
return pFullName->name;
|
||
|
|
}
|
||
|
|
|
||
|
|
return BSFixedString();
|
||
|
|
}(speaker->baseForm, speaker->extraDataList);
|
||
|
|
printf("topic info: %X / speaker: %X(%s)\n", topicInfo->formID, speaker->formID, name.c_str());
|
||
|
|
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
SpeakData data{ self.player->GetEntityID(), self.player->GetRefFormID() == speaker->formID ? 0x0 : speaker->formID, topicInfo->formID };
|
||
|
|
librg_message_send_all(&self.ctx, MessageType::Speak, &data, sizeof(SpeakData));
|
||
|
|
}, vm));
|
||
|
|
|
||
|
|
vm->SetFunctionFlags("F4MP", "GetClientInstanceID", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "SetClient", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "IsConnected", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "Connect", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "Disconnect", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "Tick", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "SyncWorld", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "GetPlayerEntityID", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "GetEntityID", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "SetEntityRef", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "IsEntityValid", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "GetEntityPosition", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "SetEntityPosition", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "SetEntVarNum", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "SetEntVarAnim", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "GetEntVarNum", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "GetEntVarAnim", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "GetRefsInCell", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "Atan2", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "GetWalkDir", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "GetAction", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "Inspect", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "AnimLoops", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "CopyAppearance", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "CopyWornItems", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "PlayerHit", IFunction::kFunctionFlag_NoWait);
|
||
|
|
vm->SetFunctionFlags("F4MP", "PlayerFireWeapon", IFunction::kFunctionFlag_NoWait);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}))
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
Animator::Init();
|
||
|
|
|
||
|
|
messaging->RegisterListener(handle, "F4SE", [](F4SEMessagingInterface::Message* msg)
|
||
|
|
{
|
||
|
|
switch (msg->type)
|
||
|
|
{
|
||
|
|
case F4SEMessagingInterface::kMessage_GameDataReady:
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
case F4SEMessagingInterface::kMessage_PostLoadGame:
|
||
|
|
{
|
||
|
|
struct OnTick : public IF4SEDelayFunctor
|
||
|
|
{
|
||
|
|
OnTick()
|
||
|
|
{
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
const char* ClassName() const override
|
||
|
|
{
|
||
|
|
return "F4MP";
|
||
|
|
}
|
||
|
|
|
||
|
|
UInt32 ClassVersion() const override
|
||
|
|
{
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool Save(const F4SESerializationInterface* intfc) override
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool Load(const F4SESerializationInterface* intfc, UInt32 version) override
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool Run(VMValue& resultOut) override
|
||
|
|
{
|
||
|
|
static Animator animator(Animator::Human);
|
||
|
|
static Animator::Animation animation;
|
||
|
|
|
||
|
|
static bool enabled = false;
|
||
|
|
if (!enabled)
|
||
|
|
{
|
||
|
|
if (GetAsyncKeyState(VK_F3))
|
||
|
|
{
|
||
|
|
enabled = true;
|
||
|
|
|
||
|
|
animation = Animator::Animation();
|
||
|
|
|
||
|
|
for (UInt32 node = 1; node < animator.GetAnimatedNodeCount(); node++)
|
||
|
|
{
|
||
|
|
animation.nodes.push_back(animator.GetNodeName(node));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (GetAsyncKeyState(VK_F4))
|
||
|
|
{
|
||
|
|
enabled = false;
|
||
|
|
|
||
|
|
animation.frames.pop_back();
|
||
|
|
|
||
|
|
auto getDifference = [&](size_t referenceFrame, size_t frame)
|
||
|
|
{
|
||
|
|
double difference = 0.f;
|
||
|
|
|
||
|
|
for (size_t j = 0; j < animation.frames[frame].transforms.size(); j++)
|
||
|
|
{
|
||
|
|
const Animator::Transform& transform = animation.frames[frame].transforms[j];
|
||
|
|
const Animator::Transform& initialTransform = animation.frames[referenceFrame].transforms[j];
|
||
|
|
difference += zpl_vec3_mag(transform.position - initialTransform.position) - (zpl_quat_dot(transform.rotation, initialTransform.rotation) - 1.f) + abs(transform.scale - initialTransform.scale);
|
||
|
|
}
|
||
|
|
|
||
|
|
return difference;
|
||
|
|
};
|
||
|
|
|
||
|
|
std::vector<double> differences(animation.frames.size());
|
||
|
|
|
||
|
|
for (size_t i = 0; i < animation.frames.size(); i++)
|
||
|
|
{
|
||
|
|
differences[i] = getDifference(i, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
for (size_t i = 0; i < animation.frames.size(); i++)
|
||
|
|
{
|
||
|
|
if (0 < i && i < animation.frames.size() - 1)
|
||
|
|
{
|
||
|
|
if ((differences[i] - differences[i - 1]) * (differences[i + 1] - differences[i]) < 0)
|
||
|
|
{
|
||
|
|
const double threshold = 1.0;
|
||
|
|
|
||
|
|
if (differences[i] < threshold)
|
||
|
|
{
|
||
|
|
printf("> ");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
printf("%llu: %f %f\n", i, animation.frames[i].duration, differences[i]);
|
||
|
|
}
|
||
|
|
|
||
|
|
int name = 0;
|
||
|
|
std::ifstream tmp;
|
||
|
|
|
||
|
|
do
|
||
|
|
{
|
||
|
|
tmp.close();
|
||
|
|
tmp.open((GetPath() + std::to_string(++name) + ".txt").c_str());
|
||
|
|
} while (tmp.good());
|
||
|
|
|
||
|
|
animator.Save(GetPath() + std::to_string(name) + ".txt", animation);
|
||
|
|
}
|
||
|
|
|
||
|
|
TESObjectREFR* ref = *g_player;
|
||
|
|
if (ref)
|
||
|
|
{
|
||
|
|
if (animation.frames.size() > 0)
|
||
|
|
{
|
||
|
|
float& duration = animation.frames.back().duration;
|
||
|
|
duration = zpl_time_now() - duration;
|
||
|
|
}
|
||
|
|
|
||
|
|
Animator::Frame frame;
|
||
|
|
frame.duration = zpl_time_now();
|
||
|
|
frame.transforms.resize(animation.nodes.size());
|
||
|
|
|
||
|
|
if (!animator.ForEachNode(ref, [&](NiNode* node, UInt32 nodeIndex)
|
||
|
|
{
|
||
|
|
if (nodeIndex == 0)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const NiMatrix43 rot = node->m_localTransform.rot;
|
||
|
|
zpl_mat4 mat
|
||
|
|
{
|
||
|
|
rot.data[0][0], rot.data[0][1], rot.data[0][2], rot.data[0][3],
|
||
|
|
rot.data[1][0], rot.data[1][1], rot.data[1][2], rot.data[1][3],
|
||
|
|
rot.data[2][0], rot.data[2][1], rot.data[2][2], rot.data[2][3],
|
||
|
|
};
|
||
|
|
zpl_quat quat;
|
||
|
|
zpl_quat_from_mat4(&quat, &mat);
|
||
|
|
|
||
|
|
quat *= zpl_sign(quat.w);
|
||
|
|
|
||
|
|
frame.transforms[nodeIndex - 1] = { (zpl_vec3&)node->m_localTransform.pos, quat, node->m_localTransform.scale };
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}))
|
||
|
|
{
|
||
|
|
animation.frames.push_back(frame);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
F4MP& f4mp = F4MP::GetInstance();
|
||
|
|
|
||
|
|
librg_entity_iterate(&f4mp.ctx, LIBRG_ENTITY_ALIVE, [](librg_ctx* ctx, librg_entity* entity)
|
||
|
|
{
|
||
|
|
Entity::Get(entity)->OnTick();
|
||
|
|
});
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ShouldReschedule(SInt32& delayMSOut) override
|
||
|
|
{
|
||
|
|
delayMSOut = 1;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool ShouldResumeStack(UInt32& stackIdOut) override
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
GetInstance().object->GetDelayFunctorManager().Enqueue(new OnTick());
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
librg_entity* f4mp::F4MP::FetchEntity(UInt32 id, const std::string& errorMsg)
|
||
|
|
{
|
||
|
|
librg_entity* entity = librg_entity_fetch(&ctx, id);
|
||
|
|
if (!entity)
|
||
|
|
{
|
||
|
|
_ERROR(errorMsg.c_str(), id);
|
||
|
|
}
|
||
|
|
|
||
|
|
return entity;
|
||
|
|
}
|
||
|
|
|
||
|
|
std::vector<TESForm*> f4mp::F4MP::DecodeWornItems(const WornItemsData& wornItems)
|
||
|
|
{
|
||
|
|
std::vector<TESForm*> items;
|
||
|
|
|
||
|
|
//for (auto& item : wornItems.data)
|
||
|
|
for (size_t k = 0; k < wornItems.data1.size(); k++)
|
||
|
|
{
|
||
|
|
switch (wornItems.data1[k])
|
||
|
|
{
|
||
|
|
case kFormType_ARMO:
|
||
|
|
{
|
||
|
|
auto& armos = (*g_dataHandler)->arrARMO;
|
||
|
|
for (UInt32 i = 0; i < armos.count; i++)
|
||
|
|
{
|
||
|
|
if (wornItems.data2[k].compare(armos[i]->fullName.name.c_str()) == 0)
|
||
|
|
{
|
||
|
|
items.push_back(armos[i]);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case kFormType_WEAP:
|
||
|
|
{
|
||
|
|
auto& weaps = (*g_dataHandler)->arrWEAP;
|
||
|
|
for (UInt32 i = 0; i < weaps.count; i++)
|
||
|
|
{
|
||
|
|
if (wornItems.data2[k].compare(weaps[i]->fullName.name.c_str()) == 0)
|
||
|
|
{
|
||
|
|
items.push_back(weaps[i]);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//for (auto item : items)
|
||
|
|
//{
|
||
|
|
// _MESSAGE(item->GetFullName());
|
||
|
|
//}
|
||
|
|
|
||
|
|
return items;
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::SyncTransform(TESObjectREFR* ref, zpl_vec3 position, zpl_vec3 angles, bool ignoreAngleXY)
|
||
|
|
{
|
||
|
|
// HACK: somehow calling F4MP::TranslateTo causes a crash.
|
||
|
|
|
||
|
|
if (!ref)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ignoreAngleXY)
|
||
|
|
{
|
||
|
|
angles.x = angles.y = 0.f;
|
||
|
|
}
|
||
|
|
|
||
|
|
VMVariable x, y, z, angleX, angleY, angleZ, speedVar, rotSpeedVar;
|
||
|
|
x.Set<Float32>(&position.x); y.Set<Float32>(&position.y); z.Set<Float32>(&position.z);
|
||
|
|
angleX.Set<Float32>(&angles.x); angleY.Set<Float32>(&angles.y); angleZ.Set<Float32>(&angles.z);
|
||
|
|
|
||
|
|
// HACK: 10 might be too much
|
||
|
|
float speed = zpl_vec3_mag(position - (zpl_vec3&)ref->pos) * 10.f, rotSpeed = 500.f;
|
||
|
|
speedVar.Set<Float32>(&speed); rotSpeedVar.Set<Float32>(&rotSpeed);
|
||
|
|
|
||
|
|
VMArray<VMVariable> args;
|
||
|
|
args.Push(&x); args.Push(&y); args.Push(&z);
|
||
|
|
args.Push(&angleX); args.Push(&angleY); args.Push(&angleZ);
|
||
|
|
args.Push(&speedVar); args.Push(&rotSpeedVar);
|
||
|
|
|
||
|
|
CallFunctionNoWait(ref, "TranslateTo", args);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::TranslateTo(TESObjectREFR* ref, zpl_vec3 position, zpl_vec3 angles, Float32 speed, Float32 rotSpeed)
|
||
|
|
{
|
||
|
|
if (!ref)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
VMVariable x, y, z, angleX, angleY, angleZ, speedVar, rotSpeedVar;
|
||
|
|
x.Set<Float32>(&position.x); y.Set<Float32>(&position.y); z.Set<Float32>(&position.z);
|
||
|
|
angleX.Set<Float32>(&angles.x); angleY.Set<Float32>(&angles.y); angleZ.Set<Float32>(&angles.z);
|
||
|
|
|
||
|
|
float _speed = 500.f, _rotSpeed = 500.f;
|
||
|
|
speedVar.Set<Float32>(&_speed); rotSpeedVar.Set<Float32>(&_rotSpeed);
|
||
|
|
|
||
|
|
VMArray<VMVariable> args;
|
||
|
|
args.Push(&x); args.Push(&y); args.Push(&z);
|
||
|
|
args.Push(&angleX); args.Push(&angleY); args.Push(&angleZ);
|
||
|
|
args.Push(&speedVar); args.Push(&rotSpeedVar);
|
||
|
|
|
||
|
|
CallFunctionNoWait(ref, "TranslateTo", args);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::MoveTo(TESObjectREFR* ref, zpl_vec3 position, zpl_vec3 angles)
|
||
|
|
{
|
||
|
|
if (!ref)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
VMVariable x, y, z, angleX, angleY, angleZ, speed, rotSpeed;
|
||
|
|
x.Set<Float32>(&position.x); y.Set<Float32>(&position.y); z.Set<Float32>(&position.z);
|
||
|
|
angleX.Set<Float32>(&angles.x); angleY.Set<Float32>(&angles.y); angleZ.Set<Float32>(&angles.z);
|
||
|
|
|
||
|
|
VMArray<VMVariable> args1, args2;
|
||
|
|
args1.Push(&x); args1.Push(&y); args1.Push(&z);
|
||
|
|
args2.Push(&angleX); args2.Push(&angleY); args2.Push(&angleZ);
|
||
|
|
|
||
|
|
CallFunctionNoWait(ref, "SetPosition", args1);
|
||
|
|
CallFunctionNoWait(ref, "SetAngle", args2);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnConnectRequest(librg_event* event)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
_MESSAGE("trying to connect to %s:%d..", self.address.c_str(), self.port);
|
||
|
|
|
||
|
|
self.player->OnConnectRequest(event);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnConnectAccept(librg_event* event)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
_MESSAGE("successfully connected to %s:%d", self.address.c_str(), self.port);
|
||
|
|
|
||
|
|
self.player->OnConnectAccept(event);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnConnectRefuse(librg_event* event)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
_ERROR("failed to connect to %s:%d!", self.address.c_str(), self.port);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnDisonnect(librg_event* event)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
self.player->OnDisonnect(event);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnEntityCreate(librg_event* event)
|
||
|
|
{
|
||
|
|
_MESSAGE("entity with ID '%d' has created", event->entity->id);
|
||
|
|
|
||
|
|
Entity::Create(event);
|
||
|
|
|
||
|
|
// The Papyrus event "OnEntityCreate" is proccessed by the Player class.
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnEntityUpdate(librg_event* event)
|
||
|
|
{
|
||
|
|
//F4MP& self = GetInstance();
|
||
|
|
//
|
||
|
|
//self.papyrus->GetExternalEventRegistrations("OnEntityUpdate", event, [](UInt64 handle, const char* scriptName, const char* callbackName, void* dataPtr)
|
||
|
|
// {
|
||
|
|
// librg_event* event = static_cast<librg_event*>(dataPtr);
|
||
|
|
// UInt32 id = event->entity->id;
|
||
|
|
// SendPapyrusEvent1<UInt32>(handle, scriptName, callbackName, id);
|
||
|
|
// });
|
||
|
|
|
||
|
|
Entity* entity = Entity::Get(event);
|
||
|
|
if (!entity)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
entity->OnEntityUpdate(event);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnEntityRemove(librg_event* event)
|
||
|
|
{
|
||
|
|
Entity* entity = Entity::Get(event);
|
||
|
|
if (entity)
|
||
|
|
{
|
||
|
|
entity->OnEntityRemove(event);
|
||
|
|
}
|
||
|
|
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
_MESSAGE("entity with ID '%d' has removed", event->entity->id);
|
||
|
|
|
||
|
|
self.papyrus->GetExternalEventRegistrations("OnEntityRemove", event, [](UInt64 handle, const char* scriptName, const char* callbackName, void* dataPtr)
|
||
|
|
{
|
||
|
|
librg_event* event = static_cast<librg_event*>(dataPtr);
|
||
|
|
UInt32 id = event->entity->id;
|
||
|
|
SendPapyrusEvent1<UInt32>(handle, scriptName, callbackName, id);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnClientUpdate(librg_event* event)
|
||
|
|
{
|
||
|
|
//F4MP& self = GetInstance();
|
||
|
|
//
|
||
|
|
//self.papyrus->GetExternalEventRegistrations("OnClientUpdate", event, [](UInt64 handle, const char* scriptName, const char* callbackName, void* dataPtr)
|
||
|
|
// {
|
||
|
|
// librg_event* event = static_cast<librg_event*>(dataPtr);
|
||
|
|
// UInt32 id = event->entity->id;
|
||
|
|
// SendPapyrusEvent1<UInt32>(handle, scriptName, callbackName, id);
|
||
|
|
// });
|
||
|
|
|
||
|
|
Entity* entity = Entity::Get(event);
|
||
|
|
if (!entity)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
entity->OnClientUpdate(event);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnHit(librg_message* msg)
|
||
|
|
{
|
||
|
|
HitData data;
|
||
|
|
librg_data_rptr(msg->data, &data, sizeof(HitData));
|
||
|
|
|
||
|
|
_MESSAGE("OnHit: %u -> %u", data.hitter, data.hittee);
|
||
|
|
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
self.papyrus->GetExternalEventRegistrations("OnPlayerHit", &data, [](UInt64 handle, const char* scriptName, const char* callbackName, void* dataPtr)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
HitData* data = static_cast<HitData*>(dataPtr);
|
||
|
|
if (data->hittee == self.player->GetEntityID())
|
||
|
|
{
|
||
|
|
SendPapyrusEvent1<Float32>(handle, scriptName, callbackName, data->damage);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnFireWeapon(librg_message* msg)
|
||
|
|
{
|
||
|
|
UInt32 entity;
|
||
|
|
librg_data_rptr(msg->data, &entity, sizeof(UInt32));
|
||
|
|
|
||
|
|
_MESSAGE("OnFireWeapon: %u", entity);
|
||
|
|
|
||
|
|
for (auto& instance : instances)
|
||
|
|
{
|
||
|
|
Player* player = Entity::GetAs<Player>(instance->FetchEntity(entity));
|
||
|
|
if (!player || !player->GetRef())
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
VMArray<VMVariable> args;
|
||
|
|
CallFunctionNoWait(player->GetRef(), "FireWeapon", args);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnSyncEntity(librg_message* msg)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
SyncEntityData data;
|
||
|
|
librg_data_rptr(msg->data, &data, sizeof(SyncEntityData));
|
||
|
|
|
||
|
|
TESObjectREFR* ref = DYNAMIC_CAST(LookupFormByID(data.formID), TESForm, TESObjectREFR);
|
||
|
|
if (ref != nullptr)
|
||
|
|
{
|
||
|
|
MoveTo(ref, data.position, data.angles);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnSpawnBuilding(librg_message* msg)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
SpawnBuildingData data;
|
||
|
|
librg_data_rptr(msg->data, &data, sizeof(SpawnBuildingData));
|
||
|
|
|
||
|
|
UInt64 uniqueID = GetUniqueFormID(data.ownerEntityID, data.formID);
|
||
|
|
|
||
|
|
auto othersBuilding = self.buildings.find(uniqueID);
|
||
|
|
if (othersBuilding != self.buildings.end())
|
||
|
|
{
|
||
|
|
TESObjectREFR* ref = DYNAMIC_CAST(LookupFormByID(othersBuilding->second.formID), TESForm, TESObjectREFR);
|
||
|
|
if (ref)
|
||
|
|
{
|
||
|
|
TranslateTo(ref, data.position, data.angles, 500.f, 500.f);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (data.baseFormID == 0)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
TESObjectREFR* building = PlaceAtMe_Native((*g_gameVM)->m_virtualMachine, 0, (TESObjectREFR**)g_player.GetPtr(), LookupFormByID(data.baseFormID), 1, true, false, false);
|
||
|
|
MoveTo(building, data.position, data.angles);
|
||
|
|
|
||
|
|
printf("building spawned: %llx %f %f %f\n", uniqueID, data.position.x, data.position.y, data.position.z);
|
||
|
|
|
||
|
|
for (auto& instance : instances)
|
||
|
|
{
|
||
|
|
instance->buildings[uniqueID] = { building->formID, data.position, data.angles };
|
||
|
|
instance->knownBuildings.insert(building->formID);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnRemoveBuilding(librg_message* msg)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
RemoveBuildingData data;
|
||
|
|
librg_data_rptr(msg->data, &data, sizeof(RemoveBuildingData));
|
||
|
|
|
||
|
|
auto building = self.buildings.find(data.uniqueFormID);
|
||
|
|
if (building != self.buildings.end())
|
||
|
|
{
|
||
|
|
TESObjectREFR* ref = DYNAMIC_CAST(LookupFormByID(building->second.formID), TESForm, TESObjectREFR);
|
||
|
|
if (ref)
|
||
|
|
{
|
||
|
|
VMArray<VMVariable> args;
|
||
|
|
CallFunctionNoWait(ref, "Delete", args);
|
||
|
|
ref->flags |= TESObjectREFR::kFlag_IsDeleted;
|
||
|
|
}
|
||
|
|
|
||
|
|
printf("building removed: %llx %p\n", data.uniqueFormID, ref);
|
||
|
|
|
||
|
|
for (auto& instance : instances)
|
||
|
|
{
|
||
|
|
instance->buildings.erase(building->first);
|
||
|
|
instance->knownBuildings.erase(building->second.formID);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::OnSpeak(librg_message* msg)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
SpeakData data;
|
||
|
|
librg_data_rptr(msg->data, &data, sizeof(SpeakData));
|
||
|
|
|
||
|
|
if (!self.topicInstance)
|
||
|
|
{
|
||
|
|
self.topicInstance = static_cast<Topic*>(Runtime_DynamicCast(IFormFactory::GetFactoryForType(kFormType_DIAL)->Create(), RTTI_TESForm, RTTI_TESTopic));
|
||
|
|
Topic* templateTopic = static_cast<Topic*>(Runtime_DynamicCast(LookupFormByID(0x0022FA98), RTTI_TESForm, RTTI_TESTopic));
|
||
|
|
|
||
|
|
memcpy((UInt8*)self.topicInstance + sizeof(TESForm), (UInt8*)templateTopic + sizeof(TESForm), 88);
|
||
|
|
|
||
|
|
UInt8* ptr = (UInt8*)self.topicInstance;
|
||
|
|
TESForm**& infos = *(TESForm***)(ptr + sizeof(TESForm) + 48);
|
||
|
|
UInt32& count1 = *(UInt32*)(ptr + sizeof(TESForm) + 64);
|
||
|
|
UInt32& count2 = *(UInt32*)(ptr + sizeof(TESForm) + 68);
|
||
|
|
infos = new TESForm * [1];
|
||
|
|
count1 = count2 = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
TESObjectREFR* speaker = nullptr;
|
||
|
|
if (data.speakerFormID == 0x0)
|
||
|
|
{
|
||
|
|
speaker = Entity::GetAs<Player>(self.FetchEntity(data.clientEntityID))->GetRef();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
speaker = DYNAMIC_CAST(LookupFormByID(data.speakerFormID), TESForm, TESObjectREFR);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!speaker)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
TESForm* topicInfo = LookupFormByID(data.topicInfoFormID);
|
||
|
|
if (!topicInfo)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
Topic* topic = self.topicInstance;
|
||
|
|
Actor* actor = nullptr;
|
||
|
|
bool inHead = false;
|
||
|
|
TESObjectREFR* target = nullptr;
|
||
|
|
|
||
|
|
UInt8* ptr = (UInt8*)self.topicInstance;
|
||
|
|
TESForm**& infos = *(TESForm***)(ptr + sizeof(TESForm) + 48);
|
||
|
|
infos[0] = topicInfo;
|
||
|
|
|
||
|
|
VMVariable topicVar, actorVar, inHeadVar, targetVar;
|
||
|
|
topicVar.Set(&topic);
|
||
|
|
actorVar.Set(&actor);
|
||
|
|
inHeadVar.Set(&inHead);
|
||
|
|
targetVar.Set(&target);
|
||
|
|
|
||
|
|
VMArray<VMVariable> args;
|
||
|
|
args.Push(&topicVar);
|
||
|
|
args.Push(&actorVar);
|
||
|
|
args.Push(&inHeadVar);
|
||
|
|
args.Push(&targetVar);
|
||
|
|
|
||
|
|
CallFunctionNoWait(speaker, "Say", args);
|
||
|
|
|
||
|
|
self.linesToSpeak[speaker->formID].insert(topicInfo->formID);
|
||
|
|
|
||
|
|
printf("topic info: %X / speaker: %X / client: %u\n", topicInfo->formID, speaker->formID, data.clientEntityID);
|
||
|
|
}
|
||
|
|
|
||
|
|
UInt32 f4mp::F4MP::GetClientInstanceID(StaticFunctionTag* base)
|
||
|
|
{
|
||
|
|
return activeInstance;
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::SetClient(StaticFunctionTag* base, UInt32 instance)
|
||
|
|
{
|
||
|
|
nextActiveInstance = instance;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool f4mp::F4MP::IsConnected(StaticFunctionTag* base)
|
||
|
|
{
|
||
|
|
return !!librg_is_connected(&GetInstance().ctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool f4mp::F4MP::Connect(StaticFunctionTag* base, Actor* player, TESNPC* playerActorBase, BSFixedString address, SInt32 port)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
librg_network_stop(&self.ctx);
|
||
|
|
|
||
|
|
self.player = std::make_unique<Player>();
|
||
|
|
self.player->OnConnect(player, playerActorBase);
|
||
|
|
|
||
|
|
self.address = strlen(address.c_str()) > 0 ? address.c_str() : self.config.hostAddress;
|
||
|
|
self.port = port;
|
||
|
|
|
||
|
|
if (librg_network_start(&self.ctx, { self.port, const_cast<char*>(self.address.c_str()) }))
|
||
|
|
{
|
||
|
|
_ERROR("failed to connect to the server!");
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
self.topicInfoRemainders.clear();
|
||
|
|
|
||
|
|
std::vector<TESForm*> topicInfos;
|
||
|
|
|
||
|
|
std::unordered_set<UInt32> existingTopicInfoIDs
|
||
|
|
{
|
||
|
|
0x0018E8EE, 0x001768FF, 0x00047258, 0x0017C9BE, 0x000EC830, 0x0017978C, 0x001A765A, 0x000A8226, 0x00056A76, 0x00140532, 0x0017973F, 0x00087754, 0x00177E35, 0x00185031, 0x000D3E99, 0x00086365, 0x00166AC2, 0x000A8237, 0x00167F53, 0x00036FCF, 0x00179746, 0x00179F73, 0x00225312, 0x0017C9DF, 0x00177E20, 0x0017647C, 0x0022530F, 0x001AAD51, 0x00062BCC, 0x000D3EA4, 0x0001FA52, 0x000EF21E, 0x00112DDC, 0x0017C9BF, 0x000304E6, 0x00140535, 0x001797C3, 0x0004724C, 0x0023C65D, 0x001768FD, 0x00166ABF, 0x0017B46E, 0x0017C9F1, 0x00166AC3, 0x001797BC, 0x00178EDA, 0x0017978D, 0x0003710F, 0x00144F4F, 0x000C4515, 0x001797C2, 0x0017643F, 0x001797A0, 0x0017C9CC, 0x00135759, 0x00061541, 0x000304CF, 0x000EC82F, 0x0023C668, 0x00176498, 0x00179775, 0x001764AC, 0x001651F1, 0x000A02BC, 0x000A822C, 0x0019B4D3, 0x000FFC4D, 0x001AADBA, 0x0019B4EB, 0x00177E18, 0x0017FBA7, 0x000304D4, 0x00225310, 0x0014FC9C, 0x000304D9, 0x00225321, 0x0015B87C, 0x00179734, 0x0007814E, 0x00144F59, 0x000304E7, 0x00149E7A, 0x001768F6, 0x00086361, 0x00179F6B, 0x00179788, 0x00087761, 0x00140526, 0x000E7515, 0x00225333, 0x00176446, 0x0003059A, 0x0017C9DA, 0x000AEFA1, 0x00135762, 0x0017B44D, 0x0019B4DC, 0x00176491, 0x001A6AA4, 0x001AB266, 0x0017977C, 0x00166AC0, 0x000ED746, 0x0023C656, 0x00178EEA, 0x00144F38, 0x00179737, 0x000E6201, 0x00179750, 0x001684A7, 0x000304CD, 0x00185030, 0x000E0BC7, 0x000E6F64, 0x00179769, 0x000C450E, 0x000E0BC8, 0x0017FBAB, 0x000A8220, 0x0017B467, 0x000370FE, 0x0014051B, 0x0007814A, 0x00177E1F, 0x00178EE8, 0x0006566E, 0x001768F8, 0x00037104, 0x000304C7, 0x000E4786, 0x0017B479, 0x0017C9CB, 0x000A600D, 0x000C2B72, 0x00179797, 0x0014052A, 0x00176486, 0x001651EF, 0x00176453, 0x0011FBC2, 0x000A823B, 0x0015FA42, 0x0018502C, 0x0019B4E3, 0x00225319, 0x0013576D, 0x0019B4D6, 0x001764A8, 0x00147713, 0x00087753, 0x0017B43D, 0x0017648E, 0x00135758, 0x00140519, 0x00047267, 0x0017C9D4, 0x001A6AA2, 0x00140538, 0x001797C9, 0x001A7666, 0x00140537, 0x0017B477, 0x001797AA, 0x000CF6C1, 0x0017B46C, 0x000304E3, 0x0017C9AA, 0x0017973A, 0x00144F4C, 0x000304CC, 0x0001FDBB, 0x00176470, 0x00176460, 0x001578A2, 0x00176454, 0x000304DD, 0x00061542, 0x00193ADA, 0x0003D180, 0x0012C0B2, 0x0019B4E5, 0x000C370E, 0x001FDE74, 0x0008774C, 0x0017B44F, 0x000E61B2, 0x000F47FF, 0x00179F6D, 0x000EDF49, 0x000304EC, 0x00176449, 0x001768D3, 0x00144F3A, 0x001768E9, 0x00176479, 0x0023B89A, 0x00086354, 0x00061549, 0x00178EF6, 0x00178EF0, 0x0003D181, 0x0023C666, 0x001651EE, 0x0023C655, 0x00036FDE, 0x0016042F, 0x000FF3EA, 0x00179751, 0x001764B0, 0x001A0D1C, 0x00166AB5, 0x00087750, 0x00144F49, 0x00112D1A, 0x00182EAA, 0x00061543, 0x001768EA, 0x0023C660, 0x001797BE, 0x0023C66B, 0x000A8239, 0x00061554, 0x0017C9FC, 0x0017975F, 0x001813B3, 0x00036FCD, 0x000ED741, 0x00037101, 0x00178ED7, 0x000AA789, 0x000A8223, 0x00037116, 0x000E7513, 0x000FF3E9, 0x00176490, 0x000D3E9F, 0x00174F85, 0x00144F42, 0x000E2128, 0x00166ABB, 0x001768FA, 0x00036FD7, 0x00179762, 0x00144A53, 0x0014DC52, 0x000FF3E0, 0x0018501D, 0x0023C659, 0x000304DA, 0x001D2862, 0x000E61F8, 0x00179782, 0x00176477, 0x00107B0B, 0x00036FD5, 0x0017C99F, 0x00178EF1, 0x000304C5, 0x0017977F, 0x00186052, 0x00174F87, 0x000304C8, 0x000FFF5C, 0x00185033, 0x0017FBB0, 0x0023C653, 0x0017978E, 0x001797C1, 0x001D2861, 0x0014771A, 0x000ED4E6, 0x00179773, 0x000780CD, 0x00178EE4, 0x00147726, 0x0017C995, 0x0008774E, 0x00178EDE, 0x001797A6, 0x001ABC01, 0x00178EF2, 0x0008775D, 0x0003710C, 0x0004724F, 0x0018E384, 0x001768F0, 0x00036FDB, 0x000553BE, 0x001832A6, 0x0008635D, 0x00149E6D, 0x00140530, 0x0017646C, 0x0004725E, 0x001768DF, 0x0017B440, 0x000D3E92, 0x000A3759, 0x000E0BC5, 0x00087765, 0x001797C7, 0x00177E39, 0x0022531F, 0x000304ED, 0x00186050, 0x000304E5, 0x0017973D, 0x0017FB9D, 0x00217A8C, 0x0017C9B5, 0x000E37C4, 0x0023C65B, 0x00062BCE, 0x00179765, 0x0012A210, 0x0014771C, 0x000304CA, 0x0015F078, 0x001A6AA3, 0x0017B476, 0x00179768, 0x0017C9A4, 0x00087766, 0x00179F70, 0x00176464, 0x00185014, 0x00179799, 0x00176478, 0x00144F43, 0x00176473, 0x0017C9B1, 0x00225320, 0x000E7514, 0x00177E3E, 0x0017FB95, 0x000AA788, 0x000D3EA2, 0x0017972A, 0x000A600E, 0x00037114, 0x000A8242, 0
|
||
|
|
};
|
||
|
|
|
||
|
|
for (UInt32 topicInfoID : topicInfoIDs)
|
||
|
|
{
|
||
|
|
/*switch (topicInfoID)
|
||
|
|
{
|
||
|
|
case 0x2A191: case 0x2A192: case 0x4337F: case 0x43380: case 0x43381: case 0x43382: case 0x43383: case 0x43384: case 0x43385: case 0xAF8F3: case 0xAF8F4: case 0xAF8F5:
|
||
|
|
continue;
|
||
|
|
}*/
|
||
|
|
|
||
|
|
if (existingTopicInfoIDs.count(topicInfoID) > 0)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
TESForm* topicInfo = (TESForm*)Runtime_DynamicCast(LookupFormByID(topicInfoID), RTTI_TESForm, RTTI_TESTopicInfo);
|
||
|
|
if (!topicInfo)
|
||
|
|
{
|
||
|
|
self.topicInfoRemainders.push_back(topicInfoID);
|
||
|
|
//printf("%X ", topicInfoID);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
topicInfos.push_back(topicInfo);
|
||
|
|
existingTopicInfoIDs.insert(topicInfoID);
|
||
|
|
}
|
||
|
|
|
||
|
|
printf("%llu %llu\n", sizeof(topicInfoIDs) / sizeof(UInt32), topicInfos.size());
|
||
|
|
|
||
|
|
tArray<TESForm*>& topics = (*g_dataHandler)->arrDIAL;
|
||
|
|
|
||
|
|
for (UInt32 i = 0; i < topics.count; i++)
|
||
|
|
{
|
||
|
|
UInt8* ptr = (UInt8*)topics[i];
|
||
|
|
TESForm**& infos = *(TESForm***)(ptr + sizeof(TESForm) + 48);
|
||
|
|
UInt32& count1 = *(UInt32*)(ptr + sizeof(TESForm) + 64);
|
||
|
|
UInt32& count2 = *(UInt32*)(ptr + sizeof(TESForm) + 68);
|
||
|
|
|
||
|
|
for (UInt32 j = 0; j < count1; j++)
|
||
|
|
{
|
||
|
|
TESForm* info = (TESForm*)Runtime_DynamicCast(infos[j], RTTI_TESForm, RTTI_TESTopicInfo);
|
||
|
|
if (!info)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (existingTopicInfoIDs.count(info->formID) > 0)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
topicInfos.push_back(info);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
printf("%llu %llu\n", sizeof(topicInfoIDs) / sizeof(UInt32), topicInfos.size());
|
||
|
|
|
||
|
|
VMArray<TESForm*> topicInfoArray(topicInfos);
|
||
|
|
|
||
|
|
// TODO: disabled for testing.
|
||
|
|
/*GetInstance().papyrus->GetExternalEventRegistrations("OnTopicInfoRegister", &topicInfoArray, [](UInt64 handle, const char* scriptName, const char* callbackName, void* dataPtr)
|
||
|
|
{
|
||
|
|
VMArray<TESForm*>* topicInfos = (VMArray<TESForm*>*)dataPtr;
|
||
|
|
SendPapyrusEvent1(handle, scriptName, callbackName, *topicInfos);
|
||
|
|
});*/
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool f4mp::F4MP::Disconnect(StaticFunctionTag* base)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
librg_network_stop(&self.ctx);
|
||
|
|
|
||
|
|
_MESSAGE("successfully ended the network connection");
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::Tick(StaticFunctionTag* base)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
//librg_tick(&self.ctx);
|
||
|
|
|
||
|
|
std::vector<TESForm*> newTopicInfos;
|
||
|
|
|
||
|
|
for (auto it = self.topicInfoRemainders.begin(); it != self.topicInfoRemainders.end();)
|
||
|
|
{
|
||
|
|
TESForm* topicInfo = (TESForm*)Runtime_DynamicCast(LookupFormByID(*it), RTTI_TESForm, RTTI_TESTopicInfo);
|
||
|
|
if (!topicInfo)
|
||
|
|
{
|
||
|
|
it++;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
newTopicInfos.push_back(topicInfo);
|
||
|
|
it = self.topicInfoRemainders.erase(it);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (newTopicInfos.size() > 0)
|
||
|
|
{
|
||
|
|
printf("new topic infos: %llu\n", newTopicInfos.size());
|
||
|
|
|
||
|
|
VMArray<TESForm*> topicInfoArray(newTopicInfos);
|
||
|
|
|
||
|
|
GetInstance().papyrus->GetExternalEventRegistrations("OnAdditionalTopicInfoRegister", &topicInfoArray, [](UInt64 handle, const char* scriptName, const char* callbackName, void* dataPtr)
|
||
|
|
{
|
||
|
|
VMArray<TESForm*>* topicInfos = (VMArray<TESForm*>*)dataPtr;
|
||
|
|
SendPapyrusEvent1(handle, scriptName, callbackName, *topicInfos);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
for (auto& instance : instances)
|
||
|
|
{
|
||
|
|
// this part is essential
|
||
|
|
if (librg_is_connected(&instance->ctx))
|
||
|
|
{
|
||
|
|
SyncWorld(base);
|
||
|
|
}
|
||
|
|
|
||
|
|
librg_tick(&instance->ctx);
|
||
|
|
}
|
||
|
|
|
||
|
|
activeInstance = nextActiveInstance;
|
||
|
|
|
||
|
|
while (instances.size() <= activeInstance)
|
||
|
|
{
|
||
|
|
instances.push_back(std::make_unique<F4MP>());
|
||
|
|
instances.back()->player = std::make_unique<Player>();
|
||
|
|
|
||
|
|
F4MP& mainInstance = *instances.front();
|
||
|
|
F4MP& newInstance = *instances.back();
|
||
|
|
newInstance.messaging = mainInstance.messaging;
|
||
|
|
newInstance.papyrus = mainInstance.papyrus;
|
||
|
|
newInstance.task = mainInstance.task;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::SyncWorld(StaticFunctionTag* base)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
if (!librg_is_connected(&self.ctx))
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const float syncRadius = 500.f;
|
||
|
|
const float syncRadiusSqr = syncRadius * syncRadius;
|
||
|
|
|
||
|
|
PlayerCharacter* player = *g_player;
|
||
|
|
auto& refs = player->parentCell->objectList;
|
||
|
|
const NiPoint3& playerPos = player->pos;
|
||
|
|
|
||
|
|
std::list<UInt32> newBuildings;
|
||
|
|
|
||
|
|
for (UInt32 i = 0; i < refs.count; i++)
|
||
|
|
{
|
||
|
|
TESObjectREFR* ref = refs[i];
|
||
|
|
Actor* actor = DYNAMIC_CAST(ref, TESForm, Actor);
|
||
|
|
bool isActor = actor != nullptr;
|
||
|
|
|
||
|
|
if ((ref->formID & 0xff000000) == 0xff000000)
|
||
|
|
{
|
||
|
|
if (ref->baseForm->formType == 36 && !(ref->flags & TESObjectREFR::kFlag_IsDeleted))
|
||
|
|
{
|
||
|
|
// 36 means static objects, right?
|
||
|
|
if (ref->baseForm && ref->baseForm->formType == 36 && self.knownBuildings.count(ref->formID) == 0)
|
||
|
|
{
|
||
|
|
newBuildings.push_back(ref->formID);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (isActor)
|
||
|
|
{
|
||
|
|
if (ref == player || ref->baseForm == nullptr)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (self.entityIDs.find(ref->formID) == self.entityIDs.end())
|
||
|
|
{
|
||
|
|
self.entityIDs[ref->formID] = (UInt32)-1;
|
||
|
|
|
||
|
|
TESFullName* fullName = DYNAMIC_CAST(ref->baseForm, TESForm, TESFullName);
|
||
|
|
if (fullName == nullptr || strstr(fullName->name.c_str(), "F4MP") != nullptr)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
SpawnEntityData data{ ref->formID, (zpl_vec3&)ref->pos, ToDegrees((zpl_vec3&)ref->rot) };
|
||
|
|
librg_message_send_all(&self.ctx, MessageType::SpawnEntity, &data, sizeof(SpawnEntityData));
|
||
|
|
|
||
|
|
printf("%s(%x)\n", fullName->name.c_str(), data.formID);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
NiPoint3 dist = playerPos - ref->pos;
|
||
|
|
float distSqr = zpl_vec3_mag2({ dist.x, dist.y, dist.z });
|
||
|
|
|
||
|
|
if (distSqr < syncRadiusSqr)
|
||
|
|
{
|
||
|
|
SyncEntityData data{ ref->formID, (zpl_vec3&)ref->pos, ToDegrees((zpl_vec3&)ref->rot), librg_time_now(&self.ctx) };
|
||
|
|
librg_message_send_all(&self.ctx, MessageType::SyncEntity, &data, sizeof(SyncEntityData));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// TODO: optimize to work by events
|
||
|
|
|
||
|
|
const float epsilon = 1e-8f;
|
||
|
|
|
||
|
|
if (newBuildings.size() > 0)
|
||
|
|
{
|
||
|
|
for (auto it = newBuildings.begin(); it != newBuildings.end();)
|
||
|
|
{
|
||
|
|
TESObjectREFR* ref = DYNAMIC_CAST(LookupFormByID(*it), TESForm, TESObjectREFR);
|
||
|
|
if (!ref || (ref->flags & TESObjectREFR::kFlag_IsDeleted))
|
||
|
|
{
|
||
|
|
it = newBuildings.erase(it);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// HACK: find the real solution, not the way around it!
|
||
|
|
/*if (std::find_if(self.buildings.begin(), self.buildings.end(), [&](const std::pair<UInt64, TransformData>& building)
|
||
|
|
{
|
||
|
|
TESObjectREFR* known = DYNAMIC_CAST(LookupFormByID(building.second.formID), TESForm, TESObjectREFR);
|
||
|
|
if (!known)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return zpl_vec3_mag2((zpl_vec3&)known->pos - (zpl_vec3&)ref->pos) < epsilon;
|
||
|
|
}) != self.buildings.end())
|
||
|
|
{
|
||
|
|
it = newBuildings.erase(it);
|
||
|
|
continue;
|
||
|
|
}*/
|
||
|
|
|
||
|
|
SpawnBuildingData data{ self.player->GetEntityID(), ref->formID, ref->baseForm->formID, (zpl_vec3&)ref->pos, ToDegrees((zpl_vec3&)ref->rot) };
|
||
|
|
librg_message_send_all(&self.ctx, MessageType::SpawnBuilding, &data, sizeof(SpawnBuildingData));
|
||
|
|
|
||
|
|
for (auto& instance : instances)
|
||
|
|
{
|
||
|
|
instance->buildings[GetUniqueFormID(data.ownerEntityID, data.formID)] = { data.formID, data.position, data.angles };
|
||
|
|
instance->knownBuildings.insert(data.formID);
|
||
|
|
}
|
||
|
|
|
||
|
|
it++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for (auto it = self.buildings.begin(); it != self.buildings.end();)
|
||
|
|
{
|
||
|
|
UInt64 uniqueID = it->first;
|
||
|
|
UInt32 formID = it->second.formID;
|
||
|
|
TESObjectREFR* ref = DYNAMIC_CAST(LookupFormByID(formID), TESForm, TESObjectREFR);
|
||
|
|
|
||
|
|
if (ref)
|
||
|
|
{
|
||
|
|
if (zpl_vec3_mag2((zpl_vec3&)ref->pos - it->second.position) > epsilon || zpl_vec3_mag2(ToDegrees((zpl_vec3&)ref->rot) - it->second.angles) > epsilon)
|
||
|
|
{
|
||
|
|
// HACK: 0 for just a transform update.
|
||
|
|
SpawnBuildingData data{ self.player->GetEntityID(), ref->formID, 0/*ref->baseForm->formID*/, (zpl_vec3&)ref->pos, ToDegrees((zpl_vec3&)ref->rot) };
|
||
|
|
librg_message_send_all(&self.ctx, MessageType::SpawnBuilding, &data, sizeof(SpawnBuildingData));
|
||
|
|
|
||
|
|
it->second.position = data.position;
|
||
|
|
it->second.angles = data.angles;
|
||
|
|
}
|
||
|
|
it++;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
RemoveBuildingData data{ uniqueID };
|
||
|
|
librg_message_send_all(&self.ctx, MessageType::RemoveBuilding, &data, sizeof(RemoveBuildingData));
|
||
|
|
|
||
|
|
it = self.buildings.erase(it);
|
||
|
|
|
||
|
|
for (auto& instance : instances)
|
||
|
|
{
|
||
|
|
instance->buildings.erase(uniqueID);
|
||
|
|
instance->knownBuildings.erase(formID);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
UInt32 f4mp::F4MP::GetPlayerEntityID(StaticFunctionTag* base)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
return self.player->GetEntityID();
|
||
|
|
}
|
||
|
|
|
||
|
|
UInt32 f4mp::F4MP::GetEntityID(StaticFunctionTag* base, TESObjectREFR* ref)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
auto entityID = self.entityIDs.find(ref->formID);
|
||
|
|
if (entityID == self.entityIDs.end())
|
||
|
|
{
|
||
|
|
return (UInt32)-1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return entityID->second;
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::SetEntityRef(StaticFunctionTag* base, UInt32 entityID, TESObjectREFR* ref)
|
||
|
|
{
|
||
|
|
Entity* entity = Entity::Get(GetInstance().FetchEntity(entityID));
|
||
|
|
if (!entity)
|
||
|
|
{
|
||
|
|
throw std::exception();
|
||
|
|
}
|
||
|
|
|
||
|
|
entity->SetRef(ref);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool f4mp::F4MP::IsEntityValid(StaticFunctionTag* base, UInt32 entityID)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
return !!librg_entity_valid(&self.ctx, entityID);
|
||
|
|
}
|
||
|
|
|
||
|
|
VMArray<Float32> f4mp::F4MP::GetEntityPosition(StaticFunctionTag* base, UInt32 entityID)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
std::vector<Float32> result{ -1, -1, -1 };
|
||
|
|
|
||
|
|
librg_entity* entity = self.FetchEntity(entityID);
|
||
|
|
if (!entity)
|
||
|
|
{
|
||
|
|
return VMArray<Float32>(result);
|
||
|
|
}
|
||
|
|
|
||
|
|
result = { entity->position.x, entity->position.y, entity->position.z };
|
||
|
|
return VMArray<Float32>(result);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::SetEntityPosition(StaticFunctionTag* base, UInt32 entityID, float x, float y, float z)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
librg_entity* entity = self.FetchEntity(entityID);
|
||
|
|
if (!entity)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// HACK: temporary
|
||
|
|
|
||
|
|
if (entityID == self.player->GetEntityID())
|
||
|
|
{
|
||
|
|
for (auto& instance : instances)
|
||
|
|
{
|
||
|
|
if (!librg_is_connected(&instance->ctx))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
entity = instance->FetchEntity(instance->player->GetEntityID());
|
||
|
|
entity->position.x = x;
|
||
|
|
entity->position.y = y;
|
||
|
|
entity->position.z = z;
|
||
|
|
}
|
||
|
|
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
entity->position.x = x;
|
||
|
|
entity->position.y = y;
|
||
|
|
entity->position.z = z;
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::SetEntVarNum(StaticFunctionTag* base, UInt32 entityID, BSFixedString name, Float32 value)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
Player* player = Entity::GetAs<Player>(self.FetchEntity(entityID));
|
||
|
|
if (!player)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
player->SetNumber(name.c_str(), value);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::SetEntVarAnim(StaticFunctionTag* base, UInt32 entityID, BSFixedString animState)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
Player* player = Entity::GetAs<Player>(self.FetchEntity(entityID));
|
||
|
|
if (!player)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
player->SetAnimState(animState.c_str());
|
||
|
|
}
|
||
|
|
|
||
|
|
Float32 f4mp::F4MP::GetEntVarNum(StaticFunctionTag* base, UInt32 entityID, BSFixedString name)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
Player* player = Entity::GetAs<Player>(self.FetchEntity(entityID));
|
||
|
|
if (!player)
|
||
|
|
{
|
||
|
|
return 0.f;
|
||
|
|
}
|
||
|
|
|
||
|
|
return player->GetNumber(name.c_str());
|
||
|
|
}
|
||
|
|
|
||
|
|
BSFixedString f4mp::F4MP::GetEntVarAnim(StaticFunctionTag* base, UInt32 entityID)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
Player* player = Entity::GetAs<Player>(self.FetchEntity(entityID));
|
||
|
|
if (!player)
|
||
|
|
{
|
||
|
|
return Animator::GetStateName(0).c_str();
|
||
|
|
}
|
||
|
|
|
||
|
|
return player->GetAnimState().c_str();
|
||
|
|
}
|
||
|
|
|
||
|
|
VMArray<TESObjectREFR*> f4mp::F4MP::GetRefsInCell(StaticFunctionTag* base, TESObjectCELL* cell)
|
||
|
|
{
|
||
|
|
auto refs = std::vector<TESObjectREFR*>(&cell->objectList.entries[0], &cell->objectList.entries[cell->objectList.count]);
|
||
|
|
return refs;
|
||
|
|
}
|
||
|
|
|
||
|
|
Float32 f4mp::F4MP::Atan2(StaticFunctionTag* base, Float32 y, Float32 x)
|
||
|
|
{
|
||
|
|
Float32 angle = atan2(y, x);
|
||
|
|
return angle - floor(angle / ZPL_TAU) * ZPL_TAU;
|
||
|
|
}
|
||
|
|
|
||
|
|
BSFixedString f4mp::F4MP::GetWalkDir(StaticFunctionTag* base, Float32 dX, Float32 dY, Float32 angleZ)
|
||
|
|
{
|
||
|
|
switch (Player::GetWalkDir(zpl_vec2{ dX, dY }, angleZ))
|
||
|
|
{
|
||
|
|
case 0:
|
||
|
|
return "Forward";
|
||
|
|
case 1:
|
||
|
|
return "ForwardRight";
|
||
|
|
case 2:
|
||
|
|
return "Right";
|
||
|
|
case 3:
|
||
|
|
return "BackwardRight";
|
||
|
|
case 4:
|
||
|
|
return "Backward";
|
||
|
|
case 5:
|
||
|
|
return "BackwardLeft";
|
||
|
|
case 6:
|
||
|
|
return "Left";
|
||
|
|
case 7:
|
||
|
|
return "ForwardLeft";
|
||
|
|
default:
|
||
|
|
return "None";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool f4mp::F4MP::AnimLoops(StaticFunctionTag* base, BSFixedString animState)
|
||
|
|
{
|
||
|
|
return Animator::Loops(Animator::GetStateID(animState.c_str()));
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::CopyAppearance(StaticFunctionTag* base, TESNPC* src, TESNPC* dest)
|
||
|
|
{
|
||
|
|
client::AppearanceData appearance;
|
||
|
|
appearance.Fill(src);
|
||
|
|
Player::SetAppearance(dest, appearance);
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::CopyWornItems(StaticFunctionTag* base, Actor* src, Actor* dest)
|
||
|
|
{
|
||
|
|
client::WornItemsData wornItems;
|
||
|
|
wornItems.Fill(src);
|
||
|
|
Player::SetWornItems(dest, wornItems);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*static void CopyHeardPart(BGSHeadPart* src, BGSHeadPart*& dest)
|
||
|
|
{
|
||
|
|
dest = (BGSHeadPart*)Heap_Allocate(sizeof(BGSHeadPart));
|
||
|
|
memset(dest, 0, sizeof(BGSHeadPart));
|
||
|
|
|
||
|
|
*dest = *src;
|
||
|
|
|
||
|
|
return;
|
||
|
|
|
||
|
|
dest->fullName = src->fullName;
|
||
|
|
dest->partFlags = src->partFlags;
|
||
|
|
dest->type = src->type;
|
||
|
|
|
||
|
|
for (UInt32 i = 0; i < src->extraParts.count; i++)
|
||
|
|
{
|
||
|
|
BGSHeadPart* extra;
|
||
|
|
CopyHeardPart(src->extraParts[i], extra);
|
||
|
|
dest->extraParts.Push(extra);
|
||
|
|
}
|
||
|
|
|
||
|
|
return;
|
||
|
|
|
||
|
|
src->textureSet = dest->textureSet;
|
||
|
|
|
||
|
|
//dest->model = src->model;
|
||
|
|
//std::copy(&src->morphs[0], &src->morphs[3], &dest->morphs[0]);
|
||
|
|
|
||
|
|
dest->unk158 = src->unk158;
|
||
|
|
dest->partName = src->partName;
|
||
|
|
}*/
|
||
|
|
|
||
|
|
void f4mp::F4MP::PlayerHit(StaticFunctionTag* base, UInt32 hitter, UInt32 hittee, Float32 damage)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
HitData data{ hitter, hittee, damage };
|
||
|
|
|
||
|
|
librg_message_send_all(&self.ctx, MessageType::Hit, &data, sizeof(HitData));
|
||
|
|
}
|
||
|
|
|
||
|
|
void f4mp::F4MP::PlayerFireWeapon(StaticFunctionTag* base)
|
||
|
|
{
|
||
|
|
F4MP& self = GetInstance();
|
||
|
|
|
||
|
|
UInt32 playerEntityID = self.player->GetEntityID();
|
||
|
|
librg_message_send_all(&self.ctx, MessageType::FireWeapon, &playerEntityID, sizeof(UInt32));
|
||
|
|
}
|