Compare commits

...

36 Commits

Author SHA1 Message Date
Wincent
194cd669ee Migrations only flag 2025-04-18 16:01:02 +00:00
Wincent
6b92536c41 Cache build directory when using docker with BuildKit 2025-04-18 16:00:17 +00:00
Wincent
5796e62567 Merge remote-tracking branch 'origin/main' into dCinema 2025-04-18 14:49:18 +00:00
David Markowitz
6b0f3a66e9 fix: session flags not being loaded every other world load (#1763)
tested that news screen no longer shows up on every other world load
2025-04-11 09:10:38 -05:00
David Markowitz
f5c212fb86 fix: sys addr for private zones (#1760)
* fix: sys addr for private zones

* Initialize variables in Instance
2025-04-11 09:05:31 -05:00
wincent
bd863a6dcf Added a navmesh for old gf 2024-09-12 21:20:17 +02:00
wincent
f78b28b454 New records and commands 2024-09-12 21:18:48 +02:00
wincent
04e9e74d7c Additional record types 2024-09-12 15:32:44 +02:00
wincent
dfe924061f Amend preconditions fix 2024-09-12 15:31:36 +02:00
Wincent
a086fe730a Added clean-up param 2024-09-10 14:33:58 +00:00
wincent
06063c8c8a Fixed mission related preconditions 2024-09-10 12:40:49 +02:00
wincent
0d37da73c7 Add a max showing distance 2024-09-08 19:40:08 +02:00
wincent
c948e4bb84 Update to new apis 2024-09-08 17:54:43 +02:00
wincent
3efad8aa50 Merge remote-tracking branch 'origin/main' into dCinema 2024-09-08 17:43:20 +02:00
wincent
d52ce8000b Merge remote-tracking branch 'refs/remotes/origin/main'
Conflicts:
	CMakeLists.txt
	dGame/CMakeLists.txt
	dGame/Entity.cpp
	dGame/dBehaviors/AttackDelayBehavior.cpp
	dGame/dBehaviors/PlayEffectBehavior.cpp
2024-04-02 21:51:29 +02:00
wincent
685bd5d45b Updated to new API 2024-02-17 23:07:16 +01:00
wincent
88e5f0e8fb Merge remote-tracking branch 'refs/remotes/origin/main'
Conflicts:
	dGame/EntityManager.cpp
	dGame/Player.h
	dGame/dComponents/RenderComponent.cpp
2024-02-17 23:06:43 +01:00
wincent
7b7c0622b3 Update to new API 2024-01-16 21:46:44 +01:00
wincent
a91cf3adc7 Merge remote-tracking branch 'refs/remotes/origin/main' 2024-01-16 21:46:31 +01:00
wincent
3a60fffe1d Resolved some more comments 2023-11-20 20:26:52 +01:00
wincent
17a62d95e0 Resolved some comments 2023-11-20 19:52:27 +01:00
wincent
32cbd18e6b Fix CMake file 2023-11-14 14:13:58 +01:00
wincent
25e1482a25 Merge remote-tracking branch 'origin/main' into dCinema 2023-11-14 14:06:20 +01:00
wincent
7a7bdba3e1 Removed test script 2023-11-14 13:53:43 +01:00
wincent
e5467379e1 Reverted local CMakeVariables changes 2023-11-14 13:51:41 +01:00
wincent
14d4c87a2f More record types:
+ Can now specify a cordinate to path find to
+ Can now enable/disable the combat AI
2023-11-14 13:24:09 +01:00
wincent
a09bbdba32 Merge remote-tracking branch 'origin/main' into dCinema 2023-11-11 14:20:13 +01:00
wincent
54060f7f40 Minor refactor 2023-11-11 14:19:57 +01:00
wincent
9954e20eac More scene metadata
* Added the ability to specify a change to play
* Added the ability to specify if a scene can play multiple times to the same player
2023-10-30 19:02:54 +01:00
wincent
cdc9dda3c4 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.
2023-10-29 17:37:26 +01:00
wincent
e4320d3e63 Brought the branch up to speed
* Updated logging
* Added an image to the README
2023-10-28 12:28:17 +02:00
wincent
3f90a4dd0b Merge remote-tracking branch 'origin/main' into dCinema 2023-10-28 11:23:29 +02:00
wincent
b274ea1b8f Substational additions to dCinema
Includes documentation of how to create acts, prefabs and scenes.
2023-10-27 23:51:38 +02:00
wincent
01b40ffa08 Merge fix 2023-10-22 17:38:19 +02:00
wincent
da236f272d Merge remote-tracking branch 'refs/remotes/origin/main'
Conflicts:
	dGame/dGameMessages/GameMessages.cpp
2023-10-22 17:38:08 +02:00
wincent
9e56725cff Initial changes.
* Recorder to recall player actions.
* Server precondtions to manage entity visiblity.
2023-10-22 17:36:08 +02:00
46 changed files with 4432 additions and 59 deletions

View File

@@ -11,7 +11,12 @@ COPY --chmod=0500 ./build.sh /app/
RUN sed -i 's/MARIADB_CONNECTOR_COMPILE_JOBS__=.*/MARIADB_CONNECTOR_COMPILE_JOBS__=2/' /app/CMakeVariables.txt
RUN ./build.sh
RUN --mount=type=cache,target=/app/build,id=build-cache \
mkdir -p /app/build /tmp/persisted-build && \
cd /app/build && \
cmake .. && \
make -j$(nproc --ignore 1) && \
cp -r /app/build/* /tmp/persisted-build/
FROM debian:12 as runtime
@@ -23,23 +28,23 @@ RUN --mount=type=cache,id=build-apt-cache,target=/var/cache/apt \
rm -rf /var/lib/apt/lists/*
# Grab libraries and load them
COPY --from=build /app/build/mariadbcpp/libmariadbcpp.so /usr/local/lib/
COPY --from=build /tmp/persisted-build/mariadbcpp/libmariadbcpp.so /usr/local/lib/
RUN ldconfig
# Server bins
COPY --from=build /app/build/*Server /app/
COPY --from=build /tmp/persisted-build/*Server /app/
# Necessary suplimentary files
COPY --from=build /app/build/*.ini /app/configs/
COPY --from=build /app/build/vanity/*.* /app/vanity/
COPY --from=build /app/build/navmeshes /app/navmeshes
COPY --from=build /app/build/migrations /app/migrations
COPY --from=build /app/build/*.dcf /app/
COPY --from=build /tmp/persisted-build/*.ini /app/configs/
COPY --from=build /tmp/persisted-build/vanity/*.* /app/vanity/
COPY --from=build /tmp/persisted-build/navmeshes /app/navmeshes
COPY --from=build /tmp/persisted-build/migrations /app/migrations
COPY --from=build /tmp/persisted-build/*.dcf /app/
# backup of config and vanity files to copy to the host incase
# of a mount clobbering the copy from above
COPY --from=build /app/build/*.ini /app/default-configs/
COPY --from=build /app/build/vanity/*.* /app/default-vanity/
COPY --from=build /tmp/persisted-build/*.ini /app/default-configs/
COPY --from=build /tmp/persisted-build/vanity/*.* /app/default-vanity/
# needed as the container runs with the root user
# and therefore sudo doesn't exist

View File

@@ -16,7 +16,7 @@ include_directories(
add_library(dGameBase OBJECT ${DGAME_SOURCES})
target_precompile_headers(dGameBase PRIVATE ${HEADERS_DGAME})
target_include_directories(dGameBase PUBLIC "." "dEntity"
PRIVATE "dComponents" "dGameMessages" "dBehaviors" "dMission" "dUtilities" "dInventory"
PRIVATE "dComponents" "dGameMessages" "dBehaviors" "dMission" "dUtilities" "dInventory" "dCinema"
$<TARGET_PROPERTY:dPropertyBehaviors,INTERFACE_INCLUDE_DIRECTORIES>
"${PROJECT_SOURCE_DIR}/dCommon"
"${PROJECT_SOURCE_DIR}/dCommon/dEnums"
@@ -40,6 +40,7 @@ add_subdirectory(dInventory)
add_subdirectory(dMission)
add_subdirectory(dPropertyBehaviors)
add_subdirectory(dUtilities)
add_subdirectory(dCinema)
add_library(dGame STATIC
$<TARGET_OBJECTS:dGameBase>
@@ -51,6 +52,7 @@ add_library(dGame STATIC
$<TARGET_OBJECTS:dMission>
$<TARGET_OBJECTS:dPropertyBehaviors>
$<TARGET_OBJECTS:dUtilities>
$<TARGET_OBJECTS:dCinema>
)
target_link_libraries(dGame INTERFACE dNet)
target_include_directories(dGame INTERFACE
@@ -63,4 +65,5 @@ target_include_directories(dGame INTERFACE
$<TARGET_PROPERTY:dMission,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:dPropertyBehaviors,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:dUtilities,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:dCinema,INTERFACE_INCLUDE_DIRECTORIES>
)

View File

@@ -204,6 +204,7 @@ void Character::DoQuickXMLDataParse() {
while (currentChild) {
const auto* temp = currentChild->Attribute("v");
const auto* id = currentChild->Attribute("id");
const auto* si = currentChild->Attribute("si");
if (temp && id) {
uint32_t index = 0;
uint64_t value = 0;
@@ -212,6 +213,9 @@ void Character::DoQuickXMLDataParse() {
value = std::stoull(temp);
m_PlayerFlags.insert(std::make_pair(index, value));
} else if (si) {
auto value = GeneralUtils::TryParse<uint32_t>(si);
if (value) m_SessionFlags.insert(value.value());
}
currentChild = currentChild->NextSiblingElement();
}

View File

@@ -82,6 +82,7 @@
#include "CollectibleComponent.h"
#include "ItemComponent.h"
#include "GhostComponent.h"
#include "Recorder.h"
#include "AchievementVendorComponent.h"
#include "VanityUtilities.h"
@@ -2178,6 +2179,20 @@ void Entity::ProcessPositionUpdate(PositionUpdate& update) {
if (updateChar) Game::entityManager->SerializeEntity(this);
auto* recorder = Cinema::Recording::Recorder::GetRecorder(GetObjectID());
if (recorder != nullptr) {
recorder->AddRecord(new Cinema::Recording::MovementRecord(
update.position,
update.rotation,
update.velocity,
update.angularVelocity,
update.onGround,
update.velocity != NiPoint3Constant::ZERO,
update.angularVelocity != NiPoint3Constant::ZERO
));
}
OnPlayerPositionUpdate.Notify(this, update);
}

View File

@@ -24,6 +24,7 @@
#include "eReplicaPacketType.h"
#include "PlayerManager.h"
#include "GhostComponent.h"
#include "ServerPreconditions.h"
#include <ranges>
// Configure which zones have ghosting disabled, mostly small worlds.
@@ -493,13 +494,15 @@ void EntityManager::UpdateGhosting(Entity* player) {
ghostingDistanceMax = ghostingDistanceMin;
}
if (observed && distance > ghostingDistanceMax && !isOverride) {
auto condition = ServerPreconditions::CheckPreconditions(entity, player);
if (observed && ((distance > ghostingDistanceMax && !isOverride) || !condition)) {
ghostComponent->GhostEntity(id);
DestructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() - 1);
} else if (!observed && ghostingDistanceMin > distance) {
} else if (!observed && ghostingDistanceMin > distance && condition) {
// Check collectables, don't construct if it has been collected
uint32_t collectionId = entity->GetCollectibleID();
@@ -539,13 +542,15 @@ void EntityManager::CheckGhosting(Entity* entity) {
const auto distance = NiPoint3::DistanceSquared(referencePoint, entityPoint);
if (observed && distance > m_GhostDistanceMaxSquared) {
const auto precondition = ServerPreconditions::CheckPreconditions(entity, player);
if (observed && (distance > m_GhostDistanceMaxSquared || !precondition)) {
ghostComponent->GhostEntity(id);
DestructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() - 1);
} else if (!observed && m_GhostDistanceMinSqaured > distance) {
} else if (!observed && (m_GhostDistanceMinSqaured > distance && precondition)) {
ghostComponent->ObserveEntity(id);
ConstructEntity(entity, player->GetSystemAddress());

View File

@@ -29,6 +29,7 @@
#include "MessageType/Chat.h"
#include "BitStreamUtils.h"
#include "CheatDetection.h"
#include "dConfig.h"
UserManager* UserManager::m_Address = nullptr;
@@ -517,7 +518,14 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID
Database::Get()->UpdateLastLoggedInCharacter(playerID);
uint32_t zoneID = character->GetZoneID();
if (zoneID == LWOZONEID_INVALID) zoneID = 1000; //Send char to VE
if (zoneID == LWOZONEID_INVALID) {
const std::string& defaultWorld = Game::config->GetValue("default_world");
if (!defaultWorld.empty()) {
zoneID = std::stoul(defaultWorld);
} else {
zoneID = 1000; //Send char to VE
}
}
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, zoneID, character->GetZoneClone(), false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);

View File

@@ -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);

View File

@@ -63,6 +63,7 @@ target_include_directories(dBehaviors PUBLIC "."
"${PROJECT_SOURCE_DIR}/dGame/dUtilities" # Preconditions.h via QuickBuildComponent.h
"${PROJECT_SOURCE_DIR}/dGame/dEntity" # via dZoneManager.h, Spawner.h
"${PROJECT_SOURCE_DIR}/dGame/dInventory" # via CharacterComponent.h
"${PROJECT_SOURCE_DIR}/dGame/dCinema"
"${PROJECT_SOURCE_DIR}/dZoneManager" # via BasicAttackBehavior.cpp
)
target_precompile_headers(dBehaviors REUSE_FROM dGameBase)

View File

@@ -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);
}

View File

@@ -0,0 +1,24 @@
set(DGAME_DCINEMA_SOURCES "Recorder.cpp"
"Prefab.cpp"
"Scene.cpp"
"Play.cpp")
add_library(dCinema STATIC ${DGAME_DCINEMA_SOURCES})
target_precompile_headers(dCinema REUSE_FROM dGameBase)
target_link_libraries(dCinema
PUBLIC dPhysics dDatabase
INTERFACE dUtilities dCommon dBehaviors dChatFilter dMission dInventory dComponents
)
target_include_directories(dCinema PUBLIC "."
PRIVATE
"${PROJECT_SOURCE_DIR}/dGame/dComponents" # direct MissionComponent.h
"${PROJECT_SOURCE_DIR}/dGame/dUtilities" # direct SlashCommandHandler.h
"${PROJECT_SOURCE_DIR}/dGame/dPropertyBehaviors" # direct ControlBehaviors.h
"${PROJECT_SOURCE_DIR}/dGame/dMission" # via MissionComponent.h
"${PROJECT_SOURCE_DIR}/dGame/dBehaviors" # via InventoryComponent.h
"${PROJECT_SOURCE_DIR}/dGame/dInventory" # via InventoryComponent.h
"${PROJECT_SOURCE_DIR}/dGame/dEntity" # via dZoneManager/Spawner.h
"${PROJECT_SOURCE_DIR}/dGame/dGameMessages"
"${PROJECT_SOURCE_DIR}/dZoneManager" # via GameMessages.cpp, GameMessageHandler.cpp
)

87
dGame/dCinema/Play.cpp Normal file
View File

@@ -0,0 +1,87 @@
#include "Play.h"
#include "Scene.h"
#include "EntityManager.h"
using namespace Cinema;
void Play::Conclude() {
auto* player = Game::entityManager->GetEntity(this->player);
if (player == nullptr) {
return;
}
scene->Conclude(player);
}
void Play::SetupCheckForAudience() {
if (m_CheckForAudience) {
return;
}
m_CheckForAudience = true;
CheckForAudience();
}
void Play::CheckForAudience() {
auto* player = Game::entityManager->GetEntity(this->player);
if (player == nullptr) {
CleanUp();
return;
}
if (scene->IsPlayerInBounds(player)) {
SignalBarrier("audience");
m_PlayerHasBeenInsideBounds = true;
}
if (!scene->IsPlayerInMaximumShowingDistance(player)) {
if (m_PlayerHasBeenInsideBounds) {
Conclude();
}
CleanUp();
return;
}
// As the scene isn't associated with a specifc objects, we'll use the zone control entity to setup a callback.
Game::entityManager->GetZoneControlEntity()->AddCallbackTimer(1.0f, [this]() {
CheckForAudience();
});
}
void Play::CleanUp() {
LOG("Cleaning up play with %d entities", entities.size());
for (const auto& entity : entities) {
Game::entityManager->DestroyEntity(entity);
}
}
void Play::SetupBarrier(const std::string& barrier, const std::function<void()>& callback) {
// Add the callback to the barrier
m_Barriers[barrier].push_back(callback);
}
void Play::SignalBarrier(const std::string& barrier) {
const auto& it = m_Barriers.find(barrier);
if (it == m_Barriers.end()) {
return;
}
for (const auto& callback : it->second) {
callback();
}
m_Barriers.erase(it);
}

86
dGame/dCinema/Play.h Normal file
View File

@@ -0,0 +1,86 @@
#pragma once
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <vector>
#include <functional>
#include "dCommonVars.h"
namespace Cinema
{
/**
* @brief A collection of variables and other information to be shared between scenes and acts.
*/
struct Play
{
public:
/**
* @brief The variables in the collection.
*/
std::unordered_map<std::string, std::string> variables;
/**
* @brief Associated player which is watching the scene.
*/
LWOOBJID player;
/**
* @brief A set of all entities involved in the scene.
*/
std::unordered_set<LWOOBJID> entities;
/**
* @brief The scene that is currently being performed.
*/
class Scene* scene;
/**
* @brief Conclude the play. Accepting/completing missions. This will not remove the entities.
*/
void Conclude();
/**
* @brief Setup a check for if the audience is still present.
*
* If the audience is no longer present, the play will be concluded and the entities will be removed.
*/
void SetupCheckForAudience();
/**
* @brief Clean up the play.
*/
void CleanUp();
/**
* @brief Setup a barrier. A callback is given when a signal is sent with the given barrier name.
*
* @param barrier The name of the barrier.
* @param callback The callback to call when the barrier is signaled.
*/
void SetupBarrier(const std::string& barrier, const std::function<void()>& callback);
/**
* @brief Signal a barrier.
*
* @param barrier The name of the barrier to signal.
*/
void SignalBarrier(const std::string& barrier);
private:
/**
* @brief Check if the audience is still present.
*/
void CheckForAudience();
bool m_CheckForAudience = false;
bool m_PlayerHasBeenInsideBounds = false;
std::unordered_map<std::string, std::vector<std::function<void()>>> m_Barriers;
};
}

190
dGame/dCinema/Prefab.cpp Normal file
View File

@@ -0,0 +1,190 @@
#include "Prefab.h"
#include "tinyxml2.h"
#include "ObjectIDManager.h"
#include "EntityManager.h"
#include "EntityInfo.h"
#include "RenderComponent.h"
using namespace Cinema;
struct PrefabInstance
{
std::vector<LWOOBJID> m_Entities;
};
namespace {
std::unordered_map<std::string, Prefab> m_Prefabs;
std::unordered_map<size_t, PrefabInstance> m_Instances;
float m_AngleToRadians = 0.0174532925f;
}
size_t Prefab::AddObject(LOT lot, NiPoint3 position, NiQuaternion rotation, float scale) {
const auto id = ObjectIDManager::GenerateRandomObjectID();
m_Pieces.emplace(id, Prefab::Piece {
lot,
position,
rotation,
scale,
std::vector<int32_t>()
});
return id;
}
void Prefab::AddEffect(size_t id, int32_t effect) {
m_Pieces[id].m_Effects.push_back(effect);
}
void Prefab::RemoveObject(size_t id) {
m_Pieces.erase(id);
}
const Prefab& Prefab::LoadFromFile(std::string file) {
if (m_Prefabs.find(file) != m_Prefabs.end()) {
return m_Prefabs[file];
}
Prefab prefab;
tinyxml2::XMLDocument doc;
doc.LoadFile(file.c_str());
tinyxml2::XMLElement* root = doc.FirstChildElement("Prefab");
if (!root) {
LOG("Failed to load prefab from file: %s", file.c_str());
m_Prefabs.emplace(file, prefab);
return m_Prefabs[file];
}
for (tinyxml2::XMLElement* element = root->FirstChildElement("Object"); element; element = element->NextSiblingElement("Object")) {
const auto lot = element->UnsignedAttribute("lot");
const auto position = NiPoint3(element->FloatAttribute("x"), element->FloatAttribute("y"), element->FloatAttribute("z"));
NiQuaternion rotation;
// Check if the qx attribute exists, if so the rotation is a quaternion, otherwise it's a vector
if (!element->Attribute("qx")) {
rotation = NiQuaternion::FromEulerAngles( {
element->FloatAttribute("rx") * m_AngleToRadians,
element->FloatAttribute("ry") * m_AngleToRadians,
element->FloatAttribute("rz") * m_AngleToRadians
} );
}
else {
rotation = NiQuaternion(element->FloatAttribute("qx"), element->FloatAttribute("qy"), element->FloatAttribute("qz"), element->FloatAttribute("qw"));
}
float scale = 1.0f;
if (element->Attribute("scale")) {
scale = element->FloatAttribute("scale");
}
const auto id = prefab.AddObject(lot, position, rotation, scale);
for (tinyxml2::XMLElement* effect = element->FirstChildElement("Effect"); effect; effect = effect->NextSiblingElement("Effect")) {
prefab.AddEffect(id, effect->IntAttribute("id"));
}
}
m_Prefabs.emplace(file, prefab);
return m_Prefabs[file];
}
void Prefab::SaveToFile(std::string file) {
tinyxml2::XMLDocument doc;
tinyxml2::XMLElement* root = doc.NewElement("Prefab");
doc.InsertFirstChild(root);
for (const auto& [id, piece] : m_Pieces) {
tinyxml2::XMLElement* object = doc.NewElement("Object");
object->SetAttribute("lot", piece.m_Lot);
object->SetAttribute("x", piece.m_Position.x);
object->SetAttribute("y", piece.m_Position.y);
object->SetAttribute("z", piece.m_Position.z);
object->SetAttribute("qx", piece.m_Rotation.x);
object->SetAttribute("qy", piece.m_Rotation.y);
object->SetAttribute("qz", piece.m_Rotation.z);
object->SetAttribute("qw", piece.m_Rotation.w);
object->SetAttribute("scale", piece.m_Scale);
for (const auto& effect : piece.m_Effects) {
tinyxml2::XMLElement* effectElement = doc.NewElement("Effect");
effectElement->SetAttribute("id", effect);
object->InsertEndChild(effectElement);
}
root->InsertEndChild(object);
}
doc.SaveFile(file.c_str());
}
size_t Prefab::Instantiate(NiPoint3 position, float scale) const {
if (m_Pieces.empty()) {
return 0;
}
const auto id = ObjectIDManager::GenerateRandomObjectID();
std::vector<LWOOBJID> entities;
for (const auto& [_, piece] : m_Pieces) {
EntityInfo info;
info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID();
info.lot = piece.m_Lot;
info.pos = (piece.m_Position * scale) + position;
info.rot = piece.m_Rotation;
info.scale = piece.m_Scale * scale;
const auto entity = Game::entityManager->CreateEntity(info);
for (const auto& effect : piece.m_Effects) {
auto* renderComponent = entity->GetComponent<RenderComponent>();
if (!renderComponent) {
continue;
}
// Generate random name
std::string effectName = "Effect_";
for (int i = 0; i < 10; ++i) {
effectName += std::to_string(GeneralUtils::GenerateRandomNumber<size_t>(0, 10));
}
renderComponent->PlayEffect(effect, u"create", effectName);
}
entities.push_back(entity->GetObjectID());
Game::entityManager->ConstructEntity(entity);
}
m_Instances.emplace(id, PrefabInstance { entities });
return id;
}
const std::vector<LWOOBJID>& Cinema::Prefab::GetEntities(size_t instanceID) {
return m_Instances[instanceID].m_Entities;
}
void Prefab::DestroyInstance(size_t id) {
const auto& instance = m_Instances[id];
for (const auto& entity : instance.m_Entities) {
Game::entityManager->DestroyEntity(entity);
}
m_Instances.erase(id);
}

105
dGame/dCinema/Prefab.h Normal file
View File

@@ -0,0 +1,105 @@
#pragma once
#include "dCommonVars.h"
#include "Entity.h"
#include <unordered_map>
#include <cstdint>
namespace Cinema
{
/**
* @brief A prefab is a collection of objects that can be placed in the world together.
*
* Can be saved and loaded from XML.
*/
class Prefab
{
public:
Prefab() = default;
~Prefab() = default;
/**
* @brief Adds an object to the prefab.
*
* @param lot The LOT of the object to add.
* @param position The position of the object to add.
* @param rotation The rotation of the object to add.
* @param scale The scale of the object to add.
*
* @return The ID of the object that was added.
*/
size_t AddObject(LOT lot, NiPoint3 position, NiQuaternion rotation, float scale = 1.0f);
/**
* @brief Adds an effect to the prefab.
*
* @param objectID The ID of the object to add the effect to.
* @param effectID The ID of the effect to add.
*/
void AddEffect(size_t objectID, int32_t effectID);
/**
* @brief Removes an object from the prefab.
*
* @param objectID The ID of the object to remove.
*/
void RemoveObject(size_t objectID);
/**
* @brief Loads a prefab from the given file.
*
* @param file The file to load the prefab from.
* @return The prefab that was loaded.
*/
static const Prefab& LoadFromFile(std::string file);
/**
* @brief Saves the prefab to the given file.
*
* @param file The file to save the prefab to.
*/
void SaveToFile(std::string file);
/**
* @brief Instantiates the prefab in the world.
*
* @param position The position to instantiate the prefab at.
* @param scale The scale to instantiate the prefab with.
*
* @return The ID of the instance that was created.
*/
size_t Instantiate(NiPoint3 position, float scale = 1.0f) const;
/**
* @brief Get the list of entities in the instance with the given ID.
*
* @param instanceID The ID of the instance to get the entities for.
*
* @return The list of entities in the instance.
*/
static const std::vector<LWOOBJID>& GetEntities(size_t instanceID);
/**
* @brief Destroys the instance with the given ID.
*
* @param instanceID The ID of the instance to destroy.
*/
static void DestroyInstance(size_t instanceID);
private:
struct Piece
{
LOT m_Lot;
NiPoint3 m_Position;
NiQuaternion m_Rotation;
float m_Scale;
std::vector<int32_t> m_Effects;
};
std::unordered_map<size_t, Piece> m_Pieces;
};
}

352
dGame/dCinema/README.md Normal file
View File

@@ -0,0 +1,352 @@
# Darkflame Cinema
A more emersive way to experience story in Darkflame Universe.
## What is Darkflame Cinema?
Darkflame Cinema is a way to create interactive — acted — experiences in Darkflame Universe. It aims to provide a complement to the ordinary mission structure with static NPCs and mission texts to convey story. Insperation is drawn from games likes of World of Warcraft, where NPCs frequently act out scenes, alone and in combination with both the player and other NPCs.
## Top-level design
Cinema works with a few concepts:
* **Actors** are the non player characters that act and perform actions.
* **Props** are objects in the world which complement the actors.
* **Scenes** are a collection of actors and props that perform an interactive play.
* **Plays** are scenes which are performed to a player.
## Play isolation
When a play is performed to a player, it is always isolated from the rest of the players in the world. This is to ensure that the player can experience the play without being disturbed by others, and others can't be disturbed by the play. This is achieved by ghosting all NPCs and props in the play to all players except the one experiencing the play.
<img src="media/isolation.png" width="800">
## How to create a scene
A play is created is a couple of steps:
1. Acts out the scene in the world as how the NPCs should reenact it.
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.
To start acting type: `/record-start (clear/copy)` in the chat. This will start recording your actions in the world. The actions recorded include:
* Movement
* Talking
* Equipping and unequipping items
* Explicit animations
By appending `clear` to the command, all gear on the NPC will be removed before reenactment. By appending `copy` to the command, all gear on the NPC will be copied from the player before reenactment. Which, if either, of these options to use depends on which NPC object will be acting. Usually you want to use `copy`. This can be edited manually later.
This acting is the core of the scene. Later we will learn how to add some interactivity to the scene.
When you are done acting, type `/record-stop` in the chat. This will stop recording your actions in the world.
Save your amazing performance by typing `/record-save <file.xml>` in the chat. **Do not forget to do this before starting your next recording!** This will save your performance to a file relative to where the server is running.
If your scene has multiple NPCs acting, now is the time to switch to the next NPC and repeat the steps above.
#### 1.1. Previewing your performances
See <a href="./media/preview.mp4">media/preview.mp4</a> for an example of how to preview a performance.
To show a preview of the performance you can type `/record-act (lot) (name)`, this will play the performance. If a lot is specified, the actor will be of that object. If a name is specified, the actor will have that name. If neither is specified, the actor will be a predetermined NPC.
You may load a performance by typing `/record-load <file.xml>` in the chat. This will load a performance from a file relative to where the server is running. You may than type `/record-act` to show a preview of the performance.
### 2. Create prefabs of props in the scene
Prefabs are a collection of objects that can be instantiated together. This is currently done exclusively by writing xml files.
Here is an example of a prefab:
```xml
<Prefab>
<!--
Lot is the id of the object to spawn.
Position is defined in either coordinates (x,y,z).
These are relative to the position of the prefab.
Rotation is defined in either euler angles (rx,ry,rz);
or in a quaternion (qx,qy,qz,qw).
Use /pos and /rot in the chat to get your current position and rotation.
-->
<Object lot="359" x="0" y="0" z="0" rx="0" ry="0" rz="0"/>
<Object lot="53" x="0" y="5" z="0" rx="0" rz="0">
<Effect id="60073"/> <!-- Custom effect -->
</Object>
<Object lot="359" x="0" y="5" z="0" rx="0" ry="0" rz="0"/>
<Object lot="359" x="0" y="10" z="0" rx="0" ry="0" rz="0"/>
<Object lot="53" x="0" y="0" z="0" rx="0" rz="0"/>
<Object lot="53" x="0" y="10" z="0" rx="0" rz="0"/>
<Object lot="53" x="0" y="15" z="0" rx="0" rz="0"/>
</Prefab>
```
This creates the following prefab:
<img src="media/image.png" width="800">
You can spawn a prefab by typing `/prefab-spawn <file.xml>`. This will spawn the prefab at your current position.
### 3. Put the NPCs and props in a scene file
A scene file is a collection of actors and props that perform an interactive play. This is currently done exclusively by writing xml files.
Here is an example of a scene:
```xml
<!--
The scene is defined by a collection of actors and props.
These are defined by their name and prefab file.
-->
<Scene>
<!--
Props are defined by their file name and a position.
See media/SFlagSmall.xml for this file.
-->
<Prefab file="vanity/prefabs/SFlagSmall.xml" x="-655.564" y="1123.76" z="75.993"/>
<!--
Actors are defined by their name and the file which holds the performance.
See media/my-act.xml and media/my-act-2.xml for these files.
-->
<NPC name="John" act="vanity/acts/my-act.xml"/>
<NPC name="Bill" act="vanity/acts/my-act-2.xml"/>
</Scene>
```
See <a href="./media/scene.mp4">media/scene.mp4</a> for how the above scene looks in the world.
You preview a scene by typing `/scene-act <file.xml>` in the chat. This will act out the scene for you.
### 4. Edit the performed acts
When you preview a newly created scene the NPCs will act out exactly what you did when you recorded the performance. This is not always what you want. You may want to add some interactivity to the scene. This is done by editing the performed acts.
An act is made out of a collection of records which will be evaluated in order. A record either performs some visual action or provides for flow control.
Here is an example of a slightly modified act which adds some interactivity:
```xml
<!--
See media/my-act-mod.xml for this file.
-->
<Recorder>
<!-- The following three records were added automaticly because I appended 'copy' to the command when I acted, but these may be modified freely here. -->
<ClearEquippedRecord t="0"/>
<EquipRecord item="9856" t="0"/>
<EquipRecord item="6500" t="0"/>
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.39199999"/>
...
<!--
The following records will assure that before the act continues, the player is within 10 units of the NPC.
If the player is not within 10 units of them within 5 seconds, the NPC will ask the player to come closer. The NPC will continue to ask the player to come closer every 5 seconds until the player is within 10 units of them.
-->
<!--
This is flow-control, the act will jump to the record with the name "check-player". This makes sure that if the player is already close enough, the NPC does not tell them to come closer.
-->
<JumpRecord label="check-player" t="0" />
<!--
This is a manually added speak record, which will have the NPC speak, telling the player to come closer.
-->
<SpeakRecord name="join-me" text="Join me!" t="3.0" />
<!--
This is a player proximity record, it can branch two different ways. If the player comes within 10 units of the NPC within 5 seconds, the act will continue to the record. If however, the player is not within 10 units within 5 seconds, the act will jump to the record named "join-me", the record above this one.
-->
<PlayerProximityRecord name="check-player" distance="10" timeout="5" timeoutLabel="join-me" t="0" />
<SpeakRecord text="Welcome!" t="4.026"/>
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="2.046"/>
...
<SpeakRecord text="Great!&quot;" t="2.3759999"/>
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="3.6960001"/>
</Recorder>
```
See <a href="./media/interactive.mp4">media/interactive.mp4</a> for how the above scene looks in the world.
The list of available records is extensive.
#### 4.1. Records
Each record specifies a time `t`. This is how long after this record is evaluated that the next record will be evaluated.
A record may also specify a `name`. This is used for flow control. If a record specifies a name, it may be jumped to by for example `JumpRecord` or `ForkRecord`.
#### 4.1. MovementRecord
This record sets the properties of the actors controllable physics component. This is the only record which isn't meant to be edited manually.
#### 4.2. SpeakRecord
This record makes the actor speak.
```xml
<SpeakRecord text="Hello!" t="3.0" />
```
#### 4.3. EquipRecord
This record makes the actor equip an item.
```xml
<EquipRecord item="9856" t="0"/>
```
#### 4.4. UnequipRecord
This record makes the actor unequip an item.
```xml
<UnequipRecord item="9856" t="0"/>
```
#### 4.5. ClearEquippedRecord
This record makes the actor unequip all items.
```xml
<ClearEquippedRecord t="0"/>
```
#### 4.6. AnimationRecord
This record makes the actor play an animation.
```xml
<AnimationRecord animation="salute" t="2.0"/>
```
#### 4.7. WaitRecord
This record makes the actor wait for a specified amount of time.
```xml
<WaitRecord t="5.5"/>
```
#### 4.8. JumpRecord
This record makes the act jump to a record with a specified name.
```xml
<JumpRecord label="check-player" t="0" />
```
#### 4.9. ForkRecord
This record makes a decision based on a condition, and jumps accordingly. The condition may either be a scene variable or a precondtion.
```xml
<!-- Jump to 'jump-true' if my-variable=1, else jump to 'jump-false'. -->
<ForkRecord variable="my-variable" value="1" success="jump-true" failure="jump-false" t="0" />
<!-- Jump to 'jump-true' if the given precondition holds for the player, else jump to 'jump-false'. -->
<ForkRecord precondition="42,99" success="jump-true" failure="jump-false" t="0" />
```
#### 4.10. PlayerProximityRecord
This record checks if the player is within a specified distance of the actor. If the player is within the distance, the act will continue to the next record. If the player is not within the distance, the act will jump to a record with a specified name.
```xml
<!-- Specifying the timeout is optional. If it is not specified, the act will wait forever for the player to come within the distance. -->
<PlayerProximityRecord name="check-player" distance="10" timeout="5" timeoutLabel="join-me" t="0" />
```
#### 4.11. SetVariableRecord
This record sets a scene variable to a specified value. These are shared between all actors in the scene.
```xml
<SetVariableRecord variable="my-variable" value="1" t="0" />
```
#### 4.12. BarrierRecord
This record makes the act wait until it gets a signal to continue from another act, or until an optional timeout is reached. This is useful for synchronizing multiple actors, for example in a conversation.
```xml
<!-- Specifying the timeout is optional. If it is not specified, the act will wait forever for the signal. -->
<BarrierRecord signal="my-signal" timeout="5" timeoutLabel="do-something-else" t="0" />
```
#### 4.13. SignalRecord
This record sends a signal to all acts waiting for it.
```xml
<SignalRecord signal="my-signal" t="0" />
```
#### 4.14. ConcludeRecord
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.

1507
dGame/dCinema/Recorder.cpp Normal file

File diff suppressed because it is too large Load Diff

473
dGame/dCinema/Recorder.h Normal file
View File

@@ -0,0 +1,473 @@
#pragma once
#include "Game.h"
#include "EntityManager.h"
#include "tinyxml2.h"
#include "Play.h"
#include <chrono>
namespace Cinema::Recording
{
class Record
{
public:
virtual void Act(Entity* actor) = 0;
virtual void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) = 0;
virtual void Deserialize(tinyxml2::XMLElement* element) = 0;
float m_Delay;
std::string m_Name;
};
class Recorder
{
public:
Recorder();
~Recorder();
void AddRecord(Record* record);
void Act(Entity* actor, Play* variables = nullptr);
Entity* ActFor(Entity* actorTemplate, Entity* player, Play* variables = nullptr);
void StopActingFor(Entity* actor, Entity* actorTemplate, LWOOBJID playerID);
bool IsRecording() const;
void SaveToFile(const std::string& filename);
float GetDuration() const;
static Recorder* LoadFromFile(const std::string& filename);
static void AddRecording(LWOOBJID actorID, Recorder* recorder);
static void StartRecording(LWOOBJID actorID);
static void StopRecording(LWOOBJID actorID);
static Recorder* GetRecorder(LWOOBJID actorID);
static void RegisterEffectForActor(LWOOBJID actorID, const int32_t& effectId);
static void LoadRecords(tinyxml2::XMLElement* root, std::vector<Record*>& records);
static void ActingDispatch(Entity* actor, const std::vector<Record*>& records, size_t index, Play* variables);
private:
static void PlayerProximityDispatch(Entity* actor, const std::vector<Record*>& records, size_t index, Play* variables, std::shared_ptr<bool> actionTaken);
static void PathFindDispatch(Entity* actor, const std::vector<Record*>& records, size_t index, Play* variables);
std::vector<Record*> m_Records;
bool m_IsRecording;
std::chrono::milliseconds m_StartTime;
std::chrono::milliseconds m_LastRecordTime;
};
class MovementRecord : public Record
{
public:
NiPoint3 position;
NiQuaternion rotation;
NiPoint3 velocity;
NiPoint3 angularVelocity;
bool onGround;
bool dirtyVelocity;
bool dirtyAngularVelocity;
MovementRecord() = default;
MovementRecord(
const NiPoint3& position,
const NiQuaternion& rotation,
const NiPoint3& velocity,
const NiPoint3& angularVelocity,
bool onGround, bool dirtyVelocity, bool dirtyAngularVelocity
);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class SpeakRecord : public Record
{
public:
std::string text;
SpeakRecord() = default;
SpeakRecord(const std::string& text);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class AnimationRecord : public Record
{
public:
std::string animation;
AnimationRecord() = default;
AnimationRecord(const std::string& animation);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class EquipRecord : public Record
{
public:
LOT item = LOT_NULL;
EquipRecord() = default;
EquipRecord(LOT item);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class UnequipRecord : public Record
{
public:
LOT item = LOT_NULL;
UnequipRecord() = default;
UnequipRecord(LOT item);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class ClearEquippedRecord : public Record
{
public:
ClearEquippedRecord() = default;
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class WaitRecord : public Record
{
public:
WaitRecord() = default;
WaitRecord(float delay);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class ForkRecord : public Record
{
public:
std::string variable;
std::string value;
std::string precondition;
std::string success;
std::string failure;
ForkRecord() = default;
ForkRecord(const std::string& variable, const std::string& value, const std::string& success, const std::string& failure);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class JumpRecord : public Record
{
public:
std::string label;
JumpRecord() = default;
JumpRecord(const std::string& label);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class SetVariableRecord : public Record
{
public:
std::string variable;
std::string value;
SetVariableRecord() = default;
SetVariableRecord(const std::string& variable, const std::string& value);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class BarrierRecord : public Record
{
public:
std::string signal;
float timeout = 0.0f;
std::string timeoutLabel;
BarrierRecord() = default;
BarrierRecord(const std::string& signal, float timeout, const std::string& timeoutLabel);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class SignalRecord : public Record
{
public:
std::string signal;
SignalRecord() = default;
SignalRecord(const std::string& signal);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class ConcludeRecord : public Record
{
public:
bool cleanUp = false;
ConcludeRecord() = default;
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class PlayerProximityRecord : public Record
{
public:
float distance = 0.0f;
float timeout = 0.0f;
std::string timeoutLabel;
PlayerProximityRecord() = default;
PlayerProximityRecord(float distance, float timeout, const std::string& timeoutLabel);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
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 CoroutineRecord : public Record
{
public:
std::vector<Record*> records;
CoroutineRecord() = default;
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class PathFindRecord : public Record
{
public:
NiPoint3 position;
float speed = 1.0f;
PathFindRecord() = default;
PathFindRecord(const NiPoint3& position, float speed);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class CombatAIRecord : public Record
{
public:
bool enabled = false;
CombatAIRecord() = default;
CombatAIRecord(bool enabled);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class CompanionRecord : public Record
{
public:
std::vector<Record*> records;
CompanionRecord() = default;
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
static void SetCompanion(Entity* actor, LWOOBJID player);
};
class SpawnRecord : public Record
{
public:
LOT lot = LOT_NULL;
NiPoint3 position;
NiQuaternion rotation;
std::vector<Record*> onSpawnRecords;
std::vector<Record*> onDespawnRecords;
SpawnRecord() = default;
SpawnRecord(LOT lot, const NiPoint3& position, const NiQuaternion& rotation);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class MissionRecord : public Record
{
public:
int32_t mission;
MissionRecord() = default;
MissionRecord(const int32_t& mission);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
class CinematicRecord : public Record
{
public:
std::string cinematic;
CinematicRecord() = default;
CinematicRecord(const std::string& cinematic);
void Act(Entity* actor) override;
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
void Deserialize(tinyxml2::XMLElement* element) override;
};
}

742
dGame/dCinema/Scene.cpp Normal file
View File

@@ -0,0 +1,742 @@
#include "Scene.h"
#include <filesystem>
#include <tinyxml2.h>
#include "ServerPreconditions.h"
#include "EntityManager.h"
#include "EntityInfo.h"
#include "MissionComponent.h"
#include "dConfig.h"
#include "PlayerManager.h"
#include "SlashCommandHandler.h"
#include "ChatPackets.h"
#include "InventoryComponent.h"
#include "MovementAIComponent.h"
#include "Recorder.h"
#include "GhostComponent.h"
using namespace Cinema;
std::unordered_map<std::string, Scene> Scene::m_Scenes;
void Cinema::Scene::AddObject(LOT lot, NiPoint3 position, NiQuaternion rotation) {
m_Objects.push_back(std::make_pair(lot, std::make_pair(position, rotation)));
}
void Scene::AddPrefab(const Prefab& prefab, NiPoint3 position) {
m_Prefabs.push_back(std::make_pair(prefab, position));
}
void Cinema::Scene::AddNPC(LOT npc, const std::string& name, Recording::Recorder* act) {
m_NPCs.push_back(std::make_pair(npc, std::make_pair(act, name)));
}
void Cinema::Scene::Rehearse() {
CheckForShowings();
}
void Cinema::Scene::Conclude(Entity* player) {
if (player == nullptr) {
return;
}
if (m_Audience.find(player->GetObjectID()) == m_Audience.end()) {
return;
}
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent == nullptr) {
return;
}
if (m_CompleteMission != 0) {
missionComponent->CompleteMission(m_CompleteMission);
}
if (m_AcceptMission != 0) {
missionComponent->AcceptMission(m_AcceptMission);
}
// Remove the player from the audience
m_Audience.erase(player->GetObjectID());
m_HasBeenOutside.erase(player->GetObjectID());
m_VisitedPlayers.emplace(player->GetObjectID());
}
bool Cinema::Scene::IsPlayerInBounds(Entity* player) const {
if (player == nullptr) {
return false;
}
if (m_Bounds == 0.0f) {
return true;
}
const auto& position = player->GetPosition();
auto distance = NiPoint3::Distance(position, m_Center);
return distance <= m_Bounds;
}
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;
}
bool Cinema::Scene::IsPlayerInMaximumShowingDistance(Entity* player) const {
if (player == nullptr) {
return false;
}
if (m_MaximumShowingDistance == 0.0f) {
return true;
}
const auto& position = player->GetPosition();
auto distance = NiPoint3::Distance(position, m_Center);
return distance <= m_MaximumShowingDistance;
}
void Cinema::Scene::AutoLoadScenesForZone(LWOMAPID zone) {
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "record-act" },
.handle = CommandRecordAct,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "record-start" },
.handle = CommandRecordStart,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "record-stop" },
.handle = CommandRecordStop,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "record-save" },
.handle = CommandRecordSave,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "record-load" },
.handle = CommandRecordLoad,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "prefab-spawn" },
.handle = CommandPrefabSpawn,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "prefab-destroy" },
.handle = CommandPrefabDestroy,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "scene-act" },
.handle = CommandSceneAct,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "scene-setup" },
.handle = CommandSceneSetup,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "companion" },
.handle = CommandCompanion,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "cinematic" },
.handle = CommandCinematic,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
SlashCommandHandler::RegisterCommand(Command{
.help = "",
.info = "",
.aliases = { "ghost" },
.handle = CommandGhostReference,
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
});
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);
if (scene.m_ChanceToPlay != 1.0f) {
const auto chance = GeneralUtils::GenerateRandomNumber<float>(0.0f, 1.0f);
if (chance > scene.m_ChanceToPlay) {
continue;
}
}
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) {
m_Audience.erase(member);
}
}
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 : PlayerManager::GetAllPlayers()) {
if (m_Audience.find(player->GetObjectID()) != m_Audience.end()) {
continue;
}
if (!m_Repeatable && m_VisitedPlayers.find(player->GetObjectID()) != m_VisitedPlayers.end()) {
continue;
}
CheckTicket(player);
}
CheckForShowings();
});
}
void Cinema::Scene::CheckTicket(Entity* player) {
if (m_Audience.find(player->GetObjectID()) != m_Audience.end()) {
return;
}
for (const auto& [expression, invert] : m_Preconditions) {
if (expression.Check(player) == invert) {
return;
}
}
if (m_ShowingDistance != 0.0f) {
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);
}
Play* Cinema::Scene::Act(Entity* player) {
auto* play = new Play();
if (player != nullptr) {
play->player = player->GetObjectID();
}
play->scene = this;
for (const auto& [lot, transform] : m_Objects) {
const auto& [position, rotation] = transform;
EntityInfo info;
info.pos = position;
info.rot = rotation;
info.lot = lot;
info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID();
auto* entity = Game::entityManager->CreateEntity(info);
Game::entityManager->ConstructEntity(entity);
if (player != nullptr) {
ServerPreconditions::AddSoloActor(entity->GetObjectID(), player->GetObjectID());
}
LOG("Spawing object %d", entity->GetObjectID());
}
for (const auto& [prefab, position] : m_Prefabs) {
const auto instanceId = prefab.Instantiate(position);
const auto& entities = prefab.GetEntities(instanceId);
if (player != nullptr) {
for (const auto& entity : entities) {
ServerPreconditions::AddSoloActor(entity, player->GetObjectID());
}
}
for (const auto& entity : entities) {
play->entities.emplace(entity);
}
LOG("Spawing prefab %d", instanceId);
}
for (const auto& [npc, meta] : m_NPCs) {
const auto& [act, name] = meta;
EntityInfo info;
info.pos = NiPoint3();
info.rot = NiQuaternion();
info.lot = npc;
info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID();
info.settings = {
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
};
auto* entity = Game::entityManager->CreateEntity(info);
play->entities.emplace(entity->GetObjectID());
Game::entityManager->ConstructEntity(entity);
Entity* actor;
if (player != nullptr) {
actor = act->ActFor(entity, player, play);
}
else
{
act->Act(entity, play);
actor = entity;
}
if (actor != nullptr) {
actor->SetVar(u"npcName", name);
}
play->entities.emplace(actor->GetObjectID());
LOG("Spawing npc %d", entity->GetObjectID());
}
if (player != nullptr) {
play->SetupCheckForAudience();
}
return play;
}
Scene& Cinema::Scene::LoadFromFile(std::string file) {
if (m_Scenes.find(file) != m_Scenes.end()) {
return m_Scenes[file];
}
Scene scene;
tinyxml2::XMLDocument doc;
doc.LoadFile(file.c_str());
tinyxml2::XMLElement* root = doc.FirstChildElement("Scene");
if (!root) {
LOG("Failed to load scene from file: %s", file.c_str());
m_Scenes.emplace(file, scene);
return m_Scenes[file];
}
// Load center and bounds
if (root->Attribute("x")) {
scene.m_Center = NiPoint3(root->FloatAttribute("x"), root->FloatAttribute("y"), root->FloatAttribute("z"));
}
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;
}
if (root->Attribute("maximumShowingDistance")) {
scene.m_MaximumShowingDistance = root->FloatAttribute("maximumShowingDistance");
} else {
scene.m_MaximumShowingDistance = scene.m_ShowingDistance * 2.0f;
}
if (root->Attribute("chanceToPlay")) {
scene.m_ChanceToPlay = root->FloatAttribute("chanceToPlay");
}
if (root->Attribute("repeatable")) {
scene.m_Repeatable = root->BoolAttribute("repeatable");
}
// Load accept and complete mission
if (root->Attribute("acceptMission")) {
scene.m_AcceptMission = root->IntAttribute("acceptMission");
}
if (root->Attribute("completeMission")) {
scene.m_CompleteMission = root->IntAttribute("completeMission");
}
// Load preconditions
for (tinyxml2::XMLElement* element = root->FirstChildElement("Precondition"); element; element = element->NextSiblingElement("Precondition")) {
scene.m_Preconditions.push_back(std::make_pair(Preconditions::CreateExpression(element->Attribute("expression")), element->BoolAttribute("not")));
}
for (tinyxml2::XMLElement* element = root->FirstChildElement("Object"); element; element = element->NextSiblingElement("Object")) {
const auto lot = element->UnsignedAttribute("lot");
const auto position = NiPoint3(element->FloatAttribute("x"), element->FloatAttribute("y"), element->FloatAttribute("z"));
const auto rotation = NiQuaternion(element->FloatAttribute("qx"), element->FloatAttribute("qy"), element->FloatAttribute("qz"), element->FloatAttribute("qw"));
scene.AddObject(lot, position, rotation);
}
for (tinyxml2::XMLElement* element = root->FirstChildElement("Prefab"); element; element = element->NextSiblingElement("Prefab")) {
const auto prefab = element->Attribute("file");
const auto position = NiPoint3(element->FloatAttribute("x"), element->FloatAttribute("y"), element->FloatAttribute("z"));
scene.AddPrefab(Prefab::LoadFromFile(prefab), position);
}
for (tinyxml2::XMLElement* element = root->FirstChildElement("NPC"); element; element = element->NextSiblingElement("NPC")) {
LOT npc = 2097253;
if (element->Attribute("lot")) {
npc = element->UnsignedAttribute("lot");
}
std::string name = "";
if (element->Attribute("name")) {
name = element->Attribute("name");
}
const auto act = element->Attribute("act");
scene.AddNPC(npc, name, Recording::Recorder::LoadFromFile(act));
}
LOG("Loaded scene from file: %s", file.c_str());
m_Scenes.emplace(file, scene);
return m_Scenes[file];
}
void Cinema::Scene::CommandRecordAct(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
EntityInfo info;
info.lot = 0;
info.pos = entity->GetPosition();
info.rot = entity->GetRotation();
info.scale = 1;
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
info.settings = {
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
};
// If there is an argument, set the lot
const auto lotOptional = GeneralUtils::TryParse<LOT>(splitArgs[0]);
if (lotOptional) {
info.lot = lotOptional.value();
} else {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot.");
return;
}
// Spawn it
auto* actor = Game::entityManager->CreateEntity(info);
// If there is an argument, set the actors name
if (args.size() > 1) {
actor->SetVar(u"npcName", args[1]);
}
// Construct it
Game::entityManager->ConstructEntity(actor);
auto* record = Cinema::Recording::Recorder::GetRecorder(entity->GetObjectID());
if (record) {
record->Act(actor);
} else {
LOG("Failed to get recorder for objectID: %llu", entity->GetObjectID());
}
}
void Cinema::Scene::CommandRecordStart(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
auto splitArgs = GeneralUtils::SplitString(args, ' ');
Cinema::Recording::Recorder::StartRecording(entity->GetObjectID());
auto* record = Cinema::Recording::Recorder::GetRecorder(entity->GetObjectID());
if (record) {
if (args.size() > 0 && splitArgs[0] == "clear") {
record->AddRecord(new Cinema::Recording::ClearEquippedRecord());
}
else if (args.size() > 0 && splitArgs[0] == "copy") {
record->AddRecord(new Cinema::Recording::ClearEquippedRecord());
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent) {
for (const auto& [k, v] : inventoryComponent->GetEquippedItems()) {
auto* equipmentRecord = new Cinema::Recording::EquipRecord();
equipmentRecord->item = v.lot;
record->AddRecord(equipmentRecord);
}
}
}
} else {
LOG("Failed to get recorder for objectID: %llu", entity->GetObjectID());
}
}
void Cinema::Scene::CommandRecordStop(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
Cinema::Recording::Recorder::StopRecording(entity->GetObjectID());
}
void Cinema::Scene::CommandRecordSave(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
auto* record = Cinema::Recording::Recorder::GetRecorder(entity->GetObjectID());
if (record) {
record->SaveToFile(splitArgs[0]);
} else {
LOG("Failed to get recorder for objectID: %llu", entity->GetObjectID());
}
}
void Cinema::Scene::CommandRecordLoad(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
auto* record = Cinema::Recording::Recorder::LoadFromFile(splitArgs[0]);
if (record) {
Cinema::Recording::Recorder::AddRecording(entity->GetObjectID(), record);
} else {
LOG("Failed to load recording from file: %s", splitArgs[0].c_str());
}
}
void Cinema::Scene::CommandPrefabSpawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
const auto& prefab = Cinema::Prefab::LoadFromFile(splitArgs[0]);
float scale = 1.0f;
if (args.size() >= 2) {
const auto scaleOptional = GeneralUtils::TryParse<float>(splitArgs[1]);
if (scaleOptional) {
scale = scaleOptional.value();
}
}
size_t id = prefab.Instantiate(entity->GetPosition(), scale);
ChatPackets::SendSystemMessage(sysAddr, u"Spawned prefab with ID: " + GeneralUtils::to_u16string(id));
}
void Cinema::Scene::CommandPrefabDestroy(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
size_t id = GeneralUtils::TryParse<size_t>(splitArgs[0]).value_or(0);
if (id == 0) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid prefab ID.");
return;
}
Cinema::Prefab::DestroyInstance(id);
ChatPackets::SendSystemMessage(sysAddr, u"Destroyed prefab with ID: " + GeneralUtils::to_u16string(id));
return;
}
void Cinema::Scene::CommandSceneAct(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
auto& scene = Cinema::Scene::LoadFromFile(splitArgs[0]);
scene.Act(entity);
}
void Cinema::Scene::CommandSceneSetup(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
auto& scene = Cinema::Scene::LoadFromFile(splitArgs[0]);
scene.Rehearse();
}
void Cinema::Scene::CommandCompanion(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
EntityInfo info;
info.lot = 0;
info.pos = entity->GetPosition();
info.rot = entity->GetRotation();
info.scale = 1;
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
info.settings = {
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
};
// If there is an argument, set the lot
const auto lotOptional = GeneralUtils::TryParse<LOT>(splitArgs[0]);
if (lotOptional) {
info.lot = lotOptional.value();
} else {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot.");
return;
}
// Spawn it
auto* actor = Game::entityManager->CreateEntity(info);
// If there is an argument, set the actors name
if (args.size() > 1) {
actor->SetVar(u"npcName", args[1]);
}
// Construct it
Game::entityManager->ConstructEntity(actor);
const auto follow = entity->GetObjectID();
actor->AddCallbackTimer(1.0f, [actor, follow]() {
Cinema::Recording::CompanionRecord::SetCompanion(actor, follow);
});
}
void Cinema::Scene::CommandCinematic(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
const auto path = splitArgs[0];
GameMessages::SendPlayCinematic(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(path), sysAddr);
}
void Cinema::Scene::CommandGhostReference(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
auto* ghostComponent = entity->GetComponent<GhostComponent>();
if (ghostComponent == nullptr) {
ChatPackets::SendSystemMessage(sysAddr, u"Entity does not have a ghost component.");
return;
}
const auto& ref = ghostComponent->GetGhostReferencePoint();
std::stringstream ss;
ss << "<" << ref.x << ", " << ref.y << ", " << ref.z << ">";
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(ss.str()));
}

141
dGame/dCinema/Scene.h Normal file
View File

@@ -0,0 +1,141 @@
#pragma once
#include "Prefab.h"
#include "Recorder.h"
#include "Preconditions.h"
namespace Cinema
{
/**
* @brief A scene is a collection of prefabs, npcs and behaviors that can be loaded into the world.
*/
class Scene
{
public:
Scene() = default;
/**
* @brief Adds an object to the scene.
*
* @param lot The LOT of the object to add.
* @param position The position of the object to add.
* @param rotation The rotation of the object to add.
*/
void AddObject(LOT lot, NiPoint3 position, NiQuaternion rotation);
/**
* @brief Adds a prefab to the scene.
*
* @param prefab The prefab to add.
* @param position The position to add the prefab at.
*/
void AddPrefab(const Prefab& prefab, NiPoint3 position);
/**
* @brief Adds an NPC to the scene.
*
* @param npc The NPC to add.
* @param act The act to set for the NPC.
*/
void AddNPC(LOT npc, const std::string& name, Recording::Recorder* act);
/**
* @brief Set up the scene to be acted when a player enters the theater and meets the preconditions.
*/
void Rehearse();
/**
* @brief Conclude the scene for a given player.
*
* @param player The player to conclude the scene for (not nullptr).
*/
void Conclude(Entity* player);
/**
* @brief Checks if a given player is within the bounds of the scene.
*
* @param player The player to check.
*/
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 Checks if a given player is within the maximum showing distance of the scene.
*
* @param player The player to check.
*/
bool IsPlayerInMaximumShowingDistance(Entity* player) const;
/**
* @brief Act the scene.
*
* @param player The player to act the scene for (or nullptr to act for all players).
* @return The variables that were set by the scene.
*/
Play* Act(Entity* player);
/**
* @brief Loads a scene from the given file.
*
* @param file The file to load the scene from.
* @return The scene that was loaded.
*/
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();
void CheckTicket(Entity* player);
std::vector<std::pair<LOT, std::pair<NiPoint3, NiQuaternion>>> m_Objects;
std::vector<std::pair<Prefab, NiPoint3>> m_Prefabs;
std::vector<std::pair<LOT, std::pair<Recording::Recorder*, std::string>>> m_NPCs;
NiPoint3 m_Center;
float m_Bounds = 0.0f;
float m_ShowingDistance = 0.0f;
float m_MaximumShowingDistance = 0.0f;
float m_ChanceToPlay = 1.0f;
bool m_Repeatable = true;
std::vector<std::pair<PreconditionExpression, bool>> m_Preconditions;
int32_t m_AcceptMission = 0;
int32_t m_CompleteMission = 0;
std::unordered_set<LWOOBJID> m_Audience;
std::unordered_set<LWOOBJID> m_HasBeenOutside;
std::unordered_set<LWOOBJID> m_VisitedPlayers;
static std::unordered_map<std::string, Scene> m_Scenes;
static void CommandRecordAct(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandRecordStart(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandRecordStop(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandRecordSave(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandRecordLoad(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandPrefabSpawn(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandPrefabDestroy(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandSceneAct(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandSceneSetup(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandCompanion(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandCinematic(Entity* entity, const SystemAddress& sysAddr, const std::string args);
static void CommandGhostReference(Entity* entity, const SystemAddress& sysAddr, const std::string args);
};
} // namespace Cinema

View File

@@ -0,0 +1,11 @@
<Prefab>
<Object lot="359" x="0" y="0" z="0" rx="0" ry="0" rz="0"/>
<Object lot="53" x="0" y="5" z="0" rx="0" rz="0">
<Effect id="60073"/>
</Object>
<Object lot="359" x="0" y="5" z="0" rx="0" ry="0" rz="0"/>
<Object lot="359" x="0" y="10" z="0" rx="0" ry="0" rz="0"/>
<Object lot="53" x="0" y="0" z="0" rx="0" rz="0"/>
<Object lot="53" x="0" y="10" z="0" rx="0" rz="0"/>
<Object lot="53" x="0" y="15" z="0" rx="0" rz="0"/>
</Prefab>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -0,0 +1,90 @@
<Recorder>
<MovementRecord x="-666.4151" y="1123.7156" z="-5.6110449" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.028999999"/>
<MovementRecord x="-666.3476" y="1123.7156" z="-5.2612491" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.3501134" vy="0.0033733833" vz="6.9959145" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.89099997"/>
<MovementRecord x="-666.01447" y="1123.717" z="-3.5337467" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-665.68048" y="1123.7185" z="-1.8018351" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-665.3465" y="1123.72" z="-0.069923788" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-664.98468" y="1123.7216" z="1.8063134" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-664.6507" y="1123.723" z="3.5382249" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-664.28888" y="1123.7246" z="5.4144602" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-663.92706" y="1123.7262" z="7.2906947" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-663.62091" y="1123.7275" z="8.8782806" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
<MovementRecord x="-663.25909" y="1123.7291" z="10.754521" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-662.92511" y="1123.7306" z="12.486436" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-662.56329" y="1123.7322" z="14.362677" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-662.20148" y="1123.7338" z="16.238916" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-661.86749" y="1123.7352" z="17.970819" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-661.53351" y="1123.7367" z="19.702723" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-661.19952" y="1123.7382" z="21.434626" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-660.86554" y="1123.7396" z="23.166529" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-660.50372" y="1123.7412" z="25.042757" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-660.16974" y="1123.7427" z="26.77466" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-659.80792" y="1123.7443" z="28.650888" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.004175582" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-659.50177" y="1123.7456" z="30.238466" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-659.16779" y="1123.7471" z="31.970369" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-658.80597" y="1123.7487" z="33.8466" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-658.44415" y="1123.7502" z="35.722828" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-658.11017" y="1123.7517" z="37.454731" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-657.75244" y="1123.7533" z="39.331692" qx="0" qy="0.081039831" qz="0" qw="0.99671084" vx="1.4247334" vy="0.0041755605" vz="8.703496" avx="0" avy="-1.7030833" avz="0" g="true" dv="true" dav="true" t="0.198"/>
<MovementRecord x="-657.45453" y="1123.7382" z="41.2188" qx="0" qy="0.078293413" qz="0" qw="0.99693036" vx="1.3767529" vy="0.0041751657" vz="8.711215" avx="0" avy="0.020716017" avz="0" g="true" dv="true" dav="true" t="0.198"/>
<MovementRecord x="-657.17914" y="1123.7396" z="42.961048" qx="0" qy="0.078293413" qz="0" qw="0.99693036" vx="1.3767529" vy="0.0041751661" vz="8.711215" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-656.9267" y="1123.741" z="44.558109" qx="0" qy="0.078293413" qz="0" qw="0.99693036" vx="1.3767529" vy="0.0041751661" vz="8.711215" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-656.63043" y="1123.7426" z="46.445869" qx="0" qy="0.075789861" qz="0" qw="0.99712384" vx="1.3329875" vy="0.0041746967" vz="8.7180195" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-656.36383" y="1123.744" z="48.189491" qx="0" qy="0.075789861" qz="0" qw="0.99712384" vx="1.3329875" vy="0.0041746967" vz="8.7180195" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-656.10962" y="1123.7456" z="50.083252" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="1.0742536" vy="0.0041697873" vz="8.7536678" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-655.89502" y="1123.7471" z="51.833969" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="1.0742536" vy="0.0041697873" vz="8.7536678" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-655.66541" y="1123.7485" z="53.707008" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="0.90067899" vy="0.0034960457" vz="7.3392773" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-655.63428" y="1123.7485" z="53.960564" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="0.20638135" vy="0.0008010827" vz="1.6817197" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-655.63373" y="1123.7485" z="53.965019" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.198"/>
<SpeakRecord text="Hi!" t="1.584"/>
<MovementRecord x="-655.63373" y="1123.7485" z="53.965019" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="4.5869999"/>
<AnimationRecord animation="salute" t="4.0939999"/>
<MovementRecord x="-655.63373" y="1123.7485" z="53.965019" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="1.978"/>
<MovementRecord x="-655.63373" y="1123.7485" z="53.965019" qx="0" qy="0.0056772209" qz="0" qw="0.99998391" vx="0" vy="0" vz="0" avx="0" avy="-1.02" avz="0" g="true" dv="false" dav="true" t="1.914"/>
<MovementRecord x="-655.91522" y="1123.7493" z="55.306641" qx="0" qy="-0.16600978" qz="0" qw="0.9861241" vx="-2.8875616" vy="0.0036441826" vz="8.3332291" avx="0" avy="-2.2004657" avz="0" g="true" dv="true" dav="true" t="0.198"/>
<MovementRecord x="-656.8963" y="1123.7493" z="56.298153" qx="0" qy="-0.60278374" qz="0" qw="0.79790461" vx="-8.4835682" vy="0.00041772827" vz="2.4103563" avx="0" avy="-5.2572789" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
<MovementRecord x="-658.90973" y="1123.7493" z="56.085861" qx="0" qy="-0.81736016" qz="0" qw="0.57612699" vx="-8.3061113" vy="-0.0020730058" vz="-2.964668" avx="0" avy="-0.59541434" avz="0" g="true" dv="true" dav="true" t="0.198"/>
<MovementRecord x="-660.45416" y="1123.7493" z="55.155827" qx="0" qy="0.90611506" qz="0" qw="-0.42303139" vx="-6.7611709" vy="-0.0032021664" vz="-5.6627994" avx="0" avy="-1.0992103" avz="0" g="true" dv="true" dav="true" t="0.198"/>
<MovementRecord x="-661.45642" y="1123.7483" z="53.758194" qx="0" qy="0.97830099" qz="0" qw="-0.20718889" vx="-3.5752378" vy="-0.0040555298" vz="-8.0621586" avx="0" avy="-2.5476563" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
<MovementRecord x="-661.73975" y="1123.7469" z="51.782887" qx="0" qy="0.99977756" qz="0" qw="-0.021090925" vx="-0.37193322" vy="-0.0041383081" vz="-8.811492" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-661.96057" y="1123.7456" z="50.093998" qx="0" qy="0.99636191" qz="0" qw="-0.085222892" vx="-1.49775" vy="-0.0041759168" vz="-8.6912289" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-662.29297" y="1123.744" z="48.224056" qx="0" qy="0.99613321" qz="0" qw="-0.087855853" vx="-1.5436686" vy="-0.0041759908" vz="-8.6831913" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-662.57861" y="1123.7426" z="46.483566" qx="0" qy="0.99748456" qz="0" qw="-0.070884049" vx="-1.2471558" vy="-0.004173473" vz="-8.7307119" avx="0" avy="-0.30132243" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
<MovementRecord x="-662.83026" y="1123.741" z="44.589401" qx="0" qy="0.99784493" qz="0" qw="-0.065616116" vx="-1.1548871" vy="-0.0041717072" vz="-8.7433949" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
<MovementRecord x="-663.065" y="1123.7395" z="42.841267" qx="0" qy="0.99736089" qz="0" qw="-0.07260374" vx="-1.2772541" vy="-0.0041739475" vz="-8.7263594" avx="0" avy="0.30980942" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
<MovementRecord x="-663.34338" y="1123.7379" z="40.95084" qx="0" qy="0.99732316" qz="0" qw="-0.073120199" vx="-1.286291" vy="-0.0041740802" vz="-8.7250319" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-663.62146" y="1123.7363" z="39.060375" qx="0" qy="0.99733573" qz="0" qw="-0.072948046" vx="-1.2832787" vy="-0.0041740369" vz="-8.7254753" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-663.89001" y="1123.7351" z="37.317089" qx="0" qy="0.99676812" qz="0" qw="-0.080332734" vx="-1.4123834" vy="-0.0036389432" vz="-8.7055092" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-664.20569" y="1123.7341" z="35.432461" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036370354" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-664.52148" y="1123.7332" z="33.547852" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.003687297" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-664.81299" y="1123.7324" z="31.808212" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0037204367" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-665.10449" y="1123.7316" z="30.068573" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036486434" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-665.396" y="1123.7307" z="28.328934" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036817831" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-665.6875" y="1123.7299" z="26.589294" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036099749" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-666.0033" y="1123.729" z="24.704685" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036602514" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-666.31311" y="1123.728" z="22.855684" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036934214" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-666.604" y="1123.7273" z="21.119551" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036277049" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-666.9198" y="1123.7263" z="19.234941" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036779814" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-667.2356" y="1123.7255" z="17.350332" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0037282431" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-667.52557" y="1123.7247" z="15.619577" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036350512" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-667.84137" y="1123.7238" z="13.734968" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036328458" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-668.15717" y="1123.7228" z="11.850359" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036831074" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-668.47296" y="1123.7219" z="9.9657497" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.003733384" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-668.76447" y="1123.7212" z="8.2261105" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036444538" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-669.05597" y="1123.7202" z="6.4864712" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036947303" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-669.37177" y="1123.7194" z="4.601862" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.003744992" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-669.66327" y="1123.7186" z="2.8622227" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036560618" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-669.93048" y="1123.7178" z="1.2675533" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.003689202" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-670.24628" y="1123.7169" z="-0.61705661" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036174082" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-670.56207" y="1123.7159" z="-2.5016661" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036152028" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-670.85358" y="1123.7151" z="-4.2413054" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0037179464" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-671.16937" y="1123.7142" z="-6.1259146" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036986046" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-671.46088" y="1123.7134" z="-7.8655539" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036268109" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-671.77667" y="1123.7124" z="-9.7501631" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036770874" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-672.04388" y="1123.7118" z="-11.344832" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036406391" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-672.35968" y="1123.7108" z="-13.229442" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036384189" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-672.65118" y="1123.71" z="-14.969081" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0037411624" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-672.89941" y="1123.7094" z="-16.451059" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-0.27985972" vy="-0.00032467872" vz="-1.6710598" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-672.90015" y="1123.7094" z="-16.455486" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-3.193491e-08" vy="0.00038383523" vz="-1.7891979e-07" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-672.90015" y="1123.7094" z="-16.455486" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-3.193491e-08" vy="0.00038383523" vz="-1.7891979e-07" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="4.8179998"/>
</Recorder>

View File

@@ -0,0 +1,58 @@
<Recorder>
<ClearEquippedRecord t="0"/>
<EquipRecord item="9856" t="0"/>
<EquipRecord item="6500" t="0"/>
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.39199999"/>
<JumpRecord label="check-player" t="0" />
<SpeakRecord name="join-me" text="Join me!" t="3.0" />
<PlayerProximityRecord name="check-player" distance="10" timeout="5" timeoutLabel="join-me" t="0" />
<SpeakRecord text="Welcome!" t="4.026"/>
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="2.046"/>
<MovementRecord x="-557.55542" y="1128.7369" z="50.891663" qx="0" qy="0.77823329" qz="0" qw="0.62797529" vx="-1.4221454" vy="3.3353501e-07" vz="0.090151384" avx="0" avy="188.49556" avz="0" g="true" dv="true" dav="true" t="0.46200001"/>
<MovementRecord x="-559.65845" y="1128.7369" z="51.024967" qx="0" qy="0.97620082" qz="0" qw="-0.21686859" vx="-16.871138" vy="3.9568704e-06" vz="1.0694356" avx="0" avy="80.788872" avz="0" g="true" dv="true" dav="true" t="0.198"/>
<MovementRecord x="-563.03271" y="1128.7369" z="51.238667" qx="0" qy="-0.68442959" qz="0" qw="0.72907895" vx="-16.871298" vy="3.9620759e-06" vz="1.0669085" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-566.12579" y="1128.7369" z="51.43425" qx="0" qy="-0.68442959" qz="0" qw="0.72907895" vx="-16.871298" vy="3.9620759e-06" vz="1.0669085" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-569.76996" y="1128.7369" z="51.779564" qx="0" qy="-0.65352851" qz="0" qw="0.75690192" vx="-16.724352" vy="1.0602414e-06" vz="2.4647665" avx="0" avy="0.29643202" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
<MovementRecord x="-573.31696" y="1128.7369" z="52.304493" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-576.93988" y="1128.7369" z="52.840672" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-580.00543" y="1128.7369" z="53.294361" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-583.6225" y="1128.7369" z="53.869492" qx="0" qy="-0.64578879" qz="0" qw="0.76351613" vx="-16.6707" vy="3.4790952e-07" vz="2.8047707" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-587.23431" y="1128.7369" z="54.47718" qx="0" qy="-0.64578879" qz="0" qw="0.76351613" vx="-16.6707" vy="3.4790952e-07" vz="2.8047707" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-590.78857" y="1128.7369" z="55.154781" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-594.10425" y="1128.7369" z="55.814648" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-597.41992" y="1128.7369" z="56.474514" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-600.37299" y="1129.1835" z="57.067936" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.550844" vy="-0.02764282" vz="3.293468" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-603.67401" y="1129.1776" z="57.794476" qx="0" qy="-0.60967135" qz="0" qw="0.79265428" vx="-16.338949" vy="-0.027341407" vz="4.3378429" avx="0" avy="0.68940604" avz="0" g="true" dv="true" dav="true" t="0.264"/>
<MovementRecord x="-607.30255" y="1128.5726" z="58.873291" qx="0" qy="-0.58935732" qz="0" qw="0.80787253" vx="-16.122616" vy="-15" vz="5.0832543" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-610.39264" y="1126.5226" z="59.848522" qx="0" qy="-0.58935732" qz="0" qw="0.80787253" vx="-13.727316" vy="-8.8299694" vz="4.4012947" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-612.91327" y="1124.842" z="60.493713" qx="0" qy="-0.64014912" qz="0" qw="0.7682507" vx="-13.939328" vy="-9.2160997" vz="2.5568676" avx="0" avy="-0.90714848" avz="0" g="true" dv="true" dav="true" t="0.495"/>
<MovementRecord x="-615.60901" y="1123.9226" z="60.914558" qx="0" qy="-0.65074474" qz="0" qw="0.7592966" vx="-16.705799" vy="0" vz="2.5875225" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
<MovementRecord x="-619.20642" y="1123.9226" z="61.475426" qx="0" qy="-0.64724869" qz="0" qw="0.76227891" vx="-16.681313" vy="0" vz="2.7409482" avx="0" avy="0.31377235" avz="0" g="true" dv="true" dav="true" t="0.264"/>
<MovementRecord x="-622.5293" y="1123.9226" z="62.100258" qx="0" qy="-0.63752168" qz="0" qw="0.77043241" vx="-16.606367" vy="0" vz="3.163466" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
<MovementRecord x="-626.12823" y="1123.7505" z="62.781879" qx="0" qy="-0.64108664" qz="0" qw="0.76746851" vx="-16.634985" vy="4.6775982e-05" vz="3.0093575" avx="0" avy="-0.30132243" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
<MovementRecord x="-629.71057" y="1123.7505" z="63.393055" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-16.665606" vy="-4.7832516e-05" vz="2.8348761" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-633.59003" y="1124.8367" z="64.052917" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-16.896" vy="20.297354" vz="2.8736222" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-637.22083" y="1127.7493" z="64.670441" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-19.241386" vy="8.9419651" vz="3.2726972" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-641.23969" y="1128.4006" z="65.354004" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-20.97077" vy="-2.3861933" vz="3.5669582" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-645.92755" y="1128.7273" z="66.15139" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-22.068434" vy="11.44259" vz="3.7537298" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-650.06995" y="1129.8671" z="66.85601" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-22.994522" vy="0.9925909" vz="3.9113069" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-655.15118" y="1128.7443" z="67.720322" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-23.803282" vy="-11.357409" vz="4.0489192" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-659.56635" y="1124.9456" z="68.471329" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-7.8723621" vy="-23.707413" vz="1.3390911" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-660.31464" y="1123.7504" z="68.5979" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-1.6779066" vy="1.007928e-05" vz="0.28236043" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-660.31915" y="1123.7504" z="68.598663" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.198"/>
<MovementRecord x="-660.29572" y="1123.7504" z="68.594681" qx="0" qy="-0.5861969" qz="0" qw="0.81016856" vx="1.4048207" vy="2.2637594e-05" vz="-0.23896487" avx="0" avy="188.49556" avz="0" g="true" dv="true" dav="true" t="0.29699999"/>
<MovementRecord x="-658.21826" y="1123.7504" z="68.241364" qx="0" qy="0.26788518" qz="0" qw="0.96345085" vx="16.665703" vy="8.9682879e-05" vz="-2.8343182" avx="0" avy="80.787056" avz="0" g="true" dv="true" dav="true" t="0.198"/>
<MovementRecord x="-655.93872" y="1123.7504" z="67.853683" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="4.0222421" vy="4.9844002e-05" vz="-0.6840589" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-655.87488" y="1123.7504" z="67.842827" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="1.2125849" vy="3.9280545e-05" vz="-0.20622317" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-655.87488" y="1123.7504" z="67.842827" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.23100001"/>
<MovementRecord x="-655.04889" y="1123.7504" z="67.242249" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="4.6101446" vy="-0.0011672372" vz="-3.3521008" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="1.1525371" vy="-0.00029180956" vz="-0.83802569" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.198"/>
<SpeakRecord text="Great!&quot;" t="2.3759999"/>
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="3.6960001"/>
</Recorder>

View File

@@ -0,0 +1,50 @@
<Recorder>
<ClearEquippedRecord t="0"/>
<EquipRecord item="9856" t="0"/>
<EquipRecord item="6500" t="0"/>
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.39199999"/>
<SpeakRecord text="Welcome!" t="4.026"/>
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="2.046"/>
<MovementRecord x="-557.55542" y="1128.7369" z="50.891663" qx="0" qy="0.77823329" qz="0" qw="0.62797529" vx="-1.4221454" vy="3.3353501e-07" vz="0.090151384" avx="0" avy="188.49556" avz="0" g="true" dv="true" dav="true" t="0.46200001"/>
<MovementRecord x="-559.65845" y="1128.7369" z="51.024967" qx="0" qy="0.97620082" qz="0" qw="-0.21686859" vx="-16.871138" vy="3.9568704e-06" vz="1.0694356" avx="0" avy="80.788872" avz="0" g="true" dv="true" dav="true" t="0.198"/>
<MovementRecord x="-563.03271" y="1128.7369" z="51.238667" qx="0" qy="-0.68442959" qz="0" qw="0.72907895" vx="-16.871298" vy="3.9620759e-06" vz="1.0669085" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-566.12579" y="1128.7369" z="51.43425" qx="0" qy="-0.68442959" qz="0" qw="0.72907895" vx="-16.871298" vy="3.9620759e-06" vz="1.0669085" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-569.76996" y="1128.7369" z="51.779564" qx="0" qy="-0.65352851" qz="0" qw="0.75690192" vx="-16.724352" vy="1.0602414e-06" vz="2.4647665" avx="0" avy="0.29643202" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
<MovementRecord x="-573.31696" y="1128.7369" z="52.304493" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-576.93988" y="1128.7369" z="52.840672" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-580.00543" y="1128.7369" z="53.294361" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-583.6225" y="1128.7369" z="53.869492" qx="0" qy="-0.64578879" qz="0" qw="0.76351613" vx="-16.6707" vy="3.4790952e-07" vz="2.8047707" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-587.23431" y="1128.7369" z="54.47718" qx="0" qy="-0.64578879" qz="0" qw="0.76351613" vx="-16.6707" vy="3.4790952e-07" vz="2.8047707" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-590.78857" y="1128.7369" z="55.154781" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-594.10425" y="1128.7369" z="55.814648" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-597.41992" y="1128.7369" z="56.474514" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-600.37299" y="1129.1835" z="57.067936" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.550844" vy="-0.02764282" vz="3.293468" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-603.67401" y="1129.1776" z="57.794476" qx="0" qy="-0.60967135" qz="0" qw="0.79265428" vx="-16.338949" vy="-0.027341407" vz="4.3378429" avx="0" avy="0.68940604" avz="0" g="true" dv="true" dav="true" t="0.264"/>
<MovementRecord x="-607.30255" y="1128.5726" z="58.873291" qx="0" qy="-0.58935732" qz="0" qw="0.80787253" vx="-16.122616" vy="-15" vz="5.0832543" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-610.39264" y="1126.5226" z="59.848522" qx="0" qy="-0.58935732" qz="0" qw="0.80787253" vx="-13.727316" vy="-8.8299694" vz="4.4012947" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-612.91327" y="1124.842" z="60.493713" qx="0" qy="-0.64014912" qz="0" qw="0.7682507" vx="-13.939328" vy="-9.2160997" vz="2.5568676" avx="0" avy="-0.90714848" avz="0" g="true" dv="true" dav="true" t="0.495"/>
<MovementRecord x="-615.60901" y="1123.9226" z="60.914558" qx="0" qy="-0.65074474" qz="0" qw="0.7592966" vx="-16.705799" vy="0" vz="2.5875225" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
<MovementRecord x="-619.20642" y="1123.9226" z="61.475426" qx="0" qy="-0.64724869" qz="0" qw="0.76227891" vx="-16.681313" vy="0" vz="2.7409482" avx="0" avy="0.31377235" avz="0" g="true" dv="true" dav="true" t="0.264"/>
<MovementRecord x="-622.5293" y="1123.9226" z="62.100258" qx="0" qy="-0.63752168" qz="0" qw="0.77043241" vx="-16.606367" vy="0" vz="3.163466" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
<MovementRecord x="-626.12823" y="1123.7505" z="62.781879" qx="0" qy="-0.64108664" qz="0" qw="0.76746851" vx="-16.634985" vy="4.6775982e-05" vz="3.0093575" avx="0" avy="-0.30132243" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
<MovementRecord x="-629.71057" y="1123.7505" z="63.393055" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-16.665606" vy="-4.7832516e-05" vz="2.8348761" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-633.59003" y="1124.8367" z="64.052917" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-16.896" vy="20.297354" vz="2.8736222" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-637.22083" y="1127.7493" z="64.670441" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-19.241386" vy="8.9419651" vz="3.2726972" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-641.23969" y="1128.4006" z="65.354004" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-20.97077" vy="-2.3861933" vz="3.5669582" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-645.92755" y="1128.7273" z="66.15139" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-22.068434" vy="11.44259" vz="3.7537298" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-650.06995" y="1129.8671" z="66.85601" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-22.994522" vy="0.9925909" vz="3.9113069" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-655.15118" y="1128.7443" z="67.720322" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-23.803282" vy="-11.357409" vz="4.0489192" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-659.56635" y="1124.9456" z="68.471329" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-7.8723621" vy="-23.707413" vz="1.3390911" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-660.31464" y="1123.7504" z="68.5979" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-1.6779066" vy="1.007928e-05" vz="0.28236043" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
<MovementRecord x="-660.31915" y="1123.7504" z="68.598663" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.198"/>
<MovementRecord x="-660.29572" y="1123.7504" z="68.594681" qx="0" qy="-0.5861969" qz="0" qw="0.81016856" vx="1.4048207" vy="2.2637594e-05" vz="-0.23896487" avx="0" avy="188.49556" avz="0" g="true" dv="true" dav="true" t="0.29699999"/>
<MovementRecord x="-658.21826" y="1123.7504" z="68.241364" qx="0" qy="0.26788518" qz="0" qw="0.96345085" vx="16.665703" vy="8.9682879e-05" vz="-2.8343182" avx="0" avy="80.787056" avz="0" g="true" dv="true" dav="true" t="0.198"/>
<MovementRecord x="-655.93872" y="1123.7504" z="67.853683" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="4.0222421" vy="4.9844002e-05" vz="-0.6840589" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-655.87488" y="1123.7504" z="67.842827" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="1.2125849" vy="3.9280545e-05" vz="-0.20622317" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-655.87488" y="1123.7504" z="67.842827" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.23100001"/>
<MovementRecord x="-655.04889" y="1123.7504" z="67.242249" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="4.6101446" vy="-0.0011672372" vz="-3.3521008" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="1.1525371" vy="-0.00029180956" vz="-0.83802569" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.198"/>
<SpeakRecord text="Great!&quot;" t="2.3759999"/>
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="3.6960001"/>
</Recorder>

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,5 @@
<Scene>
<Prefab file="vanity/prefabs/SFlagSmall.xml" x="-655.564" y="1123.76" z="75.993"/>
<NPC name="John" act="vanity/acts/my-act.xml"/>
<NPC name="Bill" act="vanity/acts/my-act-2.xml"/>
</Scene>

View File

@@ -611,7 +611,23 @@ void BaseCombatAIComponent::ClearThreat() {
m_DirtyThreat = true;
}
void BaseCombatAIComponent::SetStartPosition(const NiPoint3& position) {
m_StartPosition = position;
}
void BaseCombatAIComponent::Wander() {
if (m_FocusPosition != NiPoint3Constant::ZERO) {
m_MovementAI->SetHaltDistance(m_FocusRadius);
m_MovementAI->SetDestination(m_FocusPosition);
m_MovementAI->SetMaxSpeed(m_TetherSpeed);
m_Timer += 0.5f;
return;
}
if (!m_MovementAI->AtFinalWaypoint()) {
return;
}
@@ -762,6 +778,38 @@ void BaseCombatAIComponent::SetAggroRadius(const float value) {
m_AggroRadius = value;
}
float BaseCombatAIComponent::GetSoftTetherRadius() const {
return m_SoftTetherRadius;
}
void BaseCombatAIComponent::SetSoftTetherRadius(const float value) {
m_SoftTetherRadius = value;
}
void BaseCombatAIComponent::SetHardTetherRadius(const float value) {
m_HardTetherRadius = value;
}
const NiPoint3& BaseCombatAIComponent::GetFocusPosition() const {
return m_FocusPosition;
}
void BaseCombatAIComponent::SetFocusPosition(const NiPoint3& value) {
m_FocusPosition = value;
}
float BaseCombatAIComponent::GetFocusRadius() const {
return m_FocusRadius;
}
void BaseCombatAIComponent::SetFocusRadius(const float value) {
m_FocusRadius = value;
}
float BaseCombatAIComponent::GetHardTetherRadius() const {
return m_HardTetherRadius;
}
void BaseCombatAIComponent::LookAt(const NiPoint3& point) {
if (m_Stunned) {
return;

View File

@@ -120,6 +120,12 @@ public:
*/
const NiPoint3& GetStartPosition() const;
/**
* Sets the position where the entity spawned
* @param position the position where the entity spawned
*/
void SetStartPosition(const NiPoint3& position);
/**
* Removes all threats for this entities, and thus chances for it attacking other entities
*/
@@ -196,6 +202,54 @@ public:
*/
void SetAggroRadius(float value);
/**
* Gets the soft tether radius
* @return the soft tether radius
*/
float GetSoftTetherRadius() const;
/**
* Sets the soft tether radius
* @param value the soft tether radius
*/
void SetSoftTetherRadius(float value);
/**
* Gets the hard tether radius
* @return the hard tether radius
*/
float GetHardTetherRadius() const;
/**
* Sets the hard tether radius
* @param value the hard tether radius
*/
void SetHardTetherRadius(float value);
/**
* Get the position that the entity should stay around
* @return the focus position
*/
const NiPoint3& GetFocusPosition() const;
/**
* Set the position that the entity should stay around
* @param position the focus position
*/
void SetFocusPosition(const NiPoint3& position);
/**
* Get the radius that the entity should stay around the focus position
* @return the focus radius
*/
float GetFocusRadius() const;
/**
* Set the radius that the entity should stay around the focus position
* @param radius the focus radius
*/
void SetFocusRadius(float radius);
/**
* Makes the entity look at a certain point in space
* @param point the point to look at
@@ -398,6 +452,16 @@ private:
// The amount of time a removed threat will be ignored for.
std::map<LWOOBJID, float> m_RemovedThreatList;
/**
* A position that the entity should stay around
*/
NiPoint3 m_FocusPosition;
/**
* How far the entity should stay from the focus position
*/
float m_FocusRadius = 0;
/**
* Whether the current entity is a mech enemy, needed as mechs tether radius works differently
* @return whether this entity is a mech

View File

@@ -182,6 +182,10 @@ const MovementAIInfo& MovementAIComponent::GetInfo() const {
return m_Info;
}
MovementAIInfo& MovementAIComponent::GetInfo() {
return m_Info;
}
bool MovementAIComponent::AdvanceWaypointIndex() {
if (m_PathIndex >= m_InterpolatedWaypoints.size()) {
return false;

View File

@@ -74,6 +74,12 @@ public:
*/
const MovementAIInfo& GetInfo() const;
/**
* Returns a mutable reference to the basic settings that this entity uses to move around
* @return a mutable reference to the basic settings that this entity uses to move around
*/
MovementAIInfo& GetInfo();
/**
* Set a destination point for the entity to move towards
* @param value the destination point to move towards

View File

@@ -16,10 +16,20 @@
std::unordered_map<int32_t, float> RenderComponent::m_DurationCache{};
std::unordered_map<int32_t, std::vector<int32_t>> RenderComponent::m_AnimationGroupCache{};
RenderComponent::RenderComponent(Entity* const parentEntity, const int32_t componentId) : Component{ parentEntity } {
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();
@@ -43,6 +53,8 @@ RenderComponent::RenderComponent(Entity* const parentEntity, const int32_t compo
}
}
result.finalize();
m_AnimationGroupCache[componentId] = m_animationGroupIds;
}
void RenderComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {

View File

@@ -145,6 +145,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

View File

@@ -18,6 +18,7 @@ target_include_directories(dGameMessages PUBLIC "."
"${PROJECT_SOURCE_DIR}/dGame/dBehaviors" # via InventoryComponent.h
"${PROJECT_SOURCE_DIR}/dGame/dInventory" # via InventoryComponent.h
"${PROJECT_SOURCE_DIR}/dGame/dEntity" # via dZoneManager/Spawner.h
"${PROJECT_SOURCE_DIR}/dGame/dCinema"
"${PROJECT_SOURCE_DIR}/dZoneManager" # via GameMessages.cpp, GameMessageHandler.cpp
)
target_precompile_headers(dGameMessages REUSE_FROM dGameBase)

View File

@@ -103,6 +103,8 @@
#include "CDObjectsTable.h"
#include "eItemType.h"
#include "Recorder.h"
void GameMessages::SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender) {
CBITSTREAM;
CMSGHEADER;
@@ -193,6 +195,12 @@ void GameMessages::SendPlayAnimation(Entity* entity, const std::u16string& anima
if (fScale != 1.0f) bitStream.Write(fScale);
SEND_PACKET_BROADCAST;
auto* recorder = Cinema::Recording::Recorder::GetRecorder(entity->GetObjectID());
if (recorder != nullptr) {
recorder->AddRecord(new Cinema::Recording::AnimationRecord(GeneralUtils::UTF16ToWTF8(animationName)));
}
}
void GameMessages::SendPlayerReady(Entity* entity, const SystemAddress& sysAddr) {
@@ -5303,6 +5311,12 @@ void GameMessages::HandleEquipItem(RakNet::BitStream& inStream, Entity* entity)
item->Equip();
Game::entityManager->SerializeEntity(entity);
auto* recorder = Cinema::Recording::Recorder::GetRecorder(entity->GetObjectID());
if (recorder != nullptr) {
recorder->AddRecord(new Cinema::Recording::EquipRecord(item->GetLot()));
}
}
void GameMessages::HandleUnequipItem(RakNet::BitStream& inStream, Entity* entity) {
@@ -5323,6 +5337,12 @@ void GameMessages::HandleUnequipItem(RakNet::BitStream& inStream, Entity* entity
item->UnEquip();
Game::entityManager->SerializeEntity(entity);
auto* recorder = Cinema::Recording::Recorder::GetRecorder(entity->GetObjectID());
if (recorder != nullptr) {
recorder->AddRecord(new Cinema::Recording::UnequipRecord(item->GetLot()));
}
}
void GameMessages::HandleRemoveItemFromInventory(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {

View File

@@ -6,6 +6,7 @@ set(DGAME_DUTILITIES_SOURCES "BrickDatabase.cpp"
"ObjectIDManager.cpp"
"Preconditions.cpp"
"SlashCommandHandler.cpp"
"ServerPreconditions.cpp"
"VanityUtilities.cpp")
add_subdirectory(SlashCommands)

View File

@@ -0,0 +1,115 @@
#include "ServerPreconditions.h"
#include "tinyxml2.h"
using namespace ServerPreconditions;
namespace {
std::unordered_map<LOT, std::vector<std::pair<bool, PreconditionExpression>>> m_Preconditions;
std::unordered_map<LWOOBJID, LWOOBJID> m_SoloActors;
std::unordered_map<LWOOBJID, std::unordered_set<LWOOBJID>> m_ExcludeForPlayer;
}
void ServerPreconditions::LoadPreconditions(std::string file) {
tinyxml2::XMLDocument doc;
doc.LoadFile(file.c_str());
tinyxml2::XMLElement* root = doc.FirstChildElement("Preconditions");
if (!root) {
return;
}
for (tinyxml2::XMLElement* element = root->FirstChildElement("Entity"); element; element = element->NextSiblingElement("Entity")) {
LOT lot = element->UnsignedAttribute("lot");
std::vector<std::pair<bool, PreconditionExpression>> preconditions;
for (tinyxml2::XMLElement* precondition = element->FirstChildElement("Precondition"); precondition; precondition = precondition->NextSiblingElement("Precondition")) {
const auto condition = Preconditions::CreateExpression(precondition->GetText());
int64_t inverted;
if (precondition->QueryInt64Attribute("not", &inverted) == tinyxml2::XML_SUCCESS) {
preconditions.push_back(std::make_pair(inverted > 0, condition));
}
else {
preconditions.push_back(std::make_pair(false, condition));
}
}
m_Preconditions[lot] = preconditions;
}
}
bool ServerPreconditions::CheckPreconditions(Entity* target, Entity* entity) {
if (IsExcludedFor(entity->GetObjectID(), target->GetObjectID())) {
return false;
}
if (IsSoloActor(target->GetObjectID())) {
return IsActingFor(target->GetObjectID(), entity->GetObjectID());
}
if (m_Preconditions.find(target->GetLOT()) == m_Preconditions.end()) {
return true;
}
for (const auto& [inverse, precondition] : m_Preconditions[target->GetLOT()]) {
if (precondition.Check(entity) == inverse) {
return false;
}
}
return true;
}
bool ServerPreconditions::IsSoloActor(LWOOBJID actor) {
return m_SoloActors.find(actor) != m_SoloActors.end();
}
bool ServerPreconditions::IsActingFor(LWOOBJID actor, LWOOBJID target) {
return m_SoloActors.find(actor) != m_SoloActors.end() && m_SoloActors[actor] == target;
}
void ServerPreconditions::AddSoloActor(LWOOBJID actor, LWOOBJID target) {
m_SoloActors[actor] = target;
}
void ServerPreconditions::RemoveSoloActor(LWOOBJID actor) {
m_SoloActors.erase(actor);
}
void ServerPreconditions::AddExcludeFor(LWOOBJID player, LWOOBJID target) {
const auto& it = m_ExcludeForPlayer.find(player);
if (it == m_ExcludeForPlayer.end()) {
m_ExcludeForPlayer[player] = std::unordered_set<LWOOBJID>();
}
m_ExcludeForPlayer[player].insert(target);
}
void ServerPreconditions::RemoveExcludeFor(LWOOBJID player, LWOOBJID target) {
const auto& it = m_ExcludeForPlayer.find(player);
if (it == m_ExcludeForPlayer.end()) {
return;
}
m_ExcludeForPlayer[player].erase(target);
if (m_ExcludeForPlayer[player].empty()) {
m_ExcludeForPlayer.erase(player);
}
}
bool ServerPreconditions::IsExcludedFor(LWOOBJID player, LWOOBJID target) {
const auto& it = m_ExcludeForPlayer.find(player);
if (it == m_ExcludeForPlayer.end()) {
return false;
}
return it->second.find(target) != it->second.end();
}

View File

@@ -0,0 +1,103 @@
#pragma once
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "dCommonVars.h"
#include "Preconditions.h"
class Entity;
/**
* @brief Contains a series of additions to the server-side checks for whether or not an entity
* is shown to the client or not.
*/
namespace ServerPreconditions
{
/**
* @brief Loads the preconditions from the given file.
*
* @param file The file to load the preconditions from.
*
* @section Example
* <Preconditions>
* <Entity lot="2097254">
* <Precondition>1006</Precondition>
* </Entity>
* <Entity lot="12261">
* <Precondition not="1">1006</Precondition>
* </Entity>
* </Preconditions>
*/
void LoadPreconditions(std::string file);
/**
* @brief Checks the additional server-side preconditions for the given entity.
*
* @param target The entity to check the preconditions for.
* @param entity The entity to check the preconditions against (usually the player).
*
* @return Whether or not the entity passes the preconditions.
*/
bool CheckPreconditions(Entity* target, Entity* entity);
/**
* @brief Checks if a given entity is a solo actor.
*
* Solo actors are entities that are only shown to the client if they are acting for the player.
*/
bool IsSoloActor(LWOOBJID actor);
/**
* @brief Checks if a given entity is acting for another entity.
*
* @param actor The entity to check if it is acting for another entity.
* @param target The entity to check if the actor is acting for (usually the player).
*
* @return Whether or not the actor is acting for the target.
*/
bool IsActingFor(LWOOBJID actor, LWOOBJID target);
/**
* @brief Adds an entity to the list of solo actors.
*
* @param actor The entity to add to the list of solo actors.
* @param target The entity to add the actor to the list of solo actors for (usually the player).
*/
void AddSoloActor(LWOOBJID actor, LWOOBJID target);
/**
* @brief Removes an entity from the list of solo actors.
*
* @param actor The entity to remove from the list of solo actors.
*/
void RemoveSoloActor(LWOOBJID actor);
/**
* @brief Adds an entity to the list of entities to exclude for another entity.
*
* @param player The entity to exclude the target for (usually the player).
* @param target The entity to exclude for the player.
*/
void AddExcludeFor(LWOOBJID player, LWOOBJID target);
/**
* @brief Removes an entity from the list of entities to exclude for another entity.
*
* @param player The entity to remove the target from the list of entities to exclude for (usually the player).
* @param target The entity to remove from the list of entities to exclude for the player.
*/
void RemoveExcludeFor(LWOOBJID player, LWOOBJID target);
/**
* @brief Checks if an entity is excluded for another entity.
*
* @param player The entity to check if the target is excluded for (usually the player).
* @param target The entity to check if it is excluded for the player.
*
* @return Whether or not the target is excluded for the player.
*/
bool IsExcludedFor(LWOOBJID player, LWOOBJID target);
}

View File

@@ -273,6 +273,16 @@ Instance* InstanceManager::FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID
return nullptr;
}
Instance* InstanceManager::FindInstanceWithPrivate(LWOMAPID mapID, LWOINSTANCEID instanceID) {
for (Instance* i : m_Instances) {
if (i && i->GetMapID() == mapID && i->GetInstanceID() == instanceID && !i->GetShutdownComplete() && !i->GetIsShuttingDown()) {
return i;
}
}
return nullptr;
}
Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password) {
auto* instance = FindPrivateInstance(password);

View File

@@ -76,25 +76,25 @@ public:
void Shutdown();
private:
std::string m_IP;
uint32_t m_Port;
LWOZONEID m_ZoneID;
int m_MaxClientsSoftCap;
int m_MaxClientsHardCap;
int m_CurrentClientCount;
std::vector<Player> m_Players;
SystemAddress m_SysAddr;
bool m_Ready;
bool m_IsShuttingDown;
std::vector<PendingInstanceRequest> m_PendingRequests;
std::vector<PendingInstanceRequest> m_PendingAffirmations;
std::string m_IP{};
uint32_t m_Port{};
LWOZONEID m_ZoneID{};
int m_MaxClientsSoftCap{};
int m_MaxClientsHardCap{};
int m_CurrentClientCount{};
std::vector<Player> m_Players{};
SystemAddress m_SysAddr{};
bool m_Ready{};
bool m_IsShuttingDown{};
std::vector<PendingInstanceRequest> m_PendingRequests{};
std::vector<PendingInstanceRequest> m_PendingAffirmations{};
uint32_t m_AffirmationTimeout;
uint32_t m_AffirmationTimeout{};
bool m_IsPrivate;
std::string m_Password;
bool m_IsPrivate{};
std::string m_Password{};
bool m_Shutdown;
bool m_Shutdown{};
//Private functions:
};
@@ -125,6 +125,7 @@ public:
Instance* FindInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneId = 0);
Instance* FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID);
Instance* FindInstanceWithPrivate(LWOMAPID mapID, LWOINSTANCEID instanceID);
Instance* CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password);
Instance* FindPrivateInstance(const std::string& password);

View File

@@ -42,6 +42,7 @@
#include "Server.h"
#include "CDZoneTableTable.h"
#include "eGameMasterLevel.h"
#include "StringifiedEnum.h"
#ifdef DARKFLAME_PLATFORM_UNIX
@@ -212,6 +213,13 @@ int main(int argc, char** argv) {
// Run migrations should any need to be run.
MigrationRunner::RunSQLiteMigrations();
// Check for the --migrations-only flag
if ((argc > 1 &&
(strcmp(argv[1], "--migrations-only") == 0 || strcmp(argv[1], "-m") == 0))) {
LOG("Migrations only flag detected. Exiting.");
return EXIT_SUCCESS;
}
//If the first command line argument is -a or --account then make the user
//input a username and password, with the password being hidden.
bool createAccount = Database::Get()->GetAccountCount() == 0 && Game::config->GetValue("skip_account_creation") != "1";
@@ -556,7 +564,7 @@ void HandlePacket(Packet* packet) {
Instance* in = Game::im->GetInstance(zoneID, false, zoneClone);
for (auto* instance : Game::im->GetInstances()) {
LOG("Instance: %i/%i/%i -> %i", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance == in);
LOG("Instance: %i/%i/%i -> %i %s", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance == in, instance->GetSysAddr().ToString());
}
if (in && !in->GetIsReady()) //Instance not ready, make a pending request
@@ -597,15 +605,10 @@ void HandlePacket(Packet* packet) {
if (!Game::im->IsPortInUse(theirPort)) {
Instance* in = new Instance(theirIP.string, theirPort, theirZoneID, theirInstanceID, 0, 12, 12);
SystemAddress copy;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
in->SetSysAddr(copy);
in->SetSysAddr(packet->systemAddress);
Game::im->AddInstance(in);
} else {
auto instance = Game::im->FindInstance(
theirZoneID, static_cast<uint16_t>(theirInstanceID));
auto* instance = Game::im->FindInstanceWithPrivate(theirZoneID, static_cast<LWOINSTANCEID>(theirInstanceID));
if (instance) {
instance->SetSysAddr(packet->systemAddress);
}
@@ -613,22 +616,14 @@ void HandlePacket(Packet* packet) {
}
if (theirServerType == ServerType::Chat) {
SystemAddress copy;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
chatServerMasterPeerSysAddr = copy;
chatServerMasterPeerSysAddr = packet->systemAddress;
}
if (theirServerType == ServerType::Auth) {
SystemAddress copy;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
authServerMasterPeerSysAddr = copy;
authServerMasterPeerSysAddr = packet->systemAddress;
}
LOG("Received server info, instance: %i port: %i", theirInstanceID, theirPort);
LOG("Received %s server info, instance: %i port: %i", StringifiedEnum::ToString(theirServerType).data(), theirInstanceID, theirPort);
break;
}
@@ -692,7 +687,7 @@ void HandlePacket(Packet* packet) {
if (instance) {
instance->AddPlayer(Player());
} else {
printf("Instance missing? What?");
LOG("Instance missing? What?");
}
break;
}
@@ -733,8 +728,8 @@ void HandlePacket(Packet* packet) {
inStream.Read<char>(character);
password += character;
}
Game::im->CreatePrivateInstance(mapId, cloneId, password.c_str());
auto* newInst = Game::im->CreatePrivateInstance(mapId, cloneId, password.c_str());
LOG("Creating private zone %i/%i/%i with password %s", newInst->GetMapID(), newInst->GetCloneID(), newInst->GetInstanceID(), password.c_str());
break;
}
@@ -835,11 +830,10 @@ void HandlePacket(Packet* packet) {
}
case MessageType::Master::SHUTDOWN_RESPONSE: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
CINSTREAM_SKIP_HEADER;
auto* instance = Game::im->GetInstanceBySysAddr(packet->systemAddress);
LOG("Got shutdown response from %s", packet->systemAddress.ToString());
if (instance == nullptr) {
return;
}

View File

@@ -83,6 +83,9 @@
#include "SlashCommandHandler.h"
#include "InventoryComponent.h"
#include "ServerPreconditions.h"
#include "Scene.h"
namespace Game {
Logger* logger = nullptr;
dServer* server = nullptr;
@@ -296,6 +299,16 @@ int main(int argc, char** argv) {
}
}
// 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
@@ -1329,6 +1342,12 @@ void HandlePacket(Packet* packet) {
std::string sMessage = GeneralUtils::UTF16ToWTF8(chatMessage.message);
LOG("%s: %s", playerName.c_str(), sMessage.c_str());
ChatPackets::SendChatMessage(packet->systemAddress, chatMessage.chatChannel, playerName, user->GetLoggedInChar(), isMythran, chatMessage.message);
auto* recorder = Cinema::Recording::Recorder::GetRecorder(user->GetLoggedInChar());
if (recorder != nullptr) {
recorder->AddRecord(new Cinema::Recording::SpeakRecord(sMessage));
}
}
break;

Binary file not shown.