Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions src/Effects/Actors/ZActorToWineEffect.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#include "ZActorToWineEffect.h"

#include <Glacier/ZEntity.h>
#include <Glacier/ZActor.h>
#include <Glacier/ZSpatialEntity.h>

#include "Logging.h"

#include "EffectRegistry.h"
#include "Helpers/EntityUtils.h"

#define TAG "[ZActorToWineEffect] "

void ZActorToWineEffect::LoadResources()
{
ZActorWellbeingChangeEffectBase::LoadResources();
ZSpawnRepositoryItemEffectBase::LoadResources();

// pre-load wine bottle repository prop for later use
// Melee_Llama_WineBottle_WhiteLabel_Vintage / "1945 Grand Paladin"
m_WineBottleProp = GetRepositoryPropByID("2d960bf0-217c-400d-a1ee-f721e18f2926");
if (!m_WineBottleProp)
{
Logger::Error(TAG "Failed to load wine bottle repository prop");
m_bIsAvailable = false;
}
}

void ZActorToWineEffect::OnDrawDebugUI()
{
ImGui::Checkbox("Teleport Bodies", &m_bTeleportBodies);
}

void ZActorToWineEffect::OnActorWellbeingChanged(ZActor* p_pActor, const SActorState& p_OldState, const SActorState& p_NewState)
{
if (!p_OldState.m_bDead && p_NewState.m_bDead && !p_pActor->m_bBodyHidden)
{
Logger::Debug(TAG "Actor '{}' died", p_pActor->m_sActorName);
OnActorPerished(p_pActor);
return;
}
}

void ZActorToWineEffect::OnActorPerished(ZActor* p_pActor)
{
ZEntityRef s_rActor;
p_pActor->GetID(s_rActor);
if (!s_rActor)
{
return;
}

if (auto* s_pActorSpatial = s_rActor.QueryInterface<ZSpatialEntity>())
{
auto s_mActorTransform = s_pActorSpatial->GetObjectToWorldMatrix();

// spawn wine bottle slightly above the actor
auto s_vPos = s_mActorTransform.Trans;
s_vPos += (s_mActorTransform.Up.Normalized()) * 0.1f;

SpawnRepositoryPropAt(m_WineBottleProp, s_vPos);

// mark body hidden
// idk if this works...
p_pActor->m_bBodyHidden = true;

// make the actor invisible
s_rActor.SetProperty("m_bVisible", false);

if (!m_bTeleportBodies)
{
return;
}

// aquire ref to physics entity
ZEntityRef s_rPhysicsSystem;
if (auto* s_pBlueprint = Utils::GetEntityBlueprintFactoryFor(s_rActor))
{
// [assembly:/templates/gameplay/ai2/actors.template?/npcactor.entitytemplate].pc_entitytype
// sub-entity "PhysicsSystem" of "NPCActor"
if (const auto s_nIdx = s_pBlueprint->GetSubEntityIndex(0x66aaaad61b6a51a5); s_nIdx != -1)
{
if (auto* s_pEntity = s_pBlueprint->GetSubEntity(s_rActor.m_pObj, s_nIdx))
{
s_rPhysicsSystem = ZEntityRef(s_pEntity);
}
}
}

// disable physics of ragdoll so the TP sticks
if (s_rPhysicsSystem)
{
s_rPhysicsSystem.SetProperty("m_bActive", false);
}
else
{
Logger::Debug(TAG "could not get PhysicsSystem sub-entity of NPCActor {}", p_pActor->GetActorName());
}

// setting the body invisible does NOT prevent interactions, so teleport out of the way
s_mActorTransform.Pos.x = 0.f;
s_mActorTransform.Pos.y = 0.f;
s_mActorTransform.Pos.z = -999.f;
s_pActorSpatial->SetObjectToWorldMatrixFromEditor(s_mActorTransform);
}
}

REGISTER_CHAOS_EFFECT(ZActorToWineEffect);
24 changes: 24 additions & 0 deletions src/Effects/Actors/ZActorToWineEffect.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once
#include "Effects/Base/ZActorWellbeingChangeEffectBase.h"
#include "Effects/Base/ZSpawnRepositoryItemEffectBase.h"

class ZActorToWineEffect : public ZActorWellbeingChangeEffectBase, public ZSpawnRepositoryItemEffectBase
{
public:
void LoadResources() override;
void OnDrawDebugUI() override;

std::string GetDisplayName(const bool p_bVoting) const override
{
return "VINO HUMANO";
}

protected:
void OnActorWellbeingChanged(ZActor* p_pActor, const SActorState& p_OldState, const SActorState& p_NewState) override;

private:
SRepositoryPropInfo m_WineBottleProp;
bool m_bTeleportBodies = true;

void OnActorPerished(ZActor* p_pActor);
};
24 changes: 20 additions & 4 deletions src/Effects/Base/ZActorWellbeingChangeEffectBase.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,7 @@ void ZActorWellbeingChangeEffectBase::OnSlowUpdate(const float32 p_fDeltaTime, c

for (auto* s_pActor : Utils::GetActors(true, true))
{
const SActorState s_CurrentState{
.m_bDead = s_pActor->IsDead(),
.m_bPacified = s_pActor->IsPacified()
};
const auto s_CurrentState = GetActorState(s_pActor);

auto s_it = m_mLastActorStates.find(s_pActor);
if (s_it == m_mLastActorStates.end())
Expand All @@ -47,3 +44,22 @@ void ZActorWellbeingChangeEffectBase::OnSlowUpdate(const float32 p_fDeltaTime, c
OnActorWellbeingChanged(s_pActor, s_LastState, s_CurrentState);
}
}

ZActorWellbeingChangeEffectBase::SActorState ZActorWellbeingChangeEffectBase::GetActorState(ZActor* p_pActor)
{
// ZActor's methods for detecting the state of actors are quite unintuitive:
// - when Pacified: IsDead() = true, IsAlive = false, IsPacified() = true, m_bAlive = false
// - when Killed: IsDead() = true, IsAlive = false, IsPacified() = false, m_bAlive = false
//
// i gues for IOI, a pacified actor is considered "dead" in the sense that they cannot do anything...
// for our purposes, we'd want a clear distinction between dead and pacified actors.
// thus, this method does some additional checks to determine the actual state of the actor, which is then used for comparison in the effect logic.

const auto s_bIsPacified = p_pActor->IsPacified();
const auto s_bIsDead = p_pActor->IsDead();

return {
.m_bDead = s_bIsDead && !s_bIsPacified, // only consider dead when not pacified
.m_bPacified = s_bIsPacified
};
}
10 changes: 10 additions & 0 deletions src/Effects/Base/ZActorWellbeingChangeEffectBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ class ZActorWellbeingChangeEffectBase : public virtual IChaosEffect
protected:
struct SActorState
{
/**
* Is the Actor fully dead (not just pacified)?
* Note that this does NOT correspond to the IsDead() method of ZActor.
*/
bool m_bDead;

/**
* Is the Actor pacified?
*/
bool m_bPacified;

bool operator==(const SActorState& p_Other) const
Expand All @@ -30,6 +38,8 @@ class ZActorWellbeingChangeEffectBase : public virtual IChaosEffect
}
};

static SActorState GetActorState(ZActor* p_pActor);

virtual void OnActorWellbeingChanged(ZActor* p_pActor, const SActorState& p_OldState, const SActorState& p_NewState) = 0;

private:
Expand Down
195 changes: 195 additions & 0 deletions src/Effects/Base/ZSpawnRepositoryItemEffectBase.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#include "ZSpawnRepositoryItemEffectBase.h"

#include <Glacier/ZContentKitManager.h>

#include <Logging.h>

#include "Helpers/Math.h"

#define TAG "[ZSpawnRepositoryItemEffectBase] "

std::vector<ZSpawnRepositoryItemEffectBase::SRepositoryPropInfo> ZSpawnRepositoryItemEffectBase::m_aRepositoryProps;

void ZSpawnRepositoryItemEffectBase::LoadResources()
{
m_pItemSpawnerSpawner = ZTemplateEntitySpawner::Create<"[modules:/zitemspawner.class].pc_entitytype">();
m_pRepositoryKeywordSpawner = ZTemplateEntitySpawner::Create<"[modules:/zitemrepositorykeyentity.class].pc_entitytype">();

m_bIsAvailable = LoadRepositoryProps();
}

void ZSpawnRepositoryItemEffectBase::OnClearScene()
{
m_aRepositoryProps.clear();

m_pItemSpawnerSpawner = nullptr;
m_pRepositoryKeywordSpawner = nullptr;
}

bool ZSpawnRepositoryItemEffectBase::Available() const
{
return IChaosEffect::Available()
&& m_pItemSpawnerSpawner && m_pItemSpawnerSpawner->IsAvailable()
&& m_pRepositoryKeywordSpawner && m_pRepositoryKeywordSpawner->IsAvailable()
&& m_aRepositoryProps.size() > 0;
}

void ZSpawnRepositoryItemEffectBase::OnDrawDebugUI()
{
ImGui::TextUnformatted(fmt::format("Item Spawner Prop: {}", m_pItemSpawnerSpawner->ToString()).c_str());
ImGui::TextUnformatted(fmt::format("Keyword Prop: {}", m_pRepositoryKeywordSpawner->ToString()).c_str());
ImGui::TextUnformatted(fmt::format("# Repository Props Loaded: {}", m_aRepositoryProps.size()).c_str());
}

bool ZSpawnRepositoryItemEffectBase::SpawnRepositoryPropAt(const SRepositoryPropInfo& p_RepositoryProp, const float4 s_vPosition, const ZItemSpawner::EPhysicsMode p_ePhysicsMode)
{
if (!p_RepositoryProp || !m_pItemSpawnerSpawner || !m_pRepositoryKeywordSpawner)
{
Logger::Debug(TAG "invalid parameters or spawners not initialized");
return false;
}

const auto s_rItemSpawner = m_pItemSpawnerSpawner->SpawnAs<ZItemSpawner>();
if (!s_rItemSpawner)
{
Logger::Debug(TAG "failed to spawn item spawner");
return false;
}

const auto s_rKeywordEntity = m_pRepositoryKeywordSpawner->SpawnAs<ZItemRepositoryKeyEntity>();
if (!s_rKeywordEntity)
{
Logger::Debug(TAG "failed to spawn repository keyword entity");
m_pItemSpawnerSpawner->Despawn(s_rItemSpawner.m_entityRef);
return false;
}

s_rItemSpawner.m_pInterfaceRef->m_ePhysicsMode = p_ePhysicsMode;
s_rItemSpawner.m_pInterfaceRef->m_rMainItemKey = s_rKeywordEntity;
s_rItemSpawner.m_pInterfaceRef->m_bUsePlacementAttach = false;

SMatrix s_Transform = SMatrix();
s_Transform.Trans = s_vPosition;
s_rItemSpawner.m_pInterfaceRef->SetObjectToWorldMatrixFromEditor(s_Transform);

s_rKeywordEntity.m_pInterfaceRef->m_RepositoryId = ZRepositoryID(p_RepositoryProp.m_sID);

//Functions::ZItemSpawner_RequestContentLoad->Call(s_rItemSpawner.m_pInterfaceRef);
s_rItemSpawner.m_entityRef.SignalInputPin("SpawnItem");

Logger::Debug(TAG "spawned item '{}' [{}] ({}).",
p_RepositoryProp.m_sDisplayName,
p_RepositoryProp.m_sID,
p_RepositoryProp.m_sCommonName
);
return true;
}

bool ZSpawnRepositoryItemEffectBase::LoadRepositoryProps()
{
if (m_aRepositoryProps.size() > 0)
{
Logger::Debug(TAG "repository props already loaded, skipping load");
return true;
}

const auto s_RepoID = ResId<"[assembly:/repository/pro.repo].pc_repo">;
TResourcePtr<ZTemplateEntityFactory> s_RepositoryResource;
Globals::ResourceManager->GetResourcePtr(s_RepositoryResource, s_RepoID, 0);

if (!s_RepositoryResource || s_RepositoryResource.GetResourceInfo().status != RESOURCE_STATUS_VALID)
{
Logger::Debug(TAG "pro.repo not loaded");
return false;
}

const auto s_mRepositoryData = static_cast<THashMap<ZRepositoryID, ZDynamicObject, TDefaultHashMapPolicy<ZRepositoryID>>*>(s_RepositoryResource.GetResourceData());
for (const auto& [s_RepositoryId, s_Obj] : *s_mRepositoryData)
{
const auto* s_pEntries = s_Obj.As<TArray<SDynamicObjectKeyValuePair>>();

std::string s_sId, s_sTitle, s_sCommonName, s_sName;
bool s_bIsItem = false;
for (const auto& s_Entry : *s_pEntries)
{
const std::string s_sKey = std::string(s_Entry.sKey.c_str(), s_Entry.sKey.size());

if (s_sKey == "ID_")
s_sId = DynamicObjectToString(s_Entry.value);
else if (s_sKey == "Title")
s_sTitle = DynamicObjectToString(s_Entry.value);
else if (s_sKey == "CommonName")
s_sCommonName = DynamicObjectToString(s_Entry.value);
else if (s_sKey == "Name")
s_sName = DynamicObjectToString(s_Entry.value);
else if (s_sKey == "ItemType")
s_bIsItem = true;
}

if (s_sId.empty() || !s_bIsItem)
{
continue;
}

std::string s_sDisplayName;
if (!s_sTitle.empty())
s_sDisplayName = s_sTitle;
else if (!s_sCommonName.empty())
s_sDisplayName = s_sCommonName;
else if (!s_sName.empty())
s_sDisplayName = s_sName;
else
s_sDisplayName = "";

SRepositoryPropInfo s_Info{
.m_sID = s_sId,
.m_sCommonName = s_sCommonName,
.m_sDisplayName = s_sDisplayName
};
m_aRepositoryProps.push_back(s_Info);
}

Logger::Debug(TAG "Loaded {} repository items.", m_aRepositoryProps.size());
return m_aRepositoryProps.size() > 0;
}

std::string ZSpawnRepositoryItemEffectBase::DynamicObjectToString(const ZDynamicObject& p_DynamicObject)
{
const auto* s_pType = p_DynamicObject.GetTypeID()->GetTypeInfo();
const auto s_sTypeName = std::string(s_pType->pszTypeName);

if (s_sTypeName == "ZString")
{
const auto* s_pValue = p_DynamicObject.As<ZString>();
return s_pValue->c_str();
}

// don't care about any other types for now
return "";
}

ZSpawnRepositoryItemEffectBase::SRepositoryPropInfo ZSpawnRepositoryItemEffectBase::GetRepositoryPropByID(const std::string& p_sID) const
{
for (const auto& s_Prop : m_aRepositoryProps)
{
if (s_Prop.m_sID == p_sID)
{
return s_Prop;
}
}

return {};
}

ZSpawnRepositoryItemEffectBase::SRepositoryPropInfo ZSpawnRepositoryItemEffectBase::GetRepositoryPropByCommonName(const std::string& p_sCommonName) const
{
for (const auto& s_Prop : m_aRepositoryProps)
{
if (s_Prop.m_sCommonName == p_sCommonName)
{
return s_Prop;
}
}

return {};
}
Loading