Skip to content

Commit

Permalink
feat(skymp5-server): add FindClosestReferenceOfTypeFromRef Papyrus na…
Browse files Browse the repository at this point in the history
…tive (#2159)
  • Loading branch information
Pospelove authored Sep 12, 2024
1 parent 291ea9f commit 0e5dc79
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 23 deletions.
35 changes: 35 additions & 0 deletions misc/tests/test_findclosestreferenceoftypefromref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const assert = require("node:assert");

const main = async () => {
const akFormToPlace = { type: 'espm', desc: mp.getDescFromId(0x7) };
const barrelInWhiterun = 0x4cc2d;
const barrelObject = { type: 'form', desc: mp.getDescFromId(barrelInWhiterun) };

const barrelBaseForm = { type: 'espm', desc: mp.get(barrelInWhiterun, 'baseDesc') };

let searchRes;

// expect no actor to be found around the barrel
searchRes = mp.callPapyrusFunction("global", "Game", "FindClosestReferenceOfTypeFromRef", null, [akFormToPlace, barrelObject, 100]);
assert.deepEqual(searchRes, null);

// placing
const placedActor = mp.callPapyrusFunction("method", "ObjectReference", "PlaceAtMe", barrelObject, [akFormToPlace, 1, true, false]);

// expect the actor to be found around the barrel
searchRes = mp.callPapyrusFunction("global", "Game", "FindClosestReferenceOfTypeFromRef", null, [akFormToPlace, barrelObject, 100]);
assert.deepEqual(searchRes, placedActor);

// vice-versa
searchRes = mp.callPapyrusFunction("global", "Game", "FindClosestReferenceOfTypeFromRef", null, [barrelBaseForm, placedActor, 100]);
assert.deepEqual(searchRes, barrelObject);
};

main().then(() => {
console.log("Test passed!");
process.exit(0);
}).catch((err) => {
console.log("Test failed!")
console.error(err);
process.exit(1);
});
4 changes: 0 additions & 4 deletions skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -932,10 +932,6 @@ void MpObjectReference::Subscribe(MpObjectReference* emitter,
return;
}

// I don't know how often Subscrbe is called but I suppose
// it is to be invoked quite frequently. In this case, each
// time if below is performed we are obtaining a copy of
// MpChangeForm which can be large. See what it consists of.
if (!emitter->pImpl->onInitEventSent &&
listener->GetChangeForm().profileId != -1) {
emitter->pImpl->onInitEventSent = true;
Expand Down
83 changes: 64 additions & 19 deletions skymp5-server/cpp/server_guest_lib/script_classes/PapyrusGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,49 @@ bool ExistsInFormList(const VarValue& formList, uint32_t baseId)
}
}

namespace {
VarValue FindClosestReferenceHelper(
MpObjectReference* arCenter, double afRadius,
std::function<bool(MpObjectReference*)> criteria)
{
if (!arCenter) {
spdlog::warn("FindClosestReferenceHelper - arCenter is nullptr");
return VarValue::None();
}

// why not < 0? because of NaN
if (!(afRadius >= 0)) {
spdlog::warn("FindClosestReferenceHelper - expected afRadius to be >= 0");
return VarValue::None();
}

float bestDistance = std::numeric_limits<float>::infinity();
MpObjectReference* bestNeighbour = nullptr;

arCenter->VisitNeighbours([&](MpObjectReference* neighbour) {
if (!criteria(neighbour)) {
return;
}

float distance = (arCenter->GetPos() - neighbour->GetPos()).SqrLength();
if (distance > afRadius * afRadius) {
return;
}

if (bestDistance > distance) {
bestDistance = distance;
bestNeighbour = neighbour;
}
});

if (bestNeighbour) {
return VarValue(std::make_shared<MpFormGameObject>(bestNeighbour));
}

return VarValue::None();
}
}

VarValue PapyrusGame::FindClosestReferenceOfAnyTypeInListFromRef(
VarValue self, const std::vector<VarValue>& arguments)
{
Expand All @@ -36,29 +79,29 @@ VarValue PapyrusGame::FindClosestReferenceOfAnyTypeInListFromRef(
auto arCenter = GetFormPtr<MpObjectReference>(arguments[1]);
double afRadius = static_cast<double>(arguments[2].CastToFloat());

if (arBaseObjects && arCenter && afRadius >= 0) {

float bestDistance = std::numeric_limits<float>::infinity();
MpObjectReference* bestNeighbour = nullptr;

arCenter->VisitNeighbours([&](MpObjectReference* neighbour) {
return FindClosestReferenceHelper(
arCenter, afRadius, [&](MpObjectReference* neighbour) {
auto baseId = neighbour->GetBaseId();
if (!ExistsInFormList(arBaseObjects, baseId))
return;
return ExistsInFormList(arBaseObjects, baseId);
});
}
return VarValue::None();
}

float distance = (arCenter->GetPos() - neighbour->GetPos()).Length();
if (distance > afRadius)
return;
VarValue PapyrusGame::FindClosestReferenceOfTypeFromRef(
VarValue self, const std::vector<VarValue>& arguments)
{
if (arguments.size() >= 3) {
auto arBaseObject = GetRecordPtr(arguments[0]);
auto arCenter = GetFormPtr<MpObjectReference>(arguments[1]);
double afRadius = static_cast<double>(arguments[2].CastToFloat());

if (bestDistance > distance) {
bestDistance = distance;
bestNeighbour = neighbour;
}
return FindClosestReferenceHelper(
arCenter, afRadius, [&](MpObjectReference* neighbour) {
auto baseId = neighbour->GetBaseId();
return arBaseObject.rec &&
baseId == arBaseObject.ToGlobalId(arBaseObject.rec->GetId());
});

if (bestNeighbour)
return VarValue(std::make_shared<MpFormGameObject>(bestNeighbour));
}
}
return VarValue::None();
}
Expand Down Expand Up @@ -184,6 +227,8 @@ void PapyrusGame::Register(VirtualMachine& vm,
AddStatic(vm, "EnablePlayerControls", &PapyrusGame::EnablePlayerControls);
AddStatic(vm, "FindClosestReferenceOfAnyTypeInListFromRef",
&PapyrusGame::FindClosestReferenceOfAnyTypeInListFromRef);
AddStatic(vm, "FindClosestReferenceOfTypeFromRef",
&PapyrusGame::FindClosestReferenceOfTypeFromRef);
AddStatic(vm, "GetPlayer", &PapyrusGame::GetPlayer);
AddStatic(vm, "ShowRaceMenu", &PapyrusGame::ShowRaceMenu);
AddStatic(vm, "ShowLimitedRaceMenu", &PapyrusGame::ShowLimitedRaceMenu);
Expand Down
11 changes: 11 additions & 0 deletions skymp5-server/cpp/server_guest_lib/script_classes/PapyrusGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,19 @@ class PapyrusGame final : public IPapyrusClass<PapyrusGame>

VarValue IncrementStat(VarValue self,
const std::vector<VarValue>& arguments);

// In Skyrim it's not a native, just a wrapper around
// FindClosestReferenceOfAnyTypeInList
// TODO: implement FindClosestReferenceOfAnyTypeInList native instead
VarValue FindClosestReferenceOfAnyTypeInListFromRef(
VarValue self, const std::vector<VarValue>& arguments);

// In Skyrim it's not a native, just a wrapper around
// FindClosestReferenceOfType
// TODO: implement FindClosestReferenceOfType native instead
VarValue FindClosestReferenceOfTypeFromRef(
VarValue self, const std::vector<VarValue>& arguments);

VarValue GetPlayer(VarValue self, const std::vector<VarValue>& arguments);
VarValue ShowRaceMenu(VarValue self, const std::vector<VarValue>& arguments);
VarValue ShowLimitedRaceMenu(VarValue self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,8 @@ VarValue PapyrusObjectReference::PlaceAtMe(

bool isExplosion = akFormToPlace.rec->GetType() == "EXPL";
if (isExplosion) {
spdlog::warn(
"PapyrusObjectReference::PlaceAtMe - explosion is not supported yet");
// Well sp snippet fails ATM. and I don't want to overpollute clients and
// network with those placeatme s for now

Expand Down

0 comments on commit 0e5dc79

Please sign in to comment.