mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-10-14 11:28:08 +00:00
Public release of the DLU server code!
Have fun!
This commit is contained in:
570
dGame/dComponents/RebuildComponent.cpp
Normal file
570
dGame/dComponents/RebuildComponent.cpp
Normal file
@@ -0,0 +1,570 @@
|
||||
#include "RebuildComponent.h"
|
||||
#include "Entity.h"
|
||||
#include "DestroyableComponent.h"
|
||||
#include "GameMessages.h"
|
||||
#include "EntityManager.h"
|
||||
#include "Game.h"
|
||||
#include "dLogger.h"
|
||||
#include "CharacterComponent.h"
|
||||
|
||||
#include "dServer.h"
|
||||
#include "PacketUtils.h"
|
||||
#include "Spawner.h"
|
||||
#include "MovingPlatformComponent.h"
|
||||
#include "Preconditions.h"
|
||||
|
||||
#include "CppScripts.h"
|
||||
|
||||
RebuildComponent::RebuildComponent(Entity* entity) : Component(entity) {
|
||||
std::u16string checkPreconditions = entity->GetVar<std::u16string>(u"CheckPrecondition");
|
||||
|
||||
if (!checkPreconditions.empty())
|
||||
{
|
||||
m_Precondition = new PreconditionExpression(GeneralUtils::UTF16ToWTF8(checkPreconditions));
|
||||
}
|
||||
}
|
||||
|
||||
RebuildComponent::~RebuildComponent() {
|
||||
delete m_Precondition;
|
||||
|
||||
Entity* builder = GetBuilder();
|
||||
if (builder) {
|
||||
CancelRebuild(builder, eFailReason::REASON_BUILD_ENDED, true);
|
||||
}
|
||||
|
||||
DespawnActivator();
|
||||
}
|
||||
|
||||
void RebuildComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
|
||||
if (m_Parent->GetComponent(COMPONENT_TYPE_DESTROYABLE) == nullptr) {
|
||||
if (bIsInitialUpdate) {
|
||||
outBitStream->Write(false);
|
||||
}
|
||||
|
||||
outBitStream->Write(false);
|
||||
|
||||
outBitStream->Write(false);
|
||||
}
|
||||
|
||||
// BEGIN Scripted Activity
|
||||
outBitStream->Write1();
|
||||
|
||||
Entity* builder = GetBuilder();
|
||||
|
||||
if (builder) {
|
||||
outBitStream->Write((uint32_t)1);
|
||||
outBitStream->Write(builder->GetObjectID());
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
outBitStream->Write(0.0f);
|
||||
}
|
||||
}
|
||||
else {
|
||||
outBitStream->Write((uint32_t)0);
|
||||
}
|
||||
// END Scripted Activity
|
||||
|
||||
outBitStream->Write1();
|
||||
|
||||
outBitStream->Write<uint32_t>(m_State);
|
||||
|
||||
outBitStream->Write(m_ShowResetEffect);
|
||||
outBitStream->Write(m_Activator != nullptr);
|
||||
|
||||
outBitStream->Write(m_Timer);
|
||||
outBitStream->Write(m_TimerIncomplete);
|
||||
|
||||
if (bIsInitialUpdate) {
|
||||
outBitStream->Write(false);
|
||||
outBitStream->Write(m_ActivatorPosition);
|
||||
outBitStream->Write(m_RepositionPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildComponent::Update(float deltaTime) {
|
||||
m_Activator = GetActivator();
|
||||
|
||||
// Serialize the quickbuild every so often, fixes the odd bug where the quickbuild is not buildable
|
||||
/*if (m_SoftTimer > 0.0f) {
|
||||
m_SoftTimer -= deltaTime;
|
||||
}
|
||||
else {
|
||||
m_SoftTimer = 5.0f;
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
}*/
|
||||
|
||||
switch (m_State) {
|
||||
case REBUILD_OPEN: {
|
||||
SpawnActivator();
|
||||
m_TimeBeforeDrain = 0;
|
||||
|
||||
auto* spawner = m_Parent->GetSpawner();
|
||||
const bool isSmashGroup = spawner != nullptr ? spawner->GetIsSpawnSmashGroup() : false;
|
||||
|
||||
if (isSmashGroup) {
|
||||
m_TimerIncomplete += deltaTime;
|
||||
|
||||
// For reset times < 0 this has to be handled manually
|
||||
if (m_TimeBeforeSmash > 0) {
|
||||
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f) {
|
||||
m_ShowResetEffect = true;
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
if (m_TimerIncomplete >= m_TimeBeforeSmash) {
|
||||
m_Builder = LWOOBJID_EMPTY;
|
||||
|
||||
GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 0.0f, false, true);
|
||||
|
||||
ResetRebuild(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case REBUILD_COMPLETED: {
|
||||
m_Timer += deltaTime;
|
||||
|
||||
// For reset times < 0 this has to be handled manually
|
||||
if (m_ResetTime > 0) {
|
||||
if (m_Timer >= m_ResetTime - 4.0f) {
|
||||
if (!m_ShowResetEffect) {
|
||||
m_ShowResetEffect = true;
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Timer >= m_ResetTime) {
|
||||
m_Builder = LWOOBJID_EMPTY;
|
||||
|
||||
GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 0.0f, false, true);
|
||||
|
||||
ResetRebuild(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case REBUILD_BUILDING:
|
||||
{
|
||||
Entity* builder = GetBuilder();
|
||||
|
||||
if (builder == nullptr)
|
||||
{
|
||||
ResetRebuild(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
m_TimeBeforeDrain -= deltaTime;
|
||||
m_Timer += deltaTime;
|
||||
m_TimerIncomplete = 0;
|
||||
m_ShowResetEffect = false;
|
||||
|
||||
if (m_TimeBeforeDrain <= 0.0f) {
|
||||
m_TimeBeforeDrain = m_CompleteTime / static_cast<float>(m_TakeImagination);
|
||||
|
||||
DestroyableComponent* destComp = builder->GetComponent<DestroyableComponent>();
|
||||
if (!destComp) break;
|
||||
|
||||
int newImagination = destComp->GetImagination() - 1;
|
||||
|
||||
destComp->SetImagination(newImagination);
|
||||
EntityManager::Instance()->SerializeEntity(builder);
|
||||
|
||||
++m_DrainedImagination;
|
||||
|
||||
if (newImagination == 0 && m_DrainedImagination < m_TakeImagination) {
|
||||
CancelRebuild(builder, eFailReason::REASON_OUT_OF_IMAGINATION, true);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Timer >= m_CompleteTime && m_DrainedImagination >= m_TakeImagination) {
|
||||
CompleteRebuild(builder);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case REBUILD_INCOMPLETE: {
|
||||
m_TimerIncomplete += deltaTime;
|
||||
|
||||
// For reset times < 0 this has to be handled manually
|
||||
if (m_TimeBeforeSmash > 0) {
|
||||
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f) {
|
||||
m_ShowResetEffect = true;
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
if (m_TimerIncomplete >= m_TimeBeforeSmash) {
|
||||
m_Builder = LWOOBJID_EMPTY;
|
||||
|
||||
GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 0.0f, false, true);
|
||||
|
||||
ResetRebuild(false);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildComponent::OnUse(Entity* originator) {
|
||||
if (GetBuilder() != nullptr || m_State == REBUILD_COMPLETED) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_Precondition != nullptr && !m_Precondition->Check(originator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
StartRebuild(originator);
|
||||
}
|
||||
|
||||
void RebuildComponent::SpawnActivator() {
|
||||
if (!m_SelfActivator || m_ActivatorPosition != NiPoint3::ZERO) {
|
||||
if (!m_Activator) {
|
||||
EntityInfo info;
|
||||
|
||||
info.lot = 6604;
|
||||
info.spawnerID = m_Parent->GetObjectID();
|
||||
info.pos = m_ActivatorPosition == NiPoint3::ZERO ? m_Parent->GetPosition() : m_ActivatorPosition;
|
||||
|
||||
m_Activator = EntityManager::Instance()->CreateEntity(info, nullptr, m_Parent);
|
||||
if (m_Activator) {
|
||||
m_ActivatorId = m_Activator->GetObjectID();
|
||||
EntityManager::Instance()->ConstructEntity(m_Activator);
|
||||
}
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildComponent::DespawnActivator() {
|
||||
if (m_Activator) {
|
||||
EntityManager::Instance()->DestructEntity(m_Activator);
|
||||
|
||||
m_Activator->ScheduleKillAfterUpdate();
|
||||
|
||||
m_Activator = nullptr;
|
||||
|
||||
m_ActivatorId = LWOOBJID_EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
Entity* RebuildComponent::GetActivator()
|
||||
{
|
||||
return EntityManager::Instance()->GetEntity(m_ActivatorId);
|
||||
}
|
||||
|
||||
NiPoint3 RebuildComponent::GetActivatorPosition() {
|
||||
return m_ActivatorPosition;
|
||||
}
|
||||
|
||||
float RebuildComponent::GetResetTime() {
|
||||
return m_ResetTime;
|
||||
}
|
||||
|
||||
float RebuildComponent::GetCompleteTime() {
|
||||
return m_CompleteTime;
|
||||
}
|
||||
|
||||
int RebuildComponent::GetTakeImagination() {
|
||||
return m_TakeImagination;
|
||||
}
|
||||
|
||||
bool RebuildComponent::GetInterruptible() {
|
||||
return m_Interruptible;
|
||||
}
|
||||
|
||||
bool RebuildComponent::GetSelfActivator() {
|
||||
return m_SelfActivator;
|
||||
}
|
||||
|
||||
std::vector<int> RebuildComponent::GetCustomModules() {
|
||||
return m_CustomModules;
|
||||
}
|
||||
|
||||
int RebuildComponent::GetActivityId() {
|
||||
return m_ActivityId;
|
||||
}
|
||||
|
||||
int RebuildComponent::GetPostImaginationCost() {
|
||||
return m_PostImaginationCost;
|
||||
}
|
||||
|
||||
float RebuildComponent::GetTimeBeforeSmash() {
|
||||
return m_TimeBeforeSmash;
|
||||
}
|
||||
|
||||
eRebuildState RebuildComponent::GetState() {
|
||||
return m_State;
|
||||
}
|
||||
|
||||
Entity* RebuildComponent::GetBuilder() const {
|
||||
auto* builder = EntityManager::Instance()->GetEntity(m_Builder);
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
bool RebuildComponent::GetRepositionPlayer() const {
|
||||
return m_RepositionPlayer;
|
||||
}
|
||||
|
||||
void RebuildComponent::SetActivatorPosition(NiPoint3 value) {
|
||||
m_ActivatorPosition = value;
|
||||
}
|
||||
|
||||
void RebuildComponent::SetResetTime(float value) {
|
||||
m_ResetTime = value;
|
||||
}
|
||||
|
||||
void RebuildComponent::SetCompleteTime(float value) {
|
||||
if (value < 0) {
|
||||
m_CompleteTime = 4.5f;
|
||||
}
|
||||
else {
|
||||
m_CompleteTime = value;
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildComponent::SetTakeImagination(int value) {
|
||||
m_TakeImagination = value;
|
||||
}
|
||||
|
||||
void RebuildComponent::SetInterruptible(bool value) {
|
||||
m_Interruptible = value;
|
||||
}
|
||||
|
||||
void RebuildComponent::SetSelfActivator(bool value) {
|
||||
m_SelfActivator = value;
|
||||
}
|
||||
|
||||
void RebuildComponent::SetCustomModules(std::vector<int> value) {
|
||||
m_CustomModules = value;
|
||||
}
|
||||
|
||||
void RebuildComponent::SetActivityId(int value) {
|
||||
m_ActivityId = value;
|
||||
}
|
||||
|
||||
void RebuildComponent::SetPostImaginationCost(int value) {
|
||||
m_PostImaginationCost = value;
|
||||
}
|
||||
|
||||
void RebuildComponent::SetTimeBeforeSmash(float value) {
|
||||
if (value < 0) {
|
||||
m_TimeBeforeSmash = 10.0f;
|
||||
}
|
||||
else {
|
||||
m_TimeBeforeSmash = value;
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildComponent::SetRepositionPlayer(bool value) {
|
||||
m_RepositionPlayer = value;
|
||||
}
|
||||
|
||||
void RebuildComponent::StartRebuild(Entity* user) {
|
||||
if (m_State == eRebuildState::REBUILD_OPEN || m_State == eRebuildState::REBUILD_COMPLETED || m_State == eRebuildState::REBUILD_INCOMPLETE) {
|
||||
m_Builder = user->GetObjectID();
|
||||
|
||||
auto* character = user->GetComponent<CharacterComponent>();
|
||||
character->SetCurrentActivity(eGameActivities::ACTIVITY_QUICKBUILDING);
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(user);
|
||||
|
||||
GameMessages::SendRebuildNotifyState(m_Parent, m_State, eRebuildState::REBUILD_COMPLETED, user->GetObjectID());
|
||||
GameMessages::SendEnableRebuild(m_Parent, true, false, false, eFailReason::REASON_NOT_GIVEN, 0.0f, user->GetObjectID());
|
||||
|
||||
m_State = eRebuildState::REBUILD_BUILDING;
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
|
||||
auto* movingPlatform = m_Parent->GetComponent<MovingPlatformComponent>();
|
||||
if (movingPlatform != nullptr) {
|
||||
movingPlatform->OnRebuildInitilized();
|
||||
}
|
||||
|
||||
for (auto* script : CppScripts::GetEntityScripts(m_Parent)) {
|
||||
script->OnRebuildStart(m_Parent, user);
|
||||
}
|
||||
|
||||
// Notify scripts and possible subscribers
|
||||
for (auto* script : CppScripts::GetEntityScripts(m_Parent))
|
||||
script->OnRebuildNotifyState(m_Parent, m_State);
|
||||
for (const auto& cb : m_RebuildStateCallbacks)
|
||||
cb(m_State);
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildComponent::CompleteRebuild(Entity* user) {
|
||||
if (user == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* characterComponent = user->GetComponent<CharacterComponent>();
|
||||
if (characterComponent != nullptr) {
|
||||
characterComponent->SetCurrentActivity(eGameActivities::ACTIVITY_NONE);
|
||||
characterComponent->TrackRebuildComplete();
|
||||
} else {
|
||||
Game::logger->Log("RebuildComponent", "Some user tried to finish the rebuild but they didn't have a character somehow.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(user);
|
||||
|
||||
GameMessages::SendRebuildNotifyState(m_Parent, m_State, eRebuildState::REBUILD_COMPLETED, user->GetObjectID());
|
||||
GameMessages::SendEnableRebuild(m_Parent, false, true, false, eFailReason::REASON_NOT_GIVEN, m_ResetTime, user->GetObjectID());
|
||||
|
||||
m_State = eRebuildState::REBUILD_COMPLETED;
|
||||
m_Timer = 0.0f;
|
||||
m_DrainedImagination = 0;
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
|
||||
GameMessages::SendPlayFXEffect(m_Parent, 507, u"create", "BrickFadeUpVisCompleteEffect", LWOOBJID_EMPTY, 0.4f, 1.0f, true);
|
||||
GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
|
||||
|
||||
// Removes extra item requirements, isn't live accurate.
|
||||
// In live, all items were removed at the start of the quickbuild, then returned if it was cancelled.
|
||||
// TODO: fix?
|
||||
if (m_Precondition != nullptr) {
|
||||
m_Precondition->Check(user, true);
|
||||
}
|
||||
|
||||
DespawnActivator();
|
||||
|
||||
// Set owner override so that entities smashed by this quickbuild will result in the builder getting rewards.
|
||||
m_Parent->SetOwnerOverride(user->GetObjectID());
|
||||
|
||||
auto* builder = GetBuilder();
|
||||
|
||||
if (builder != nullptr) {
|
||||
auto* missionComponent = builder->GetComponent<MissionComponent>();
|
||||
if (missionComponent != nullptr) {
|
||||
missionComponent->Progress(MissionTaskType::MISSION_TASK_TYPE_ACTIVITY, m_ActivityId);
|
||||
}
|
||||
|
||||
Loot::DropActivityLoot(builder, m_Parent, m_ActivityId, 1);
|
||||
}
|
||||
|
||||
m_Builder = LWOOBJID_EMPTY;
|
||||
|
||||
// Notify scripts
|
||||
for (auto* script : CppScripts::GetEntityScripts(m_Parent)) {
|
||||
script->OnRebuildComplete(m_Parent, user);
|
||||
script->OnRebuildNotifyState(m_Parent, m_State);
|
||||
}
|
||||
|
||||
// Notify subscribers
|
||||
for (const auto& callback : m_RebuildStateCallbacks)
|
||||
callback(m_State);
|
||||
for (const auto& callback : m_RebuildCompleteCallbacks)
|
||||
callback(user);
|
||||
|
||||
auto* movingPlatform = m_Parent->GetComponent<MovingPlatformComponent>();
|
||||
if (movingPlatform != nullptr) {
|
||||
movingPlatform->OnCompleteRebuild();
|
||||
}
|
||||
|
||||
// Set flag
|
||||
auto* character = user->GetCharacter();
|
||||
|
||||
if (character != nullptr) {
|
||||
const auto flagNumber = m_Parent->GetVar<int32_t>(u"quickbuild_single_build_player_flag");
|
||||
|
||||
if (flagNumber != 0) {
|
||||
character->SetPlayerFlag(flagNumber, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildComponent::ResetRebuild(bool failed) {
|
||||
Entity* builder = GetBuilder();
|
||||
|
||||
if (m_State == eRebuildState::REBUILD_BUILDING && builder) {
|
||||
GameMessages::SendEnableRebuild(m_Parent, false, false, failed, eFailReason::REASON_NOT_GIVEN, m_ResetTime, builder->GetObjectID());
|
||||
|
||||
if (failed) {
|
||||
GameMessages::SendPlayAnimation(builder, u"rebuild-fail");
|
||||
}
|
||||
}
|
||||
|
||||
GameMessages::SendRebuildNotifyState(m_Parent, m_State, eRebuildState::REBUILD_RESETTING, LWOOBJID_EMPTY);
|
||||
|
||||
m_State = eRebuildState::REBUILD_RESETTING;
|
||||
m_Timer = 0.0f;
|
||||
m_TimerIncomplete = 0.0f;
|
||||
m_ShowResetEffect = false;
|
||||
m_DrainedImagination = 0;
|
||||
|
||||
m_Builder = LWOOBJID_EMPTY;
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
|
||||
// Notify scripts and possible subscribers
|
||||
for (auto* script : CppScripts::GetEntityScripts(m_Parent))
|
||||
script->OnRebuildNotifyState(m_Parent, m_State);
|
||||
for (const auto& cb : m_RebuildStateCallbacks)
|
||||
cb(m_State);
|
||||
|
||||
m_Parent->ScheduleKillAfterUpdate();
|
||||
|
||||
if (m_Activator)
|
||||
{
|
||||
m_Activator->ScheduleKillAfterUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildComponent::CancelRebuild(Entity* entity, eFailReason failReason, bool skipChecks) {
|
||||
if (m_State != eRebuildState::REBUILD_COMPLETED || skipChecks) {
|
||||
|
||||
m_Builder = LWOOBJID_EMPTY;
|
||||
|
||||
const auto entityID = entity != nullptr ? entity->GetObjectID() : LWOOBJID_EMPTY;
|
||||
|
||||
// Notify the client that a state has changed
|
||||
GameMessages::SendRebuildNotifyState(m_Parent, m_State, eRebuildState::REBUILD_INCOMPLETE, entityID);
|
||||
GameMessages::SendEnableRebuild(m_Parent, false, true, false, failReason, m_Timer, entityID);
|
||||
|
||||
// Now terminate any interaction with the rebuild
|
||||
GameMessages::SendTerminateInteraction(entityID, eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
|
||||
GameMessages::SendTerminateInteraction(m_Parent->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
|
||||
|
||||
// Now update the component itself
|
||||
m_State = eRebuildState::REBUILD_INCOMPLETE;
|
||||
|
||||
// Notify scripts and possible subscribers
|
||||
for (auto* script : CppScripts::GetEntityScripts(m_Parent))
|
||||
script->OnRebuildNotifyState(m_Parent, m_State);
|
||||
for (const auto& cb : m_RebuildStateCallbacks)
|
||||
cb(m_State);
|
||||
|
||||
EntityManager::Instance()->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
if (entity == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
CharacterComponent* characterComponent = entity->GetComponent<CharacterComponent>();
|
||||
if (characterComponent) {
|
||||
characterComponent->SetCurrentActivity(eGameActivities::ACTIVITY_NONE);
|
||||
EntityManager::Instance()->SerializeEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void RebuildComponent::AddRebuildCompleteCallback(const std::function<void(Entity* user)>& callback) {
|
||||
m_RebuildCompleteCallbacks.push_back(callback);
|
||||
}
|
||||
|
||||
void RebuildComponent::AddRebuildStateCallback(const std::function<void(eRebuildState state)>& callback) {
|
||||
m_RebuildStateCallbacks.push_back(callback);
|
||||
}
|
Reference in New Issue
Block a user