Skip to content

Commit

Permalink
feat(skymp5-server): add onEatItem, onDropItem gamemode events & impl…
Browse files Browse the repository at this point in the history
…ement Potion.IsFood (#2085)
  • Loading branch information
Pospelove authored Aug 5, 2024
1 parent a4afff6 commit 5373446
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 42 deletions.
4 changes: 3 additions & 1 deletion libespm/include/libespm/ALCH.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ class ALCH final : public RecordHeader
struct Data
{
std::vector<Effects::Effect> effects;
float weight;
float weight = 0.f;
bool isFood = false;
bool isPoison = false;
};

Data GetData(CompressedFieldsCache& compressedFieldsCache) const noexcept;
Expand Down
6 changes: 6 additions & 0 deletions libespm/src/ALCH.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ ALCH::Data ALCH::GetData(
if (!std::memcmp(type, "DATA", 4)) {
result.weight = *reinterpret_cast<const float*>(data);
}
if (!std::memcmp(type, "ENIT", 4)) {
const auto flags = *reinterpret_cast<const uint32_t*>(data + 0x4);
// Other flags are not used in Skyrim
result.isFood = flags & 0x00002;
result.isPoison = flags & 0x20000;
}
},
compressedFieldsCache);
return result;
Expand Down
96 changes: 62 additions & 34 deletions skymp5-server/cpp/server_guest_lib/MpActor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -850,8 +850,56 @@ bool MpActor::MpApiCraft(uint32_t craftedItemBaseId, uint32_t count,
return !isCraftBlocked;
}

bool MpActor::MpApiDropItem(uint32_t baseId, uint32_t count)
{
simdjson::dom::parser parser;
bool isDropItemBlocked = false;

std::string s =
"[" + std::to_string(baseId) + "," + std::to_string(count) + "]";
auto args = parser.parse(s).value();

if (auto wst = GetParent()) {
const auto id = GetFormId();
for (auto& listener : wst->listeners) {
if (listener->OnMpApiEvent("onDropItem", args, id) == false) {
isDropItemBlocked = true;
};
}
}

return !isDropItemBlocked;
}

bool MpActor::MpApiEatItem(uint32_t baseId)
{
simdjson::dom::parser parser;
bool isEatItemBlocked = false;

std::string s = "[" + std::to_string(baseId) + "]";
auto args = parser.parse(s).value();

if (auto wst = GetParent()) {
const auto id = GetFormId();
for (auto& listener : wst->listeners) {
if (listener->OnMpApiEvent("onEatItem", args, id) == false) {
isEatItemBlocked = true;
};
}
}

return !isEatItemBlocked;
}

void MpActor::EatItem(uint32_t baseId, espm::Type t)
{
if (!MpApiEatItem(baseId)) {
spdlog::info(
"MpActor::DropItem {:x} - blocked by MpApiEatItem (baseId={:x})",
GetFormId(), baseId);
return;
}

auto espmProvider = GetParent();
std::vector<espm::Effects::Effect> effects;
if (t == "ALCH") {
Expand Down Expand Up @@ -902,16 +950,6 @@ bool MpActor::ReadBook(const uint32_t baseId)
return false;
}

bool MpActor::CanActorValueBeRestored(espm::ActorValue av)
{
if (std::chrono::steady_clock::now() - GetLastRestorationTime(av) <
std::chrono::minutes(1)) {
return false;
}
SetLastRestorationTime(av, std::chrono::steady_clock::now());
return true;
}

void MpActor::EnsureTemplateChainEvaluated(espm::Loader& loader,
ChangeFormGuard::Mode mode)
{
Expand Down Expand Up @@ -1039,18 +1077,6 @@ std::map<uint32_t, uint32_t> MpActor::EvaluateDeathItem()
return map;
}

std::chrono::steady_clock::time_point MpActor::GetLastRestorationTime(
espm::ActorValue av) const noexcept
{
return pImpl->restorationTimePoints[av];
}

void MpActor::SetLastRestorationTime(
espm::ActorValue av, std::chrono::steady_clock::time_point timePoint)
{
pImpl->restorationTimePoints[av] = timePoint;
}

void MpActor::ModifyActorValuePercentage(espm::ActorValue av,
float percentageDelta)
{
Expand Down Expand Up @@ -1367,12 +1393,19 @@ void MpActor::DropItem(const uint32_t baseId, const Inventory::Entry& entry)

constexpr uint32_t kGold001 = 0x0000000f;
if (baseId == kGold001) {
spdlog::warn("MpActor::DropItem - Attempt to drop Gold001 by actor {:x}",
spdlog::warn("MpActor::DropItem {:x} - Attempt to drop Gold001 by actor",
GetFormId());
return;
}

int count = entry.count;
const int count = entry.count;

if (!MpApiDropItem(baseId, count)) {
spdlog::info("MpActor::DropItem {:x} - blocked by MpApiDropItem "
"(baseId={:x}, count={})",
GetFormId(), baseId, count);
return;
}

auto worldState = GetParent();

Expand Down Expand Up @@ -1586,22 +1619,17 @@ void MpActor::ApplyMagicEffect(espm::Effects::Effect& effect, bool hasSweetpie,

if (isValue) { // other types are unsupported
if (hasSweetpie) {
if (CanActorValueBeRestored(av)) {
// this coefficient (workaround) has been added for sake of game
// balance and because of disability to restrict players use potions
// often on client side
constexpr float kMagnitudeCoeff = 100.f;
RestoreActorValuePatched(this, av, effect.magnitude * kMagnitudeCoeff);
}
// this coefficient (workaround) has been added for sake of game
// balance and because of disability to restrict players use potions
// often on client side
constexpr float kMagnitudeCoeff = 100.f;
RestoreActorValuePatched(this, av, effect.magnitude * kMagnitudeCoeff);
} else {
RestoreActorValuePatched(this, av, effect.magnitude);
}
}

if (isRate || isMult) {
if (hasSweetpie && !CanActorValueBeRestored(av)) {
return;
}
MpChangeForm changeForm = GetChangeForm();
BaseActorValues baseValues = GetBaseActorValues(
GetParent(), GetBaseId(), GetRaceId(), changeForm.templateChain);
Expand Down
9 changes: 2 additions & 7 deletions skymp5-server/cpp/server_guest_lib/MpActor.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ class MpActor : public MpObjectReference

bool MpApiCraft(uint32_t craftedItemBaseId, uint32_t count,
uint32_t recipeId);
bool MpApiDropItem(uint32_t baseId, uint32_t count);
bool MpApiEatItem(uint32_t baseId);

void AddSpell(uint32_t spellId);
void RemoveSpell(uint32_t spellId);
Expand All @@ -165,13 +167,6 @@ class MpActor : public MpObjectReference

void ModifyActorValuePercentage(espm::ActorValue av, float percentageDelta);

std::chrono::steady_clock::time_point GetLastRestorationTime(
espm::ActorValue av) const noexcept;

void SetLastRestorationTime(espm::ActorValue av,
std::chrono::steady_clock::time_point timePoint);
bool CanActorValueBeRestored(espm::ActorValue av);

void EnsureTemplateChainEvaluated(
espm::Loader& loader,
ChangeFormGuard::Mode mode = ChangeFormGuard::Mode::RequestSave);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "PapyrusMessage.h"
#include "PapyrusNetImmerse.h"
#include "PapyrusObjectReference.h"
#include "PapyrusPotion.h"
#include "PapyrusSkymp.h"
#include "PapyrusSound.h"
#include "PapyrusUtility.h"
Expand Down Expand Up @@ -39,6 +40,7 @@ PapyrusClassesFactory::CreateAndRegister(
result.emplace_back(std::make_unique<PapyrusCell>());
result.emplace_back(std::make_unique<PapyrusSound>());
result.emplace_back(std::make_unique<PapyrusNetImmerse>());
result.emplace_back(std::make_unique<PapyrusPotion>());
result.emplace_back(std::make_unique<PapyrusVisualEffect>());

for (auto& papyrusClass : result) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "PapyrusPotion.h"
#include "script_objects/EspmGameObject.h"

VarValue PapyrusPotion::IsFood(VarValue self,
const std::vector<VarValue>& arguments)
{
const auto& item = GetRecordPtr(self);

if (!item.rec) {
return VarValue(false);
}

auto alch = espm::Convert<espm::ALCH>(item.rec);

if (!alch) {
return VarValue(false);
}

espm::CompressedFieldsCache cache;
espm::ALCH::Data data = alch->GetData(cache);
return VarValue(data.isFood);
}

void PapyrusPotion::Register(
VirtualMachine& vm, std::shared_ptr<IPapyrusCompatibilityPolicy> policy)
{
AddMethod(vm, "IsFood", &PapyrusPotion::IsFood);
}
16 changes: 16 additions & 0 deletions skymp5-server/cpp/server_guest_lib/script_classes/PapyrusPotion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#pragma once
#include "IPapyrusClass.h"
#include "SpSnippetFunctionGen.h"

class PapyrusPotion final : public IPapyrusClass<PapyrusPotion>
{
public:
const char* GetName() override { return "potion"; }

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

void Register(VirtualMachine& vm,
std::shared_ptr<IPapyrusCompatibilityPolicy> policy) override;

std::shared_ptr<IPapyrusCompatibilityPolicy> compatibilityPolicy;
};
16 changes: 16 additions & 0 deletions unit/EspmTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -615,3 +615,19 @@ TEST_CASE("MGEF parsing", "[espm]")
auto data = espm::GetData<espm::MGEF>(0x51B15, &provider);
REQUIRE(data.data.primaryAV == espm::ActorValue::DamageResist);
}

TEST_CASE("isFood flag is not set for heal potion", "[espm]")
{
MyEspmProvider provider;
auto data = espm::GetData<espm::ALCH>(0x0003eade, &provider);
REQUIRE(data.isFood == false);
REQUIRE(data.isPoison == false);
}

TEST_CASE("isFood flag is set for sweet roll", "[espm]")
{
MyEspmProvider provider;
auto data = espm::GetData<espm::ALCH>(0x00064B3D, &provider);
REQUIRE(data.isFood == true);
REQUIRE(data.isPoison == false);
}

0 comments on commit 5373446

Please sign in to comment.