Skip to content

Commit

Permalink
feat(skymp5-server): implement AddSpell/RemoveSpell Papyrus methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Pospelove committed Nov 12, 2023
1 parent c4cea74 commit 1bf67ea
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 9 deletions.
46 changes: 42 additions & 4 deletions skymp5-server/cpp/server_guest_lib/MpActor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ void MpActor::EquipBestWeapon()
}
}

void MpActor::AddSpell(const uint32_t spellId)
{
EditChangeForm([&](MpChangeForm& changeForm) {
changeForm.learnedSpells.LearnSpell(spellId);
});
}

void MpActor::RemoveSpell(const uint32_t spellId)
{
EditChangeForm([&](MpChangeForm& changeForm) {
changeForm.learnedSpells.ForgetSpell(spellId);
});
}

void MpActor::SetRaceMenuOpen(bool isOpen)
{
EditChangeForm(
Expand Down Expand Up @@ -476,13 +490,37 @@ const bool& MpActor::IsRespawning() const
return pImpl->isRespawning;
}

bool MpActor::IsSpellLearned(const uint32_t baseId) const
bool MpActor::IsSpellLearned(const uint32_t spellId) const
{
return ChangeForm().learnedSpells.IsSpellLearned(spellId) ||
IsSpellLearnedFromBase(spellId);
}

bool MpActor::IsSpellLearnedFromBase(const uint32_t spellId) const
{
const auto npcData = espm::GetData<espm::NPC_>(GetBaseId(), GetParent());
const auto raceData = espm::GetData<espm::RACE>(npcData.race, GetParent());
const auto npc = GetParent()->GetEspm().GetBrowser().LookupById(GetBaseId());

const uint32_t raceId = npc.ToGlobalId(npcData.race);

return npcData.spells.count(baseId) || raceData.spells.count(baseId) ||
ChangeForm().learnedSpells.IsSpellLearned(baseId);
const auto raceData = espm::GetData<espm::RACE>(raceId, GetParent());
const auto race = GetParent()->GetEspm().GetBrowser().LookupById(raceId);

for (auto npcSpellRaw : npcData.spells) {
const auto npcSpell = npc.ToGlobalId(npcSpellRaw);
if (npcSpell == spellId) {
return true;
}
}

for (auto raceSpellRaw : raceData.spells) {
const auto raceSpell = race.ToGlobalId(raceSpellRaw);
if (raceSpell == spellId) {
return true;
}
}

return false;
}

std::unique_ptr<const Appearance> MpActor::GetAppearance() const
Expand Down
6 changes: 5 additions & 1 deletion skymp5-server/cpp/server_guest_lib/MpActor.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class MpActor : public MpObjectReference
const bool& IsDead() const;
const bool& IsRespawning() const;

[[nodiscard]] bool IsSpellLearned(uint32_t baseId) const;
bool IsSpellLearned(uint32_t spellId) const; // including from base
bool IsSpellLearnedFromBase(uint32_t spellId) const;

std::unique_ptr<const Appearance> GetAppearance() const;
const std::string& GetAppearanceAsJson();
Expand Down Expand Up @@ -133,6 +134,9 @@ class MpActor : public MpObjectReference
bool MpApiCraft(uint32_t craftedItemBaseId, uint32_t count,
uint32_t recipeId);

void AddSpell(uint32_t spellId);
void RemoveSpell(uint32_t spellId);

private:
struct Impl;
std::shared_ptr<Impl> pImpl;
Expand Down
13 changes: 9 additions & 4 deletions skymp5-server/cpp/server_guest_lib/MpChangeForms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -218,19 +218,24 @@ MpChangeForm MpChangeForm::JsonToChangeForm(simdjson::dom::element& element)
return res;
}

void LearnedSpells::LearnSpell(const Data::key_type baseId)
void LearnedSpells::LearnSpell(const Data::key_type spellId)
{
_learnedSpellIds.emplace(baseId);
_learnedSpellIds.emplace(spellId);
}

void LearnedSpells::ForgetSpell(const Data::key_type spellId)
{
_learnedSpellIds.erase(spellId);
}

size_t LearnedSpells::Count() const noexcept
{
return _learnedSpellIds.size();
}

bool LearnedSpells::IsSpellLearned(const Data::key_type baseId) const
bool LearnedSpells::IsSpellLearned(const Data::key_type spellId) const
{
return _learnedSpellIds.count(baseId) != 0;
return _learnedSpellIds.count(spellId) != 0;
}

std::vector<LearnedSpells::Data::key_type> LearnedSpells::GetLearnedSpells()
Expand Down
2 changes: 2 additions & 0 deletions skymp5-server/cpp/server_guest_lib/MpChangeForms.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ struct LearnedSpells

void LearnSpell(Data::key_type baseId);

void ForgetSpell(Data::key_type baseId);

[[nodiscard]] size_t Count() const noexcept;

[[nodiscard]] bool IsSpellLearned(Data::key_type baseId) const;
Expand Down
87 changes: 87 additions & 0 deletions skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,90 @@ VarValue PapyrusActor::WornHasKeyword(VarValue self,
return VarValue(false);
}

VarValue PapyrusActor::AddSpell(VarValue self,
const std::vector<VarValue>& arguments)
{
// TODO: should we sync spell list in general? should we show spell add for
// actor neighbors?

if (auto actor = GetFormPtr<MpActor>(self)) {
if (arguments.size() < 2) {
throw std::runtime_error(
"Actor.AddSpell requires at least two arguments");
}

const auto& spell = GetRecordPtr(arguments[0]);
if (!spell.rec) {
spdlog::error("Actor.AddSpell - invalid spell form");
return VarValue(false);
}

if (spell.rec->GetType().ToString() != "SPEL") {
spdlog::error("Actor.AddSpell - type expected to be SPEL, but it is {}",
spell.rec->GetType().ToString());
return VarValue(false);
}

uint32_t spellId = spell.ToGlobalId(spell.rec->GetId());

if (!actor->IsSpellLearned(spellId)) {
actor->AddSpell(spellId);

SpSnippet(GetName(), "AddSpell",
SpSnippetFunctionGen::SerializeArguments(arguments).data(),
actor->GetFormId())
.Execute(actor);

return VarValue(true);
}
}

return VarValue(false);
}

VarValue PapyrusActor::RemoveSpell(VarValue self,
const std::vector<VarValue>& arguments)
{
if (auto actor = GetFormPtr<MpActor>(self)) {
if (arguments.size() < 1) {
throw std::runtime_error(
"Actor.RemoveSpell requires at least one argument");
}

const auto& spell = GetRecordPtr(arguments[0]);
if (!spell.rec) {
spdlog::error("Actor.RemoveSpell - invalid spell form");
return VarValue(false);
}

if (spell.rec->GetType().ToString() != "SPEL") {
spdlog::error(
"Actor.RemoveSpell - type expected to be SPEL, but it is {}",
spell.rec->GetType().ToString());
return VarValue(false);
}

uint32_t spellId = spell.ToGlobalId(spell.rec->GetId());

if (actor->IsSpellLearnedFromBase(spellId)) {
spdlog::warn("Actor.RemoveSpell - can't remove spells inherited from "
"RACE/NPC_ records");
} else if (!actor->IsSpellLearned(spellId)) {
spdlog::warn("Actor.RemoveSpell - spell already removed/not learned");
} else {
actor->RemoveSpell(spellId);

SpSnippet(GetName(), "RemoveSpell",
SpSnippetFunctionGen::SerializeArguments(arguments).data(),
actor->GetFormId())
.Execute(actor);

return VarValue(true);
}
}
return VarValue(false);
}

void PapyrusActor::Register(
VirtualMachine& vm, std::shared_ptr<IPapyrusCompatibilityPolicy> policy)
{
Expand All @@ -275,4 +359,7 @@ void PapyrusActor::Register(
AddMethod(vm, "EquipItem", &PapyrusActor::EquipItem);
AddMethod(vm, "SetDontMove", &PapyrusActor::SetDontMove);
AddMethod(vm, "IsDead", &PapyrusActor::IsDead);
AddMethod(vm, "WornHasKeyword", &PapyrusActor::WornHasKeyword);
AddMethod(vm, "AddSpell", &PapyrusActor::AddSpell);
AddMethod(vm, "RemoveSpell", &PapyrusActor::RemoveSpell);
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,21 @@ class PapyrusActor final : public IPapyrusClass<PapyrusActor>
const std::vector<VarValue>& arguments);

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

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

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

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

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

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

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

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

Expand Down

0 comments on commit 1bf67ea

Please sign in to comment.