Compare commits

...

22 Commits

Author SHA1 Message Date
David Markowitz
4d664ecd89 Use simpler conversion 2025-01-21 01:41:38 -08:00
David Markowitz
b5ef41e6ef Add better debug information 2025-01-21 01:39:52 -08:00
David Markowitz
60971409c1 debug stuff 2025-01-20 19:20:58 -08:00
David Markowitz
af2ba5b287 Add FlagComponent and msg handlers 2025-01-20 18:01:28 -08:00
David Markowitz
e4c2eecbc7 add msg handling (#1737) 2025-01-20 00:42:15 -06:00
David Markowitz
1b3cdc6d9c Use proper session flag checks (#1734) 2025-01-18 21:25:53 -06:00
David Markowitz
d860552776 ok sir (#1733) 2025-01-18 21:23:03 -06:00
Gie "Max" Vanommeslaeghe
b0d993de33 Merge pull request #1732 from DarkflameUniverse/quickbuild
fix: reduce networked traffic for QuickBuildComponent Serialization
2025-01-17 22:38:38 +01:00
Gie "Max" Vanommeslaeghe
dc602a94eb Merge pull request #1715 from DarkflameUniverse/webapiv2
feat: Chat Web API (now with no threading)
2025-01-17 22:38:15 +01:00
3faf9eea45 fix invalid players from showing up in api response 2025-01-15 15:50:30 -06:00
9542216650 comments and tweaks 2025-01-15 14:59:30 -06:00
5b8fe2cba0 refactor again 2025-01-15 13:48:49 -06:00
David Markowitz
f4b55915bc fix: server runs now (#1730) 2025-01-14 13:21:58 -06:00
David Markowitz
a0913ff23d remove grandmas code 2025-01-14 02:18:46 -08:00
David Markowitz
b738504812 reduce traffic greatly 2025-01-14 02:09:54 -08:00
David Markowitz
c968dc9028 fix lego club teleport (#1731)
Tested that the lego club teleport and starbase 3001 teleports work now both before and after you visit nexus tower
2025-01-14 01:28:24 -06:00
David Markowitz
2209a4432f Update AgSpiderBossMessage.cpp (#1729) 2025-01-14 01:14:24 -06:00
David Markowitz
c855a6b9cf Remove added physics objects (#1727) 2025-01-12 14:03:35 -06:00
David Markowitz
8abc545bd1 fix: use generated bcrypt password for internal master connections (#1720)
* add password hashing for master server

* use define
2025-01-10 01:45:20 -08:00
David Markowitz
136133dde2 fix: friends (#1726)
* fix

* Update ChatPacketHandler.cpp
2025-01-08 22:44:55 -06:00
David Markowitz
23551d4ed8 fix: friends not updating and using incorrect world (#1724)
* fix: friends not updating and using incorrect world

* use better reset logic

* actual fix for real
2025-01-06 20:25:51 -06:00
David Markowitz
7599a2e81e i am surprised no one noticed this for 3 yeasr (#1721) 2025-01-06 20:19:27 -06:00
93 changed files with 1544 additions and 793 deletions

View File

@@ -70,12 +70,15 @@ int main(int argc, char** argv) {
//Find out the master's IP:
std::string masterIP;
uint32_t masterPort = 1500;
std::string masterPassword;
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
masterPassword = masterInfo->password;
}
LOG("Master is at %s:%d", masterIP.c_str(), masterPort);
Game::randomEngine = std::mt19937(time(0));
@@ -89,7 +92,7 @@ int main(int argc, char** argv) {
const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::lastSignal);
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::lastSignal, masterPassword);
//Run it until server gets a kill message from Master:
auto t = std::chrono::high_resolution_clock::now();

View File

@@ -29,35 +29,33 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
auto& player = Game::playerContainer.GetPlayerDataMutable(playerID);
if (!player) return;
if (player.friends.empty()) {
auto friendsList = Database::Get()->GetFriendsList(playerID);
for (const auto& friendData : friendsList) {
FriendData fd;
fd.isFTP = false; // not a thing in DLU
fd.friendID = friendData.friendID;
GeneralUtils::SetBit(fd.friendID, eObjectBits::PERSISTENT);
GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER);
auto friendsList = Database::Get()->GetFriendsList(playerID);
for (const auto& friendData : friendsList) {
FriendData fd;
fd.isFTP = false; // not a thing in DLU
fd.friendID = friendData.friendID;
GeneralUtils::SetBit(fd.friendID, eObjectBits::PERSISTENT);
GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER);
fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
if (fd.isBestFriend) player.countOfBestFriends += 1;
fd.friendName = friendData.friendName;
fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
if (fd.isBestFriend) player.countOfBestFriends += 1;
fd.friendName = friendData.friendName;
//Now check if they're online:
const auto& fr = Game::playerContainer.GetPlayerData(fd.friendID);
//Now check if they're online:
const auto& fr = Game::playerContainer.GetPlayerData(fd.friendID);
if (fr) {
fd.isOnline = true;
fd.zoneID = fr.zoneID;
if (fr) {
fd.isOnline = true;
fd.zoneID = fr.zoneID;
//Since this friend is online, we need to update them on the fact that we've just logged in:
SendFriendUpdate(fr, player, 1, fd.isBestFriend);
} else {
fd.isOnline = false;
fd.zoneID = LWOZONEID();
}
player.friends.push_back(fd);
//Since this friend is online, we need to update them on the fact that we've just logged in:
if (player.isLogin) SendFriendUpdate(fr, player, 1, fd.isBestFriend);
} else {
fd.isOnline = false;
fd.zoneID = LWOZONEID();
}
player.friends.push_back(fd);
}
//Now, we need to send the friendlist to the server they came from:
@@ -105,7 +103,8 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
return;
};
auto& requestee = Game::playerContainer.GetPlayerDataMutable(playerName);
// Intentional copy
PlayerData requestee = Game::playerContainer.GetPlayerData(playerName);
// Check if player is online first
if (isBestFriendRequest && !requestee) {
@@ -190,19 +189,24 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus);
// Sent the best friend update here if the value is 3
if (bestFriendStatus == 3U) {
requestee.countOfBestFriends += 1;
requestor.countOfBestFriends += 1;
if (requestee.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
for (auto& friendData : requestor.friends) {
if (friendData.friendID == requestee.playerID) {
friendData.isBestFriend = true;
}
}
for (auto& friendData : requestee.friends) {
if (friendData.friendID == requestor.playerID) {
friendData.isBestFriend = true;
requestor.countOfBestFriends += 1;
auto& toModify = Game::playerContainer.GetPlayerDataMutable(playerName);
if (toModify) {
for (auto& friendData : toModify.friends) {
if (friendData.friendID == requestor.playerID) {
friendData.isBestFriend = true;
}
}
toModify.countOfBestFriends += 1;
}
}
}

View File

@@ -76,7 +76,8 @@ int main(int argc, char** argv) {
Game::assetManager = new AssetManager(clientPath);
} catch (std::runtime_error& ex) {
LOG("Got an error while setting up assets: %s", ex.what());
delete Game::logger;
delete Game::config;
return EXIT_FAILURE;
}
@@ -86,18 +87,32 @@ int main(int argc, char** argv) {
} catch (std::exception& ex) {
LOG("Got an error while connecting to the database: %s", ex.what());
Database::Destroy("ChatServer");
delete Game::server;
delete Game::logger;
delete Game::config;
return EXIT_FAILURE;
}
// seyup the chat api web server
bool web_server_enabled = Game::config->GetValue("web_server_enabled") == "1";
ChatWebAPI chatwebapi;
if (web_server_enabled && !chatwebapi.Startup()){
// if we want the web api and it fails to start, exit
LOG("Failed to start web server, shutting down.");
Database::Destroy("ChatServer");
delete Game::logger;
delete Game::config;
return EXIT_FAILURE;
};
//Find out the master's IP:
std::string masterIP;
uint32_t masterPort = 1000;
std::string masterPassword;
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
masterPassword = masterInfo->password;
}
//It's safe to pass 'localhost' here, as the IP is only used as the external IP.
std::string ourIP = "localhost";
@@ -106,7 +121,7 @@ int main(int argc, char** argv) {
const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal);
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal, masterPassword);
const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false);
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF);
@@ -124,11 +139,6 @@ int main(int argc, char** argv) {
uint32_t framesSinceMasterDisconnect = 0;
uint32_t framesSinceLastSQLPing = 0;
bool web_server_enabled = Game::config->GetValue("web_server_enabled") == "1";
ChatWebAPI chatwebapi;
if (web_server_enabled) chatwebapi.Listen();
auto lastTime = std::chrono::high_resolution_clock::now();
Game::logger->Flush(); // once immediately before main loop

View File

@@ -8,13 +8,13 @@
#include "dServer.h"
#include "dConfig.h"
#include "PlayerContainer.h"
#include "GeneralUtils.h"
#include "JSONUtils.h"
#include "GeneralUtils.h"
#include "eHTTPMethod.h"
#include "eHTTPStatusCode.h"
#include "magic_enum.hpp"
#include "ChatPackets.h"
#include "StringifiedEnum.h"
#include "Database.h"
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma push_macro("DELETE")
@@ -27,16 +27,18 @@ typedef struct mg_connection mg_connection;
typedef struct mg_http_message mg_http_message;
namespace {
const std::string root_path = "/api/v1/";
const char* json_content_type = "Content-Type: application/json\r\n";
std::map<std::pair<eHTTPMethod, std::string>, WebAPIHTTPRoute> Routes {};
}
struct HTTPReply {
eHTTPStatusCode status = eHTTPStatusCode::NOT_FOUND;
std::string message = "{\"error\":\"Not Found\"}";
};
bool ValidateAuthentication(const mg_http_message* http_msg) {
// TO DO: This is just a placeholder for now
// use tokens or something at a later point if we want to implement authentication
// bit using the listen bind address to limit external access is good enough to start with
return true;
}
bool CheckValidJSON(std::optional<json> data, HTTPReply& reply) {
bool ValidateJSON(std::optional<json> data, HTTPReply& reply) {
if (!data) {
reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = "{\"error\":\"Invalid JSON\"}";
@@ -45,40 +47,21 @@ bool CheckValidJSON(std::optional<json> data, HTTPReply& reply) {
return true;
}
void HandlePlayersRequest(HTTPReply& reply) {
void HandlePlayersRequest(HTTPReply& reply, std::string body) {
const json data = Game::playerContainer;
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
reply.message = data.empty() ? "{\"error\":\"No Players Online\"}" : data.dump();
}
void HandleTeamsRequest(HTTPReply& reply) {
void HandleTeamsRequest(HTTPReply& reply, std::string body) {
const json data = Game::playerContainer.GetTeamContainer();
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump();
}
void HandleInvalidRoute(HTTPReply& reply) {
reply.status = eHTTPStatusCode::NOT_FOUND;
reply.message = "{\"error\":\"Invalid Route\"}";
}
void HandleGET(HTTPReply& reply, const ChatWebAPI::eRoute& route , const std::string& body) {
switch (route) {
case ChatWebAPI::eRoute::PLAYERS:
HandlePlayersRequest(reply);
break;
case ChatWebAPI::eRoute::TEAMS:
HandleTeamsRequest(reply);
break;
case ChatWebAPI::eRoute::INVALID:
default:
HandleInvalidRoute(reply);
break;
}
}
void HandleAnnounceRequest(HTTPReply& reply, const std::optional<json>& data) {
if (!CheckValidJSON(data, reply)) return;
void HandleAnnounceRequest(HTTPReply& reply, std::string body) {
auto data = GeneralUtils::TryParse<json>(body);
if (!ValidateJSON(data, reply)) return;
const auto& good_data = data.value();
auto check = JSONUtils::CheckRequiredData(good_data, { "title", "message" });
@@ -97,18 +80,9 @@ void HandleAnnounceRequest(HTTPReply& reply, const std::optional<json>& data) {
}
}
void HandlePOST(HTTPReply& reply, const ChatWebAPI::eRoute& route , const std::string& body) {
auto data = GeneralUtils::TryParse<json>(body);
switch (route) {
case ChatWebAPI::eRoute::ANNOUNCE:{
HandleAnnounceRequest(reply, data.value());
break;
}
case ChatWebAPI::eRoute::INVALID:
default:
HandleInvalidRoute(reply);
break;
}
void HandleInvalidRoute(HTTPReply& reply) {
reply.status = eHTTPStatusCode::NOT_FOUND;
reply.message = "{\"error\":\"Invalid Route\"}";
}
void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_msg) {
@@ -117,7 +91,7 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms
if (!http_msg) {
reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = "{\"error\":\"Invalid Request\"}";
} else {
} else if (ValidateAuthentication(http_msg)) {
// convert method from cstring to std string
std::string method_string(http_msg->method.buf, http_msg->method.len);
@@ -126,39 +100,21 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms
// convert uri from cstring to std string
std::string uri(http_msg->uri.buf, http_msg->uri.len);
// check for root path
if (uri.find(root_path) == 0) {
// remove root path from uri
uri.erase(0, root_path.length());
// convert uri to uppercase
std::transform(uri.begin(), uri.end(), uri.begin(), ::toupper);
// convert uri string to route enum
ChatWebAPI::eRoute route = magic_enum::enum_cast<ChatWebAPI::eRoute>(uri).value_or(ChatWebAPI::eRoute::INVALID);
std::transform(uri.begin(), uri.end(), uri.begin(), ::tolower);
// convert body from cstring to std string
std::string body(http_msg->body.buf, http_msg->body.len);
// convert body from cstring to std string
std::string body(http_msg->body.buf, http_msg->body.len);
switch (method) {
case eHTTPMethod::GET:
HandleGET(reply, route, body);
break;
case eHTTPMethod::POST:
HandlePOST(reply, route, body);
break;
case eHTTPMethod::PUT:
case eHTTPMethod::DELETE:
case eHTTPMethod::HEAD:
case eHTTPMethod::CONNECT:
case eHTTPMethod::OPTIONS:
case eHTTPMethod::TRACE:
case eHTTPMethod::PATCH:
case eHTTPMethod::INVALID:
default:
reply.status = eHTTPStatusCode::METHOD_NOT_ALLOWED;
reply.message = "{\"error\":\"Invalid Method\"}";
break;
}
}
const auto routeItr = Routes.find({method, uri});
if (routeItr != Routes.end()) {
const auto& [_, route] = *routeItr;
route.handle(reply, body);
} else HandleInvalidRoute(reply);
} else {
reply.status = eHTTPStatusCode::UNAUTHORIZED;
reply.message = "{\"error\":\"Unauthorized\"}";
}
mg_http_reply(connection, static_cast<int>(reply.status), json_content_type, reply.message.c_str());
}
@@ -174,9 +130,14 @@ void HandleRequests(mg_connection* connection, int request, void* request_data)
}
}
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma pop_macro("DELETE")
#endif
void ChatWebAPI::RegisterHTTPRoutes(WebAPIHTTPRoute route) {
auto [_, success] = Routes.try_emplace({ route.method, route.path }, route);
if (!success) {
LOG_DEBUG("Failed to register route %s", route.path.c_str());
} else {
LOG_DEBUG("Registered route %s", route.path.c_str());
}
}
ChatWebAPI::ChatWebAPI() {
mg_log_set(MG_LL_NONE);
@@ -187,8 +148,8 @@ ChatWebAPI::~ChatWebAPI() {
mg_mgr_free(&mgr);
}
void ChatWebAPI::Listen() {
// make listen address
bool ChatWebAPI::Startup() {
// Make listen address
std::string listen_ip = Game::config->GetValue("web_server_listen_ip");
if (listen_ip == "localhost") listen_ip = "127.0.0.1";
@@ -198,10 +159,38 @@ void ChatWebAPI::Listen() {
// Create HTTP listener
if (!mg_http_listen(&mgr, listen_address.c_str(), HandleRequests, NULL)) {
LOG("Failed to create web server listener");
LOG("Failed to create web server listener on %s", listen_port.c_str());
return false;
}
// Register routes
// API v1 routes
std::string v1_route = "/api/v1/";
RegisterHTTPRoutes({
.path = v1_route + "players",
.method = eHTTPMethod::GET,
.handle = HandlePlayersRequest
});
RegisterHTTPRoutes({
.path = v1_route + "teams",
.method = eHTTPMethod::GET,
.handle = HandleTeamsRequest
});
RegisterHTTPRoutes({
.path = v1_route + "announce",
.method = eHTTPMethod::POST,
.handle = HandleAnnounceRequest
});
return true;
}
void ChatWebAPI::ReceiveRequests() {
mg_mgr_poll(&mgr, 15);
}
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma pop_macro("DELETE")
#endif

View File

@@ -1,29 +1,36 @@
#ifndef CHATWEBAPI_H
#define CHATWEBAPI_H
#ifndef __CHATWEBAPI_H__
#define __CHATWEBAPI_H__
#include <string>
#include <functional>
#include "mongoose.h"
#include "eHTTPStatusCode.h"
enum class eHTTPMethod;
typedef struct mg_mgr mg_mgr;
struct HTTPReply {
eHTTPStatusCode status = eHTTPStatusCode::NOT_FOUND;
std::string message = "{\"error\":\"Not Found\"}";
};
struct WebAPIHTTPRoute {
std::string path;
eHTTPMethod method;
std::function<void(HTTPReply&, const std::string&)> handle;
};
class ChatWebAPI {
public:
ChatWebAPI();
~ChatWebAPI();
void ReceiveRequests();
void Listen();
enum class eRoute {
// GET
PLAYERS,
TEAMS,
// POST
ANNOUNCE,
// INVALID
INVALID
};
void RegisterHTTPRoutes(WebAPIHTTPRoute route);
bool Startup();
private:
mg_mgr mgr;
};
#endif
#endif // __CHATWEBAPI_H__

View File

@@ -17,7 +17,11 @@ void to_json(json& data, const PlayerData& playerData) {
}
void to_json(json& data, const PlayerContainer& playerContainer) {
data = playerContainer.GetAllPlayers();
data = json::array();
for(auto& playerData : playerContainer.GetAllPlayers()) {
if (playerData.first == LWOOBJID_EMPTY) continue;
data.push_back(playerData.second);
}
}
void to_json(json& data, const TeamContainer& teamContainer) {

View File

@@ -32,7 +32,10 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
return;
}
auto isLogin = !m_Players.contains(playerId);
auto& data = m_Players[playerId];
data = PlayerData();
data.isLogin = isLogin;
data.playerID = playerId;
uint32_t len;

View File

@@ -52,6 +52,7 @@ struct PlayerData {
std::vector<IgnoreData> ignoredPlayers;
eGameMasterLevel gmLevel = static_cast<eGameMasterLevel>(0); // CIVILLIAN
bool isFTP = false;
bool isLogin = false;
};
@@ -97,8 +98,8 @@ public:
LWOOBJID GetId(const std::u16string& playerName);
uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; }
uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; }
const TeamContainer& GetTeamContainer() { return teamContainer; }
std::vector<TeamData*>& GetTeamsMut() { return teamContainer.mTeams; };
const TeamContainer& GetTeamContainer() { return m_TeamContainer; }
std::vector<TeamData*>& GetTeamsMut() { return m_TeamContainer.mTeams; };
const std::vector<TeamData*>& GetTeams() { return GetTeamsMut(); };
void Update(const float deltaTime);
@@ -107,7 +108,7 @@ public:
private:
LWOOBJID m_TeamIDCounter = 0;
std::map<LWOOBJID, PlayerData> m_Players;
TeamContainer teamContainer{};
TeamContainer m_TeamContainer{};
std::unordered_map<LWOOBJID, std::u16string> m_Names;
std::map<LWOOBJID, float> m_PlayersToRemove;
uint32_t m_MaxNumberOfBestFriends = 5;

View File

@@ -5,6 +5,7 @@
#include "Logger.h"
#include "Game.h"
#include <type_traits>
#include <unordered_map>
#include <vector>
@@ -39,6 +40,7 @@ public:
// AMFValue template class instantiations
template <typename ValueType>
class AMFValue : public AMFBaseValue {
static_assert(!std::is_same_v<ValueType, std::string_view>, "AMFValue cannot be instantiated with std::string_view");
public:
AMFValue() = default;
AMFValue(const ValueType value) : m_Data{ value } {}
@@ -51,6 +53,15 @@ public:
void SetValue(const ValueType value) { m_Data = value; }
AMFValue<ValueType>& operator=(const AMFValue<ValueType>& other) {
return operator=(other.m_Data);
}
AMFValue<ValueType>& operator=(const ValueType& other) {
m_Data = other;
return *this;
}
protected:
ValueType m_Data;
};
@@ -210,13 +221,17 @@ public:
* @param key The key to associate with the value
* @param value The value to insert
*/
void Insert(const std::string_view key, std::unique_ptr<AMFBaseValue> value) {
template<typename AmfType>
AmfType& Insert(const std::string_view key, std::unique_ptr<AmfType> value) {
const auto element = m_Associative.find(key);
auto& toReturn = *value;
if (element != m_Associative.cend() && element->second) {
element->second = std::move(value);
} else {
m_Associative.emplace(key, std::move(value));
}
return toReturn;
}
/**
@@ -228,11 +243,15 @@ public:
* @param key The key to associate with the value
* @param value The value to insert
*/
void Insert(const size_t index, std::unique_ptr<AMFBaseValue> value) {
template<typename AmfType>
AmfType& Insert(const size_t index, std::unique_ptr<AmfType> value) {
auto& toReturn = *value;
if (index >= m_Dense.size()) {
m_Dense.resize(index + 1);
}
m_Dense.at(index) = std::move(value);
return toReturn;
}
/**
@@ -257,10 +276,10 @@ public:
*
* @param key The key to remove from the associative portion
*/
void Remove(const std::string& key, const bool deleteValue = true) {
void Remove(const std::string& key) {
const AMFAssociative::const_iterator it = m_Associative.find(key);
if (it != m_Associative.cend()) {
if (deleteValue) m_Associative.erase(it);
m_Associative.erase(it);
}
}
@@ -343,6 +362,18 @@ public:
return index < m_Dense.size() ? m_Dense.at(index).get() : nullptr;
}
void Reset() {
m_Associative.clear();
m_Dense.clear();
}
template<typename AmfType = AMFArrayValue>
AmfType& PushDebug(const std::string_view name) {
auto* value = PushArray();
value->Insert("name", name.data());
return value->Insert<AmfType>("value", std::make_unique<AmfType>());
}
private:
/**
* The associative portion. These values are key'd with strings to an AMFValue.

View File

@@ -1603,12 +1603,17 @@ namespace MessageType {
UPDATE_FORGED_ITEM = 1768,
CAN_ITEMS_BE_REFORGED = 1769,
NOTIFY_CLIENT_RAIL_START_FAILED = 1771,
GET_IS_ON_RAIL = 1772
GET_IS_ON_RAIL = 1772,
// DLU CUSTOM GAME MESSAGES, DO NOT NETWORK OR SEND TO CLIENTS (it wont do anything bad but still dont do it >:( )
CLEAR_SESSION_FLAGS = 2000,
SET_RETROACTIVE_FLAGS = 2001,
GAME_MESSAGES_END,
};
}
template <>
struct magic_enum::customize::enum_range<MessageType::Game> {
static constexpr int min = 0;
static constexpr int max = 1772;
static constexpr int max = static_cast<int>(MessageType::Game::GAME_MESSAGES_END) - 1;
};

View File

@@ -62,7 +62,7 @@ enum class eReplicaComponentType : uint32_t {
SOUND_AMBIENT_2D,
SOUND_AMBIENT_3D,
PRECONDITION,
PLAYER_FLAG,
FLAG,
CUSTOM_BUILD_ASSEMBLY,
BASE_COMBAT_AI,
MODULE_ASSEMBLY,

View File

@@ -0,0 +1,38 @@
#include "CDPlayerFlagsTable.h"
#include "CDClientDatabase.h"
namespace CDPlayerFlagsTable {
Table entries;
void ReadEntry(CppSQLite3Query& table) {
Entry entry;
entry.sessionOnly = table.getIntField("SessionOnly") == 1;
entry.onlySetByServer = table.getIntField("OnlySetByServer") == 1;
entry.sessionZoneOnly = table.getIntField("SessionZoneOnly") == 1;
entries[table.getIntField("id")] = entry;
}
void LoadValuesFromDatabase() {
auto table = CDClientDatabase::ExecuteQuery("SELECT * FROM PlayerFlags;");
if (!table.eof()) {
do {
ReadEntry(table);
} while (!table.nextRow());
}
}
const std::optional<Entry> GetEntry(const FlagId flagId) {
if (!entries.contains(flagId)) {
auto table = CDClientDatabase::CreatePreppedStmt("SELECT * FROM PlayerFlags WHERE id = ?;");
table.bind(1, static_cast<int>(flagId));
auto result = table.execQuery();
if (!result.eof()) {
ReadEntry(result);
}
}
return entries[flagId];
}
}

View File

@@ -0,0 +1,21 @@
#ifndef CDPLAYERFLAGSTABLE_H
#define CDPLAYERFLAGSTABLE_H
#include <map>
#include <optional>
namespace CDPlayerFlagsTable {
struct Entry {
bool sessionOnly{};
bool onlySetByServer{};
bool sessionZoneOnly{};
};
using FlagId = uint32_t;
using Table = std::map<FlagId, std::optional<Entry>>;
void LoadValuesFromDatabase();
const std::optional<Entry> GetEntry(const FlagId flagId);
};
#endif //!CDPLAYERFLAGSTABLE_H

View File

@@ -25,6 +25,7 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp"
"CDObjectsTable.cpp"
"CDPetComponentTable.cpp"
"CDPackageComponentTable.cpp"
"CDPlayerFlagsTable.cpp"
"CDPhysicsComponentTable.cpp"
"CDPropertyEntranceComponentTable.cpp"
"CDPropertyTemplateTable.cpp"

View File

@@ -9,10 +9,11 @@ public:
struct MasterInfo {
std::string ip;
uint32_t port{};
std::string password{};
};
// Set the master server ip and port.
virtual void SetMasterIp(const std::string_view ip, const uint32_t port) = 0;
virtual void SetMasterInfo(const MasterInfo& info) = 0;
// Get the master server info.
virtual std::optional<MasterInfo> GetMasterInfo() = 0;

View File

@@ -96,7 +96,7 @@ public:
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterIp(const std::string_view ip, const uint32_t port) override;
void SetMasterInfo(const IServers::MasterInfo& info) override;
std::optional<uint32_t> GetCurrentPersistentId() override;
void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;

View File

@@ -1,14 +1,14 @@
#include "MySQLDatabase.h"
void MySQLDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) {
void MySQLDatabase::SetMasterInfo(const MasterInfo& info) {
// We only want our 1 entry anyways, so we can just delete all and reinsert the one we want
// since it would be two queries anyways.
ExecuteDelete("TRUNCATE TABLE servers;");
ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`) VALUES ('master', ?, ?, 0, 171022)", ip, port);
ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`, `master_password`) VALUES ('master', ?, ?, 0, 171022, ?)", info.ip, info.port, info.password);
}
std::optional<IServers::MasterInfo> MySQLDatabase::GetMasterInfo() {
auto result = ExecuteSelect("SELECT ip, port FROM servers WHERE name='master' LIMIT 1;");
auto result = ExecuteSelect("SELECT ip, port, master_password FROM servers WHERE name='master' LIMIT 1;");
if (!result->next()) {
return std::nullopt;
@@ -18,6 +18,7 @@ std::optional<IServers::MasterInfo> MySQLDatabase::GetMasterInfo() {
toReturn.ip = result->getString("ip").c_str();
toReturn.port = result->getInt("port");
toReturn.password = result->getString("master_password").c_str();
return toReturn;
}

View File

@@ -94,7 +94,7 @@ public:
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterIp(const std::string_view ip, const uint32_t port) override;
void SetMasterInfo(const IServers::MasterInfo& info) override;
std::optional<uint32_t> GetCurrentPersistentId() override;
void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;

View File

@@ -1,14 +1,14 @@
#include "SQLiteDatabase.h"
void SQLiteDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) {
void SQLiteDatabase::SetMasterInfo(const MasterInfo& info) {
// We only want our 1 entry anyways, so we can just delete all and reinsert the one we want
// since it would be two queries anyways.
ExecuteDelete("DELETE FROM servers;");
ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`) VALUES ('master', ?, ?, 0, 171022)", ip, port);
ExecuteInsert("INSERT INTO `servers` (`name`, `ip`, `port`, `state`, `version`, `master_password`) VALUES ('master', ?, ?, 0, 171022, ?)", info.ip, info.port, info.password);
}
std::optional<IServers::MasterInfo> SQLiteDatabase::GetMasterInfo() {
auto [_, result] = ExecuteSelect("SELECT ip, port FROM servers WHERE name='master' LIMIT 1;");
auto [_, result] = ExecuteSelect("SELECT ip, port, master_password FROM servers WHERE name='master' LIMIT 1;");
if (result.eof()) {
return std::nullopt;
@@ -18,6 +18,7 @@ std::optional<IServers::MasterInfo> SQLiteDatabase::GetMasterInfo() {
toReturn.ip = result.getStringField("ip");
toReturn.port = result.getIntField("port");
toReturn.password = result.getStringField("master_password");
return toReturn;
}

View File

@@ -236,7 +236,7 @@ void TestSQLDatabase::InsertNewAccount(const std::string_view username, const st
}
void TestSQLDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) {
void TestSQLDatabase::SetMasterInfo(const IServers::MasterInfo& info) {
}

View File

@@ -73,7 +73,7 @@ class TestSQLDatabase : public GameDatabase {
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterIp(const std::string_view ip, const uint32_t port) override;
void SetMasterInfo(const IServers::MasterInfo& info) override;
std::optional<uint32_t> GetCurrentPersistentId() override;
void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;

View File

@@ -21,6 +21,8 @@
#include "eObjectBits.h"
#include "eGameMasterLevel.h"
#include "ePlayerFlag.h"
#include "CDPlayerFlagsTable.h"
#include "FlagComponent.h"
Character::Character(uint32_t id, User* parentUser) {
//First load the name, etc:
@@ -36,7 +38,7 @@ Character::~Character() {
m_ParentUser = nullptr;
}
void Character::UpdateInfoFromDatabase() {
void Character::UpdateInfoFromDatabase(bool clearSessionFlags) {
auto charInfo = Database::Get()->GetCharacterInfo(m_ID);
if (charInfo) {
@@ -64,10 +66,17 @@ void Character::UpdateInfoFromDatabase() {
m_OurEntity = nullptr;
m_BuildMode = false;
// This is not done through a game message because our current implementation does this at a point
// in time where an Entity does not exist, so there is no FlagComponent to handle the msg.
if (clearSessionFlags) {
FlagComponent::ClearSessionFlags(m_Doc);
WriteToDatabase();
}
}
void Character::UpdateFromDatabase() {
UpdateInfoFromDatabase();
void Character::UpdateFromDatabase(bool clearSessionFlags) {
UpdateInfoFromDatabase(clearSessionFlags);
}
void Character::DoQuickXMLDataParse() {
@@ -196,25 +205,6 @@ void Character::DoQuickXMLDataParse() {
character->QueryAttribute("lzrz", &m_OriginalRotation.z);
character->QueryAttribute("lzrw", &m_OriginalRotation.w);
}
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (flags) {
auto* currentChild = flags->FirstChildElement();
while (currentChild) {
const auto* temp = currentChild->Attribute("v");
const auto* id = currentChild->Attribute("id");
if (temp && id) {
uint32_t index = 0;
uint64_t value = 0;
index = std::stoul(id);
value = std::stoull(temp);
m_PlayerFlags.insert(std::make_pair(index, value));
}
currentChild = currentChild->NextSiblingElement();
}
}
}
void Character::UnlockEmote(int emoteID) {
@@ -231,6 +221,12 @@ void Character::SetBuildMode(bool buildMode) {
}
void Character::SaveXMLToDatabase() {
// Check that we can actually _save_ before saving
if (!m_OurEntity) {
LOG("%i:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str());
return;
}
//For metrics, we'll record the time it took to save:
auto start = std::chrono::system_clock::now();
@@ -269,47 +265,8 @@ void Character::SaveXMLToDatabase() {
character->LinkEndChild(emotes);
}
//Export our flags:
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (!flags) {
flags = m_Doc.NewElement("flag"); //Create a flags tag if we don't have one
m_Doc.FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time
}
flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes
for (std::pair<uint32_t, uint64_t> flag : m_PlayerFlags) {
auto* f = m_Doc.NewElement("f");
f->SetAttribute("id", flag.first);
//Because of the joy that is tinyxml2, it doesn't offer a function to set a uint64 as an attribute.
//Only signed 64-bits ints would work.
std::string v = std::to_string(flag.second);
f->SetAttribute("v", v.c_str());
flags->LinkEndChild(f);
}
// Prevents the news feed from showing up on world transfers
if (GetPlayerFlag(ePlayerFlag::IS_NEWS_SCREEN_VISIBLE)) {
auto* s = m_Doc.NewElement("s");
s->SetAttribute("si", ePlayerFlag::IS_NEWS_SCREEN_VISIBLE);
flags->LinkEndChild(s);
}
if (GetPlayerFlag(ePlayerFlag::EQUPPED_TRIAL_FACTION_GEAR)) {
auto* s = m_Doc.NewElement("s");
s->SetAttribute("si", ePlayerFlag::EQUPPED_TRIAL_FACTION_GEAR);
flags->LinkEndChild(s);
}
SaveXmlRespawnCheckpoints();
//Call upon the entity to update our xmlDoc:
if (!m_OurEntity) {
LOG("%i:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str());
return;
}
m_OurEntity->UpdateXMLDoc(m_Doc);
WriteToDatabase();
@@ -320,23 +277,6 @@ void Character::SaveXMLToDatabase() {
LOG("%i:%s Saved character to Database in: %fs", this->GetID(), this->GetName().c_str(), elapsed.count());
}
void Character::SetIsNewLogin() {
// If we dont have a flag element, then we cannot have a s element as a child of flag.
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (!flags) return;
auto* currentChild = flags->FirstChildElement();
while (currentChild) {
auto* nextChild = currentChild->NextSiblingElement();
if (currentChild->Attribute("si")) {
flags->DeleteChild(currentChild);
LOG("Removed isLoggedIn flag from character %i:%s, saving character to database", GetID(), GetName().c_str());
WriteToDatabase();
}
currentChild = nextChild;
}
}
void Character::WriteToDatabase() {
//Dump our xml into m_XMLData:
tinyxml2::XMLPrinter printer(0, true, 0);
@@ -346,88 +286,6 @@ void Character::WriteToDatabase() {
Database::Get()->UpdateCharacterXml(m_ID, printer.CStr());
}
void Character::SetPlayerFlag(const uint32_t flagId, const bool value) {
// If the flag is already set, we don't have to recalculate it
if (GetPlayerFlag(flagId) == value) return;
if (value) {
// Update the mission component:
auto* player = Game::entityManager->GetEntity(m_ObjectID);
if (player != nullptr) {
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::PLAYER_FLAG, flagId);
}
}
}
if (flagId == EQUPPED_TRIAL_FACTION_GEAR || flagId == IS_NEWS_SCREEN_VISIBLE) {
if (value) m_SessionFlags.insert(flagId);
else m_SessionFlags.erase(flagId);
} else {
// Calculate the index first
auto flagIndex = uint32_t(std::floor(flagId / 64));
const auto shiftedValue = 1ULL << flagId % 64;
auto it = m_PlayerFlags.find(flagIndex);
// Check if flag index exists
if (it != m_PlayerFlags.end()) {
// Update the value
if (value) {
it->second |= shiftedValue;
} else {
it->second &= ~shiftedValue;
}
} else {
if (value) {
// Otherwise, insert the value
uint64_t flagValue = 0;
flagValue |= shiftedValue;
m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue));
}
}
}
// Notify the client that a flag has changed server-side
GameMessages::SendNotifyClientFlagChange(m_ObjectID, flagId, value, m_ParentUser->GetSystemAddress());
}
bool Character::GetPlayerFlag(const uint32_t flagId) const {
using enum ePlayerFlag;
bool toReturn = false; //by def, return false.
// TODO make actual session flag checker using flags table in database.
if (flagId == EQUPPED_TRIAL_FACTION_GEAR || flagId == IS_NEWS_SCREEN_VISIBLE) {
toReturn = m_SessionFlags.contains(flagId);
} else {
// Calculate the index first
const auto flagIndex = uint32_t(std::floor(flagId / 64));
const auto shiftedValue = 1ULL << flagId % 64;
auto it = m_PlayerFlags.find(flagIndex);
if (it != m_PlayerFlags.end()) {
// Don't set the data if we don't have to
toReturn = (it->second & shiftedValue) != 0;
}
}
return toReturn;
}
void Character::SetRetroactiveFlags() {
// Retroactive check for if player has joined a faction to set their 'joined a faction' flag to true.
if (GetPlayerFlag(ePlayerFlag::VENTURE_FACTION) || GetPlayerFlag(ePlayerFlag::ASSEMBLY_FACTION) || GetPlayerFlag(ePlayerFlag::PARADOX_FACTION) || GetPlayerFlag(ePlayerFlag::SENTINEL_FACTION)) {
SetPlayerFlag(ePlayerFlag::JOINED_A_FACTION, true);
}
}
void Character::SaveXmlRespawnCheckpoints() {
//Export our respawn points:
auto* points = m_Doc.FirstChildElement("obj")->FirstChildElement("res");
@@ -484,7 +342,11 @@ void Character::OnZoneLoad() {
if (missionComponent != nullptr) {
// Fix the monument race flag
if (missionComponent->GetMissionState(319) >= eMissionState::READY_TO_COMPLETE) {
SetPlayerFlag(ePlayerFlag::AG_FINISH_LINE_BUILT, true);
GameMessages::SetFlag setFlag{};
setFlag.target = m_ObjectID;
setFlag.iFlagId = ePlayerFlag::AG_FINISH_LINE_BUILT;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
}

View File

@@ -31,7 +31,7 @@ public:
*/
void WriteToDatabase();
void SaveXMLToDatabase();
void UpdateFromDatabase();
void UpdateFromDatabase(bool clearSessionFlags = false);
void SaveXmlRespawnCheckpoints();
void LoadXmlRespawnCheckpoints();
@@ -40,15 +40,6 @@ public:
const tinyxml2::XMLDocument& GetXMLDoc() const { return m_Doc; }
void _setXmlDoc(tinyxml2::XMLDocument& doc) { doc.DeepCopy(&m_Doc); }
/**
* Out of abundance of safety and clarity of what this saves, this is its own function.
*
* Clears the s element from the flag element and saves the xml to the database. Used to prevent the news
* feed from showing up on world transfers.
*
*/
void SetIsNewLogin();
/**
* Gets the database ID of the character
* @return the database ID of the character
@@ -410,32 +401,11 @@ public:
*/
void UnlockEmote(int emoteID);
/**
* Sets a flag for the character, indicating certain parts of the game that have been interacted with. Not to be
* confused with the permissions
* @param flagId the ID of the flag to set
* @param value the value to set for the flag
*/
void SetPlayerFlag(uint32_t flagId, bool value);
/**
* Gets the value for a certain character flag
* @param flagId the ID of the flag to get a value for
* @return the value of the flag given the ID (the default is false, obviously)
*/
bool GetPlayerFlag(uint32_t flagId) const;
/**
* Notifies the character that they're now muted
*/
void SendMuteNotice() const;
/**
* Sets any flags that are meant to have been set that may not have been set due to them being
* missing in a previous patch.
*/
void SetRetroactiveFlags();
/**
* Get the equipped items for this character, only used for character creation
* @return the equipped items for this character on world load
@@ -465,7 +435,7 @@ public:
void _setXmlData(const std::string& xmlData) { m_XMLData = xmlData; }
private:
void UpdateInfoFromDatabase();
void UpdateInfoFromDatabase(bool clearSessionFlags);
/**
* The ID of this character. First 32 bits of the ObjectID.
*/
@@ -620,17 +590,6 @@ private:
*/
uint64_t m_LastLogin{};
/**
* Flags only set for the duration of a session
*
*/
std::set<uint32_t> m_SessionFlags;
/**
* The gameplay flags this character has (not just true values)
*/
std::unordered_map<uint32_t, uint64_t> m_PlayerFlags;
/**
* The character XML belonging to this character
*/

View File

@@ -84,6 +84,7 @@
#include "GhostComponent.h"
#include "AchievementVendorComponent.h"
#include "VanityUtilities.h"
#include "FlagComponent.h"
// Table includes
#include "CDComponentsRegistryTable.h"
@@ -96,6 +97,7 @@
#include "CDScriptComponentTable.h"
#include "CDSkillBehaviorTable.h"
#include "CDZoneTableTable.h"
#include "StringifiedEnum.h"
#include <ranges>
@@ -139,6 +141,8 @@ Entity::Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser, Enti
PlayerManager::AddPlayer(this);
}
RegisterMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, this, &Entity::OnRequestServerObjectInfo);
}
Entity::~Entity() {
@@ -386,6 +390,9 @@ void Entity::Initialize() {
if (m_Character) {
comp->LoadFromXml(m_Character->GetXMLDoc());
} else {
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
comp->SetIsSmashable(comp->GetIsSmashable() | isSmashable);
if (componentID > 0) {
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
@@ -420,9 +427,6 @@ void Entity::Initialize() {
comp->SetMinCoins(currencyValues[0].minvalue);
comp->SetMaxCoins(currencyValues[0].maxvalue);
}
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
comp->SetIsSmashable(comp->GetIsSmashable() | isSmashable);
}
} else {
comp->SetHealth(1);
@@ -482,6 +486,8 @@ void Entity::Initialize() {
AddComponent<CharacterComponent>(m_Character, systemAddress)->LoadFromXml(m_Character->GetXMLDoc());
AddComponent<GhostComponent>();
AddComponent<FlagComponent>()->LoadFromXml(m_Character->GetXMLDoc());
}
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::INVENTORY) > 0 || m_Character) {
@@ -1565,7 +1571,7 @@ void Entity::Kill(Entity* murderer, const eKillType killType) {
m_DieCallbacks.clear();
//OMAI WA MOU, SHINDERIU
//お前はもう死んでいる
GetScript()->OnDie(this, murderer);
@@ -2208,7 +2214,7 @@ void Entity::SetRespawnRot(const NiQuaternion& rotation) {
int32_t Entity::GetCollisionGroup() const {
for (const auto* component : m_Components | std::views::values) {
auto* compToCheck = dynamic_cast<const PhysicsComponent*>(component);
auto* compToCheck = dynamic_cast<const PhysicsComponent*>(component);
if (compToCheck) {
return compToCheck->GetCollisionGroup();
}
@@ -2216,3 +2222,125 @@ int32_t Entity::GetCollisionGroup() const {
return 0;
}
bool Entity::HandleMsg(GameMessages::GameMsg& msg) const {
bool handled = false;
const auto [beg, end] = m_MsgHandlers.equal_range(msg.msgId);
for (auto it = beg; it != end; ++it) {
if (it->second) handled |= it->second(msg);
}
return handled;
}
void Entity::RegisterMsg(const MessageType::Game msgId, std::function<bool(GameMessages::GameMsg&)> handler) {
m_MsgHandlers.emplace(msgId, handler);
}
bool Entity::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) {
auto& request = static_cast<GameMessages::RequestServerObjectInfo&>(msg);
AMFArrayValue response;
response.Insert("visible", true);
response.Insert("objectID", std::to_string(request.targetForReport));
response.Insert("serverInfo", true);
auto& data = *response.InsertArray("data");
auto& objectDetails = data.PushDebug("Object Details");
objectDetails.PushDebug<AMFStringValue>("Name") = "";
objectDetails.PushDebug<AMFIntValue>("Template ID(LOT)") = GetLOT();
objectDetails.PushDebug<AMFStringValue>("Object ID") = std::to_string(GetObjectID());
objectDetails.PushDebug<AMFStringValue>("Type") = "";
if (!m_ParentEntity) {
objectDetails.PushDebug<AMFBoolValue>("Has a parent object") = false;
} else {
objectDetails.PushDebug<AMFStringValue>("Parent's Object ID") = std::to_string(m_ParentEntity->GetObjectID());
}
if (!m_ChildEntities.empty()) {
if (!request.bVerbose) {
objectDetails.PushDebug("Child Objects (shown only in high-detail reports)");
} else {
auto& childDetails = objectDetails.PushDebug("Child Objects");
for (const auto* childObj : m_ChildEntities) {
std::stringstream stream;
stream << "Child ID: " << childObj->GetObjectID();
childDetails.PushDebug<AMFStringValue>(stream.str().c_str()) = std::to_string(childObj->GetObjectID());
}
}
}
if (!request.bVerbose) {
objectDetails.PushDebug("Component Information (shown only in high-detail reports)");
objectDetails.PushDebug("Memory Usage Info (shown only in high-detail reports)");
objectDetails.PushDebug("Registered Messages (shown only in high-detail reports)");
} else {
auto& componentInfo = objectDetails.PushDebug("Component Information");
for (const auto& [componentType, component] : m_Components) {
std::stringstream stream;
const auto componentName = StringifiedEnum::ToString(componentType);
stream << componentName.data() << ", Active";
auto& currentCompInfo = componentInfo.PushDebug(stream.str().c_str());
currentCompInfo.PushDebug<AMFIntValue>("Type ID") = GeneralUtils::ToUnderlying(componentType);
auto& memUsageInfo = objectDetails.PushDebug("Memory Usage Info");
memUsageInfo.PushDebug<AMFIntValue>("Static size of this GameObject") = sizeof(Entity);
int staticSizeObject{};
objectDetails.PushDebug<AMFBoolValue>("Is a human player") = IsPlayer();
if (!m_Groups.empty() && request.bVerbose) {
auto& groupInfo = objectDetails.PushDebug("Group Info");
for (const auto& group : m_Groups) {
groupInfo.PushDebug<AMFStringValue>(group) = "";
}
}
LWOOBJID upper32Bits = GetObjectID();
upper32Bits >>= 32;
if (((upper32Bits & 0xfc000000) == 0x4000000) && ((upper32Bits & 0xffffc000) != 0x4000000)) {
objectDetails.PushDebug<AMFStringValue>("Object ID Type") = "Spawned";
} else if (!(upper32Bits & 0xf8000000U)) {
if ((upper32Bits & 0xffffc000U) == 0x4000000) {
objectDetails.PushDebug<AMFStringValue>("Object ID Type") = "Local";
} else if (!(upper32Bits & 0xfc000000U)) {
objectDetails.PushDebug<AMFStringValue>("Object ID Type") = "Static";
}
} else {
objectDetails.PushDebug<AMFStringValue>("Object ID Type") = "Global";
}
LWOOBJID spawnerId{ LWOOBJID_EMPTY };
if (m_Spawner) {
objectDetails.PushDebug<AMFStringValue>("Spawner's Object ID") = std::to_string(m_Spawner->m_Info.spawnerID);
}
objectDetails.PushDebug<AMFStringValue>("Precondition(s)");
objectDetails.PushDebug<AMFIntValue>("Components Waiting") = 0;
if (!request.bVerbose) {
objectDetails.PushDebug("Config Data (shown only in high-detail reports)");
} else {
auto& configInfo = objectDetails.PushDebug("Config Data");
for (const auto& setting : GetSettings()) {
configInfo.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString();
}
}
}
}
GameMessages::GetObjectReportInfo report;
report.target = request.targetForReport;
report.bVerbose = request.bVerbose;
report.info = &data;
SEND_ENTITY_MSG(report);
auto* player = Game::entityManager->GetEntity(request.clientId);
if (player) GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, player->GetSystemAddress());
return true;
}

View File

@@ -14,12 +14,17 @@
#include "Observable.h"
namespace GameMessages {
struct GameMsg;
struct ActivityNotify;
struct ShootingGalleryFire;
struct ChildLoaded;
struct PlayerResurrectionFinished;
};
namespace MessageType {
enum class Game : uint16_t;
}
namespace Loot {
class Info;
};
@@ -316,6 +321,16 @@ public:
// Scale will only be communicated to the client when the construction packet is sent
void SetScale(const float scale) { m_Scale = scale; };
void RegisterMsg(const MessageType::Game msgId, std::function<bool(GameMessages::GameMsg&)> handler);
bool HandleMsg(GameMessages::GameMsg& msg) const;
bool OnRequestServerObjectInfo(GameMessages::GameMsg& msg);
void RegisterMsg(const MessageType::Game msgId, auto* self, const auto handler) {
RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1));
}
/**
* @brief The observable for player entity position updates.
*/
@@ -377,6 +392,8 @@ protected:
// objectID of receiver and map of notification name to script
std::map<LWOOBJID, std::map<std::string, CppScripts::Script*>> m_Subscriptions;
std::multimap<MessageType::Game, std::function<bool(GameMessages::GameMsg&)>> m_MsgHandlers;
};
/**

View File

@@ -419,7 +419,7 @@ void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr)
void EntityManager::SerializeEntity(Entity* entity) {
if (!entity) return;
EntityManager::SerializeEntity(*entity);
}
@@ -604,3 +604,23 @@ void EntityManager::FireEventServerSide(Entity* origin, std::string args) {
bool EntityManager::IsExcludedFromGhosting(LOT lot) {
return std::find(m_GhostingExcludedLOTs.begin(), m_GhostingExcludedLOTs.end(), lot) != m_GhostingExcludedLOTs.end();
}
#ifndef _DEBUG
bool EntityManager::SendMsg(GameMessages::GameMsg& msg, std::source_location location) {
if (msg.target == LWOOBJID_EMPTY) {
LOG("Attempted to send message to empty target from funcion %s:%d", location.function_name(), location.line());
}
bool bRet = false;
auto* entity = GetEntity(msg.target);
if (entity) bRet = entity->HandleMsg(msg);
return bRet;
}
#else
bool EntityManager::SendMsg(GameMessages::GameMsg& msg) {
bool bRet = false;
auto* entity = GetEntity(msg.target);
if (entity) bRet = entity->HandleMsg(msg);
return bRet;
}
#endif

View File

@@ -2,12 +2,20 @@
#define ENTITYMANAGER_H
#include <map>
#include <source_location>
#include <stack>
#include <vector>
#include <unordered_map>
#include "dCommonVars.h"
// Convenience macro to send a message to the entity manager
#define SEND_ENTITY_MSG(msg) Game::entityManager->SendMsg(msg)
namespace GameMessages {
struct GameMsg;
};
class Entity;
class EntityInfo;
class Player;
@@ -72,6 +80,14 @@ public:
const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; };
const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; };
// Sends a message to be handled by the receiving entity
#ifndef _DEBUG
bool SendMsg(GameMessages::GameMsg& msg, std::source_location location = std::source_location::current());
#else
bool SendMsg(GameMessages::GameMsg& msg);
#endif
private:
void SerializeEntities();
void KillEntities();

View File

@@ -197,6 +197,10 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) {
skillComponent->Reset();
}
GameMessages::ClearSessionFlags msg{};
msg.target = chars[i]->GetObjectID();
SEND_ENTITY_MSG(msg);
Game::entityManager->DestroyEntity(chars[i]->GetEntity());
chars[i]->SaveXMLToDatabase();
@@ -210,8 +214,7 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) {
for (const auto& characterId : Database::Get()->GetAccountCharacterIds(u->GetAccountID())) {
Character* character = new Character(characterId, u);
character->UpdateFromDatabase();
character->SetIsNewLogin();
character->UpdateFromDatabase(true);
chars.push_back(character);
}

View File

@@ -10,6 +10,7 @@ set(DGAME_DCOMPONENTS_SOURCES
"ControllablePhysicsComponent.cpp"
"DestroyableComponent.cpp"
"DonationVendorComponent.cpp"
"FlagComponent.cpp"
"GhostComponent.cpp"
"InventoryComponent.cpp"
"ItemComponent.cpp"

View File

@@ -22,6 +22,7 @@
#include "Mail.h"
#include "ZoneInstanceManager.h"
#include "WorldPackets.h"
#include "MessageType/Game.h"
#include <ctime>
CharacterComponent::CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress) : Component(parent) {
@@ -47,6 +48,36 @@ CharacterComponent::CharacterComponent(Entity* parent, Character* character, con
m_CountryCode = 0;
m_LastUpdateTimestamp = std::time(nullptr);
m_SystemAddress = systemAddress;
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &CharacterComponent::OnGetObjectReportInfo);
}
bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& request = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& cmptType = request.info->PushDebug("Character");
cmptType.PushDebug<AMFIntValue>("Component ID") = GeneralUtils::ToUnderlying(ComponentType);
cmptType.PushDebug<AMFIntValue>("Character's account ID") = m_Character->GetParentUser()->GetAccountID();
cmptType.PushDebug<AMFBoolValue>("Last log out time") = m_Character->GetLastLogin();
cmptType.PushDebug<AMFDoubleValue>("Seconds played this session") = 0;
cmptType.PushDebug<AMFBoolValue>("Editor enabled") = false;
cmptType.PushDebug<AMFDoubleValue>("Total number of seconds played") = m_TotalTimePlayed;
cmptType.PushDebug<AMFStringValue>("Total currency") = std::to_string(m_Character->GetCoins());
cmptType.PushDebug<AMFStringValue>("Currency able to be picked up") = std::to_string(m_DroppedCoins);
cmptType.PushDebug<AMFStringValue>("Tooltip flags value") = "0";
// visited locations
cmptType.PushDebug<AMFBoolValue>("is a GM") = m_GMLevel > eGameMasterLevel::CIVILIAN;
cmptType.PushDebug<AMFBoolValue>("Has PVP flag turned on") = m_PvpEnabled;
cmptType.PushDebug<AMFIntValue>("GM Level") = GeneralUtils::ToUnderlying(m_GMLevel);
cmptType.PushDebug<AMFIntValue>("Editor level") = GeneralUtils::ToUnderlying(m_EditorLevel);
cmptType.PushDebug<AMFStringValue>("Guild ID") = "0";
cmptType.PushDebug<AMFStringValue>("Guild Name") = "";
cmptType.PushDebug<AMFDoubleValue>("Reputation") = m_Reputation;
cmptType.PushDebug<AMFIntValue>("Current Activity Type") = GeneralUtils::ToUnderlying(m_CurrentActivity);
cmptType.PushDebug<AMFDoubleValue>("Property Clone ID") = m_Character->GetPropertyCloneID();
return true;
}
bool CharacterComponent::LandingAnimDisabled(int zoneID) {
@@ -81,6 +112,8 @@ CharacterComponent::~CharacterComponent() {
void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
if (bIsInitialUpdate) {
if (!m_Character || !m_Character->GetParentUser()) return;
outBitStream.Write(m_ClaimCodes[0] != 0);
if (m_ClaimCodes[0] != 0) outBitStream.Write(m_ClaimCodes[0]);
outBitStream.Write(m_ClaimCodes[1] != 0);
@@ -100,7 +133,7 @@ void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInit
outBitStream.Write(m_Character->GetEyebrows());
outBitStream.Write(m_Character->GetEyes());
outBitStream.Write(m_Character->GetMouth());
outBitStream.Write<uint64_t>(0); //AccountID, trying out if 0 works.
outBitStream.Write<uint64_t>(m_Character->GetParentUser()->GetAccountID());
outBitStream.Write(m_Character->GetLastLogin()); //Last login
outBitStream.Write<uint64_t>(0); //"prop mod last display time"
outBitStream.Write<uint64_t>(m_Uscore); //u-score

View File

@@ -323,6 +323,8 @@ public:
Character* m_Character;
private:
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
/**
* The map of active venture vision effects
*/

View File

@@ -8,6 +8,10 @@ namespace RakNet {
class BitStream;
}
namespace GameMessages {
struct GameMsg;
}
class Entity;
/**
@@ -52,6 +56,10 @@ public:
protected:
void RegisterMsg(const MessageType::Game msgId, auto* self, const auto handler) {
m_Parent->RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1));
}
/**
* The entity that owns this component
*/

View File

@@ -0,0 +1,222 @@
#include "FlagComponent.h"
#include "CDPlayerFlagsTable.h"
#include "eMissionTaskType.h"
#include "ePlayerFlag.h"
#include "MissionComponent.h"
#include "Amf3.h"
FlagComponent::FlagComponent(Entity* parent) : Component(parent) {
RegisterMsg(MessageType::Game::SET_FLAG, this, &FlagComponent::OnSetFlag);
RegisterMsg(MessageType::Game::GET_FLAG, this, &FlagComponent::OnGetFlag);
RegisterMsg(MessageType::Game::CLEAR_SESSION_FLAGS, this, &FlagComponent::OnClearSessionFlags);
RegisterMsg(MessageType::Game::SET_RETROACTIVE_FLAGS, this, &FlagComponent::OnSetRetroactiveFlags);
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &FlagComponent::OnGetObjectReportInfo);
}
bool FlagComponent::OnSetFlag(GameMessages::GameMsg& msg) {
auto& setFlag = static_cast<GameMessages::SetFlag&>(msg);
SetPlayerFlag(setFlag.iFlagId, setFlag.bFlag);
// This is always set the first time a player loads into a world from character select
// and is used to know when to refresh the players inventory items so they show up.
if (setFlag.iFlagId == ePlayerFlag::IS_NEWS_SCREEN_VISIBLE && setFlag.bFlag) {
m_Parent->SetVar<bool>(u"dlu_first_time_load", true);
}
return true;
}
bool FlagComponent::OnGetFlag(GameMessages::GameMsg& msg) {
auto& getFlag = static_cast<GameMessages::GetFlag&>(msg);
getFlag.bFlag = GetPlayerFlag(getFlag.iFlagId);
return true;
}
void FlagComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
if (!doc.FirstChildElement("obj")) return;
auto& obj = *doc.FirstChildElement("obj");
if (!obj.FirstChildElement("flag")) {
obj.InsertNewChildElement("flag");
}
auto& flags = *obj.FirstChildElement("flag");
flags.DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes
// Save our flags
for (const auto& [index, flagBucket] : m_PlayerFlags) {
auto& f = *flags.InsertNewChildElement("f");
f.SetAttribute("id", index);
f.SetAttribute("v", flagBucket);
}
// Save our session flags
for (const auto& sessionFlag : m_SessionFlags) {
auto& s = *flags.InsertNewChildElement("s");
LOG("Saving session flag %i", sessionFlag);
s.SetAttribute("si", sessionFlag);
}
}
void FlagComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
if (!doc.FirstChildElement("obj")) return;
auto& obj = *doc.FirstChildElement("obj");
if (!obj.FirstChildElement("flag")) return;
auto& flags = *obj.FirstChildElement("flag");
const auto* currentChild = flags.FirstChildElement("f");
while (currentChild) {
const auto* temp = currentChild->Attribute("v");
const auto* id = currentChild->Attribute("id");
if (temp && id) {
uint32_t index = 0;
uint64_t value = 0;
index = std::stoul(id);
value = std::stoull(temp);
m_PlayerFlags.insert(std::make_pair(index, value));
}
currentChild = currentChild->NextSiblingElement("f");
}
// Now load our session flags
currentChild = flags.FirstChildElement("s");
while (currentChild) {
const auto* temp = currentChild->Attribute("si");
if (temp) {
uint32_t sessionIndex = 0;
sessionIndex = std::stoul(temp);
m_SessionFlags.insert(sessionIndex);
}
currentChild = currentChild->NextSiblingElement("s");
}
}
void FlagComponent::SetPlayerFlag(const uint32_t flagId, const bool value) {
// If the flag is already set, we don't have to recalculate it
if (GetPlayerFlag(flagId) == value) return;
if (value) {
// Update the mission component:
auto* missionComponent = m_Parent->GetComponent<MissionComponent>();
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::PLAYER_FLAG, flagId);
}
}
const auto flagEntry = CDPlayerFlagsTable::GetEntry(flagId);
if (flagEntry && flagEntry->sessionOnly) {
if (value) m_SessionFlags.insert(flagId);
else m_SessionFlags.erase(flagId);
} else {
// Calculate the index first
auto flagIndex = uint32_t(std::floor(flagId / 64));
const auto shiftedValue = 1ULL << flagId % 64;
auto it = m_PlayerFlags.find(flagIndex);
// Check if flag index exists
if (it != m_PlayerFlags.end()) {
// Update the value
if (value) {
it->second |= shiftedValue;
} else {
it->second &= ~shiftedValue;
}
} else {
if (value) {
// Otherwise, insert the value
uint64_t flagValue = 0;
flagValue |= shiftedValue;
m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue));
}
}
}
// Notify the client that a flag has changed server-side
GameMessages::SendNotifyClientFlagChange(m_Parent->GetObjectID(), flagId, value, m_Parent->GetSystemAddress());
}
bool FlagComponent::GetPlayerFlag(const uint32_t flagId) const {
bool toReturn = false; //by def, return false.
const auto flagEntry = CDPlayerFlagsTable::GetEntry(flagId);
if (flagEntry && flagEntry->sessionOnly) {
toReturn = m_SessionFlags.contains(flagId);
} else {
// Calculate the index first
const auto flagIndex = uint32_t(std::floor(flagId / 64));
const auto shiftedValue = 1ULL << flagId % 64;
auto it = m_PlayerFlags.find(flagIndex);
if (it != m_PlayerFlags.end()) {
// Don't set the data if we don't have to
toReturn = (it->second & shiftedValue) != 0;
}
}
return toReturn;
}
bool FlagComponent::OnSetRetroactiveFlags(GameMessages::GameMsg& msg) {
// Retroactive check for if player has joined a faction to set their 'joined a faction' flag to true.
if (GetPlayerFlag(ePlayerFlag::VENTURE_FACTION) ||
GetPlayerFlag(ePlayerFlag::ASSEMBLY_FACTION) ||
GetPlayerFlag(ePlayerFlag::PARADOX_FACTION) ||
GetPlayerFlag(ePlayerFlag::SENTINEL_FACTION)) {
SetPlayerFlag(ePlayerFlag::JOINED_A_FACTION, true);
}
return true;
}
bool FlagComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& request = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& cmptType = request.info->PushDebug("Player Flag");
cmptType.PushDebug<AMFIntValue>("Component ID") = GeneralUtils::ToUnderlying(ComponentType);
auto& allFlags = cmptType.PushDebug("All flags");
for (const auto& [id, flagChunk] : m_PlayerFlags) {
const auto base = id * 64;
auto flagChunkCopy = flagChunk;
for (int i = 0; i < 64; i++) {
if (static_cast<bool>(flagChunkCopy & 1)) {
const int32_t flagId = base + i;
std::stringstream stream;
stream << "Flag: " << flagId;
allFlags.PushDebug(stream.str().c_str());
}
flagChunkCopy >>= 1;
}
}
return true;
}
void FlagComponent::ClearSessionFlags(tinyxml2::XMLDocument& doc) {
if (!doc.FirstChildElement("obj")) return;
auto& obj = *doc.FirstChildElement("obj");
if (!obj.FirstChildElement("flag")) return;
auto& flags = *obj.FirstChildElement("flag");
auto* currentChild = flags.FirstChildElement();
while (currentChild) {
auto* nextChild = currentChild->NextSiblingElement();
if (currentChild->Attribute("si")) {
flags.DeleteChild(currentChild);
}
currentChild = nextChild;
}
}

View File

@@ -0,0 +1,62 @@
#ifndef FLAGCOMPONENT_H
#define FLAGCOMPONENT_H
#include <cstdint>
#include <set>
#include <unordered_map>
#include "Component.h"
#include "eReplicaComponentType.h"
class FlagComponent final : public Component {
public:
static const inline eReplicaComponentType ComponentType = eReplicaComponentType::FLAG;
FlagComponent(Entity* parent);
void UpdateXml(tinyxml2::XMLDocument& doc) override;
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
// Used to clear the save data from a static context where you only have a doc (switching characters)
static void ClearSessionFlags(tinyxml2::XMLDocument& doc);
private:
/**
* Sets a flag for the character, indicating certain parts of the game that have been interacted with. Not to be
* confused with the permissions
* @param flagId the ID of the flag to set
* @param value the value to set for the flag
*/
bool OnSetFlag(GameMessages::GameMsg& msg);
void SetPlayerFlag(const uint32_t flagId, const bool value);
/**
* Gets the value for a certain character flag
* @param flagId the ID of the flag to get a value for
* @return the value of the flag given the ID (the default is false, obviously)
*/
bool OnGetFlag(GameMessages::GameMsg& msg);
bool GetPlayerFlag(const uint32_t flagId) const;
bool OnClearSessionFlags(GameMessages::GameMsg& msg) { m_SessionFlags.clear(); return true; }
/**
* Sets any flags that are meant to have been set that may not have been set due to them being
* missing in a previous patch.
*/
bool OnSetRetroactiveFlags(GameMessages::GameMsg& msg);
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
/**
* Flags only set for the duration of a session
*
*/
std::set<uint32_t> m_SessionFlags;
/**
* The gameplay flags this character has (not just true values)
*/
std::unordered_map<uint32_t, uint64_t> m_PlayerFlags;
};
#endif //!FLAGCOMPONENT_H

View File

@@ -557,7 +557,11 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
// Triggers the catch a pet missions
if (petFlags.find(m_Parent->GetLOT()) != petFlags.end()) {
tamer->GetCharacter()->SetPlayerFlag(petFlags.at(m_Parent->GetLOT()), true);
GameMessages::SetFlag setFlag{};
setFlag.target = tamer->GetObjectID();
setFlag.iFlagId = petFlags.at(m_Parent->GetLOT());
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
auto* missionComponent = tamer->GetComponent<MissionComponent>();
@@ -847,7 +851,11 @@ void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) {
Game::entityManager->SerializeEntity(m_Parent);
owner->GetCharacter()->SetPlayerFlag(ePlayerFlag::FIRST_MANUAL_PET_HIBERNATE, true);
GameMessages::SetFlag setFlag{};
setFlag.target = owner->GetObjectID();
setFlag.iFlagId = ePlayerFlag::FIRST_MANUAL_PET_HIBERNATE;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
if (registerPet) {
GameMessages::SendAddPetToPlayer(m_Owner, 0, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, m_Parent->GetLOT(), owner->GetSystemAddress());

View File

@@ -84,11 +84,12 @@ dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type) {
} else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true);
m_Position.y -= (111.467964f * m_Parent->GetDefaultScale()) / 2;
} else if (info->physicsAsset == "env\\GFTrack_DeathVolume1_CaveExit.hkx") {
// Leaving these out for now since they cause more issues than they solve in racing tracks without proper OBB checks.
} /* else if (info->physicsAsset == "env\\GFTrack_DeathVolume1_CaveExit.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 112.416870f, 50.363434f, 87.679268f);
} else if (info->physicsAsset == "env\\GFTrack_DeathVolume2_RoadGaps.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 48.386536f, 50.363434f, 259.361755f);
} else {
} */ else {
// LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str());
//add fallback cube:

View File

@@ -65,14 +65,7 @@ void QuickBuildComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIni
outBitStream.Write(false);
}
// If build state is completed and we've already serialized once in the completed state,
// don't serializing this component anymore as this will cause the build to jump again.
// If state changes, serialization will begin again.
if (!m_StateDirty && m_State == eQuickBuildState::COMPLETED) {
outBitStream.Write0();
outBitStream.Write0();
return;
}
// BEGIN Scripted Activity
outBitStream.Write1();
@@ -90,36 +83,27 @@ void QuickBuildComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIni
}
// END Scripted Activity
outBitStream.Write1();
outBitStream.Write(m_StateDirty || bIsInitialUpdate);
if (m_StateDirty || bIsInitialUpdate) {
outBitStream.Write(m_State);
outBitStream.Write(m_State);
outBitStream.Write(m_ShowResetEffect);
outBitStream.Write(m_Activator != nullptr);
outBitStream.Write(m_ShowResetEffect);
outBitStream.Write(m_Activator != nullptr);
outBitStream.Write(m_Timer);
outBitStream.Write(m_TimerIncomplete);
outBitStream.Write(m_Timer);
outBitStream.Write(m_TimerIncomplete);
if (bIsInitialUpdate) {
outBitStream.Write(false);
outBitStream.Write(m_ActivatorPosition);
outBitStream.Write(m_RepositionPlayer);
if (bIsInitialUpdate) {
outBitStream.Write(false); // IsChoiceBuild
outBitStream.Write(m_ActivatorPosition);
outBitStream.Write(m_RepositionPlayer);
}
m_StateDirty = false;
}
m_StateDirty = false;
}
void QuickBuildComponent::Update(float deltaTime) {
m_Activator = GetActivator();
// Serialize the quickbuild every so often, fixes the odd bug where the quickbuild is not buildable
/*if (m_SoftTimer > 0.0f) {
m_SoftTimer -= deltaTime;
}
else {
m_SoftTimer = 5.0f;
Game::entityManager->SerializeEntity(m_Parent);
}*/
SetActivator(GetActivator());
switch (m_State) {
case eQuickBuildState::OPEN: {
@@ -130,12 +114,12 @@ void QuickBuildComponent::Update(float deltaTime) {
const bool isSmashGroup = spawner != nullptr ? spawner->GetIsSpawnSmashGroup() : false;
if (isSmashGroup) {
m_TimerIncomplete += deltaTime;
ModifyIncompleteTimer(deltaTime);
// For reset times < 0 this has to be handled manually
if (m_TimeBeforeSmash > 0) {
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f) {
m_ShowResetEffect = true;
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f && !m_ShowResetEffect) {
SetShowResetEffect(true);
Game::entityManager->SerializeEntity(m_Parent);
}
@@ -153,21 +137,19 @@ void QuickBuildComponent::Update(float deltaTime) {
break;
}
case eQuickBuildState::COMPLETED: {
m_Timer += deltaTime;
ModifyTimer(deltaTime);
// For reset times < 0 this has to be handled manually
if (m_ResetTime > 0) {
if (m_Timer >= m_ResetTime - 4.0f) {
if (!m_ShowResetEffect) {
m_ShowResetEffect = true;
if (m_Timer >= m_ResetTime - 4.0f && !m_ShowResetEffect) {
SetShowResetEffect(true);
Game::entityManager->SerializeEntity(m_Parent);
}
Game::entityManager->SerializeEntity(m_Parent);
}
if (m_Timer >= m_ResetTime) {
GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 0.0f, false, true);
GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 7.0f, false, true);
ResetQuickBuild(false);
}
@@ -185,9 +167,9 @@ void QuickBuildComponent::Update(float deltaTime) {
}
m_TimeBeforeDrain -= deltaTime;
m_Timer += deltaTime;
m_TimerIncomplete = 0;
m_ShowResetEffect = false;
ModifyTimer(deltaTime);
SetIncompleteTimer(0.0f);
SetShowResetEffect(false);
if (m_TimeBeforeDrain <= 0.0f) {
m_TimeBeforeDrain = m_CompleteTime / static_cast<float>(m_TakeImagination);
@@ -215,12 +197,12 @@ void QuickBuildComponent::Update(float deltaTime) {
break;
}
case eQuickBuildState::INCOMPLETE: {
m_TimerIncomplete += deltaTime;
ModifyIncompleteTimer(deltaTime);
// For reset times < 0 this has to be handled manually
if (m_TimeBeforeSmash > 0) {
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f) {
m_ShowResetEffect = true;
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f && !m_ShowResetEffect) {
SetShowResetEffect(true);
Game::entityManager->SerializeEntity(m_Parent);
}
@@ -260,7 +242,7 @@ void QuickBuildComponent::SpawnActivator() {
info.spawnerID = m_Parent->GetObjectID();
info.pos = m_ActivatorPosition == NiPoint3Constant::ZERO ? m_Parent->GetPosition() : m_ActivatorPosition;
m_Activator = Game::entityManager->CreateEntity(info, nullptr, m_Parent);
SetActivator(Game::entityManager->CreateEntity(info, nullptr, m_Parent));
if (m_Activator) {
m_ActivatorId = m_Activator->GetObjectID();
Game::entityManager->ConstructEntity(m_Activator);
@@ -277,7 +259,7 @@ void QuickBuildComponent::DespawnActivator() {
m_Activator->ScheduleKillAfterUpdate();
m_Activator = nullptr;
SetActivator(nullptr);
m_ActivatorId = LWOOBJID_EMPTY;
}
@@ -405,8 +387,7 @@ void QuickBuildComponent::StartQuickBuild(Entity* const user) {
GameMessages::SendQuickBuildNotifyState(m_Parent, m_State, eQuickBuildState::BUILDING, user->GetObjectID());
GameMessages::SendEnableQuickBuild(m_Parent, true, false, false, eQuickBuildFailReason::NOT_GIVEN, 0.0f, user->GetObjectID());
m_State = eQuickBuildState::BUILDING;
m_StateDirty = true;
SetState(eQuickBuildState::BUILDING);
Game::entityManager->SerializeEntity(m_Parent);
auto* movingPlatform = m_Parent->GetComponent<MovingPlatformComponent>();
@@ -444,9 +425,8 @@ void QuickBuildComponent::CompleteQuickBuild(Entity* const user) {
GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
m_State = eQuickBuildState::COMPLETED;
m_StateDirty = true;
m_Timer = 0.0f;
SetState(eQuickBuildState::COMPLETED);
SetTimer(0.0f);
m_DrainedImagination = 0;
Game::entityManager->SerializeEntity(m_Parent);
@@ -501,14 +481,12 @@ void QuickBuildComponent::CompleteQuickBuild(Entity* const user) {
}
// Set flag
auto* character = user->GetCharacter();
if (character != nullptr) {
const auto flagNumber = m_Parent->GetVar<int32_t>(u"quickbuild_single_build_player_flag");
if (flagNumber != 0) {
character->SetPlayerFlag(flagNumber, true);
}
if (m_Parent->HasVar(u"quickbuild_single_build_player_flag")) {
GameMessages::SetFlag setFlag{};
setFlag.target = user->GetObjectID();
setFlag.iFlagId = m_Parent->GetVar<int32_t>(u"quickbuild_single_build_player_flag");
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
RenderComponent::PlayAnimation(user, u"rebuild-celebrate", 1.09f);
}
@@ -526,11 +504,10 @@ void QuickBuildComponent::ResetQuickBuild(const bool failed) {
GameMessages::SendQuickBuildNotifyState(m_Parent, m_State, eQuickBuildState::RESETTING, LWOOBJID_EMPTY);
m_State = eQuickBuildState::RESETTING;
m_StateDirty = true;
m_Timer = 0.0f;
m_TimerIncomplete = 0.0f;
m_ShowResetEffect = false;
SetState(eQuickBuildState::RESETTING);
SetTimer(0.0f);
SetIncompleteTimer(0.0f);
SetShowResetEffect(false);
m_DrainedImagination = 0;
Game::entityManager->SerializeEntity(m_Parent);
@@ -563,8 +540,7 @@ void QuickBuildComponent::CancelQuickBuild(Entity* const entity, const eQuickBui
GameMessages::SendTerminateInteraction(m_Parent->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
// Now update the component itself
m_State = eQuickBuildState::INCOMPLETE;
m_StateDirty = true;
SetState(eQuickBuildState::INCOMPLETE);
// Notify scripts and possible subscribers
m_Parent->GetScript()->OnQuickBuildNotifyState(m_Parent, m_State);

View File

@@ -218,6 +218,48 @@ public:
* @param skipChecks whether or not to skip the check for the quickbuild not being completed
*/
void CancelQuickBuild(Entity* const builder, const eQuickBuildFailReason failReason, const bool skipChecks = false);
void SetState(const eQuickBuildState state) {
if (m_State == state) return;
m_State = state;
m_StateDirty = true;
}
void SetShowResetEffect(const bool value) {
if (m_ShowResetEffect == value) return;
m_ShowResetEffect = value;
m_StateDirty = true;
}
void SetActivator(Entity* const activator) {
if (m_Activator == activator) return;
m_Activator = activator;
m_StateDirty = true;
}
void SetTimer(const float value) {
if (m_Timer == value) return;
m_Timer = value;
m_StateDirty = true;
}
void ModifyTimer(const float value) {
if (value == 0.0f) return;
m_Timer += value;
m_StateDirty = true;
}
void SetIncompleteTimer(const float value) {
if (m_TimerIncomplete == value) return;
m_TimerIncomplete = value;
m_StateDirty = true;
}
void ModifyIncompleteTimer(const float value) {
if (value == 0.0f) return;
m_TimerIncomplete += value;
m_StateDirty = true;
}
private:
/**
* Whether or not the quickbuild state has been changed since we last serialized it.

View File

@@ -37,8 +37,20 @@
#include "ePlayerFlag.h"
#include "dConfig.h"
#include "GhostComponent.h"
#include "eGameMasterLevel.h"
#include "StringifiedEnum.h"
namespace {
using enum MessageType::Game;
using namespace GameMessages;
using MessageCreator = std::function<std::unique_ptr<GameMessages::GameMsg>()>;
std::map<MessageType::Game, MessageCreator> g_MessageHandlers = {
{ REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique<RequestServerObjectInfo>(); } },
{ SHOOTING_GALLERY_FIRE, []() { return std::make_unique<ShootingGalleryFire>(); } },
{ SET_FLAG, []() { return std::make_unique<SetFlag>(); } },
};
};
void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const SystemAddress& sysAddr, LWOOBJID objectID, MessageType::Game messageID) {
CBITSTREAM;
@@ -55,6 +67,24 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
if (messageID != MessageType::Game::READY_FOR_UPDATES) LOG_DEBUG("Received GM with ID and name: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
auto handler = g_MessageHandlers.find(messageID);
if (handler != g_MessageHandlers.end()) {
auto msg = handler->second();
// Verify that the system address user is able to use this message.
if (msg->requiredGmLevel > eGameMasterLevel::CIVILIAN) {
auto* usingEntity = Game::entityManager->GetEntity(usr->GetLoggedInChar());
if (!usingEntity || usingEntity->GetGMLevel() < msg->requiredGmLevel) {
LOG("User %s (%llu) does not have the required GM level to execute this command.", usingEntity->GetCharacter()->GetName().c_str(), usingEntity->GetObjectID());
return;
}
}
msg->Deserialize(inStream);
msg->Handle(*entity, sysAddr);
return;
}
switch (messageID) {
case MessageType::Game::UN_USE_BBB_MODEL: {
@@ -94,23 +124,18 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
break;
}
case MessageType::Game::SET_FLAG: {
GameMessages::HandleSetFlag(inStream, entity);
break;
}
case MessageType::Game::HAS_BEEN_COLLECTED: {
GameMessages::HandleHasBeenCollected(inStream, entity);
break;
}
// Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client.
// Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client.
case MessageType::Game::SELECT_SKILL: {
auto var = entity->GetVar<bool>(u"dlu_first_time_load");
if (var) {
entity->SetVar<bool>(u"dlu_first_time_load", false);
InventoryComponent* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent) inventoryComponent->FixInvisibleItems();
}
break;
@@ -186,13 +211,17 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
// After we've done our thing, tell the client they're ready
GameMessages::SendPlayerReady(Game::zoneManager->GetZoneControlObject(), sysAddr);
if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1"
|| !entity->GetCharacter()
|| !entity->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return;
entity->AddCallbackTimer(0.5f, [entity, sysAddr]() {
if (!entity) return;
GameMessages::SendEndCinematic(entity->GetObjectID(), u"", sysAddr);
});
GameMessages::GetFlag getFlag{};
getFlag.target = entity->GetObjectID();
getFlag.iFlagId = ePlayerFlag::DLU_SKIP_CINEMATICS;
SEND_ENTITY_MSG(getFlag);
if (Game::config->GetValue("allow_players_to_skip_cinematics") == "1" && getFlag.bFlag) {
entity->AddCallbackTimer(0.5f, [entity, sysAddr]() {
if (!entity) return;
GameMessages::SendEndCinematic(entity->GetObjectID(), u"", sysAddr);
});
}
break;
}
@@ -704,12 +733,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
case MessageType::Game::UPDATE_INVENTORY_GROUP_CONTENTS:
GameMessages::HandleUpdateInventoryGroupContents(inStream, entity, sysAddr);
break;
case MessageType::Game::SHOOTING_GALLERY_FIRE: {
GameMessages::ShootingGalleryFire fire{};
fire.Deserialize(inStream);
fire.Handle(*entity, sysAddr);
break;
}
default:
LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());

View File

@@ -843,8 +843,10 @@ void GameMessages::SendDieNoImplCode(Entity* entity, const LWOOBJID& killerID, c
bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::DIE);
bitStream.Write(bClientDeath);
bitStream.Write(bSpawnLoot);
bitStream.Write<uint32_t>(deathType.size());
bitStream.Write(deathType);
bitStream.Write(directionRelative_AngleXZ);
bitStream.Write(directionRelative_AngleY);
@@ -854,7 +856,10 @@ void GameMessages::SendDieNoImplCode(Entity* entity, const LWOOBJID& killerID, c
if (killType != eKillType::VIOLENT) bitStream.Write(killType);
bitStream.Write(killerID);
bitStream.Write(lootOwnerID);
bitStream.Write(lootOwnerID != LWOOBJID_EMPTY);
if (lootOwnerID != LWOOBJID_EMPTY) {
bitStream.Write(lootOwnerID);
}
SEND_PACKET_BROADCAST;
}
@@ -5071,23 +5076,6 @@ void GameMessages::HandleModularBuildConvertModel(RakNet::BitStream& inStream, E
item->SetCount(item->GetCount() - 1, false, false, true, eLootSourceType::QUICKBUILD);
}
void GameMessages::HandleSetFlag(RakNet::BitStream& inStream, Entity* entity) {
bool bFlag{};
int32_t iFlagID{};
inStream.Read(bFlag);
inStream.Read(iFlagID);
auto character = entity->GetCharacter();
if (character) character->SetPlayerFlag(iFlagID, bFlag);
// This is always set the first time a player loads into a world from character select
// and is used to know when to refresh the players inventory items so they show up.
if (iFlagID == ePlayerFlag::IS_NEWS_SCREEN_VISIBLE && bFlag) {
entity->SetVar<bool>(u"dlu_first_time_load", true);
}
}
void GameMessages::HandleRespondToMission(RakNet::BitStream& inStream, Entity* entity) {
int missionID{};
LWOOBJID playerID{};
@@ -5152,13 +5140,17 @@ void GameMessages::HandleMissionDialogOK(RakNet::BitStream& inStream, Entity* en
missionComponent->CompleteMission(missionID);
}
if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1"
|| !player->GetCharacter()
|| !player->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return;
player->AddCallbackTimer(0.5f, [player]() {
if (!player) return;
GameMessages::SendEndCinematic(player->GetObjectID(), u"", player->GetSystemAddress());
});
GameMessages::GetFlag getFlag{};
getFlag.target = entity->GetObjectID();
getFlag.iFlagId = ePlayerFlag::DLU_SKIP_CINEMATICS;
SEND_ENTITY_MSG(getFlag);
if (Game::config->GetValue("allow_players_to_skip_cinematics") == "1" && getFlag.bFlag) {
player->AddCallbackTimer(0.5f, [player]() {
if (!player) return;
GameMessages::SendEndCinematic(player->GetObjectID(), u"", player->GetSystemAddress());
});
}
}
void GameMessages::HandleRequestLinkedMission(RakNet::BitStream& inStream, Entity* entity) {
@@ -6426,4 +6418,26 @@ namespace GameMessages {
void ShootingGalleryFire::Handle(Entity& entity, const SystemAddress& sysAddr) {
entity.OnShootingGalleryFire(*this);
}
bool RequestServerObjectInfo::Deserialize(RakNet::BitStream& bitStream) {
if (!bitStream.Read(bVerbose)) return false;
if (!bitStream.Read(clientId)) return false;
if (!bitStream.Read(targetForReport)) return false;
return true;
}
void RequestServerObjectInfo::Handle(Entity& entity, const SystemAddress& sysAddr) {
auto* handlingEntity = Game::entityManager->GetEntity(targetForReport);
if (handlingEntity) handlingEntity->HandleMsg(*this);
}
bool SetFlag::Deserialize(RakNet::BitStream& bitStream) {
if (!bitStream.Read(bFlag)) return false;
if (!bitStream.Read(iFlagId)) return false;
return true;
}
void SetFlag::Handle(Entity& entity, const SystemAddress& sysAddr) {
entity.HandleMsg(*this);
}
}

View File

@@ -12,6 +12,7 @@
#include "eLootSourceType.h"
#include "Brick.h"
#include "MessageType/Game.h"
#include "eGameMasterLevel.h"
class AMFBaseValue;
class Entity;
@@ -22,6 +23,7 @@ class Leaderboard;
class PropertySelectQueryProperty;
class TradeItem;
class LDFBaseData;
class AMFArrayValue;
enum class eAnimationFlags : uint32_t;
@@ -50,7 +52,8 @@ enum class eCameraTargetCyclingMode : int32_t {
namespace GameMessages {
struct GameMsg {
GameMsg(MessageType::Game gmId) : msgId{ gmId } {}
GameMsg(MessageType::Game gmId, eGameMasterLevel lvl) : msgId{ gmId }, requiredGmLevel{ lvl } {}
GameMsg(MessageType::Game gmId) : GameMsg(gmId, eGameMasterLevel::CIVILIAN) {}
virtual ~GameMsg() = default;
void Send(const SystemAddress& sysAddr) const;
virtual void Serialize(RakNet::BitStream& bitStream) const {}
@@ -58,6 +61,7 @@ namespace GameMessages {
virtual void Handle(Entity& entity, const SystemAddress& sysAddr) {};
MessageType::Game msgId;
LWOOBJID target{ LWOOBJID_EMPTY };
eGameMasterLevel requiredGmLevel;
};
class PropertyDataMessage;
@@ -631,7 +635,6 @@ namespace GameMessages {
void HandleRequestUse(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandlePlayEmote(RakNet::BitStream& inStream, Entity* entity);
void HandleModularBuildConvertModel(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleSetFlag(RakNet::BitStream& inStream, Entity* entity);
void HandleRespondToMission(RakNet::BitStream& inStream, Entity* entity);
void HandleMissionDialogOK(RakNet::BitStream& inStream, Entity* entity);
void HandleRequestLinkedMission(RakNet::BitStream& inStream, Entity* entity);
@@ -769,6 +772,47 @@ namespace GameMessages {
struct PlayerResurrectionFinished : public GameMsg {
PlayerResurrectionFinished() : GameMsg(MessageType::Game::PLAYER_RESURRECTION_FINISHED) {}
};
struct RequestServerObjectInfo : public GameMsg {
bool bVerbose{};
LWOOBJID clientId{};
LWOOBJID targetForReport{};
RequestServerObjectInfo() : GameMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, eGameMasterLevel::DEVELOPER) {}
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
};
struct SetFlag : public GameMsg {
SetFlag() : GameMsg(MessageType::Game::SET_FLAG) {}
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
uint32_t iFlagId{};
bool bFlag{};
};
struct GetFlag : public GameMsg {
GetFlag() : GameMsg(MessageType::Game::GET_FLAG) {}
uint32_t iFlagId{};
bool bFlag{};
};
struct ClearSessionFlags : public GameMsg {
ClearSessionFlags() : GameMsg(MessageType::Game::CLEAR_SESSION_FLAGS) {}
};
struct SetRetroactiveFlags : public GameMsg {
SetRetroactiveFlags() : GameMsg(MessageType::Game::SET_RETROACTIVE_FLAGS) {}
};
struct GetObjectReportInfo : public GameMsg {
GetObjectReportInfo() : GameMsg(MessageType::Game::GET_OBJECT_REPORT_INFO) {}
AMFArrayValue* info{};
bool bVerbose{};
};
};
#endif // GAMEMESSAGES_H

View File

@@ -390,10 +390,15 @@ void Mission::Catchup() {
}
if (type == eMissionTaskType::PLAYER_FLAG) {
for (int32_t target : task->GetAllTargets()) {
const auto flag = GetCharacter()->GetPlayerFlag(target);
GameMessages::GetFlag getFlag{};
getFlag.target = entity->GetObjectID();
if (!flag) {
for (int32_t target : task->GetAllTargets()) {
getFlag.iFlagId = target;
getFlag.bFlag = false;
SEND_ENTITY_MSG(getFlag);
if (!getFlag.bFlag) {
continue;
}

View File

@@ -146,17 +146,22 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat
return missionComponent->GetMissionState(value) == eMissionState::AVAILABLE || missionComponent->GetMissionState(value) == eMissionState::COMPLETE_AVAILABLE;
case PreconditionType::OnMission:
if (missionComponent == nullptr) return false;
return missionComponent->GetMissionState(value) == eMissionState::ACTIVE ||
missionComponent->GetMissionState(value) == eMissionState::COMPLETE_ACTIVE ||
missionComponent->GetMissionState(value) == eMissionState::READY_TO_COMPLETE ||
missionComponent->GetMissionState(value) == eMissionState::COMPLETE_READY_TO_COMPLETE;
return missionComponent->GetMissionState(value) == eMissionState::ACTIVE ||
missionComponent->GetMissionState(value) == eMissionState::COMPLETE_ACTIVE ||
missionComponent->GetMissionState(value) == eMissionState::READY_TO_COMPLETE ||
missionComponent->GetMissionState(value) == eMissionState::COMPLETE_READY_TO_COMPLETE;
case PreconditionType::MissionComplete:
if (missionComponent == nullptr) return false;
return missionComponent->GetMissionState(value) >= eMissionState::COMPLETE;
case PreconditionType::PetDeployed:
return false; // TODO
case PreconditionType::HasFlag:
return character->GetPlayerFlag(value);
case PreconditionType::HasFlag: {
GameMessages::GetFlag getFlag{};
getFlag.target = player->GetObjectID();
getFlag.iFlagId = value;
SEND_ENTITY_MSG(getFlag);
return getFlag.bFlag;
}
case PreconditionType::WithinShape:
return true; // Client checks this one
case PreconditionType::InBuild:

View File

@@ -108,11 +108,18 @@ namespace DEVGMCommands {
void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER) return;
auto* character = entity->GetCharacter();
if (!character) return;
bool current = character->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS);
character->SetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS, !current);
if (!current) {
GameMessages::GetFlag current{};
current.target = entity->GetObjectID();
current.iFlagId = ePlayerFlag::DLU_SKIP_CINEMATICS;
SEND_ENTITY_MSG(current);
GameMessages::SetFlag setFlag{};
setFlag.target = entity->GetObjectID();
setFlag.iFlagId = ePlayerFlag::DLU_SKIP_CINEMATICS;
setFlag.bFlag = !current.bFlag;
SEND_ENTITY_MSG(setFlag);
if (!current.bFlag) {
GameMessages::SendSlashCommandFeedbackText(entity, u"You have elected to skip cinematics. Note that not all cinematics can be skipped, but most will be skipped now.");
} else {
GameMessages::SendSlashCommandFeedbackText(entity, u"Cinematics will no longer be skipped.");
@@ -424,6 +431,9 @@ namespace DEVGMCommands {
void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
GameMessages::SetFlag setFlag{};
setFlag.target = entity->GetObjectID();
if (splitArgs.size() == 1) {
const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(0));
@@ -431,8 +441,9 @@ namespace DEVGMCommands {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id.");
return;
}
entity->GetCharacter()->SetPlayerFlag(flagId.value(), true);
setFlag.iFlagId = flagId.value();
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
} else if (splitArgs.size() >= 2) {
const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(1));
std::string onOffFlag = splitArgs.at(0);
@@ -445,12 +456,17 @@ namespace DEVGMCommands {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type.");
return;
}
entity->GetCharacter()->SetPlayerFlag(flagId.value(), onOffFlag == "on");
setFlag.iFlagId = flagId.value();
setFlag.bFlag = onOffFlag == "on";
SEND_ENTITY_MSG(setFlag);
}
}
void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
GameMessages::SetFlag setFlag{};
setFlag.target = entity->GetObjectID();
setFlag.bFlag = false;
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
@@ -461,7 +477,8 @@ namespace DEVGMCommands {
return;
}
entity->GetCharacter()->SetPlayerFlag(flagId.value(), false);
setFlag.iFlagId = flagId.value();
SEND_ENTITY_MSG(setFlag);
}
void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
@@ -1511,6 +1528,13 @@ namespace DEVGMCommands {
if (!closest) return;
GameMessages::RequestServerObjectInfo request{};
request.target = closest->GetObjectID();
request.targetForReport = closest->GetObjectID();
request.bVerbose = true;
request.clientId = entity->GetObjectID();
SEND_ENTITY_MSG(request);
Game::entityManager->SerializeEntity(closest);
auto* table = CDClientManager::GetTable<CDObjectsTable>();

View File

@@ -62,6 +62,14 @@ std::map<uint32_t, std::string> activeSessions;
SystemAddress authServerMasterPeerSysAddr;
SystemAddress chatServerMasterPeerSysAddr;
int GenerateBCryptPassword(const std::string& password, const int workFactor, char salt[BCRYPT_HASHSIZE], char hash[BCRYPT_HASHSIZE]) {
int32_t bcryptState = ::bcrypt_gensalt(workFactor, salt);
assert(bcryptState == 0);
bcryptState = ::bcrypt_hashpw(password.c_str(), salt, hash);
assert(bcryptState == 0);
return 0;
}
int main(int argc, char** argv) {
constexpr uint32_t masterFramerate = mediumFramerate;
constexpr uint32_t masterFrameDelta = mediumFrameDelta;
@@ -94,7 +102,7 @@ int main(int argc, char** argv) {
std::string(folder) +
") folder from your download to the binary directory or re-run cmake.";
LOG("%s", msg.c_str());
// toss an error box up for windows users running the download
// toss an error box up for windows users running the download
#ifdef DARKFLAME_PLATFORM_WIN32
MessageBoxA(nullptr, msg.c_str(), "Missing Folder", MB_OK | MB_ICONERROR);
#endif
@@ -238,10 +246,7 @@ int main(int argc, char** argv) {
// Regenerate hash based on new password
char salt[BCRYPT_HASHSIZE];
char hash[BCRYPT_HASHSIZE];
int32_t bcryptState = ::bcrypt_gensalt(12, salt);
assert(bcryptState == 0);
bcryptState = ::bcrypt_hashpw(password.c_str(), salt, hash);
assert(bcryptState == 0);
assert(GenerateBCryptPassword(password, 12, salt, hash) == 0);
Database::Get()->UpdateAccountPassword(accountId->id, std::string(hash, BCRYPT_HASHSIZE));
@@ -279,10 +284,7 @@ int main(int argc, char** argv) {
//Generate new hash for bcrypt
char salt[BCRYPT_HASHSIZE];
char hash[BCRYPT_HASHSIZE];
int32_t bcryptState = ::bcrypt_gensalt(12, salt);
assert(bcryptState == 0);
bcryptState = ::bcrypt_hashpw(password.c_str(), salt, hash);
assert(bcryptState == 0);
assert(GenerateBCryptPassword(password, 12, salt, hash) == 0);
//Create account
try {
@@ -318,15 +320,24 @@ int main(int argc, char** argv) {
const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(ourIP, ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master, Game::config, &Game::lastSignal);
char salt[BCRYPT_HASHSIZE];
char hash[BCRYPT_HASHSIZE];
const auto& cfgPassword = Game::config->GetValue("master_password");
GenerateBCryptPassword(!cfgPassword.empty() ? cfgPassword : "3.25DARKFLAME1", 13, salt, hash);
Game::server = new dServer(ourIP, ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master, Game::config, &Game::lastSignal, hash);
std::string master_server_ip = "localhost";
const auto masterServerIPString = Game::config->GetValue("master_ip");
if (!masterServerIPString.empty()) master_server_ip = masterServerIPString;
if (master_server_ip == "") master_server_ip = Game::server->GetIP();
IServers::MasterInfo info;
info.ip = master_server_ip;
info.port = Game::server->GetPort();
info.password = hash;
Database::Get()->SetMasterIp(master_server_ip, Game::server->GetPort());
Database::Get()->SetMasterInfo(info);
//Create additional objects here:
PersistentIDManager::Initialize();

View File

@@ -3,4 +3,3 @@
#include "RakPeer.h"
#define NET_PASSWORD_EXTERNAL "3.25 ND1"
#define NET_PASSWORD_INTERNAL "3.25 DARKFLAME1"

View File

@@ -40,7 +40,21 @@ public:
}
} ReceiveDownloadCompleteCB;
dServer::dServer(const std::string& ip, int port, int instanceID, int maxConnections, bool isInternal, bool useEncryption, Logger* logger, const std::string masterIP, int masterPort, ServerType serverType, dConfig* config, Game::signal_t* lastSignal, unsigned int zoneID) {
dServer::dServer(
const std::string& ip,
int port,
int instanceID,
int maxConnections,
bool isInternal,
bool useEncryption,
Logger* logger,
const std::string masterIP,
int masterPort,
ServerType serverType,
dConfig* config,
Game::signal_t* lastSignal,
const std::string& masterPassword,
unsigned int zoneID) {
mIP = ip;
mPort = port;
mZoneID = zoneID;
@@ -56,6 +70,7 @@ dServer::dServer(const std::string& ip, int port, int instanceID, int maxConnect
mReplicaManager = nullptr;
mServerType = serverType;
mConfig = config;
mMasterPassword = masterPassword;
mShouldShutdown = lastSignal;
//Attempt to start our server here:
mIsOkay = Startup();
@@ -203,11 +218,11 @@ bool dServer::Startup() {
if (!mPeer->Startup(mMaxConnections, 10, &mSocketDescriptor, 1)) return false;
if (mIsInternal) {
mPeer->SetIncomingPassword("3.25 DARKFLAME1", 15);
mPeer->SetIncomingPassword(mMasterPassword.c_str(), mMasterPassword.size());
} else {
UpdateBandwidthLimit();
UpdateMaximumMtuSize();
mPeer->SetIncomingPassword("3.25 ND1", 8);
mPeer->SetIncomingPassword(NET_PASSWORD_EXTERNAL, strnlen(NET_PASSWORD_EXTERNAL, sizeof(NET_PASSWORD_EXTERNAL)));
}
mPeer->SetMaximumIncomingConnections(mMaxConnections);
@@ -257,7 +272,7 @@ void dServer::SetupForMasterConnection() {
bool dServer::ConnectToMaster() {
//LOG("Connection to Master %s:%d", mMasterIP.c_str(), mMasterPort);
return mMasterPeer->Connect(mMasterIP.c_str(), mMasterPort, "3.25 DARKFLAME1", 15);
return mMasterPeer->Connect(mMasterIP.c_str(), mMasterPort, mMasterPassword.c_str(), mMasterPassword.size());
}
void dServer::UpdateReplica() {

View File

@@ -46,6 +46,7 @@ public:
ServerType serverType,
dConfig* config,
Game::signal_t* shouldShutdown,
const std::string& masterPassword,
unsigned int zoneID = 0);
~dServer();
@@ -121,4 +122,5 @@ protected:
std::string mMasterIP;
int mMasterPort;
std::chrono::steady_clock::time_point mStartTime = std::chrono::steady_clock::now();
std::string mMasterPassword;
};

View File

@@ -1,9 +1,8 @@
#include "AgCagedBricksServer.h"
#include "InventoryComponent.h"
#include "GameMessages.h"
#include "Character.h"
#include "EntityManager.h"
#include "eReplicaComponentType.h"
#include "ePlayerFlag.h"
void AgCagedBricksServer::OnUse(Entity* self, Entity* user) {
@@ -14,14 +13,14 @@ void AgCagedBricksServer::OnUse(Entity* self, Entity* user) {
}
//Set the flag & mission status:
auto character = user->GetCharacter();
if (!character) return;
character->SetPlayerFlag(ePlayerFlag::CAGED_SPIDER, true);
GameMessages::SetFlag setFlag{};
setFlag.target = user->GetObjectID();
setFlag.iFlagId = ePlayerFlag::CAGED_SPIDER;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
//Remove the maelstrom cube:
auto inv = static_cast<InventoryComponent*>(user->GetComponent(eReplicaComponentType::INVENTORY));
auto* inv = user->GetComponent<InventoryComponent>();
if (inv) {
inv->RemoveItem(14553, 1);

View File

@@ -1,9 +1,8 @@
#include "RemoveRentalGear.h"
#include "InventoryComponent.h"
#include "Item.h"
#include "eMissionState.h"
#include "Character.h"
#include "eReplicaComponentType.h"
#include "ePlayerFlag.h"
/*
@@ -23,7 +22,7 @@ void RemoveRentalGear::OnMissionDialogueOK(Entity* self, Entity* target, int mis
if (missionID != defaultMission && missionID != 313) return;
if (missionState == eMissionState::COMPLETE || missionState == eMissionState::READY_TO_COMPLETE) {
auto inv = static_cast<InventoryComponent*>(target->GetComponent(eReplicaComponentType::INVENTORY));
auto* inv = target->GetComponent<InventoryComponent>();
if (!inv) return;
//remove the inventory items
@@ -36,7 +35,10 @@ void RemoveRentalGear::OnMissionDialogueOK(Entity* self, Entity* target, int mis
}
//reset the equipment flag
auto character = target->GetCharacter();
if (character) character->SetPlayerFlag(ePlayerFlag::EQUPPED_TRIAL_FACTION_GEAR, false);
GameMessages::SetFlag setFlag{};
setFlag.target = target->GetObjectID();
setFlag.iFlagId = ePlayerFlag::EQUPPED_TRIAL_FACTION_GEAR;
setFlag.bFlag = false;
SEND_ENTITY_MSG(setFlag);
}
}

View File

@@ -2,7 +2,6 @@
#include "SkillComponent.h"
#include "EntityManager.h"
#include "EntityInfo.h"
#include "Character.h"
void AmBlueX::OnUse(Entity* self, Entity* user) {
auto* skillComponent = user->GetComponent<SkillComponent>();
@@ -16,10 +15,11 @@ void AmBlueX::OnSkillEventFired(Entity* self, Entity* caster, const std::string&
self->SetNetworkVar<bool>(m_XUsedVariable, true);
self->SetNetworkVar<bool>(m_StartEffectVariable, true);
auto* character = caster->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(self->GetVar<int32_t>(m_FlagVariable), true);
}
GameMessages::SetFlag setFlag{};
setFlag.target = caster->GetObjectID();
setFlag.iFlagId = self->GetVar<int32_t>(m_FlagVariable);
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
EntityInfo info{};
info.lot = m_FXObject;

View File

@@ -1,15 +1,26 @@
#include "Binoculars.h"
#include "Character.h"
#include "GameMessages.h"
#include "Game.h"
#include "dServer.h"
#include "dZoneManager.h"
void Binoculars::OnUse(Entity* self, Entity* user) {
const auto number = self->GetVarAsString(u"number");
int32_t flag = std::stoi(std::to_string(Game::server->GetZoneID()).substr(0, 2) + number);
if (user->GetCharacter()->GetPlayerFlag(flag) == false) {
user->GetCharacter()->SetPlayerFlag(flag, true);
int32_t flag = std::stoi(std::to_string(Game::zoneManager->GetZoneID().GetMapID()).substr(0, 2) + number);
GameMessages::GetFlag getFlag{};
getFlag.target = user->GetObjectID();
getFlag.iFlagId = flag;
SEND_ENTITY_MSG(getFlag);
if (!getFlag.bFlag) {
GameMessages::SetFlag setFlag{};
setFlag.target = user->GetObjectID();
setFlag.iFlagId = flag;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
GameMessages::SendFireEventClientSide(self->GetObjectID(), user->GetSystemAddress(), u"achieve", LWOOBJID_EMPTY, 0, -1, LWOOBJID_EMPTY);
}
}

View File

@@ -1,6 +1,5 @@
#include "NjRailActivatorsServer.h"
#include "QuickBuildComponent.h"
#include "Character.h"
void NjRailActivatorsServer::OnUse(Entity* self, Entity* user) {
const auto flag = self->GetVar<int32_t>(u"RailFlagNum");
@@ -8,9 +7,10 @@ void NjRailActivatorsServer::OnUse(Entity* self, Entity* user) {
// Only allow use if this is not a quick build or the quick build is built
if (quickBuildComponent == nullptr || quickBuildComponent->GetState() == eQuickBuildState::COMPLETED) {
auto* character = user->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(flag, true);
}
GameMessages::SetFlag setFlag{};
setFlag.target = user->GetObjectID();
setFlag.iFlagId = flag;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
}

View File

@@ -1,10 +1,9 @@
#include "dZoneManager.h"
#include "PetDigServer.h"
#include "dZoneManager.h"
#include "MissionComponent.h"
#include "EntityManager.h"
#include "Character.h"
#include "PetComponent.h"
#include "User.h"
#include "eMissionState.h"
std::vector<LWOOBJID> PetDigServer::treasures{};
@@ -110,7 +109,6 @@ void PetDigServer::HandleXBuildDig(const Entity* self, Entity* owner, Entity* pe
if (!playerEntity || !playerEntity->GetCharacter())
return;
auto* player = playerEntity->GetCharacter();
const auto groupID = self->GetVar<std::u16string>(u"groupID");
int32_t playerFlag = 0;
@@ -123,15 +121,22 @@ void PetDigServer::HandleXBuildDig(const Entity* self, Entity* owner, Entity* pe
playerFlag = 63;
}
GameMessages::GetFlag getFlag{};
getFlag.target = playerEntity->GetObjectID();
getFlag.iFlagId = playerFlag;
SEND_ENTITY_MSG(getFlag);
// If the player doesn't have the flag yet
if (playerFlag != 0 && !player->GetPlayerFlag(playerFlag)) {
if (playerFlag != 0 && SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
auto* petComponent = pet->GetComponent<PetComponent>();
if (petComponent != nullptr) {
// TODO: Pet state = 9 ??
}
// Shows the flag object to the player
player->SetPlayerFlag(playerFlag, true);
GameMessages::SetFlag setFlag{};
setFlag.target = playerEntity->GetObjectID();
setFlag.iFlagId = playerFlag;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
auto* xObject = Game::entityManager->GetEntity(self->GetVar<LWOOBJID>(u"X"));
@@ -173,12 +178,17 @@ void PetDigServer::ProgressPetDigMissions(const Entity* owner, const Entity* che
if (excavatorMissionState == eMissionState::ACTIVE) {
if (chest->HasVar(u"PetDig")) {
int32_t playerFlag = 1260 + chest->GetVarAs<int32_t>(u"PetDig");
Character* player = owner->GetCharacter();
GameMessages::GetFlag getFlag{};
getFlag.target = owner->GetObjectID();
getFlag.iFlagId = playerFlag;
// check if player flag is set
if (!player->GetPlayerFlag(playerFlag)) {
if (SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
missionComponent->ForceProgress(505, 767, 1);
player->SetPlayerFlag(playerFlag, 1);
GameMessages::SetFlag setFlag{};
setFlag.target = owner->GetObjectID();
setFlag.iFlagId = playerFlag;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
}
}

View File

@@ -1,7 +1,7 @@
#include "StoryBoxInteractServer.h"
#include "Character.h"
#include "GameMessages.h"
#include "dServer.h"
#include "dZoneManager.h"
#include "Amf3.h"
#include "Entity.h"
@@ -36,11 +36,19 @@ void StoryBoxInteractServer::OnUse(Entity* self, Entity* user) {
if(!storyValue) return;
int32_t boxFlag = self->GetVar<int32_t>(u"altFlagID");
if (boxFlag <= 0) {
boxFlag = (10000 + Game::server->GetZoneID() + storyValue.value());
boxFlag = (10000 + Game::zoneManager->GetZoneID().GetMapID() + storyValue.value());
}
if (user->GetCharacter()->GetPlayerFlag(boxFlag) == false) {
user->GetCharacter()->SetPlayerFlag(boxFlag, true);
GameMessages::GetFlag getFlag{};
getFlag.target = user->GetObjectID();
getFlag.iFlagId = boxFlag;
if (SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
GameMessages::SetFlag setFlag{};
setFlag.target = user->GetObjectID();
setFlag.iFlagId = boxFlag;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
GameMessages::SendFireEventClientSide(self->GetObjectID(), user->GetSystemAddress(), u"achieve", LWOOBJID_EMPTY, 0, -1, LWOOBJID_EMPTY);
}
}

View File

@@ -1,15 +1,14 @@
#include "TokenConsoleServer.h"
#include "InventoryComponent.h"
#include "GameMessages.h"
#include "Character.h"
#include "eReplicaComponentType.h"
#include "eTerminateType.h"
#include "ePlayerFlag.h"
//2021-05-03 - max - added script, omitted some parts related to inheritance in lua which we don't need
void TokenConsoleServer::OnUse(Entity* self, Entity* user) {
auto* inv = static_cast<InventoryComponent*>(user->GetComponent(eReplicaComponentType::INVENTORY));
auto* inv = user->GetComponent<InventoryComponent>();
//make sure the user has the required amount of infected bricks
if (inv && inv->GetLotCount(6194) >= bricksToTake) {
@@ -22,17 +21,18 @@ void TokenConsoleServer::OnUse(Entity* self, Entity* user) {
}
//figure out which faction the player belongs to:
auto character = user->GetCharacter();
if (!character) return;
// At this point the player has to be in a faction.
GameMessages::GetFlag getFlag{};
getFlag.target = user->GetObjectID();
LOT tokenLOT = 0;
if (character->GetPlayerFlag(ePlayerFlag::VENTURE_FACTION)) //venture
if (getFlag.iFlagId = ePlayerFlag::VENTURE_FACTION, SEND_ENTITY_MSG(getFlag) && getFlag.bFlag) //venture
tokenLOT = 8321;
else if (character->GetPlayerFlag(ePlayerFlag::ASSEMBLY_FACTION)) //assembly
else if (getFlag.iFlagId = ePlayerFlag::ASSEMBLY_FACTION, SEND_ENTITY_MSG(getFlag) && getFlag.bFlag) //assembly
tokenLOT = 8318;
else if (character->GetPlayerFlag(ePlayerFlag::PARADOX_FACTION)) //paradox
else if (getFlag.iFlagId = ePlayerFlag::PARADOX_FACTION, SEND_ENTITY_MSG(getFlag) && getFlag.bFlag) //paradox
tokenLOT = 8320;
else if (character->GetPlayerFlag(ePlayerFlag::SENTINEL_FACTION)) //sentinel
else if (getFlag.iFlagId = ePlayerFlag::SENTINEL_FACTION, SEND_ENTITY_MSG(getFlag) && getFlag.bFlag) //sentinel
tokenLOT = 8319;
inv->AddItem(tokenLOT, tokensToGive, eLootSourceType::NONE);
}

View File

@@ -10,49 +10,38 @@ void NsLegoClubDoor::OnStartup(Entity* self) {
self->SetVar(u"teleportString", m_TeleportString);
self->SetVar(u"spawnPoint", m_SpawnPoint);
args = {};
teleportArgs.Reset();
args.Insert("callbackClient", std::to_string(self->GetObjectID()));
args.Insert("strIdentifier", "choiceDoor");
args.Insert("title", "%[UI_CHOICE_DESTINATION]");
teleportArgs.Insert("callbackClient", std::to_string(self->GetObjectID()));
teleportArgs.Insert("strIdentifier", "choiceDoor");
teleportArgs.Insert("title", "%[UI_CHOICE_DESTINATION]");
AMFArrayValue* choiceOptions = args.InsertArray("options");
auto& choiceOptions = *teleportArgs.InsertArray("options");
{
AMFArrayValue* nsArgs = choiceOptions->PushArray();
auto& nsArgs = *choiceOptions.PushArray();
nsArgs->Insert("image", "textures/ui/zone_thumnails/Nimbus_Station.dds");
nsArgs->Insert("caption", "%[UI_CHOICE_NS]");
nsArgs->Insert("identifier", "zoneID_1200");
nsArgs->Insert("tooltipText", "%[UI_CHOICE_NS_HOVER]");
nsArgs.Insert("image", "textures/ui/zone_thumnails/Nimbus_Station.dds");
nsArgs.Insert("caption", "%[UI_CHOICE_NS]");
nsArgs.Insert("identifier", "zoneID_1200");
nsArgs.Insert("tooltipText", "%[UI_CHOICE_NS_HOVER]");
}
{
AMFArrayValue* ntArgs = choiceOptions->PushArray();
auto& ntArgs = *choiceOptions.PushArray();
ntArgs->Insert("image", "textures/ui/zone_thumnails/Nexus_Tower.dds");
ntArgs->Insert("caption", "%[UI_CHOICE_NT]");
ntArgs->Insert("identifier", "zoneID_1900");
ntArgs->Insert("tooltipText", "%[UI_CHOICE_NT_HOVER]");
ntArgs.Insert("image", "textures/ui/zone_thumnails/Nexus_Tower.dds");
ntArgs.Insert("caption", "%[UI_CHOICE_NT]");
ntArgs.Insert("identifier", "zoneID_1900");
ntArgs.Insert("tooltipText", "%[UI_CHOICE_NT_HOVER]");
}
options = choiceOptions;
}
void NsLegoClubDoor::OnUse(Entity* self, Entity* user) {
auto* player = user;
if (CheckChoice(self, player)) {
AMFArrayValue multiArgs;
multiArgs.Insert("callbackClient", std::to_string(self->GetObjectID()));
multiArgs.Insert("strIdentifier", "choiceDoor");
multiArgs.Insert("title", "%[UI_CHOICE_DESTINATION]");
multiArgs.Insert("options", static_cast<AMFBaseValue*>(options));
GameMessages::SendUIMessageServerToSingleClient(player, player->GetSystemAddress(), "QueueChoiceBox", multiArgs);
multiArgs.Remove("options", false); // We do not want the local amf to delete the options!
GameMessages::SendUIMessageServerToSingleClient(player, player->GetSystemAddress(), "QueueChoiceBox", teleportArgs);
} else if (self->GetVar<int32_t>(u"currentZone") != m_ChoiceZoneID) {
AMFArrayValue multiArgs;
multiArgs.Insert("state", "Lobby");

View File

@@ -19,6 +19,5 @@ private:
std::string m_SpawnPoint = "NS_LEGO_Club";
std::u16string m_TeleportAnim = u"lup-teleport";
std::u16string m_TeleportString = u"ROCKET_TOOLTIP_USE_THE_GATEWAY_TO_TRAVEL_TO_LUP_WORLD";
AMFArrayValue args = {};
AMFArrayValue* options = {};
AMFArrayValue teleportArgs{};
};

View File

@@ -1,7 +1,6 @@
#include "NsTokenConsoleServer.h"
#include "InventoryComponent.h"
#include "GameMessages.h"
#include "Character.h"
#include "MissionComponent.h"
#include "QuickBuildComponent.h"
#include "eTerminateType.h"
@@ -24,9 +23,8 @@ void NsTokenConsoleServer::OnUse(Entity* self, Entity* user) {
auto* inventoryComponent = user->GetComponent<InventoryComponent>();
auto* missionComponent = user->GetComponent<MissionComponent>();
auto* character = user->GetCharacter();
if (inventoryComponent == nullptr || missionComponent == nullptr || character == nullptr) {
if (inventoryComponent == nullptr || missionComponent == nullptr) {
return;
}
@@ -42,15 +40,18 @@ void NsTokenConsoleServer::OnUse(Entity* self, Entity* user) {
GameMessages::SendPlayNDAudioEmitter(self, user->GetSystemAddress(), useSound);
}
GameMessages::GetFlag getFlag{};
getFlag.target = user->GetObjectID();
// Player must be in faction to interact with this entity.
LOT tokenLOT = 0;
if (character->GetPlayerFlag(ePlayerFlag::VENTURE_FACTION)) //venture
if (getFlag.iFlagId = ePlayerFlag::VENTURE_FACTION, SEND_ENTITY_MSG(getFlag) && getFlag.bFlag) //venture
tokenLOT = 8321;
else if (character->GetPlayerFlag(ePlayerFlag::ASSEMBLY_FACTION)) //assembly
else if (getFlag.iFlagId = ePlayerFlag::ASSEMBLY_FACTION, SEND_ENTITY_MSG(getFlag) && getFlag.bFlag) //assembly
tokenLOT = 8318;
else if (character->GetPlayerFlag(ePlayerFlag::PARADOX_FACTION)) //paradox
else if (getFlag.iFlagId = ePlayerFlag::PARADOX_FACTION, SEND_ENTITY_MSG(getFlag) && getFlag.bFlag) //paradox
tokenLOT = 8320;
else if (character->GetPlayerFlag(ePlayerFlag::SENTINEL_FACTION)) //sentinel
else if (getFlag.iFlagId = ePlayerFlag::SENTINEL_FACTION, SEND_ENTITY_MSG(getFlag) && getFlag.bFlag) //sentinel
tokenLOT = 8319;
inventoryComponent->AddItem(tokenLOT, 5, eLootSourceType::NONE);

View File

@@ -1,15 +1,15 @@
#include "NTPipeVisibilityServer.h"
#include "Entity.h"
#include "Character.h"
void NTPipeVisibilityServer::OnQuickBuildComplete(Entity* self, Entity* target) {
const auto flag = self->GetVar<int32_t>(u"flag");
if (flag == 0) return;
auto* character = target->GetCharacter();
if (!character) return;
character->SetPlayerFlag(flag, true);
GameMessages::SetFlag setFlag{};
setFlag.target = target->GetObjectID();
setFlag.iFlagId = flag;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"PipeBuilt");
}

View File

@@ -1,12 +1,14 @@
#include "NtImagimeterVisibility.h"
#include "GameMessages.h"
#include "Entity.h"
#include "Character.h"
#include "ePlayerFlag.h"
void NTImagimeterVisibility::OnQuickBuildComplete(Entity* self, Entity* target) {
auto* character = target->GetCharacter();
if (character) character->SetPlayerFlag(ePlayerFlag::NT_PLINTH_REBUILD, true);
GameMessages::SetFlag setFlag{};
setFlag.target = target->GetObjectID();
setFlag.iFlagId = ePlayerFlag::NT_PLINTH_REBUILD;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"PlinthBuilt", 0, 0, LWOOBJID_EMPTY, "", target->GetSystemAddress());
}

View File

@@ -1,8 +1,8 @@
#include "NtParadoxPanelServer.h"
#include "GameMessages.h"
#include "MissionComponent.h"
#include "EntityManager.h"
#include "Character.h"
#include "eMissionState.h"
#include "RenderComponent.h"
#include "eTerminateType.h"
@@ -32,8 +32,11 @@ void NtParadoxPanelServer::OnUse(Entity* self, Entity* user) {
}
const auto flag = self->GetVar<int32_t>(u"flag");
player->GetCharacter()->SetPlayerFlag(flag, true);
GameMessages::SetFlag setFlag{};
setFlag.target = playerID;
setFlag.iFlagId = flag;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
RenderComponent::PlayAnimation(player, u"rebuild-celebrate");

View File

@@ -292,34 +292,45 @@ void ZoneAgProperty::BaseTimerDone(Entity* self, const std::string& timerName) {
void ZoneAgProperty::OnZonePropertyRented(Entity* self, Entity* player) {
BaseZonePropertyRented(self, player);
auto* character = player->GetCharacter();
if (character == nullptr)
return;
character->SetPlayerFlag(108, true);
GameMessages::SetFlag setFlag{};
setFlag.target = player->GetObjectID();
setFlag.iFlagId = 108;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
void ZoneAgProperty::OnZonePropertyModelPlaced(Entity* self, Entity* player) {
auto* character = player->GetCharacter();
auto* missionComponent = player->GetComponent<MissionComponent>();
if (!missionComponent) return;
if (!character->GetPlayerFlag(101)) {
GameMessages::GetFlag getFlag{};
getFlag.target = player->GetObjectID();
GameMessages::SetFlag setFlag{};
setFlag.target = player->GetObjectID();
setFlag.bFlag = true;
if (getFlag.iFlagId = 101, SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
BaseZonePropertyModelPlaced(self, player);
character->SetPlayerFlag(101, true);
setFlag.iFlagId = 101;
SEND_ENTITY_MSG(setFlag);
if (missionComponent->GetMissionState(871) == eMissionState::ACTIVE) {
self->SetNetworkVar<std::u16string>(u"Tooltip", u"AnotherModel");
}
} else if (!character->GetPlayerFlag(102)) {
character->SetPlayerFlag(102, true);
} else if (getFlag.iFlagId = 102, SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
setFlag.iFlagId = 102;
SEND_ENTITY_MSG(setFlag);
if (missionComponent->GetMissionState(871) == eMissionState::ACTIVE) {
self->SetNetworkVar<std::u16string>(u"Tooltip", u"TwoMoreModels");
}
} else if (!character->GetPlayerFlag(103)) {
character->SetPlayerFlag(103, true);
} else if (!character->GetPlayerFlag(104)) {
character->SetPlayerFlag(104, true);
} else if (getFlag.iFlagId = 103, SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
setFlag.iFlagId = 103;
SEND_ENTITY_MSG(setFlag);
} else if (getFlag.iFlagId = 104, SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
setFlag.iFlagId = 104;
SEND_ENTITY_MSG(setFlag);
self->SetNetworkVar<std::u16string>(u"Tooltip", u"TwoMoreModelsOff");
} else if (self->GetVar<std::string>(u"tutorial") == "place_model") {
self->SetVar<std::string>(u"tutorial", "");
@@ -328,20 +339,34 @@ void ZoneAgProperty::OnZonePropertyModelPlaced(Entity* self, Entity* player) {
}
void ZoneAgProperty::OnZonePropertyModelPickedUp(Entity* self, Entity* player) {
auto* character = player->GetCharacter();
auto* missionComponent = player->GetComponent<MissionComponent>();
if (!missionComponent) return;
if (!character->GetPlayerFlag(109)) {
character->SetPlayerFlag(109, true);
if (missionComponent->GetMissionState(891) == eMissionState::ACTIVE && !character->GetPlayerFlag(110)) {
GameMessages::GetFlag getFlag{};
getFlag.target = player->GetObjectID();
getFlag.iFlagId = 109;
if (SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
GameMessages::SetFlag setFlag{};
setFlag.target = player->GetObjectID();
setFlag.iFlagId = 109;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
getFlag.iFlagId = 110;
getFlag.bFlag = false;
if (missionComponent->GetMissionState(891) == eMissionState::ACTIVE && SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
self->SetNetworkVar<std::u16string>(u"Tooltip", u"Rotate");
}
}
}
void ZoneAgProperty::OnZonePropertyModelRemoved(Entity* self, Entity* player) {
auto* character = player->GetCharacter();
character->SetPlayerFlag(111, true);
GameMessages::SetFlag setFlag{};
setFlag.target = player->GetObjectID();
setFlag.iFlagId = 111;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
void ZoneAgProperty::OnZonePropertyModelRemovedWhileEquipped(Entity* self, Entity* player) {
@@ -349,11 +374,18 @@ void ZoneAgProperty::OnZonePropertyModelRemovedWhileEquipped(Entity* self, Entit
}
void ZoneAgProperty::OnZonePropertyModelRotated(Entity* self, Entity* player) {
auto* character = player->GetCharacter();
auto* missionComponent = player->GetComponent<MissionComponent>();
if (!missionComponent) return;
GameMessages::GetFlag getFlag{};
getFlag.target = player->GetObjectID();
getFlag.iFlagId = 110;
if (!character->GetPlayerFlag(110)) {
character->SetPlayerFlag(110, true);
if (SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
GameMessages::SetFlag setFlag{};
setFlag.target = player->GetObjectID();
setFlag.iFlagId = 110;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
if (missionComponent->GetMissionState(891) == eMissionState::ACTIVE) {
self->SetNetworkVar<std::u16string>(u"Tooltip", u"PlaceModel");
@@ -413,7 +445,12 @@ void ZoneAgProperty::BaseOnFireEventServerSide(Entity* self, Entity* sender, std
if (player == nullptr)
return;
player->GetCharacter()->SetPlayerFlag(self->GetVar<int32_t>(defeatedProperyFlag), true);
GameMessages::SetFlag setFlag{};
setFlag.target = player->GetObjectID();
setFlag.iFlagId = self->GetVar<int32_t>(defeatedProperyFlag);
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"PlayCinematic", 0, 0,
LWOOBJID_EMPTY, destroyedCinematic, UNASSIGNED_SYSTEM_ADDRESS);

View File

@@ -1,21 +1,21 @@
#include "VeEpsilonServer.h"
#include "Character.h"
#include "EntityManager.h"
#include "GameMessages.h"
#include "eMissionState.h"
#include "Entity.h"
void VeEpsilonServer::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, eMissionState missionState) {
auto* character = target->GetCharacter();
if (character == nullptr)
return;
// Resets the player flags that track which consoles they've used
if ((missionID == m_ConsoleMissionID || missionID == m_ConsoleRepeatMissionID)
&& (missionState == eMissionState::AVAILABLE || missionState == eMissionState::COMPLETE_AVAILABLE)) {
GameMessages::SetFlag setFlag{};
setFlag.target = target->GetObjectID();
for (auto i = 0; i < 10; i++) {
character->SetPlayerFlag(m_ConsoleBaseFlag + i, false);
setFlag.iFlagId = m_ConsoleBaseFlag + i;
setFlag.bFlag = false;
SEND_ENTITY_MSG(setFlag);
}
}

View File

@@ -1,6 +1,6 @@
#include "VeMissionConsole.h"
#include "InventoryComponent.h"
#include "Character.h"
#include "GameMessages.h"
#include "Loot.h"
#include "eTerminateType.h"
@@ -17,10 +17,11 @@ void VeMissionConsole::OnUse(Entity* self, Entity* user) {
const auto flagNumber = self->GetVar<std::u16string>(m_NumberVariable);
const int32_t flag = std::stoi("101" + GeneralUtils::UTF16ToWTF8(flagNumber));
auto* character = user->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(flag, true);
}
GameMessages::SetFlag setFlag{};
setFlag.target = user->GetObjectID();
setFlag.iFlagId = flag;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"");
GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, self->GetObjectID());

View File

@@ -1,8 +1,8 @@
#include "CavePrisonCage.h"
#include "EntityManager.h"
#include "QuickBuildComponent.h"
#include "GameMessages.h"
#include "Character.h"
#include "dZoneManager.h"
#include "RenderComponent.h"
@@ -161,10 +161,14 @@ void CavePrisonCage::OnTimerDone(Entity* self, std::string timerName) {
return;
}
// Set the flag on the builder character
const auto flagNum = 2020 + self->GetVarAs<int32_t>(u"myNumber");
// Set the flag on the builder character
builder->GetCharacter()->SetPlayerFlag(flagNum, true);
GameMessages::SetFlag setFlag{};
setFlag.target = builder->GetObjectID();
setFlag.iFlagId = flagNum;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
// Setup a timer named 'VillagerEscape' to be triggered in 5 seconds
self->AddTimer("VillagerEscape", 5.0f);

View File

@@ -1,16 +1,15 @@
#include "NjDragonEmblemChestServer.h"
#include "Character.h"
#include "EntityInfo.h"
#include "Loot.h"
#include "Entity.h"
#include "DestroyableComponent.h"
#include "ePlayerFlag.h"
void NjDragonEmblemChestServer::OnUse(Entity* self, Entity* user) {
auto* character = user->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(ePlayerFlag::NJ_WU_SHOW_DAILY_CHEST, false);
}
GameMessages::SetFlag setFlag{};
setFlag.target = user->GetObjectID();
setFlag.iFlagId = ePlayerFlag::NJ_WU_SHOW_DAILY_CHEST;
setFlag.bFlag = false;
SEND_ENTITY_MSG(setFlag);
auto* destroyable = self->GetComponent<DestroyableComponent>();
if (destroyable != nullptr) {

View File

@@ -1,17 +1,19 @@
#include "NjGarmadonCelebration.h"
#include "Character.h"
#include "GameMessages.h"
#include "ePlayerFlag.h"
void NjGarmadonCelebration::OnCollisionPhantom(Entity* self, Entity* target) {
auto* character = target->GetCharacter();
GameMessages::GetFlag getFlag{};
getFlag.target = target->GetObjectID();
getFlag.iFlagId = ePlayerFlag::NJ_GARMADON_CINEMATIC_SEEN;
if (character == nullptr) {
return;
}
if (!character->GetPlayerFlag(ePlayerFlag::NJ_GARMADON_CINEMATIC_SEEN)) {
character->SetPlayerFlag(ePlayerFlag::NJ_GARMADON_CINEMATIC_SEEN, true);
if (SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
GameMessages::SetFlag setFlag{};
setFlag.target = target->GetObjectID();
setFlag.iFlagId = ePlayerFlag::NJ_GARMADON_CINEMATIC_SEEN;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
GameMessages::SendStartCelebrationEffect(target, target->GetSystemAddress(), GarmadonCelebrationID);
}

View File

@@ -1,6 +1,5 @@
#include "NjNPCMissionSpinjitzuServer.h"
#include "Character.h"
#include "EntityManager.h"
#include "eMissionState.h"
void NjNPCMissionSpinjitzuServer::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, eMissionState missionState) {
@@ -12,13 +11,11 @@ void NjNPCMissionSpinjitzuServer::OnMissionDialogueOK(Entity* self, Entity* targ
// Wait for an animation to complete and flag that the player has learned spinjitzu
self->AddCallbackTimer(5.0f, [targetID, element]() {
auto* target = Game::entityManager->GetEntity(targetID);
if (target != nullptr) {
auto* character = target->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(ElementFlags.at(element), true);
}
}
GameMessages::SetFlag setFlag{};
setFlag.target = targetID;
setFlag.iFlagId = ElementFlags.at(element);
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
});
}
}

View File

@@ -1,6 +1,5 @@
#include "NjWuNPC.h"
#include "MissionComponent.h"
#include "Character.h"
#include "EntityManager.h"
#include "GameMessages.h"
#include "eMissionState.h"
@@ -10,10 +9,8 @@ void NjWuNPC::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, e
// The Dragon statue daily mission
if (missionID == m_MainDragonMissionID) {
auto* character = target->GetCharacter();
auto* missionComponent = target->GetComponent<MissionComponent>();
if (character == nullptr || missionComponent == nullptr)
return;
if (!missionComponent) return;
switch (missionState) {
case eMissionState::AVAILABLE:
@@ -24,8 +21,11 @@ void NjWuNPC::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, e
missionComponent->RemoveMission(subMissionID);
missionComponent->AcceptMission(subMissionID);
}
character->SetPlayerFlag(ePlayerFlag::NJ_WU_SHOW_DAILY_CHEST, false);
GameMessages::SetFlag setFlag{};
setFlag.target = target->GetObjectID();
setFlag.iFlagId = ePlayerFlag::NJ_WU_SHOW_DAILY_CHEST;
setFlag.bFlag = false;
SEND_ENTITY_MSG(setFlag);
// Hide the chest
for (auto* chest : Game::entityManager->GetEntitiesInGroup(m_DragonChestGroup)) {
@@ -38,7 +38,11 @@ void NjWuNPC::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, e
case eMissionState::READY_TO_COMPLETE:
case eMissionState::COMPLETE_READY_TO_COMPLETE:
{
character->SetPlayerFlag(ePlayerFlag::NJ_WU_SHOW_DAILY_CHEST, true);
GameMessages::SetFlag setFlag{};
setFlag.target = target->GetObjectID();
setFlag.iFlagId = ePlayerFlag::NJ_WU_SHOW_DAILY_CHEST;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
// Show the chest
for (auto* chest : Game::entityManager->GetEntitiesInGroup(m_DragonChestGroup)) {

View File

@@ -127,7 +127,10 @@ void BasePropertyServer::BasePlayerLoaded(Entity* self, Entity* player) {
if (player->GetObjectID() != propertyOwner)
return;
} else {
const auto defeatedFlag = player->GetCharacter()->GetPlayerFlag(self->GetVar<int32_t>(defeatedProperyFlag));
GameMessages::GetFlag getFlag{};
getFlag.target = player->GetObjectID();
getFlag.iFlagId = self->GetVar<int32_t>(defeatedProperyFlag);
const auto defeatedFlag = SEND_ENTITY_MSG(getFlag) && getFlag.bFlag;
self->SetNetworkVar(UnclaimedVariable, true);
self->SetVar<LWOOBJID>(PlayerIDVariable, player->GetObjectID());
@@ -184,8 +187,13 @@ void BasePropertyServer::BaseZonePropertyModelPlaced(Entity* self, Entity* playe
return;
auto flag = self->GetVar<int32_t>(placedModelFlag);
if (flag)
character->SetPlayerFlag(flag, true);
if (flag) {
GameMessages::SetFlag setFlag{};
setFlag.target = player->GetObjectID();
setFlag.iFlagId = flag;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
}
void BasePropertyServer::KillClouds(Entity* self) {
@@ -462,10 +470,11 @@ void BasePropertyServer::HandleOrbsTimer(Entity* self) {
// Notifies the client that the property has been claimed with a flag, completes missions too
auto* player = Game::entityManager->GetEntity(self->GetVar<LWOOBJID>(PlayerIDVariable));
if (player != nullptr) {
auto* character = player->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(self->GetVar<int32_t>(defeatedProperyFlag), true);
}
GameMessages::SetFlag setFlag{};
setFlag.target = player->GetObjectID();
setFlag.iFlagId = self->GetVar<int32_t>(defeatedProperyFlag);
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
self->AddTimer(TornadoOffTimer, 0.5f);

View File

@@ -1,9 +1,9 @@
#include "Darkitect.h"
#include "MissionComponent.h"
#include "DestroyableComponent.h"
#include "EntityManager.h"
#include "GameMessages.h"
#include "Character.h"
#include "eMissionState.h"
void Darkitect::Reveal(Entity* self, Entity* player) {
@@ -18,15 +18,18 @@ void Darkitect::Reveal(Entity* self, Entity* player) {
auto* destroyableComponent = player->GetComponent<DestroyableComponent>();
auto* missionComponent = player->GetComponent<MissionComponent>();
auto* character = player->GetCharacter();
if (destroyableComponent != nullptr && missionComponent != nullptr && character != nullptr) {
if (destroyableComponent != nullptr && missionComponent != nullptr) {
destroyableComponent->SetArmor(0);
destroyableComponent->SetHealth(1);
destroyableComponent->SetImagination(0);
if (missionComponent->GetMissionState(1295) == eMissionState::ACTIVE) {
character->SetPlayerFlag(1911, true);
GameMessages::SetFlag setFlag{};
setFlag.target = player->GetObjectID();
setFlag.iFlagId = 1911;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
Game::entityManager->SerializeEntity(player);

View File

@@ -1,16 +1,21 @@
#include "TrialFactionArmorServer.h"
#include "Character.h"
#include "ePlayerFlag.h"
#include "GameMessages.h"
#include "EntityManager.h"
#include "DestroyableComponent.h"
void TrialFactionArmorServer::OnFactionTriggerItemEquipped(Entity* itemOwner, LWOOBJID itemObjId) {
auto* character = itemOwner->GetCharacter();
if (!character) return;
GameMessages::GetFlag flag{};
flag.target = itemOwner->GetObjectID();
flag.iFlagId = ePlayerFlag::EQUPPED_TRIAL_FACTION_GEAR;
auto flag = character->GetPlayerFlag(ePlayerFlag::EQUPPED_TRIAL_FACTION_GEAR);
if (!flag) {
character->SetPlayerFlag(ePlayerFlag::EQUPPED_TRIAL_FACTION_GEAR, true);
if (SEND_ENTITY_MSG(flag) && !flag.bFlag) {
GameMessages::SetFlag setFlag{};
setFlag.target = itemOwner->GetObjectID();
setFlag.iFlagId = ePlayerFlag::EQUPPED_TRIAL_FACTION_GEAR;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
// technically a TimerWithCancel but our current implementation doesnt support this.
itemOwner->AddCallbackTimer(1.0f, [itemOwner]() {

View File

@@ -1,11 +1,10 @@
#include "NtFactionSpyServer.h"
#include "Character.h"
#include "ProximityMonitorComponent.h"
#include "InventoryComponent.h"
#include "GameMessages.h"
#include "MissionComponent.h"
#include "eMissionState.h"
#include "eReplicaComponentType.h"
#include "eCinematicEvent.h"
#include "ePlayerFlag.h"
@@ -54,12 +53,14 @@ bool NtFactionSpyServer::IsSpy(Entity* self, Entity* possibleSpy) {
auto* missionComponent = possibleSpy->GetComponent<MissionComponent>();
auto* inventoryComponent = possibleSpy->GetComponent<InventoryComponent>();
auto* character = possibleSpy->GetCharacter();
GameMessages::GetFlag getFlag{};
getFlag.target = possibleSpy->GetObjectID();
getFlag.iFlagId = spyData.flagID;
// A player is a spy if they have the spy mission, have the spy equipment equipped and don't have the spy flag set yet
return missionComponent != nullptr && missionComponent->GetMissionState(spyData.missionID) == eMissionState::ACTIVE
&& inventoryComponent != nullptr && inventoryComponent->IsEquipped(spyData.itemID)
&& character != nullptr && !character->GetPlayerFlag(spyData.flagID);
&& SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag;
}
void NtFactionSpyServer::OnCinematicUpdate(Entity* self, Entity* sender, eCinematicEvent event,
@@ -87,10 +88,11 @@ void NtFactionSpyServer::OnCinematicUpdate(Entity* self, Entity* sender, eCinema
} else if (event == eCinematicEvent::ENDED && pathIndex >= dialogueTable.size() - 1) {
auto spyData = self->GetVar<SpyData>(m_SpyDataVariable);
auto* character = sender->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(spyData.flagID, true);
}
GameMessages::SetFlag setFlag{};
setFlag.target = sender->GetObjectID();
setFlag.iFlagId = spyData.flagID;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
}
}

View File

@@ -1,6 +1,6 @@
#include "BaseFootRaceManager.h"
#include "EntityManager.h"
#include "Character.h"
#include "Entity.h"
void BaseFootRaceManager::OnStartup(Entity* self) {
@@ -21,31 +21,31 @@ void BaseFootRaceManager::OnFireEventServerSide(Entity* self, Entity* sender, st
if (eventName == "updatePlayer") {
UpdatePlayer(self, player->GetObjectID());
} else if (IsPlayerInActivity(self, player->GetObjectID())) {
GameMessages::SetFlag setFlag{};
setFlag.target = player->GetObjectID();
setFlag.iFlagId = 115;
if (eventName == "initialActivityScore") {
auto* character = player->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(115, true);
}
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
SetActivityScore(self, player->GetObjectID(), 1);
} else if (eventName == "updatePlayerTrue") {
auto* character = player->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(115, false);
}
setFlag.bFlag = false;
SEND_ENTITY_MSG(setFlag);
UpdatePlayer(self, player->GetObjectID(), true);
} else if (eventName == "PlayerWon") {
auto* character = player->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(115, false);
if (param2 != -1) // Certain footraces set a flag
character->SetPlayerFlag(param2, true);
setFlag.bFlag = false;
SEND_ENTITY_MSG(setFlag);
if (param2 != -1) {
setFlag.iFlagId = param2;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
StopActivity(self, player->GetObjectID(), 0, param1);
SaveScore(self, player->GetObjectID(), static_cast<float>(param1), static_cast<float>(param2), static_cast<float>(param3));
}
StopActivity(self, player->GetObjectID(), 0, param1);
SaveScore(self, player->GetObjectID(), static_cast<float>(param1), static_cast<float>(param2), static_cast<float>(param3));
}
}
}

View File

@@ -36,14 +36,14 @@ void AgSpiderBossMessage::OnCollisionPhantom(Entity* self, Entity* target) {
auto box = GetBox(self);
// knockback the target
auto forward = target->GetRotation().GetForwardVector();
auto forward = self->GetRotation().GetForwardVector();
box.boxTarget = target->GetObjectID();
GameMessages::SendPlayFXEffect(target->GetObjectID(), 1378, u"create", "pushBack");
RenderComponent::PlayAnimation(target, "knockback-recovery");
forward.y += 15;
forward.x *= 100;
forward.z *= 100;
GameMessages::SendKnockback(target->GetObjectID(), self->GetObjectID(), self->GetObjectID(), 0, forward);
GameMessages::SendKnockback(target->GetObjectID(), LWOOBJID_EMPTY, LWOOBJID_EMPTY, 0, forward);
if (box.isTouch || box.isDisplayed) return;
box.boxSelf = self->GetObjectID();

View File

@@ -1,7 +1,9 @@
#include "FvFreeGfNinjas.h"
#include "Character.h"
#include "MissionComponent.h"
#include "eMissionState.h"
#include "Game.h"
#include "EntityManager.h"
void FvFreeGfNinjas::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, eMissionState missionState) {
if (missionID == 705 && missionState == eMissionState::AVAILABLE) {
@@ -14,13 +16,17 @@ void FvFreeGfNinjas::OnMissionDialogueOK(Entity* self, Entity* target, int missi
missionComponent->AcceptMission(703);
missionComponent->AcceptMission(704);
auto* character = target->GetCharacter();
if (character != nullptr)
character->SetPlayerFlag(68, true);
GameMessages::SetFlag setFlag{};
setFlag.target = target->GetObjectID();
setFlag.iFlagId = 68;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
} else if (missionID == 786) {
auto* character = target->GetCharacter();
if (character != nullptr)
character->SetPlayerFlag(81, true);
GameMessages::SetFlag setFlag{};
setFlag.target = target->GetObjectID();
setFlag.iFlagId = 81;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
}
@@ -31,9 +37,11 @@ void FvFreeGfNinjas::OnUse(Entity* self, Entity* user) {
return;
if (missionComponent->GetMissionState(705) == eMissionState::ACTIVE) {
auto* character = user->GetCharacter();
if (character != nullptr)
character->SetPlayerFlag(68, true);
GameMessages::SetFlag setFlag{};
setFlag.target = user->GetObjectID();
setFlag.iFlagId = 68;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
missionComponent->AcceptMission(701, true);
missionComponent->AcceptMission(702, true);

View File

@@ -1,6 +1,6 @@
#include "FvPandaServer.h"
#include "PetComponent.h"
#include "Character.h"
#include "ePetTamingNotifyType.h"
void FvPandaServer::OnStartup(Entity* self) {
@@ -19,10 +19,11 @@ void FvPandaServer::OnNotifyPetTamingMinigame(Entity* self, Entity* tamer, ePetT
} else if (type == ePetTamingNotifyType::SUCCESS) {
// TODO: Remove from groups
auto* character = tamer->GetCharacter();
if (character != nullptr) {
character->SetPlayerFlag(82, true);
}
GameMessages::SetFlag setFlag{};
setFlag.target = tamer->GetObjectID();
setFlag.iFlagId = 82;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
}

View File

@@ -1,13 +1,15 @@
#include "FvPandaSpawnerServer.h"
#include "Character.h"
#include "EntityManager.h"
#include "GameMessages.h"
#include "EntityInfo.h"
#include "ScriptedActivityComponent.h"
void FvPandaSpawnerServer::OnCollisionPhantom(Entity* self, Entity* target) {
auto* character = target->GetCharacter();
if (character != nullptr && character->GetPlayerFlag(81)) {
GameMessages::GetFlag getFlag{};
getFlag.target = target->GetObjectID();
getFlag.iFlagId = 81;
if (SEND_ENTITY_MSG(getFlag) && getFlag.bFlag) {
auto raceObjects = Game::entityManager->GetEntitiesInGroup("PandaRaceObject");
if (raceObjects.empty())

View File

@@ -1,6 +1,6 @@
#include "GfJailkeepMission.h"
#include "MissionComponent.h"
#include "Character.h"
#include "eMissionState.h"
void GfJailkeepMission::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, eMissionState missionState) {
@@ -14,8 +14,11 @@ void GfJailkeepMission::OnMissionDialogueOK(Entity* self, Entity* target, int mi
missionComponent->AcceptMission(388, true);
missionComponent->AcceptMission(390, true);
} else if (missionID == 385 && missionState == eMissionState::COMPLETE_READY_TO_COMPLETE) {
auto* character = target->GetCharacter();
if (character != nullptr && character->GetPlayerFlag(68)) {
GameMessages::GetFlag getFlag{};
getFlag.target = target->GetObjectID();
getFlag.iFlagId = 68;
if (SEND_ENTITY_MSG(getFlag) && getFlag.bFlag) {
missionComponent->AcceptMission(701);
missionComponent->AcceptMission(702);
missionComponent->AcceptMission(703);

View File

@@ -1,14 +1,15 @@
#include "PirateRep.h"
#include "Character.h"
#include "eMissionState.h"
#include "Entity.h"
#include "ePlayerFlag.h"
void PirateRep::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, eMissionState missionState) {
if (missionID == m_PirateRepMissionID && missionState >= eMissionState::READY_TO_COMPLETE) {
auto* character = target->GetCharacter();
if (character) {
character->SetPlayerFlag(ePlayerFlag::GF_PIRATE_REP, true);
}
GameMessages::SetFlag setFlag{};
setFlag.target = target->GetObjectID();
setFlag.iFlagId = ePlayerFlag::GF_PIRATE_REP;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
}

View File

@@ -1,7 +1,7 @@
#include "NsGetFactionMissionServer.h"
#include "GameMessages.h"
#include "MissionComponent.h"
#include "Character.h"
#include "eReplicaComponentType.h"
#include "ePlayerFlag.h"
@@ -42,11 +42,17 @@ void NsGetFactionMissionServer::OnRespondToMission(Entity* self, int missionID,
}
if (flagID != -1) {
player->GetCharacter()->SetPlayerFlag(ePlayerFlag::JOINED_A_FACTION, true);
player->GetCharacter()->SetPlayerFlag(flagID, true);
GameMessages::SetFlag setFlag{};
setFlag.target = player->GetObjectID();
setFlag.iFlagId = ePlayerFlag::JOINED_A_FACTION;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
setFlag.iFlagId = flagID;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
MissionComponent* mis = static_cast<MissionComponent*>(player->GetComponent(eReplicaComponentType::MISSION));
auto* mis = player->GetComponent<MissionComponent>();
for (int mission : factionMissions) {
mis->AcceptMission(mission);

View File

@@ -1,6 +1,6 @@
#include "AgPropGuard.h"
#include "Entity.h"
#include "Character.h"
#include "EntityManager.h"
#include "InventoryComponent.h"
#include "MissionComponent.h"
@@ -8,13 +8,16 @@
#include "eMissionState.h"
void AgPropGuard::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, eMissionState missionState) {
auto* character = target->GetCharacter();
auto* missionComponent = target->GetComponent<MissionComponent>();
auto* inventoryComponent = target->GetComponent<InventoryComponent>();
if (!missionComponent || !inventoryComponent) return;
const auto state = missionComponent->GetMissionState(320);
if (missionID == 768 && missionState == eMissionState::AVAILABLE) {
if (!character->GetPlayerFlag(71)) {
GameMessages::GetFlag getFlag{};
getFlag.target = target->GetObjectID();
getFlag.iFlagId = 71;
if (SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
// TODO: Cinematic "MissionCam"
}
} else if (missionID == 768 && missionState >= eMissionState::READY_TO_COMPLETE) {
@@ -27,13 +30,12 @@ void AgPropGuard::OnMissionDialogueOK(Entity* self, Entity* target, int missionI
inventoryComponent->RemoveItem(id->GetLot(), id->GetCount());
}
}
} else if (
(missionID == 320 && state == eMissionState::AVAILABLE) /*||
(state == eMissionState::COMPLETE && missionID == 891 && missionState == eMissionState::READY_TO_COMPLETE)*/
) {
//GameMessages::SendNotifyClientObject(Game::entityManager->GetZoneControlEntity()->GetObjectID(), u"GuardChat", target->GetObjectID(), 0, target->GetObjectID(), "", target->GetSystemAddress());
target->GetCharacter()->SetPlayerFlag(113, true);
} else if (missionID == 320 && state == eMissionState::AVAILABLE) {
GameMessages::SetFlag setFlag{};
setFlag.target = target->GetObjectID();
setFlag.iFlagId = 113;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
Game::entityManager->GetZoneControlEntity()->AddTimer("GuardFlyAway", 1.0f);
}

View File

@@ -1,21 +1,21 @@
#include "AgPropguards.h"
#include "Character.h"
#include "GameMessages.h"
#include "EntityManager.h"
#include "dZoneManager.h"
#include "eMissionState.h"
void AgPropguards::OnMissionDialogueOK(Entity* self, Entity* target, int missionID, eMissionState missionState) {
auto* character = target->GetCharacter();
if (character == nullptr)
return;
const auto flag = GetFlagForMission(missionID);
if (flag == 0)
return;
GameMessages::GetFlag getFlag{};
getFlag.target = target->GetObjectID();
getFlag.iFlagId = flag;
if ((missionState == eMissionState::AVAILABLE || missionState == eMissionState::ACTIVE)
&& !character->GetPlayerFlag(flag)) {
&& SEND_ENTITY_MSG(getFlag) && !getFlag.bFlag) {
// If the player just started the mission, play a cinematic highlighting the target
GameMessages::SendPlayCinematic(target->GetObjectID(), u"MissionCam", target->GetSystemAddress());
} else if (missionState == eMissionState::READY_TO_COMPLETE) {

View File

@@ -202,11 +202,13 @@ int main(int argc, char** argv) {
//Find out the master's IP:
std::string masterIP = "localhost";
uint32_t masterPort = 1000;
std::string masterPassword;
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
masterPassword = masterInfo->password;
}
UserManager::Instance()->Initialize();
@@ -214,7 +216,7 @@ int main(int argc, char** argv) {
const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false);
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF);
Game::server = new dServer(masterIP, ourPort, instanceID, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::World, Game::config, &Game::lastSignal, zoneID);
Game::server = new dServer(masterIP, ourPort, instanceID, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::World, Game::config, &Game::lastSignal, masterPassword, zoneID);
//Connect to the chat server:
uint32_t chatPort = 1501;
@@ -223,7 +225,7 @@ int main(int argc, char** argv) {
auto chatSock = SocketDescriptor(static_cast<uint16_t>(ourPort + 2), 0);
Game::chatServer = RakNetworkFactory::GetRakPeerInterface();
Game::chatServer->Startup(1, 30, &chatSock, 1);
Game::chatServer->Connect(masterIP.c_str(), chatPort, "3.25 ND1", 8);
Game::chatServer->Connect(masterIP.c_str(), chatPort, NET_PASSWORD_EXTERNAL, strnlen(NET_PASSWORD_EXTERNAL, sizeof(NET_PASSWORD_EXTERNAL)));
//Set up other things:
Game::randomEngine = std::mt19937(time(0));
@@ -371,7 +373,7 @@ int main(int argc, char** argv) {
if (framesSinceChatDisconnect >= chatReconnectionTime) {
framesSinceChatDisconnect = 0;
Game::chatServer->Connect(masterIP.c_str(), chatPort, "3.25 ND1", 8);
Game::chatServer->Connect(masterIP.c_str(), chatPort, NET_PASSWORD_EXTERNAL, strnlen(NET_PASSWORD_EXTERNAL, sizeof(NET_PASSWORD_EXTERNAL)));
}
} else framesSinceChatDisconnect = 0;
@@ -1042,10 +1044,12 @@ void HandlePacket(Packet* packet) {
switch (version) {
case eCharacterVersion::RELEASE:
// TODO: Implement, super low priority
case eCharacterVersion::LIVE:
case eCharacterVersion::LIVE: {
LOG("Updating Character Flags");
c->SetRetroactiveFlags();
GameMessages::SetRetroactiveFlags flags{};
SEND_ENTITY_MSG(flags);
levelComponent->SetCharacterVersion(eCharacterVersion::PLAYER_FACTION_FLAGS);
}
case eCharacterVersion::PLAYER_FACTION_FLAGS:
LOG("Updating Vault Size");
player->RetroactiveVaultSize();

View File

@@ -0,0 +1 @@
ALTER TABLE servers ADD COLUMN master_password TEXT NOT NULL DEFAULT ('3.25 DARKFLAME1');

View File

@@ -0,0 +1 @@
ALTER TABLE servers ADD COLUMN master_password TEXT NOT NULL DEFAULT ('3.25 DARKFLAME1');

View File

@@ -9,3 +9,5 @@ world_port_start=3000
# 0 or 1, should autostart auth, chat, and char servers
prestart_servers=1
master_password=3.25DARKFLAME1

View File

@@ -68,7 +68,7 @@ TEST(dCommonTests, AMF3InsertionAssociativeTest) {
array.Insert<int32_t>("Integer", 42U);
array.Insert("Double", 42.0);
array.InsertArray("Array");
array.Insert<std::vector<uint32_t>>("Undefined", {});
array.Insert<std::vector<uint32_t>>("Undefined", std::vector<uint32_t>{});
array.Insert("Null", nullptr);
ASSERT_EQ(array.Get<const char*>("CString")->GetValueType(), eAmf::String);

View File

@@ -573,16 +573,18 @@ bool CppSQLite3Query::eof()
}
void CppSQLite3Query::nextRow()
bool CppSQLite3Query::nextRow()
{
checkVM();
int nRet = sqlite3_step(mpVM);
bool bRet = true;
if (nRet == SQLITE_DONE)
{
// no rows
mbEof = true;
bRet = false;
}
else if (nRet == SQLITE_ROW)
{
@@ -590,6 +592,7 @@ void CppSQLite3Query::nextRow()
}
else
{
bRet = false;
nRet = sqlite3_finalize(mpVM);
mpVM = 0;
const char* szError = sqlite3_errmsg(mpDB);
@@ -597,6 +600,7 @@ void CppSQLite3Query::nextRow()
(char*)szError,
DONT_DELETE_MSG);
}
return bRet;
}

View File

@@ -165,7 +165,8 @@ public:
bool eof();
void nextRow();
// Returns true if there is another row to read, false otherwise.
bool nextRow();
void finalize();
@@ -207,6 +208,9 @@ public:
int getIntField(int nField, int nNullValue=0);
int getIntField(const char* szField, int nNullValue=0);
sqlite_int64 getInt64Field(int nField, sqlite_int64 nNullValue=0);
sqlite_int64 getInt64Field(const char* szField, sqlite_int64 nNullValue=0);
double getFloatField(int nField, double fNullValue=0.0);
double getFloatField(const char* szField, double fNullValue=0.0);
@@ -218,6 +222,9 @@ public:
void setRow(int nRow);
// Returns true if there is another row to read, false otherwise.
bool nextRow();
void finalize();
private:
@@ -226,6 +233,7 @@ private:
int mnCols;
int mnRows;
bool mbEof;
int mnCurrentRow;
char** mpaszResults;
};

View File

@@ -59,7 +59,7 @@ Source/DS_List.h Source/NetworkIDObject.h S
Source/DS_Map.h Source/PacketConsoleLogger.h Source/SingleProducerConsumer.h
Source/DS_MemoryPool.h Source/PacketFileLogger.h Source/SocketLayer.h
Source/DS_OrderedChannelHeap.h Source/PacketLogger.h Source/StringCompressor.h
Source/DS_OrderedList.h Source/PacketPool.h Source/StringTable.h
Source/DS_OrderedList.h Source/StringTable.h
Source/DS_Queue.h Source/PacketPriority.h Source/SystemAddressList.h
Source/DS_QueueLinkedList.h Source/PluginInterface.h Source/TableSerializer.h
Source/DS_RangeList.h Source/RakAssert.h Source/TCPInterface.h

View File

@@ -1 +0,0 @@
// REMOVEME