mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-07-11 09:51:55 +00:00
Compare commits
10 Commits
chore--mou
...
raw-parsin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6df22db8b3 | ||
|
|
9c2062231a | ||
|
|
915fedb156 | ||
|
|
0eeb5b0285 | ||
|
|
feeaf339d4 | ||
|
|
35337291fa | ||
|
|
5d523a1e7b | ||
|
|
5745742c91 | ||
|
|
62f58f5307 | ||
|
|
83707e2210 |
@@ -4,7 +4,6 @@
|
||||
#include "dCommonVars.h"
|
||||
#include "Logger.h"
|
||||
#include "Game.h"
|
||||
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -368,9 +367,37 @@ public:
|
||||
}
|
||||
|
||||
template<typename AmfType = AMFArrayValue>
|
||||
AmfType& PushDebug(const std::string_view name) {
|
||||
AmfType& PushDebug(const std::string_view name, const std::string& objectType = "", const uint32_t warningLevel = 0) {
|
||||
size_t i = 0;
|
||||
for (; i < m_Dense.size(); i++) {
|
||||
const auto& cast = dynamic_cast<AMFArrayValue*>(m_Dense[i].get());
|
||||
if (!cast) continue;
|
||||
|
||||
const auto& nameValue = cast->Get<std::string>("name");
|
||||
if (!nameValue || nameValue->GetValue() != name) continue;
|
||||
|
||||
if (!objectType.empty()) {
|
||||
cast->Insert<std::string>("type", objectType);
|
||||
}
|
||||
|
||||
if (warningLevel != 0) {
|
||||
cast->Insert<double>("warningLevel", warningLevel);
|
||||
}
|
||||
|
||||
// found a duplicate, return this instead
|
||||
auto valueCast = dynamic_cast<AmfType*>(cast->Get("value"));
|
||||
if (valueCast) return *valueCast;
|
||||
}
|
||||
|
||||
auto* value = PushArray();
|
||||
value->Insert("name", name.data());
|
||||
value->Insert<std::string>("name", name.data());
|
||||
if (!objectType.empty()) {
|
||||
value->Insert<std::string>("type", objectType);
|
||||
}
|
||||
|
||||
if (warningLevel != 0) {
|
||||
value->Insert<double>("warningLevel", warningLevel);
|
||||
}
|
||||
return value->Insert<AmfType>("value", std::make_unique<AmfType>());
|
||||
}
|
||||
|
||||
|
||||
34
dCommon/NiColor.h
Normal file
34
dCommon/NiColor.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef NICOLOR_H
|
||||
#define NICOLOR_H
|
||||
|
||||
struct NiColor {
|
||||
float m_Red;
|
||||
float m_Green;
|
||||
float m_Blue;
|
||||
|
||||
constexpr NiColor(float red, float green, float blue) : m_Red(red), m_Green(green), m_Blue(blue) {}
|
||||
constexpr NiColor() : NiColor(0.0f, 0.0f, 0.0f) {}
|
||||
|
||||
/* reduce RGB files to grayscale, with or without alpha
|
||||
* using the equation given in Poynton's ColorFAQ at
|
||||
* <http://www.inforamp.net/~poynton/> // dead link
|
||||
* Copyright (c) 1998-01-04 Charles Poynton poynton at inforamp.net
|
||||
*
|
||||
* Y = 0.212671 * R + 0.715160 * G + 0.072169 * B
|
||||
*
|
||||
* We approximate this with
|
||||
*
|
||||
* Y = 0.21268 * R + 0.7151 * G + 0.07217 * B
|
||||
*
|
||||
* which can be expressed with integers as
|
||||
*
|
||||
* Y = (6969 * R + 23434 * G + 2365 * B)/32768
|
||||
*
|
||||
* The calculation is to be done in a linear colorspace.
|
||||
*
|
||||
* Other integer coefficents can be used via png_set_rgb_to_gray().
|
||||
*/
|
||||
float ToXYZ() const { return (m_Red * 0.212671f) + (m_Green * 0.71516f) + (m_Blue * 0.072169f); };
|
||||
};
|
||||
|
||||
#endif // NICOLOR_H
|
||||
166
dCommon/dClient/SceneColor.h
Normal file
166
dCommon/dClient/SceneColor.h
Normal file
@@ -0,0 +1,166 @@
|
||||
#ifndef SCENE_COLOR_H
|
||||
#define SCENE_COLOR_H
|
||||
|
||||
#include "NiColor.h"
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace SceneColor {
|
||||
// these are not random values, they are the actual template colors used by the game
|
||||
static constexpr std::array<NiColor, 146> TEMPLATE_COLORS = {{
|
||||
{ 0.5019608f, 0.5019608f, 0.5019608f },
|
||||
{ 1.0f, 0.0f, 0.0f },
|
||||
{ 0.0f, 1.0f, 0.0f },
|
||||
{ 0.0f, 0.0f, 1.0f },
|
||||
{ 1.0f, 1.0f, 0.0f },
|
||||
{ 1.0f, 0.0f, 1.0f },
|
||||
{ 0.0f, 1.0f, 1.0f },
|
||||
{ 0.5019608f, 0.0f, 1.0f },
|
||||
{ 1.0f, 0.5019608f, 0.0f },
|
||||
{ 1.0f, 0.5019608f, 0.5019608f },
|
||||
{ 0.5019608f, 0.2509804f, 0.0f },
|
||||
{ 0.5019608f, 0.0f, 0.2509804f },
|
||||
{ 0.0f, 0.5019608f, 0.2509804f },
|
||||
{ 0.2509804f, 0.0f, 0.5019608f },
|
||||
{ 0.8745098f, 0.0f, 0.2509804f },
|
||||
{ 0.2509804f, 0.8745098f, 0.5019608f },
|
||||
{ 1.0f, 0.7490196f, 0.0f },
|
||||
{ 1.0f, 0.2509804f, 0.0627451f },
|
||||
{ 0.2509804f, 0.0f, 0.8745098f },
|
||||
{ 0.7490196f, 0.0627451f, 0.0627451f },
|
||||
{ 0.0627451f, 0.7490196f, 0.0627451f },
|
||||
{ 1.0f, 0.5019608f, 1.0f },
|
||||
{ 0.9372549f, 0.8705882f, 0.8039216f },
|
||||
{ 0.8039216f, 0.5843138f, 0.4588235f },
|
||||
{ 0.9921569f, 0.8509804f, 0.7098039f },
|
||||
{ 0.4705882f, 0.8588235f, 0.8862745f },
|
||||
{ 0.5294118f, 0.6627451f, 0.4196078f },
|
||||
{ 1.0f, 0.6431373f, 0.454902f },
|
||||
{ 0.9803922f, 0.9058824f, 0.7098039f },
|
||||
{ 0.6235294f, 0.5058824f, 0.4392157f },
|
||||
{ 0.9921569f, 0.4862745f, 0.4313726f },
|
||||
{ 0.0f, 0.0f, 0.0f },
|
||||
{ 0.6745098f, 0.8980392f, 0.9333333f },
|
||||
{ 0.1215686f, 0.4588235f, 0.9960784f },
|
||||
{ 0.6352941f, 0.6352941f, 0.8156863f },
|
||||
{ 0.4f, 0.6f, 0.8f },
|
||||
{ 0.05098039f, 0.5960785f, 0.7294118f },
|
||||
{ 0.4509804f, 0.4f, 0.7411765f },
|
||||
{ 0.8705882f, 0.3647059f, 0.5137255f },
|
||||
{ 0.7960784f, 0.254902f, 0.3294118f },
|
||||
{ 0.7058824f, 0.4039216f, 0.3019608f },
|
||||
{ 1.0f, 0.4980392f, 0.2862745f },
|
||||
{ 0.9176471f, 0.4941176f, 0.3647059f },
|
||||
{ 0.6901961f, 0.7176471f, 0.7764706f },
|
||||
{ 1.0f, 1.0f, 0.6f },
|
||||
{ 0.1098039f, 0.827451f, 0.6352941f },
|
||||
{ 1.0f, 0.6666667f, 0.8f },
|
||||
{ 0.8666667f, 0.2666667f, 0.572549f },
|
||||
{ 0.1137255f, 0.6745098f, 0.8392157f },
|
||||
{ 0.7372549f, 0.3647059f, 0.345098f },
|
||||
{ 0.8666667f, 0.5803922f, 0.4588235f },
|
||||
{ 0.6039216f, 0.8078431f, 0.9215686f },
|
||||
{ 1.0f, 0.7372549f, 0.8509804f },
|
||||
{ 0.9921569f, 0.8588235f, 0.427451f },
|
||||
{ 0.1686275f, 0.4235294f, 0.7686275f },
|
||||
{ 0.9372549f, 0.8039216f, 0.7215686f },
|
||||
{ 0.4313726f, 0.3176471f, 0.3764706f },
|
||||
{ 0.8078431f, 1.0f, 0.1137255f },
|
||||
{ 0.427451f, 0.682353f, 0.5058824f },
|
||||
{ 0.7647059f, 0.3921569f, 0.772549f },
|
||||
{ 0.8f, 0.4f, 0.4f },
|
||||
{ 0.9058824f, 0.7764706f, 0.5921569f },
|
||||
{ 0.9882353f, 0.8509804f, 0.4588235f },
|
||||
{ 0.6588235f, 0.8941177f, 0.627451f },
|
||||
{ 0.5843138f, 0.5686275f, 0.5490196f },
|
||||
{ 0.1098039f, 0.6745098f, 0.4705882f },
|
||||
{ 0.06666667f, 0.3921569f, 0.7058824f },
|
||||
{ 0.9411765f, 0.9098039f, 0.5686275f },
|
||||
{ 1.0f, 0.1137255f, 0.8078431f },
|
||||
{ 0.6980392f, 0.9254902f, 0.3647059f },
|
||||
{ 0.3647059f, 0.4627451f, 0.7960784f },
|
||||
{ 0.7921569f, 0.2156863f, 0.4039216f },
|
||||
{ 0.2313726f, 0.6901961f, 0.5607843f },
|
||||
{ 0.9882353f, 0.7058824f, 0.8352941f },
|
||||
{ 1.0f, 0.9568627f, 0.3098039f },
|
||||
{ 1.0f, 0.7411765f, 0.5333334f },
|
||||
{ 0.9647059f, 0.3921569f, 0.6862745f },
|
||||
{ 0.6666667f, 0.9411765f, 0.8196079f },
|
||||
{ 0.8039216f, 0.2901961f, 0.2980392f },
|
||||
{ 0.9294118f, 0.8196079f, 0.6117647f },
|
||||
{ 0.5921569f, 0.6039216f, 0.6666667f },
|
||||
{ 0.7843137f, 0.2196078f, 0.3529412f },
|
||||
{ 0.9372549f, 0.5960785f, 0.6666667f },
|
||||
{ 0.9921569f, 0.7372549f, 0.7058824f },
|
||||
{ 0.1019608f, 0.282353f, 0.4627451f },
|
||||
{ 0.1882353f, 0.7294118f, 0.5607843f },
|
||||
{ 0.772549f, 0.2941177f, 0.5490196f },
|
||||
{ 0.09803922f, 0.454902f, 0.8235294f },
|
||||
{ 0.7294118f, 0.7215686f, 0.4235294f },
|
||||
{ 1.0f, 0.4588235f, 0.2196078f },
|
||||
{ 1.0f, 0.1686275f, 0.1686275f },
|
||||
{ 0.972549f, 0.8352941f, 0.4078431f },
|
||||
{ 0.9019608f, 0.6588235f, 0.8431373f },
|
||||
{ 0.254902f, 0.2901961f, 0.2980392f },
|
||||
{ 1.0f, 0.4313726f, 0.2901961f },
|
||||
{ 0.1098039f, 0.6627451f, 0.7882353f },
|
||||
{ 1.0f, 0.8117647f, 0.6705883f },
|
||||
{ 0.772549f, 0.8156863f, 0.9019608f },
|
||||
{ 0.9921569f, 0.8666667f, 0.9019608f },
|
||||
{ 0.08235294f, 0.5019608f, 0.4705882f },
|
||||
{ 0.9882353f, 0.454902f, 0.9921569f },
|
||||
{ 0.9686275f, 0.5607843f, 0.654902f },
|
||||
{ 0.5568628f, 0.2705882f, 0.5215687f },
|
||||
{ 0.454902f, 0.2588235f, 0.7843137f },
|
||||
{ 0.6156863f, 0.5058824f, 0.7294118f },
|
||||
{ 1.0f, 0.2862745f, 0.4235294f },
|
||||
{ 0.8392157f, 0.5411765f, 0.3490196f },
|
||||
{ 0.4431373f, 0.2941177f, 0.1372549f },
|
||||
{ 1.0f, 0.282353f, 0.8156863f },
|
||||
{ 0.9333333f, 0.1254902f, 0.3019608f },
|
||||
{ 1.0f, 0.3254902f, 0.2862745f },
|
||||
{ 0.7529412f, 0.2666667f, 0.5607843f },
|
||||
{ 0.1215686f, 0.8078431f, 0.7960784f },
|
||||
{ 0.4705882f, 0.3176471f, 0.6627451f },
|
||||
{ 1.0f, 0.6078432f, 0.6666667f },
|
||||
{ 0.9882353f, 0.1568628f, 0.2784314f },
|
||||
{ 0.4627451f, 1.0f, 0.4784314f },
|
||||
{ 0.6235294f, 0.8862745f, 0.7490196f },
|
||||
{ 0.6470588f, 0.4117647f, 0.3098039f },
|
||||
{ 0.5411765f, 0.4745098f, 0.3647059f },
|
||||
{ 0.2705882f, 0.8078431f, 0.6352941f },
|
||||
{ 0.8039216f, 0.772549f, 0.7607843f },
|
||||
{ 0.5019608f, 0.854902f, 0.9215686f },
|
||||
{ 0.9254902f, 0.9176471f, 0.7450981f },
|
||||
{ 1.0f, 0.8117647f, 0.282353f },
|
||||
{ 0.9921569f, 0.3686275f, 0.3254902f },
|
||||
{ 0.9803922f, 0.654902f, 0.4235294f },
|
||||
{ 0.09411765f, 0.654902f, 0.7098039f },
|
||||
{ 0.9215686f, 0.7803922f, 0.8745098f },
|
||||
{ 0.9882353f, 0.5372549f, 0.6745098f },
|
||||
{ 0.8588235f, 0.8431373f, 0.8235294f },
|
||||
{ 0.8705882f, 0.6666667f, 0.5333334f },
|
||||
{ 0.4666667f, 0.8666667f, 0.9058824f },
|
||||
{ 1.0f, 1.0f, 0.4f },
|
||||
{ 0.572549f, 0.4313726f, 0.682353f },
|
||||
{ 0.1960784f, 0.2901961f, 0.6980392f },
|
||||
{ 0.9686275f, 0.3254902f, 0.5803922f },
|
||||
{ 1.0f, 0.627451f, 0.5372549f },
|
||||
{ 0.5607843f, 0.3137255f, 0.6156863f },
|
||||
{ 1.0f, 1.0f, 1.0f },
|
||||
{ 0.6352941f, 0.6784314f, 0.8156863f },
|
||||
{ 0.9882353f, 0.4235294f, 0.5215687f },
|
||||
{ 0.8039216f, 0.6431373f, 0.8705882f },
|
||||
{ 0.9882353f, 0.9098039f, 0.5137255f },
|
||||
{ 0.772549f, 0.8901961f, 0.5176471f },
|
||||
{ 1.0f, 0.682353f, 0.2588235f },
|
||||
}};
|
||||
|
||||
static constexpr NiColor FALLBACK_COLOR{ 1.0f, 1.0f, 1.0f };
|
||||
|
||||
inline const NiColor& Get(uint8_t index) {
|
||||
return (index < TEMPLATE_COLORS.size()) ? TEMPLATE_COLORS[index] : FALLBACK_COLOR;
|
||||
}
|
||||
} // namespace SceneColor
|
||||
|
||||
#endif // SCENE_COLOR_H
|
||||
@@ -1019,6 +1019,7 @@ void Entity::WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacke
|
||||
const bool hasParent = m_ParentEntity != nullptr || m_SpawnerID != 0;
|
||||
outBitStream.Write(hasParent);
|
||||
if (hasParent) {
|
||||
// 触るな!
|
||||
if (m_ParentEntity != nullptr) outBitStream.Write(GeneralUtils::SetBit(m_ParentEntity->GetObjectID(), static_cast<uint32_t>(eObjectBits::CLIENT)));
|
||||
else if (m_Spawner != nullptr && m_Spawner->m_Info.isNetwork) outBitStream.Write(m_SpawnerID);
|
||||
else outBitStream.Write(GeneralUtils::SetBit(m_SpawnerID, static_cast<uint32_t>(eObjectBits::CLIENT)));
|
||||
@@ -2235,11 +2236,16 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::RequestServerObjectInfo& r
|
||||
|
||||
const auto& objTableInfo = table->GetByID(GetLOT());
|
||||
|
||||
objectInfo.PushDebug<AMFStringValue>("Name") = objTableInfo.name;
|
||||
objectInfo.PushDebug<AMFIntValue>("Template ID(LOT)") = GetLOT();
|
||||
objectInfo.PushDebug<AMFStringValue>("Object ID") = std::to_string(GetObjectID());
|
||||
objectInfo.PushDebug<AMFStringValue>("Spawner's Object ID") = std::to_string(GetSpawnerID());
|
||||
objectInfo.PushDebug<AMFStringValue>("Owner override") = std::to_string(m_OwnerOverride);
|
||||
objectInfo.PushDebug<AMFStringValue>("Name", "name") = objTableInfo.name;
|
||||
objectInfo.PushDebug<AMFIntValue>("Template ID(LOT)", "LOT") = GetLOT();
|
||||
objectInfo.PushDebug<AMFStringValue>("Object ID", "LWOOBJID") = std::to_string(GetObjectID());
|
||||
objectInfo.PushDebug<AMFStringValue>("Spawner's Object ID", "LWOOBJID") = std::to_string(GetSpawnerID());
|
||||
objectInfo.PushDebug<AMFStringValue>("Owner override", "LWOOBJID") = std::to_string(m_OwnerOverride);
|
||||
auto& children = objectInfo.PushDebug("Child Objects");
|
||||
int i = 1;
|
||||
for (const auto* child : m_ChildEntities) {
|
||||
if (child) children.PushDebug<AMFStringValue>("Child " + std::to_string(i++), "LWOOBJID") = std::to_string(child->GetObjectID());
|
||||
}
|
||||
|
||||
auto& componentDetails = objectInfo.PushDebug("Component Information");
|
||||
for (const auto [id, component] : m_Components) {
|
||||
|
||||
@@ -521,6 +521,10 @@ void BaseCombatAIComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsI
|
||||
|
||||
void BaseCombatAIComponent::SetAiState(AiState newState) {
|
||||
if (newState == this->m_State) return;
|
||||
GameMessages::NotifyCombatAIStateChange stateMsg;
|
||||
stateMsg.prevState = this->m_State;
|
||||
stateMsg.newState = newState;
|
||||
m_Parent->HandleMsg(stateMsg);
|
||||
this->m_State = newState;
|
||||
m_DirtyStateOrTarget = true;
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
@@ -860,7 +864,7 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport
|
||||
auto& cmptType = reportInfo.info->PushDebug("Base Combat AI");
|
||||
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
auto& targetInfo = cmptType.PushDebug("Current Target Info");
|
||||
targetInfo.PushDebug<AMFStringValue>("Current Target ID") = std::to_string(m_Target);
|
||||
targetInfo.PushDebug<AMFStringValue>("Current Target ID", "LWOOBJID") = std::to_string(m_Target);
|
||||
// if (m_Target != LWOOBJID_EMPTY) {
|
||||
// LWOGameMessages::ObjGetName nameMsg(m_CurrentTarget);
|
||||
// SEND_GAMEOBJ_MSG(nameMsg);
|
||||
@@ -901,10 +905,7 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport
|
||||
//}
|
||||
//cmptType.PushDebug("Current Combat Role") = curState;
|
||||
|
||||
auto& tetherPoint = cmptType.PushDebug("Tether Point");
|
||||
tetherPoint.PushDebug<AMFDoubleValue>("X") = m_StartPosition.x;
|
||||
tetherPoint.PushDebug<AMFDoubleValue>("Y") = m_StartPosition.y;
|
||||
tetherPoint.PushDebug<AMFDoubleValue>("Z") = m_StartPosition.z;
|
||||
cmptType.PushDebug("Tether Point").PushDebug(m_StartPosition);
|
||||
cmptType.PushDebug<AMFDoubleValue>("Hard Tether Radius") = m_HardTetherRadius;
|
||||
cmptType.PushDebug<AMFDoubleValue>("Soft Tether Radius") = m_SoftTetherRadius;
|
||||
cmptType.PushDebug<AMFDoubleValue>("Aggro Radius") = m_AggroRadius;
|
||||
|
||||
@@ -361,17 +361,11 @@ void ControllablePhysicsComponent::SetStunImmunity(
|
||||
|
||||
bool ControllablePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
PhysicsComponent::OnGetObjectReportInfo(reportInfo);
|
||||
auto& info = reportInfo.subCategory->PushDebug("Controllable Info");
|
||||
auto& info = reportInfo.subCategory->PushDebug("Controllable Physics");
|
||||
|
||||
auto& vel = info.PushDebug("Velocity");
|
||||
vel.PushDebug<AMFDoubleValue>("x") = m_Velocity.x;
|
||||
vel.PushDebug<AMFDoubleValue>("y") = m_Velocity.y;
|
||||
vel.PushDebug<AMFDoubleValue>("z") = m_Velocity.z;
|
||||
info.PushDebug("Velocity").PushDebug(m_Velocity);
|
||||
|
||||
auto& angularVelocity = info.PushDebug("Angular Velocity");
|
||||
angularVelocity.PushDebug<AMFDoubleValue>("x") = m_AngularVelocity.x;
|
||||
angularVelocity.PushDebug<AMFDoubleValue>("y") = m_AngularVelocity.y;
|
||||
angularVelocity.PushDebug<AMFDoubleValue>("z") = m_AngularVelocity.z;
|
||||
info.PushDebug("Angular Velocity").PushDebug(m_AngularVelocity);
|
||||
|
||||
info.PushDebug<AMFBoolValue>("Is On Ground") = m_IsOnGround;
|
||||
info.PushDebug<AMFBoolValue>("Is On Rail") = m_IsOnRail;
|
||||
@@ -403,12 +397,13 @@ bool ControllablePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObject
|
||||
info.PushDebug<AMFBoolValue>("Is In Bubble") = m_IsInBubble;
|
||||
info.PushDebug<AMFStringValue>("Bubble Type") = StringifiedEnum::ToString(m_BubbleType).data();
|
||||
info.PushDebug<AMFBoolValue>("Special Anims") = m_SpecialAnims;
|
||||
info.PushDebug<AMFIntValue>("Immune To Stun Attack Count") = m_ImmuneToStunAttackCount;
|
||||
info.PushDebug<AMFIntValue>("Immune To Stun Equip Count") = m_ImmuneToStunEquipCount;
|
||||
info.PushDebug<AMFIntValue>("Immune To Stun Interact Count") = m_ImmuneToStunInteractCount;
|
||||
info.PushDebug<AMFIntValue>("Immune To Stun Jump Count") = m_ImmuneToStunJumpCount;
|
||||
info.PushDebug<AMFIntValue>("Immune To Stun Move Count") = m_ImmuneToStunMoveCount;
|
||||
info.PushDebug<AMFIntValue>("Immune To Stun Turn Count") = m_ImmuneToStunTurnCount;
|
||||
info.PushDebug<AMFIntValue>("Immune To Stun UseItem Count") = m_ImmuneToStunUseItemCount;
|
||||
auto& immunity = info.PushDebug("Immunity Effects");
|
||||
immunity.PushDebug<AMFBoolValue>("Immune to Stun Move") = m_ImmuneToStunMoveCount != 0;
|
||||
immunity.PushDebug<AMFBoolValue>("Immune to Stun Turn") = m_ImmuneToStunTurnCount != 0;
|
||||
immunity.PushDebug<AMFBoolValue>("Immune to Stun Attack") = m_ImmuneToStunAttackCount != 0;
|
||||
immunity.PushDebug<AMFBoolValue>("Immune to Stun Use Item") = m_ImmuneToStunUseItemCount != 0;
|
||||
immunity.PushDebug<AMFBoolValue>("Immune to Stun Equip") = m_ImmuneToStunEquipCount != 0;
|
||||
immunity.PushDebug<AMFBoolValue>("Immune to Stun Interact") = m_ImmuneToStunInteractCount != 0;
|
||||
immunity.PushDebug<AMFBoolValue>("Immune to Stun Jump") = m_ImmuneToStunJumpCount != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -328,7 +328,7 @@ private:
|
||||
/**
|
||||
* The effect that plays while using the jetpack
|
||||
*/
|
||||
int32_t m_JetpackEffectID;
|
||||
int32_t m_JetpackEffectID{};
|
||||
|
||||
/**
|
||||
* The current speed multiplier, allowing an entity to run faster
|
||||
|
||||
@@ -1146,7 +1146,7 @@ bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportIn
|
||||
destroyableInfo.PushDebug<AMFDoubleValue>("Explode Factor") = m_ExplodeFactor;
|
||||
destroyableInfo.PushDebug<AMFBoolValue>("Has Threats") = m_HasThreats;
|
||||
|
||||
destroyableInfo.PushDebug<AMFStringValue>("Killer ID") = std::to_string(m_KillerID);
|
||||
destroyableInfo.PushDebug<AMFStringValue>("Killer ID", "LWOOBJID") = std::to_string(m_KillerID);
|
||||
|
||||
// "Scripts"; idk what to do about scripts yet
|
||||
auto& immuneCounts = destroyableInfo.PushDebug("Immune Counts");
|
||||
|
||||
@@ -108,7 +108,7 @@ bool GhostComponent::OnGetGMInvis(GameMessages::GetGMInvis& gmInvisMsg) {
|
||||
bool GhostComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportMsg) {
|
||||
auto& cmptType = reportMsg.info->PushDebug("Ghost");
|
||||
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
cmptType.PushDebug<AMFBoolValue>("Is GM Invis") = false;
|
||||
cmptType.PushDebug<AMFBoolValue>("Is GM Invis") = m_IsGMInvisible;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -110,15 +110,9 @@ bool HavokVehiclePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObject
|
||||
|
||||
auto& info = reportInfo.subCategory->PushDebug("Havok Vehicle Physics Info");
|
||||
|
||||
auto& velocity = info.PushDebug("Velocity");
|
||||
velocity.PushDebug<AMFDoubleValue>("x") = m_Velocity.x;
|
||||
velocity.PushDebug<AMFDoubleValue>("y") = m_Velocity.y;
|
||||
velocity.PushDebug<AMFDoubleValue>("z") = m_Velocity.z;
|
||||
auto& velocity = info.PushDebug("Velocity").PushDebug(m_Velocity);
|
||||
|
||||
auto& angularVelocity = info.PushDebug("Angular Velocity");
|
||||
angularVelocity.PushDebug<AMFDoubleValue>("x") = m_AngularVelocity.x;
|
||||
angularVelocity.PushDebug<AMFDoubleValue>("y") = m_AngularVelocity.y;
|
||||
angularVelocity.PushDebug<AMFDoubleValue>("z") = m_AngularVelocity.z;
|
||||
auto& angularVelocity = info.PushDebug("Angular Velocity").PushDebug(m_AngularVelocity);
|
||||
|
||||
info.PushDebug<AMFBoolValue>("Is On Ground") = m_IsOnGround;
|
||||
info.PushDebug<AMFBoolValue>("Is On Rail") = m_IsOnRail;
|
||||
|
||||
@@ -723,10 +723,6 @@ void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool b
|
||||
for (const auto& pair : m_Equipped) {
|
||||
const auto item = pair.second;
|
||||
|
||||
if (bIsInitialUpdate) {
|
||||
AddItemSkills(item.lot);
|
||||
}
|
||||
|
||||
outBitStream.Write(item.id);
|
||||
outBitStream.Write(item.lot);
|
||||
|
||||
@@ -1151,14 +1147,12 @@ LOT InventoryComponent::GetConsumable() const {
|
||||
void InventoryComponent::AddItemSkills(const LOT lot) {
|
||||
const auto info = Inventory::FindItemComponent(lot);
|
||||
|
||||
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
|
||||
const auto slot = FindBehaviorSlot(info.equipLocation);
|
||||
|
||||
if (slot == BehaviorSlot::Invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = m_Skills.find(slot);
|
||||
|
||||
const auto skill = FindSkill(lot);
|
||||
|
||||
SetSkill(slot, skill);
|
||||
@@ -1186,7 +1180,7 @@ void InventoryComponent::FixInvisibleItems() {
|
||||
void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||
const auto info = Inventory::FindItemComponent(lot);
|
||||
|
||||
const auto slot = FindBehaviorSlot(static_cast<eItemType>(info.itemType));
|
||||
const auto slot = FindBehaviorSlot(info.equipLocation);
|
||||
|
||||
if (slot == BehaviorSlot::Invalid) {
|
||||
return;
|
||||
@@ -1198,15 +1192,31 @@ void InventoryComponent::RemoveItemSkills(const LOT lot) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto old = index->second;
|
||||
const auto skillId = FindSkill(lot);
|
||||
|
||||
GameMessages::SendRemoveSkill(m_Parent, old);
|
||||
// Only act on this slot if it still holds the skill from this item.
|
||||
// Another item may have overwritten the slot since this one was equipped.
|
||||
if (index->second != skillId) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_Skills.erase(slot);
|
||||
|
||||
// Find another slot that still holds this skillID (if any).
|
||||
const auto surviving = std::ranges::find_if(m_Skills, [skillId](const auto& pair) {
|
||||
return pair.second == skillId;
|
||||
});
|
||||
|
||||
// The client stores one acquiredSkillsInfo entry per skillID, tagged with the slotID
|
||||
// it was originally added with. Always send RemoveSkill to clear that entry, then
|
||||
// re-add with the surviving slot so the client shows it in the correct place.
|
||||
GameMessages::SendRemoveSkill(m_Parent, skillId);
|
||||
if (surviving != m_Skills.end()) {
|
||||
GameMessages::SendAddSkill(m_Parent, skillId, surviving->first);
|
||||
}
|
||||
|
||||
if (slot == BehaviorSlot::Primary) {
|
||||
m_Skills.insert_or_assign(BehaviorSlot::Primary, 1);
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, 1, BehaviorSlot::Primary);
|
||||
}
|
||||
}
|
||||
@@ -1298,23 +1308,17 @@ void InventoryComponent::RemoveDatabasePet(LWOOBJID id) {
|
||||
m_Pets.erase(id);
|
||||
}
|
||||
|
||||
BehaviorSlot InventoryComponent::FindBehaviorSlot(const eItemType type) {
|
||||
switch (type) {
|
||||
case eItemType::HAT:
|
||||
return BehaviorSlot::Head;
|
||||
case eItemType::NECK:
|
||||
return BehaviorSlot::Neck;
|
||||
case eItemType::LEFT_HAND:
|
||||
return BehaviorSlot::Offhand;
|
||||
case eItemType::RIGHT_HAND:
|
||||
return BehaviorSlot::Primary;
|
||||
case eItemType::CONSUMABLE:
|
||||
return BehaviorSlot::Consumable;
|
||||
default:
|
||||
return BehaviorSlot::Invalid;
|
||||
}
|
||||
BehaviorSlot InventoryComponent::FindBehaviorSlot(const std::string& equipLocation) {
|
||||
// Skill slot is determined by equipLocation, not itemType.
|
||||
// Mapping confirmed against live captures and client data (issue #1339).
|
||||
if (equipLocation == "special_r") return BehaviorSlot::Primary;
|
||||
if (equipLocation == "hair") return BehaviorSlot::Head;
|
||||
if (equipLocation == "special_l") return BehaviorSlot::Offhand;
|
||||
if (equipLocation == "clavicle") return BehaviorSlot::Neck;
|
||||
return BehaviorSlot::Invalid;
|
||||
}
|
||||
|
||||
|
||||
bool InventoryComponent::IsTransferInventory(eInventoryType type, bool includeVault) {
|
||||
return type == VENDOR_BUYBACK || (includeVault && (type == VAULT_ITEMS || type == VAULT_MODELS)) || type == TEMP_ITEMS || type == TEMP_MODELS || type == MODELS_IN_BBB;
|
||||
}
|
||||
@@ -1655,10 +1659,28 @@ bool InventoryComponent::SetSkill(BehaviorSlot slot, uint32_t skillId) {
|
||||
const auto index = m_Skills.find(slot);
|
||||
if (index != m_Skills.end()) {
|
||||
const auto old = index->second;
|
||||
GameMessages::SendRemoveSkill(m_Parent, old);
|
||||
// Only remove the old skill from the client if no other slot still holds it.
|
||||
// The client's acquiredSkillsInfo is keyed by skillID (one entry per skill),
|
||||
// so RemoveSkill clears it globally — sending it while another slot still uses
|
||||
// the same skillID would break that slot on the client.
|
||||
const auto usedElsewhere = std::ranges::any_of(m_Skills, [&](const auto& pair) {
|
||||
return pair.first != slot && pair.second == old;
|
||||
});
|
||||
if (!usedElsewhere) {
|
||||
GameMessages::SendRemoveSkill(m_Parent, old);
|
||||
}
|
||||
}
|
||||
|
||||
// Only send AddSkill if the client doesn't already know about this skillID.
|
||||
// The client early-exits on duplicate AddSkill (same skillID already in
|
||||
// acquiredSkillsInfo) without updating the slot — so only send when it's new.
|
||||
const auto alreadyKnown = std::ranges::any_of(m_Skills, [&](const auto& pair) {
|
||||
return pair.first != slot && pair.second == skillId;
|
||||
});
|
||||
if (!alreadyKnown) {
|
||||
GameMessages::SendAddSkill(m_Parent, skillId, slot);
|
||||
}
|
||||
|
||||
GameMessages::SendAddSkill(m_Parent, skillId, slot);
|
||||
m_Skills.insert_or_assign(slot, skillId);
|
||||
return true;
|
||||
}
|
||||
@@ -1838,9 +1860,9 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo
|
||||
std::stringstream ss;
|
||||
ss << "%[Objects_" << item->GetLot() << "_name] Slot " << item->GetSlot();
|
||||
auto& slot = curInv.PushDebug(ss.str());
|
||||
slot.PushDebug<AMFStringValue>("Object ID") = std::to_string(item->GetId());
|
||||
slot.PushDebug<AMFIntValue>("LOT") = item->GetLot();
|
||||
if (item->GetSubKey() != LWOOBJID_EMPTY) slot.PushDebug<AMFStringValue>("Subkey") = std::to_string(item->GetSubKey());
|
||||
slot.PushDebug<AMFStringValue>("Object ID", "LWOOBJID") = std::to_string(item->GetId());
|
||||
slot.PushDebug<AMFIntValue>("LOT", "LOT") = item->GetLot();
|
||||
if (item->GetSubKey() != LWOOBJID_EMPTY) slot.PushDebug<AMFStringValue>("Subkey", "LWOOBJID") = std::to_string(item->GetSubKey());
|
||||
slot.PushDebug<AMFIntValue>("Count") = item->GetCount();
|
||||
slot.PushDebug<AMFIntValue>("Slot") = item->GetSlot();
|
||||
slot.PushDebug<AMFBoolValue>("Bind on pickup") = item->GetInfo().isBOP;
|
||||
@@ -1859,7 +1881,8 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo
|
||||
ss << "%[Objects_" << info.lot << "_name]";
|
||||
auto& equipSlot = equipped.PushDebug(ss.str());
|
||||
equipSlot.PushDebug<AMFStringValue>("Location") = location;
|
||||
equipSlot.PushDebug<AMFStringValue>("Object ID") = std::to_string(info.id);
|
||||
equipSlot.PushDebug<AMFStringValue>("Object ID", "LWOOBJID") = std::to_string(info.id);
|
||||
equipSlot.PushDebug<AMFIntValue>("LOT", "LOT") = info.lot;
|
||||
equipSlot.PushDebug<AMFIntValue>("Slot") = info.slot;
|
||||
equipSlot.PushDebug<AMFIntValue>("Count") = info.count;
|
||||
auto& extra = equipSlot.PushDebug("Extra Info");
|
||||
|
||||
@@ -367,11 +367,10 @@ public:
|
||||
void RemoveDatabasePet(LWOOBJID id);
|
||||
|
||||
/**
|
||||
* Returns the current behavior slot active for the passed item type
|
||||
* @param type the item type to find the behavior slot for
|
||||
* @return the current behavior slot active for the passed item type
|
||||
* Returns the behavior slot for the given equipLocation string.
|
||||
* This is the authoritative mapping used for skill slot assignment.
|
||||
*/
|
||||
static BehaviorSlot FindBehaviorSlot(eItemType type);
|
||||
static BehaviorSlot FindBehaviorSlot(const std::string& equipLocation);
|
||||
|
||||
/**
|
||||
* Checks if the inventory type is a temp inventory
|
||||
@@ -403,6 +402,8 @@ public:
|
||||
|
||||
std::map<BehaviorSlot, uint32_t> GetSkills() { return m_Skills; };
|
||||
|
||||
void ClearSkills() { m_Skills.clear(); };
|
||||
|
||||
bool SetSkill(int slot, uint32_t skillId);
|
||||
bool SetSkill(BehaviorSlot slot, uint32_t skillId);
|
||||
|
||||
|
||||
@@ -652,7 +652,7 @@ void PushMissions(const std::map<uint32_t, Mission*>& missions, AMFArrayValue& V
|
||||
}
|
||||
|
||||
bool MissionComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
auto& missionInfo = reportInfo.info->PushDebug("Mission (Laggy)");
|
||||
auto& missionInfo = reportInfo.info->PushDebug("Mission (Laggy)", "", 1);
|
||||
missionInfo.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
// Sort the missions so they are easier to parse and present to the end user
|
||||
std::map<uint32_t, Mission*> achievements;
|
||||
|
||||
@@ -355,8 +355,8 @@ bool ModelComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& re
|
||||
|
||||
cmptInfo.PushDebug<AMFStringValue>("Name") = "Objects_" + std::to_string(m_Parent->GetLOT()) + "_name";
|
||||
cmptInfo.PushDebug<AMFBoolValue>("Has Unique Name") = false;
|
||||
cmptInfo.PushDebug<AMFStringValue>("UGID (from item)") = std::to_string(m_userModelID);
|
||||
cmptInfo.PushDebug<AMFStringValue>("UGID") = std::to_string(m_userModelID);
|
||||
cmptInfo.PushDebug<AMFStringValue>("UGID (from item)", "LWOOBJID") = std::to_string(m_userModelID);
|
||||
cmptInfo.PushDebug<AMFStringValue>("UGID", "LWOOBJID") = std::to_string(m_userModelID);
|
||||
cmptInfo.PushDebug<AMFStringValue>("Description") = "";
|
||||
cmptInfo.PushDebug<AMFIntValue>("Behavior Count") = m_Behaviors.size();
|
||||
|
||||
|
||||
@@ -535,20 +535,14 @@ bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf
|
||||
movementInfo.PushDebug<AMFDoubleValue>("Pulling To Point") = m_PullingToPoint;
|
||||
movementInfo.PushDebug<AMFBoolValue>("At Final Waypoint") = m_AtFinalWaypoint;
|
||||
|
||||
auto& pullPointInfo = movementInfo.PushDebug("Pull Point");
|
||||
pullPointInfo.PushDebug<AMFDoubleValue>("X") = m_PullPoint.x;
|
||||
pullPointInfo.PushDebug<AMFDoubleValue>("Y") = m_PullPoint.y;
|
||||
pullPointInfo.PushDebug<AMFDoubleValue>("Z") = m_PullPoint.z;
|
||||
auto& pullPointInfo = movementInfo.PushDebug("Pull Point").PushDebug(m_PullPoint);
|
||||
|
||||
// movementInfo.PushDebug<AMFDoubleValue>("Delay") = m_Delay;
|
||||
|
||||
auto& waypoints = movementInfo.PushDebug("Interpolated Waypoints");
|
||||
int i = 0;
|
||||
for (const auto& point : m_InterpolatedWaypoints) {
|
||||
auto& waypoint = waypoints.PushDebug("Waypoint " + std::to_string(++i));
|
||||
waypoint.PushDebug<AMFDoubleValue>("X") = point.x;
|
||||
waypoint.PushDebug<AMFDoubleValue>("Y") = point.y;
|
||||
waypoint.PushDebug<AMFDoubleValue>("Z") = point.z;
|
||||
waypoints.PushDebug("Waypoint " + std::to_string(++i)).PushDebug(point);
|
||||
}
|
||||
|
||||
i = 0;
|
||||
@@ -556,10 +550,7 @@ bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf
|
||||
auto pathCopy = m_CurrentPath; // Copy to avoid modifying the original stack
|
||||
while (!pathCopy.empty()) {
|
||||
const auto& waypoint = pathCopy.top();
|
||||
auto& pathWaypoint = currentPath.PushDebug("Waypoint " + std::to_string(++i));
|
||||
pathWaypoint.PushDebug<AMFDoubleValue>("X") = waypoint.position.x;
|
||||
pathWaypoint.PushDebug<AMFDoubleValue>("Y") = waypoint.position.y;
|
||||
pathWaypoint.PushDebug<AMFDoubleValue>("Z") = waypoint.position.z;
|
||||
currentPath.PushDebug("Waypoint " + std::to_string(++i)).PushDebug(waypoint.position);
|
||||
|
||||
pathCopy.pop();
|
||||
}
|
||||
|
||||
@@ -238,10 +238,7 @@ bool PhantomPhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectRepor
|
||||
info.PushDebug<AMFIntValue>("Effect Type") = static_cast<int>(m_EffectType);
|
||||
info.PushDebug<AMFDoubleValue>("Directional Multiplier") = m_DirectionalMultiplier;
|
||||
info.PushDebug<AMFBoolValue>("Is Directional") = m_IsDirectional;
|
||||
auto& direction = info.PushDebug("Direction");
|
||||
direction.PushDebug<AMFDoubleValue>("x") = m_Direction.x;
|
||||
direction.PushDebug<AMFDoubleValue>("y") = m_Direction.y;
|
||||
direction.PushDebug<AMFDoubleValue>("z") = m_Direction.z;
|
||||
auto& direction = info.PushDebug("Direction").PushDebug(m_Direction);
|
||||
|
||||
if (m_MinMax) {
|
||||
auto& minMaxInfo = info.PushDebug("Min Max Info");
|
||||
@@ -252,15 +249,8 @@ bool PhantomPhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectRepor
|
||||
if (m_IsRespawnVolume) {
|
||||
auto& respawnInfo = info.PushDebug("Respawn Info");
|
||||
respawnInfo.PushDebug<AMFBoolValue>("Is Respawn Volume") = m_IsRespawnVolume;
|
||||
auto& respawnPos = respawnInfo.PushDebug("Respawn Position");
|
||||
respawnPos.PushDebug<AMFDoubleValue>("x") = m_RespawnPos.x;
|
||||
respawnPos.PushDebug<AMFDoubleValue>("y") = m_RespawnPos.y;
|
||||
respawnPos.PushDebug<AMFDoubleValue>("z") = m_RespawnPos.z;
|
||||
auto& respawnRot = respawnInfo.PushDebug("Respawn Rotation");
|
||||
respawnRot.PushDebug<AMFDoubleValue>("w") = m_RespawnRot.w;
|
||||
respawnRot.PushDebug<AMFDoubleValue>("x") = m_RespawnRot.x;
|
||||
respawnRot.PushDebug<AMFDoubleValue>("y") = m_RespawnRot.y;
|
||||
respawnRot.PushDebug<AMFDoubleValue>("z") = m_RespawnRot.z;
|
||||
respawnInfo.PushDebug("Respawn Position").PushDebug(m_RespawnPos);
|
||||
respawnInfo.PushDebug("Respawn Rotation").PushDebug(m_RespawnRot);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -249,18 +249,11 @@ bool PhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo&
|
||||
auto& info = reportInfo.info->PushDebug("Physics");
|
||||
reportInfo.subCategory = &info;
|
||||
|
||||
auto& pos = info.PushDebug("Position");
|
||||
pos.PushDebug<AMFDoubleValue>("x") = m_Position.x;
|
||||
pos.PushDebug<AMFDoubleValue>("y") = m_Position.y;
|
||||
pos.PushDebug<AMFDoubleValue>("z") = m_Position.z;
|
||||
auto& pos = info.PushDebug("Position").PushDebug(m_Position);
|
||||
|
||||
auto& rot = info.PushDebug("Rotation");
|
||||
rot.PushDebug<AMFDoubleValue>("w") = m_Rotation.w;
|
||||
rot.PushDebug<AMFDoubleValue>("x") = m_Rotation.x;
|
||||
rot.PushDebug<AMFDoubleValue>("y") = m_Rotation.y;
|
||||
rot.PushDebug<AMFDoubleValue>("z") = m_Rotation.z;
|
||||
auto& rot = info.PushDebug("Rotation").PushDebug(m_Rotation);
|
||||
|
||||
info.PushDebug<AMFIntValue>("CollisionGroup") = m_CollisionGroup;
|
||||
info.PushDebug<AMFIntValue>("Collision Group") = m_CollisionGroup;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -80,8 +80,9 @@ bool ProximityMonitorComponent::OnGetObjectReportInfo(GameMessages::GetObjectRep
|
||||
proxAmf.PushDebug("Position").PushDebug(entity->GetPosition());
|
||||
proxAmf.PushDebug("Rotation").PushDebug(entity->GetRotation());
|
||||
auto& collidingAmf = proxAmf.PushDebug("Colliding Objects");
|
||||
int i = 1;
|
||||
for (const auto& colliding : entity->GetCurrentlyCollidingObjects()) {
|
||||
collidingAmf.PushDebug(std::to_string(colliding));
|
||||
collidingAmf.PushDebug<AMFStringValue>(std::to_string(i++), "LWOOBJID") = std::to_string(colliding);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -576,25 +576,25 @@ bool QuickBuildComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf
|
||||
auto& quickbuild = reportInfo.info->PushDebug("Quick Build");
|
||||
quickbuild.PushDebug<AMFStringValue>("State") = StringifiedEnum::ToString(m_State).data();
|
||||
quickbuild.PushDebug<AMFDoubleValue>("Timer") = m_Timer;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("TimerIncomplete") = m_TimerIncomplete;
|
||||
quickbuild.PushDebug("ActivatorPosition").PushDebug(m_ActivatorPosition);
|
||||
quickbuild.PushDebug<AMFStringValue>("ActivatorId") = std::to_string(m_ActivatorId);
|
||||
quickbuild.PushDebug<AMFBoolValue>("ShowResetEffect") = m_ShowResetEffect;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("Timer Incomplete") = m_TimerIncomplete;
|
||||
quickbuild.PushDebug("Activator Position").PushDebug(m_ActivatorPosition);
|
||||
quickbuild.PushDebug<AMFStringValue>("Activator ID", "LWOOBJID") = std::to_string(m_ActivatorId);
|
||||
quickbuild.PushDebug<AMFBoolValue>("Show Reset Effect") = m_ShowResetEffect;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("Taken") = m_Taken;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("ResetTime") = m_ResetTime;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("CompleteTime") = m_CompleteTime;
|
||||
quickbuild.PushDebug<AMFIntValue>("TakeImagination") = m_TakeImagination;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("Reset Time") = m_ResetTime;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("Complete Time") = m_CompleteTime;
|
||||
quickbuild.PushDebug<AMFIntValue>("Take Imagination") = m_TakeImagination;
|
||||
quickbuild.PushDebug<AMFBoolValue>("Interruptible") = m_Interruptible;
|
||||
quickbuild.PushDebug<AMFBoolValue>("SelfActivator") = m_SelfActivator;
|
||||
auto& modules = quickbuild.PushDebug("CustomModules");
|
||||
quickbuild.PushDebug<AMFBoolValue>("Self Activator") = m_SelfActivator;
|
||||
auto& modules = quickbuild.PushDebug("Custom Modules");
|
||||
for (const auto cmodule : m_CustomModules) modules.PushDebug<AMFIntValue>("Module") = cmodule;
|
||||
quickbuild.PushDebug<AMFIntValue>("ActivityId") = m_ActivityId;
|
||||
quickbuild.PushDebug<AMFIntValue>("PostImaginationCost") = m_PostImaginationCost;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("TimeBeforeSmash") = m_TimeBeforeSmash;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("TimeBeforeDrain") = m_TimeBeforeDrain;
|
||||
quickbuild.PushDebug<AMFIntValue>("DrainedImagination") = m_DrainedImagination;
|
||||
quickbuild.PushDebug<AMFBoolValue>("RepositionPlayer") = m_RepositionPlayer;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("SoftTimer") = m_SoftTimer;
|
||||
quickbuild.PushDebug<AMFStringValue>("Builder") = std::to_string(m_Builder);
|
||||
quickbuild.PushDebug<AMFIntValue>("Activity Id") = m_ActivityId;
|
||||
quickbuild.PushDebug<AMFIntValue>("Post Imagination Cost") = m_PostImaginationCost;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("Time Before Smash") = m_TimeBeforeSmash;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("Time Before Drain") = m_TimeBeforeDrain;
|
||||
quickbuild.PushDebug<AMFIntValue>("Drained Imagination") = m_DrainedImagination;
|
||||
quickbuild.PushDebug<AMFBoolValue>("Reposition Player") = m_RepositionPlayer;
|
||||
quickbuild.PushDebug<AMFDoubleValue>("Soft Timer") = m_SoftTimer;
|
||||
quickbuild.PushDebug<AMFStringValue>("Builder", "LWOOBJID") = std::to_string(m_Builder);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ ScriptComponent::ScriptComponent(Entity* parent, const int32_t componentID, cons
|
||||
m_ScriptName = scriptName;
|
||||
|
||||
SetScript(scriptName);
|
||||
RegisterMsg(&ScriptComponent::OnGetObjectReportInfo);
|
||||
Component::RegisterMsg(&ScriptComponent::OnGetObjectReportInfo);
|
||||
}
|
||||
|
||||
void ScriptComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
#include "CppScripts.h"
|
||||
#include "Component.h"
|
||||
#include "GameMessages.h"
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "eReplicaComponentType.h"
|
||||
|
||||
@@ -42,9 +45,22 @@ public:
|
||||
* @param scriptName the name of the script to find
|
||||
*/
|
||||
void SetScript(const std::string& scriptName);
|
||||
|
||||
|
||||
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
|
||||
|
||||
// Registers a message from a script to be listened for on the parent object
|
||||
template<typename ScriptClass, typename DerivedMsgType>
|
||||
void RegisterMsg(ScriptClass* scriptThis, bool (ScriptClass::*scriptHandler)(Entity&, DerivedMsgType&)) {
|
||||
static_assert(std::is_base_of_v<GameMessages::GameMsg, DerivedMsgType>, "DerivedMsgType must derive from GameMessages::GameMsg base class.");
|
||||
const auto boundMsg = std::bind(scriptHandler, scriptThis, std::placeholders::_1, std::placeholders::_2);
|
||||
auto* const parent = m_Parent;
|
||||
const auto castWrapper = [parent, boundMsg](GameMessages::GameMsg& msg) {
|
||||
return boundMsg(*parent, static_cast<DerivedMsgType&>(msg));
|
||||
};
|
||||
DerivedMsgType msg;
|
||||
m_Parent->RegisterMsg(msg.msgId, castWrapper);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
|
||||
@@ -79,14 +79,8 @@ void SimplePhysicsComponent::SetPhysicsMotionState(uint32_t value) {
|
||||
bool SimplePhysicsComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
|
||||
PhysicsComponent::OnGetObjectReportInfo(reportInfo);
|
||||
auto& info = reportInfo.subCategory->PushDebug("Simple Physics Info");
|
||||
auto& velocity = info.PushDebug("Velocity");
|
||||
velocity.PushDebug<AMFDoubleValue>("x") = m_Velocity.x;
|
||||
velocity.PushDebug<AMFDoubleValue>("y") = m_Velocity.y;
|
||||
velocity.PushDebug<AMFDoubleValue>("z") = m_Velocity.z;
|
||||
auto& angularVelocity = info.PushDebug("Angular Velocity");
|
||||
angularVelocity.PushDebug<AMFDoubleValue>("x") = m_AngularVelocity.x;
|
||||
angularVelocity.PushDebug<AMFDoubleValue>("y") = m_AngularVelocity.y;
|
||||
angularVelocity.PushDebug<AMFDoubleValue>("z") = m_AngularVelocity.z;
|
||||
info.PushDebug("Velocity").PushDebug(m_Velocity);
|
||||
info.PushDebug("Angular Velocity").PushDebug(m_AngularVelocity);
|
||||
info.PushDebug<AMFIntValue>("Physics Motion State") = m_PhysicsMotionState;
|
||||
info.PushDebug<AMFStringValue>("Climbable Type") = StringifiedEnum::ToString(m_ClimbableType).data();
|
||||
return true;
|
||||
|
||||
@@ -159,6 +159,10 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
|
||||
InventoryComponent* inv = entity->GetComponent<InventoryComponent>();
|
||||
if (inv) {
|
||||
// Clear server-side skill state so AddItemSkills sends fresh AddSkill
|
||||
// packets to the now-ready client. Skills sent during entity construction
|
||||
// (Serialize) arrive before LWOSkillComponent is initialized and are dropped.
|
||||
inv->ClearSkills();
|
||||
auto items = inv->GetEquippedItems();
|
||||
for (auto pair : items) {
|
||||
const auto item = pair.second;
|
||||
|
||||
@@ -45,6 +45,7 @@ enum class BehaviorSlot : int32_t;
|
||||
enum class eVendorTransactionResult : uint32_t;
|
||||
enum class eReponseMoveItemBetweenInventoryTypeCode : int32_t;
|
||||
enum class eMissionState : int;
|
||||
enum class AiState : uint32_t;
|
||||
|
||||
enum class eCameraTargetCyclingMode : int32_t {
|
||||
ALLOW_CYCLE_TEAMMATES,
|
||||
@@ -980,5 +981,12 @@ namespace GameMessages {
|
||||
LWOOBJID objectID{};
|
||||
LOT lot{};
|
||||
};
|
||||
|
||||
struct NotifyCombatAIStateChange : public GameMsg {
|
||||
NotifyCombatAIStateChange() : GameMsg(MessageType::Game::NOTIFY_COMBAT_AI_STATE_CHANGE) {}
|
||||
|
||||
AiState newState{};
|
||||
AiState prevState{};
|
||||
};
|
||||
};
|
||||
#endif // GAMEMESSAGES_H
|
||||
|
||||
@@ -817,6 +817,42 @@ void SlashCommandHandler::Startup() {
|
||||
};
|
||||
RegisterCommand(ExecuteCommand);
|
||||
|
||||
Command GetSceneCommand{
|
||||
.help = "Get the current scene ID and name at your position",
|
||||
.info = "Displays the scene ID and name at the player's current position. Scenes do not care about height.",
|
||||
.aliases = { "getscene", "scene" },
|
||||
.handle = DEVGMCommands::GetScene,
|
||||
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||
};
|
||||
RegisterCommand(GetSceneCommand);
|
||||
|
||||
Command GetAdjacentScenesCommand{
|
||||
.help = "Get all scenes adjacent to your current scene",
|
||||
.info = "Displays all scenes that are directly connected to the player's current scene via scene transitions.",
|
||||
.aliases = { "getadjacentscenes", "adjacentscenes" },
|
||||
.handle = DEVGMCommands::GetAdjacentScenes,
|
||||
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||
};
|
||||
RegisterCommand(GetAdjacentScenesCommand);
|
||||
|
||||
Command SpawnScenePointsCommand{
|
||||
.help = "Spawn bricks at points across your current scene",
|
||||
.info = "Spawns bricks at sampled points across the player's current scene using terrain scene map data.",
|
||||
.aliases = { "spawnscenepoints" },
|
||||
.handle = DEVGMCommands::SpawnScenePoints,
|
||||
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||
};
|
||||
RegisterCommand(SpawnScenePointsCommand);
|
||||
|
||||
Command SpawnAllScenePointsCommand{
|
||||
.help = "Spawn bricks at ALL vertices in ALL scenes (high density, many entities)",
|
||||
.info = "Spawns bricks at every vertex in the terrain mesh for all scenes in the zone. WARNING: Creates a massive number of entities for maximum accuracy visualization.",
|
||||
.aliases = { "spawnallscenepoints", "spawnallscenes" },
|
||||
.handle = DEVGMCommands::SpawnAllScenePoints,
|
||||
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||
};
|
||||
RegisterCommand(SpawnAllScenePointsCommand);
|
||||
|
||||
// Register Greater Than Zero Commands
|
||||
|
||||
Command KickCommand{
|
||||
|
||||
@@ -1890,4 +1890,231 @@ namespace DEVGMCommands {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetScene(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
const auto position = entity->GetPosition();
|
||||
|
||||
// Get the scene ID from the zone manager
|
||||
const auto sceneID = Game::zoneManager->GetSceneIDFromPosition(position);
|
||||
|
||||
if (sceneID == LWOSCENEID_INVALID) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"No scene found at current position.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the scene reference from the zone to get the name
|
||||
const auto* zone = Game::zoneManager->GetZone();
|
||||
if (!zone) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the feedback message
|
||||
std::ostringstream feedback;
|
||||
feedback << "Scene ID: " << sceneID.GetSceneID();
|
||||
feedback << " (Layer: " << sceneID.GetLayerID() << ")";
|
||||
|
||||
// Get the scene name
|
||||
const auto* sceneRef = zone->GetScene(sceneID);
|
||||
if (sceneRef && !sceneRef->name.empty()) {
|
||||
feedback << " - Name: " << sceneRef->name;
|
||||
}
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||
}
|
||||
|
||||
void GetAdjacentScenes(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
const auto position = entity->GetPosition();
|
||||
|
||||
// Get the scene ID from the zone manager
|
||||
const auto sceneID = Game::zoneManager->GetSceneIDFromPosition(position);
|
||||
|
||||
if (sceneID == LWOSCENEID_INVALID) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"No scene found at current position.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the zone reference
|
||||
const auto* zone = Game::zoneManager->GetZone();
|
||||
if (!zone) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current scene info
|
||||
const auto* currentScene = zone->GetScene(sceneID);
|
||||
std::string currentSceneName = currentScene && !currentScene->name.empty() ? currentScene->name : "Unknown";
|
||||
|
||||
// Get adjacent scenes
|
||||
const auto adjacentSceneIDs = Game::zoneManager->GetAdjacentScenes(sceneID);
|
||||
|
||||
if (adjacentSceneIDs.empty()) {
|
||||
std::ostringstream feedback;
|
||||
feedback << "Current Scene: " << sceneID.GetSceneID() << " (" << currentSceneName << ")";
|
||||
feedback << " - No adjacent scenes found.";
|
||||
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the feedback message with current scene
|
||||
std::ostringstream feedback;
|
||||
feedback << "Current Scene: " << sceneID.GetSceneID() << " (" << currentSceneName << ")";
|
||||
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||
|
||||
// List all adjacent scenes
|
||||
feedback.str("");
|
||||
feedback << "Adjacent Scenes (" << adjacentSceneIDs.size() << "):";
|
||||
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||
|
||||
for (const auto& adjSceneID : adjacentSceneIDs) {
|
||||
feedback.str("");
|
||||
feedback << " - Scene ID: " << adjSceneID.GetSceneID();
|
||||
feedback << " (Layer: " << adjSceneID.GetLayerID() << ")";
|
||||
|
||||
// Get the scene name if available
|
||||
const auto* sceneRef = zone->GetScene(adjSceneID);
|
||||
if (sceneRef && !sceneRef->name.empty()) {
|
||||
feedback << " - " << sceneRef->name;
|
||||
}
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||
}
|
||||
}
|
||||
|
||||
void SpawnScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
// Hardcoded to use LOT 33
|
||||
const uint32_t lot = 33;
|
||||
|
||||
// Get player's current position and scene
|
||||
const auto position = entity->GetPosition();
|
||||
const auto currentSceneID = Game::zoneManager->GetSceneIDFromPosition(position);
|
||||
if (currentSceneID == LWOSCENEID_INVALID) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"No scene found at current position.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the zone
|
||||
const auto* zone = Game::zoneManager->GetZone();
|
||||
if (!zone) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the Raw terrain data
|
||||
const auto& raw = zone->GetZoneRaw();
|
||||
if (raw.chunks.empty()) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Zone does not have valid terrain data.");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t spawnedCount = 0;
|
||||
|
||||
for (const auto& chunk : raw.chunks) {
|
||||
if (!chunk.IsValidForSceneLookup()) continue;
|
||||
|
||||
for (uint32_t i = 0; i < chunk.width; ++i) {
|
||||
for (uint32_t j = 0; j < chunk.height; ++j) {
|
||||
if (i * chunk.width + j >= chunk.heightMap.size()) continue;
|
||||
|
||||
const uint8_t sceneID = chunk.GetSceneIDAtGrid(i, j);
|
||||
if (sceneID != currentSceneID.GetSceneID()) continue;
|
||||
|
||||
EntityInfo info;
|
||||
info.lot = lot + sceneID;
|
||||
info.pos = chunk.GridToWorldPos(i, j);
|
||||
info.rot = QuatUtils::IDENTITY;
|
||||
info.spawner = nullptr;
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
info.spawnerNodeID = 0;
|
||||
info.settings.Insert(u"SpawnedFromSlashCommand", true);
|
||||
|
||||
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
||||
if (newEntity != nullptr) {
|
||||
Game::entityManager->ConstructEntity(newEntity);
|
||||
spawnedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnedCount == 0) {
|
||||
std::ostringstream feedback;
|
||||
feedback << "No spawn points found in current scene (ID: " << currentSceneID.GetSceneID() << ").";
|
||||
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Send feedback
|
||||
const auto* sceneRef = zone->GetScene(currentSceneID);
|
||||
const std::string sceneName = sceneRef ? sceneRef->name : "Unknown";
|
||||
std::ostringstream feedback;
|
||||
feedback << "Spawned " << spawnedCount << " points (LOT " << lot + currentSceneID.GetSceneID() << ") in scene "
|
||||
<< currentSceneID.GetSceneID() << " (" << sceneName << ").";
|
||||
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||
}
|
||||
|
||||
void SpawnAllScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
// Hardcoded to use LOT 33
|
||||
const uint32_t lot = 33;
|
||||
|
||||
// Get the zone
|
||||
const auto* zone = Game::zoneManager->GetZone();
|
||||
if (!zone) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"No zone loaded.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the Raw terrain data
|
||||
const auto& raw = zone->GetZoneRaw();
|
||||
if (raw.chunks.empty()) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Zone does not have valid terrain data.");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t spawnedCount = 0;
|
||||
std::map<uint8_t, uint32_t> sceneSpawnCounts;
|
||||
|
||||
for (const auto& chunk : raw.chunks) {
|
||||
if (!chunk.IsValidForSceneLookup()) continue;
|
||||
|
||||
for (uint32_t i = 0; i < chunk.width; ++i) {
|
||||
for (uint32_t j = 0; j < chunk.height; ++j) {
|
||||
if (i * chunk.width + j >= chunk.heightMap.size()) continue;
|
||||
|
||||
const uint8_t sceneID = chunk.GetSceneIDAtGrid(i, j);
|
||||
if (sceneID == 0) continue;
|
||||
|
||||
EntityInfo info;
|
||||
info.lot = lot + sceneID;
|
||||
info.pos = chunk.GridToWorldPos(i, j);
|
||||
info.rot = QuatUtils::IDENTITY;
|
||||
info.spawner = nullptr;
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
info.spawnerNodeID = 0;
|
||||
info.settings.Insert(u"SpawnedFromSlashCommand", true);
|
||||
|
||||
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
||||
if (newEntity != nullptr) {
|
||||
Game::entityManager->ConstructEntity(newEntity);
|
||||
spawnedCount++;
|
||||
sceneSpawnCounts[sceneID]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send detailed feedback
|
||||
std::ostringstream feedback;
|
||||
feedback << "Spawned " << spawnedCount << " total points (base LOT " << lot << ") across "
|
||||
<< sceneSpawnCounts.size() << " scenes:\n";
|
||||
|
||||
for (const auto& [sceneID, count] : sceneSpawnCounts) {
|
||||
const auto* sceneRef = zone->GetScene(LWOSCENEID(sceneID));
|
||||
const std::string sceneName = sceneRef ? sceneRef->name : "Unknown";
|
||||
feedback << " Scene " << static_cast<int>(sceneID) << ", LOT: " << (lot + sceneID) << " (" << sceneName << "): " << count << " points\n";
|
||||
}
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::ASCIIToUTF16(feedback.str()));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -77,6 +77,10 @@ namespace DEVGMCommands {
|
||||
void Barfight(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void Despawn(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void Execute(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void GetScene(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void GetAdjacentScenes(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void SpawnScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void SpawnAllScenePoints(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
}
|
||||
|
||||
#endif //!DEVGMCOMMANDS_H
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
set(DNAVIGATION_SOURCES "dNavMesh.cpp")
|
||||
|
||||
add_subdirectory(dTerrain)
|
||||
|
||||
foreach(file ${DNAVIGATIONS_DTERRAIN_SOURCES})
|
||||
set(DNAVIGATION_SOURCES ${DNAVIGATION_SOURCES} "dTerrain/${file}")
|
||||
endforeach()
|
||||
|
||||
add_library(dNavigation OBJECT ${DNAVIGATION_SOURCES})
|
||||
target_include_directories(dNavigation PUBLIC "."
|
||||
PRIVATE
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "dNavMesh.h"
|
||||
|
||||
#include "RawFile.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Logger.h"
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
set(DNAVIGATIONS_DTERRAIN_SOURCES "RawFile.cpp"
|
||||
"RawChunk.cpp"
|
||||
"RawHeightMap.cpp" PARENT_SCOPE)
|
||||
@@ -1,93 +0,0 @@
|
||||
#include "RawChunk.h"
|
||||
|
||||
#include "BinaryIO.h"
|
||||
|
||||
#include "RawMesh.h"
|
||||
#include "RawHeightMap.h"
|
||||
|
||||
RawChunk::RawChunk(std::ifstream& stream) {
|
||||
// Read the chunk index and info
|
||||
|
||||
BinaryIO::BinaryRead(stream, m_ChunkIndex);
|
||||
BinaryIO::BinaryRead(stream, m_Width);
|
||||
BinaryIO::BinaryRead(stream, m_Height);
|
||||
BinaryIO::BinaryRead(stream, m_X);
|
||||
BinaryIO::BinaryRead(stream, m_Z);
|
||||
|
||||
m_HeightMap = new RawHeightMap(stream, m_Height, m_Width);
|
||||
|
||||
// We can just skip the rest of the data so we can read the next chunks, we don't need anymore data
|
||||
|
||||
// Possible overflow here? TODO make reasonable upper bound or confirm big numbers arent necessary to have
|
||||
uint32_t colorMapSize;
|
||||
BinaryIO::BinaryRead(stream, colorMapSize);
|
||||
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (colorMapSize * colorMapSize * 4));
|
||||
|
||||
uint32_t lightmapSize;
|
||||
BinaryIO::BinaryRead(stream, lightmapSize);
|
||||
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (lightmapSize));
|
||||
|
||||
uint32_t colorMapSize2;
|
||||
BinaryIO::BinaryRead(stream, colorMapSize2);
|
||||
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (colorMapSize2 * colorMapSize2 * 4));
|
||||
|
||||
uint8_t unknown;
|
||||
BinaryIO::BinaryRead(stream, unknown);
|
||||
|
||||
uint32_t blendmapSize;
|
||||
BinaryIO::BinaryRead(stream, blendmapSize);
|
||||
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (blendmapSize));
|
||||
|
||||
uint32_t pointSize;
|
||||
BinaryIO::BinaryRead(stream, pointSize);
|
||||
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (pointSize * 9 * 4));
|
||||
|
||||
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (colorMapSize * colorMapSize));
|
||||
|
||||
uint32_t endCounter;
|
||||
BinaryIO::BinaryRead(stream, endCounter);
|
||||
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (endCounter * 2));
|
||||
|
||||
if (endCounter != 0) {
|
||||
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (32));
|
||||
|
||||
for (int i = 0; i < 0x10; i++) {
|
||||
uint16_t finalCountdown;
|
||||
BinaryIO::BinaryRead(stream, finalCountdown);
|
||||
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (finalCountdown * 2));
|
||||
}
|
||||
}
|
||||
|
||||
// Generate our mesh/geo data for this chunk
|
||||
|
||||
this->GenerateMesh();
|
||||
}
|
||||
|
||||
RawChunk::~RawChunk() {
|
||||
if (m_Mesh) delete m_Mesh;
|
||||
if (m_HeightMap) delete m_HeightMap;
|
||||
}
|
||||
|
||||
void RawChunk::GenerateMesh() {
|
||||
RawMesh* meshData = new RawMesh();
|
||||
|
||||
for (int i = 0; i < m_Width; ++i) {
|
||||
for (int j = 0; j < m_Height; ++j) {
|
||||
float y = *std::next(m_HeightMap->m_FloatMap.begin(), m_Width * i + j);
|
||||
|
||||
meshData->m_Vertices.push_back(NiPoint3(i, y, j));
|
||||
|
||||
if (i == 0 || j == 0) continue;
|
||||
|
||||
meshData->m_Triangles.push_back(m_Width * i + j);
|
||||
meshData->m_Triangles.push_back(m_Width * i + j - 1);
|
||||
meshData->m_Triangles.push_back(m_Width * (i - 1) + j - 1);
|
||||
|
||||
meshData->m_Triangles.push_back(m_Width * (i - 1) + j - 1);
|
||||
meshData->m_Triangles.push_back(m_Width * (i - 1) + j);
|
||||
meshData->m_Triangles.push_back(m_Width * i + j);
|
||||
}
|
||||
}
|
||||
|
||||
m_Mesh = meshData;
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
|
||||
struct RawMesh;
|
||||
class RawHeightMap;
|
||||
|
||||
class RawChunk {
|
||||
public:
|
||||
RawChunk(std::ifstream& stream);
|
||||
~RawChunk();
|
||||
|
||||
void GenerateMesh();
|
||||
|
||||
uint32_t m_ChunkIndex;
|
||||
uint32_t m_Width;
|
||||
uint32_t m_Height;
|
||||
float m_X;
|
||||
float m_Z;
|
||||
|
||||
RawHeightMap* m_HeightMap;
|
||||
RawMesh* m_Mesh;
|
||||
};
|
||||
@@ -1,84 +0,0 @@
|
||||
#include "RawFile.h"
|
||||
|
||||
#include "BinaryIO.h"
|
||||
#include "RawChunk.h"
|
||||
#include "RawMesh.h"
|
||||
#include "RawHeightMap.h"
|
||||
|
||||
RawFile::RawFile(std::string fileName) {
|
||||
if (!BinaryIO::DoesFileExist(fileName)) return;
|
||||
|
||||
std::ifstream file(fileName, std::ios::binary);
|
||||
|
||||
// Read header
|
||||
|
||||
BinaryIO::BinaryRead(file, m_Version);
|
||||
BinaryIO::BinaryRead(file, m_Padding);
|
||||
BinaryIO::BinaryRead(file, m_ChunkCount);
|
||||
BinaryIO::BinaryRead(file, m_Width);
|
||||
BinaryIO::BinaryRead(file, m_Height);
|
||||
|
||||
|
||||
if (m_Version < 0x20) {
|
||||
return; // Version is too old to be supported
|
||||
}
|
||||
|
||||
// Read in chunks
|
||||
|
||||
m_Chunks = {};
|
||||
|
||||
for (uint32_t i = 0; i < m_ChunkCount; i++) {
|
||||
RawChunk* chunk = new RawChunk(file);
|
||||
m_Chunks.push_back(chunk);
|
||||
}
|
||||
|
||||
m_FinalMesh = new RawMesh();
|
||||
|
||||
this->GenerateFinalMeshFromChunks();
|
||||
}
|
||||
|
||||
RawFile::~RawFile() {
|
||||
if (m_FinalMesh) delete m_FinalMesh;
|
||||
for (const auto* item : m_Chunks) {
|
||||
if (item) delete item;
|
||||
}
|
||||
}
|
||||
|
||||
void RawFile::GenerateFinalMeshFromChunks() {
|
||||
uint32_t lenOfLastChunk = 0; // index of last vert set in the last chunk
|
||||
|
||||
for (const auto& chunk : m_Chunks) {
|
||||
for (const auto& vert : chunk->m_Mesh->m_Vertices) {
|
||||
auto tempVert = vert;
|
||||
|
||||
// Scale X and Z by the chunk's position in the world
|
||||
// Scale Y by the chunk's heightmap scale factor
|
||||
tempVert.SetX(tempVert.GetX() + (chunk->m_X / chunk->m_HeightMap->m_ScaleFactor));
|
||||
tempVert.SetY(tempVert.GetY() / chunk->m_HeightMap->m_ScaleFactor);
|
||||
tempVert.SetZ(tempVert.GetZ() + (chunk->m_Z / chunk->m_HeightMap->m_ScaleFactor));
|
||||
|
||||
// Then scale it again for some reason
|
||||
tempVert *= chunk->m_HeightMap->m_ScaleFactor;
|
||||
|
||||
m_FinalMesh->m_Vertices.push_back(tempVert);
|
||||
}
|
||||
|
||||
for (const auto& tri : chunk->m_Mesh->m_Triangles) {
|
||||
m_FinalMesh->m_Triangles.push_back(tri + lenOfLastChunk);
|
||||
}
|
||||
|
||||
lenOfLastChunk += chunk->m_Mesh->m_Vertices.size();
|
||||
}
|
||||
}
|
||||
|
||||
void RawFile::WriteFinalMeshToOBJ(std::string path) {
|
||||
std::ofstream file(path);
|
||||
|
||||
for (const auto& v : m_FinalMesh->m_Vertices) {
|
||||
file << "v " << v.x << ' ' << v.y << ' ' << v.z << '\n';
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_FinalMesh->m_Triangles.size(); i += 3) {
|
||||
file << "f " << *std::next(m_FinalMesh->m_Triangles.begin(), i) + 1 << ' ' << *std::next(m_FinalMesh->m_Triangles.begin(), i + 1) + 1 << ' ' << *std::next(m_FinalMesh->m_Triangles.begin(), i + 2) + 1 << '\n';
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
class RawChunk;
|
||||
struct RawMesh;
|
||||
|
||||
class RawFile {
|
||||
public:
|
||||
RawFile(std::string filePath);
|
||||
~RawFile();
|
||||
|
||||
private:
|
||||
|
||||
void GenerateFinalMeshFromChunks();
|
||||
void WriteFinalMeshToOBJ(std::string path);
|
||||
|
||||
uint8_t m_Version;
|
||||
uint16_t m_Padding;
|
||||
uint32_t m_ChunkCount;
|
||||
uint32_t m_Width;
|
||||
uint32_t m_Height;
|
||||
|
||||
std::vector<RawChunk*> m_Chunks;
|
||||
RawMesh* m_FinalMesh = nullptr;
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
#include "RawHeightMap.h"
|
||||
|
||||
#include "BinaryIO.h"
|
||||
|
||||
RawHeightMap::RawHeightMap() {}
|
||||
|
||||
RawHeightMap::RawHeightMap(std::ifstream& stream, float height, float width) {
|
||||
// Read in height map data header and scale
|
||||
|
||||
BinaryIO::BinaryRead(stream, m_Unknown1);
|
||||
BinaryIO::BinaryRead(stream, m_Unknown2);
|
||||
BinaryIO::BinaryRead(stream, m_Unknown3);
|
||||
BinaryIO::BinaryRead(stream, m_Unknown4);
|
||||
BinaryIO::BinaryRead(stream, m_ScaleFactor);
|
||||
|
||||
// read all vertices in
|
||||
|
||||
for (uint64_t i = 0; i < width * height; i++) {
|
||||
float value;
|
||||
BinaryIO::BinaryRead(stream, value);
|
||||
m_FloatMap.push_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
RawHeightMap::~RawHeightMap() {
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
|
||||
class RawHeightMap {
|
||||
public:
|
||||
RawHeightMap();
|
||||
RawHeightMap(std::ifstream& stream, float height, float width);
|
||||
~RawHeightMap();
|
||||
|
||||
uint32_t m_Unknown1;
|
||||
uint32_t m_Unknown2;
|
||||
uint32_t m_Unknown3;
|
||||
uint32_t m_Unknown4;
|
||||
|
||||
float m_ScaleFactor;
|
||||
|
||||
std::vector<float> m_FloatMap = {};
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "NiPoint3.h"
|
||||
|
||||
struct RawMesh {
|
||||
std::vector<NiPoint3> m_Vertices;
|
||||
std::vector<uint32_t> m_Triangles;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
set(DSCRIPTS_SOURCES_02_SERVER_ENEMY_FV
|
||||
"DragonRonin.cpp"
|
||||
"FvMaelstromCavalry.cpp"
|
||||
"FvMaelstromDragon.cpp"
|
||||
PARENT_SCOPE)
|
||||
|
||||
6
dScripts/02_server/Enemy/FV/DragonRonin.cpp
Normal file
6
dScripts/02_server/Enemy/FV/DragonRonin.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#include "DragonRonin.h"
|
||||
|
||||
void DragonRonin::OnStartup(Entity* self) {
|
||||
self->SetVar<float>(u"suicideTimer", 40.0f);
|
||||
CountdownDestroyAI::OnStartup(self);
|
||||
}
|
||||
7
dScripts/02_server/Enemy/FV/DragonRonin.h
Normal file
7
dScripts/02_server/Enemy/FV/DragonRonin.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "CountdownDestroyAI.h"
|
||||
|
||||
class DragonRonin : public CountdownDestroyAI {
|
||||
public:
|
||||
void OnStartup(Entity* self) override;
|
||||
};
|
||||
@@ -1,6 +1,7 @@
|
||||
set(DSCRIPTS_SOURCES_02_SERVER_ENEMY_GENERAL
|
||||
"BaseEnemyMech.cpp"
|
||||
"BaseEnemyApe.cpp"
|
||||
"CountdownDestroyAI.cpp"
|
||||
"GfApeSmashingQB.cpp"
|
||||
"TreasureChestDragonServer.cpp"
|
||||
"EnemyNjBuff.cpp"
|
||||
|
||||
50
dScripts/02_server/Enemy/General/CountdownDestroyAI.cpp
Normal file
50
dScripts/02_server/Enemy/General/CountdownDestroyAI.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "CountdownDestroyAI.h"
|
||||
|
||||
#include "BaseCombatAIComponent.h"
|
||||
#include "ScriptComponent.h"
|
||||
|
||||
void CountdownDestroyAI::OnStartup(Entity* self) {
|
||||
CountdownStartup(*self);
|
||||
auto* scriptComp = self->GetComponent<ScriptComponent>();
|
||||
if (scriptComp) scriptComp->RegisterMsg(this, &CountdownDestroyAI::OnNotifyCombatAIStateChange);
|
||||
}
|
||||
|
||||
void CountdownDestroyAI::CountdownStartup(Entity& self) {
|
||||
auto suicideTimer = self.GetVar<float>(u"suicideTimer");
|
||||
if (suicideTimer == 0.0f) suicideTimer = 60;
|
||||
self.AddTimer("Dead", suicideTimer);
|
||||
}
|
||||
|
||||
void CountdownDestroyAI::OnHit(Entity* self, Entity* attacker) {
|
||||
if (!self->GetVar<bool>(u"ShouldBeDead")) return;
|
||||
self->CancelTimer("IsBeingAttacked");
|
||||
self->AddTimer("Dead", 5.0f);
|
||||
}
|
||||
|
||||
void CountdownDestroyAI::OnTimerDone(Entity* self, std::string timerName) {
|
||||
if (timerName == "Dead") {
|
||||
self->SetVar<bool>(u"ShouldBeDead", true);
|
||||
if (self->GetVar<bool>(u"Busy")) {
|
||||
self->AddTimer("IsBeingAttacked", 5.0f);
|
||||
} else {
|
||||
self->Smash();
|
||||
}
|
||||
} else if (timerName == "IsBeingAttacked") {
|
||||
self->Smash();
|
||||
}
|
||||
}
|
||||
|
||||
bool CountdownDestroyAI::OnNotifyCombatAIStateChange(Entity& self, GameMessages::NotifyCombatAIStateChange& notifyMsg) {
|
||||
const auto curState = notifyMsg.newState;
|
||||
if (curState == AiState::dead) return true;
|
||||
|
||||
if (curState == AiState::aggro || curState == AiState::tether) {
|
||||
self.SetVar(u"Busy", true);
|
||||
} else {
|
||||
self.SetVar(u"Busy", false);
|
||||
if (self.GetVar<bool>(u"ShouldBeDead")) {
|
||||
self.Smash();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
13
dScripts/02_server/Enemy/General/CountdownDestroyAI.h
Normal file
13
dScripts/02_server/Enemy/General/CountdownDestroyAI.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "CppScripts.h"
|
||||
|
||||
#include "GameMessages.h"
|
||||
|
||||
class CountdownDestroyAI : public CppScripts::Script {
|
||||
public:
|
||||
void OnStartup(Entity* self) override;
|
||||
void CountdownStartup(Entity& self);
|
||||
void OnHit(Entity* self, Entity* attacker) override;
|
||||
void OnTimerDone(Entity* self, std::string timerName) override;
|
||||
bool OnNotifyCombatAIStateChange(Entity& self, GameMessages::NotifyCombatAIStateChange& msg);
|
||||
};
|
||||
@@ -6,6 +6,7 @@ set(DSCRIPTS_SOURCES_02_SERVER_MAP_NJHUB
|
||||
"EnemySkeletonSpawner.cpp"
|
||||
"FallingTile.cpp"
|
||||
"FlameJetServer.cpp"
|
||||
"LightningOrbServer.cpp"
|
||||
"ImaginationShrineServer.cpp"
|
||||
"Lieutenant.cpp"
|
||||
"MonCoreNookDoors.cpp"
|
||||
|
||||
12
dScripts/02_server/Map/njhub/LightningOrbServer.cpp
Normal file
12
dScripts/02_server/Map/njhub/LightningOrbServer.cpp
Normal file
@@ -0,0 +1,12 @@
|
||||
#include "LightningOrbServer.h"
|
||||
|
||||
void LightningOrbServer::OnCollisionPhantom(Entity* self, Entity* target) {
|
||||
GameMessages::GetPosition playerPos;
|
||||
playerPos.Send(target->GetObjectID());
|
||||
GameMessages::GetPosition selfPos;
|
||||
selfPos.Send(self->GetObjectID());
|
||||
const NiPoint3 newVec((playerPos.pos.x - selfPos.pos.x) * 2.5, 15, (playerPos.pos.z - selfPos.pos.z) * 2.5);
|
||||
// ahhhh aron said to put a TODO here moving platforms don't work lol. disable this so people can actually do the puzzle
|
||||
// GameMessages::SendKnockback(target->GetObjectID(), self->GetObjectID(), self->GetObjectID(), 0, newVec);
|
||||
// GameMessages::SendPlayFXEffect(target->GetObjectID(), -1, u"knockback", "knockback");
|
||||
}
|
||||
8
dScripts/02_server/Map/njhub/LightningOrbServer.h
Normal file
8
dScripts/02_server/Map/njhub/LightningOrbServer.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "CppScripts.h"
|
||||
|
||||
class LightningOrbServer : public CppScripts::Script
|
||||
{
|
||||
public:
|
||||
void OnCollisionPhantom(Entity* self, Entity* target) override;
|
||||
};
|
||||
@@ -135,8 +135,11 @@
|
||||
#include "FvMaelstromCavalry.h"
|
||||
#include "FvHorsemenTrigger.h"
|
||||
#include "FvFlyingCreviceDragon.h"
|
||||
#include "FvDragonInstanceServer.h"
|
||||
#include "FvMaelstromDragon.h"
|
||||
#include "DragonRonin.h"
|
||||
#include "FvDragonSmashingGolemQb.h"
|
||||
#include "CountdownDestroyAI.h"
|
||||
#include "TreasureChestDragonServer.h"
|
||||
#include "InstanceExitTransferPlayerToLastNonInstance.h"
|
||||
#include "FvFreeGfNinjas.h"
|
||||
@@ -274,6 +277,7 @@
|
||||
#include "MonCoreNookDoors.h"
|
||||
#include "MonCoreSmashableDoors.h"
|
||||
#include "FlameJetServer.h"
|
||||
#include "LightningOrbServer.h"
|
||||
#include "BurningTile.h"
|
||||
#include "NjEarthDragonPetServer.h"
|
||||
#include "NjEarthPetServer.h"
|
||||
@@ -490,7 +494,10 @@ namespace {
|
||||
{"scripts\\ai\\FV\\L_ACT_NINJA_TURRET_1.lua", []() {return new ActNinjaTurret();}},
|
||||
{"scripts\\02_server\\Map\\FV\\L_FV_HORSEMEN_TRIGGER.lua", []() {return new FvHorsemenTrigger();}},
|
||||
{"scripts\\ai\\FV\\L_FV_FLYING_CREVICE_DRAGON.lua", []() {return new FvFlyingCreviceDragon();}},
|
||||
{"scripts\\ai\\FV\\Dragon_Instance\\L_FV_DRAGON_INSTANCE_SERVER.lua", []() {return new FvDragonInstanceServer();}},
|
||||
{"scripts\\02_server\\Enemy\\FV\\L_FV_DRAGON_RONIN.lua", []() {return new DragonRonin();}},
|
||||
{"scripts\\02_server\\Enemy\\FV\\L_FV_MAELSTROM_DRAGON.lua", []() {return new FvMaelstromDragon();}},
|
||||
{"scripts\\02_server\\Enemy\\General\\L_COUNTDOWN_DESTROY_AI.lua", []() {return new CountdownDestroyAI();}},
|
||||
{"scripts\\ai\\FV\\L_FV_DRAGON_SMASHING_GOLEM_QB.lua", []() {return new FvDragonSmashingGolemQb();}},
|
||||
{"scripts\\02_server\\Enemy\\General\\L_TREASURE_CHEST_DRAGON_SERVER.lua", []() {return new TreasureChestDragonServer();}},
|
||||
{"scripts\\ai\\GENERAL\\L_INSTANCE_EXIT_TRANSFER_PLAYER_TO_LAST_NON_INSTANCE.lua", []() {return new InstanceExitTransferPlayerToLastNonInstance();}},
|
||||
@@ -628,6 +635,7 @@ namespace {
|
||||
{"scripts\\02_server\\Map\\njhub\\L_MON_CORE_SMASHABLE_DOORS.lua", []() {return new MonCoreSmashableDoors();}},
|
||||
{"scripts\\02_server\\Map\\njhub\\L_MON_CORE_SMASHABLE_DOORS.lua", []() {return new MonCoreSmashableDoors();}},
|
||||
{"scripts\\02_server\\Map\\njhub\\L_FLAME_JET_SERVER.lua", []() {return new FlameJetServer();}},
|
||||
{"scripts\\02_server\\Map\\njhub\\L_LIGHTNING_ORB_SERVER.lua", []() {return new LightningOrbServer();}},
|
||||
{"scripts\\02_server\\Map\\njhub\\L_BURNING_TILE.lua", []() {return new BurningTile();}},
|
||||
{"scripts\\02_server\\Map\\njhub\\L_SPAWN_EARTH_PET_SERVER.lua", []() {return new NjEarthDragonPetServer();}},
|
||||
{"scripts\\02_server\\Map\\njhub\\L_EARTH_PET_SERVER.lua", []() {return new NjEarthPetServer();}},
|
||||
|
||||
@@ -18,7 +18,13 @@ set(DSCRIPTS_SOURCES_AI_FV
|
||||
"FvMaelstromGeyser.cpp"
|
||||
"TriggerGas.cpp")
|
||||
|
||||
add_subdirectory(Dragon_Instance)
|
||||
|
||||
foreach(file ${DSCRIPTS_SOURCES_AI_FV_DRAGON_INSTANCE})
|
||||
set(DSCRIPTS_SOURCES_AI_FV ${DSCRIPTS_SOURCES_AI_FV} "Dragon_Instance/${file}")
|
||||
endforeach()
|
||||
|
||||
add_library(dScriptsAiFV OBJECT ${DSCRIPTS_SOURCES_AI_FV})
|
||||
target_include_directories(dScriptsAiFV PUBLIC ".")
|
||||
target_include_directories(dScriptsAiFV PUBLIC "." "Dragon_Instance")
|
||||
target_precompile_headers(dScriptsAiFV REUSE_FROM dScriptsBase)
|
||||
|
||||
|
||||
3
dScripts/ai/FV/Dragon_Instance/CMakeLists.txt
Normal file
3
dScripts/ai/FV/Dragon_Instance/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
set(DSCRIPTS_SOURCES_AI_FV_DRAGON_INSTANCE
|
||||
"FvDragonInstanceServer.cpp"
|
||||
PARENT_SCOPE)
|
||||
14
dScripts/ai/FV/Dragon_Instance/FvDragonInstanceServer.cpp
Normal file
14
dScripts/ai/FV/Dragon_Instance/FvDragonInstanceServer.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "FvDragonInstanceServer.h"
|
||||
|
||||
#include "Entity.h"
|
||||
#include "DestroyableComponent.h"
|
||||
|
||||
void FvDragonInstanceServer::OnPlayerLoaded(Entity* self, Entity* player) {
|
||||
auto* const destComp = player->GetComponent<DestroyableComponent>();
|
||||
if (destComp) {
|
||||
destComp->SetHealth(destComp->GetMaxHealth());
|
||||
destComp->SetArmor(destComp->GetMaxArmor());
|
||||
destComp->SetImagination(destComp->GetMaxImagination());
|
||||
Game::entityManager->SerializeEntity(player);
|
||||
}
|
||||
}
|
||||
7
dScripts/ai/FV/Dragon_Instance/FvDragonInstanceServer.h
Normal file
7
dScripts/ai/FV/Dragon_Instance/FvDragonInstanceServer.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "CppScripts.h"
|
||||
|
||||
class FvDragonInstanceServer : public CppScripts::Script {
|
||||
public:
|
||||
void OnPlayerLoaded(Entity* self, Entity* player) override;
|
||||
};
|
||||
@@ -1,5 +1,6 @@
|
||||
set(DZONEMANAGER_SOURCES "dZoneManager.cpp"
|
||||
"Level.cpp"
|
||||
"Raw.cpp"
|
||||
"Spawner.cpp"
|
||||
"Zone.cpp")
|
||||
|
||||
@@ -14,6 +15,7 @@ target_include_directories(dZoneManager PUBLIC "."
|
||||
"${PROJECT_SOURCE_DIR}/dGame" # Entity.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dEntity" # EntityInfo.h
|
||||
PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}/dCommon/dClient" # SceneColors.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dComponents" #InventoryComponent.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dInventory" #InventoryComponent.h (transitive)
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dBehaviors" #BehaviorSlot.h
|
||||
|
||||
501
dZoneManager/Raw.cpp
Normal file
501
dZoneManager/Raw.cpp
Normal file
@@ -0,0 +1,501 @@
|
||||
#include "Raw.h"
|
||||
#include "BinaryIO.h"
|
||||
#include "Logger.h"
|
||||
#include "SceneColor.h"
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace {
|
||||
constexpr uint32_t kMaxResolution = 4096;
|
||||
constexpr size_t kMaxBlobBytes = 64ULL * 1024 * 1024; // 64 MiB
|
||||
constexpr uint32_t kMaxChunks = 1024;
|
||||
} // namespace
|
||||
|
||||
namespace Raw {
|
||||
|
||||
bool Chunk::IsValidForSceneLookup() const {
|
||||
return !sceneMap.empty() && colorMapResolution > 0 && !heightMap.empty()
|
||||
&& scaleFactor > 0.0f && width > 1 && height > 1;
|
||||
}
|
||||
|
||||
uint8_t Chunk::GetSceneIDAtGrid(uint32_t i, uint32_t j) const {
|
||||
const float sceneMapI = (static_cast<float>(i) / static_cast<float>(width - 1)) * static_cast<float>(colorMapResolution - 1);
|
||||
const float sceneMapJ = (static_cast<float>(j) / static_cast<float>(height - 1)) * static_cast<float>(colorMapResolution - 1);
|
||||
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), colorMapResolution - 1);
|
||||
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), colorMapResolution - 1);
|
||||
const uint32_t sceneIndex = sceneI * colorMapResolution + sceneJ;
|
||||
if (sceneIndex >= sceneMap.size()) return 0;
|
||||
return sceneMap[sceneIndex];
|
||||
}
|
||||
|
||||
NiPoint3 Chunk::GridToWorldPos(uint32_t i, uint32_t j) const {
|
||||
const float y = (i * width + j < heightMap.size()) ? heightMap[i * width + j] : 0.0f;
|
||||
return NiPoint3(
|
||||
(static_cast<float>(i) + (offsetX / scaleFactor)) * scaleFactor,
|
||||
y,
|
||||
(static_cast<float>(j) + (offsetZ / scaleFactor)) * scaleFactor
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read flair attributes from stream
|
||||
*/
|
||||
static bool ReadFlairAttributes(std::istream& stream, FlairAttributes& flair) {
|
||||
try {
|
||||
BinaryIO::BinaryRead(stream, flair.id);
|
||||
BinaryIO::BinaryRead(stream, flair.scaleFactor);
|
||||
BinaryIO::BinaryRead(stream, flair.position.x);
|
||||
BinaryIO::BinaryRead(stream, flair.position.y);
|
||||
BinaryIO::BinaryRead(stream, flair.position.z);
|
||||
BinaryIO::BinaryRead(stream, flair.rotation.x);
|
||||
BinaryIO::BinaryRead(stream, flair.rotation.y);
|
||||
BinaryIO::BinaryRead(stream, flair.rotation.z);
|
||||
BinaryIO::BinaryRead(stream, flair.colorR);
|
||||
BinaryIO::BinaryRead(stream, flair.colorG);
|
||||
BinaryIO::BinaryRead(stream, flair.colorB);
|
||||
BinaryIO::BinaryRead(stream, flair.colorA);
|
||||
return true;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read mesh triangle data from stream
|
||||
*/
|
||||
static bool ReadMeshTri(std::istream& stream, MeshTri& meshTri) {
|
||||
try {
|
||||
BinaryIO::BinaryRead(stream, meshTri.meshTriListSize);
|
||||
meshTri.meshTriList.resize(meshTri.meshTriListSize);
|
||||
for (uint16_t i = 0; i < meshTri.meshTriListSize; ++i) {
|
||||
BinaryIO::BinaryRead(stream, meshTri.meshTriList[i]);
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a chunk from stream
|
||||
*/
|
||||
static bool ReadChunk(std::istream& stream, Chunk& chunk, uint16_t version) {
|
||||
try {
|
||||
// Read basic chunk info
|
||||
BinaryIO::BinaryRead(stream, chunk.id);
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BinaryIO::BinaryRead(stream, chunk.width);
|
||||
BinaryIO::BinaryRead(stream, chunk.height);
|
||||
BinaryIO::BinaryRead(stream, chunk.offsetX);
|
||||
BinaryIO::BinaryRead(stream, chunk.offsetZ);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// For version < 32, shader ID comes before texture IDs
|
||||
if (version < 32) {
|
||||
BinaryIO::BinaryRead(stream, chunk.shaderId);
|
||||
}
|
||||
|
||||
// Read texture IDs (4 textures)
|
||||
chunk.textureIds.resize(4);
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
BinaryIO::BinaryRead(stream, chunk.textureIds[i]);
|
||||
}
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read scale factor
|
||||
BinaryIO::BinaryRead(stream, chunk.scaleFactor);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read heightmap
|
||||
const size_t width = static_cast<size_t>(chunk.width);
|
||||
const size_t height = static_cast<size_t>(chunk.height);
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
LOG("Chunk %u has invalid heightmap dimensions: width=%zu, height=%zu", chunk.id, width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (width > kMaxResolution || height > kMaxResolution) {
|
||||
LOG("Chunk %u heightmap dimensions exceed maximum resolution %u: width=%zu, height=%zu", chunk.id, kMaxResolution, width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (height != 0 && width > std::numeric_limits<size_t>::max() / height) {
|
||||
LOG("Chunk %u heightmap size multiplication overflows: width=%zu, height=%zu", chunk.id, width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t heightMapSize = width * height;
|
||||
const size_t elementSize = sizeof(chunk.heightMap[0]);
|
||||
|
||||
if (elementSize != 0 && heightMapSize > std::numeric_limits<size_t>::max() / elementSize) {
|
||||
LOG("Chunk %u heightmap byte size overflows: elements=%zu, elementSize=%zu", chunk.id, heightMapSize, elementSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t totalBytes = heightMapSize * elementSize;
|
||||
if (totalBytes == 0 || totalBytes > kMaxBlobBytes) {
|
||||
LOG("Chunk %u heightmap total size invalid: bytes=%zu (max %zu)", chunk.id, totalBytes, kMaxBlobBytes);
|
||||
return false;
|
||||
}
|
||||
|
||||
chunk.heightMap.resize(heightMapSize);
|
||||
for (size_t i = 0; i < heightMapSize; ++i) {
|
||||
BinaryIO::BinaryRead(stream, chunk.heightMap[i]);
|
||||
}
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ColorMap
|
||||
if (version >= 32) {
|
||||
BinaryIO::BinaryRead(stream, chunk.colorMapResolution);
|
||||
} else {
|
||||
chunk.colorMapResolution = chunk.width - 1;
|
||||
}
|
||||
|
||||
if (chunk.colorMapResolution > kMaxResolution) {
|
||||
LOG("Chunk colorMapResolution %u exceeds maximum %u", chunk.colorMapResolution, kMaxResolution);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (version >= 32) {
|
||||
const size_t colorMapPixelCount = static_cast<size_t>(chunk.colorMapResolution) * chunk.colorMapResolution * 4;
|
||||
if (colorMapPixelCount > kMaxBlobBytes) {
|
||||
LOG("Chunk colorMap size %zu exceeds maximum %zu bytes", colorMapPixelCount, kMaxBlobBytes);
|
||||
return false;
|
||||
}
|
||||
chunk.colorMap.resize(colorMapPixelCount);
|
||||
stream.read(reinterpret_cast<char*>(chunk.colorMap.data()), static_cast<std::streamsize>(colorMapPixelCount));
|
||||
} else {
|
||||
const size_t legacyColorBytes = static_cast<size_t>(chunk.width) * chunk.width * 4;
|
||||
if (legacyColorBytes > kMaxBlobBytes) {
|
||||
LOG("Chunk legacy colorMap size %zu exceeds maximum %zu bytes", legacyColorBytes, kMaxBlobBytes);
|
||||
return false;
|
||||
}
|
||||
chunk.colorMap.resize(legacyColorBytes);
|
||||
stream.read(reinterpret_cast<char*>(chunk.colorMap.data()), static_cast<std::streamsize>(legacyColorBytes));
|
||||
}
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// LightMap/diffusemap.dds (v>=32 only)
|
||||
if (version >= 32) {
|
||||
uint32_t lightMapSize;
|
||||
BinaryIO::BinaryRead(stream, lightMapSize);
|
||||
|
||||
if (lightMapSize > kMaxBlobBytes) {
|
||||
LOG("Chunk lightMap size %u exceeds maximum %zu bytes", lightMapSize, kMaxBlobBytes);
|
||||
return false;
|
||||
}
|
||||
chunk.lightMap.resize(lightMapSize);
|
||||
stream.read(reinterpret_cast<char*>(chunk.lightMap.data()), static_cast<std::streamsize>(lightMapSize));
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Blend/texture map
|
||||
BinaryIO::BinaryRead(stream, chunk.textureMapResolution);
|
||||
|
||||
if (chunk.textureMapResolution > kMaxResolution) {
|
||||
LOG("Chunk textureMapResolution %u exceeds maximum %u", chunk.textureMapResolution, kMaxResolution);
|
||||
return false;
|
||||
}
|
||||
const size_t textureMapPixelCount = static_cast<size_t>(chunk.textureMapResolution) * chunk.textureMapResolution * 4;
|
||||
if (textureMapPixelCount > kMaxBlobBytes) {
|
||||
LOG("Chunk textureMap size %zu exceeds maximum %zu bytes", textureMapPixelCount, kMaxBlobBytes);
|
||||
return false;
|
||||
}
|
||||
chunk.textureMap.resize(textureMapPixelCount);
|
||||
stream.read(reinterpret_cast<char*>(chunk.textureMap.data()), static_cast<std::streamsize>(textureMapPixelCount));
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Texture settings + blend map DDS (v>=32 only)
|
||||
if (version >= 32) {
|
||||
BinaryIO::BinaryRead(stream, chunk.textureSettings);
|
||||
|
||||
uint32_t blendMapDDSSize;
|
||||
BinaryIO::BinaryRead(stream, blendMapDDSSize);
|
||||
|
||||
if (blendMapDDSSize > kMaxBlobBytes) {
|
||||
LOG("Chunk blendMap size %u exceeds maximum %zu bytes", blendMapDDSSize, kMaxBlobBytes);
|
||||
return false;
|
||||
}
|
||||
chunk.blendMap.resize(blendMapDDSSize);
|
||||
stream.read(reinterpret_cast<char*>(chunk.blendMap.data()), static_cast<std::streamsize>(blendMapDDSSize));
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Read flairs
|
||||
uint32_t numFlairs;
|
||||
BinaryIO::BinaryRead(stream, numFlairs);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t flairBytes = static_cast<size_t>(numFlairs) * sizeof(FlairAttributes);
|
||||
if (flairBytes > kMaxBlobBytes) {
|
||||
LOG("Chunk %u flair count %u exceeds maximum (byte size %zu > %zu)", chunk.id, numFlairs, flairBytes, kMaxBlobBytes);
|
||||
return false;
|
||||
}
|
||||
chunk.flairs.resize(numFlairs);
|
||||
for (uint32_t i = 0; i < numFlairs; ++i) {
|
||||
if (!ReadFlairAttributes(stream, chunk.flairs[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Scene map
|
||||
if (version >= 32) {
|
||||
const size_t sceneMapSize = static_cast<size_t>(chunk.colorMapResolution) * chunk.colorMapResolution;
|
||||
|
||||
if (sceneMapSize > kMaxBlobBytes) {
|
||||
LOG("Chunk sceneMap size %zu exceeds maximum %zu bytes", sceneMapSize, kMaxBlobBytes);
|
||||
return false;
|
||||
}
|
||||
chunk.sceneMap.resize(sceneMapSize);
|
||||
stream.read(reinterpret_cast<char*>(chunk.sceneMap.data()), static_cast<std::streamsize>(sceneMapSize));
|
||||
} else if (version == 31) {
|
||||
const size_t sceneMapCells = static_cast<size_t>(chunk.colorMapResolution + 1) * (chunk.colorMapResolution + 1);
|
||||
if (sceneMapCells > kMaxBlobBytes) {
|
||||
LOG("Chunk v31 sceneMap size %zu exceeds maximum %zu bytes", sceneMapCells, kMaxBlobBytes);
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> rawSceneMap(sceneMapCells);
|
||||
stream.read(reinterpret_cast<char*>(rawSceneMap.data()), static_cast<std::streamsize>(sceneMapCells));
|
||||
chunk.sceneMap.resize(static_cast<size_t>(chunk.colorMapResolution) * chunk.colorMapResolution);
|
||||
for (uint32_t row = 0; row < chunk.colorMapResolution; ++row) {
|
||||
for (uint32_t col = 0; col < chunk.colorMapResolution; ++col) {
|
||||
chunk.sceneMap[row * chunk.colorMapResolution + col] = rawSceneMap[row * (chunk.colorMapResolution + 1) + col];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stream.seekg(1, std::ios::cur);
|
||||
}
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mesh data (v>=32 only)
|
||||
if (version < 32) {
|
||||
return true;
|
||||
}
|
||||
|
||||
BinaryIO::BinaryRead(stream, chunk.vertSize);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chunk.vertSize == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const size_t vertBytes = static_cast<size_t>(chunk.vertSize) * sizeof(uint16_t);
|
||||
if (vertBytes > kMaxBlobBytes) {
|
||||
LOG("Chunk %u vertSize %u exceeds maximum (byte size %zu > %zu)", chunk.id, chunk.vertSize, vertBytes, kMaxBlobBytes);
|
||||
return false;
|
||||
}
|
||||
chunk.meshVertUsage.resize(chunk.vertSize);
|
||||
for (uint32_t i = 0; i < chunk.vertSize; ++i) {
|
||||
BinaryIO::BinaryRead(stream, chunk.meshVertUsage[i]);
|
||||
}
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
chunk.meshVertSize.resize(16);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
BinaryIO::BinaryRead(stream, chunk.meshVertSize[i]);
|
||||
}
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
chunk.meshTri.resize(16);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
if (!ReadMeshTri(stream, chunk.meshTri[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadRaw(std::istream& stream, Raw& outRaw) {
|
||||
// Get stream size
|
||||
stream.seekg(0, std::ios::end);
|
||||
auto streamSize = stream.tellg();
|
||||
stream.seekg(0, std::ios::beg);
|
||||
|
||||
if (streamSize <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Read header
|
||||
BinaryIO::BinaryRead(stream, outRaw.version);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BinaryIO::BinaryRead(stream, outRaw.dev);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only read chunks if dev == 0
|
||||
if (outRaw.dev == 0) {
|
||||
BinaryIO::BinaryRead(stream, outRaw.numChunks);
|
||||
BinaryIO::BinaryRead(stream, outRaw.numChunksWidth);
|
||||
BinaryIO::BinaryRead(stream, outRaw.numChunksHeight);
|
||||
|
||||
if (outRaw.numChunks > kMaxChunks) {
|
||||
LOG("Raw numChunks %u exceeds maximum %u", outRaw.numChunks, kMaxChunks);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read all chunks
|
||||
outRaw.chunks.resize(outRaw.numChunks);
|
||||
for (uint32_t i = 0; i < outRaw.numChunks; ++i) {
|
||||
if (!ReadChunk(stream, outRaw.chunks[i], outRaw.version)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate terrain bounds from all chunks
|
||||
if (!outRaw.chunks.empty()) {
|
||||
outRaw.minBoundsX = std::numeric_limits<float>::max();
|
||||
outRaw.minBoundsZ = std::numeric_limits<float>::max();
|
||||
outRaw.maxBoundsX = std::numeric_limits<float>::lowest();
|
||||
outRaw.maxBoundsZ = std::numeric_limits<float>::lowest();
|
||||
|
||||
for (const auto& chunk : outRaw.chunks) {
|
||||
const float chunkMinX = chunk.offsetX;
|
||||
const float chunkMinZ = chunk.offsetZ;
|
||||
const float chunkMaxX = chunkMinX + (chunk.width * chunk.scaleFactor);
|
||||
const float chunkMaxZ = chunkMinZ + (chunk.height * chunk.scaleFactor);
|
||||
|
||||
outRaw.minBoundsX = std::min(outRaw.minBoundsX, chunkMinX);
|
||||
outRaw.minBoundsZ = std::min(outRaw.minBoundsZ, chunkMinZ);
|
||||
outRaw.maxBoundsX = std::max(outRaw.maxBoundsX, chunkMaxX);
|
||||
outRaw.maxBoundsZ = std::max(outRaw.maxBoundsZ, chunkMaxZ);
|
||||
}
|
||||
LOG("Raw terrain bounds: X[%.2f, %.2f], Z[%.2f, %.2f]",
|
||||
outRaw.minBoundsX, outRaw.maxBoundsX, outRaw.minBoundsZ, outRaw.maxBoundsZ);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (const std::exception&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateTerrainMesh(const Raw& raw, TerrainMesh& outMesh) {
|
||||
outMesh.vertices.clear();
|
||||
outMesh.triangles.clear();
|
||||
|
||||
if (raw.chunks.empty() || raw.version < 32) {
|
||||
return; // No scene data available
|
||||
}
|
||||
|
||||
LOG("GenerateTerrainMesh: Processing %zu chunks", raw.chunks.size());
|
||||
|
||||
uint32_t vertexOffset = 0;
|
||||
|
||||
for (const auto& chunk : raw.chunks) {
|
||||
if (!chunk.IsValidForSceneLookup()) continue;
|
||||
|
||||
for (uint32_t i = 0; i < chunk.width; ++i) {
|
||||
for (uint32_t j = 0; j < chunk.height; ++j) {
|
||||
const uint32_t heightIndex = chunk.width * i + j;
|
||||
if (heightIndex >= chunk.heightMap.size()) continue;
|
||||
|
||||
outMesh.vertices.emplace_back(chunk.GridToWorldPos(i, j), chunk.GetSceneIDAtGrid(i, j));
|
||||
if (i > 0 && j > 0) {
|
||||
const uint32_t currentVert = vertexOffset + chunk.width * i + j;
|
||||
const uint32_t leftVert = currentVert - 1;
|
||||
const uint32_t bottomLeftVert = vertexOffset + chunk.width * (i - 1) + j - 1;
|
||||
const uint32_t bottomVert = vertexOffset + chunk.width * (i - 1) + j;
|
||||
|
||||
// First triangle
|
||||
outMesh.triangles.push_back(currentVert);
|
||||
outMesh.triangles.push_back(leftVert);
|
||||
outMesh.triangles.push_back(bottomLeftVert);
|
||||
|
||||
// Second triangle
|
||||
outMesh.triangles.push_back(bottomLeftVert);
|
||||
outMesh.triangles.push_back(bottomVert);
|
||||
outMesh.triangles.push_back(currentVert);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vertexOffset += chunk.width * chunk.height;
|
||||
}
|
||||
}
|
||||
|
||||
bool WriteTerrainMeshToOBJ(const TerrainMesh& mesh, const std::string& path) {
|
||||
try {
|
||||
std::ofstream file(path);
|
||||
if (!file.is_open()) {
|
||||
LOG("Failed to open OBJ file for writing: %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& v : mesh.vertices) {
|
||||
const NiColor& color = SceneColor::Get(v.sceneID);
|
||||
file << "v " << v.position.x << ' ' << v.position.y << ' ' << v.position.z
|
||||
<< ' ' << color.m_Red << ' ' << color.m_Green << ' ' << color.m_Blue << '\n';
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < mesh.triangles.size(); i += 3) {
|
||||
file << "f " << (mesh.triangles[i] + 1) << ' '
|
||||
<< (mesh.triangles[i + 1] + 1) << ' '
|
||||
<< (mesh.triangles[i + 2] + 1) << '\n';
|
||||
}
|
||||
|
||||
file.close();
|
||||
LOG("Successfully wrote terrain mesh to OBJ: %s (%zu vertices, %zu triangles)",
|
||||
path.c_str(), mesh.vertices.size(), mesh.triangles.size() / 3);
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
LOG("Exception while writing OBJ file: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Raw
|
||||
161
dZoneManager/Raw.h
Normal file
161
dZoneManager/Raw.h
Normal file
@@ -0,0 +1,161 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __RAW_H__
|
||||
#define __RAW_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <istream>
|
||||
#include "NiPoint3.h"
|
||||
#include "dCommonVars.h"
|
||||
|
||||
namespace Raw {
|
||||
|
||||
/**
|
||||
* @brief Flair attributes structure
|
||||
* Represents decorative elements on the terrain
|
||||
*/
|
||||
struct FlairAttributes {
|
||||
uint32_t id;
|
||||
float scaleFactor;
|
||||
NiPoint3 position;
|
||||
NiPoint3 rotation;
|
||||
uint8_t colorR;
|
||||
uint8_t colorG;
|
||||
uint8_t colorB;
|
||||
uint8_t colorA;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Mesh triangle structure
|
||||
* Contains triangle indices for terrain mesh
|
||||
*/
|
||||
struct MeshTri {
|
||||
uint16_t meshTriListSize;
|
||||
std::vector<uint16_t> meshTriList;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Vertex with scene ID
|
||||
* Used for the generated terrain mesh to enable fast scene lookups
|
||||
*/
|
||||
struct SceneVertex {
|
||||
NiPoint3 position;
|
||||
uint8_t sceneID;
|
||||
|
||||
SceneVertex() : position(), sceneID(0) {}
|
||||
SceneVertex(const NiPoint3& pos, uint8_t scene) : position(pos), sceneID(scene) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Generated terrain mesh
|
||||
* Contains vertices with scene IDs for fast scene lookups at arbitrary positions
|
||||
*/
|
||||
struct TerrainMesh {
|
||||
std::vector<SceneVertex> vertices;
|
||||
std::vector<uint32_t> triangles; // Indices into vertices array (groups of 3)
|
||||
|
||||
TerrainMesh() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Terrain chunk structure
|
||||
* Represents a single chunk of terrain with heightmap, textures, and meshes
|
||||
*/
|
||||
struct Chunk {
|
||||
uint32_t id;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
float offsetX;
|
||||
float offsetZ;
|
||||
uint32_t shaderId;
|
||||
|
||||
// Texture IDs (4 textures per chunk)
|
||||
std::vector<uint32_t> textureIds;
|
||||
|
||||
// Terrain scale factor
|
||||
float scaleFactor;
|
||||
|
||||
// Heightmap data (width * height floats)
|
||||
std::vector<float> heightMap;
|
||||
|
||||
// Version 32+ fields
|
||||
uint32_t colorMapResolution = 0;
|
||||
std::vector<uint8_t> colorMap; // RGBA pixels (colorMap * colorMap * 4)
|
||||
std::vector<uint8_t> lightMap;
|
||||
|
||||
uint32_t textureMapResolution = 0;
|
||||
std::vector<uint8_t> textureMap; // (textureMapResolution * textureMapResolution * 4)
|
||||
uint8_t textureSettings = 0;
|
||||
std::vector<uint8_t> blendMap;
|
||||
|
||||
// Flair data
|
||||
std::vector<FlairAttributes> flairs;
|
||||
|
||||
// Scene map (version 32+)
|
||||
std::vector<uint8_t> sceneMap;
|
||||
|
||||
// Mesh data
|
||||
uint32_t vertSize = 0;
|
||||
std::vector<uint16_t> meshVertUsage;
|
||||
std::vector<uint16_t> meshVertSize;
|
||||
std::vector<MeshTri> meshTri;
|
||||
|
||||
bool IsValidForSceneLookup() const;
|
||||
uint8_t GetSceneIDAtGrid(uint32_t i, uint32_t j) const;
|
||||
NiPoint3 GridToWorldPos(uint32_t i, uint32_t j) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief RAW terrain file structure
|
||||
* Complete representation of a .raw terrain file
|
||||
*/
|
||||
struct Raw {
|
||||
uint16_t version;
|
||||
uint8_t dev;
|
||||
uint32_t numChunks = 0;
|
||||
uint32_t numChunksWidth = 0;
|
||||
uint32_t numChunksHeight = 0;
|
||||
std::vector<Chunk> chunks;
|
||||
|
||||
// Calculated bounds of the entire terrain
|
||||
float minBoundsX = 0.0f;
|
||||
float minBoundsZ = 0.0f;
|
||||
float maxBoundsX = 0.0f;
|
||||
float maxBoundsZ = 0.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Read a RAW terrain file from an input stream
|
||||
*
|
||||
* @param stream Input stream containing RAW file data
|
||||
* @param outRaw Output RAW file structure
|
||||
* @return true if successfully read, false otherwise
|
||||
*/
|
||||
bool ReadRaw(std::istream& stream, Raw& outRaw);
|
||||
|
||||
/**
|
||||
* @brief Generate a terrain mesh from raw chunks
|
||||
* Similar to dTerrain's GenerateFinalMeshFromChunks but creates a mesh with scene IDs
|
||||
* per vertex for fast scene lookups at arbitrary positions.
|
||||
*
|
||||
* @param raw The RAW terrain data to generate mesh from
|
||||
* @param outMesh Output terrain mesh with vertices and scene IDs
|
||||
*/
|
||||
void GenerateTerrainMesh(const Raw& raw, TerrainMesh& outMesh);
|
||||
|
||||
/**
|
||||
* @brief Write terrain mesh to OBJ file for debugging/visualization
|
||||
* Merged from dTerrain's WriteFinalMeshToOBJ functionality
|
||||
* Vertices are colored based on their scene ID using a hash function
|
||||
*
|
||||
* @param mesh The terrain mesh to export
|
||||
* @param path Output path for the OBJ file
|
||||
* @return true if successfully written, false otherwise
|
||||
*/
|
||||
bool WriteTerrainMeshToOBJ(const TerrainMesh& mesh, const std::string& path);
|
||||
|
||||
} // namespace Raw
|
||||
|
||||
#endif // __RAW_H__
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "GeneralUtils.h"
|
||||
#include "BinaryIO.h"
|
||||
#include "LUTriggers.h"
|
||||
#include "dConfig.h"
|
||||
|
||||
#include "AssetManager.h"
|
||||
#include "CDClientManager.h"
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "eTriggerEventType.h"
|
||||
#include "eWaypointCommandType.h"
|
||||
#include "dNavMesh.h"
|
||||
#include "Raw.h"
|
||||
|
||||
Zone::Zone(const LWOZONEID zoneID) :
|
||||
m_ZoneID(zoneID) {
|
||||
@@ -78,12 +80,57 @@ void Zone::LoadZoneIntoMemory() {
|
||||
LoadScene(file);
|
||||
}
|
||||
|
||||
//Read generic zone info:
|
||||
BinaryIO::ReadString<uint8_t>(file, m_ZonePath, BinaryIO::ReadType::String);
|
||||
// Zone boundary lines — skip past them for correct file positioning
|
||||
uint8_t numBoundaries = 0;
|
||||
BinaryIO::BinaryRead(file, numBoundaries);
|
||||
for (uint8_t i = 0; i < numBoundaries; ++i) {
|
||||
NiPoint3 normal, point, spawnLocation;
|
||||
uint32_t packed, destSceneID;
|
||||
BinaryIO::BinaryRead(file, normal);
|
||||
BinaryIO::BinaryRead(file, point);
|
||||
BinaryIO::BinaryRead(file, packed);
|
||||
BinaryIO::BinaryRead(file, destSceneID);
|
||||
BinaryIO::BinaryRead(file, spawnLocation);
|
||||
}
|
||||
|
||||
BinaryIO::ReadString<uint8_t>(file, m_ZoneRawPath, BinaryIO::ReadType::String);
|
||||
BinaryIO::ReadString<uint8_t>(file, m_ZoneName, BinaryIO::ReadType::String);
|
||||
BinaryIO::ReadString<uint8_t>(file, m_ZoneDesc, BinaryIO::ReadType::String);
|
||||
|
||||
auto zoneFolderPath = m_ZoneFilePath.substr(0, m_ZoneFilePath.rfind('/') + 1);
|
||||
if (!Game::assetManager->HasFile(zoneFolderPath + m_ZoneRawPath)) {
|
||||
LOG("Failed to find %s", (zoneFolderPath + m_ZoneRawPath).c_str());
|
||||
throw std::runtime_error("Aborting Zone loading due to no Zone Raw File.");
|
||||
}
|
||||
|
||||
auto rawFile = Game::assetManager->GetFile(zoneFolderPath + m_ZoneRawPath);
|
||||
if (m_FileFormatVersion < Zone::FileFormatVersion::PrePreAlpha) {
|
||||
LOG("Zone %s uses legacy raw terrain format (version %u) which is not supported", m_ZoneFilePath.c_str(), static_cast<uint32_t>(m_FileFormatVersion));
|
||||
throw std::runtime_error("Aborting Zone loading due to unsupported Raw File version.");
|
||||
} else {
|
||||
if (Raw::ReadRaw(rawFile, m_Raw)) {
|
||||
LOG("Successfully parsed %s", (zoneFolderPath + m_ZoneRawPath).c_str());
|
||||
} else {
|
||||
LOG("Failed to parse %s", (zoneFolderPath + m_ZoneRawPath).c_str());
|
||||
throw std::runtime_error("Aborting Zone loading due to invalid Raw File.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Optionally export terrain mesh to OBJ for debugging/visualization
|
||||
if (Game::config->GetValue("export_terrain_to_obj") == "1") {
|
||||
|
||||
// Generate terrain mesh
|
||||
Raw::GenerateTerrainMesh(m_Raw, m_TerrainMesh);
|
||||
LOG("Generated terrain mesh with %zu vertices and %zu triangles", m_TerrainMesh.vertices.size(), m_TerrainMesh.triangles.size() / 3);
|
||||
|
||||
// Write to OBJ
|
||||
std::string objFileName = "terrain_" + std::to_string(m_ZoneID.GetMapID()) + ".obj";
|
||||
if (Raw::WriteTerrainMeshToOBJ(m_TerrainMesh, objFileName)) {
|
||||
LOG("Exported terrain mesh to %s", objFileName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (m_FileFormatVersion >= Zone::FileFormatVersion::PreAlpha) {
|
||||
BinaryIO::BinaryRead(file, m_NumberOfSceneTransitionsLoaded);
|
||||
for (uint32_t i = 0; i < m_NumberOfSceneTransitionsLoaded; ++i) {
|
||||
@@ -236,7 +283,8 @@ void Zone::LoadScene(std::istream& file) {
|
||||
}
|
||||
if (m_FileFormatVersion >= Zone::FileFormatVersion::LatePreAlpha) {
|
||||
BinaryIO::BinaryRead(file, scene.sceneType);
|
||||
lwoSceneID.SetLayerID(scene.sceneType);
|
||||
lwoSceneID.SetLayerID(static_cast<uint32_t>(scene.sceneType));
|
||||
|
||||
|
||||
BinaryIO::ReadString<uint8_t>(file, scene.name, BinaryIO::ReadType::String);
|
||||
}
|
||||
@@ -247,9 +295,11 @@ void Zone::LoadScene(std::istream& file) {
|
||||
}
|
||||
|
||||
if (m_FileFormatVersion >= Zone::FileFormatVersion::LatePreAlpha) {
|
||||
BinaryIO::BinaryRead(file, scene.color_r);
|
||||
BinaryIO::BinaryRead(file, scene.color_b);
|
||||
BinaryIO::BinaryRead(file, scene.color_g);
|
||||
uint8_t r, b, g;
|
||||
BinaryIO::BinaryRead(file, r);
|
||||
BinaryIO::BinaryRead(file, b);
|
||||
BinaryIO::BinaryRead(file, g);
|
||||
scene.color = NiColor(r / 255.0f, g / 255.0f, b / 255.0f);
|
||||
}
|
||||
|
||||
m_Scenes[lwoSceneID] = std::move(scene);
|
||||
@@ -350,7 +400,10 @@ void Zone::LoadSceneTransition(std::istream& file) {
|
||||
|
||||
SceneTransitionInfo Zone::LoadSceneTransitionInfo(std::istream& file) {
|
||||
SceneTransitionInfo info;
|
||||
BinaryIO::BinaryRead(file, info.sceneID);
|
||||
uint32_t sceneID, layerID;
|
||||
BinaryIO::BinaryRead(file, sceneID);
|
||||
BinaryIO::BinaryRead(file, layerID);
|
||||
info.sceneID = LWOSCENEID(sceneID, layerID);
|
||||
BinaryIO::BinaryRead(file, info.position);
|
||||
return info;
|
||||
}
|
||||
@@ -422,7 +475,6 @@ void Zone::LoadPath(std::istream& file) {
|
||||
BinaryIO::BinaryRead(file, waypoint.position.y);
|
||||
BinaryIO::BinaryRead(file, waypoint.position.z);
|
||||
|
||||
|
||||
if (path.pathType == PathType::Spawner || path.pathType == PathType::MovingPlatform || path.pathType == PathType::Race || path.pathType == PathType::Camera || path.pathType == PathType::Rail) {
|
||||
BinaryIO::BinaryRead(file, waypoint.rotation.w);
|
||||
BinaryIO::BinaryRead(file, waypoint.rotation.x);
|
||||
@@ -483,7 +535,7 @@ void Zone::LoadPath(std::istream& file) {
|
||||
}
|
||||
|
||||
// We verify the waypoint heights against the navmesh because in many movement paths,
|
||||
// the waypoint is located near 0 height,
|
||||
// the waypoint is located near 0 height,
|
||||
if (path.pathType == PathType::Movement) {
|
||||
if (dpWorld::IsLoaded()) {
|
||||
// 2000 should be large enough for every world.
|
||||
@@ -494,3 +546,9 @@ void Zone::LoadPath(std::istream& file) {
|
||||
}
|
||||
m_Paths.push_back(path);
|
||||
}
|
||||
|
||||
const SceneRef* Zone::GetScene(LWOSCENEID sceneID) const {
|
||||
auto it = m_Scenes.find(sceneID);
|
||||
if (it != m_Scenes.end()) return &it->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
#include "dZMCommon.h"
|
||||
#include "LDFFormat.h"
|
||||
#include "NiColor.h"
|
||||
#include "tinyxml2.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "Raw.h"
|
||||
|
||||
namespace LUTriggers {
|
||||
struct Trigger;
|
||||
@@ -21,22 +23,25 @@ struct WaypointCommand {
|
||||
};
|
||||
|
||||
|
||||
enum class eSceneType : uint32_t {
|
||||
General = 0,
|
||||
Audio = 1,
|
||||
};
|
||||
|
||||
struct SceneRef {
|
||||
std::string filename;
|
||||
uint32_t id{};
|
||||
uint32_t sceneType{}; //0 = general, 1 = audio?
|
||||
eSceneType sceneType{};
|
||||
std::string name;
|
||||
NiPoint3 unknown1;
|
||||
float unknown2{};
|
||||
uint8_t color_r{};
|
||||
uint8_t color_g{};
|
||||
uint8_t color_b{};
|
||||
NiColor color;
|
||||
std::unique_ptr<Level> level;
|
||||
std::map<uint32_t, LUTriggers::Trigger*> triggers;
|
||||
};
|
||||
|
||||
struct SceneTransitionInfo {
|
||||
uint64_t sceneID{}; //id of the scene being transitioned to.
|
||||
LWOSCENEID sceneID;
|
||||
NiPoint3 position;
|
||||
};
|
||||
|
||||
@@ -228,6 +233,12 @@ public:
|
||||
void SetSpawnPos(const NiPoint3& pos) { m_Spawnpoint = pos; }
|
||||
void SetSpawnRot(const NiQuaternion& rot) { m_SpawnpointRotation = rot; }
|
||||
|
||||
const Raw::Raw& GetZoneRaw() const { return m_Raw; }
|
||||
const Raw::TerrainMesh& GetTerrainMesh() const { return m_TerrainMesh; }
|
||||
const SceneRef* GetScene(LWOSCENEID sceneID) const;
|
||||
const std::vector<SceneTransition>& GetSceneTransitions() const { return m_SceneTransitions; }
|
||||
const std::map<LWOSCENEID, SceneRef>& GetScenes() const { return m_Scenes; }
|
||||
|
||||
private:
|
||||
LWOZONEID m_ZoneID;
|
||||
std::string m_ZoneFilePath;
|
||||
@@ -244,6 +255,8 @@ private:
|
||||
std::string m_ZoneName; //Name given to the zone by a level designer
|
||||
std::string m_ZoneDesc; //Description of the zone by a level designer
|
||||
std::string m_ZoneRawPath; //Path to the .raw file of this zone.
|
||||
Raw::Raw m_Raw; // The Raw data for this zone
|
||||
Raw::TerrainMesh m_TerrainMesh; // Pre-generated terrain mesh for fast scene lookups
|
||||
|
||||
std::map<LWOSCENEID, SceneRef> m_Scenes;
|
||||
std::vector<SceneTransition> m_SceneTransitions;
|
||||
|
||||
@@ -10,9 +10,11 @@
|
||||
#include "VanityUtilities.h"
|
||||
#include "WorldConfig.h"
|
||||
#include "CDZoneTableTable.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <set>
|
||||
#include "eObjectBits.h"
|
||||
#include "CDZoneTableTable.h"
|
||||
#include "AssetManager.h"
|
||||
#include <ranges>
|
||||
|
||||
@@ -62,6 +64,9 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) {
|
||||
|
||||
m_pZone->Initalize();
|
||||
|
||||
// Build the scene graph after zone is loaded
|
||||
BuildSceneGraph();
|
||||
|
||||
endTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
||||
|
||||
LoadWorldConfig();
|
||||
@@ -298,3 +303,111 @@ void dZoneManager::LoadWorldConfig() {
|
||||
|
||||
LOG_DEBUG("Loaded WorldConfig into memory");
|
||||
}
|
||||
|
||||
LWOSCENEID dZoneManager::GetSceneIDFromPosition(const NiPoint3& position) const {
|
||||
if (!m_pZone) return LWOSCENEID_INVALID;
|
||||
|
||||
const auto& raw = m_pZone->GetZoneRaw();
|
||||
|
||||
// If no chunks, no scene data available
|
||||
if (raw.chunks.empty()) {
|
||||
return LWOSCENEID_INVALID;
|
||||
}
|
||||
|
||||
// Convert 3D position to 2D (XZ plane) and clamp to terrain bounds
|
||||
float posX = std::clamp(position.x, raw.minBoundsX, raw.maxBoundsX);
|
||||
float posZ = std::clamp(position.z, raw.minBoundsZ, raw.maxBoundsZ);
|
||||
|
||||
// Find the chunk containing this position
|
||||
// Reverse the world position calculation from GenerateTerrainMesh
|
||||
for (const auto& chunk : raw.chunks) {
|
||||
if (!chunk.IsValidForSceneLookup()) continue;
|
||||
|
||||
const float heightI = posX / chunk.scaleFactor - (chunk.offsetX / chunk.scaleFactor);
|
||||
const float heightJ = posZ / chunk.scaleFactor - (chunk.offsetZ / chunk.scaleFactor);
|
||||
|
||||
if (heightI >= 0.0f && heightI < static_cast<float>(chunk.width) &&
|
||||
heightJ >= 0.0f && heightJ < static_cast<float>(chunk.height)) {
|
||||
|
||||
const uint32_t gridI = std::min(static_cast<uint32_t>(heightI), chunk.width - 1);
|
||||
const uint32_t gridJ = std::min(static_cast<uint32_t>(heightJ), chunk.height - 1);
|
||||
return LWOSCENEID(chunk.GetSceneIDAtGrid(gridI, gridJ), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Position not found in any chunk
|
||||
return LWOSCENEID_INVALID;
|
||||
}
|
||||
|
||||
void dZoneManager::BuildSceneGraph() {
|
||||
if (!m_pZone) return;
|
||||
|
||||
// Clear any existing adjacency list
|
||||
m_SceneAdjacencyList.clear();
|
||||
|
||||
// Initialize adjacency list with all scenes
|
||||
const auto& scenes = m_pZone->GetScenes();
|
||||
for (const auto& [sceneID, sceneRef] : scenes) {
|
||||
if (sceneRef.sceneType != eSceneType::General) continue;
|
||||
m_SceneAdjacencyList.try_emplace(sceneID, std::vector<LWOSCENEID>());
|
||||
}
|
||||
|
||||
// Build adjacency list from scene transitions
|
||||
const auto& transitions = m_pZone->GetSceneTransitions();
|
||||
for (const auto& transition : transitions) {
|
||||
// Each transition has multiple points, each pointing to a scene
|
||||
// We need to determine which scenes this transition connects
|
||||
|
||||
// Group transition points by their scene IDs to find unique connections
|
||||
std::set<LWOSCENEID> connectedScenes;
|
||||
for (const auto& point : transition.points) {
|
||||
if (point.sceneID != LWOSCENEID_INVALID && m_SceneAdjacencyList.contains(point.sceneID)) {
|
||||
connectedScenes.insert(point.sceneID);
|
||||
}
|
||||
}
|
||||
|
||||
// Create bidirectional edges between all scenes in this transition
|
||||
// (transitions typically connect two scenes, but can be more complex)
|
||||
std::vector<LWOSCENEID> sceneList(connectedScenes.begin(), connectedScenes.end());
|
||||
|
||||
for (size_t i = 0; i < sceneList.size(); ++i) {
|
||||
for (size_t j = 0; j < sceneList.size(); ++j) {
|
||||
if (i != j) {
|
||||
LWOSCENEID fromScene = sceneList[i];
|
||||
LWOSCENEID toScene = sceneList[j];
|
||||
|
||||
// Add edge if it doesn't already exist
|
||||
auto& adjacentScenes = m_SceneAdjacencyList[fromScene];
|
||||
if (std::find(adjacentScenes.begin(), adjacentScenes.end(), toScene) == adjacentScenes.end()) {
|
||||
adjacentScenes.push_back(toScene);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scene 0 (global scene) is always loaded and adjacent to all other scenes
|
||||
LWOSCENEID globalScene = LWOSCENEID(0, 0);
|
||||
for (auto& [sceneID, adjacentScenes] : m_SceneAdjacencyList) {
|
||||
if (sceneID != globalScene) {
|
||||
// Add global scene to this scene's adjacency list if not already present
|
||||
if (std::find(adjacentScenes.begin(), adjacentScenes.end(), globalScene) == adjacentScenes.end()) {
|
||||
adjacentScenes.push_back(globalScene);
|
||||
}
|
||||
|
||||
// Add this scene to global scene's adjacency list if not already present
|
||||
auto& globalAdjacent = m_SceneAdjacencyList[globalScene];
|
||||
if (std::find(globalAdjacent.begin(), globalAdjacent.end(), sceneID) == globalAdjacent.end()) {
|
||||
globalAdjacent.push_back(sceneID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<LWOSCENEID> dZoneManager::GetAdjacentScenes(LWOSCENEID sceneID) const {
|
||||
auto it = m_SceneAdjacencyList.find(sceneID);
|
||||
if (it != m_SceneAdjacencyList.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return std::vector<LWOSCENEID>();
|
||||
}
|
||||
|
||||
@@ -53,6 +53,30 @@ public:
|
||||
uint32_t GetUniqueMissionIdStartingValue();
|
||||
bool CheckIfAccessibleZone(LWOMAPID zoneID);
|
||||
|
||||
/**
|
||||
* @brief Get the scene ID at a given position. Scenes do not care about height (Y coordinate).
|
||||
*
|
||||
* @param position The position to query
|
||||
* @return The scene ID at that position, or LWOSCENEID_INVALID if not found
|
||||
*/
|
||||
LWOSCENEID GetSceneIDFromPosition(const NiPoint3& position) const;
|
||||
|
||||
/**
|
||||
* @brief Get the adjacency list for the scene graph.
|
||||
* The adjacency list maps each scene ID to a list of scene IDs it can transition to.
|
||||
*
|
||||
* @return A reference to the scene adjacency list
|
||||
*/
|
||||
const std::map<LWOSCENEID, std::vector<LWOSCENEID>>& GetSceneAdjacencyList() const { return m_SceneAdjacencyList; }
|
||||
|
||||
/**
|
||||
* @brief Get all scenes adjacent to (connected to) a given scene.
|
||||
*
|
||||
* @param sceneID The scene ID to query
|
||||
* @return A vector of scene IDs that are directly connected to this scene, or empty vector if scene not found
|
||||
*/
|
||||
std::vector<LWOSCENEID> GetAdjacentScenes(LWOSCENEID sceneID) const;
|
||||
|
||||
// The world config should not be modified by a caller.
|
||||
const WorldConfig& GetWorldConfig() {
|
||||
if (!m_WorldConfig) LoadWorldConfig();
|
||||
@@ -60,6 +84,10 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* Builds the scene graph adjacency list from scene transitions
|
||||
*/
|
||||
void BuildSceneGraph();
|
||||
/**
|
||||
* The starting unique mission ID.
|
||||
*/
|
||||
@@ -75,4 +103,9 @@ private:
|
||||
std::optional<WorldConfig> m_WorldConfig = std::nullopt;
|
||||
|
||||
Entity* m_ZoneControlObject = nullptr;
|
||||
|
||||
/**
|
||||
* Scene graph adjacency list: maps each scene ID to a list of scenes it can transition to
|
||||
*/
|
||||
std::map<LWOSCENEID, std::vector<LWOSCENEID>> m_SceneAdjacencyList;
|
||||
};
|
||||
|
||||
@@ -103,5 +103,9 @@ hardcore_disabled_worlds=
|
||||
# Keeps this percentage of a players' coins on death in hardcore
|
||||
hardcore_coin_keep=
|
||||
|
||||
# Export terrain meshes to OBJ files when zones load
|
||||
# OBJ files will be saved as terrain_<zoneID>.obj in the server directory
|
||||
export_terrain_to_obj=0
|
||||
|
||||
# save pre-split lxfmls to disk for debugging
|
||||
save_lxfmls=0
|
||||
|
||||
Reference in New Issue
Block a user