Skip to content

Commit

Permalink
feat: add isSecondActivation handling for client and server (skyrim-m…
Browse files Browse the repository at this point in the history
  • Loading branch information
Pospelove authored Sep 7, 2024
1 parent 5c63623 commit 64c14cb
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 84 deletions.
1 change: 1 addition & 0 deletions skymp5-client/src/services/messages/activateMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ export interface ActivateMessage {
interface ActivateMessageData {
caster: number;
target: number;
isSecondActivation: boolean;
}
2 changes: 1 addition & 1 deletion skymp5-client/src/services/services/activationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class ActivationService extends ClientListener {
this.controller.emitter.emit("sendMessage", {
message: {
t: MsgType.Activate,
data: { caster, target }
data: { caster, target, isSecondActivation: false }
},
reliability: "reliable"
});
Expand Down
29 changes: 23 additions & 6 deletions skymp5-client/src/services/services/remoteServer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Actor, Form, FormType } from 'skyrimPlatform';
import { Actor, Form, FormType, Menu } from 'skyrimPlatform';
import {
Armor,
Cell,
Expand Down Expand Up @@ -175,30 +175,47 @@ export class RemoteServer extends ClientListener {

const baseObject = refr.getBaseObject();
const baseType = baseObject?.getType();
const isContainer = baseType === FormType.Container;

if (!isContainer) {
let functionChecker: (() => boolean) | null = null;
let factName = "";
if (baseType === FormType.Container) {
functionChecker = () => Ui.isMenuOpen("ContainerMenu");
factName = "'ContainerMenu open'";
} else if (baseType === FormType.Furniture) {
functionChecker = () => !!Game.getPlayer()?.getFurnitureReference();
factName = "'getFurnitureReference not null'";
}

if (functionChecker === null) {
logTrace(this, "onOpenContainerMesage - not a container or furniture", baseType);
return;
}

// In SkyMP containers have 2-nd, closing activation under the hood.
// This differs from Skyrim's behavior, where it's just one activation.

(async () => {
while (!Ui.isMenuOpen('ContainerMenu')) await Utility.wait(0.1);
while (Ui.isMenuOpen('ContainerMenu')) await Utility.wait(0.1);
logTrace(this, "onOpenContainerMesage - waiting for", factName, "to be true");
while (!functionChecker()) await Utility.wait(0.1);

logTrace(this, "onOpenContainerMesage - waiting for", factName, "to be false");
while (functionChecker()) await Utility.wait(0.1);

logTrace(this, "onOpenContainerMesage - menu closed", factName);

const message: ActivateMessage = {
t: messages.MsgType.Activate,
data: {
caster: 0x14, target: event.message.target
caster: 0x14, target: event.message.target, isSecondActivation: true
}
};

this.controller.emitter.emit("sendMessage", {
message: message,
reliability: "reliable"
});

logTrace(this, "onOpenContainerMesage - sent ActivateMessage", message);
})();
});
}
Expand Down
7 changes: 5 additions & 2 deletions skymp5-server/cpp/server_guest_lib/ActionListener.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,8 @@ void ActionListener::OnUpdateEquipment(
}

void ActionListener::OnActivate(const RawMessageData& rawMsgData,
uint32_t caster, uint32_t target)
uint32_t caster, uint32_t target,
bool isSecondActivation)
{
if (!partOne.HasEspm())
throw std::runtime_error("No loaded esm or esp files are found");
Expand All @@ -291,9 +292,11 @@ void ActionListener::OnActivate(const RawMessageData& rawMsgData,
if (!targetPtr)
return;

constexpr bool kDefaultProcessingOnlyFalse = false;
targetPtr->Activate(
caster == 0x14 ? *ac
: partOne.worldState.GetFormAt<MpObjectReference>(caster));
: partOne.worldState.GetFormAt<MpObjectReference>(caster),
kDefaultProcessingOnlyFalse, isSecondActivation);
if (hosterId) {
auto actor = std::dynamic_pointer_cast<MpActor>(
partOne.worldState.LookupFormById(caster));
Expand Down
2 changes: 1 addition & 1 deletion skymp5-server/cpp/server_guest_lib/ActionListener.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class ActionListener
uint32_t voiceSpell, uint32_t instantSpell);

virtual void OnActivate(const RawMessageData& rawMsgData, uint32_t caster,
uint32_t target);
uint32_t target, bool isSecondActivation);

virtual void OnPutItem(const RawMessageData& rawMsgData, uint32_t target,
const Inventory::Entry& entry);
Expand Down
86 changes: 63 additions & 23 deletions skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,8 @@ void MpObjectReference::VisitProperties(const PropertiesVisitor& visitor,
}

void MpObjectReference::Activate(MpObjectReference& activationSource,
bool defaultProcessingOnly)
bool defaultProcessingOnly,
bool isSecondActivation)
{
if (spdlog::should_log(spdlog::level::trace)) {
for (auto& script : ListActivePexInstances()) {
Expand Down Expand Up @@ -428,25 +429,33 @@ void MpObjectReference::Activate(MpObjectReference& activationSource,
}
}

ActivateEvent activateEvent(GetFormId(), activationSource.GetFormId());
if (isSecondActivation) {
bool processed = ProcessActivateSecond(activationSource);
if (processed) {
auto arg = activationSource.ToVarValue();
SendPapyrusEvent("SkympOnActivateClose", &arg, 1);
}
} else {
ActivateEvent activateEvent(GetFormId(), activationSource.GetFormId());

bool activationBlockedByMpApi = !activateEvent.Fire(GetParent());
bool activationBlockedByMpApi = !activateEvent.Fire(GetParent());

if (!activationBlockedByMpApi &&
(!activationBlocked || defaultProcessingOnly)) {
ProcessActivate(activationSource);
ActivateChilds();
} else {
spdlog::trace(
"Activation of form {:#x} has been blocked. Reasons: "
"blocked by MpApi={}, form is blocked={}, defaultProcessingOnly={}",
GetFormId(), activationBlockedByMpApi, activationBlocked,
defaultProcessingOnly);
}
if (!activationBlockedByMpApi &&
(!activationBlocked || defaultProcessingOnly)) {
ProcessActivateNormal(activationSource);
ActivateChilds();
} else {
spdlog::trace(
"Activation of form {:#x} has been blocked. Reasons: "
"blocked by MpApi={}, form is blocked={}, defaultProcessingOnly={}",
GetFormId(), activationBlockedByMpApi, activationBlocked,
defaultProcessingOnly);
}

if (!defaultProcessingOnly) {
auto arg = activationSource.ToVarValue();
SendPapyrusEvent("OnActivate", &arg, 1);
if (!defaultProcessingOnly) {
auto arg = activationSource.ToVarValue();
SendPapyrusEvent("OnActivate", &arg, 1);
}
}
}

Expand Down Expand Up @@ -1293,7 +1302,8 @@ bool MpObjectReference::IsLocationSavingNeeded() const
std::chrono::system_clock::now() - *last > std::chrono::seconds(30);
}

void MpObjectReference::ProcessActivate(MpObjectReference& activationSource)
void MpObjectReference::ProcessActivateNormal(
MpObjectReference& activationSource)
{
auto actorActivator = activationSource.AsActor();

Expand Down Expand Up @@ -1458,10 +1468,6 @@ void MpObjectReference::ProcessActivate(MpObjectReference& activationSource)
this->occupantDestroySink.reset(
new OccupantDestroyEventSink(*GetParent(), this));
this->occupant->AddEventSink(occupantDestroySink);
} else if (this->occupant == &activationSource) {
SetOpen(false);
this->occupant->RemoveEventSink(this->occupantDestroySink);
this->occupant = nullptr;
}
} else if (t == espm::ACTI::kType && actorActivator) {
// SendOpenContainer being used to activate the object
Expand Down Expand Up @@ -1492,11 +1498,45 @@ void MpObjectReference::ProcessActivate(MpObjectReference& activationSource)
this->occupantDestroySink.reset(
new OccupantDestroyEventSink(*GetParent(), this));
this->occupant->AddEventSink(occupantDestroySink);
} else if (this->occupant == &activationSource) {
}
}
}

bool MpObjectReference::ProcessActivateSecond(
MpObjectReference& activationSource)
{
auto actorActivator = activationSource.AsActor();

auto worldState = GetParent();
auto& loader = GetParent()->GetEspm();
auto& compressedFieldsCache = GetParent()->GetEspmCache();

auto base = loader.GetBrowser().LookupById(GetBaseId());
if (!base.rec || !GetBaseId()) {
spdlog::error("MpObjectReference::ProcessActivate {:x} - doesn't "
"have base form, activationSource is {:x}",
GetFormId(), activationSource.GetFormId());
return false;
}

auto t = base.rec->GetType();

if (t == espm::CONT::kType && actorActivator) {
if (this->occupant == &activationSource) {
SetOpen(false);
this->occupant->RemoveEventSink(this->occupantDestroySink);
this->occupant = nullptr;
return true;
}
} else if (t == "FURN" && actorActivator) {
if (this->occupant == &activationSource) {
this->occupant->RemoveEventSink(this->occupantDestroySink);
this->occupant = nullptr;
return true;
}
}

return false;
}

void MpObjectReference::ActivateChilds()
Expand Down
6 changes: 4 additions & 2 deletions skymp5-server/cpp/server_guest_lib/MpObjectReference.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ class MpObjectReference
virtual void VisitProperties(const PropertiesVisitor& visitor,
VisitPropertiesMode mode);
virtual void Activate(MpObjectReference& activationSource,
bool defaultProcessingOnly = false);
bool defaultProcessingOnly = false,
bool isSecondActivation = false);
virtual void Disable();
virtual void Enable();

Expand Down Expand Up @@ -228,7 +229,8 @@ class MpObjectReference
void SendOpenContainer(uint32_t refId);
void CheckInteractionAbility(MpObjectReference& ac);
bool IsLocationSavingNeeded() const;
void ProcessActivate(MpObjectReference& activationSource);
void ProcessActivateNormal(MpObjectReference& activationSource);
bool ProcessActivateSecond(MpObjectReference& activationSource);
void ActivateChilds();

bool everSubscribedOrListened = false;
Expand Down
8 changes: 6 additions & 2 deletions skymp5-server/cpp/server_guest_lib/PacketParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ static const JsonPointer t("t"), idx("idx"), content("content"), data("data"),
magicka("magicka"), stamina("stamina"), leftSpell("leftSpell"),
rightSpell("rightSpell"), voiceSpell("voiceSpell"),
instantSpell("instantSpell"), weaponId("weaponId"), ammoId("ammoId"),
power("power"), isSunGazing("isSunGazing");
power("power"), isSunGazing("isSunGazing"),
isSecondActivation("isSecondActivation");
}

struct PacketParser::Impl
Expand Down Expand Up @@ -165,8 +166,11 @@ void PacketParser::TransformPacketIntoAction(Networking::UserId userId,
uint64_t caster, target;
ReadEx(data_, JsonPointers::caster, &caster);
ReadEx(data_, JsonPointers::target, &target);
bool isSecondActivation;
ReadEx(data_, JsonPointers::isSecondActivation, &isSecondActivation);
actionListener.OnActivate(rawMsgData, FormIdCasts::LongToNormal(caster),
FormIdCasts::LongToNormal(target));
FormIdCasts::LongToNormal(target),
isSecondActivation);
} break;
case MsgType::UpdateProperty:
break;
Expand Down
Loading

0 comments on commit 64c14cb

Please sign in to comment.