Public release of the DLU server code!

Have fun!
This commit is contained in:
Unknown
2021-12-05 18:54:36 +01:00
parent 5f7270e4ad
commit 0545adfac3
1146 changed files with 368646 additions and 1 deletions

View File

@@ -0,0 +1,92 @@
#include <sstream>
#include <fstream>
#include "BrickDatabase.h"
#include "Game.h"
std::vector<Brick> BrickDatabase::emptyCache {};
BrickDatabase* BrickDatabase::m_Address = nullptr;
BrickDatabase::BrickDatabase() = default;
BrickDatabase::~BrickDatabase() = default;
std::vector<Brick>& BrickDatabase::GetBricks(const std::string& lxfmlPath)
{
const auto cached = m_Cache.find(lxfmlPath);
if (cached != m_Cache.end()) {
return cached->second;
}
std::ifstream file(lxfmlPath);
if (!file.good()) {
return emptyCache;
}
std::stringstream data;
data << file.rdbuf();
if (data.str().empty()) {
return emptyCache;
}
auto* doc = new tinyxml2::XMLDocument();
if (doc->Parse(data.str().c_str(), data.str().size()) != 0) {
delete doc;
return emptyCache;
}
std::vector<Brick> parts;
auto* lxfml = doc->FirstChildElement("LXFML");
auto* bricks = lxfml->FirstChildElement("Bricks");
std::string searchTerm = "Brick";
if (!bricks) {
searchTerm = "Part";
bricks = lxfml->FirstChildElement("Scene")->FirstChildElement("Model")->FirstChildElement("Group");
if (!bricks) {
return emptyCache;
}
}
auto* currentBrick = bricks->FirstChildElement(searchTerm.c_str());
while (currentBrick != nullptr) {
auto* part = currentBrick->FirstChildElement("Part");
if (part == nullptr) part = currentBrick;
if (part->Attribute("designID") != nullptr) {
Brick brick { static_cast<uint32_t>(part->IntAttribute("designID")) };
// Depends on the file, some don't specify a list but just a single material
const auto* materialList = part->Attribute("materials");
const auto* materialID = part->Attribute("materialID");
if (materialList != nullptr) {
std::string materialString(materialList);
const auto materials = GeneralUtils::SplitString(materialString, ',');
if (!materials.empty()) {
brick.materialID = std::stoi(materials[0]);
} else {
brick.materialID = 0;
}
} else if (materialID != nullptr) {
brick.materialID = std::stoi(materialID);
} else {
brick.materialID = 0; // This is bad, makes it so the minigame can't be played
}
parts.push_back(brick);
}
currentBrick = currentBrick->NextSiblingElement(searchTerm.c_str());
}
m_Cache[lxfmlPath] = parts;
delete doc;
return m_Cache[lxfmlPath];
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "Entity.h"
class BrickDatabase
{
public:
static BrickDatabase* Instance()
{
if (m_Address == nullptr)
{
m_Address = new BrickDatabase();
}
return m_Address;
}
std::vector<Brick>& GetBricks(const std::string& lxfmlPath);
explicit BrickDatabase();
~BrickDatabase();
private:
std::unordered_map<std::string, std::vector<Brick>> m_Cache;
static std::vector<Brick> emptyCache;
static BrickDatabase* m_Address; //For singleton method
/* data */
};

27
dGame/dUtilities/GUID.cpp Normal file
View File

@@ -0,0 +1,27 @@
#include "GUID.h"
GUID::GUID(const std::string &guid) {
sscanf(guid.c_str(),
"{%8x-%4hx-%4hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx}",
&this->data1, &this->data2, &this->data3,
&this->data4[0], &this->data4[1], &this->data4[2], &this->data4[3],
&this->data4[4], &this->data4[5], &this->data4[6], &this->data4[7]);
}
uint32_t GUID::GetData1() const {
return data1;
}
uint16_t GUID::GetData2() const {
return data2;
}
uint16_t GUID::GetData3() const {
return data3;
}
std::array<uint8_t, 8> GUID::GetData4() const {
return data4;
}
GUID::GUID() = default;

19
dGame/dUtilities/GUID.h Normal file
View File

@@ -0,0 +1,19 @@
#pragma once
#include <cstdint>
#include <string>
#include <array>
class GUID {
public:
explicit GUID();
explicit GUID(const std::string& guid);
uint32_t GetData1() const;
uint16_t GetData2() const;
uint16_t GetData3() const;
std::array<uint8_t, 8> GetData4() const;
private:
uint32_t data1 = 0;
uint16_t data2 = 0;
uint16_t data3 = 0;
std::array<uint8_t, 8> data4 = { 0, 0, 0, 0, 0, 0, 0, 0};
};

View File

@@ -0,0 +1,50 @@
#include "GameConfig.h"
#include <sstream>
std::map<std::string, std::string> GameConfig::m_Config {};
std::string GameConfig::m_EmptyString {};
void GameConfig::Load(const std::string& filepath) {
m_EmptyString = "";
std::ifstream in(filepath);
if (!in.good()) return;
std::string line;
while (std::getline(in, line)) {
if (line.length() > 0) {
if (line[0] != '#') ProcessLine(line);
}
}
}
const std::string& GameConfig::GetValue(const std::string& key) {
const auto& it = m_Config.find(key);
if (it != m_Config.end()) {
return it->second;
}
return m_EmptyString;
}
void GameConfig::SetValue(const std::string& key, const std::string& value) {
m_Config.insert_or_assign(key, value);
}
void GameConfig::ProcessLine(const std::string& line) {
std::stringstream ss(line);
std::string segment;
std::vector<std::string> seglist;
while (std::getline(ss, segment, '=')) {
seglist.push_back(segment);
}
if (seglist.size() != 2) return;
//Make sure that on Linux, we remove special characters:
if (!seglist[1].empty() && seglist[1][seglist[1].size() - 1] == '\r')
seglist[1].erase(seglist[1].size() - 1);
m_Config.insert_or_assign(seglist[0], seglist[1]);
}

View File

@@ -0,0 +1,38 @@
#pragma once
#include <fstream>
#include <string>
#include <vector>
#include <map>
#include "GeneralUtils.h"
class GameConfig {
public:
static void Load(const std::string& filepath);
static const std::string& GetValue(const std::string& key);
static void SetValue(const std::string& key, const std::string& value);
template <typename T>
static T GetValue(const std::string& key) {
T value;
if (GeneralUtils::TryParse(GetValue(key), value)) {
return value;
}
return T();
}
template <typename T>
static void SetValue(const std::string& key, const T& value) {
SetValue(key, std::to_string(value));
}
private:
static void ProcessLine(const std::string& line);
static std::map<std::string, std::string> m_Config;
static std::string m_EmptyString;
};

384
dGame/dUtilities/Loot.cpp Normal file
View File

@@ -0,0 +1,384 @@
#include "Loot.h"
#include "GameMessages.h"
#include "CDClientManager.h"
#include "CDLootMatrixTable.h"
#include "CDLootTableTable.h"
#include "SimplePhysicsComponent.h"
#include "ControllablePhysicsComponent.h"
#include "DestroyableComponent.h"
#include "MissionComponent.h"
#include "CharacterComponent.h"
#include "TeamManager.h"
#include <algorithm>
std::vector<CDLootTable> Loot::GetLootOfRarity(const std::vector<CDLootTable> &lootTable, uint32_t rarity) {
std::vector<CDLootTable> refinedLoot;
for (auto loot : lootTable) {
CDItemComponent item = Inventory::FindItemComponent(loot.itemid);
if (item.rarity == rarity) {
refinedLoot.push_back(loot);
}
else if (item.rarity == 0) {
refinedLoot.push_back(loot); // powerups
}
}
return refinedLoot;
}
void Loot::GiveLoot(Entity* user, uint32_t lootMatrixID) {
user = user->GetOwner(); // If the owner is overwritten, we collect that here
std::unordered_map<LOT, int32_t> result {};
CalculateLootMatrix(lootMatrixID, user, result);
GiveLoot(user, result);
}
void Loot::DropLoot(Entity* user, Entity* killedObject, uint32_t lootMatrixID, uint32_t minCoins, uint32_t maxCoins) {
user = user->GetOwner(); // If the owner is overwritten, we collect that here
auto* inventoryComponent = user->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
}
std::unordered_map<LOT, int32_t> result {};
CalculateLootMatrix(lootMatrixID, user, result);
DropLoot(user, killedObject, result, minCoins, maxCoins);
}
void Loot::GiveLoot(Entity* user, std::unordered_map<LOT, int32_t>& result) {
user = user->GetOwner(); // If the owner is overwritten, we collect that here
auto* inventoryComponent = user->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
}
for (const auto& pair : result) {
inventoryComponent->AddItem(pair.first, pair.second);
}
}
void Loot::DropLoot(Entity* user, Entity* killedObject, std::unordered_map<LOT, int32_t>& result, uint32_t minCoins, uint32_t maxCoins) {
user = user->GetOwner(); // If the owner is overwritten, we collect that here
auto* inventoryComponent = user->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) {
return;
}
const auto spawnPosition = killedObject->GetPosition();
const auto source = killedObject->GetObjectID();
for (const auto& pair : result) {
for (int i = 0; i < pair.second; ++i) {
GameMessages::SendDropClientLoot(user, source, pair.first, 0, spawnPosition, 1);
}
}
uint32_t coins = (int)(minCoins + GeneralUtils::GenerateRandomNumber<float>(0, 1) * (maxCoins - minCoins));
GameMessages::SendDropClientLoot(user, source, LOT_NULL, coins, spawnPosition);
}
void Loot::DropActivityLoot(Entity* user, Entity* source, uint32_t activityID, int32_t rating)
{
CDActivityRewardsTable* activityRewardsTable = CDClientManager::Instance()->GetTable<CDActivityRewardsTable>("ActivityRewards");
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([=](CDActivityRewards entry) { return (entry.objectTemplate == activityID); });
const CDActivityRewards* selectedReward = nullptr;
for (const auto& activityReward : activityRewards)
{
if (activityReward.activityRating <= rating && (selectedReward == nullptr || activityReward.activityRating > selectedReward->activityRating))
{
selectedReward = &activityReward;
}
}
if (selectedReward == nullptr)
{
return;
}
uint32_t minCoins = 0;
uint32_t maxCoins = 0;
CDCurrencyTableTable* currencyTableTable = CDClientManager::Instance()->GetTable<CDCurrencyTableTable>("CurrencyTable");
std::vector<CDCurrencyTable> currencyTable = currencyTableTable->Query([=](CDCurrencyTable entry) { return (entry.currencyIndex == selectedReward->CurrencyIndex && entry.npcminlevel == 1); });
if (currencyTable.size() > 0) {
minCoins = currencyTable[0].minvalue;
maxCoins = currencyTable[0].maxvalue;
}
Loot::DropLoot(user, source, selectedReward->LootMatrixIndex, minCoins, maxCoins);
}
void Loot::GiveActivityLoot(Entity* user, Entity* source, uint32_t activityID, int32_t rating)
{
CDActivityRewardsTable* activityRewardsTable = CDClientManager::Instance()->GetTable<CDActivityRewardsTable>("ActivityRewards");
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([=](CDActivityRewards entry) { return (entry.objectTemplate == activityID); });
const CDActivityRewards* selectedReward = nullptr;
for (const auto& activityReward : activityRewards)
{
if (activityReward.activityRating <= rating && (selectedReward == nullptr || activityReward.activityRating > selectedReward->activityRating))
{
selectedReward = &activityReward;
}
}
if (selectedReward == nullptr)
{
return;
}
uint32_t minCoins = 0;
uint32_t maxCoins = 0;
CDCurrencyTableTable* currencyTableTable = CDClientManager::Instance()->GetTable<CDCurrencyTableTable>("CurrencyTable");
std::vector<CDCurrencyTable> currencyTable = currencyTableTable->Query([=](CDCurrencyTable entry) { return (entry.currencyIndex == selectedReward->CurrencyIndex && entry.npcminlevel == 1); });
if (currencyTable.size() > 0) {
minCoins = currencyTable[0].minvalue;
maxCoins = currencyTable[0].maxvalue;
}
Loot::GiveLoot(user, selectedReward->LootMatrixIndex);
uint32_t coins = (int)(minCoins + GeneralUtils::GenerateRandomNumber<float>(0, 1) * (maxCoins - minCoins));
auto* charactert = user->GetCharacter();
charactert->SetCoins(charactert->GetCoins() + coins);
}
void Loot::CalculateLootMatrix(uint32_t lootMatrixID, Entity* user, std::unordered_map<LOT, int32_t>& result)
{
user = user->GetOwner();
auto* missionComponent = user->GetComponent<MissionComponent>();
// Get our loot for this LOT's lootMatrixID:
CDLootMatrixTable* lootMatrixTable = CDClientManager::Instance()->GetTable<CDLootMatrixTable>("LootMatrix");
CDLootTableTable* lootTableTable = CDClientManager::Instance()->GetTable<CDLootTableTable>("LootTable");
CDRarityTableTable* rarityTableTable = CDClientManager::Instance()->GetTable<CDRarityTableTable>("RarityTable");
std::vector<CDLootMatrix> lootMatrix = lootMatrixTable->Query([lootMatrixID](CDLootMatrix entry) { return (entry.LootMatrixIndex == lootMatrixID); });
// Now, loop through each entry
for (uint32_t i = 0; i < lootMatrix.size(); ++i) {
// Now, determine whether or not we should drop this
float chanceToDrop = 1.0 - lootMatrix[i].percent;
float shouldDrop = GeneralUtils::GenerateRandomNumber<float>(0, 1);
const auto rarityTableIndex = lootMatrix[i].RarityTableIndex;
std::vector<CDRarityTable> rarityTable = rarityTableTable->Query([rarityTableIndex](CDRarityTable entry) { return (entry.RarityTableIndex == rarityTableIndex); });
std::sort(rarityTable.begin(), rarityTable.end());
if (shouldDrop < chanceToDrop) {
// We are not able to drop this item, so continue
continue;
}
// If we reached here, we are able to drop the item
uint32_t minToDrop = lootMatrix[i].minToDrop;
uint32_t maxToDrop = lootMatrix[i].maxToDrop;
// Now determine the number we will drop of items from this table
uint32_t numToDrop = GeneralUtils::GenerateRandomNumber<uint32_t>(minToDrop, maxToDrop);
// Now, query the loot matrix index
const auto lootTableIndex = lootMatrix[i].LootTableIndex;
std::vector<CDLootTable> lootTable = lootTableTable->Query([lootTableIndex](CDLootTable entry) { return (entry.LootTableIndex == lootTableIndex); });
// Now randomize these entries
if (lootTable.size() > 1) {
std::shuffle(std::begin(lootTable), std::end(lootTable), Game::randomEngine);
}
uint32_t addedItems = 0;
if (lootTable.empty()) continue;
while (addedItems < numToDrop) {
addedItems++;
float rarityRoll = GeneralUtils::GenerateRandomNumber<float>(0, 1);
// im sorry
uint32_t highestRarity = 1; // LOOT_COMMON
float highestRandMax = 0.0f;
for (const auto& rarity : rarityTable) {
if (rarityRoll > rarity.randmax && rarity.randmax > highestRandMax) {
highestRandMax = rarity.randmax;
highestRarity = rarity.rarity + 1;
}
}
std::vector<CDLootTable> refinedLoot;
if (lootTable.size() == 1)
{
refinedLoot = lootTable;
}
else
{
refinedLoot = GetLootOfRarity(lootTable, highestRarity);
bool continueLoop = false;
while (refinedLoot.empty())
{
if (highestRarity == 1)
{
continueLoop = true;
break;
}
highestRarity -= 1;
refinedLoot = GetLootOfRarity(lootTable, highestRarity);
if (!refinedLoot.empty())
{
break;
}
}
if (continueLoop) continue;
}
int randomTable = GeneralUtils::GenerateRandomNumber<int>(0, refinedLoot.size() - 1);
const auto& selectedTable = refinedLoot[randomTable];
uint32_t itemLOT = selectedTable.itemid;
bool isMissionItem = selectedTable.MissionDrop;
if (isMissionItem && missionComponent != nullptr)
{
// TODO: this executes a query in a hot path, might be worth refactoring away
if (!missionComponent->RequiresItem(itemLOT))
{
continue;
}
}
if (lootTable.size() > numToDrop)
{
for (size_t i = 0; i < lootTable.size(); i++)
{
if (lootTable[i].id == selectedTable.id)
{
lootTable.erase(lootTable.begin() + i);
break;
}
}
}
const auto& it = result.find(itemLOT);
if (it != result.end()) {
it->second++;
}
else {
result.emplace(itemLOT, 1);
}
}
}
int32_t tokenCount = 0;
const auto& tokens = result.find(13763);
if (tokens != result.end()) {
tokenCount = tokens->second;
result.erase(tokens);
}
if (tokenCount == 0 || user == nullptr) {
return;
}
if (missionComponent == nullptr) {
return;
}
LOT tokenId = -1;
if (missionComponent->GetMissionState(545) == MissionState::MISSION_STATE_COMPLETE) // "Join Assembly!"
{
tokenId = 8318; // "Assembly Token"
}
if (missionComponent->GetMissionState(556) == MissionState::MISSION_STATE_COMPLETE) // "Join Venture League!"
{
tokenId = 8321; // "Venture League Token"
}
if (missionComponent->GetMissionState(567) == MissionState::MISSION_STATE_COMPLETE) // "Join The Sentinels!"
{
tokenId = 8319; // "Sentinels Token"
}
if (missionComponent->GetMissionState(578) == MissionState::MISSION_STATE_COMPLETE) // "Join Paradox!"
{
tokenId = 8320; // "Paradox Token"
}
if (tokenId != -1)
{
result.emplace(tokenId, tokenCount);
}
}
void Loot::DropItem(Entity* user, Entity* sourceObject, LOT item, int32_t currency, int32_t count, bool useTeam, bool freeForAll)
{
if (sourceObject == nullptr)
{
return;
}
const auto sourceID = sourceObject->GetObjectID();
const auto sourcePosition = sourceObject->GetPosition();
// If useTeam, drop the item once for each team member.
auto* team = TeamManager::Instance()->GetTeam(user->GetObjectID());
if (team != nullptr && useTeam)
{
for (const auto& memberID : team->members)
{
// Get the team member from its ID.
auto* member = EntityManager::Instance()->GetEntity(memberID);
if (member == nullptr)
{
continue;
}
// Drop the item.
GameMessages::SendDropClientLoot(member, sourceID, item, currency, sourcePosition, count);
}
return;
}
GameMessages::SendDropClientLoot(user, sourceID, item, currency, sourcePosition, count);
}

31
dGame/dUtilities/Loot.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include "dCommonVars.h"
#include <vector>
#include "CDClientManager.h"
class Entity;
namespace Loot {
struct Info {
LWOOBJID id;
LOT lot;
uint32_t count;
};
std::vector<CDLootTable> GetLootOfRarity(const std::vector<CDLootTable> &lootTable, uint32_t rarity);
void DropActivityLoot(Entity* user, Entity* source, uint32_t activityID, int32_t rating = 0);
void GiveActivityLoot(Entity* user, Entity* source, uint32_t activityID, int32_t rating = 0);
void CalculateLootMatrix(uint32_t lootMatrixID, Entity* user, std::unordered_map<LOT, int32_t>& result);
void GiveLoot(Entity* user, uint32_t lootMatrixID);
void DropLoot(Entity* user, Entity* killedObject, uint32_t lootMatrixID, uint32_t minCoins, uint32_t maxCoins);
void GiveLoot(Entity* user, std::unordered_map<LOT, int32_t>& result);
void DropLoot(Entity* user, Entity* killedObject, std::unordered_map<LOT, int32_t>& result, uint32_t minCoins, uint32_t maxCoins);
void DropItem(Entity* user, Entity* sourceObject, LOT item, int32_t currency, int32_t count, bool useTeam = false, bool freeForAll = false);
};

473
dGame/dUtilities/Mail.cpp Normal file
View File

@@ -0,0 +1,473 @@
#include "Mail.h"
#include <functional>
#include <string>
#include <algorithm>
#include <regex>
#include <time.h>
#include <future>
#include "GeneralUtils.h"
#include "Database.h"
#include "Game.h"
#include "dServer.h"
#include "Entity.h"
#include "Character.h"
#include "PacketUtils.h"
#include "dMessageIdentifiers.h"
#include "dLogger.h"
#include "EntityManager.h"
#include "InventoryComponent.h"
#include "GameMessages.h"
#include "Item.h"
#include "MissionComponent.h"
#include "ChatPackets.h"
#include "Character.h"
void Mail::SendMail(const Entity* recipient, const std::string& subject, const std::string& body, const LOT attachment,
const uint16_t attachmentCount)
{
SendMail(
LWOOBJID_EMPTY,
ServerName,
recipient->GetObjectID(),
recipient->GetCharacter()->GetName(),
subject,
body,
attachment,
attachmentCount,
recipient->GetSystemAddress()
);
}
void Mail::SendMail(const LWOOBJID recipient, const std::string& recipientName, const std::string& subject,
const std::string& body, const LOT attachment, const uint16_t attachmentCount, const SystemAddress& sysAddr)
{
SendMail(
LWOOBJID_EMPTY,
ServerName,
recipient,
recipientName,
subject,
body,
attachment,
attachmentCount,
sysAddr
);
}
void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, const Entity* recipient, const std::string& subject,
const std::string& body, const LOT attachment, const uint16_t attachmentCount)
{
SendMail(
sender,
senderName,
recipient->GetObjectID(),
recipient->GetCharacter()->GetName(),
subject,
body,
attachment,
attachmentCount,
recipient->GetSystemAddress()
);
}
void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJID recipient,
const std::string& recipientName, const std::string& subject, const std::string& body, const LOT attachment,
const uint16_t attachmentCount, const SystemAddress& sysAddr)
{
auto* ins = Database::CreatePreppedStmt("INSERT INTO `mail`(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`) VALUES (?,?,?,?,?,?,?,?,?,?,?,0)");
ins->setUInt(1, sender);
ins->setString(2, senderName);
ins->setUInt(3, recipient);
ins->setString(4, recipientName.c_str());
ins->setUInt64(5, time(nullptr));
ins->setString(6, subject);
ins->setString(7, body);
ins->setUInt(8, 0);
ins->setInt(9, attachment);
ins->setInt(10, 0);
ins->setInt(11, attachmentCount);
ins->execute();
delete ins;
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) return; // TODO: Echo to chat server
SendNotification(sysAddr, 1); //Show the "one new mail" message
}
//Because we need it:
std::string ReadWStringAsString(RakNet::BitStream* bitStream, uint32_t size) {
std::string toReturn = "";
uint8_t buffer;
bool isFinishedReading = false;
for (uint32_t i = 0; i < size; ++i) {
bitStream->Read(buffer);
if (!isFinishedReading) toReturn.push_back(buffer);
if (buffer == '\0') isFinishedReading = true; //so we don't continue to read garbage as part of the string.
bitStream->Read(buffer); //Read the null term
}
return toReturn;
}
void WriteStringAsWString(RakNet::BitStream* bitStream, std::string str, uint32_t size) {
uint32_t sizeToFill = size - str.size();
for (uint32_t i = 0; i < str.size(); ++i) {
bitStream->Write(str[i]);
bitStream->Write(uint8_t(0));
}
for (uint32_t i = 0; i < sizeToFill; ++i) {
bitStream->Write(uint16_t(0));
}
}
void Mail::HandleMailStuff(RakNet::BitStream* packet, const SystemAddress& sysAddr, Entity* entity) {
int mailStuffID = 0;
packet->Read(mailStuffID);
std::async(std::launch::async, [packet, &sysAddr, entity, mailStuffID]() {
Mail::MailMessageID stuffID = MailMessageID(mailStuffID);
switch (stuffID) {
case MailMessageID::AttachmentCollect:
Mail::HandleAttachmentCollect(packet, sysAddr, entity);
break;
case MailMessageID::DataRequest:
Mail::HandleDataRequest(packet, sysAddr, entity);
break;
case MailMessageID::MailDelete:
Mail::HandleMailDelete(packet, sysAddr);
break;
case MailMessageID::MailRead:
Mail::HandleMailRead(packet, sysAddr);
break;
case MailMessageID::NotificationRequest:
Mail::HandleNotificationRequest(sysAddr, entity->GetObjectID());
break;
case MailMessageID::Send:
Mail::HandleSendMail(packet, sysAddr, entity);
break;
default:
Game::logger->Log("Mail", "Unhandled and possibly undefined MailStuffID: %i\n", int(stuffID));
}
});
}
void Mail::HandleSendMail(RakNet::BitStream* packet, const SystemAddress& sysAddr, Entity* entity) {
//std::string subject = GeneralUtils::WStringToString(ReadFromPacket(packet, 50));
//std::string body = GeneralUtils::WStringToString(ReadFromPacket(packet, 400));
//std::string recipient = GeneralUtils::WStringToString(ReadFromPacket(packet, 32));
// Check if the player has restricted mail access
auto* character = entity->GetCharacter();
if (!character) return;
if (character->HasPermission(PermissionMap::RestrictedMailAccess))
{
// Send a message to the player
ChatPackets::SendSystemMessage(
sysAddr,
u"This character has restricted mail access."
);
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::AccountIsMuted);
return;
}
std::string subject = ReadWStringAsString(packet, 50);
std::string body = ReadWStringAsString(packet, 400);
std::string recipient = ReadWStringAsString(packet, 32);
//Cleanse recipient:
recipient = std::regex_replace(recipient, std::regex("[^0-9a-zA-Z]+"), "");
uint64_t unknown64 = 0;
LWOOBJID attachmentID;
uint16_t attachmentCount;
packet->Read(unknown64);
packet->Read(attachmentID);
packet->Read(attachmentCount); //We don't care about the rest of the packet.
uint32_t itemID = static_cast<uint32_t>(attachmentID);
LOT itemLOT = 0;
//Inventory::InventoryType itemType;
int mailCost = 25;
int stackSize = 0;
auto inv = static_cast<InventoryComponent*>(entity->GetComponent(COMPONENT_TYPE_INVENTORY));
Item* item = nullptr;
if (itemID > 0 && attachmentCount > 0 && inv) {
item = inv->FindItemById(attachmentID);
if (item) {
mailCost += (item->GetInfo().baseValue * 0.1f);
stackSize = item->GetCount();
itemLOT = item->GetLot();
} else {
Mail::SendSendResponse(sysAddr, MailSendResponse::AttachmentNotFound);
return;
}
}
//Check if we can even send this mail (negative coins bug):
if (entity->GetCharacter()->GetCoins() - mailCost < 0) {
Mail::SendSendResponse(sysAddr, MailSendResponse::NotEnoughCoins);
return;
}
//Get the receiver's id:
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT id from charinfo WHERE name=? LIMIT 1;");
stmt->setString(1, recipient);
sql::ResultSet* res = stmt->executeQuery();
uint32_t receiverID = 0;
if (res->rowsCount() > 0) {
while (res->next()) receiverID = res->getUInt(1);
}
else {
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::RecipientNotFound);
delete stmt;
delete res;
return;
}
delete stmt;
delete res;
//Check if we have a valid receiver:
if (GeneralUtils::CaseInsensitiveStringCompare(recipient, character->GetName()) || receiverID == character->GetObjectID()) {
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::CannotMailSelf);
return;
}
else {
uint64_t currentTime = time(NULL);
sql::PreparedStatement* ins = Database::CreatePreppedStmt("INSERT INTO `mail`(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`) VALUES (?,?,?,?,?,?,?,?,?,?,?,0)");
ins->setUInt(1, character->GetObjectID());
ins->setString(2, character->GetName());
ins->setUInt(3, receiverID);
ins->setString(4, recipient);
ins->setUInt64(5, currentTime);
ins->setString(6, subject);
ins->setString(7, body);
ins->setUInt(8, itemID);
ins->setInt(9, itemLOT);
ins->setInt(10, 0);
ins->setInt(11, attachmentCount);
ins->execute();
delete ins;
}
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::Success);
entity->GetCharacter()->SetCoins(entity->GetCharacter()->GetCoins() - mailCost);
Game::logger->Log("Mail", "Seeing if we need to remove item with ID/count/LOT: %i %i %i\n", itemID, attachmentCount, itemLOT);
if (inv && itemLOT != 0 && attachmentCount > 0 && item) {
Game::logger->Log("Mail", "Trying to remove item with ID/count/LOT: %i %i %i\n", itemID, attachmentCount, itemLOT);
inv->RemoveItem(itemLOT, attachmentCount, INVALID, true);
auto* missionCompoent = entity->GetComponent<MissionComponent>();
if (missionCompoent != nullptr)
{
missionCompoent->Progress(MissionTaskType::MISSION_TASK_TYPE_ITEM_COLLECTION, itemLOT, LWOOBJID_EMPTY, "", -attachmentCount);
}
}
character->SaveXMLToDatabase();
}
void Mail::HandleDataRequest(RakNet::BitStream* packet, const SystemAddress& sysAddr, Entity* player) {
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT * FROM mail WHERE receiver_id=? limit 20;");
stmt->setUInt(1, player->GetCharacter()->GetObjectID());
sql::ResultSet* res = stmt->executeQuery();
RakNet::BitStream bitStream;
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_MAIL);
bitStream.Write(int(MailMessageID::MailData));
bitStream.Write(int(0));
bitStream.Write(uint16_t(res->rowsCount()));
bitStream.Write(uint16_t(0));
if (res->rowsCount() > 0) {
while (res->next()) {
bitStream.Write(res->getUInt64(1)); //MailID
/*std::u16string subject = GeneralUtils::ASCIIToUTF16(res->getString(7));
std::u16string body = GeneralUtils::ASCIIToUTF16(res->getString(8));
std::u16string sender = GeneralUtils::ASCIIToUTF16(res->getString(3));
WriteToPacket(&bitStream, subject, 50);
WriteToPacket(&bitStream, body, 400);
WriteToPacket(&bitStream, sender, 32);*/
WriteStringAsWString(&bitStream, res->getString(7), 50); //subject
WriteStringAsWString(&bitStream, res->getString(8), 400); //body
WriteStringAsWString(&bitStream, res->getString(3), 32); //sender
bitStream.Write(uint32_t(0));
bitStream.Write(uint64_t(0));
bitStream.Write(res->getUInt64(9)); //Attachment ID
LOT lot = res->getInt(10);
if (lot <= 0) bitStream.Write(LOT(-1));
else bitStream.Write(lot);
bitStream.Write(uint32_t(0));
bitStream.Write(res->getInt64(11)); //Attachment subKey
bitStream.Write(uint16_t(res->getInt(12))); //Attachment count
bitStream.Write(uint32_t(0));
bitStream.Write(uint16_t(0));
bitStream.Write(uint64_t(res->getUInt64(6))); //time sent (twice?)
bitStream.Write(uint64_t(res->getUInt64(6)));
bitStream.Write(uint8_t(res->getBoolean(13))); //was read
bitStream.Write(uint8_t(0));
bitStream.Write(uint16_t(0));
bitStream.Write(uint32_t(0));
}
}
Game::server->Send(&bitStream, sysAddr, false);
PacketUtils::SavePacket("Max_Mail_Data.bin", (const char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed());
}
void Mail::HandleAttachmentCollect(RakNet::BitStream* packet, const SystemAddress& sysAddr, Entity* player) {
int unknown;
uint64_t mailID;
LWOOBJID playerID;
packet->Read(unknown);
packet->Read(mailID);
packet->Read(playerID);
if (mailID > 0 && playerID == player->GetObjectID()) {
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;");
stmt->setUInt64(1, mailID);
sql::ResultSet* res = stmt->executeQuery();
LOT attachmentLOT = 0;
uint32_t attachmentCount = 0;
while (res->next()) {
attachmentLOT = res->getInt(1);
attachmentCount = res->getInt(2);
}
auto inv = static_cast<InventoryComponent*>(player->GetComponent(COMPONENT_TYPE_INVENTORY));
if (!inv) return;
inv->AddItem(attachmentLOT, attachmentCount);
Mail::SendAttachmentRemoveConfirm(sysAddr, mailID);
sql::PreparedStatement* up = Database::CreatePreppedStmt("UPDATE mail SET attachment_lot=0 WHERE id=?;");
up->setUInt64(1, mailID);
up->execute();
delete up;
delete res;
delete stmt;
}
}
void Mail::HandleMailDelete(RakNet::BitStream* packet, const SystemAddress& sysAddr) {
int unknown;
uint64_t mailID;
LWOOBJID playerID;
packet->Read(unknown);
packet->Read(mailID);
packet->Read(playerID);
if (mailID > 0) Mail::SendDeleteConfirm(sysAddr, mailID, playerID);
}
void Mail::HandleMailRead(RakNet::BitStream* packet, const SystemAddress& sysAddr) {
int unknown;
uint64_t mailID;
packet->Read(unknown);
packet->Read(mailID);
if (mailID > 0) Mail::SendReadConfirm(sysAddr, mailID);
}
void Mail::HandleNotificationRequest(const SystemAddress& sysAddr, uint32_t objectID) {
std::async(std::launch::async, [&]() {
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT id FROM mail WHERE receiver_id=? AND was_read=0");
stmt->setUInt(1, objectID);
sql::ResultSet* res = stmt->executeQuery();
if (res->rowsCount() > 0) Mail::SendNotification(sysAddr, res->rowsCount());
delete res;
delete stmt;
});
}
void Mail::SendSendResponse(const SystemAddress& sysAddr, MailSendResponse response) {
RakNet::BitStream bitStream;
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_MAIL);
bitStream.Write(int(MailMessageID::SendResponse));
bitStream.Write(int(response));
Game::server->Send(&bitStream, sysAddr, false);
}
void Mail::SendNotification(const SystemAddress& sysAddr, int mailCount) {
RakNet::BitStream bitStream;
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_MAIL);
uint64_t messageType = 2;
uint64_t s1 = 0;
uint64_t s2 = 0;
uint64_t s3 = 0;
uint64_t s4 = 0;
bitStream.Write(messageType);
bitStream.Write(s1);
bitStream.Write(s2);
bitStream.Write(s3);
bitStream.Write(s4);
bitStream.Write(mailCount);
bitStream.Write(int(0)); //Unknown
Game::server->Send(&bitStream, sysAddr, false);
}
void Mail::SendAttachmentRemoveConfirm(const SystemAddress& sysAddr, uint64_t mailID) {
RakNet::BitStream bitStream;
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_MAIL);
bitStream.Write(int(MailMessageID::AttachmentCollectConfirm));
bitStream.Write(int(0)); //unknown
bitStream.Write(mailID);
Game::server->Send(&bitStream, sysAddr, false);
}
void Mail::SendDeleteConfirm(const SystemAddress& sysAddr, uint64_t mailID, LWOOBJID playerID) {
RakNet::BitStream bitStream;
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_MAIL);
bitStream.Write(int(MailMessageID::MailDeleteConfirm));
bitStream.Write(int(0)); //unknown
bitStream.Write(mailID);
Game::server->Send(&bitStream, sysAddr, false);
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("DELETE FROM mail WHERE id=? LIMIT 1;");
stmt->setUInt64(1, mailID);
stmt->execute();
delete stmt;
}
void Mail::SendReadConfirm(const SystemAddress& sysAddr, uint64_t mailID) {
RakNet::BitStream bitStream;
PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_MAIL);
bitStream.Write(int(MailMessageID::MailReadConfirm));
bitStream.Write(int(0)); //unknown
bitStream.Write(mailID);
Game::server->Send(&bitStream, sysAddr, false);
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("UPDATE mail SET was_read=1 WHERE id=?");
stmt->setUInt64(1, mailID);
stmt->execute();
delete stmt;
}

95
dGame/dUtilities/Mail.h Normal file
View File

@@ -0,0 +1,95 @@
#pragma once
#include "BitStream.h"
#include "RakNetTypes.h"
#include "dCommonVars.h"
class Entity;
namespace Mail {
enum class MailMessageID {
Send = 0x00,
SendResponse = 0x01,
DataRequest = 0x03,
MailData = 0x04,
AttachmentCollect = 0x05,
AttachmentCollectConfirm = 0x06,
MailDelete = 0x07,
MailDeleteConfirm = 0x08,
MailRead = 0x09,
MailReadConfirm = 0x0a,
NotificationRequest = 0x0b
};
enum class MailSendResponse {
Success = 0,
NotEnoughCoins,
AttachmentNotFound,
ItemCannotBeMailed,
CannotMailSelf,
RecipientNotFound,
DifferentFaction,
Unknown,
ModerationFailure,
AccountIsMuted,
UnknownFailure,
RecipientIsIgnored,
UnknownFailure3,
RecipientIsFTP
};
const std::string ServerName = "Darkflame Universe";
void SendMail(
const Entity* recipient,
const std::string& subject,
const std::string& body,
LOT attachment,
uint16_t attachmentCount
);
void SendMail(
LWOOBJID recipient,
const std::string& recipientName,
const std::string& subject,
const std::string& body,
LOT attachment,
uint16_t attachmentCount,
const SystemAddress& sysAddr
);
void SendMail(
LWOOBJID sender,
const std::string& senderName,
const Entity* recipient,
const std::string& subject,
const std::string& body,
LOT attachment,
uint16_t attachmentCount
);
void SendMail(
LWOOBJID sender,
const std::string& senderName,
LWOOBJID recipient,
const std::string& recipientName,
const std::string& subject,
const std::string& body,
LOT attachment,
uint16_t attachmentCount,
const SystemAddress& sysAddr
);
void HandleMailStuff(RakNet::BitStream* packet, const SystemAddress& sysAddr, Entity* entity);
void HandleSendMail(RakNet::BitStream* packet, const SystemAddress& sysAddr, Entity* entity);
void HandleDataRequest(RakNet::BitStream* packet, const SystemAddress& sysAddr, Entity* player);
void HandleAttachmentCollect(RakNet::BitStream* packet, const SystemAddress& sysAddr, Entity* player);
void HandleMailDelete(RakNet::BitStream* packet, const SystemAddress& sysAddr);
void HandleMailRead(RakNet::BitStream* packet, const SystemAddress& sysAddr);
void HandleNotificationRequest(const SystemAddress& sysAddr, uint32_t objectID);
void SendSendResponse(const SystemAddress& sysAddr, MailSendResponse response);
void SendNotification(const SystemAddress& sysAddr, int mailCount);
void SendAttachmentRemoveConfirm(const SystemAddress& sysAddr, uint64_t mailID);
void SendDeleteConfirm(const SystemAddress& sysAddr, uint64_t mailID, LWOOBJID playerID);
void SendReadConfirm(const SystemAddress& sysAddr, uint64_t mailID);
};

View File

@@ -0,0 +1,367 @@
#include "Preconditions.h"
#include "Game.h"
#include "dLogger.h"
#include <sstream>
#include "InventoryComponent.h"
#include "MissionComponent.h"
#include "Character.h"
#include "CharacterComponent.h"
#include "DestroyableComponent.h"
#include "GameMessages.h"
std::map<uint32_t, Precondition*> Preconditions::cache = {};
Precondition::Precondition(const uint32_t condition)
{
std::stringstream query;
query << "SELECT type, targetLOT, targetCount FROM Preconditions WHERE id = " << std::to_string(condition) << ";";
auto result = CDClientDatabase::ExecuteQuery(query.str());
if (result.eof())
{
this->type = PreconditionType::ItemEquipped;
this->count = 1;
this->values = { 0 };
Game::logger->Log("Precondition", "Failed to find precondition of id (%i)!\n", condition);
return;
}
this->type = static_cast<PreconditionType>(result.fieldIsNull(0) ? 0 : result.getIntField(0));
if (!result.fieldIsNull(1))
{
std::istringstream stream(result.getStringField(1));
std::string token;
while (std::getline(stream, token, ','))
{
uint32_t value;
if (GeneralUtils::TryParse(token, value))
{
this->values.push_back(value);
}
}
}
this->count = result.fieldIsNull(2) ? 1 : result.getIntField(2);
result.finalize();
}
bool Precondition::Check(Entity* player, bool evaluateCosts) const
{
if (values.empty())
{
return true; // There are very few of these
}
bool any;
// Guesses
switch (type) {
case PreconditionType::HasItem:
case PreconditionType::ItemEquipped:
case PreconditionType::HasAchievement:
case PreconditionType::MissionAvailable:
case PreconditionType::OnMission:
case PreconditionType::MissionComplete:
case PreconditionType::PetDeployed:
case PreconditionType::HasFlag:
case PreconditionType::WithinShape:
case PreconditionType::InBuild:
case PreconditionType::TeamCheck:
case PreconditionType::IsPetTaming:
case PreconditionType::HasFaction:
case PreconditionType::DoesNotHaveFaction:
case PreconditionType::HasRacingLicence:
case PreconditionType::DoesNotHaveRacingLicence:
case PreconditionType::LegoClubMember:
case PreconditionType::NoInteraction:
any = true;
break;
case PreconditionType::DoesNotHaveItem:
case PreconditionType::ItemNotEquipped:
case PreconditionType::HasLevel:
any = false;
break;
default:
any = true;
break;
}
auto passedAny = false;
for (const auto value : values)
{
const auto passed = CheckValue(player, value, evaluateCosts);
if (passed && any)
{
return true;
}
if (!passed && !any)
{
return false;
}
if (passed)
{
passedAny = true;
}
}
return passedAny;
}
bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluateCosts) const
{
auto* missionComponent = player->GetComponent<MissionComponent>();
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
auto* destroyableComponent = player->GetComponent<DestroyableComponent>();
auto* characterComponent = player->GetComponent<CharacterComponent>();
auto* character = player->GetCharacter();
Mission* mission;
switch (type)
{
case PreconditionType::ItemEquipped:
return inventoryComponent->IsEquipped(value);
case PreconditionType::ItemNotEquipped:
return !inventoryComponent->IsEquipped(value);
case PreconditionType::HasItem:
if (evaluateCosts) // As far as I know this is only used for quickbuilds, and removal shouldn't actually be handled here.
{
inventoryComponent->RemoveItem(value, count);
return true;
}
return inventoryComponent->GetLotCount(value) >= count;
case PreconditionType::DoesNotHaveItem:
return inventoryComponent->IsEquipped(value) < count;
case PreconditionType::HasAchievement:
mission = missionComponent->GetMission(value);
return mission == nullptr || mission->GetMissionState() >= MissionState::MISSION_STATE_COMPLETE;
case PreconditionType::MissionAvailable:
mission = missionComponent->GetMission(value);
return mission == nullptr || mission->GetMissionState() >= MissionState::MISSION_STATE_AVAILABLE;
case PreconditionType::OnMission:
mission = missionComponent->GetMission(value);
return mission == nullptr || mission->GetMissionState() >= MissionState::MISSION_STATE_ACTIVE;
case PreconditionType::MissionComplete:
mission = missionComponent->GetMission(value);
return mission == nullptr || mission->GetMissionState() >= MissionState::MISSION_STATE_COMPLETE;
case PreconditionType::PetDeployed:
return false; // TODO
case PreconditionType::HasFlag:
return character->GetPlayerFlag(value);
case PreconditionType::WithinShape:
return true; // Client checks this one
case PreconditionType::InBuild:
return character->GetBuildMode();
case PreconditionType::TeamCheck:
return false; // TODO
case PreconditionType::IsPetTaming:
return false; // TODO
case PreconditionType::HasFaction:
for (const auto faction : destroyableComponent->GetFactionIDs())
{
if (faction == static_cast<int>(value))
{
return true;
}
}
return false;
case PreconditionType::DoesNotHaveFaction:
for (const auto faction : destroyableComponent->GetFactionIDs())
{
if (faction == static_cast<int>(value))
{
return false;
}
}
return true;
case PreconditionType::HasRacingLicence:
return false; // TODO
case PreconditionType::DoesNotHaveRacingLicence:
return false; // TODO
case PreconditionType::LegoClubMember:
return false; // TODO
case PreconditionType::NoInteraction:
return false; // TODO
case PreconditionType::HasLevel:
return characterComponent->GetLevel() >= value;
default:
return true; // There are a couple more unknown preconditions. Always return true in this case.
}
}
PreconditionExpression::PreconditionExpression(const std::string& conditions)
{
if (conditions.empty())
{
empty = true;
return;
}
std::stringstream a;
std::stringstream b;
auto bor = false;
auto done = false;
for (auto i = 0u; i < conditions.size(); ++i)
{
if (done)
{
break;
}
const auto character = conditions[i];
switch (character)
{
case '|':
bor = true;
b << conditions.substr(i + 1);
done = true;
break;
case ' ':
case ')':
break;
case ',':
case '&':
case ';':
case '(':
b << conditions.substr(i + 1);
done = true;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
a << character;
break;
default:
break;
}
}
this->m_or = bor;
const auto aString = a.str();
if (!aString.empty())
{
this->condition = std::stoul(a.str());
}
else
{
this->condition = 0;
}
const auto bString = b.str();
if (!bString.empty())
{
this->next = new PreconditionExpression(bString);
}
else
{
this->next = nullptr;
}
}
bool PreconditionExpression::Check(Entity* player, bool evaluateCosts) const
{
if (empty)
{
return true;
}
if (player->GetGMLevel() >= 9) // Developers can skip this for testing
{
return true;
}
const auto a = Preconditions::Check(player, condition, evaluateCosts);
if (!a)
{
GameMessages::SendNotifyClientFailedPrecondition(player->GetObjectID(), player->GetSystemAddress(), u"", condition);
}
const auto b = next == nullptr ? true : next->Check(player);
return m_or ? a || b : a && b;
}
PreconditionExpression::~PreconditionExpression()
{
delete next;
}
bool Preconditions::Check(Entity* player, const uint32_t condition, bool evaluateCosts)
{
Precondition* precondition;
const auto& index = cache.find(condition);
if (index != cache.end())
{
precondition = index->second;
}
else
{
precondition = new Precondition(condition);
cache.insert_or_assign(condition, precondition);
}
return precondition->Check(player, evaluateCosts);
}
PreconditionExpression Preconditions::CreateExpression(const std::string& conditions)
{
return PreconditionExpression(conditions);
}
Preconditions::~Preconditions()
{
for (const auto& condition : cache)
{
delete condition.second;
}
cache.clear();
}

View File

@@ -0,0 +1,81 @@
#pragma once
#include <vector>
#include "Entity.h"
enum class PreconditionType
{
ItemEquipped,
ItemNotEquipped,
HasItem,
DoesNotHaveItem,
HasAchievement,
MissionAvailable,
OnMission,
MissionComplete,
PetDeployed,
HasFlag,
WithinShape,
InBuild,
TeamCheck,
IsPetTaming,
HasFaction,
DoesNotHaveFaction,
HasRacingLicence,
DoesNotHaveRacingLicence,
LegoClubMember,
NoInteraction,
HasLevel = 22
};
class Precondition final
{
public:
explicit Precondition(uint32_t condition);
bool Check(Entity* player, bool evaluateCosts = false) const;
private:
bool CheckValue(Entity* player, uint32_t value, bool evaluateCosts = false) const;
PreconditionType type;
std::vector<uint32_t> values;
uint32_t count;
};
class PreconditionExpression final
{
public:
explicit PreconditionExpression(const std::string& conditions);
bool Check(Entity* player, bool evaluateCosts = false) const;
~PreconditionExpression();
private:
uint32_t condition = 0;
bool m_or = false;
bool empty = false;
PreconditionExpression* next = nullptr;
};
class Preconditions final
{
public:
static bool Check(Entity* player, uint32_t condition, bool evaluateCosts = false);
static PreconditionExpression CreateExpression(const std::string& conditions);
~Preconditions();
private:
static std::map<uint32_t, Precondition*> cache;
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,21 @@
/*
* Darkflame Universe
* Copyright 2018
*/
#ifndef SLASHCOMMANDHANDLER_H
#define SLASHCOMMANDHANDLER_H
#include "RakNetTypes.h"
#include <string>
class Entity;
namespace SlashCommandHandler {
void HandleChatCommand(const std::u16string& command, Entity* entity, const SystemAddress& sysAddr);
bool CheckIfAccessibleZone(const unsigned int zoneID);
void SendAnnouncement(const std::string& title, const std::string& message);
};
#endif // SLASHCOMMANDHANDLER_H

View File

@@ -0,0 +1,537 @@
#include "VanityUtilities.h"
#include "DestroyableComponent.h"
#include "EntityManager.h"
#include "GameMessages.h"
#include "InventoryComponent.h"
#include "PhantomPhysicsComponent.h"
#include "ProximityMonitorComponent.h"
#include "ScriptComponent.h"
#include "dCommonVars.h"
#include "dConfig.h"
#include "dServer.h"
#include "tinyxml2.h"
#include <fstream>
std::vector<VanityNPC> VanityUtilities::m_NPCs = {};
std::vector<VanityParty> VanityUtilities::m_Parties = {};
std::vector<std::string> VanityUtilities::m_PartyPhrases = {};
void VanityUtilities::SpawnVanity()
{
if (Game::config->GetValue("disable_vanity") == "1") {
return;
}
const uint32_t zoneID = Game::server->GetZoneID();
ParseXML("./vanity/NPC.xml");
// Loop through all parties
for (const auto& party : m_Parties) {
const auto chance = party.m_Chance;
const auto zone = party.m_Zone;
if (zone != Game::server->GetZoneID()) {
continue;
}
float rate = GeneralUtils::GenerateRandomNumber<float>(0, 1);
if (chance < rate) {
continue;
}
// Copy m_NPCs into a new vector
std::vector<VanityNPC> npcList = m_NPCs;
std::vector<uint32_t> taken = {};
Game::logger->Log("VanityUtilities", "Spawning party with %i locations\n", party.m_Locations.size());
// Loop through all locations
for (const auto& location : party.m_Locations) {
rate = GeneralUtils::GenerateRandomNumber<float>(0, 1);
if (0.75f < rate) {
continue;
}
// Get a random NPC
auto npcIndex = GeneralUtils::GenerateRandomNumber<uint32_t>(0, npcList.size() - 1);
while (std::find(taken.begin(), taken.end(), npcIndex) != taken.end()) {
npcIndex = GeneralUtils::GenerateRandomNumber<uint32_t>(0, npcList.size() - 1);
}
const auto& npc = npcList[npcIndex];
taken.push_back(npcIndex);
// Spawn the NPC
std::vector<LDFBaseData*> data = { new LDFData<std::vector<std::u16string>>(
u"syncLDF", { u"custom_script_client" }),
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") };
// Spawn the NPC
auto* npcEntity = SpawnNPC(npc.m_LOT, npc.m_Name, location.m_Position, location.m_Rotation, npc.m_Equipment, data);
npcEntity->SetVar<std::vector<std::string>>(u"chats", m_PartyPhrases);
SetupNPCTalk(npcEntity);
}
return;
}
// Loop through all NPCs
for (const auto& pair : m_NPCs) {
if (pair.m_Locations.find(Game::server->GetZoneID()) == pair.m_Locations.end())
continue;
const std::vector<VanityNPCLocation>& locations = pair.m_Locations.at(Game::server->GetZoneID());
// Pick a random location
const auto& location = locations[GeneralUtils::GenerateRandomNumber<int>(
static_cast<size_t>(0), static_cast<size_t>(locations.size() - 1))];
float rate = GeneralUtils::GenerateRandomNumber<float>(0, 1);
if (location.m_Chance < rate) {
continue;
}
std::vector<LDFBaseData*> data = { new LDFData<std::vector<std::u16string>>(
u"syncLDF", { u"custom_script_client" }),
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua") };
// Spawn the NPC
auto* npc = SpawnNPC(pair.m_LOT, pair.m_Name, location.m_Position, location.m_Rotation, pair.m_Equipment, data);
npc->SetVar<std::vector<std::string>>(u"chats", pair.m_Phrases);
auto* scriptComponent = npc->GetComponent<ScriptComponent>();
if (scriptComponent != nullptr) {
scriptComponent->SetScript(pair.m_Script);
scriptComponent->SetSerialized(false);
for (const auto& pair : pair.m_Flags) {
npc->SetVar<bool>(GeneralUtils::ASCIIToUTF16(pair.first), pair.second);
}
}
SetupNPCTalk(npc);
}
if (zoneID == 1200) {
{
EntityInfo info;
info.lot = 8139;
info.pos = { 259.5f, 246.4f, -705.2f };
info.rot = { 0.0f, 0.0f, 1.0f, 0.0f };
info.spawnerID = EntityManager::Instance()->GetZoneControlEntity()->GetObjectID();
info.settings = { new LDFData<bool>(u"hasCustomText", true),
new LDFData<std::string>(u"customText", ParseMarkdown("./vanity/TESTAMENT.md")) };
auto* entity = EntityManager::Instance()->CreateEntity(info);
EntityManager::Instance()->ConstructEntity(entity);
}
}
}
Entity* VanityUtilities::SpawnNPC(LOT lot, const std::string& name, const NiPoint3& position,
const NiQuaternion& rotation, const std::vector<LOT>& inventory, const std::vector<LDFBaseData*>& ldf)
{
EntityInfo info;
info.lot = lot;
info.pos = position;
info.rot = rotation;
info.spawnerID = EntityManager::Instance()->GetZoneControlEntity()->GetObjectID();
info.settings = ldf;
auto* entity = EntityManager::Instance()->CreateEntity(info);
entity->SetVar(u"npcName", name);
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent != nullptr) {
inventoryComponent->SetNPCItems(inventory);
}
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr) {
destroyableComponent->SetIsGMImmune(true);
destroyableComponent->SetMaxHealth(0);
destroyableComponent->SetHealth(0);
}
EntityManager::Instance()->ConstructEntity(entity);
return entity;
}
void VanityUtilities::ParseXML(const std::string& file)
{
// Read the entire file
std::ifstream xmlFile(file);
std::string xml((std::istreambuf_iterator<char>(xmlFile)), std::istreambuf_iterator<char>());
// Parse the XML
tinyxml2::XMLDocument doc;
doc.Parse(xml.c_str(), xml.size());
// Read the NPCs
auto* npcs = doc.FirstChildElement("npcs");
if (npcs == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPCs\n");
return;
}
for (auto* party = npcs->FirstChildElement("party"); party != nullptr; party = party->NextSiblingElement("party")) {
// Get 'zone' as uint32_t and 'chance' as float
uint32_t zone = 0;
float chance = 0.0f;
if (party->Attribute("zone") != nullptr) {
zone = std::stoul(party->Attribute("zone"));
}
if (party->Attribute("chance") != nullptr) {
chance = std::stof(party->Attribute("chance"));
}
VanityParty partyInfo;
partyInfo.m_Zone = zone;
partyInfo.m_Chance = chance;
auto* locations = party->FirstChildElement("locations");
if (locations == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse party locations\n");
continue;
}
for (auto* location = locations->FirstChildElement("location"); location != nullptr;
location = location->NextSiblingElement("location")) {
// Get the location data
auto* x = location->Attribute("x");
auto* y = location->Attribute("y");
auto* z = location->Attribute("z");
auto* rw = location->Attribute("rw");
auto* rx = location->Attribute("rx");
auto* ry = location->Attribute("ry");
auto* rz = location->Attribute("rz");
if (x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr
|| rz == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse party location data\n");
continue;
}
VanityNPCLocation locationData;
locationData.m_Position = { std::stof(x), std::stof(y), std::stof(z) };
locationData.m_Rotation = { std::stof(rw), std::stof(rx), std::stof(ry), std::stof(rz) };
locationData.m_Chance = 1.0f;
partyInfo.m_Locations.push_back(locationData);
}
m_Parties.push_back(partyInfo);
}
auto* partyPhrases = npcs->FirstChildElement("partyphrases");
if (partyPhrases == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse party phrases\n");
return;
}
for (auto* phrase = partyPhrases->FirstChildElement("phrase"); phrase != nullptr;
phrase = phrase->NextSiblingElement("phrase")) {
// Get the phrase
auto* text = phrase->GetText();
if (text == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse party phrase\n");
continue;
}
m_PartyPhrases.push_back(text);
}
for (auto* npc = npcs->FirstChildElement("npc"); npc != nullptr; npc = npc->NextSiblingElement("npc")) {
// Get the NPC name
auto* name = npc->Attribute("name");
if (name == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPC name\n");
continue;
}
// Get the NPC lot
auto* lot = npc->Attribute("lot");
if (lot == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPC lot\n");
continue;
}
// Get the equipment
auto* equipment = npc->FirstChildElement("equipment");
if (equipment == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPC equipment\n");
continue;
}
auto* text = equipment->GetText();
std::vector<LOT> inventory;
if (text != nullptr) {
std::string equipmentString(text);
std::vector<std::string> splitEquipment = GeneralUtils::SplitString(equipmentString, ',');
for (auto& item : splitEquipment) {
inventory.push_back(std::stoi(item));
}
}
// Get the phrases
auto* phrases = npc->FirstChildElement("phrases");
if (phrases == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPC phrases\n");
continue;
}
std::vector<std::string> phraseList;
for (auto* phrase = phrases->FirstChildElement("phrase"); phrase != nullptr;
phrase = phrase->NextSiblingElement("phrase")) {
// Get the phrase
auto* text = phrase->GetText();
if (text == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPC phrase\n");
continue;
}
phraseList.push_back(text);
}
// Get the script
auto* scriptElement = npc->FirstChildElement("script");
std::string scriptName;
if (scriptElement != nullptr) {
auto* scriptNameAttribute = scriptElement->Attribute("name");
if (scriptNameAttribute == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPC script name\n");
continue;
}
scriptName = scriptNameAttribute;
}
VanityNPC npcData;
npcData.m_Name = name;
npcData.m_LOT = std::stoi(lot);
npcData.m_Equipment = inventory;
npcData.m_Phrases = phraseList;
npcData.m_Script = scriptName;
// Get flags
auto* flags = npc->FirstChildElement("flags");
if (flags != nullptr) {
for (auto* flag = flags->FirstChildElement("flag"); flag != nullptr;
flag = flag->NextSiblingElement("flag")) {
// Get the flag name
auto* name = flag->Attribute("name");
if (name == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPC flag name\n");
continue;
}
// Get the flag value
auto* value = flag->Attribute("value");
if (value == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPC flag value\n");
continue;
}
npcData.m_Flags[name] = std::stoi(value);
}
}
// Get the zones
for (auto* zone = npc->FirstChildElement("zone"); zone != nullptr; zone = zone->NextSiblingElement("zone")) {
// Get the zone ID
auto* zoneID = zone->Attribute("id");
if (zoneID == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPC zone ID\n");
continue;
}
// Get the locations
auto* locations = zone->FirstChildElement("locations");
if (locations == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPC locations\n");
continue;
}
for (auto* location = locations->FirstChildElement("location"); location != nullptr;
location = location->NextSiblingElement("location")) {
// Get the location data
auto* x = location->Attribute("x");
auto* y = location->Attribute("y");
auto* z = location->Attribute("z");
auto* rw = location->Attribute("rw");
auto* rx = location->Attribute("rx");
auto* ry = location->Attribute("ry");
auto* rz = location->Attribute("rz");
if (x == nullptr || y == nullptr || z == nullptr || rw == nullptr || rx == nullptr || ry == nullptr
|| rz == nullptr) {
Game::logger->Log("VanityUtilities", "Failed to parse NPC location data\n");
continue;
}
VanityNPCLocation locationData;
locationData.m_Position = { std::stof(x), std::stof(y), std::stof(z) };
locationData.m_Rotation = { std::stof(rw), std::stof(rx), std::stof(ry), std::stof(rz) };
locationData.m_Chance = 1.0f;
if (location->Attribute("chance") != nullptr) {
locationData.m_Chance = std::stof(location->Attribute("chance"));
}
const auto& it = npcData.m_Locations.find(std::stoi(zoneID));
if (it != npcData.m_Locations.end()) {
it->second.push_back(locationData);
} else {
std::vector<VanityNPCLocation> locations;
locations.push_back(locationData);
npcData.m_Locations.insert(std::make_pair(std::stoi(zoneID), locations));
}
}
}
m_NPCs.push_back(npcData);
}
}
VanityNPC* VanityUtilities::GetNPC(const std::string& name)
{
for (size_t i = 0; i < m_NPCs.size(); i++) {
if (m_NPCs[i].m_Name == name) {
return &m_NPCs[i];
}
}
return nullptr;
}
std::string VanityUtilities::ParseMarkdown(const std::string& file)
{
// This function will read the file and return the content formatted as ASCII text.
// Read the file into a string
std::ifstream t(file);
// If the file does not exist, return an empty string.
if (!t.good()) {
return "";
}
std::stringstream buffer;
buffer << t.rdbuf();
std::string fileContents = buffer.str();
// Loop through all lines in the file.
// Replace all instances of the markdown syntax with the corresponding HTML.
// Only care about headers
std::stringstream output;
std::string line;
std::stringstream ss;
ss << fileContents;
while (std::getline(ss, line)) {
#define TOSTRING(x) #x
#define STRINGIFY(x) TOSTRING(x)
// Replace "__TIMESTAMP__" with the __TIMESTAMP__
GeneralUtils::ReplaceInString(line, "__TIMESTAMP__", __TIMESTAMP__);
// Replace "__VERSION__" wit'h the PROJECT_VERSION
GeneralUtils::ReplaceInString(line, "__VERSION__", STRINGIFY(PROJECT_VERSION));
// Replace "__SOURCE__" with SOURCE
GeneralUtils::ReplaceInString(line, "__SOURCE__", Game::config->GetValue("source"));
// Replace "__LICENSE__" with LICENSE
GeneralUtils::ReplaceInString(line, "__LICENSE__", STRINGIFY(LICENSE));
if (line.find("##") != std::string::npos) {
// Add "&lt;font size=&apos;18&apos; color=&apos;#000000&apos;&gt;" before the header
output << "<font size=\"14\" color=\"#000000\">";
// Add the header without the markdown syntax
output << line.substr(3);
output << "</font>";
} else if (line.find("#") != std::string::npos) {
// Add "&lt;font size=&apos;18&apos; color=&apos;#000000&apos;&gt;" before the header
output << "<font size=\"18\" color=\"#000000\">";
// Add the header without the markdown syntax
output << line.substr(2);
output << "</font>";
} else {
output << line;
}
output << "\n";
}
return output.str();
}
void VanityUtilities::SetupNPCTalk(Entity* npc)
{
npc->AddCallbackTimer(15.0f, [npc]() { NPCTalk(npc); });
npc->SetProximityRadius(20.0f, "talk");
}
void VanityUtilities::NPCTalk(Entity* npc)
{
auto* proximityMonitorComponent = npc->GetComponent<ProximityMonitorComponent>();
if (!proximityMonitorComponent->GetProximityObjects("talk").empty()) {
const auto& chats = npc->GetVar<std::vector<std::string>>(u"chats");
if (chats.empty()) {
return;
}
const auto& selected
= chats[GeneralUtils::GenerateRandomNumber<int32_t>(0, static_cast<int32_t>(chats.size() - 1))];
GameMessages::SendNotifyClientZoneObject(
npc->GetObjectID(), u"sendToclient_bubble", 0, 0, npc->GetObjectID(), selected, UNASSIGNED_SYSTEM_ADDRESS);
}
EntityManager::Instance()->SerializeEntity(npc);
const float nextTime = GeneralUtils::GenerateRandomNumber<float>(15, 60);
npc->AddCallbackTimer(nextTime, [npc]() { NPCTalk(npc); });
}

View File

@@ -0,0 +1,66 @@
#pragma once
#include "dCommonVars.h"
#include "Entity.h"
#include <map>
struct VanityNPCLocation
{
float m_Chance = 1.0f;
NiPoint3 m_Position;
NiQuaternion m_Rotation;
};
struct VanityNPC
{
std::string m_Name;
LOT m_LOT;
std::vector<LOT> m_Equipment;
std::vector<std::string> m_Phrases;
std::string m_Script;
std::map<std::string, bool> m_Flags;
std::map<uint32_t, std::vector<VanityNPCLocation>> m_Locations;
};
struct VanityParty
{
uint32_t m_Zone;
float m_Chance = 1.0f;
std::vector<VanityNPCLocation> m_Locations;
};
class VanityUtilities
{
public:
static void SpawnVanity();
static Entity* SpawnNPC(
LOT lot,
const std::string& name,
const NiPoint3& position,
const NiQuaternion& rotation,
const std::vector<LOT>& inventory,
const std::vector<LDFBaseData*>& ldf
);
static std::string ParseMarkdown(
const std::string& file
);
static void ParseXML(
const std::string& file
);
static VanityNPC* GetNPC(const std::string& name);
private:
static void SetupNPCTalk(Entity* npc);
static void NPCTalk(Entity* npc);
static std::vector<VanityNPC> m_NPCs;
static std::vector<VanityParty> m_Parties;
static std::vector<std::string> m_PartyPhrases;
};

View File

@@ -0,0 +1,81 @@
#include "dLocale.h"
#include <clocale>
#include <fstream>
#include <sstream>
#include <vector>
#include <algorithm>
#include "tinyxml2.h"
#include "Game.h"
#include "dConfig.h"
dLocale::dLocale() {
if (Game::config->GetValue("locale_enabled") != "1") {
return;
}
std::ifstream file(m_LocalePath);
if (!file.good()) {
return;
}
std::stringstream data;
data << file.rdbuf();
if (data.str().empty()) {
return;
}
auto* doc = new tinyxml2::XMLDocument();
if (doc == nullptr) {
return;
}
if (doc->Parse(data.str().c_str(), data.str().size()) != 0) {
return;
}
std::hash<std::string> hash;
auto* localization = doc->FirstChildElement("localization");
auto* phrases = localization->FirstChildElement("phrases");
auto* phrase = phrases->FirstChildElement("phrase");
while (phrase != nullptr) {
// Add the phrase hash to the vector
m_Phrases.push_back(hash(phrase->Attribute("id")));
phrase = phrase->NextSiblingElement("phrase");
}
file.close();
delete doc;
}
dLocale::~dLocale() = default;
std::string dLocale::GetTemplate(const std::string& phraseID) {
return "%[" + phraseID + "]";
}
bool dLocale::HasPhrase(const std::string& phraseID) {
if (Game::config->GetValue("locale_enabled") != "1") {
return true;
}
// Compute the hash and see if it's in the vector
std::hash<std::string> hash;
std::size_t hashValue = hash(phraseID);
return std::find(m_Phrases.begin(), m_Phrases.end(), hashValue) != m_Phrases.end();
}
/*std::string dLocale::GetPhrase(const std::string& phraseID) {
if (m_Phrases.find(phraseID) == m_Phrases.end()) {
return "";
}
return m_Phrases[phraseID];
}*/

View File

@@ -0,0 +1,19 @@
#pragma once
#include <map>
#include <string>
#include <vector>
#include <cstdint>
class dLocale {
public:
dLocale();
~dLocale();
static std::string GetTemplate(const std::string& phraseID);
bool HasPhrase(const std::string& phraseID);
//std::string GetPhrase(const std::string& phraseID);
private:
std::string m_LocalePath = "./locale/locale.xml";
std::string m_Locale = "en_US"; // TODO: add to config
std::vector<std::size_t> m_Phrases;
};