mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-01-06 08:50:14 -06:00
Compare commits
10 Commits
raw-parsin
...
mission-tr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd547dfd10 | ||
|
|
ca60787055 | ||
|
|
396dcb0465 | ||
|
|
6e545eb1b9 | ||
|
|
46aac016fd | ||
|
|
83823fa64f | ||
|
|
0dd504c803 | ||
|
|
a70c365c23 | ||
|
|
281d9762ef | ||
|
|
002aa896d8 |
@@ -477,7 +477,7 @@ TeamData* TeamContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
|
||||
}
|
||||
}
|
||||
|
||||
newTeam->lootFlag = 1;
|
||||
newTeam->lootFlag = 0;
|
||||
|
||||
TeamStatusUpdate(newTeam);
|
||||
|
||||
|
||||
@@ -374,6 +374,21 @@ public:
|
||||
return value->Insert<AmfType>("value", std::make_unique<AmfType>());
|
||||
}
|
||||
|
||||
AMFArrayValue& PushDebug(const NiPoint3& point) {
|
||||
PushDebug<AMFDoubleValue>("X") = point.x;
|
||||
PushDebug<AMFDoubleValue>("Y") = point.y;
|
||||
PushDebug<AMFDoubleValue>("Z") = point.z;
|
||||
return *this;
|
||||
}
|
||||
|
||||
AMFArrayValue& PushDebug(const NiQuaternion& rot) {
|
||||
PushDebug<AMFDoubleValue>("W") = rot.w;
|
||||
PushDebug<AMFDoubleValue>("X") = rot.x;
|
||||
PushDebug<AMFDoubleValue>("Y") = rot.y;
|
||||
PushDebug<AMFDoubleValue>("Z") = rot.z;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* The associative portion. These values are key'd with strings to an AMFValue.
|
||||
|
||||
@@ -96,3 +96,17 @@ bool Logger::GetLogToConsole() const {
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
FuncEntry::FuncEntry(const char* funcName, const char* fileName, const uint32_t line) {
|
||||
m_FuncName = funcName;
|
||||
if (!m_FuncName) m_FuncName = "Unknown";
|
||||
m_Line = line;
|
||||
m_FileName = fileName;
|
||||
LOG("--> %s::%s:%i", m_FileName, m_FuncName, m_Line);
|
||||
}
|
||||
|
||||
FuncEntry::~FuncEntry() {
|
||||
if (!m_FuncName || !m_FileName) return;
|
||||
|
||||
LOG("<-- %s::%s:%i", m_FileName, m_FuncName, m_Line);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,19 @@ constexpr const char* GetFileNameFromAbsolutePath(const char* path) {
|
||||
#define LOG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->Log(str_, message, ##__VA_ARGS__); } while(0)
|
||||
#define LOG_DEBUG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->LogDebug(str_, message, ##__VA_ARGS__); } while(0)
|
||||
|
||||
// Place this right at the start of a function. Will log a message when called and then once you leave the function.
|
||||
#define LOG_ENTRY auto str_ = GetFileNameFromAbsolutePath(__FILE__); FuncEntry funcEntry_(__FUNCTION__, str_, __LINE__)
|
||||
|
||||
class FuncEntry {
|
||||
public:
|
||||
FuncEntry(const char* funcName, const char* fileName, const uint32_t line);
|
||||
~FuncEntry();
|
||||
private:
|
||||
const char* m_FuncName = nullptr;
|
||||
const char* m_FileName = nullptr;
|
||||
uint32_t m_Line = 0;
|
||||
};
|
||||
|
||||
// Writer class for writing data to files.
|
||||
class Writer {
|
||||
public:
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
#ifndef NICOLOR_H
|
||||
#define NICOLOR_H
|
||||
|
||||
struct NiColor {
|
||||
float m_Red;
|
||||
float m_Green;
|
||||
float m_Blue;
|
||||
|
||||
NiColor(float red, float green, float blue) : m_Red(red), m_Green(green), m_Blue(blue) {}
|
||||
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
|
||||
@@ -1,170 +0,0 @@
|
||||
#ifndef SCENE_COLOR_H
|
||||
#define SCENE_COLOR_H
|
||||
|
||||
#include "NiColor.h"
|
||||
#include <vector>
|
||||
|
||||
class SceneColor {
|
||||
public:
|
||||
SceneColor() {
|
||||
TEMPLATE_COLORS.resize(146);
|
||||
|
||||
// these are not random values, they are the actual template colors used by the game
|
||||
TEMPLATE_COLORS[0] = NiColor(0.5019608f, 0.5019608f, 0.5019608f);
|
||||
TEMPLATE_COLORS[1] = NiColor(1.0f, 0.0f, 0.0f);
|
||||
TEMPLATE_COLORS[2] = NiColor(0.0f, 1.0f, 0.0f);
|
||||
TEMPLATE_COLORS[3] = NiColor(0.0f, 0.0f, 1.0f);
|
||||
TEMPLATE_COLORS[4] = NiColor(1.0f, 1.0f, 0.0f);
|
||||
TEMPLATE_COLORS[5] = NiColor(1.0f, 0.0f, 1.0f);
|
||||
TEMPLATE_COLORS[6] = NiColor(0.0f, 1.0f, 1.0f);
|
||||
TEMPLATE_COLORS[7] = NiColor(0.5019608f, 0.0f, 1.0f);
|
||||
TEMPLATE_COLORS[8] = NiColor(1.0f, 0.5019608f, 0.0f);
|
||||
TEMPLATE_COLORS[9] = NiColor(1.0f, 0.5019608f, 0.5019608f);
|
||||
TEMPLATE_COLORS[10] = NiColor(0.5019608f, 0.2509804f, 0.0f);
|
||||
TEMPLATE_COLORS[11] = NiColor(0.5019608f, 0.0f, 0.2509804f);
|
||||
TEMPLATE_COLORS[12] = NiColor(0.0f, 0.5019608f, 0.2509804f);
|
||||
TEMPLATE_COLORS[13] = NiColor(0.2509804f, 0.0f, 0.5019608f);
|
||||
TEMPLATE_COLORS[14] = NiColor(0.8745098f, 0.0f, 0.2509804f);
|
||||
TEMPLATE_COLORS[15] = NiColor(0.2509804f, 0.8745098f, 0.5019608f);
|
||||
TEMPLATE_COLORS[16] = NiColor(1.0f, 0.7490196f, 0.0f);
|
||||
TEMPLATE_COLORS[17] = NiColor(1.0f, 0.2509804f, 0.0627451f);
|
||||
TEMPLATE_COLORS[18] = NiColor(0.2509804f, 0.0f, 0.8745098f);
|
||||
TEMPLATE_COLORS[19] = NiColor(0.7490196f, 0.0627451f, 0.0627451f);
|
||||
TEMPLATE_COLORS[20] = NiColor(0.0627451f, 0.7490196f, 0.0627451f);
|
||||
TEMPLATE_COLORS[21] = NiColor(1.0f, 0.5019608f, 1.0f);
|
||||
TEMPLATE_COLORS[22] = NiColor(0.9372549f, 0.8705882f, 0.8039216f);
|
||||
TEMPLATE_COLORS[23] = NiColor(0.8039216f, 0.5843138f, 0.4588235f);
|
||||
TEMPLATE_COLORS[24] = NiColor(0.9921569f, 0.8509804f, 0.7098039f);
|
||||
TEMPLATE_COLORS[25] = NiColor(0.4705882f, 0.8588235f, 0.8862745f);
|
||||
TEMPLATE_COLORS[26] = NiColor(0.5294118f, 0.6627451f, 0.4196078f);
|
||||
TEMPLATE_COLORS[27] = NiColor(1.0f, 0.6431373f, 0.454902f);
|
||||
TEMPLATE_COLORS[28] = NiColor(0.9803922f, 0.9058824f, 0.7098039f);
|
||||
TEMPLATE_COLORS[29] = NiColor(0.6235294f, 0.5058824f, 0.4392157f);
|
||||
TEMPLATE_COLORS[30] = NiColor(0.9921569f, 0.4862745f, 0.4313726f);
|
||||
TEMPLATE_COLORS[31] = NiColor(0.0f, 0.0f, 0.0f);
|
||||
TEMPLATE_COLORS[32] = NiColor(0.6745098f, 0.8980392f, 0.9333333f);
|
||||
TEMPLATE_COLORS[33] = NiColor(0.1215686f, 0.4588235f, 0.9960784f);
|
||||
TEMPLATE_COLORS[34] = NiColor(0.6352941f, 0.6352941f, 0.8156863f);
|
||||
TEMPLATE_COLORS[35] = NiColor(0.4f, 0.6f, 0.8f);
|
||||
TEMPLATE_COLORS[36] = NiColor(0.05098039f, 0.5960785f, 0.7294118f);
|
||||
TEMPLATE_COLORS[37] = NiColor(0.4509804f, 0.4f, 0.7411765f);
|
||||
TEMPLATE_COLORS[38] = NiColor(0.8705882f, 0.3647059f, 0.5137255f);
|
||||
TEMPLATE_COLORS[39] = NiColor(0.7960784f, 0.254902f, 0.3294118f);
|
||||
TEMPLATE_COLORS[40] = NiColor(0.7058824f, 0.4039216f, 0.3019608f);
|
||||
TEMPLATE_COLORS[41] = NiColor(1.0f, 0.4980392f, 0.2862745f);
|
||||
TEMPLATE_COLORS[42] = NiColor(0.9176471f, 0.4941176f, 0.3647059f);
|
||||
TEMPLATE_COLORS[43] = NiColor(0.6901961f, 0.7176471f, 0.7764706f);
|
||||
TEMPLATE_COLORS[44] = NiColor(1.0f, 1.0f, 0.6f);
|
||||
TEMPLATE_COLORS[45] = NiColor(0.1098039f, 0.827451f, 0.6352941f);
|
||||
TEMPLATE_COLORS[46] = NiColor(1.0f, 0.6666667f, 0.8f);
|
||||
TEMPLATE_COLORS[47] = NiColor(0.8666667f, 0.2666667f, 0.572549f);
|
||||
TEMPLATE_COLORS[48] = NiColor(0.1137255f, 0.6745098f, 0.8392157f);
|
||||
TEMPLATE_COLORS[49] = NiColor(0.7372549f, 0.3647059f, 0.345098f);
|
||||
TEMPLATE_COLORS[50] = NiColor(0.8666667f, 0.5803922f, 0.4588235f);
|
||||
TEMPLATE_COLORS[51] = NiColor(0.6039216f, 0.8078431f, 0.9215686f);
|
||||
TEMPLATE_COLORS[52] = NiColor(1.0f, 0.7372549f, 0.8509804f);
|
||||
TEMPLATE_COLORS[53] = NiColor(0.9921569f, 0.8588235f, 0.427451f);
|
||||
TEMPLATE_COLORS[54] = NiColor(0.1686275f, 0.4235294f, 0.7686275f);
|
||||
TEMPLATE_COLORS[55] = NiColor(0.9372549f, 0.8039216f, 0.7215686f);
|
||||
TEMPLATE_COLORS[56] = NiColor(0.4313726f, 0.3176471f, 0.3764706f);
|
||||
TEMPLATE_COLORS[57] = NiColor(0.8078431f, 1.0f, 0.1137255f);
|
||||
TEMPLATE_COLORS[58] = NiColor(0.427451f, 0.682353f, 0.5058824f);
|
||||
TEMPLATE_COLORS[59] = NiColor(0.7647059f, 0.3921569f, 0.772549f);
|
||||
TEMPLATE_COLORS[60] = NiColor(0.8f, 0.4f, 0.4f);
|
||||
TEMPLATE_COLORS[61] = NiColor(0.9058824f, 0.7764706f, 0.5921569f);
|
||||
TEMPLATE_COLORS[62] = NiColor(0.9882353f, 0.8509804f, 0.4588235f);
|
||||
TEMPLATE_COLORS[63] = NiColor(0.6588235f, 0.8941177f, 0.627451f);
|
||||
TEMPLATE_COLORS[64] = NiColor(0.5843138f, 0.5686275f, 0.5490196f);
|
||||
TEMPLATE_COLORS[65] = NiColor(0.1098039f, 0.6745098f, 0.4705882f);
|
||||
TEMPLATE_COLORS[66] = NiColor(0.06666667f, 0.3921569f, 0.7058824f);
|
||||
TEMPLATE_COLORS[67] = NiColor(0.9411765f, 0.9098039f, 0.5686275f);
|
||||
TEMPLATE_COLORS[68] = NiColor(1.0f, 0.1137255f, 0.8078431f);
|
||||
TEMPLATE_COLORS[69] = NiColor(0.6980392f, 0.9254902f, 0.3647059f);
|
||||
TEMPLATE_COLORS[70] = NiColor(0.3647059f, 0.4627451f, 0.7960784f);
|
||||
TEMPLATE_COLORS[71] = NiColor(0.7921569f, 0.2156863f, 0.4039216f);
|
||||
TEMPLATE_COLORS[72] = NiColor(0.2313726f, 0.6901961f, 0.5607843f);
|
||||
TEMPLATE_COLORS[73] = NiColor(0.9882353f, 0.7058824f, 0.8352941f);
|
||||
TEMPLATE_COLORS[74] = NiColor(1.0f, 0.9568627f, 0.3098039f);
|
||||
TEMPLATE_COLORS[75] = NiColor(1.0f, 0.7411765f, 0.5333334f);
|
||||
TEMPLATE_COLORS[76] = NiColor(0.9647059f, 0.3921569f, 0.6862745f);
|
||||
TEMPLATE_COLORS[77] = NiColor(0.6666667f, 0.9411765f, 0.8196079f);
|
||||
TEMPLATE_COLORS[78] = NiColor(0.8039216f, 0.2901961f, 0.2980392f);
|
||||
TEMPLATE_COLORS[79] = NiColor(0.9294118f, 0.8196079f, 0.6117647f);
|
||||
TEMPLATE_COLORS[80] = NiColor(0.5921569f, 0.6039216f, 0.6666667f);
|
||||
TEMPLATE_COLORS[81] = NiColor(0.7843137f, 0.2196078f, 0.3529412f);
|
||||
TEMPLATE_COLORS[82] = NiColor(0.9372549f, 0.5960785f, 0.6666667f);
|
||||
TEMPLATE_COLORS[83] = NiColor(0.9921569f, 0.7372549f, 0.7058824f);
|
||||
TEMPLATE_COLORS[84] = NiColor(0.1019608f, 0.282353f, 0.4627451f);
|
||||
TEMPLATE_COLORS[85] = NiColor(0.1882353f, 0.7294118f, 0.5607843f);
|
||||
TEMPLATE_COLORS[86] = NiColor(0.772549f, 0.2941177f, 0.5490196f);
|
||||
TEMPLATE_COLORS[87] = NiColor(0.09803922f, 0.454902f, 0.8235294f);
|
||||
TEMPLATE_COLORS[88] = NiColor(0.7294118f, 0.7215686f, 0.4235294f);
|
||||
TEMPLATE_COLORS[89] = NiColor(1.0f, 0.4588235f, 0.2196078f);
|
||||
TEMPLATE_COLORS[90] = NiColor(1.0f, 0.1686275f, 0.1686275f);
|
||||
TEMPLATE_COLORS[91] = NiColor(0.972549f, 0.8352941f, 0.4078431f);
|
||||
TEMPLATE_COLORS[92] = NiColor(0.9019608f, 0.6588235f, 0.8431373f);
|
||||
TEMPLATE_COLORS[93] = NiColor(0.254902f, 0.2901961f, 0.2980392f);
|
||||
TEMPLATE_COLORS[94] = NiColor(1.0f, 0.4313726f, 0.2901961f);
|
||||
TEMPLATE_COLORS[95] = NiColor(0.1098039f, 0.6627451f, 0.7882353f);
|
||||
TEMPLATE_COLORS[96] = NiColor(1.0f, 0.8117647f, 0.6705883f);
|
||||
TEMPLATE_COLORS[97] = NiColor(0.772549f, 0.8156863f, 0.9019608f);
|
||||
TEMPLATE_COLORS[98] = NiColor(0.9921569f, 0.8666667f, 0.9019608f);
|
||||
TEMPLATE_COLORS[99] = NiColor(0.08235294f, 0.5019608f, 0.4705882f);
|
||||
TEMPLATE_COLORS[100] = NiColor(0.9882353f, 0.454902f, 0.9921569f);
|
||||
TEMPLATE_COLORS[101] = NiColor(0.9686275f, 0.5607843f, 0.654902f);
|
||||
TEMPLATE_COLORS[102] = NiColor(0.5568628f, 0.2705882f, 0.5215687f);
|
||||
TEMPLATE_COLORS[103] = NiColor(0.454902f, 0.2588235f, 0.7843137f);
|
||||
TEMPLATE_COLORS[104] = NiColor(0.6156863f, 0.5058824f, 0.7294118f);
|
||||
TEMPLATE_COLORS[105] = NiColor(1.0f, 0.2862745f, 0.4235294f);
|
||||
TEMPLATE_COLORS[106] = NiColor(0.8392157f, 0.5411765f, 0.3490196f);
|
||||
TEMPLATE_COLORS[107] = NiColor(0.4431373f, 0.2941177f, 0.1372549f);
|
||||
TEMPLATE_COLORS[108] = NiColor(1.0f, 0.282353f, 0.8156863f);
|
||||
TEMPLATE_COLORS[109] = NiColor(0.9333333f, 0.1254902f, 0.3019608f);
|
||||
TEMPLATE_COLORS[110] = NiColor(1.0f, 0.3254902f, 0.2862745f);
|
||||
TEMPLATE_COLORS[111] = NiColor(0.7529412f, 0.2666667f, 0.5607843f);
|
||||
TEMPLATE_COLORS[112] = NiColor(0.1215686f, 0.8078431f, 0.7960784f);
|
||||
TEMPLATE_COLORS[113] = NiColor(0.4705882f, 0.3176471f, 0.6627451f);
|
||||
TEMPLATE_COLORS[114] = NiColor(1.0f, 0.6078432f, 0.6666667f);
|
||||
TEMPLATE_COLORS[115] = NiColor(0.9882353f, 0.1568628f, 0.2784314f);
|
||||
TEMPLATE_COLORS[116] = NiColor(0.4627451f, 1.0f, 0.4784314f);
|
||||
TEMPLATE_COLORS[117] = NiColor(0.6235294f, 0.8862745f, 0.7490196f);
|
||||
TEMPLATE_COLORS[118] = NiColor(0.6470588f, 0.4117647f, 0.3098039f);
|
||||
TEMPLATE_COLORS[119] = NiColor(0.5411765f, 0.4745098f, 0.3647059f);
|
||||
TEMPLATE_COLORS[120] = NiColor(0.2705882f, 0.8078431f, 0.6352941f);
|
||||
TEMPLATE_COLORS[121] = NiColor(0.8039216f, 0.772549f, 0.7607843f);
|
||||
TEMPLATE_COLORS[122] = NiColor(0.5019608f, 0.854902f, 0.9215686f);
|
||||
TEMPLATE_COLORS[123] = NiColor(0.9254902f, 0.9176471f, 0.7450981f);
|
||||
TEMPLATE_COLORS[124] = NiColor(1.0f, 0.8117647f, 0.282353f);
|
||||
TEMPLATE_COLORS[125] = NiColor(0.9921569f, 0.3686275f, 0.3254902f);
|
||||
TEMPLATE_COLORS[126] = NiColor(0.9803922f, 0.654902f, 0.4235294f);
|
||||
TEMPLATE_COLORS[127] = NiColor(0.09411765f, 0.654902f, 0.7098039f);
|
||||
TEMPLATE_COLORS[128] = NiColor(0.9215686f, 0.7803922f, 0.8745098f);
|
||||
TEMPLATE_COLORS[129] = NiColor(0.9882353f, 0.5372549f, 0.6745098f);
|
||||
TEMPLATE_COLORS[130] = NiColor(0.8588235f, 0.8431373f, 0.8235294f);
|
||||
TEMPLATE_COLORS[131] = NiColor(0.8705882f, 0.6666667f, 0.5333334f);
|
||||
TEMPLATE_COLORS[132] = NiColor(0.4666667f, 0.8666667f, 0.9058824f);
|
||||
TEMPLATE_COLORS[133] = NiColor(1.0f, 1.0f, 0.4f);
|
||||
TEMPLATE_COLORS[134] = NiColor(0.572549f, 0.4313726f, 0.682353f);
|
||||
TEMPLATE_COLORS[135] = NiColor(0.1960784f, 0.2901961f, 0.6980392f);
|
||||
TEMPLATE_COLORS[136] = NiColor(0.9686275f, 0.3254902f, 0.5803922f);
|
||||
TEMPLATE_COLORS[137] = NiColor(1.0f, 0.627451f, 0.5372549f);
|
||||
TEMPLATE_COLORS[138] = NiColor(0.5607843f, 0.3137255f, 0.6156863f);
|
||||
TEMPLATE_COLORS[139] = NiColor(1.0f, 1.0f, 1.0f);
|
||||
TEMPLATE_COLORS[140] = NiColor(0.6352941f, 0.6784314f, 0.8156863f);
|
||||
TEMPLATE_COLORS[141] = NiColor(0.9882353f, 0.4235294f, 0.5215687f);
|
||||
TEMPLATE_COLORS[142] = NiColor(0.8039216f, 0.6431373f, 0.8705882f);
|
||||
TEMPLATE_COLORS[143] = NiColor(0.9882353f, 0.9098039f, 0.5137255f);
|
||||
TEMPLATE_COLORS[144] = NiColor(0.772549f, 0.8901961f, 0.5176471f);
|
||||
TEMPLATE_COLORS[145] = NiColor(1.0f, 0.682353f, 0.2588235f);
|
||||
}
|
||||
|
||||
const NiColor& Get(unsigned char index) const {
|
||||
return (index < 146) ? TEMPLATE_COLORS[index] : FALLBACK_COLOR;
|
||||
}
|
||||
|
||||
private:
|
||||
const NiColor FALLBACK_COLOR = NiColor(1.0f, 1.0f, 1.0f);
|
||||
std::vector<NiColor> TEMPLATE_COLORS;
|
||||
};
|
||||
|
||||
#endif // SCENE_COLOR_H
|
||||
@@ -10,7 +10,9 @@ void AndBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bitStream,
|
||||
}
|
||||
|
||||
void AndBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitStream, const BehaviorBranchContext branch) {
|
||||
LOG_ENTRY;
|
||||
for (auto* behavior : this->m_behaviors) {
|
||||
LOG("%i calculating %i", m_behaviorId, behavior->GetBehaviorID());
|
||||
behavior->Calculate(context, bitStream, branch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,4 +95,6 @@ public:
|
||||
|
||||
Behavior& operator=(const Behavior& other) = default;
|
||||
Behavior& operator=(Behavior&& other) = default;
|
||||
|
||||
uint32_t GetBehaviorID() const { return m_behaviorId; }
|
||||
};
|
||||
|
||||
@@ -114,7 +114,6 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitS
|
||||
context->FilterTargets(validTargets, this->m_ignoreFactionList, this->m_includeFactionList, this->m_targetSelf, this->m_targetEnemy, this->m_targetFriend, this->m_targetTeam);
|
||||
|
||||
for (auto validTarget : validTargets) {
|
||||
if (targets.size() >= this->m_maxTargets) break;
|
||||
if (std::find(targets.begin(), targets.end(), validTarget) != targets.end()) continue;
|
||||
if (validTarget->GetIsDead()) continue;
|
||||
|
||||
@@ -147,13 +146,28 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitS
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(targets.begin(), targets.end(), [reference](Entity* a, Entity* b) {
|
||||
std::sort(targets.begin(), targets.end(), [this, reference, combatAi](Entity* a, Entity* b) {
|
||||
const auto aDistance = Vector3::DistanceSquared(reference, a->GetPosition());
|
||||
const auto bDistance = Vector3::DistanceSquared(reference, b->GetPosition());
|
||||
|
||||
return aDistance > bDistance;
|
||||
return aDistance < bDistance;
|
||||
});
|
||||
|
||||
|
||||
if (m_useAttackPriority) {
|
||||
// this should be using the attack priority column on the destroyable component
|
||||
// We want targets with no threat level to remain the same order as above
|
||||
// std::stable_sort(targets.begin(), targets.end(), [combatAi](Entity* a, Entity* b) {
|
||||
// const auto aThreat = combatAi->GetThreat(a->GetObjectID());
|
||||
// const auto bThreat = combatAi->GetThreat(b->GetObjectID());
|
||||
|
||||
// If enabled for this behavior, prioritize threat over distance
|
||||
// return aThreat > bThreat;
|
||||
// });
|
||||
}
|
||||
|
||||
// After we've sorted and found our closest targets, size the vector down in case there are too many
|
||||
if (m_maxTargets > 0 && targets.size() > m_maxTargets) targets.resize(m_maxTargets);
|
||||
const auto hit = !targets.empty();
|
||||
bitStream.Write(hit);
|
||||
|
||||
|
||||
@@ -27,8 +27,13 @@
|
||||
#include "CDComponentsRegistryTable.h"
|
||||
#include "CDPhysicsComponentTable.h"
|
||||
#include "dNavMesh.h"
|
||||
#include "Amf3.h"
|
||||
|
||||
BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
||||
{
|
||||
using namespace GameMessages;
|
||||
RegisterMsg<GetObjectReportInfo>(this, &BaseCombatAIComponent::MsgGetObjectReportInfo);
|
||||
}
|
||||
m_Target = LWOOBJID_EMPTY;
|
||||
m_DirtyStateOrTarget = true;
|
||||
m_State = AiState::spawn;
|
||||
@@ -839,3 +844,73 @@ void BaseCombatAIComponent::IgnoreThreat(const LWOOBJID threat, const float valu
|
||||
SetThreat(threat, 0.0f);
|
||||
m_Target = LWOOBJID_EMPTY;
|
||||
}
|
||||
|
||||
bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
using enum AiState;
|
||||
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
auto& cmptType = reportMsg.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);
|
||||
// if (m_Target != LWOOBJID_EMPTY) {
|
||||
// LWOGameMessages::ObjGetName nameMsg(m_CurrentTarget);
|
||||
// SEND_GAMEOBJ_MSG(nameMsg);
|
||||
// if (!nameMsg.msg.name.empty()) targetInfo.PushDebug("Name") = nameMsg.msg.name;
|
||||
// }
|
||||
|
||||
auto& roundInfo = cmptType.PushDebug("Round Info");
|
||||
// roundInfo.PushDebug<AMFDoubleValue>("Combat Round Time") = m_CombatRoundLength;
|
||||
// roundInfo.PushDebug<AMFDoubleValue>("Minimum Time") = m_MinRoundLength;
|
||||
// roundInfo.PushDebug<AMFDoubleValue>("Maximum Time") = m_MaxRoundLength;
|
||||
// roundInfo.PushDebug<AMFDoubleValue>("Selected Time") = m_SelectedTime;
|
||||
// roundInfo.PushDebug<AMFDoubleValue>("Combat Start Delay") = m_CombatStartDelay;
|
||||
std::string curState;
|
||||
switch (m_State) {
|
||||
case idle: curState = "Idling"; break;
|
||||
case aggro: curState = "Aggroed"; break;
|
||||
case tether: curState = "Returning to Tether"; break;
|
||||
case spawn: curState = "Spawn"; break;
|
||||
case dead: curState = "Dead"; break;
|
||||
default: curState = "Unknown or Undefined"; break;
|
||||
}
|
||||
cmptType.PushDebug<AMFStringValue>("Current Combat State") = curState;
|
||||
|
||||
//switch (m_CombatBehaviorType) {
|
||||
// case 0: curState = "Passive"; break;
|
||||
// case 1: curState = "Aggressive"; break;
|
||||
// case 2: curState = "Passive (Turret)"; break;
|
||||
// case 3: curState = "Aggressive (Turret)"; break;
|
||||
// default: curState = "Unknown or Undefined"; break;
|
||||
//}
|
||||
//cmptType.PushDebug("Current Combat Behavior State") = curState;
|
||||
|
||||
//switch (m_CombatRole) {
|
||||
// case 0: curState = "Melee"; break;
|
||||
// case 1: curState = "Ranged"; break;
|
||||
// case 2: curState = "Support"; break;
|
||||
// default: curState = "Unknown or Undefined"; break;
|
||||
//}
|
||||
//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<AMFDoubleValue>("Hard Tether Radius") = m_HardTetherRadius;
|
||||
cmptType.PushDebug<AMFDoubleValue>("Soft Tether Radius") = m_SoftTetherRadius;
|
||||
cmptType.PushDebug<AMFDoubleValue>("Aggro Radius") = m_AggroRadius;
|
||||
cmptType.PushDebug<AMFDoubleValue>("Tether Speed") = m_TetherSpeed;
|
||||
cmptType.PushDebug<AMFDoubleValue>("Aggro Speed") = m_TetherSpeed;
|
||||
// cmptType.PushDebug<AMFDoubleValue>("Specified Min Range") = m_SpecificMinRange;
|
||||
// cmptType.PushDebug<AMFDoubleValue>("Specified Max Range") = m_SpecificMaxRange;
|
||||
auto& threats = cmptType.PushDebug("Target Threats");
|
||||
for (const auto& [id, threat] : m_ThreatEntries) {
|
||||
threats.PushDebug<AMFDoubleValue>(std::to_string(id)) = threat;
|
||||
}
|
||||
|
||||
auto& ignoredThreats = cmptType.PushDebug("Temp Ignored Threats");
|
||||
for (const auto& [id, threat] : m_ThreatEntries) {
|
||||
ignoredThreats.PushDebug<AMFDoubleValue>(std::to_string(id) + " - Time") = threat;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -234,6 +234,8 @@ public:
|
||||
// Ignore a threat for a certain amount of time
|
||||
void IgnoreThreat(const LWOOBJID target, const float time);
|
||||
|
||||
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Returns the current target or the target that currently is the largest threat to this entity
|
||||
|
||||
@@ -8,15 +8,33 @@
|
||||
#include "GameMessages.h"
|
||||
#include "BitStream.h"
|
||||
#include "eTriggerEventType.h"
|
||||
#include "Amf3.h"
|
||||
|
||||
BouncerComponent::BouncerComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
||||
m_PetEnabled = false;
|
||||
m_PetBouncerEnabled = false;
|
||||
m_PetSwitchLoaded = false;
|
||||
m_Destination = GeneralUtils::TryParse<NiPoint3>(
|
||||
GeneralUtils::SplitString(m_Parent->GetVarAsString(u"bouncer_destination"), '\x1f'))
|
||||
.value_or(NiPoint3Constant::ZERO);
|
||||
m_Speed = GeneralUtils::TryParse<float>(m_Parent->GetVarAsString(u"bouncer_speed")).value_or(-1.0f);
|
||||
m_UsesHighArc = GeneralUtils::TryParse<bool>(m_Parent->GetVarAsString(u"bouncer_uses_high_arc")).value_or(false);
|
||||
m_LockControls = GeneralUtils::TryParse<bool>(m_Parent->GetVarAsString(u"lock_controls")).value_or(false);
|
||||
m_IgnoreCollision = !GeneralUtils::TryParse<bool>(m_Parent->GetVarAsString(u"ignore_collision")).value_or(true);
|
||||
m_StickLanding = GeneralUtils::TryParse<bool>(m_Parent->GetVarAsString(u"stickLanding")).value_or(false);
|
||||
m_UsesGroupName = GeneralUtils::TryParse<bool>(m_Parent->GetVarAsString(u"uses_group_name")).value_or(false);
|
||||
m_GroupName = m_Parent->GetVarAsString(u"grp_name");
|
||||
m_MinNumTargets = GeneralUtils::TryParse<int32_t>(m_Parent->GetVarAsString(u"num_targets_to_activate")).value_or(1);
|
||||
m_CinematicPath = m_Parent->GetVarAsString(u"attached_cinematic_path");
|
||||
|
||||
if (parent->GetLOT() == 7625) {
|
||||
LookupPetSwitch();
|
||||
}
|
||||
|
||||
{
|
||||
using namespace GameMessages;
|
||||
RegisterMsg<GetObjectReportInfo>(this, &BouncerComponent::MsgGetObjectReportInfo);
|
||||
}
|
||||
}
|
||||
|
||||
BouncerComponent::~BouncerComponent() {
|
||||
@@ -94,3 +112,54 @@ void BouncerComponent::LookupPetSwitch() {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool BouncerComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
auto& cmptType = reportMsg.info->PushDebug("Bouncer");
|
||||
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
auto& destPos = cmptType.PushDebug("Destination Position");
|
||||
if (m_Destination != NiPoint3Constant::ZERO) {
|
||||
destPos.PushDebug(m_Destination);
|
||||
} else {
|
||||
destPos.PushDebug("<font color=\'#FF0000\'>WARNING:</font> Bouncer has no target position, is likely missing config data");
|
||||
}
|
||||
|
||||
|
||||
if (m_Speed == -1.0f) {
|
||||
cmptType.PushDebug("<font color=\'#FF0000\'>WARNING:</font> Bouncer has no speed value, is likely missing config data");
|
||||
} else {
|
||||
cmptType.PushDebug<AMFDoubleValue>("Bounce Speed") = m_Speed;
|
||||
}
|
||||
cmptType.PushDebug<AMFStringValue>("Bounce trajectory arc") = m_UsesHighArc ? "High Arc" : "Low Arc";
|
||||
cmptType.PushDebug<AMFBoolValue>("Collision Enabled") = m_IgnoreCollision;
|
||||
cmptType.PushDebug<AMFBoolValue>("Stick Landing") = m_StickLanding;
|
||||
cmptType.PushDebug<AMFBoolValue>("Locks character's controls") = m_LockControls;
|
||||
if (!m_CinematicPath.empty()) cmptType.PushDebug<AMFStringValue>("Cinematic Camera Path (plays during bounce)") = m_CinematicPath;
|
||||
|
||||
auto* switchComponent = m_Parent->GetComponent<SwitchComponent>();
|
||||
auto& respondsToFactions = cmptType.PushDebug("Responds to Factions");
|
||||
if (!switchComponent || switchComponent->GetFactionsToRespondTo().empty()) respondsToFactions.PushDebug("Faction 1");
|
||||
else {
|
||||
for (const auto faction : switchComponent->GetFactionsToRespondTo()) {
|
||||
respondsToFactions.PushDebug(("Faction " + std::to_string(faction)));
|
||||
}
|
||||
}
|
||||
|
||||
cmptType.PushDebug<AMFBoolValue>("Uses a group name for interactions") = m_UsesGroupName;
|
||||
if (!m_UsesGroupName) {
|
||||
if (m_MinNumTargets > 1) {
|
||||
cmptType.PushDebug("<font color=\'#FF0000\'>WARNING:</font> Bouncer has a required number of objects to activate, but no group for interactions.");
|
||||
}
|
||||
|
||||
if (!m_GroupName.empty()) {
|
||||
cmptType.PushDebug("<font color=\'#FF0000\'>WARNING:</font> Has a group name for interactions , but is marked to not use that name.");
|
||||
}
|
||||
} else {
|
||||
if (m_GroupName.empty()) {
|
||||
cmptType.PushDebug("<font color=\'#FF0000\'>WARNING:</font> Set to use a group name for inter actions, but no group name is assigned");
|
||||
}
|
||||
cmptType.PushDebug<AMFIntValue>("Number of interactions to activate bouncer") = m_MinNumTargets;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ public:
|
||||
*/
|
||||
void LookupPetSwitch();
|
||||
|
||||
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Whether this bouncer needs to be activated by a pet
|
||||
@@ -66,6 +68,36 @@ private:
|
||||
* Whether the pet switch for this bouncer has been located
|
||||
*/
|
||||
bool m_PetSwitchLoaded;
|
||||
|
||||
// The bouncer destination
|
||||
NiPoint3 m_Destination;
|
||||
|
||||
// The speed at which the player is bounced
|
||||
float m_Speed{};
|
||||
|
||||
// Whether to use a high arc for the bounce trajectory
|
||||
bool m_UsesHighArc{};
|
||||
|
||||
// Lock controls when bouncing
|
||||
bool m_LockControls{};
|
||||
|
||||
// Ignore collision when bouncing
|
||||
bool m_IgnoreCollision{};
|
||||
|
||||
// Stick the landing afterwards or let the player slide
|
||||
bool m_StickLanding{};
|
||||
|
||||
// Whether or not there is a group name
|
||||
bool m_UsesGroupName{};
|
||||
|
||||
// The group name for targets
|
||||
std::string m_GroupName{};
|
||||
|
||||
// The number of targets to activate the bouncer
|
||||
int32_t m_MinNumTargets{};
|
||||
|
||||
// The cinematic path to play during the bounce
|
||||
std::string m_CinematicPath{};
|
||||
};
|
||||
|
||||
#endif // BOUNCERCOMPONENT_H
|
||||
|
||||
@@ -515,12 +515,12 @@ void CharacterComponent::RocketUnEquip(Entity* player) {
|
||||
}
|
||||
|
||||
void CharacterComponent::TrackMissionCompletion(bool isAchievement) {
|
||||
UpdatePlayerStatistic(MissionsCompleted);
|
||||
|
||||
// Achievements are tracked separately for the zone
|
||||
if (isAchievement) {
|
||||
const auto mapID = Game::zoneManager->GetZoneID().GetMapID();
|
||||
GetZoneStatisticsForMap(mapID).m_AchievementsCollected++;
|
||||
} else {
|
||||
UpdatePlayerStatistic(MissionsCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
#include "CollectibleComponent.h"
|
||||
|
||||
#include "MissionComponent.h"
|
||||
#include "dServer.h"
|
||||
#include "Amf3.h"
|
||||
|
||||
CollectibleComponent::CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId) :
|
||||
Component(parentEntity, componentID), m_CollectibleId(collectibleId) {
|
||||
using namespace GameMessages;
|
||||
RegisterMsg<GetObjectReportInfo>(this, &CollectibleComponent::MsgGetObjectReportInfo);
|
||||
}
|
||||
|
||||
void CollectibleComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) {
|
||||
outBitStream.Write(GetCollectibleId());
|
||||
}
|
||||
|
||||
bool CollectibleComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
auto& cmptType = reportMsg.info->PushDebug("Collectible");
|
||||
auto collectibleID = static_cast<uint32_t>(m_CollectibleId) + static_cast<uint32_t>(Game::server->GetZoneID() << 8);
|
||||
|
||||
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
|
||||
cmptType.PushDebug<AMFIntValue>("Collectible ID") = GetCollectibleId();
|
||||
cmptType.PushDebug<AMFIntValue>("Mission Tracking ID (for save data)") = collectibleID;
|
||||
|
||||
auto* localCharEntity = Game::entityManager->GetEntity(reportMsg.clientID);
|
||||
bool collected = false;
|
||||
if (localCharEntity) {
|
||||
auto* missionComponent = localCharEntity->GetComponent<MissionComponent>();
|
||||
|
||||
if (m_CollectibleId != 0) {
|
||||
collected = missionComponent->HasCollectible(collectibleID);
|
||||
}
|
||||
}
|
||||
|
||||
cmptType.PushDebug<AMFBoolValue>("Has been collected") = collected;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
class CollectibleComponent final : public Component {
|
||||
public:
|
||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::COLLECTIBLE;
|
||||
CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId) : Component(parentEntity, componentID), m_CollectibleId(collectibleId) {}
|
||||
CollectibleComponent(Entity* parentEntity, const int32_t componentID, const int32_t collectibleId);
|
||||
|
||||
int16_t GetCollectibleId() const { return m_CollectibleId; }
|
||||
void Serialize(RakNet::BitStream& outBitStream, bool isConstruction) override;
|
||||
|
||||
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
private:
|
||||
int16_t m_CollectibleId = 0;
|
||||
};
|
||||
|
||||
@@ -1122,8 +1122,8 @@ bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
stats.PushDebug<AMFIntValue>("Imagination") = m_iImagination;
|
||||
stats.PushDebug<AMFDoubleValue>("Maximum Imagination") = m_fMaxImagination;
|
||||
stats.PushDebug<AMFIntValue>("Damage Absorption Points") = m_DamageToAbsorb;
|
||||
destroyableInfo.PushDebug<AMFBoolValue>("Is GM Immune") = m_IsGMImmune;
|
||||
destroyableInfo.PushDebug<AMFBoolValue>("Is Shielded") = m_IsShielded;
|
||||
stats.PushDebug<AMFBoolValue>("Is GM Immune") = m_IsGMImmune;
|
||||
stats.PushDebug<AMFBoolValue>("Is Shielded") = m_IsShielded;
|
||||
destroyableInfo.PushDebug<AMFIntValue>("Attacks To Block") = m_AttacksToBlock;
|
||||
destroyableInfo.PushDebug<AMFIntValue>("Damage Reduction") = m_DamageReduction;
|
||||
std::stringstream factionsStream;
|
||||
@@ -1140,7 +1140,7 @@ bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
|
||||
destroyableInfo.PushDebug<AMFStringValue>("Enemy Factions") = factionsStream.str();
|
||||
|
||||
destroyableInfo.PushDebug<AMFBoolValue>("Is Smashable") = m_IsSmashable;
|
||||
destroyableInfo.PushDebug<AMFBoolValue>("Is A Smashable") = m_IsSmashable;
|
||||
destroyableInfo.PushDebug<AMFBoolValue>("Is Smashed") = m_IsSmashed;
|
||||
destroyableInfo.PushDebug<AMFBoolValue>("Is Module Assembly") = m_IsModuleAssembly;
|
||||
destroyableInfo.PushDebug<AMFDoubleValue>("Explode Factor") = m_ExplodeFactor;
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#include "GhostComponent.h"
|
||||
|
||||
#include "Amf3.h"
|
||||
#include "GameMessages.h"
|
||||
|
||||
GhostComponent::GhostComponent(Entity* parent, const int32_t componentID) : Component(parent, componentID) {
|
||||
m_GhostReferencePoint = NiPoint3Constant::ZERO;
|
||||
m_GhostOverridePoint = NiPoint3Constant::ZERO;
|
||||
m_GhostOverride = false;
|
||||
|
||||
RegisterMsg<GameMessages::GetObjectReportInfo>(this, &GhostComponent::MsgGetObjectReportInfo);
|
||||
}
|
||||
|
||||
GhostComponent::~GhostComponent() {
|
||||
@@ -55,3 +60,12 @@ bool GhostComponent::IsObserved(LWOOBJID id) {
|
||||
void GhostComponent::GhostEntity(LWOOBJID id) {
|
||||
m_ObservedEntities.erase(id);
|
||||
}
|
||||
|
||||
bool GhostComponent::MsgGetObjectReportInfo(GameMessages::GameMsg& msg) {
|
||||
auto& reportMsg = static_cast<GameMessages::GetObjectReportInfo&>(msg);
|
||||
auto& cmptType = reportMsg.info->PushDebug("Ghost");
|
||||
cmptType.PushDebug<AMFIntValue>("Component ID") = GetComponentID();
|
||||
cmptType.PushDebug<AMFBoolValue>("Is GM Invis") = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
|
||||
void GhostEntity(const LWOOBJID id);
|
||||
|
||||
bool MsgGetObjectReportInfo(GameMessages::GameMsg& msg);
|
||||
|
||||
private:
|
||||
NiPoint3 m_GhostReferencePoint;
|
||||
|
||||
|
||||
@@ -29,19 +29,22 @@ ModelComponent::ModelComponent(Entity* parent, const int32_t componentID) : Comp
|
||||
|
||||
bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) {
|
||||
auto& reset = static_cast<GameMessages::ResetModelToDefaults&>(msg);
|
||||
for (auto& behavior : m_Behaviors) behavior.HandleMsg(reset);
|
||||
GameMessages::UnSmash unsmash;
|
||||
unsmash.target = GetParent()->GetObjectID();
|
||||
unsmash.duration = 0.0f;
|
||||
unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
if (reset.bResetBehaviors) for (auto& behavior : m_Behaviors) behavior.HandleMsg(reset);
|
||||
|
||||
m_Parent->SetPosition(m_OriginalPosition);
|
||||
m_Parent->SetRotation(m_OriginalRotation);
|
||||
if (reset.bUnSmash) {
|
||||
GameMessages::UnSmash unsmash;
|
||||
unsmash.target = GetParent()->GetObjectID();
|
||||
unsmash.duration = 0.0f;
|
||||
unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
m_NumActiveUnSmash = 0;
|
||||
}
|
||||
|
||||
if (reset.bResetPos) m_Parent->SetPosition(m_OriginalPosition);
|
||||
if (reset.bResetRot) m_Parent->SetRotation(m_OriginalRotation);
|
||||
m_Parent->SetVelocity(NiPoint3Constant::ZERO);
|
||||
|
||||
m_Speed = 3.0f;
|
||||
m_NumListeningInteract = 0;
|
||||
m_NumActiveUnSmash = 0;
|
||||
|
||||
m_NumActiveAttack = 0;
|
||||
GameMessages::SetFaction set{};
|
||||
|
||||
@@ -67,6 +67,10 @@ public:
|
||||
*/
|
||||
static SwitchComponent* GetClosestSwitch(NiPoint3 position);
|
||||
|
||||
const std::vector<int32_t>& GetFactionsToRespondTo() const {
|
||||
return m_FactionsToRespondTo;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* A list of all pet switches.
|
||||
|
||||
@@ -848,6 +848,11 @@ namespace GameMessages {
|
||||
|
||||
struct ResetModelToDefaults : public GameMsg {
|
||||
ResetModelToDefaults() : GameMsg(MessageType::Game::RESET_MODEL_TO_DEFAULTS) {}
|
||||
|
||||
bool bResetPos{ true };
|
||||
bool bResetRot{ true };
|
||||
bool bUnSmash{ true };
|
||||
bool bResetBehaviors{ true };
|
||||
};
|
||||
|
||||
struct EmotePlayed : public GameMsg {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "ControlBehaviorMsgs.h"
|
||||
#include "tinyxml2.h"
|
||||
#include "ModelComponent.h"
|
||||
#include "StringifiedEnum.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
@@ -178,13 +179,32 @@ void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) {
|
||||
}
|
||||
|
||||
void PropertyBehavior::Update(float deltaTime, ModelComponent& modelComponent) {
|
||||
for (auto& state : m_States | std::views::values) state.Update(deltaTime, modelComponent);
|
||||
auto& activeState = GetActiveState();
|
||||
UpdateResult updateResult{};
|
||||
activeState.Update(deltaTime, modelComponent, updateResult);
|
||||
if (updateResult.newState.has_value() && updateResult.newState.value() != m_ActiveState) {
|
||||
LOG("Behavior %llu is changing from state %s to %s", m_BehaviorId, StringifiedEnum::ToString(m_ActiveState).data(), StringifiedEnum::ToString(updateResult.newState.value()).data());
|
||||
GameMessages::ResetModelToDefaults resetMsg{};
|
||||
resetMsg.bResetPos = false;
|
||||
resetMsg.bResetRot = false;
|
||||
resetMsg.bUnSmash = false;
|
||||
modelComponent.OnResetModelToDefaults(resetMsg);
|
||||
HandleMsg(resetMsg);
|
||||
m_ActiveState = updateResult.newState.value();
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyBehavior::OnChatMessageReceived(const std::string& sMessage) {
|
||||
for (auto& state : m_States | std::views::values) state.OnChatMessageReceived(sMessage);
|
||||
auto& activeState = GetActiveState();
|
||||
activeState.OnChatMessageReceived(sMessage);
|
||||
}
|
||||
|
||||
void PropertyBehavior::OnHit() {
|
||||
for (auto& state : m_States | std::views::values) state.OnHit();
|
||||
auto& activeState = GetActiveState();
|
||||
activeState.OnHit();
|
||||
}
|
||||
|
||||
State& PropertyBehavior::GetActiveState() {
|
||||
DluAssert(m_States.contains(m_ActiveState));
|
||||
return m_States[m_ActiveState];
|
||||
}
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
#ifndef __PROPERTYBEHAVIOR__H__
|
||||
#define __PROPERTYBEHAVIOR__H__
|
||||
|
||||
#include "BehaviorStates.h"
|
||||
#include "State.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace tinyxml2 {
|
||||
class XMLElement;
|
||||
}
|
||||
|
||||
enum class BehaviorState : uint32_t;
|
||||
|
||||
class AMFArrayValue;
|
||||
class BehaviorMessageBase;
|
||||
class ModelComponent;
|
||||
|
||||
struct UpdateResult {
|
||||
std::optional<BehaviorState> newState;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the Entity of a Property Behavior and holds data associated with the behavior
|
||||
*/
|
||||
@@ -45,6 +50,7 @@ public:
|
||||
void OnHit();
|
||||
|
||||
private:
|
||||
State& GetActiveState();
|
||||
// The current active behavior state. Behaviors can only be in ONE state at a time.
|
||||
BehaviorState m_ActiveState;
|
||||
|
||||
|
||||
@@ -163,8 +163,8 @@ void State::Deserialize(const tinyxml2::XMLElement& state) {
|
||||
}
|
||||
}
|
||||
|
||||
void State::Update(float deltaTime, ModelComponent& modelComponent) {
|
||||
for (auto& strip : m_Strips) strip.Update(deltaTime, modelComponent);
|
||||
void State::Update(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult) {
|
||||
for (auto& strip : m_Strips) strip.Update(deltaTime, modelComponent, updateResult);
|
||||
}
|
||||
|
||||
void State::OnChatMessageReceived(const std::string& sMessage) {
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace tinyxml2 {
|
||||
|
||||
class AMFArrayValue;
|
||||
class ModelComponent;
|
||||
struct UpdateResult;
|
||||
|
||||
class State {
|
||||
public:
|
||||
@@ -21,7 +22,7 @@ public:
|
||||
void Serialize(tinyxml2::XMLElement& state) const;
|
||||
void Deserialize(const tinyxml2::XMLElement& state);
|
||||
|
||||
void Update(float deltaTime, ModelComponent& modelComponent);
|
||||
void Update(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult);
|
||||
|
||||
void OnChatMessageReceived(const std::string& sMessage);
|
||||
void OnHit();
|
||||
|
||||
@@ -160,13 +160,14 @@ void Strip::SpawnDrop(LOT dropLOT, Entity& entity) {
|
||||
}
|
||||
}
|
||||
|
||||
void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
|
||||
void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult) {
|
||||
auto& entity = *modelComponent.GetParent();
|
||||
auto& nextAction = GetNextAction();
|
||||
auto number = nextAction.GetValueParameterDouble();
|
||||
auto valueStr = nextAction.GetValueParameterString();
|
||||
auto numberAsInt = static_cast<int32_t>(number);
|
||||
auto nextActionType = GetNextAction().GetType();
|
||||
LOG_DEBUG("Processing Strip Action: %s with number %.2f and string %s", nextActionType.data(), number, valueStr.data());
|
||||
|
||||
// TODO replace with switch case and nextActionType with enum
|
||||
/* BEGIN Move */
|
||||
@@ -223,7 +224,8 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
|
||||
unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
modelComponent.AddUnSmash();
|
||||
|
||||
m_PausedTime = number;
|
||||
// since it may take time for the message to relay to clients
|
||||
m_PausedTime = number + 0.5f;
|
||||
} else if (nextActionType == "Wait") {
|
||||
m_PausedTime = number;
|
||||
} else if (nextActionType == "Chat") {
|
||||
@@ -258,6 +260,21 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
|
||||
for (; numberAsInt > 0; numberAsInt--) SpawnDrop(6431, entity); // 1 Armor powerup
|
||||
}
|
||||
/* END Gameplay */
|
||||
/* BEGIN StateMachine */
|
||||
else if (nextActionType == "ChangeStateHome") {
|
||||
updateResult.newState = BehaviorState::HOME_STATE;
|
||||
} else if (nextActionType == "ChangeStateCircle") {
|
||||
updateResult.newState = BehaviorState::CIRCLE_STATE;
|
||||
} else if (nextActionType == "ChangeStateSquare") {
|
||||
updateResult.newState = BehaviorState::SQUARE_STATE;
|
||||
} else if (nextActionType == "ChangeStateDiamond") {
|
||||
updateResult.newState = BehaviorState::DIAMOND_STATE;
|
||||
} else if (nextActionType == "ChangeStateTriangle") {
|
||||
updateResult.newState = BehaviorState::TRIANGLE_STATE;
|
||||
} else if (nextActionType == "ChangeStateStar") {
|
||||
updateResult.newState = BehaviorState::STAR_STATE;
|
||||
}
|
||||
/* END StateMachine*/
|
||||
else {
|
||||
static std::set<std::string> g_WarnedActions;
|
||||
if (!g_WarnedActions.contains(nextActionType.data())) {
|
||||
@@ -330,7 +347,7 @@ bool Strip::CheckMovement(float deltaTime, ModelComponent& modelComponent) {
|
||||
return moveFinished;
|
||||
}
|
||||
|
||||
void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
|
||||
void Strip::Update(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult) {
|
||||
// No point in running a strip with only one action.
|
||||
// Strips are also designed to have 2 actions or more to run.
|
||||
if (!HasMinimumActions()) return;
|
||||
@@ -354,9 +371,9 @@ void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
|
||||
|
||||
// Check for trigger blocks and if not a trigger block proc this blocks action
|
||||
if (m_NextActionIndex == 0) {
|
||||
LOG("Behavior strip started %s", nextAction.GetType().data());
|
||||
if (nextAction.GetType() == "OnInteract") {
|
||||
modelComponent.AddInteract();
|
||||
|
||||
} else if (nextAction.GetType() == "OnChat") {
|
||||
// logic here if needed
|
||||
} else if (nextAction.GetType() == "OnAttack") {
|
||||
@@ -365,7 +382,7 @@ void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
|
||||
Game::entityManager->SerializeEntity(entity);
|
||||
m_WaitingForAction = true;
|
||||
} else { // should be a normal block
|
||||
ProcNormalAction(deltaTime, modelComponent);
|
||||
ProcNormalAction(deltaTime, modelComponent, updateResult);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace tinyxml2 {
|
||||
|
||||
class AMFArrayValue;
|
||||
class ModelComponent;
|
||||
struct UpdateResult;
|
||||
|
||||
class Strip {
|
||||
public:
|
||||
@@ -33,9 +34,9 @@ public:
|
||||
// Checks the movement logic for whether or not to proceed
|
||||
// Returns true if the movement can continue, false if it needs to wait more.
|
||||
bool CheckMovement(float deltaTime, ModelComponent& modelComponent);
|
||||
void Update(float deltaTime, ModelComponent& modelComponent);
|
||||
void Update(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult);
|
||||
void SpawnDrop(LOT dropLOT, Entity& entity);
|
||||
void ProcNormalAction(float deltaTime, ModelComponent& modelComponent);
|
||||
void ProcNormalAction(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult);
|
||||
void RemoveStates(ModelComponent& modelComponent) const;
|
||||
|
||||
// 2 actions are required for strips to work
|
||||
|
||||
@@ -355,7 +355,7 @@ void DropLoot(Entity* player, const LWOOBJID source, const std::map<LOT, LootDro
|
||||
|
||||
// Coin roll is divided up between the members, rounded up, then dropped for each player
|
||||
const uint32_t coinRoll = static_cast<uint32_t>(minCoins + GeneralUtils::GenerateRandomNumber<float>(0, 1) * (maxCoins - minCoins));
|
||||
const auto droppedCoins = team ? std::ceil(coinRoll / team->members.size()) : coinRoll;
|
||||
const auto droppedCoins = team ? std::ceil(static_cast<float>(coinRoll) / team->members.size()) : coinRoll;
|
||||
if (team) {
|
||||
for (auto member : team->members) {
|
||||
GameMessages::DropClientLoot lootMsg{};
|
||||
@@ -365,6 +365,7 @@ void DropLoot(Entity* player, const LWOOBJID source, const std::map<LOT, LootDro
|
||||
lootMsg.spawnPos = spawnPosition;
|
||||
lootMsg.sourceID = source;
|
||||
lootMsg.item = LOT_NULL;
|
||||
CalcFinalDropPos(lootMsg);
|
||||
lootMsg.Send();
|
||||
const auto* const memberEntity = Game::entityManager->GetEntity(member);
|
||||
if (memberEntity) lootMsg.Send(memberEntity->GetSystemAddress());
|
||||
@@ -377,6 +378,7 @@ void DropLoot(Entity* player, const LWOOBJID source, const std::map<LOT, LootDro
|
||||
lootMsg.spawnPos = spawnPosition;
|
||||
lootMsg.sourceID = source;
|
||||
lootMsg.item = LOT_NULL;
|
||||
CalcFinalDropPos(lootMsg);
|
||||
lootMsg.Send();
|
||||
lootMsg.Send(player->GetSystemAddress());
|
||||
}
|
||||
|
||||
@@ -817,42 +817,6 @@ 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{
|
||||
@@ -1088,7 +1052,7 @@ void SlashCommandHandler::Startup() {
|
||||
.info = "Resurrects the player",
|
||||
.aliases = { "resurrect" },
|
||||
.handle = GMZeroCommands::Resurrect,
|
||||
.requiredLevel = eGameMasterLevel::CIVILIAN
|
||||
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||
};
|
||||
RegisterCommand(ResurrectCommand);
|
||||
|
||||
|
||||
@@ -1841,279 +1841,4 @@ 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;
|
||||
}
|
||||
|
||||
// Spawn at all sceneMap points in the current scene
|
||||
uint32_t spawnedCount = 0;
|
||||
|
||||
for (const auto& chunk : raw.chunks) {
|
||||
if (chunk.sceneMap.empty() || chunk.colorMapResolution == 0 || chunk.heightMap.empty()) continue;
|
||||
|
||||
// Iterate through the heightmap (same as GenerateTerrainMesh)
|
||||
for (uint32_t i = 0; i < chunk.width; ++i) {
|
||||
for (uint32_t j = 0; j < chunk.height; ++j) {
|
||||
// Get height at this position
|
||||
const uint32_t heightIndex = chunk.width * i + j;
|
||||
if (heightIndex >= chunk.heightMap.size()) continue;
|
||||
|
||||
const float y = chunk.heightMap[heightIndex];
|
||||
|
||||
// Map heightmap position to scene map position (same as GenerateTerrainMesh)
|
||||
const float sceneMapI = ((i) / (chunk.width - 1)) * (chunk.colorMapResolution - 1);
|
||||
const float sceneMapJ = ((j) / (chunk.height - 1)) * (chunk.colorMapResolution - 1);
|
||||
|
||||
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
|
||||
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), chunk.colorMapResolution - 1);
|
||||
const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ;
|
||||
|
||||
uint8_t sceneID = 0;
|
||||
if (sceneIndex < chunk.sceneMap.size()) {
|
||||
sceneID = chunk.sceneMap[sceneIndex];
|
||||
}
|
||||
|
||||
// Check if this point belongs to the current scene
|
||||
if (sceneID == currentSceneID.GetSceneID()) {
|
||||
// Calculate world position (same as GenerateTerrainMesh)
|
||||
const float worldX = ((i) + (chunk.offsetX / chunk.scaleFactor)) * chunk.scaleFactor;
|
||||
const float worldY = (y / chunk.scaleFactor) * chunk.scaleFactor;
|
||||
const float worldZ = ((j) + (chunk.offsetZ / chunk.scaleFactor)) * chunk.scaleFactor;
|
||||
|
||||
NiPoint3 spawnPos(worldX, worldY, worldZ); EntityInfo info;
|
||||
info.lot = lot + currentSceneID.GetSceneID(); // to differentiate scenes
|
||||
info.pos = spawnPos;
|
||||
info.rot = QuatUtils::IDENTITY;
|
||||
info.spawner = nullptr;
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
info.spawnerNodeID = 0;
|
||||
info.settings = { new LDFData<bool>(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 LOT " << lot + currentSceneID.GetSceneID() << " at " << spawnedCount << " points 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;
|
||||
}
|
||||
|
||||
// Spawn at all sceneMap points across all scenes
|
||||
uint32_t spawnedCount = 0;
|
||||
std::map<uint8_t, uint32_t> sceneSpawnCounts; // Track spawns per scene
|
||||
|
||||
for (const auto& chunk : raw.chunks) {
|
||||
if (chunk.sceneMap.empty() || chunk.colorMapResolution == 0 || chunk.heightMap.empty()) continue;
|
||||
|
||||
// Iterate through the heightmap (same as GenerateTerrainMesh)
|
||||
for (uint32_t i = 0; i < chunk.width; ++i) {
|
||||
for (uint32_t j = 0; j < chunk.height; ++j) {
|
||||
// Get height at this position
|
||||
const uint32_t heightIndex = chunk.width * i + j;
|
||||
if (heightIndex >= chunk.heightMap.size()) continue;
|
||||
|
||||
const float y = chunk.heightMap[heightIndex];
|
||||
|
||||
// Map heightmap position to scene map position (same as GenerateTerrainMesh)
|
||||
const float sceneMapI = ((i) / (chunk.width - 1)) * (chunk.colorMapResolution - 1);
|
||||
const float sceneMapJ = ((j) / (chunk.height - 1)) * (chunk.colorMapResolution - 1);
|
||||
|
||||
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
|
||||
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), chunk.colorMapResolution - 1);
|
||||
const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ;
|
||||
|
||||
uint8_t sceneID = 0;
|
||||
if (sceneIndex < chunk.sceneMap.size()) {
|
||||
sceneID = chunk.sceneMap[sceneIndex];
|
||||
}
|
||||
|
||||
// Skip invalid scenes (scene ID 0 typically means no scene)
|
||||
if (sceneID == 0) continue;
|
||||
|
||||
// Calculate world position (same as GenerateTerrainMesh)
|
||||
const float worldX = ((i) + (chunk.offsetX / chunk.scaleFactor)) * chunk.scaleFactor;
|
||||
const float worldY = (y / chunk.scaleFactor) * chunk.scaleFactor;
|
||||
const float worldZ = ((j) + (chunk.offsetZ / chunk.scaleFactor)) * chunk.scaleFactor;
|
||||
|
||||
NiPoint3 spawnPos(worldX, worldY, worldZ); EntityInfo info;
|
||||
info.lot = lot + sceneID; // to show different scenes
|
||||
info.pos = spawnPos;
|
||||
info.rot = QuatUtils::IDENTITY;
|
||||
info.spawner = nullptr;
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
info.spawnerNodeID = 0;
|
||||
info.settings = { new LDFData<bool>(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 LOT " << spawnedCount << " total points 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,10 +77,6 @@ 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,5 +1,11 @@
|
||||
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,5 +1,6 @@
|
||||
#include "dNavMesh.h"
|
||||
|
||||
#include "RawFile.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Logger.h"
|
||||
|
||||
3
dNavigation/dTerrain/CMakeLists.txt
Normal file
3
dNavigation/dTerrain/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
set(DNAVIGATIONS_DTERRAIN_SOURCES "RawFile.cpp"
|
||||
"RawChunk.cpp"
|
||||
"RawHeightMap.cpp" PARENT_SCOPE)
|
||||
92
dNavigation/dTerrain/RawChunk.cpp
Normal file
92
dNavigation/dTerrain/RawChunk.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#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
|
||||
|
||||
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;
|
||||
}
|
||||
24
dNavigation/dTerrain/RawChunk.h
Normal file
24
dNavigation/dTerrain/RawChunk.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#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;
|
||||
};
|
||||
84
dNavigation/dTerrain/RawFile.cpp
Normal file
84
dNavigation/dTerrain/RawFile.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#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';
|
||||
}
|
||||
}
|
||||
28
dNavigation/dTerrain/RawFile.h
Normal file
28
dNavigation/dTerrain/RawFile.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#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;
|
||||
};
|
||||
27
dNavigation/dTerrain/RawHeightMap.cpp
Normal file
27
dNavigation/dTerrain/RawHeightMap.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#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() {
|
||||
|
||||
}
|
||||
21
dNavigation/dTerrain/RawHeightMap.h
Normal file
21
dNavigation/dTerrain/RawHeightMap.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#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 = {};
|
||||
};
|
||||
10
dNavigation/dTerrain/RawMesh.h
Normal file
10
dNavigation/dTerrain/RawMesh.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "NiPoint3.h"
|
||||
|
||||
struct RawMesh {
|
||||
std::vector<NiPoint3> m_Vertices;
|
||||
std::vector<uint32_t> m_Triangles;
|
||||
};
|
||||
@@ -55,36 +55,20 @@ void GfBanana::OnHit(Entity* self, Entity* attacker) {
|
||||
|
||||
return;
|
||||
}
|
||||
bananaEntity->Smash(LWOOBJID_EMPTY, eKillType::SILENT);
|
||||
|
||||
bananaEntity->SetPosition(bananaEntity->GetPosition() - NiPoint3Constant::UNIT_Y * 8);
|
||||
|
||||
auto* bananaDestroyable = bananaEntity->GetComponent<DestroyableComponent>();
|
||||
|
||||
bananaDestroyable->SetHealth(0);
|
||||
|
||||
bananaDestroyable->Smash(attacker->GetObjectID());
|
||||
|
||||
/*
|
||||
auto position = self->GetPosition();
|
||||
const auto rotation = self->GetRotation();
|
||||
|
||||
position.y += 12;
|
||||
position.x -= rotation.GetRightVector().x * 5;
|
||||
position.z -= rotation.GetRightVector().z * 5;
|
||||
|
||||
EntityInfo info {};
|
||||
|
||||
info.pos = position;
|
||||
info.rot = rotation;
|
||||
EntityInfo info{};
|
||||
info.lot = 6718;
|
||||
info.pos = self->GetPosition();
|
||||
info.pos.y += 12;
|
||||
info.pos.x -= QuatUtils::Right(rotation).x * 5;
|
||||
info.pos.z -= QuatUtils::Right(rotation).z * 5;
|
||||
info.rot = rotation;
|
||||
info.spawnerID = self->GetObjectID();
|
||||
|
||||
auto* entity = Game::entityManager->CreateEntity(info);
|
||||
|
||||
Game::entityManager->ConstructEntity(entity, UNASSIGNED_SYSTEM_ADDRESS);
|
||||
*/
|
||||
|
||||
Game::entityManager->SerializeEntity(self);
|
||||
info.settings = { new LDFData<uint32_t>(u"motionType", 5) };
|
||||
auto* const newEn = Game::entityManager->CreateEntity(info, nullptr, self);
|
||||
Game::entityManager->ConstructEntity(newEn);
|
||||
}
|
||||
|
||||
void GfBanana::OnTimerDone(Entity* self, std::string timerName) {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "GfBananaCluster.h"
|
||||
#include "Entity.h"
|
||||
#include "dpWorld.h"
|
||||
#include "dNavMesh.h"
|
||||
#include "Loot.h"
|
||||
#include "DestroyableComponent.h"
|
||||
|
||||
void GfBananaCluster::OnStartup(Entity* self) {
|
||||
self->AddTimer("startup", 100);
|
||||
@@ -10,3 +14,21 @@ void GfBananaCluster::OnTimerDone(Entity* self, std::string timerName) {
|
||||
self->ScheduleKillAfterUpdate(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Hack in banana loot dropping from tree area since it seemed to do that in live for some reason
|
||||
void GfBananaCluster::OnHit(Entity* self, Entity* attacker) {
|
||||
auto* parentEntity = self->GetParentEntity();
|
||||
GameMessages::GetPosition posMsg{};
|
||||
if (parentEntity) {
|
||||
posMsg.target = parentEntity->GetObjectID();
|
||||
}
|
||||
posMsg.Send();
|
||||
|
||||
const auto rotation = parentEntity ? parentEntity->GetRotation() : self->GetRotation();
|
||||
|
||||
if (dpWorld::GetNavMesh()) posMsg.pos.y = dpWorld::GetNavMesh()->GetHeightAtPoint(posMsg.pos) + 3.0f;
|
||||
else posMsg.pos = posMsg.pos - (NiPoint3Constant::UNIT_Y * 8);
|
||||
posMsg.pos.x -= QuatUtils::Right(rotation).x * 5;
|
||||
posMsg.pos.z -= QuatUtils::Right(rotation).z * 5;
|
||||
self->SetPosition(posMsg.pos);
|
||||
}
|
||||
|
||||
@@ -7,4 +7,5 @@ public:
|
||||
void OnStartup(Entity* self) override;
|
||||
|
||||
void OnTimerDone(Entity* self, std::string timerName) override;
|
||||
void OnHit(Entity* self, Entity* attacker) override;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
set(DZONEMANAGER_SOURCES "dZoneManager.cpp"
|
||||
"Level.cpp"
|
||||
"Raw.cpp"
|
||||
"Spawner.cpp"
|
||||
"Zone.cpp")
|
||||
|
||||
@@ -15,7 +14,6 @@ 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
|
||||
|
||||
@@ -1,415 +0,0 @@
|
||||
#include "Raw.h"
|
||||
#include "BinaryIO.h"
|
||||
#include "Logger.h"
|
||||
#include "SceneColor.h"
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
|
||||
namespace Raw {
|
||||
|
||||
/**
|
||||
* @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
|
||||
uint32_t heightMapSize = chunk.width * chunk.height;
|
||||
|
||||
chunk.heightMap.resize(heightMapSize);
|
||||
for (uint32_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; // Default to chunk width for older versions
|
||||
}
|
||||
|
||||
uint32_t colorMapPixelCount = chunk.colorMapResolution * chunk.colorMapResolution * 4; // RGBA
|
||||
chunk.colorMap.resize(colorMapPixelCount);
|
||||
stream.read(reinterpret_cast<char*>(chunk.colorMap.data()), colorMapPixelCount);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
// LightMap/diffusemap.dds
|
||||
uint32_t lightMapSize;
|
||||
BinaryIO::BinaryRead(stream, lightMapSize);
|
||||
|
||||
chunk.lightMap.resize(lightMapSize);
|
||||
stream.read(reinterpret_cast<char*>(chunk.lightMap.data()), lightMapSize);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TextureMap
|
||||
if (version >= 32) {
|
||||
BinaryIO::BinaryRead(stream, chunk.textureMapResolution);
|
||||
} else {
|
||||
chunk.textureMapResolution = chunk.width; // Default to chunk width for older versions
|
||||
}
|
||||
|
||||
uint32_t textureMapPixelCount = chunk.textureMapResolution * chunk.textureMapResolution * 4;
|
||||
chunk.textureMap.resize(textureMapPixelCount);
|
||||
stream.read(reinterpret_cast<char*>(chunk.textureMap.data()), textureMapPixelCount);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Texture settings
|
||||
BinaryIO::BinaryRead(stream, chunk.textureSettings);
|
||||
|
||||
// Blend map DDS
|
||||
uint32_t blendMapDDSSize;
|
||||
BinaryIO::BinaryRead(stream, blendMapDDSSize);
|
||||
|
||||
chunk.blendMap.resize(blendMapDDSSize);
|
||||
stream.read(reinterpret_cast<char*>(chunk.blendMap.data()), blendMapDDSSize);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read flairs
|
||||
uint32_t numFlairs;
|
||||
BinaryIO::BinaryRead(stream, numFlairs);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
chunk.flairs.resize(numFlairs);
|
||||
for (uint32_t i = 0; i < numFlairs; ++i) {
|
||||
if (!ReadFlairAttributes(stream, chunk.flairs[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Scene map (version 32+ only)
|
||||
if (version >= 32) {
|
||||
uint32_t sceneMapSize = chunk.colorMapResolution * chunk.colorMapResolution;
|
||||
|
||||
chunk.sceneMap.resize(sceneMapSize);
|
||||
stream.read(reinterpret_cast<char*>(chunk.sceneMap.data()), sceneMapSize);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Mesh vertex usage (read size first, then check if empty)
|
||||
BinaryIO::BinaryRead(stream, chunk.vertSize);
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mesh vert usage
|
||||
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;
|
||||
}
|
||||
|
||||
// Only continue with mesh data if we have vertex usage data
|
||||
if (chunk.vertSize == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Mesh vert size (16 elements)
|
||||
chunk.meshVertSize.resize(16);
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
BinaryIO::BinaryRead(stream, chunk.meshVertSize[i]);
|
||||
}
|
||||
|
||||
if (stream.fail()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mesh triangles (16 elements)
|
||||
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);
|
||||
|
||||
// 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) {
|
||||
// Calculate chunk bounds
|
||||
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);
|
||||
|
||||
// Update overall bounds
|
||||
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 %u chunks", raw.chunks.size());
|
||||
|
||||
uint32_t vertexOffset = 0;
|
||||
|
||||
for (const auto& chunk : raw.chunks) {
|
||||
// Skip chunks without scene maps
|
||||
if (chunk.sceneMap.empty() || chunk.colorMapResolution == 0 || chunk.heightMap.empty()) {
|
||||
LOG("Skipping chunk %u (sceneMap: %zu, colorMapRes: %u, heightMap: %zu)",
|
||||
chunk.id, chunk.sceneMap.size(), chunk.colorMapResolution, chunk.heightMap.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG("Processing chunk %u: width=%u, height=%u, colorMapRes=%u, sceneMapSize=%zu",
|
||||
chunk.id, chunk.width, chunk.height, chunk.colorMapResolution, chunk.sceneMap.size());
|
||||
|
||||
// Generate vertices for this chunk
|
||||
for (uint32_t i = 0; i < chunk.width; ++i) {
|
||||
for (uint32_t j = 0; j < chunk.height; ++j) {
|
||||
// Get height at this position
|
||||
const uint32_t heightIndex = chunk.width * i + j;
|
||||
if (heightIndex >= chunk.heightMap.size()) continue;
|
||||
|
||||
const float y = chunk.heightMap[heightIndex];
|
||||
|
||||
// Calculate world position
|
||||
const float worldX = ((i) + (chunk.offsetX / chunk.scaleFactor)) * chunk.scaleFactor;
|
||||
const float worldY = (y / chunk.scaleFactor) * chunk.scaleFactor;
|
||||
const float worldZ = ((j) + (chunk.offsetZ / chunk.scaleFactor)) * chunk.scaleFactor;
|
||||
|
||||
const NiPoint3 worldPos(worldX, worldY, worldZ);
|
||||
|
||||
// Get scene ID at this position
|
||||
// Map heightmap position to scene map position
|
||||
// The scene map is colorMapResolution x colorMapResolution
|
||||
// We need to map from heightmap coordinates (i, j) to scene map coordinates
|
||||
const float sceneMapI = ((i) / (chunk.width - 1)) * (chunk.colorMapResolution - 1);
|
||||
const float sceneMapJ = ((j) / (chunk.height - 1)) * (chunk.colorMapResolution - 1);
|
||||
|
||||
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
|
||||
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), chunk.colorMapResolution - 1);
|
||||
// Scene map uses the same indexing pattern as heightmap: row * width + col
|
||||
const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ;
|
||||
|
||||
uint8_t sceneID = 0;
|
||||
if (sceneIndex < chunk.sceneMap.size()) {
|
||||
sceneID = chunk.sceneMap[sceneIndex];
|
||||
}
|
||||
outMesh.vertices.emplace_back(worldPos, sceneID);
|
||||
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;
|
||||
}
|
||||
|
||||
// Create instance of SceneColor for color lookup
|
||||
SceneColor sceneColor;
|
||||
|
||||
// Write vertices with colors
|
||||
// OBJ format supports vertex colors as: v x y z r g b
|
||||
for (const auto& v : mesh.vertices) {
|
||||
file << "v " << v.position.x << ' ' << v.position.y << ' ' << v.position.z;
|
||||
|
||||
uint8_t sceneID = v.sceneID;
|
||||
|
||||
const NiColor& color = sceneColor.Get(sceneID);
|
||||
file << ' ' << color.m_Red << ' ' << color.m_Green << ' ' << color.m_Blue;
|
||||
file << '\n';
|
||||
}
|
||||
|
||||
// Write faces (triangles)
|
||||
for (size_t i = 0; i < mesh.triangles.size(); i += 3) {
|
||||
// OBJ indices are 1-based
|
||||
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
|
||||
@@ -1,162 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef __RAW_H__
|
||||
#define __RAW_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <istream>
|
||||
#include <map>
|
||||
#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;
|
||||
|
||||
// Unknown data for version < 32
|
||||
std::vector<uint8_t> unknown1;
|
||||
std::vector<uint8_t> unknown2;
|
||||
};
|
||||
|
||||
/**
|
||||
* @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,7 +8,6 @@
|
||||
#include "GeneralUtils.h"
|
||||
#include "BinaryIO.h"
|
||||
#include "LUTriggers.h"
|
||||
#include "dConfig.h"
|
||||
|
||||
#include "AssetManager.h"
|
||||
#include "CDClientManager.h"
|
||||
@@ -21,7 +20,6 @@
|
||||
#include "eTriggerEventType.h"
|
||||
#include "eWaypointCommandType.h"
|
||||
#include "dNavMesh.h"
|
||||
#include "Raw.h"
|
||||
|
||||
Zone::Zone(const LWOZONEID zoneID) :
|
||||
m_ZoneID(zoneID) {
|
||||
@@ -57,11 +55,6 @@ void Zone::LoadZoneIntoMemory() {
|
||||
if (file) {
|
||||
BinaryIO::BinaryRead(file, m_FileFormatVersion);
|
||||
|
||||
if (m_FileFormatVersion < Zone::FileFormatVersion::PrePreAlpha) {
|
||||
LOG("Zone %s is too old to be supported, please update the map", m_ZoneFilePath.c_str());
|
||||
throw std::runtime_error("Aborting Zone loading due to old Zone File.");
|
||||
}
|
||||
|
||||
uint32_t mapRevision = 0;
|
||||
if (m_FileFormatVersion >= Zone::FileFormatVersion::Alpha) BinaryIO::BinaryRead(file, mapRevision);
|
||||
|
||||
@@ -91,38 +84,6 @@ void Zone::LoadZoneIntoMemory() {
|
||||
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.");
|
||||
}
|
||||
|
||||
if (m_FileFormatVersion >= Zone::FileFormatVersion::PrePreAlpha) {
|
||||
auto rawFile = Game::assetManager->GetFile(zoneFolderPath + m_ZoneRawPath);
|
||||
if (!Raw::ReadRaw(rawFile, m_Raw)) {
|
||||
LOG("Failed to parse %s", (zoneFolderPath + m_ZoneRawPath).c_str());
|
||||
throw std::runtime_error("Aborting Zone loading due to invalid Raw File.");
|
||||
}
|
||||
LOG("Loaded Raw Terrain with %u chunks", m_Raw.numChunks);
|
||||
|
||||
|
||||
// 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 %llu vertices and %llu 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());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG("Zone %s is too old to have Raw data, please update the map", m_ZoneFilePath.c_str());
|
||||
}
|
||||
|
||||
if (m_FileFormatVersion >= Zone::FileFormatVersion::PreAlpha) {
|
||||
BinaryIO::BinaryRead(file, m_NumberOfSceneTransitionsLoaded);
|
||||
for (uint32_t i = 0; i < m_NumberOfSceneTransitionsLoaded; ++i) {
|
||||
@@ -522,9 +483,3 @@ 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;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include "Raw.h"
|
||||
|
||||
namespace LUTriggers {
|
||||
struct Trigger;
|
||||
@@ -229,12 +228,6 @@ 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;
|
||||
@@ -251,8 +244,6 @@ 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;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "WorldConfig.h"
|
||||
#include "CDZoneTableTable.h"
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include "eObjectBits.h"
|
||||
#include "CDZoneTableTable.h"
|
||||
#include "AssetManager.h"
|
||||
@@ -63,9 +62,6 @@ 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();
|
||||
@@ -302,133 +298,3 @@ 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.sceneMap.empty()) continue;
|
||||
|
||||
// Reverse: worldX = (i + offsetX/scaleFactor) * scaleFactor
|
||||
// Therefore: i = worldX/scaleFactor - offsetX/scaleFactor
|
||||
const float heightI = posX / chunk.scaleFactor - (chunk.offsetX / chunk.scaleFactor);
|
||||
const float heightJ = posZ / chunk.scaleFactor - (chunk.offsetZ / chunk.scaleFactor);
|
||||
|
||||
// Check if position is within this chunk's heightmap bounds
|
||||
if (heightI >= 0.0f && heightI < static_cast<float>(chunk.width) &&
|
||||
heightJ >= 0.0f && heightJ < static_cast<float>(chunk.height)) {
|
||||
|
||||
// Map heightmap position to scene map position (same as GenerateTerrainMesh)
|
||||
const float sceneMapI = (heightI / static_cast<float>(chunk.width - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
|
||||
const float sceneMapJ = (heightJ / static_cast<float>(chunk.height - 1)) * static_cast<float>(chunk.colorMapResolution - 1);
|
||||
|
||||
const uint32_t sceneI = std::min(static_cast<uint32_t>(sceneMapI), chunk.colorMapResolution - 1);
|
||||
const uint32_t sceneJ = std::min(static_cast<uint32_t>(sceneMapJ), chunk.colorMapResolution - 1);
|
||||
|
||||
// Scene map uses the same indexing pattern as heightmap: row * width + col
|
||||
const uint32_t sceneIndex = sceneI * chunk.colorMapResolution + sceneJ;
|
||||
|
||||
// Bounds check
|
||||
if (sceneIndex >= chunk.sceneMap.size()) {
|
||||
return LWOSCENEID_INVALID;
|
||||
}
|
||||
|
||||
// Get scene ID from sceneMap
|
||||
const uint8_t sceneID = chunk.sceneMap[sceneIndex];
|
||||
|
||||
// Return the scene ID
|
||||
return LWOSCENEID(sceneID, 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) {
|
||||
// Ensure every scene has an entry, even if it has no transitions
|
||||
if (m_SceneAdjacencyList.find(sceneID) == m_SceneAdjacencyList.end()) {
|
||||
m_SceneAdjacencyList[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) {
|
||||
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(m_ZoneID.GetMapID(), 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,30 +53,6 @@ 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();
|
||||
@@ -84,10 +60,6 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
/**
|
||||
* Builds the scene graph adjacency list from scene transitions
|
||||
*/
|
||||
void BuildSceneGraph();
|
||||
/**
|
||||
* The starting unique mission ID.
|
||||
*/
|
||||
@@ -103,9 +75,4 @@ 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,9 +103,5 @@ 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