Compare commits

..

5 Commits

Author SHA1 Message Date
David Markowitz
b34236b900 Merge branch 'main' into loot-rework 2025-10-15 20:21:13 -07:00
David Markowitz
bb654752ca fix: coin dupe on team 2025-10-15 20:20:27 -07:00
David Markowitz
8384126783 change default team loot to shared 2025-10-13 21:17:11 -07:00
David Markowitz
409053e7af Allow dupe powerup pickups 2025-10-13 20:26:00 -07:00
David Markowitz
28c6bfcbd7 feat: Loot rework 2025-10-13 19:03:26 -07:00
35 changed files with 373 additions and 1508 deletions

View File

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

View File

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

View File

@@ -424,7 +424,6 @@ void Entity::Initialize() {
comp->SetIsSmashable(destCompData[0].isSmashable);
comp->SetLootMatrixID(destCompData[0].LootMatrixIndex);
comp->SetCurrencyIndex(destCompData[0].CurrencyIndex);
Loot::CacheMatrix(destCompData[0].LootMatrixIndex);
// Now get currency information
@@ -2253,7 +2252,6 @@ bool Entity::MsgRequestServerObjectInfo(GameMessages::GameMsg& msg) {
response.Insert("objectID", std::to_string(m_ObjectID));
response.Insert("serverInfo", true);
GameMessages::GetObjectReportInfo info{};
info.clientID = requestInfo.clientId;
info.bVerbose = requestInfo.bVerbose;
info.info = response.InsertArray("data");
auto& objectInfo = info.info->PushDebug("Object Details");

View File

@@ -3,9 +3,6 @@
#include "Logger.h"
#include "Game.h"
#include "dConfig.h"
#include "CDLootMatrixTable.h"
#include "CDLootTableTable.h"
#include "CDRarityTableTable.h"
#include "Amf3.h"
#include "AmfSerialize.h"
@@ -985,14 +982,7 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
for (const auto item : itemMap | std::views::values) {
// Don't drop excluded items or null ones
if (!item || Game::entityManager->GetHardcoreExcludedItemDrops().contains(item->GetLot())) continue;
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = m_Parent->GetObjectID();
lootMsg.ownerID = m_Parent->GetObjectID();
lootMsg.sourceID = m_Parent->GetObjectID();
lootMsg.item = item->GetLot();
lootMsg.count = 1;
lootMsg.spawnPos = m_Parent->GetPosition();
for (int i = 0; i < item->GetCount(); i++) Loot::DropItem(*m_Parent, lootMsg);
GameMessages::SendDropClientLoot(m_Parent, source, item->GetLot(), 0, m_Parent->GetPosition(), item->GetCount());
item->SetCount(0, false, false);
}
Game::entityManager->SerializeEntity(m_Parent);
@@ -1015,24 +1005,12 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
//drop all coins:
constexpr auto MAX_TO_DROP_PER_GM = 100'000;
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = m_Parent->GetObjectID();
lootMsg.ownerID = m_Parent->GetObjectID();
lootMsg.spawnPos = m_Parent->GetPosition();
lootMsg.sourceID = source;
lootMsg.item = LOT_NULL;
lootMsg.Send();
lootMsg.Send(m_Parent->GetSystemAddress());
while (coinsToDrop > MAX_TO_DROP_PER_GM) {
LOG("Dropping 100,000, %llu left", coinsToDrop);
lootMsg.currency = 100'000;
lootMsg.Send();
lootMsg.Send(m_Parent->GetSystemAddress());
coinsToDrop -= 100'000;
GameMessages::SendDropClientLoot(m_Parent, source, LOT_NULL, MAX_TO_DROP_PER_GM, m_Parent->GetPosition());
coinsToDrop -= MAX_TO_DROP_PER_GM;
}
lootMsg.currency = coinsToDrop;
lootMsg.Send();
lootMsg.Send(m_Parent->GetSystemAddress());
GameMessages::SendDropClientLoot(m_Parent, source, LOT_NULL, coinsToDrop, m_Parent->GetPosition());
}
return;
}
@@ -1063,89 +1041,38 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& destroyableInfo = reportInfo.info->PushDebug("Destroyable");
destroyableInfo.PushDebug<AMFIntValue>("DestructibleComponent DB Table Template ID") = m_ComponentID;
if (m_CurrencyIndex == -1) {
destroyableInfo.PushDebug<AMFBoolValue>("Has Loot Currency") = false;
} else {
destroyableInfo.PushDebug<AMFIntValue>("Loot Currency ID") = m_CurrencyIndex;
auto& detailedCoinInfo = destroyableInfo.PushDebug("Coin Info");
detailedCoinInfo.PushDebug<AMFIntValue>("Min Coins") = m_MinCoins;
detailedCoinInfo.PushDebug<AMFIntValue>("Max Coins") = m_MaxCoins;
}
if (m_LootMatrixID == -1 || m_LootMatrixID == 0) {
destroyableInfo.PushDebug<AMFBoolValue>("Has Loot Matrix") = false;
} else {
auto& lootInfo = destroyableInfo.PushDebug("Loot Info");
lootInfo.PushDebug<AMFIntValue>("Loot Matrix ID") = m_LootMatrixID;
auto* const componentsRegistryTable = CDClientManager::GetTable<CDComponentsRegistryTable>();
auto* const itemComponentTable = CDClientManager::GetTable<CDItemComponentTable>();
auto* const lootMatrixTable = CDClientManager::GetTable<CDLootMatrixTable>();
auto* const lootTableTable = CDClientManager::GetTable<CDLootTableTable>();
auto* const rarityTableTable = CDClientManager::GetTable<CDRarityTableTable>();
const auto& matrix = lootMatrixTable->GetMatrix(m_LootMatrixID);
for (const auto& entry : matrix) {
auto& thisEntry = lootInfo.PushDebug("Loot table Index - " + std::to_string(entry.LootTableIndex));
thisEntry.PushDebug<AMFDoubleValue>("Percent chance to drop") = entry.percent * 100.0f;
thisEntry.PushDebug<AMFDoubleValue>("Minimum amount to drop") = entry.minToDrop;
thisEntry.PushDebug<AMFDoubleValue>("Maximum amount to drop") = entry.maxToDrop;
const auto& lootTable = lootTableTable->GetTable(entry.LootTableIndex);
const auto& rarityTable = rarityTableTable->GetRarityTable(entry.RarityTableIndex);
auto& thisRarity = thisEntry.PushDebug("Rarity");
for (const auto& rarity : rarityTable) {
thisRarity.PushDebug<AMFDoubleValue>("Rarity " + std::to_string(rarity.rarity)) = rarity.randmax;
}
auto& thisItems = thisEntry.PushDebug("Drop(s) Info");
for (const auto& loot : lootTable) {
uint32_t itemComponentId = componentsRegistryTable->GetByIDAndType(loot.itemid, eReplicaComponentType::ITEM);
uint32_t rarity = itemComponentTable->GetItemComponentByID(itemComponentId).rarity;
auto title = "%[Objects_" + std::to_string(loot.itemid) + "_name] " + std::to_string(loot.itemid);
if (loot.MissionDrop) title += " - Mission Drop";
thisItems.PushDebug(title);
}
}
}
auto* const entity = Game::entityManager->GetEntity(reportInfo.clientID);
destroyableInfo.PushDebug<AMFBoolValue>("Is on your team") = entity ? IsFriend(entity) : false;
auto& stats = destroyableInfo.PushDebug("Statistics");
stats.PushDebug<AMFIntValue>("Health") = m_iHealth;
stats.PushDebug<AMFDoubleValue>("Maximum Health") = m_fMaxHealth;
stats.PushDebug<AMFIntValue>("Armor") = m_iArmor;
stats.PushDebug<AMFDoubleValue>("Maximum Armor") = m_fMaxArmor;
stats.PushDebug<AMFIntValue>("Imagination") = m_iImagination;
stats.PushDebug<AMFDoubleValue>("Maximum Imagination") = m_fMaxImagination;
stats.PushDebug<AMFIntValue>("Damage Absorption Points") = m_DamageToAbsorb;
destroyableInfo.PushDebug<AMFIntValue>("Health") = m_iHealth;
destroyableInfo.PushDebug<AMFDoubleValue>("Max Health") = m_fMaxHealth;
destroyableInfo.PushDebug<AMFIntValue>("Armor") = m_iArmor;
destroyableInfo.PushDebug<AMFDoubleValue>("Max Armor") = m_fMaxArmor;
destroyableInfo.PushDebug<AMFIntValue>("Imagination") = m_iImagination;
destroyableInfo.PushDebug<AMFDoubleValue>("Max Imagination") = m_fMaxImagination;
destroyableInfo.PushDebug<AMFIntValue>("Damage To Absorb") = m_DamageToAbsorb;
destroyableInfo.PushDebug<AMFBoolValue>("Is GM Immune") = m_IsGMImmune;
destroyableInfo.PushDebug<AMFBoolValue>("Is Shielded") = m_IsShielded;
destroyableInfo.PushDebug<AMFIntValue>("Attacks To Block") = m_AttacksToBlock;
destroyableInfo.PushDebug<AMFIntValue>("Damage Reduction") = m_DamageReduction;
std::stringstream factionsStream;
auto& factions = destroyableInfo.PushDebug("Factions");
size_t i = 0;
for (const auto factionID : m_FactionIDs) {
factionsStream << factionID << " ";
factions.PushDebug<AMFStringValue>(std::to_string(i++) + " " + std::to_string(factionID)) = "";
}
destroyableInfo.PushDebug<AMFStringValue>("Factions") = factionsStream.str();
factionsStream.str("");
auto& enemyFactions = destroyableInfo.PushDebug("Enemy Factions");
i = 0;
for (const auto enemyFactionID : m_EnemyFactionIDs) {
factionsStream << enemyFactionID << " ";
enemyFactions.PushDebug<AMFStringValue>(std::to_string(i++) + " " + std::to_string(enemyFactionID)) = "";
}
destroyableInfo.PushDebug<AMFStringValue>("Enemy Factions") = factionsStream.str();
destroyableInfo.PushDebug<AMFBoolValue>("Is Smashable") = m_IsSmashable;
destroyableInfo.PushDebug<AMFBoolValue>("Is Dead") = m_IsDead;
destroyableInfo.PushDebug<AMFBoolValue>("Is Smashed") = m_IsSmashed;
destroyableInfo.PushDebug<AMFBoolValue>("Is Module Assembly") = m_IsModuleAssembly;
destroyableInfo.PushDebug<AMFDoubleValue>("Explode Factor") = m_ExplodeFactor;
destroyableInfo.PushDebug<AMFBoolValue>("Has Threats") = m_HasThreats;
destroyableInfo.PushDebug<AMFIntValue>("Loot Matrix ID") = m_LootMatrixID;
destroyableInfo.PushDebug<AMFIntValue>("Min Coins") = m_MinCoins;
destroyableInfo.PushDebug<AMFIntValue>("Max Coins") = m_MaxCoins;
destroyableInfo.PushDebug<AMFStringValue>("Killer ID") = std::to_string(m_KillerID);
// "Scripts"; idk what to do about scripts yet
@@ -1160,25 +1087,7 @@ bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
immuneCounts.PushDebug<AMFIntValue>("Quickbuild Interrupt") = m_ImmuneToQuickbuildInterruptCount;
immuneCounts.PushDebug<AMFIntValue>("Pull To Point") = m_ImmuneToPullToPointCount;
auto& deathInfo = destroyableInfo.PushDebug("Death Info");
deathInfo.PushDebug<AMFBoolValue>("Is Dead") = m_IsDead;
switch (m_DeathBehavior) {
case 0:
deathInfo.PushDebug<AMFStringValue>("Death Behavior") = "Fade";
break;
case 1:
deathInfo.PushDebug<AMFStringValue>("Death Behavior") = "Stay";
break;
case 2:
deathInfo.PushDebug<AMFStringValue>("Death Behavior") = "Immediate";
break;
case -1:
deathInfo.PushDebug<AMFStringValue>("Death Behavior") = "Invulnerable";
break;
default:
deathInfo.PushDebug<AMFStringValue>("Death Behavior") = "Other";
break;
}
destroyableInfo.PushDebug<AMFIntValue>("Death Behavior") = m_DeathBehavior;
destroyableInfo.PushDebug<AMFDoubleValue>("Damage Cooldown Timer") = m_DamageCooldownTimer;
return true;

View File

@@ -370,8 +370,6 @@ public:
*/
uint32_t GetLootMatrixID() const { return m_LootMatrixID; }
void SetCurrencyIndex(int32_t currencyIndex) { m_CurrencyIndex = currencyIndex; }
/**
* Returns the ID of the entity that killed this entity, if any
* @return the ID of the entity that killed this entity, if any
@@ -589,9 +587,6 @@ private:
*/
uint32_t m_LootMatrixID;
// The currency index to determine how much loot to drop
int32_t m_CurrencyIndex{ -1 };
/**
* The min amount of coins that will drop when this entity is smashed
*/

View File

@@ -282,14 +282,7 @@ void InventoryComponent::AddItem(
case 1:
for (size_t i = 0; i < size; i++) {
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = m_Parent->GetObjectID();
lootMsg.ownerID = m_Parent->GetObjectID();
lootMsg.sourceID = m_Parent->GetObjectID();
lootMsg.item = lot;
lootMsg.count = 1;
lootMsg.spawnPos = m_Parent->GetPosition();
Loot::DropItem(*m_Parent, lootMsg);
GameMessages::SendDropClientLoot(this->m_Parent, this->m_Parent->GetObjectID(), lot, 0, this->m_Parent->GetPosition(), 1);
}
break;

View File

@@ -1067,6 +1067,44 @@ void GameMessages::SendSetNetworkScriptVar(Entity* entity, const SystemAddress&
SEND_PACKET;
}
void GameMessages::SendDropClientLoot(Entity* entity, const LWOOBJID& sourceID, LOT item, int currency, NiPoint3 spawnPos, int count) {
if (Game::config->GetValue("disable_drops") == "1" || !entity) {
return;
}
bool bUsePosition = false;
NiPoint3 finalPosition;
LWOOBJID lootID = LWOOBJID_EMPTY;
LWOOBJID owner = entity->GetObjectID();
if (item != LOT_NULL && item != 0) {
lootID = ObjectIDManager::GenerateObjectID();
Loot::Info info;
info.id = lootID;
info.count = count;
info.lot = item;
entity->AddLootItem(info);
}
if (item == LOT_NULL && currency != 0) {
entity->RegisterCoinDrop(currency);
}
if (spawnPos != NiPoint3Constant::ZERO) {
bUsePosition = true;
//Calculate where the loot will go:
uint16_t degree = GeneralUtils::GenerateRandomNumber<uint16_t>(0, 360);
double rad = degree * 3.14 / 180;
double sin_v = sin(rad) * 4.2;
double cos_v = cos(rad) * 4.2;
finalPosition = NiPoint3(static_cast<float>(spawnPos.GetX() + sin_v), spawnPos.GetY(), static_cast<float>(spawnPos.GetZ() + cos_v));
}
}
void GameMessages::SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme) {
CBITSTREAM;
CMSGHEADER;

View File

@@ -153,6 +153,7 @@ namespace GameMessages {
void SendStop2DAmbientSound(Entity* entity, bool force, std::string audioGUID, bool result = false);
void SendPlay2DAmbientSound(Entity* entity, std::string audioGUID, bool result = false);
void SendSetNetworkScriptVar(Entity* entity, const SystemAddress& sysAddr, std::string data);
void SendDropClientLoot(Entity* entity, const LWOOBJID& sourceID, LOT item, int currency, NiPoint3 spawnPos = NiPoint3Constant::ZERO, int count = 1);
void SendSetPlayerControlScheme(Entity* entity, eControlScheme controlScheme);
void SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPoint3& position, const NiQuaternion& rotation);
@@ -793,7 +794,6 @@ namespace GameMessages {
AMFArrayValue* info{};
AMFArrayValue* subCategory{};
bool bVerbose{};
LWOOBJID clientID{};
GetObjectReportInfo() : GameMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, eGameMasterLevel::DEVELOPER) {}
};

View File

@@ -13,7 +13,6 @@
#include "dChatFilter.h"
#include "DluAssert.h"
#include "Loot.h"
template <>
void Strip::HandleMsg(AddStripMessage& msg) {
@@ -149,14 +148,7 @@ void Strip::Spawn(LOT lot, Entity& entity) {
// Spawns a specific drop for all
void Strip::SpawnDrop(LOT dropLOT, Entity& entity) {
for (auto* const player : PlayerManager::GetAllPlayers()) {
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = player->GetObjectID();
lootMsg.ownerID = player->GetObjectID();
lootMsg.sourceID = entity.GetObjectID();
lootMsg.item = dropLOT;
lootMsg.count = 1;
lootMsg.spawnPos = entity.GetPosition();
Loot::DropItem(*player, lootMsg);
GameMessages::SendDropClientLoot(player, entity.GetObjectID(), dropLOT, 0, entity.GetPosition());
}
}

View File

@@ -93,19 +93,17 @@ std::map<LOT, LootDropInfo> RollLootMatrix(uint32_t matrixIndex) {
// Generates a 'random' final position for the loot drop based on its input spawn position.
void CalcFinalDropPos(GameMessages::DropClientLoot& lootMsg) {
if (lootMsg.spawnPos != NiPoint3Constant::ZERO) {
lootMsg.bUsePosition = true;
lootMsg.bUsePosition = true;
//Calculate where the loot will go:
uint16_t degree = GeneralUtils::GenerateRandomNumber<uint16_t>(0, 360);
//Calculate where the loot will go:
uint16_t degree = GeneralUtils::GenerateRandomNumber<uint16_t>(0, 360);
double rad = degree * 3.14 / 180;
double sin_v = sin(rad) * 4.2;
double cos_v = cos(rad) * 4.2;
double rad = degree * 3.14 / 180;
double sin_v = sin(rad) * 4.2;
double cos_v = cos(rad) * 4.2;
const auto [x, y, z] = lootMsg.spawnPos;
lootMsg.finalPosition = NiPoint3(static_cast<float>(x + sin_v), y, static_cast<float>(z + cos_v));
}
const auto [x, y, z] = lootMsg.spawnPos;
lootMsg.finalPosition = NiPoint3(static_cast<float>(x + sin_v), y, static_cast<float>(z + cos_v));
}
// Visually drop the loot to all team members, though only the lootMsg.ownerID can pick it up

View File

@@ -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{

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
#include "dNavMesh.h"
#include "RawFile.h"
#include "Game.h"
#include "Logger.h"

View File

@@ -0,0 +1,3 @@
set(DNAVIGATIONS_DTERRAIN_SOURCES "RawFile.cpp"
"RawChunk.cpp"
"RawHeightMap.cpp" PARENT_SCOPE)

View 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;
}

View 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;
};

View 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';
}
}

View 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;
};

View 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() {
}

View 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 = {};
};

View 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;
};

View File

@@ -7,7 +7,6 @@
#include "eReplicaComponentType.h"
#include "RenderComponent.h"
#include "eTerminateType.h"
#include "Loot.h"
void GfTikiTorch::OnStartup(Entity* self) {
LightTorch(self);
@@ -23,14 +22,7 @@ void GfTikiTorch::OnUse(Entity* self, Entity* killer) {
self->SetI64(u"userID", killer->GetObjectID());
for (int i = 0; i < m_numspawn; i++) {
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = killer->GetObjectID();
lootMsg.ownerID = killer->GetObjectID();
lootMsg.sourceID = self->GetObjectID();
lootMsg.item = 935;
lootMsg.count = 1;
lootMsg.spawnPos = self->GetPosition();
Loot::DropItem(*killer, lootMsg);
GameMessages::SendDropClientLoot(killer, self->GetObjectID(), 935, 0, self->GetPosition());
}
self->AddTimer("InteractionCooldown", 4);

View File

@@ -4,7 +4,6 @@
#include "GameMessages.h"
#include "SkillComponent.h"
#include "TeamManager.h"
#include "Loot.h"
void AgSurvivalBuffStation::OnQuickBuildComplete(Entity* self, Entity* target) {
auto destroyableComponent = self->GetComponent<DestroyableComponent>();
@@ -56,14 +55,7 @@ void AgSurvivalBuffStation::OnTimerDone(Entity* self, std::string timerName) {
for (auto memberID : team) {
auto member = Game::entityManager->GetEntity(memberID);
if (member != nullptr && !member->GetIsDead()) {
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = member->GetObjectID();
lootMsg.ownerID = member->GetObjectID();
lootMsg.sourceID = self->GetObjectID();
lootMsg.item = powerupToDrop;
lootMsg.count = 1;
lootMsg.spawnPos = self->GetPosition();
Loot::DropItem(*member, lootMsg, true, true);
GameMessages::SendDropClientLoot(member, self->GetObjectID(), powerupToDrop, 0, self->GetPosition());
}
}
}

View File

@@ -5,7 +5,6 @@
#include "EntityInfo.h"
#include "DestroyableComponent.h"
#include "eReplicaComponentType.h"
#include "Loot.h"
void AgImagSmashable::OnDie(Entity* self, Entity* killer) {
bool maxImagGreaterThanZero = false;
@@ -19,14 +18,7 @@ void AgImagSmashable::OnDie(Entity* self, Entity* killer) {
if (maxImagGreaterThanZero) {
int amount = GeneralUtils::GenerateRandomNumber<int>(0, 3);
for (int i = 0; i < amount; ++i) {
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = killer->GetObjectID();
lootMsg.ownerID = killer->GetObjectID();
lootMsg.sourceID = self->GetObjectID();
lootMsg.item = 935;
lootMsg.count = 1;
lootMsg.spawnPos = self->GetPosition();
Loot::DropItem(*killer, lootMsg);
GameMessages::SendDropClientLoot(killer, self->GetObjectID(), 935, 0, self->GetPosition());
}
}
}

View File

@@ -1,7 +1,6 @@
#include "NsQbImaginationStatue.h"
#include "EntityManager.h"
#include "GameMessages.h"
#include "Loot.h"
void NsQbImaginationStatue::OnStartup(Entity* self) {
@@ -36,12 +35,6 @@ void NsQbImaginationStatue::SpawnLoot(Entity* self) {
if (player == nullptr) return;
GameMessages::DropClientLoot lootMsg{};
lootMsg.target = player->GetObjectID();
lootMsg.ownerID = player->GetObjectID();
lootMsg.sourceID = self->GetObjectID();
lootMsg.item = 935;
lootMsg.count = 1;
Loot::DropItem(*player, lootMsg);
Loot::DropItem(*player, lootMsg);
GameMessages::SendDropClientLoot(player, self->GetObjectID(), 935, 0);
GameMessages::SendDropClientLoot(player, self->GetObjectID(), 935, 0);
}

View File

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

View File

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

View File

@@ -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__

View File

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

View File

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

View File

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

View File

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

View File

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