mirror of
				https://github.com/DarkflameUniverse/DarkflameServer.git
				synced 2025-11-03 22:21:59 +00:00 
			
		
		
		
	dCinema improvements
* Visiblity and effect records * Recorder will catch effects from behaviors * Documentation for setting up a scene to play automatically. * Documentation for server-side preconditions.
This commit is contained in:
		@@ -34,7 +34,13 @@ void dConfig::ReloadConfig() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::string& dConfig::GetValue(std::string key) {
 | 
			
		||||
	return this->m_ConfigValues[key];
 | 
			
		||||
	static std::string emptyString{};
 | 
			
		||||
	
 | 
			
		||||
	const auto& it = this->m_ConfigValues.find(key);
 | 
			
		||||
 | 
			
		||||
	if (it == this->m_ConfigValues.end()) return emptyString;
 | 
			
		||||
 | 
			
		||||
	return it->second;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void dConfig::ProcessLine(const std::string& line) {
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@
 | 
			
		||||
#include "Game.h"
 | 
			
		||||
#include "Logger.h"
 | 
			
		||||
 | 
			
		||||
#include "Recorder.h"
 | 
			
		||||
 | 
			
		||||
void AttackDelayBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, const BehaviorBranchContext branch) {
 | 
			
		||||
	uint32_t handle{};
 | 
			
		||||
 | 
			
		||||
@@ -11,6 +13,8 @@ void AttackDelayBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi
 | 
			
		||||
		LOG("Unable to read handle from bitStream, aborting Handle! %i", bitStream->GetNumberOfUnreadBits());
 | 
			
		||||
		return;
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
	Cinema::Recording::Recorder::RegisterEffectForActor(context->originator, this->m_effectId);
 | 
			
		||||
 | 
			
		||||
	for (auto i = 0u; i < this->m_numIntervals; ++i) {
 | 
			
		||||
		context->RegisterSyncBehavior(handle, this, branch, this->m_delay * i, m_ignoreInterrupts);
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,17 @@
 | 
			
		||||
#include "BehaviorContext.h"
 | 
			
		||||
#include "BehaviorBranchContext.h"
 | 
			
		||||
 | 
			
		||||
#include "Recorder.h"
 | 
			
		||||
 | 
			
		||||
void PlayEffectBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
 | 
			
		||||
	const auto& target = branch.target == LWOOBJID_EMPTY ? context->originator : branch.target;
 | 
			
		||||
 | 
			
		||||
	Cinema::Recording::Recorder::RegisterEffectForActor(target, this->m_effectId);
 | 
			
		||||
	
 | 
			
		||||
	// On managed behaviors this is handled by the client
 | 
			
		||||
	if (!context->unmanaged)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	const auto& target = branch.target == LWOOBJID_EMPTY ? context->originator : branch.target;
 | 
			
		||||
 | 
			
		||||
	PlayFx(u"", target);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -36,9 +36,17 @@ void Cinema::Play::CheckForAudience() {
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (scene->IsPlayerInBounds(player)) {
 | 
			
		||||
		SignalBarrier("audience");
 | 
			
		||||
		
 | 
			
		||||
		m_PlayerHasBeenInsideBounds = true;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	if (!scene->IsPlayerInBounds(player)) {
 | 
			
		||||
		Conclude();
 | 
			
		||||
	if (!scene->IsPlayerInShowingDistance(player)) {
 | 
			
		||||
		if (m_PlayerHasBeenInsideBounds) {
 | 
			
		||||
			Conclude();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		CleanUp();
 | 
			
		||||
 | 
			
		||||
@@ -70,7 +78,6 @@ void Cinema::Play::SetupBarrier(const std::string& barrier, std::function<void()
 | 
			
		||||
 | 
			
		||||
void Cinema::Play::SignalBarrier(const std::string& barrier) {
 | 
			
		||||
	if (m_Barriers.find(barrier) == m_Barriers.end()) {
 | 
			
		||||
		LOG("Barrier %s does not exist", barrier.c_str());
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -77,6 +77,8 @@ private:
 | 
			
		||||
 | 
			
		||||
	bool m_CheckForAudience = false;
 | 
			
		||||
 | 
			
		||||
	bool m_PlayerHasBeenInsideBounds = false;
 | 
			
		||||
 | 
			
		||||
	std::unordered_map<std::string, std::vector<std::function<void()>>> m_Barriers;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -22,6 +22,8 @@ A play is created is a couple of steps:
 | 
			
		||||
2. Create prefabs of props in the scene.
 | 
			
		||||
3. Put the NPCs and props in a scene file.
 | 
			
		||||
4. Edit the performed acts to add synchronization, conditions and additional actions.
 | 
			
		||||
5. Setting up the scene to be performed automatically.
 | 
			
		||||
6. Hiding zone objects while performing.
 | 
			
		||||
 | 
			
		||||
### 1. Acts out the scene
 | 
			
		||||
See <a href="./media/acting.mp4">media/acting.mp4</a> for an example of how to act out a scene. 
 | 
			
		||||
@@ -260,4 +262,91 @@ This record sends a signal to all acts waiting for it.
 | 
			
		||||
This record concludes the play.
 | 
			
		||||
```xml
 | 
			
		||||
<ConcludeRecord t="0" />
 | 
			
		||||
```
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.15. VisiblityRecord
 | 
			
		||||
This record makes the actor visible or invisible.
 | 
			
		||||
```xml
 | 
			
		||||
<VisiblityRecord visible="false" t="0" />
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 4.16. PlayEffectRecord
 | 
			
		||||
This record plays an effect.
 | 
			
		||||
```xml
 | 
			
		||||
<PlayEffectRecord effect="5307" t="0" />
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 5. Setting up the scene to be performed automatically
 | 
			
		||||
Scenes can be appended with metadata to describe when they should be performed and what consequences they have. This is done by editing the scene file.
 | 
			
		||||
 | 
			
		||||
#### 5.1. Scene metadata
 | 
			
		||||
There attributes can be added to the `Scene` tag:
 | 
			
		||||
 | 
			
		||||
| Attribute | Description |
 | 
			
		||||
| --- | --- |
 | 
			
		||||
| `x y z` | The center of where the following two attributes <br> are measured. |
 | 
			
		||||
| `showingDistance` | The distance at which the scene will <br> be loaded for a player.<br><br> If the player exits this area the scene is unloaded. <br> If the scene has been registered as having been<br>viewed by the player, is is concluded. |
 | 
			
		||||
| `performingDistance` | The scene is registred as having been <br>viewed by the player. This doesn't mean <br>it can't be viewed again.<br><br>A signal named `"audiance"` will be <br> sent when the player enters this area. <br>This can be used to trigger the main <br>part of the scene. |
 | 
			
		||||
| `acceptMission` | The mission with the given id will be <br> accepted when the scene is concluded. |
 | 
			
		||||
| `completeMission` | The mission with the given id will be <br> completed when the scene is concluded. |
 | 
			
		||||
 | 
			
		||||
Here is an example of a scene with metadata:
 | 
			
		||||
```xml
 | 
			
		||||
<Scene x="-368.272" y="1161.89" z="-5.25745" performingDistance="50" showingDistance="200">
 | 
			
		||||
    ...
 | 
			
		||||
</Scene>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### 5.2. Automatic scene setup
 | 
			
		||||
In either the worldconfig.ini or sharedconfig.ini file, add the following:
 | 
			
		||||
```
 | 
			
		||||
# Path to where scenes are located.
 | 
			
		||||
scenes_directory=vanity/scenes/
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Now move the scene into a subdirectory of the scenes directory. The name of the subdirectory should be **the zone id** of the zone the scene is located in.
 | 
			
		||||
 | 
			
		||||
For example:
 | 
			
		||||
```
 | 
			
		||||
build/
 | 
			
		||||
├── vanity/
 | 
			
		||||
│   ├── scenes/
 | 
			
		||||
│   │   ├── 1900/
 | 
			
		||||
│   │   │   ├── my-scene.xml
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Now the scene will be setup automatically and loaded when the player enters the `showingDistance` of the scene.
 | 
			
		||||
 | 
			
		||||
#### 5.3. Adding conditions
 | 
			
		||||
Conditions can be added to the scene to make it only performable when the player fulfills specified preconditions. This is done by editing the scene file.
 | 
			
		||||
 | 
			
		||||
Here is an example of a scene with conditions:
 | 
			
		||||
```xml
 | 
			
		||||
<Scene x="-368.272" y="1161.89" z="-5.25745" performingDistance="50" showingDistance="200">
 | 
			
		||||
    <Precondition expression="42,99"/> <!-- The player must fulfill preconditions 42 and 99. -->
 | 
			
		||||
    <Precondition expression="666" not="1"/> <!-- The player cannot fulfill precondition 666. -->
 | 
			
		||||
    ...
 | 
			
		||||
</Scene>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 6. Hiding zone objects while performing
 | 
			
		||||
When a scene should be performed, you might want to hide some objects in the zone. This is done by adding server preconditions. This is a seperate file.
 | 
			
		||||
 | 
			
		||||
In either the worldconfig.ini or sharedconfig.ini file, add the following:
 | 
			
		||||
```
 | 
			
		||||
# Path to where server preconditions are located.
 | 
			
		||||
server_preconditions_directory=vanity/server-preconditions.xml
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Now create the server preconditions file in the directory specified.
 | 
			
		||||
 | 
			
		||||
Here is an example of a server preconditions file:
 | 
			
		||||
```xml
 | 
			
		||||
<Preconditions>
 | 
			
		||||
    <Entity lot="12261">
 | 
			
		||||
        <Precondition not="1">1006</Precondition>
 | 
			
		||||
    </Entity>
 | 
			
		||||
</Preconditions>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
This will hide the objects with lot 12261 for players who fulfill precondition 1006.
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,8 @@ using namespace Cinema::Recording;
 | 
			
		||||
 | 
			
		||||
std::unordered_map<LWOOBJID, Recorder*> m_Recorders = {};
 | 
			
		||||
 | 
			
		||||
std::unordered_map<int32_t, std::string> m_EffectAnimations = {};
 | 
			
		||||
 | 
			
		||||
Recorder::Recorder() {
 | 
			
		||||
	this->m_StartTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
 | 
			
		||||
	this->m_IsRecording = false;
 | 
			
		||||
@@ -169,7 +171,7 @@ void Recorder::ActingDispatch(Entity* actor, size_t index, Play* variables) {
 | 
			
		||||
			ActingDispatch(actor, index + 1, variables);
 | 
			
		||||
		});
 | 
			
		||||
		
 | 
			
		||||
		if (barrierRecord->timeout <= 0.0f) {
 | 
			
		||||
		if (barrierRecord->timeout <= 0.0001f) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -211,6 +213,17 @@ void Recorder::ActingDispatch(Entity* actor, size_t index, Play* variables) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if the record is a visibility record
 | 
			
		||||
	auto* visibilityRecord = dynamic_cast<VisibilityRecord*>(record);
 | 
			
		||||
 | 
			
		||||
	if (visibilityRecord) {
 | 
			
		||||
		if (visibilityRecord->visible) {
 | 
			
		||||
			ServerPreconditions::AddExcludeFor(actor->GetObjectID(), variables->player);
 | 
			
		||||
		} else {
 | 
			
		||||
			ServerPreconditions::RemoveExcludeFor(actor->GetObjectID(), variables->player);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if the record is a player proximity record
 | 
			
		||||
	auto* playerProximityRecord = dynamic_cast<PlayerProximityRecord*>(record);
 | 
			
		||||
 | 
			
		||||
@@ -359,6 +372,45 @@ Recorder* Recorder::GetRecorder(LWOOBJID actorID) {
 | 
			
		||||
	return iter->second;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Cinema::Recording::Recorder::RegisterEffectForActor(LWOOBJID actorID, const int32_t& effectId) {
 | 
			
		||||
	auto iter = m_Recorders.find(actorID);
 | 
			
		||||
	if (iter == m_Recorders.end()) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto& recorder = iter->second;
 | 
			
		||||
 | 
			
		||||
	const auto& effectIter = m_EffectAnimations.find(effectId);
 | 
			
		||||
 | 
			
		||||
	if (effectIter == m_EffectAnimations.end()) {
 | 
			
		||||
		auto statement = CDClientDatabase::CreatePreppedStmt("SELECT animationName FROM BehaviorEffect WHERE effectID = ? LIMIT 1;");
 | 
			
		||||
 | 
			
		||||
		statement.bind(1, effectId);
 | 
			
		||||
 | 
			
		||||
		auto result = statement.execQuery();
 | 
			
		||||
 | 
			
		||||
		if (result.eof()) {
 | 
			
		||||
			result.finalize();
 | 
			
		||||
 | 
			
		||||
			m_EffectAnimations.emplace(effectId, "");
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			const auto animationName = result.getStringField(0);
 | 
			
		||||
 | 
			
		||||
			m_EffectAnimations.emplace(effectId, animationName);
 | 
			
		||||
 | 
			
		||||
			result.finalize();
 | 
			
		||||
 | 
			
		||||
			recorder->AddRecord(new AnimationRecord(animationName));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else {
 | 
			
		||||
		recorder->AddRecord(new AnimationRecord(effectIter->second));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	recorder->AddRecord(new PlayEffectRecord(std::to_string(effectId)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MovementRecord::MovementRecord(const NiPoint3& position, const NiQuaternion& rotation, const NiPoint3& velocity, const NiPoint3& angularVelocity, bool onGround, bool dirtyVelocity, bool dirtyAngularVelocity) {
 | 
			
		||||
	this->position = position;
 | 
			
		||||
	this->rotation = rotation;
 | 
			
		||||
@@ -691,6 +743,14 @@ Recorder* Recorder::LoadFromFile(const std::string& filename) {
 | 
			
		||||
			PlayerProximityRecord* record = new PlayerProximityRecord();
 | 
			
		||||
			record->Deserialize(element);
 | 
			
		||||
			recorder->m_Records.push_back(record);
 | 
			
		||||
		} else if (name == "VisibilityRecord") {
 | 
			
		||||
			VisibilityRecord* record = new VisibilityRecord();
 | 
			
		||||
			record->Deserialize(element);
 | 
			
		||||
			recorder->m_Records.push_back(record);
 | 
			
		||||
		} else if (name == "PlayEffectRecord") {
 | 
			
		||||
			PlayEffectRecord* record = new PlayEffectRecord();
 | 
			
		||||
			record->Deserialize(element);
 | 
			
		||||
			recorder->m_Records.push_back(record);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (element->Attribute("name")) {
 | 
			
		||||
@@ -943,4 +1003,64 @@ void Cinema::Recording::ConcludeRecord::Serialize(tinyxml2::XMLDocument& documen
 | 
			
		||||
 | 
			
		||||
void Cinema::Recording::ConcludeRecord::Deserialize(tinyxml2::XMLElement* element) {
 | 
			
		||||
	m_Delay = element->DoubleAttribute("t");
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Cinema::Recording::VisibilityRecord::VisibilityRecord(bool visible) {
 | 
			
		||||
	this->visible = visible;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Cinema::Recording::VisibilityRecord::Act(Entity* actor) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Cinema::Recording::VisibilityRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
 | 
			
		||||
	auto* element = document.NewElement("VisibilityRecord");
 | 
			
		||||
 | 
			
		||||
	element->SetAttribute("visible", visible);
 | 
			
		||||
 | 
			
		||||
	element->SetAttribute("t", m_Delay);
 | 
			
		||||
 | 
			
		||||
	parent->InsertEndChild(element);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Cinema::Recording::VisibilityRecord::Deserialize(tinyxml2::XMLElement* element) {
 | 
			
		||||
	visible = element->BoolAttribute("visible");
 | 
			
		||||
 | 
			
		||||
	m_Delay = element->DoubleAttribute("t");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Cinema::Recording::PlayEffectRecord::PlayEffectRecord(const std::string& effect) {
 | 
			
		||||
	this->effect = effect;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Cinema::Recording::PlayEffectRecord::Act(Entity* actor) {
 | 
			
		||||
	int32_t effectID = 0;
 | 
			
		||||
 | 
			
		||||
	if (!GeneralUtils::TryParse(effect, effectID)) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	GameMessages::SendPlayFXEffect(
 | 
			
		||||
		actor->GetObjectID(),
 | 
			
		||||
		effectID,
 | 
			
		||||
		u"cast",
 | 
			
		||||
		std::to_string(ObjectIDManager::GenerateRandomObjectID())
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	Game::entityManager->SerializeEntity(actor);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Cinema::Recording::PlayEffectRecord::Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) {
 | 
			
		||||
	auto* element = document.NewElement("PlayEffectRecord");
 | 
			
		||||
 | 
			
		||||
	element->SetAttribute("effect", effect.c_str());
 | 
			
		||||
 | 
			
		||||
	element->SetAttribute("t", m_Delay);
 | 
			
		||||
 | 
			
		||||
	parent->InsertEndChild(element);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Cinema::Recording::PlayEffectRecord::Deserialize(tinyxml2::XMLElement* element) {
 | 
			
		||||
	effect = element->Attribute("effect");
 | 
			
		||||
 | 
			
		||||
	m_Delay = element->DoubleAttribute("t");
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ public:
 | 
			
		||||
class EquipRecord : public Record
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	LOT item;
 | 
			
		||||
	LOT item = LOT_NULL;
 | 
			
		||||
 | 
			
		||||
	EquipRecord() = default;
 | 
			
		||||
 | 
			
		||||
@@ -105,7 +105,7 @@ public:
 | 
			
		||||
class UnequipRecord : public Record
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	LOT item;
 | 
			
		||||
	LOT item = LOT_NULL;
 | 
			
		||||
 | 
			
		||||
	UnequipRecord() = default;
 | 
			
		||||
 | 
			
		||||
@@ -204,7 +204,7 @@ class BarrierRecord : public Record
 | 
			
		||||
public:
 | 
			
		||||
	std::string signal;
 | 
			
		||||
 | 
			
		||||
	float timeout;
 | 
			
		||||
	float timeout = 0.0f;
 | 
			
		||||
 | 
			
		||||
	std::string timeoutLabel;
 | 
			
		||||
 | 
			
		||||
@@ -250,9 +250,9 @@ public:
 | 
			
		||||
class PlayerProximityRecord : public Record
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	float distance;
 | 
			
		||||
	float distance = 0.0f;
 | 
			
		||||
	
 | 
			
		||||
	float timeout;
 | 
			
		||||
	float timeout = 0.0f;
 | 
			
		||||
 | 
			
		||||
	std::string timeoutLabel;
 | 
			
		||||
 | 
			
		||||
@@ -267,6 +267,37 @@ public:
 | 
			
		||||
	void Deserialize(tinyxml2::XMLElement* element) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class VisibilityRecord : public Record
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	bool visible = false;
 | 
			
		||||
 | 
			
		||||
	VisibilityRecord() = default;
 | 
			
		||||
 | 
			
		||||
	VisibilityRecord(bool visible);
 | 
			
		||||
 | 
			
		||||
	void Act(Entity* actor) override;
 | 
			
		||||
 | 
			
		||||
	void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
 | 
			
		||||
	
 | 
			
		||||
	void Deserialize(tinyxml2::XMLElement* element) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PlayEffectRecord : public Record
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	std::string effect;
 | 
			
		||||
 | 
			
		||||
	PlayEffectRecord() = default;
 | 
			
		||||
 | 
			
		||||
	PlayEffectRecord(const std::string& effect);
 | 
			
		||||
 | 
			
		||||
	void Act(Entity* actor) override;
 | 
			
		||||
 | 
			
		||||
	void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
 | 
			
		||||
	
 | 
			
		||||
	void Deserialize(tinyxml2::XMLElement* element) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class Recorder
 | 
			
		||||
{
 | 
			
		||||
@@ -299,6 +330,8 @@ public:
 | 
			
		||||
 | 
			
		||||
	static Recorder* GetRecorder(LWOOBJID actorID);
 | 
			
		||||
 | 
			
		||||
	static void RegisterEffectForActor(LWOOBJID actorID, const int32_t& effectId);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void ActingDispatch(Entity* actor, size_t index, Play* variables);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,14 @@
 | 
			
		||||
#include "Scene.h"
 | 
			
		||||
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
 | 
			
		||||
#include <tinyxml2.h>
 | 
			
		||||
 | 
			
		||||
#include "ServerPreconditions.hpp"
 | 
			
		||||
#include "EntityManager.h"
 | 
			
		||||
#include "EntityInfo.h"
 | 
			
		||||
#include "MissionComponent.h"
 | 
			
		||||
#include "dConfig.h"
 | 
			
		||||
 | 
			
		||||
using namespace Cinema;
 | 
			
		||||
 | 
			
		||||
@@ -27,7 +30,7 @@ void Cinema::Scene::Rehearse() {
 | 
			
		||||
	CheckForShowings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Cinema::Scene::Conclude(Entity* player) const {
 | 
			
		||||
void Cinema::Scene::Conclude(Entity* player) {
 | 
			
		||||
	if (player == nullptr) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
@@ -49,6 +52,10 @@ void Cinema::Scene::Conclude(Entity* player) const {
 | 
			
		||||
	if (m_AcceptMission != 0) {
 | 
			
		||||
		missionComponent->AcceptMission(m_AcceptMission);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Remove the player from the audience
 | 
			
		||||
	m_Audience.erase(player->GetObjectID());
 | 
			
		||||
	m_HasBeenOutside.erase(player->GetObjectID());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Cinema::Scene::IsPlayerInBounds(Entity* player) const {
 | 
			
		||||
@@ -64,14 +71,60 @@ bool Cinema::Scene::IsPlayerInBounds(Entity* player) const {
 | 
			
		||||
	
 | 
			
		||||
	auto distance = NiPoint3::Distance(position, m_Center);
 | 
			
		||||
 | 
			
		||||
	LOG("Player distance from scene: %f, with bounds %f", distance, m_Bounds);
 | 
			
		||||
	return distance <= m_Bounds;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	// The player may be within 20% of the bounds
 | 
			
		||||
	return distance <= (m_Bounds * 1.2f);
 | 
			
		||||
bool Cinema::Scene::IsPlayerInShowingDistance(Entity* player) const {
 | 
			
		||||
	if (player == nullptr) {
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (m_ShowingDistance == 0.0f) {
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto& position = player->GetPosition();
 | 
			
		||||
 | 
			
		||||
	auto distance = NiPoint3::Distance(position, m_Center);
 | 
			
		||||
 | 
			
		||||
	return distance <= m_ShowingDistance;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Cinema::Scene::AutoLoadScenesForZone(LWOMAPID zone) {
 | 
			
		||||
	const auto& scenesRoot = Game::config->GetValue("scenes_directory");
 | 
			
		||||
 | 
			
		||||
	if (scenesRoot.empty()) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const auto path = std::filesystem::path(scenesRoot) / std::to_string(zone);
 | 
			
		||||
 | 
			
		||||
	if (!std::filesystem::exists(path)) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Recursively iterate through the directory
 | 
			
		||||
	for (const auto& entry : std::filesystem::recursive_directory_iterator(path)) {
 | 
			
		||||
		if (!entry.is_regular_file()) {
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Check that extension is .xml
 | 
			
		||||
		if (entry.path().extension() != ".xml") {
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const auto& file = entry.path().string();
 | 
			
		||||
 | 
			
		||||
		auto& scene = LoadFromFile(file);
 | 
			
		||||
 | 
			
		||||
		scene.Rehearse();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Cinema::Scene::CheckForShowings() {
 | 
			
		||||
	auto audience = m_Audience;
 | 
			
		||||
	auto hasBeenOutside = m_HasBeenOutside;
 | 
			
		||||
 | 
			
		||||
	for (const auto& member : audience) {
 | 
			
		||||
		if (Game::entityManager->GetEntity(member) == nullptr) {
 | 
			
		||||
@@ -79,6 +132,15 @@ void Cinema::Scene::CheckForShowings() {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (const auto& member : hasBeenOutside) {
 | 
			
		||||
		if (Game::entityManager->GetEntity(member) == nullptr) {
 | 
			
		||||
			m_HasBeenOutside.erase(member);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m_Audience = audience;
 | 
			
		||||
	m_HasBeenOutside = hasBeenOutside;
 | 
			
		||||
 | 
			
		||||
	// I don't care
 | 
			
		||||
	Game::entityManager->GetZoneControlEntity()->AddCallbackTimer(1.0f, [this]() {
 | 
			
		||||
		for (auto* player : Player::GetAllPlayers()) {
 | 
			
		||||
@@ -87,9 +149,9 @@ void Cinema::Scene::CheckForShowings() {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			CheckTicket(player);
 | 
			
		||||
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		CheckForShowings();
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -104,6 +166,16 @@ void Cinema::Scene::CheckTicket(Entity* player) {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!IsPlayerInShowingDistance(player)) {
 | 
			
		||||
		m_HasBeenOutside.emplace(player->GetObjectID());
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (m_HasBeenOutside.find(player->GetObjectID()) == m_HasBeenOutside.end()) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m_Audience.emplace(player->GetObjectID());
 | 
			
		||||
 | 
			
		||||
	Act(player);
 | 
			
		||||
@@ -225,8 +297,14 @@ Scene& Cinema::Scene::LoadFromFile(std::string file) {
 | 
			
		||||
		scene.m_Center = NiPoint3(root->FloatAttribute("x"), root->FloatAttribute("y"), root->FloatAttribute("z"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (root->Attribute("bounds")) {
 | 
			
		||||
		scene.m_Bounds = root->FloatAttribute("bounds");
 | 
			
		||||
	if (root->Attribute("performingDistance")) {
 | 
			
		||||
		scene.m_Bounds = root->FloatAttribute("performingDistance");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (root->Attribute("showingDistance")) {
 | 
			
		||||
		scene.m_ShowingDistance = root->FloatAttribute("showingDistance");
 | 
			
		||||
	} else {
 | 
			
		||||
		scene.m_ShowingDistance = scene.m_Bounds * 2.0f;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load accept and complete mission
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ public:
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param player The player to conclude the scene for (not nullptr).
 | 
			
		||||
	 */
 | 
			
		||||
	void Conclude(Entity* player) const;
 | 
			
		||||
	void Conclude(Entity* player);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @brief Checks if a given player is within the bounds of the scene.
 | 
			
		||||
@@ -59,6 +59,13 @@ public:
 | 
			
		||||
	 */
 | 
			
		||||
	bool IsPlayerInBounds(Entity* player) const;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @brief Checks if a given player is within the showing distance of the scene.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param player The player to check.
 | 
			
		||||
	 */
 | 
			
		||||
	bool IsPlayerInShowingDistance(Entity* player) const;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @brief Act the scene.
 | 
			
		||||
	 * 
 | 
			
		||||
@@ -75,6 +82,13 @@ public:
 | 
			
		||||
	 */
 | 
			
		||||
	static Scene& LoadFromFile(std::string file);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @brief Automatically loads the scenes for a given zone.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param zone The zone to load the scenes for.
 | 
			
		||||
	 */
 | 
			
		||||
	static void AutoLoadScenesForZone(LWOMAPID zone);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void CheckForShowings();
 | 
			
		||||
 | 
			
		||||
@@ -86,6 +100,7 @@ private:
 | 
			
		||||
 | 
			
		||||
	NiPoint3 m_Center;
 | 
			
		||||
	float m_Bounds = 0.0f;
 | 
			
		||||
	float m_ShowingDistance = 0.0f;
 | 
			
		||||
 | 
			
		||||
	std::vector<std::pair<PreconditionExpression, bool>> m_Preconditions;
 | 
			
		||||
 | 
			
		||||
@@ -93,6 +108,7 @@ private:
 | 
			
		||||
	int32_t m_CompleteMission = 0;
 | 
			
		||||
 | 
			
		||||
	std::unordered_set<LWOOBJID> m_Audience;
 | 
			
		||||
	std::unordered_set<LWOOBJID> m_HasBeenOutside;
 | 
			
		||||
 | 
			
		||||
	static std::unordered_map<std::string, Scene> m_Scenes;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -15,11 +15,21 @@
 | 
			
		||||
 | 
			
		||||
std::unordered_map<int32_t, float> RenderComponent::m_DurationCache{};
 | 
			
		||||
 | 
			
		||||
std::unordered_map<int32_t, std::vector<int32_t>> RenderComponent::m_AnimationGroupCache{};
 | 
			
		||||
 | 
			
		||||
RenderComponent::RenderComponent(Entity* parent, int32_t componentId): Component(parent) {
 | 
			
		||||
	m_Effects = std::vector<Effect*>();
 | 
			
		||||
	m_LastAnimationName = "";
 | 
			
		||||
	if (componentId == -1) return;
 | 
			
		||||
 | 
			
		||||
	const auto& it = m_AnimationGroupCache.find(componentId);
 | 
			
		||||
 | 
			
		||||
	if (it != m_AnimationGroupCache.end()) {
 | 
			
		||||
		m_animationGroupIds = it->second;
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM RenderComponent WHERE id = ?;");
 | 
			
		||||
	query.bind(1, componentId);
 | 
			
		||||
	auto result = query.execQuery();
 | 
			
		||||
@@ -41,6 +51,8 @@ RenderComponent::RenderComponent(Entity* parent, int32_t componentId): Component
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	result.finalize();
 | 
			
		||||
 | 
			
		||||
	m_AnimationGroupCache[componentId] = m_animationGroupIds;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RenderComponent::~RenderComponent() {
 | 
			
		||||
 
 | 
			
		||||
@@ -147,6 +147,11 @@ private:
 | 
			
		||||
	 * Cache of queries that look for the length of each effect, indexed by effect ID
 | 
			
		||||
	 */
 | 
			
		||||
	static std::unordered_map<int32_t, float> m_DurationCache;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Cache for animation groups, indexed by the component ID
 | 
			
		||||
	 */
 | 
			
		||||
	static std::unordered_map<int32_t, std::vector<int32_t>> m_AnimationGroupCache;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // RENDERCOMPONENT_H
 | 
			
		||||
 
 | 
			
		||||
@@ -1051,6 +1051,14 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (chatCommand == "scene-setup" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
 | 
			
		||||
		auto& scene = Cinema::Scene::LoadFromFile(args[0]);
 | 
			
		||||
 | 
			
		||||
		scene.Rehearse();
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if ((chatCommand == "teleport" || chatCommand == "tele") && entity->GetGMLevel() >= eGameMasterLevel::JUNIOR_MODERATOR) {
 | 
			
		||||
		NiPoint3 pos{};
 | 
			
		||||
		if (args.size() == 3) {
 | 
			
		||||
 
 | 
			
		||||
@@ -76,6 +76,7 @@
 | 
			
		||||
#include "CheatDetection.h"
 | 
			
		||||
 | 
			
		||||
#include "ServerPreconditions.hpp"
 | 
			
		||||
#include "Scene.h"
 | 
			
		||||
 | 
			
		||||
namespace Game {
 | 
			
		||||
	Logger* logger = nullptr;
 | 
			
		||||
@@ -257,8 +258,6 @@ int main(int argc, char** argv) {
 | 
			
		||||
 | 
			
		||||
	PerformanceManager::SelectProfile(zoneID);
 | 
			
		||||
 | 
			
		||||
	ServerPreconditions::LoadPreconditions("vanity/preconditions.xml");
 | 
			
		||||
 | 
			
		||||
	Game::entityManager = new EntityManager();
 | 
			
		||||
	Game::zoneManager = new dZoneManager();
 | 
			
		||||
	//Load our level:
 | 
			
		||||
@@ -312,6 +311,16 @@ int main(int argc, char** argv) {
 | 
			
		||||
		LOG("FDB Checksum calculated as: %s", databaseChecksum.c_str());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load server-side preconditions if they exist
 | 
			
		||||
	const auto& preconditionsPath = Game::config->GetValue("server_preconditions_path");
 | 
			
		||||
 | 
			
		||||
	if (!preconditionsPath.empty()) {
 | 
			
		||||
		ServerPreconditions::LoadPreconditions(preconditionsPath);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Load scenes for the zone
 | 
			
		||||
	Cinema::Scene::AutoLoadScenesForZone(zoneID);
 | 
			
		||||
 | 
			
		||||
	uint32_t currentFrameDelta = highFrameDelta;
 | 
			
		||||
	// These values are adjust them selves to the current framerate should it update.
 | 
			
		||||
	uint32_t logFlushTime = 15 * currentFramerate; // 15 seconds in frames
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user