Compare commits

..

21 Commits

Author SHA1 Message Date
214626222c address feedback 2024-04-26 06:17:54 -05:00
ce5bd68067 Merge remote-tracking branch 'refs/remotes/origin/chat-http-api' into chat-http-api 2024-04-25 22:28:02 -05:00
9192dca4e6 address feedback 2024-04-25 22:27:28 -05:00
06607c9b55 Update dChatServer/ChatWebApi.cpp
Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>
2024-04-25 21:05:35 -05:00
c3ea448be0 use reference with GetAllTeams
Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>
2024-04-25 20:55:24 -05:00
ff721ed49b Add Swagger OpenAPI doc 2024-04-25 17:24:19 -05:00
bce03ca08d fixed config value 2024-04-25 10:25:19 -05:00
faee5b72e7 Merge branch 'main' into chat-http-api 2024-04-25 09:23:20 -05:00
77143fc2cf rename files
use config vars to disable the api and whitelist listen IP
2024-04-25 09:21:59 -05:00
3801a97722 feat: add nlohmann/json lib (#1552)
* feat: add nlohmann/json lib

* remove build test off
2024-04-24 21:35:45 -05:00
5e16c13a58 Merge branch 'feat--add-nlohmann-dep' into chat-http-api 2024-04-24 10:10:32 -05:00
jadebenn
0367c67c85 chore: move the pet minigame table loading logic out of petcomponent (#1551)
* move the pet minigame table loading logic out of petcomponent

* misc fixes

* actually, using paths is dumb here when they're already char strings. why bother? silly me.

* removed unga bunga reference-casting

* add back in puzzle not found error message

* pre-allocate unordered map and make getter const-correct

* Update dDatabase/CDClientDatabase/CDClientTables/CDTamingBuildPuzzleTable.cpp

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

---------

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>
2024-04-24 10:09:15 -05:00
jadebenn
8fdc212cda chore: Convert heap allocation to optional (#1553)
* chore: Convert heap allocation to optional

* Update dGame/dComponents/PetComponent.h

Default-initialize
2024-04-24 10:09:04 -05:00
29e759f793 remove build test off 2024-04-24 10:00:37 -05:00
63f9a9f9d4 feat: add nlohmann/json lib 2024-04-23 16:29:57 -05:00
850ad2aa15 nl @ eof 2024-04-21 22:02:25 -05:00
ed7b33d8ab provide all the info for a some integration client to use
who->players
add teams endpoint
2024-04-21 21:56:10 -05:00
55a52b6cc0 WIP
Only works on linux, get rekt
2024-04-21 02:09:54 -05:00
99e7349f6c feat: slashcommands for showall, findplayer, get/openhttpmoninfo, and debug world packet (#1545)
* feat: showall, findplayer, get/openhttpmoninfo

http monitor info is planned to be used later, just putting in info that i've since reverse engineered and don't want lost

Additionally add debug world packet for duture dev use

Tested all new commands and variation of command arguments

* fix missing newline at eofs

* address most feedback

* Compormise and use struct with (de)serialize

* remove httpmoninfo commands
2024-04-17 21:47:28 -05:00
jadebenn
fafe2aefad chore: Nitpicking-utils (#1549)
* nit

* GeneralUtils const-correctness and minor fixes

* use copy instead of reference for char iteration loops

* fix typo and reorganize some functions
2024-04-14 23:14:54 -07:00
David Markowitz
5049f215ba chore: Use string to access SQLite columns (#1535)
* use string to access field name

* Update DEVGMCommands.cpp

* corrected column name

* constexpr array

include <array>

Revert "constexpr array"

This reverts commit 1492e8b1773ed5fbbe767c74466ca263178ecdd4.

Revert "include <array>"

This reverts commit 2b7a67e89ad673d420f496be97f9bc51fd2d5e59.

include <array>

constexpr array

---------

Co-authored-by: jadebenn <jonahbenn@yahoo.com>
2024-04-13 23:41:51 -05:00
55 changed files with 893 additions and 355 deletions

View File

@@ -96,6 +96,7 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR})
find_package(MariaDB) find_package(MariaDB)
find_package(JSON)
# Create a /resServer directory # Create a /resServer directory
make_directory(${CMAKE_BINARY_DIR}/resServer) make_directory(${CMAKE_BINARY_DIR}/resServer)
@@ -284,7 +285,7 @@ add_subdirectory(dPhysics)
add_subdirectory(dServer) add_subdirectory(dServer)
# Create a list of common libraries shared between all binaries # Create a list of common libraries shared between all binaries
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "MariaDB::ConnCpp" "magic_enum") set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "MariaDB::ConnCpp" "magic_enum" "nlohmann_json::nlohmann_json")
# Add platform specific common libraries # Add platform specific common libraries
if(UNIX) if(UNIX)

20
cmake/FindJSON.cmake Normal file
View File

@@ -0,0 +1,20 @@
include(FetchContent)
message(STATUS "Fetching json...")
FetchContent_Declare(
json
GIT_REPOSITORY https://github.com/nlohmann/json
GIT_TAG v3.11.3
)
FetchContent_GetProperties(json)
if(NOT json_POPULATED)
FetchContent_Populate(json)
add_subdirectory(${json_SOURCE_DIR} ${json_BINARY_DIR} EXCLUDE_FROM_ALL)
endif()
FetchContent_MakeAvailable(json)
message(STATUS "json fetched and is now ready.")
set(JSON_FOUND TRUE)

View File

@@ -1,4 +1,5 @@
set(DCHATSERVER_SOURCES set(DCHATSERVER_SOURCES
"ChatWebApi.cpp"
"ChatIgnoreList.cpp" "ChatIgnoreList.cpp"
"ChatPacketHandler.cpp" "ChatPacketHandler.cpp"
"PlayerContainer.cpp" "PlayerContainer.cpp"

View File

@@ -18,6 +18,7 @@
#include "eGameMessageType.h" #include "eGameMessageType.h"
#include "StringifiedEnum.h" #include "StringifiedEnum.h"
#include "eGameMasterLevel.h" #include "eGameMasterLevel.h"
#include "ChatPackets.h"
void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) { void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//Get from the packet which player we want to do something with: //Get from the packet which player we want to do something with:
@@ -354,6 +355,67 @@ void ChatPacketHandler::HandleGMLevelUpdate(Packet* packet) {
inStream.Read(player.gmLevel); inStream.Read(player.gmLevel);
} }
void ChatPacketHandler::HandleWho(Packet* packet) {
CINSTREAM_SKIP_HEADER;
FindPlayerRequest request;
request.Deserialize(inStream);
const auto& sender = Game::playerContainer.GetPlayerData(request.requestor);
if (!sender) return;
const auto& player = Game::playerContainer.GetPlayerData(request.playerName.GetAsString());
bool online = player;
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(request.requestor);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::WHO_RESPONSE);
bitStream.Write<uint8_t>(online);
bitStream.Write(player.zoneID.GetMapID());
bitStream.Write(player.zoneID.GetInstanceID());
bitStream.Write(player.zoneID.GetCloneID());
bitStream.Write(request.playerName);
SystemAddress sysAddr = sender.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::HandleShowAll(Packet* packet) {
CINSTREAM_SKIP_HEADER;
ShowAllRequest request;
request.Deserialize(inStream);
const auto& sender = Game::playerContainer.GetPlayerData(request.requestor);
if (!sender) return;
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WORLD_ROUTE_PACKET);
bitStream.Write(request.requestor);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::SHOW_ALL_RESPONSE);
bitStream.Write<uint8_t>(!request.displayZoneData && !request.displayIndividualPlayers);
bitStream.Write(Game::playerContainer.GetPlayerCount());
bitStream.Write(Game::playerContainer.GetSimCount());
bitStream.Write<uint8_t>(request.displayIndividualPlayers);
bitStream.Write<uint8_t>(request.displayZoneData);
if (request.displayZoneData || request.displayIndividualPlayers){
for (auto& [playerID, playerData ]: Game::playerContainer.GetAllPlayers()){
if (!playerData) continue;
bitStream.Write<uint8_t>(0); // structure packing
if (request.displayIndividualPlayers) bitStream.Write(LUWString(playerData.playerName));
if (request.displayZoneData) {
bitStream.Write(playerData.zoneID.GetMapID());
bitStream.Write(playerData.zoneID.GetInstanceID());
bitStream.Write(playerData.zoneID.GetCloneID());
}
}
}
SystemAddress sysAddr = sender.sysAddr;
SEND_PACKET;
}
// the structure the client uses to send this packet is shared in many chat messages // the structure the client uses to send this packet is shared in many chat messages
// that are sent to the server. Because of this, there are large gaps of unused data in chat messages // that are sent to the server. Because of this, there are large gaps of unused data in chat messages
void ChatPacketHandler::HandleChatMessage(Packet* packet) { void ChatPacketHandler::HandleChatMessage(Packet* packet) {

View File

@@ -50,6 +50,8 @@ namespace ChatPacketHandler {
void HandleFriendResponse(Packet* packet); void HandleFriendResponse(Packet* packet);
void HandleRemoveFriend(Packet* packet); void HandleRemoveFriend(Packet* packet);
void HandleGMLevelUpdate(Packet* packet); void HandleGMLevelUpdate(Packet* packet);
void HandleWho(Packet* packet);
void HandleShowAll(Packet* packet);
void HandleChatMessage(Packet* packet); void HandleChatMessage(Packet* packet);
void HandlePrivateChatMessage(Packet* packet); void HandlePrivateChatMessage(Packet* packet);

View File

@@ -20,6 +20,7 @@
#include "eWorldMessageType.h" #include "eWorldMessageType.h"
#include "ChatIgnoreList.h" #include "ChatIgnoreList.h"
#include "StringifiedEnum.h" #include "StringifiedEnum.h"
#include "ChatWebApi.h"
#include "Game.h" #include "Game.h"
#include "Server.h" #include "Server.h"
@@ -36,7 +37,7 @@ namespace Game {
AssetManager* assetManager = nullptr; AssetManager* assetManager = nullptr;
Game::signal_t lastSignal = 0; Game::signal_t lastSignal = 0;
std::mt19937 randomEngine; std::mt19937 randomEngine;
PlayerContainer playerContainer; PlayerContainer playerContainer;
} }
void HandlePacket(Packet* packet); void HandlePacket(Packet* packet);
@@ -122,6 +123,9 @@ int main(int argc, char** argv) {
uint32_t framesSinceMasterDisconnect = 0; uint32_t framesSinceMasterDisconnect = 0;
uint32_t framesSinceLastSQLPing = 0; uint32_t framesSinceLastSQLPing = 0;
// start the web api thread
std::thread webAPIThread(ChatWebApi::Listen, ourPort);
Game::logger->Flush(); // once immediately before main loop Game::logger->Flush(); // once immediately before main loop
while (!Game::ShouldShutdown()) { while (!Game::ShouldShutdown()) {
//Check if we're still connected to master: //Check if we're still connected to master:
@@ -174,6 +178,10 @@ int main(int argc, char** argv) {
delete Game::server; delete Game::server;
delete Game::logger; delete Game::logger;
delete Game::config; delete Game::config;
// rejoin the web api thread
ChatWebApi::Stop();
webAPIThread.join();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
@@ -289,7 +297,11 @@ void HandlePacket(Packet* packet) {
Game::playerContainer.RemovePlayer(packet); Game::playerContainer.RemovePlayer(packet);
break; break;
case eChatMessageType::WHO: case eChatMessageType::WHO:
ChatPacketHandler::HandleWho(packet);
break;
case eChatMessageType::SHOW_ALL: case eChatMessageType::SHOW_ALL:
ChatPacketHandler::HandleShowAll(packet);
break;
case eChatMessageType::USER_CHANNEL_CHAT_MESSAGE: case eChatMessageType::USER_CHANNEL_CHAT_MESSAGE:
case eChatMessageType::WORLD_DISCONNECT_REQUEST: case eChatMessageType::WORLD_DISCONNECT_REQUEST:
case eChatMessageType::WORLD_PROXIMITY_RESPONSE: case eChatMessageType::WORLD_PROXIMITY_RESPONSE:

View File

@@ -0,0 +1,93 @@
#include "ChatWebApi.h"
#include <cstdint>
#include "dCommonVars.h"
#include "eConnectionType.h"
#include "eChatMessageType.h"
#include "httplib.h"
#include "dServer.h"
#include "PlayerContainer.h"
#include "dConfig.h"
#include "httplib.h"
#include "nlohmann/json.hpp"
using json = nlohmann::json;
namespace {
httplib::Server m_APIServer;
}
void ChatWebApi::Listen(const uint32_t port) {
if (Game::config->GetValue("enable_chat_web_api") != "1") {
LOG("Chat Web API is disabled");
return;
}
LOG("Chat Web API is enabled, starting web server...");
m_APIServer.Post("/announce", [](const httplib::Request& req, httplib::Response& res) {
const json data = json::parse(req.body);
if (!data.contains("title")) {
res.set_content("{\"error\":\"Missing paramater: title\"}", "application/json");
res.status = 400;
return;
}
std::string title = data["title"];
if (!data.contains("message")) {
res.set_content("{\"error\":\"Missing paramater: message\"}", "application/json");
res.status = 400;
return;
}
std::string message = data["message"];
// build and send the packet to all world servers
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GM_ANNOUNCE);
bitStream.Write<uint32_t>(title.size());
bitStream.Write(title);
bitStream.Write<uint32_t>(message.size());
bitStream.Write(message);
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
});
m_APIServer.Get("/players", [](const httplib::Request& req, httplib::Response& res) {
auto data = json::array();
for (auto& [playerID, playerData ]: Game::playerContainer.GetAllPlayers()){
if (!playerData) continue;
data.push_back(playerData.to_json());
}
res.set_content(data.dump(), "application/json");
if (data.empty()) res.status = 204;
});
m_APIServer.Get("/teams", [](const httplib::Request& req, httplib::Response& res) {
auto data = json::array();
for (auto& teamData: Game::playerContainer.GetAllTeams()){
if (!teamData) continue;
json toInsert;
toInsert["id"] = teamData->teamID;
toInsert["loot_flag"] = teamData->lootFlag;
toInsert["local"] = teamData->local;
auto& leader = Game::playerContainer.GetPlayerData(teamData->leaderID);
toInsert["leader"] = leader.to_json();
json members;
for (auto& member : teamData->memberIDs){
auto playerData = Game::playerContainer.GetPlayerData(member);
if (!playerData) continue;
members.push_back(playerData.to_json());
}
toInsert["members"] = members;
data.push_back(toInsert);
}
res.set_content(data.dump(), "application/json");
if (data.empty()) res.status = 204;
});
m_APIServer.listen(Game::config->GetValue("chat_web_api_listen_address").c_str(), port);
};
void ChatWebApi::Stop(){
LOG("Stopping Chat Web API server...");
m_APIServer.stop();
}

6
dChatServer/ChatWebApi.h Normal file
View File

@@ -0,0 +1,6 @@
#include <cstdint>
namespace ChatWebApi {
void Listen(const uint32_t port);
void Stop();
};

View File

@@ -1,7 +1,9 @@
#include "PlayerContainer.h" #include "PlayerContainer.h"
#include "dNetCommon.h"
#include <iostream> #include <iostream>
#include <algorithm> #include <algorithm>
#include "dNetCommon.h"
#include "Game.h" #include "Game.h"
#include "Logger.h" #include "Logger.h"
#include "ChatPacketHandler.h" #include "ChatPacketHandler.h"
@@ -11,7 +13,24 @@
#include "eConnectionType.h" #include "eConnectionType.h"
#include "ChatPackets.h" #include "ChatPackets.h"
#include "dConfig.h" #include "dConfig.h"
#include "eChatMessageType.h" #include "eChatMessageType.h"
using json = nlohmann::json;
const json PlayerData::to_json() const {
json data;
data["id"] = this->playerID;
data["name"] = this->playerName;
data["gm_level"] = this->gmLevel;
data["muted"] = this->GetIsMuted();
json zoneID;
zoneID["map_id"] = std::to_string(this->zoneID.GetMapID());
zoneID["instance_id"] = std::to_string(this->zoneID.GetInstanceID());
zoneID["clone_id"] = std::to_string(this->zoneID.GetCloneID());
data["zone_id"] = zoneID;
return data;
}
void PlayerContainer::Initialize() { void PlayerContainer::Initialize() {
m_MaxNumberOfBestFriends = m_MaxNumberOfBestFriends =
@@ -49,6 +68,7 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
data.sysAddr = packet->systemAddress; data.sysAddr = packet->systemAddress;
m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName); m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName);
m_PlayerCount++;
LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID()); LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID());
@@ -87,6 +107,7 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
} }
} }
m_PlayerCount--;
LOG("Removed user: %llu", playerID); LOG("Removed user: %llu", playerID);
m_Players.erase(playerID); m_Players.erase(playerID);

View File

@@ -6,6 +6,7 @@
#include "Game.h" #include "Game.h"
#include "dServer.h" #include "dServer.h"
#include <unordered_map> #include <unordered_map>
#include "nlohmann/json.hpp"
enum class eGameMasterLevel : uint8_t; enum class eGameMasterLevel : uint8_t;
@@ -36,6 +37,8 @@ struct PlayerData {
return muteExpire == 1 || muteExpire > time(NULL); return muteExpire == 1 || muteExpire > time(NULL);
} }
const nlohmann::json to_json() const;
SystemAddress sysAddr{}; SystemAddress sysAddr{};
LWOZONEID zoneID{}; LWOZONEID zoneID{};
LWOOBJID playerID = LWOOBJID_EMPTY; LWOOBJID playerID = LWOOBJID_EMPTY;
@@ -71,6 +74,9 @@ public:
const PlayerData& GetPlayerData(const std::string& playerName); const PlayerData& GetPlayerData(const std::string& playerName);
PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID); PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID);
PlayerData& GetPlayerDataMutable(const std::string& playerName); PlayerData& GetPlayerDataMutable(const std::string& playerName);
uint32_t GetPlayerCount() { return m_PlayerCount; };
uint32_t GetSimCount() { return m_SimCount; };
const std::map<LWOOBJID, PlayerData>& GetAllPlayers() { return m_Players; };
TeamData* CreateLocalTeam(std::vector<LWOOBJID> members); TeamData* CreateLocalTeam(std::vector<LWOOBJID> members);
TeamData* CreateTeam(LWOOBJID leader, bool local = false); TeamData* CreateTeam(LWOOBJID leader, bool local = false);
@@ -85,6 +91,7 @@ public:
LWOOBJID GetId(const std::u16string& playerName); LWOOBJID GetId(const std::u16string& playerName);
uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; } uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; }
uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; } uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; }
const std::vector<TeamData*>& GetAllTeams() { return mTeams;};
private: private:
LWOOBJID m_TeamIDCounter = 0; LWOOBJID m_TeamIDCounter = 0;
@@ -93,5 +100,7 @@ private:
std::unordered_map<LWOOBJID, std::u16string> m_Names; std::unordered_map<LWOOBJID, std::u16string> m_Names;
uint32_t m_MaxNumberOfBestFriends = 5; uint32_t m_MaxNumberOfBestFriends = 5;
uint32_t m_MaxNumberOfFriends = 50; uint32_t m_MaxNumberOfFriends = 50;
uint32_t m_PlayerCount = 0;
uint32_t m_SimCount = 0;
}; };

View File

@@ -8,23 +8,23 @@
#include <map> #include <map>
template <typename T> template <typename T>
inline size_t MinSize(size_t size, const std::basic_string_view<T>& string) { static inline size_t MinSize(const size_t size, const std::basic_string_view<T> string) {
if (size == size_t(-1) || size > string.size()) { if (size == SIZE_MAX || size > string.size()) {
return string.size(); return string.size();
} else { } else {
return size; return size;
} }
} }
inline bool IsLeadSurrogate(char16_t c) { inline bool IsLeadSurrogate(const char16_t c) {
return (0xD800 <= c) && (c <= 0xDBFF); return (0xD800 <= c) && (c <= 0xDBFF);
} }
inline bool IsTrailSurrogate(char16_t c) { inline bool IsTrailSurrogate(const char16_t c) {
return (0xDC00 <= c) && (c <= 0xDFFF); return (0xDC00 <= c) && (c <= 0xDFFF);
} }
inline void PushUTF8CodePoint(std::string& ret, char32_t cp) { inline void PushUTF8CodePoint(std::string& ret, const char32_t cp) {
if (cp <= 0x007F) { if (cp <= 0x007F) {
ret.push_back(static_cast<uint8_t>(cp)); ret.push_back(static_cast<uint8_t>(cp));
} else if (cp <= 0x07FF) { } else if (cp <= 0x07FF) {
@@ -46,16 +46,16 @@ inline void PushUTF8CodePoint(std::string& ret, char32_t cp) {
constexpr const char16_t REPLACEMENT_CHARACTER = 0xFFFD; constexpr const char16_t REPLACEMENT_CHARACTER = 0xFFFD;
bool _IsSuffixChar(uint8_t c) { bool static _IsSuffixChar(const uint8_t c) {
return (c & 0xC0) == 0x80; return (c & 0xC0) == 0x80;
} }
bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) { bool GeneralUtils::details::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
size_t rem = slice.length(); const size_t rem = slice.length();
if (slice.empty()) return false; if (slice.empty()) return false;
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&slice.front()); const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&slice.front());
if (rem > 0) { if (rem > 0) {
uint8_t first = bytes[0]; const uint8_t first = bytes[0];
if (first < 0x80) { // 1 byte character if (first < 0x80) { // 1 byte character
out = static_cast<uint32_t>(first & 0x7F); out = static_cast<uint32_t>(first & 0x7F);
slice.remove_prefix(1); slice.remove_prefix(1);
@@ -64,7 +64,7 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
// middle byte, not valid at start, fall through // middle byte, not valid at start, fall through
} else if (first < 0xE0) { // two byte character } else if (first < 0xE0) { // two byte character
if (rem > 1) { if (rem > 1) {
uint8_t second = bytes[1]; const uint8_t second = bytes[1];
if (_IsSuffixChar(second)) { if (_IsSuffixChar(second)) {
out = (static_cast<uint32_t>(first & 0x1F) << 6) out = (static_cast<uint32_t>(first & 0x1F) << 6)
+ static_cast<uint32_t>(second & 0x3F); + static_cast<uint32_t>(second & 0x3F);
@@ -74,8 +74,8 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
} }
} else if (first < 0xF0) { // three byte character } else if (first < 0xF0) { // three byte character
if (rem > 2) { if (rem > 2) {
uint8_t second = bytes[1]; const uint8_t second = bytes[1];
uint8_t third = bytes[2]; const uint8_t third = bytes[2];
if (_IsSuffixChar(second) && _IsSuffixChar(third)) { if (_IsSuffixChar(second) && _IsSuffixChar(third)) {
out = (static_cast<uint32_t>(first & 0x0F) << 12) out = (static_cast<uint32_t>(first & 0x0F) << 12)
+ (static_cast<uint32_t>(second & 0x3F) << 6) + (static_cast<uint32_t>(second & 0x3F) << 6)
@@ -86,9 +86,9 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
} }
} else if (first < 0xF8) { // four byte character } else if (first < 0xF8) { // four byte character
if (rem > 3) { if (rem > 3) {
uint8_t second = bytes[1]; const uint8_t second = bytes[1];
uint8_t third = bytes[2]; const uint8_t third = bytes[2];
uint8_t fourth = bytes[3]; const uint8_t fourth = bytes[3];
if (_IsSuffixChar(second) && _IsSuffixChar(third) && _IsSuffixChar(fourth)) { if (_IsSuffixChar(second) && _IsSuffixChar(third) && _IsSuffixChar(fourth)) {
out = (static_cast<uint32_t>(first & 0x07) << 18) out = (static_cast<uint32_t>(first & 0x07) << 18)
+ (static_cast<uint32_t>(second & 0x3F) << 12) + (static_cast<uint32_t>(second & 0x3F) << 12)
@@ -107,7 +107,7 @@ bool GeneralUtils::_NextUTF8Char(std::string_view& slice, uint32_t& out) {
} }
/// See <https://www.ietf.org/rfc/rfc2781.html#section-2.1> /// See <https://www.ietf.org/rfc/rfc2781.html#section-2.1>
bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) { bool PushUTF16CodePoint(std::u16string& output, const uint32_t U, const size_t size) {
if (output.length() >= size) return false; if (output.length() >= size) return false;
if (U < 0x10000) { if (U < 0x10000) {
// If U < 0x10000, encode U as a 16-bit unsigned integer and terminate. // If U < 0x10000, encode U as a 16-bit unsigned integer and terminate.
@@ -120,7 +120,7 @@ bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) {
// Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF, // Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
// U' must be less than or equal to 0xFFFFF. That is, U' can be // U' must be less than or equal to 0xFFFFF. That is, U' can be
// represented in 20 bits. // represented in 20 bits.
uint32_t Ut = U - 0x10000; const uint32_t Ut = U - 0x10000;
// Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and // Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
// 0xDC00, respectively. These integers each have 10 bits free to // 0xDC00, respectively. These integers each have 10 bits free to
@@ -141,25 +141,25 @@ bool PushUTF16CodePoint(std::u16string& output, uint32_t U, size_t size) {
} else return false; } else return false;
} }
std::u16string GeneralUtils::UTF8ToUTF16(const std::string_view& string, size_t size) { std::u16string GeneralUtils::UTF8ToUTF16(const std::string_view string, const size_t size) {
size_t newSize = MinSize(size, string); const size_t newSize = MinSize(size, string);
std::u16string output; std::u16string output;
output.reserve(newSize); output.reserve(newSize);
std::string_view iterator = string; std::string_view iterator = string;
uint32_t c; uint32_t c;
while (_NextUTF8Char(iterator, c) && PushUTF16CodePoint(output, c, size)) {} while (details::_NextUTF8Char(iterator, c) && PushUTF16CodePoint(output, c, size)) {}
return output; return output;
} }
//! Converts an std::string (ASCII) to UCS-2 / UTF-16 //! Converts an std::string (ASCII) to UCS-2 / UTF-16
std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view& string, size_t size) { std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view string, const size_t size) {
size_t newSize = MinSize(size, string); const size_t newSize = MinSize(size, string);
std::u16string ret; std::u16string ret;
ret.reserve(newSize); ret.reserve(newSize);
for (size_t i = 0; i < newSize; i++) { for (size_t i = 0; i < newSize; ++i) {
char c = string[i]; const char c = string[i];
// Note: both 7-bit ascii characters and REPLACEMENT_CHARACTER fit in one char16_t // Note: both 7-bit ascii characters and REPLACEMENT_CHARACTER fit in one char16_t
ret.push_back((c > 0 && c <= 127) ? static_cast<char16_t>(c) : REPLACEMENT_CHARACTER); ret.push_back((c > 0 && c <= 127) ? static_cast<char16_t>(c) : REPLACEMENT_CHARACTER);
} }
@@ -169,18 +169,18 @@ std::u16string GeneralUtils::ASCIIToUTF16(const std::string_view& string, size_t
//! Converts a (potentially-ill-formed) UTF-16 string to UTF-8 //! Converts a (potentially-ill-formed) UTF-16 string to UTF-8
//! See: <http://simonsapin.github.io/wtf-8/#decoding-ill-formed-utf-16> //! See: <http://simonsapin.github.io/wtf-8/#decoding-ill-formed-utf-16>
std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view& string, size_t size) { std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view string, const size_t size) {
size_t newSize = MinSize(size, string); const size_t newSize = MinSize(size, string);
std::string ret; std::string ret;
ret.reserve(newSize); ret.reserve(newSize);
for (size_t i = 0; i < newSize; i++) { for (size_t i = 0; i < newSize; ++i) {
char16_t u = string[i]; const char16_t u = string[i];
if (IsLeadSurrogate(u) && (i + 1) < newSize) { if (IsLeadSurrogate(u) && (i + 1) < newSize) {
char16_t next = string[i + 1]; const char16_t next = string[i + 1];
if (IsTrailSurrogate(next)) { if (IsTrailSurrogate(next)) {
i += 1; i += 1;
char32_t cp = 0x10000 const char32_t cp = 0x10000
+ ((static_cast<char32_t>(u) - 0xD800) << 10) + ((static_cast<char32_t>(u) - 0xD800) << 10)
+ (static_cast<char32_t>(next) - 0xDC00); + (static_cast<char32_t>(next) - 0xDC00);
PushUTF8CodePoint(ret, cp); PushUTF8CodePoint(ret, cp);
@@ -195,40 +195,40 @@ std::string GeneralUtils::UTF16ToWTF8(const std::u16string_view& string, size_t
return ret; return ret;
} }
bool GeneralUtils::CaseInsensitiveStringCompare(const std::string& a, const std::string& b) { bool GeneralUtils::CaseInsensitiveStringCompare(const std::string_view a, const std::string_view b) {
return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == tolower(b); }); return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](char a, char b) { return tolower(a) == tolower(b); });
} }
// MARK: Bits // MARK: Bits
//! Sets a specific bit in a signed 64-bit integer //! Sets a specific bit in a signed 64-bit integer
int64_t GeneralUtils::SetBit(int64_t value, uint32_t index) { int64_t GeneralUtils::SetBit(int64_t value, const uint32_t index) {
return value |= 1ULL << index; return value |= 1ULL << index;
} }
//! Clears a specific bit in a signed 64-bit integer //! Clears a specific bit in a signed 64-bit integer
int64_t GeneralUtils::ClearBit(int64_t value, uint32_t index) { int64_t GeneralUtils::ClearBit(int64_t value, const uint32_t index) {
return value &= ~(1ULL << index); return value &= ~(1ULL << index);
} }
//! Checks a specific bit in a signed 64-bit integer //! Checks a specific bit in a signed 64-bit integer
bool GeneralUtils::CheckBit(int64_t value, uint32_t index) { bool GeneralUtils::CheckBit(int64_t value, const uint32_t index) {
return value & (1ULL << index); return value & (1ULL << index);
} }
bool GeneralUtils::ReplaceInString(std::string& str, const std::string& from, const std::string& to) { bool GeneralUtils::ReplaceInString(std::string& str, const std::string_view from, const std::string_view to) {
size_t start_pos = str.find(from); const size_t start_pos = str.find(from);
if (start_pos == std::string::npos) if (start_pos == std::string::npos)
return false; return false;
str.replace(start_pos, from.length(), to); str.replace(start_pos, from.length(), to);
return true; return true;
} }
std::vector<std::wstring> GeneralUtils::SplitString(std::wstring& str, wchar_t delimiter) { std::vector<std::wstring> GeneralUtils::SplitString(const std::wstring_view str, const wchar_t delimiter) {
std::vector<std::wstring> vector = std::vector<std::wstring>(); std::vector<std::wstring> vector = std::vector<std::wstring>();
std::wstring current; std::wstring current;
for (const auto& c : str) { for (const wchar_t c : str) {
if (c == delimiter) { if (c == delimiter) {
vector.push_back(current); vector.push_back(current);
current = L""; current = L"";
@@ -237,15 +237,15 @@ std::vector<std::wstring> GeneralUtils::SplitString(std::wstring& str, wchar_t d
} }
} }
vector.push_back(current); vector.push_back(std::move(current));
return vector; return vector;
} }
std::vector<std::u16string> GeneralUtils::SplitString(const std::u16string& str, char16_t delimiter) { std::vector<std::u16string> GeneralUtils::SplitString(const std::u16string_view str, const char16_t delimiter) {
std::vector<std::u16string> vector = std::vector<std::u16string>(); std::vector<std::u16string> vector = std::vector<std::u16string>();
std::u16string current; std::u16string current;
for (const auto& c : str) { for (const char16_t c : str) {
if (c == delimiter) { if (c == delimiter) {
vector.push_back(current); vector.push_back(current);
current = u""; current = u"";
@@ -254,17 +254,15 @@ std::vector<std::u16string> GeneralUtils::SplitString(const std::u16string& str,
} }
} }
vector.push_back(current); vector.push_back(std::move(current));
return vector; return vector;
} }
std::vector<std::string> GeneralUtils::SplitString(const std::string& str, char delimiter) { std::vector<std::string> GeneralUtils::SplitString(const std::string_view str, const char delimiter) {
std::vector<std::string> vector = std::vector<std::string>(); std::vector<std::string> vector = std::vector<std::string>();
std::string current = ""; std::string current = "";
for (size_t i = 0; i < str.length(); i++) { for (const char c : str) {
char c = str[i];
if (c == delimiter) { if (c == delimiter) {
vector.push_back(current); vector.push_back(current);
current = ""; current = "";
@@ -273,8 +271,7 @@ std::vector<std::string> GeneralUtils::SplitString(const std::string& str, char
} }
} }
vector.push_back(current); vector.push_back(std::move(current));
return vector; return vector;
} }
@@ -283,7 +280,7 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream& inStream) {
inStream.Read<uint32_t>(length); inStream.Read<uint32_t>(length);
std::u16string string; std::u16string string;
for (auto i = 0; i < length; i++) { for (uint32_t i = 0; i < length; ++i) {
uint16_t c; uint16_t c;
inStream.Read(c); inStream.Read(c);
string.push_back(c); string.push_back(c);
@@ -292,29 +289,29 @@ std::u16string GeneralUtils::ReadWString(RakNet::BitStream& inStream) {
return string; return string;
} }
std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::string& folder) { std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::string_view folder) {
// Because we dont know how large the initial number before the first _ is we need to make it a map like so. // Because we dont know how large the initial number before the first _ is we need to make it a map like so.
std::map<uint32_t, std::string> filenames{}; std::map<uint32_t, std::string> filenames{};
for (auto& t : std::filesystem::directory_iterator(folder)) { for (const auto& t : std::filesystem::directory_iterator(folder)) {
auto filename = t.path().filename().string(); auto filename = t.path().filename().string();
auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0)); const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0));
filenames.insert(std::make_pair(index, filename)); filenames.emplace(index, std::move(filename));
} }
// Now sort the map by the oldest migration. // Now sort the map by the oldest migration.
std::vector<std::string> sortedFiles{}; std::vector<std::string> sortedFiles{};
auto fileIterator = filenames.begin(); auto fileIterator = filenames.cbegin();
std::map<uint32_t, std::string>::iterator oldest = filenames.begin(); auto oldest = filenames.cbegin();
while (!filenames.empty()) { while (!filenames.empty()) {
if (fileIterator == filenames.end()) { if (fileIterator == filenames.cend()) {
sortedFiles.push_back(oldest->second); sortedFiles.push_back(oldest->second);
filenames.erase(oldest); filenames.erase(oldest);
fileIterator = filenames.begin(); fileIterator = filenames.cbegin();
oldest = filenames.begin(); oldest = filenames.cbegin();
continue; continue;
} }
if (oldest->first > fileIterator->first) oldest = fileIterator; if (oldest->first > fileIterator->first) oldest = fileIterator;
fileIterator++; ++fileIterator;
} }
return sortedFiles; return sortedFiles;

View File

@@ -3,17 +3,18 @@
// C++ // C++
#include <charconv> #include <charconv>
#include <cstdint> #include <cstdint>
#include <random>
#include <ctime> #include <ctime>
#include <functional>
#include <optional>
#include <random>
#include <span>
#include <stdexcept>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <optional>
#include <functional>
#include <type_traits> #include <type_traits>
#include <stdexcept>
#include "BitStream.h" #include "BitStream.h"
#include "NiPoint3.h" #include "NiPoint3.h"
#include "dPlatforms.h" #include "dPlatforms.h"
#include "Game.h" #include "Game.h"
#include "Logger.h" #include "Logger.h"
@@ -32,29 +33,31 @@ namespace GeneralUtils {
//! Converts a plain ASCII string to a UTF-16 string //! Converts a plain ASCII string to a UTF-16 string
/*! /*!
\param string The string to convert \param string The string to convert
\param size A size to trim the string to. Default is -1 (No trimming) \param size A size to trim the string to. Default is SIZE_MAX (No trimming)
\return An UTF-16 representation of the string \return An UTF-16 representation of the string
*/ */
std::u16string ASCIIToUTF16(const std::string_view& string, size_t size = -1); std::u16string ASCIIToUTF16(const std::string_view string, const size_t size = SIZE_MAX);
//! Converts a UTF-8 String to a UTF-16 string //! Converts a UTF-8 String to a UTF-16 string
/*! /*!
\param string The string to convert \param string The string to convert
\param size A size to trim the string to. Default is -1 (No trimming) \param size A size to trim the string to. Default is SIZE_MAX (No trimming)
\return An UTF-16 representation of the string \return An UTF-16 representation of the string
*/ */
std::u16string UTF8ToUTF16(const std::string_view& string, size_t size = -1); std::u16string UTF8ToUTF16(const std::string_view string, const size_t size = SIZE_MAX);
//! Internal, do not use namespace details {
bool _NextUTF8Char(std::string_view& slice, uint32_t& out); //! Internal, do not use
bool _NextUTF8Char(std::string_view& slice, uint32_t& out);
}
//! Converts a UTF-16 string to a UTF-8 string //! Converts a UTF-16 string to a UTF-8 string
/*! /*!
\param string The string to convert \param string The string to convert
\param size A size to trim the string to. Default is -1 (No trimming) \param size A size to trim the string to. Default is SIZE_MAX (No trimming)
\return An UTF-8 representation of the string \return An UTF-8 representation of the string
*/ */
std::string UTF16ToWTF8(const std::u16string_view& string, size_t size = -1); std::string UTF16ToWTF8(const std::u16string_view string, const size_t size = SIZE_MAX);
/** /**
* Compares two basic strings but does so ignoring case sensitivity * Compares two basic strings but does so ignoring case sensitivity
@@ -62,7 +65,7 @@ namespace GeneralUtils {
* \param b the second string to compare against the first string * \param b the second string to compare against the first string
* @return if the two strings are equal * @return if the two strings are equal
*/ */
bool CaseInsensitiveStringCompare(const std::string& a, const std::string& b); bool CaseInsensitiveStringCompare(const std::string_view a, const std::string_view b);
// MARK: Bits // MARK: Bits
@@ -70,9 +73,9 @@ namespace GeneralUtils {
//! Sets a bit on a numerical value //! Sets a bit on a numerical value
template <typename T> template <typename T>
inline void SetBit(T& value, eObjectBits bits) { inline void SetBit(T& value, const eObjectBits bits) {
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type"); static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
auto index = static_cast<size_t>(bits); const auto index = static_cast<size_t>(bits);
if (index > (sizeof(T) * 8) - 1) { if (index > (sizeof(T) * 8) - 1) {
return; return;
} }
@@ -82,9 +85,9 @@ namespace GeneralUtils {
//! Clears a bit on a numerical value //! Clears a bit on a numerical value
template <typename T> template <typename T>
inline void ClearBit(T& value, eObjectBits bits) { inline void ClearBit(T& value, const eObjectBits bits) {
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type"); static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
auto index = static_cast<size_t>(bits); const auto index = static_cast<size_t>(bits);
if (index > (sizeof(T) * 8 - 1)) { if (index > (sizeof(T) * 8 - 1)) {
return; return;
} }
@@ -97,14 +100,14 @@ namespace GeneralUtils {
\param value The value to set the bit for \param value The value to set the bit for
\param index The index of the bit \param index The index of the bit
*/ */
int64_t SetBit(int64_t value, uint32_t index); int64_t SetBit(int64_t value, const uint32_t index);
//! Clears a specific bit in a signed 64-bit integer //! Clears a specific bit in a signed 64-bit integer
/*! /*!
\param value The value to clear the bit from \param value The value to clear the bit from
\param index The index of the bit \param index The index of the bit
*/ */
int64_t ClearBit(int64_t value, uint32_t index); int64_t ClearBit(int64_t value, const uint32_t index);
//! Checks a specific bit in a signed 64-bit integer //! Checks a specific bit in a signed 64-bit integer
/*! /*!
@@ -112,19 +115,19 @@ namespace GeneralUtils {
\param index The index of the bit \param index The index of the bit
\return Whether or not the bit is set \return Whether or not the bit is set
*/ */
bool CheckBit(int64_t value, uint32_t index); bool CheckBit(int64_t value, const uint32_t index);
bool ReplaceInString(std::string& str, const std::string& from, const std::string& to); bool ReplaceInString(std::string& str, const std::string_view from, const std::string_view to);
std::u16string ReadWString(RakNet::BitStream& inStream); std::u16string ReadWString(RakNet::BitStream& inStream);
std::vector<std::wstring> SplitString(std::wstring& str, wchar_t delimiter); std::vector<std::wstring> SplitString(const std::wstring_view str, const wchar_t delimiter);
std::vector<std::u16string> SplitString(const std::u16string& str, char16_t delimiter); std::vector<std::u16string> SplitString(const std::u16string_view str, const char16_t delimiter);
std::vector<std::string> SplitString(const std::string& str, char delimiter); std::vector<std::string> SplitString(const std::string_view str, const char delimiter);
std::vector<std::string> GetSqlFileNamesFromFolder(const std::string& folder); std::vector<std::string> GetSqlFileNamesFromFolder(const std::string_view folder);
// Concept constraining to enum types // Concept constraining to enum types
template <typename T> template <typename T>
@@ -144,7 +147,7 @@ namespace GeneralUtils {
// If a boolean, present an alias to an intermediate integral type for parsing // If a boolean, present an alias to an intermediate integral type for parsing
template <Numeric T> requires std::same_as<T, bool> template <Numeric T> requires std::same_as<T, bool>
struct numeric_parse<T> { using type = uint32_t; }; struct numeric_parse<T> { using type = uint8_t; };
// Shorthand type alias // Shorthand type alias
template <Numeric T> template <Numeric T>
@@ -205,7 +208,7 @@ namespace GeneralUtils {
* @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters * @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters
*/ */
template <typename T> template <typename T>
[[nodiscard]] std::optional<NiPoint3> TryParse(const std::string& strX, const std::string& strY, const std::string& strZ) { [[nodiscard]] std::optional<NiPoint3> TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ) {
const auto x = TryParse<float>(strX); const auto x = TryParse<float>(strX);
if (!x) return std::nullopt; if (!x) return std::nullopt;
@@ -217,17 +220,17 @@ namespace GeneralUtils {
} }
/** /**
* The TryParse overload for handling NiPoint3 by passingn a reference to a vector of three strings * The TryParse overload for handling NiPoint3 by passing a span of three strings
* @param str The string vector representing the X, Y, and Xcoordinates * @param str The string vector representing the X, Y, and Z coordinates
* @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters * @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters
*/ */
template <typename T> template <typename T>
[[nodiscard]] std::optional<NiPoint3> TryParse(const std::vector<std::string>& str) { [[nodiscard]] std::optional<NiPoint3> TryParse(const std::span<const std::string> str) {
return (str.size() == 3) ? TryParse<NiPoint3>(str[0], str[1], str[2]) : std::nullopt; return (str.size() == 3) ? TryParse<NiPoint3>(str[0], str[1], str[2]) : std::nullopt;
} }
template <typename T> template <typename T>
std::u16string to_u16string(T value) { std::u16string to_u16string(const T value) {
return GeneralUtils::ASCIIToUTF16(std::to_string(value)); return GeneralUtils::ASCIIToUTF16(std::to_string(value));
} }
@@ -246,7 +249,7 @@ namespace GeneralUtils {
\param max The maximum to generate to \param max The maximum to generate to
*/ */
template <typename T> template <typename T>
inline T GenerateRandomNumber(std::size_t min, std::size_t max) { inline T GenerateRandomNumber(const std::size_t min, const std::size_t max) {
// Make sure it is a numeric type // Make sure it is a numeric type
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type"); static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
@@ -273,10 +276,10 @@ namespace GeneralUtils {
// on Windows we need to undef these or else they conflict with our numeric limits calls // on Windows we need to undef these or else they conflict with our numeric limits calls
// DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS // DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS
#ifdef _WIN32 #ifdef _WIN32
#undef min #undef min
#undef max #undef max
#endif #endif
template <typename T> template <typename T>
inline T GenerateRandomNumber() { inline T GenerateRandomNumber() {

View File

@@ -25,6 +25,7 @@
#include "CDScriptComponentTable.h" #include "CDScriptComponentTable.h"
#include "CDSkillBehaviorTable.h" #include "CDSkillBehaviorTable.h"
#include "CDZoneTableTable.h" #include "CDZoneTableTable.h"
#include "CDTamingBuildPuzzleTable.h"
#include "CDVendorComponentTable.h" #include "CDVendorComponentTable.h"
#include "CDActivitiesTable.h" #include "CDActivitiesTable.h"
#include "CDPackageComponentTable.h" #include "CDPackageComponentTable.h"
@@ -41,8 +42,6 @@
#include "CDRewardCodesTable.h" #include "CDRewardCodesTable.h"
#include "CDPetComponentTable.h" #include "CDPetComponentTable.h"
#include <exception>
#ifndef CDCLIENT_CACHE_ALL #ifndef CDCLIENT_CACHE_ALL
// Uncomment this to cache the full cdclient database into memory. This will make the server load faster, but will use more memory. // Uncomment this to cache the full cdclient database into memory. This will make the server load faster, but will use more memory.
// A vanilla CDClient takes about 46MB of memory + the regular world data. // A vanilla CDClient takes about 46MB of memory + the regular world data.
@@ -55,13 +54,6 @@
#define CDCLIENT_DONT_CACHE_TABLE(x) #define CDCLIENT_DONT_CACHE_TABLE(x)
#endif #endif
class CDClientConnectionException : public std::exception {
public:
virtual const char* what() const throw() {
return "CDClientDatabase is not connected!";
}
};
// Using a macro to reduce repetitive code and issues from copy and paste. // Using a macro to reduce repetitive code and issues from copy and paste.
// As a note, ## in a macro is used to concatenate two tokens together. // As a note, ## in a macro is used to concatenate two tokens together.
@@ -108,11 +100,14 @@ DEFINE_TABLE_STORAGE(CDRewardCodesTable);
DEFINE_TABLE_STORAGE(CDRewardsTable); DEFINE_TABLE_STORAGE(CDRewardsTable);
DEFINE_TABLE_STORAGE(CDScriptComponentTable); DEFINE_TABLE_STORAGE(CDScriptComponentTable);
DEFINE_TABLE_STORAGE(CDSkillBehaviorTable); DEFINE_TABLE_STORAGE(CDSkillBehaviorTable);
DEFINE_TABLE_STORAGE(CDTamingBuildPuzzleTable);
DEFINE_TABLE_STORAGE(CDVendorComponentTable); DEFINE_TABLE_STORAGE(CDVendorComponentTable);
DEFINE_TABLE_STORAGE(CDZoneTableTable); DEFINE_TABLE_STORAGE(CDZoneTableTable);
void CDClientManager::LoadValuesFromDatabase() { void CDClientManager::LoadValuesFromDatabase() {
if (!CDClientDatabase::isConnected) throw CDClientConnectionException(); if (!CDClientDatabase::isConnected) {
throw std::runtime_error{ "CDClientDatabase is not connected!" };
}
CDActivityRewardsTable::Instance().LoadValuesFromDatabase(); CDActivityRewardsTable::Instance().LoadValuesFromDatabase();
CDActivitiesTable::Instance().LoadValuesFromDatabase(); CDActivitiesTable::Instance().LoadValuesFromDatabase();
@@ -152,6 +147,7 @@ void CDClientManager::LoadValuesFromDatabase() {
CDRewardsTable::Instance().LoadValuesFromDatabase(); CDRewardsTable::Instance().LoadValuesFromDatabase();
CDScriptComponentTable::Instance().LoadValuesFromDatabase(); CDScriptComponentTable::Instance().LoadValuesFromDatabase();
CDSkillBehaviorTable::Instance().LoadValuesFromDatabase(); CDSkillBehaviorTable::Instance().LoadValuesFromDatabase();
CDTamingBuildPuzzleTable::Instance().LoadValuesFromDatabase();
CDVendorComponentTable::Instance().LoadValuesFromDatabase(); CDVendorComponentTable::Instance().LoadValuesFromDatabase();
CDZoneTableTable::Instance().LoadValuesFromDatabase(); CDZoneTableTable::Instance().LoadValuesFromDatabase();
} }

View File

@@ -50,7 +50,7 @@ void CDPetComponentTable::LoadValuesFromDatabase() {
} }
void CDPetComponentTable::LoadValuesFromDefaults() { void CDPetComponentTable::LoadValuesFromDefaults() {
GetEntriesMutable().insert(std::make_pair(defaultEntry.id, defaultEntry)); GetEntriesMutable().emplace(defaultEntry.id, defaultEntry);
} }
CDPetComponent& CDPetComponentTable::GetByID(const uint32_t componentID) { CDPetComponent& CDPetComponentTable::GetByID(const uint32_t componentID) {

View File

@@ -0,0 +1,35 @@
#include "CDTamingBuildPuzzleTable.h"
void CDTamingBuildPuzzleTable::LoadValuesFromDatabase() {
// First, get the size of the table
uint32_t size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM TamingBuildPuzzles");
while (!tableSize.eof()) {
size = tableSize.getIntField(0, 0);
tableSize.nextRow();
}
// Reserve the size
auto& entries = GetEntriesMutable();
entries.reserve(size);
// Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM TamingBuildPuzzles");
while (!tableData.eof()) {
const auto lot = static_cast<LOT>(tableData.getIntField("NPCLot", LOT_NULL));
entries.emplace(lot, CDTamingBuildPuzzle{
.puzzleModelLot = lot,
.validPieces{ tableData.getStringField("ValidPiecesLXF") },
.timeLimit = static_cast<float>(tableData.getFloatField("Timelimit", 30.0f)),
.numValidPieces = tableData.getIntField("NumValidPieces", 6),
.imaginationCost = tableData.getIntField("imagCostPerBuild", 10)
});
tableData.nextRow();
}
}
const CDTamingBuildPuzzle* CDTamingBuildPuzzleTable::GetByLOT(const LOT lot) const {
const auto& entries = GetEntries();
const auto itr = entries.find(lot);
return itr != entries.cend() ? &itr->second : nullptr;
}

View File

@@ -0,0 +1,60 @@
#pragma once
#include "CDTable.h"
/**
* Information for the minigame to be completed
*/
struct CDTamingBuildPuzzle {
UNUSED_COLUMN(uint32_t id = 0;)
// The LOT of the object that is to be created
LOT puzzleModelLot = LOT_NULL;
// The LOT of the NPC
UNUSED_COLUMN(LOT npcLot = LOT_NULL;)
// The .lxfml file that contains the bricks required to build the model
std::string validPieces{};
// The .lxfml file that contains the bricks NOT required to build the model
UNUSED_COLUMN(std::string invalidPieces{};)
// Difficulty value
UNUSED_COLUMN(int32_t difficulty = 1;)
// The time limit to complete the build
float timeLimit = 30.0f;
// The number of pieces required to complete the minigame
int32_t numValidPieces = 6;
// Number of valid pieces
UNUSED_COLUMN(int32_t totalNumPieces = 16;)
// Model name
UNUSED_COLUMN(std::string modelName{};)
// The .lxfml file that contains the full model
UNUSED_COLUMN(std::string fullModel{};)
// The duration of the pet taming minigame
UNUSED_COLUMN(float duration = 45.0f;)
// The imagination cost for the tamer to start the minigame
int32_t imaginationCost = 10;
};
class CDTamingBuildPuzzleTable : public CDTable<CDTamingBuildPuzzleTable, std::unordered_map<LOT, CDTamingBuildPuzzle>> {
public:
/**
* Load values from the CD client database
*/
void LoadValuesFromDatabase();
/**
* Gets the pet ability table corresponding to the pet LOT
* @returns A pointer to the corresponding table, or nullptr if one cannot be found
*/
[[nodiscard]]
const CDTamingBuildPuzzle* GetByLOT(const LOT lot) const;
};

View File

@@ -36,5 +36,6 @@ set(DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES "CDActivitiesTable.cpp"
"CDRewardsTable.cpp" "CDRewardsTable.cpp"
"CDScriptComponentTable.cpp" "CDScriptComponentTable.cpp"
"CDSkillBehaviorTable.cpp" "CDSkillBehaviorTable.cpp"
"CDTamingBuildPuzzleTable.cpp"
"CDVendorComponentTable.cpp" "CDVendorComponentTable.cpp"
"CDZoneTableTable.cpp" PARENT_SCOPE) "CDZoneTableTable.cpp" PARENT_SCOPE)

View File

@@ -254,7 +254,7 @@ void Entity::Initialize() {
} }
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MINI_GAME_CONTROL) > 0) { if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::MINI_GAME_CONTROL) > 0) {
AddComponent<MiniGameControlComponent>(m_TemplateID); AddComponent<MiniGameControlComponent>();
} }
uint32_t possessableComponentId = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::POSSESSABLE); uint32_t possessableComponentId = compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::POSSESSABLE);
@@ -666,7 +666,7 @@ void Entity::Initialize() {
// Shooting gallery component // Shooting gallery component
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SHOOTING_GALLERY) > 0) { if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::SHOOTING_GALLERY) > 0) {
AddComponent<ShootingGalleryComponent>(m_TemplateID); AddComponent<ShootingGalleryComponent>();
} }
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY, -1) != -1) { if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::PROPERTY, -1) != -1) {

View File

@@ -536,13 +536,13 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID
uint32_t FindCharShirtID(uint32_t shirtColor, uint32_t shirtStyle) { uint32_t FindCharShirtID(uint32_t shirtColor, uint32_t shirtStyle) {
try { try {
auto stmt = CDClientDatabase::CreatePreppedStmt( auto stmt = CDClientDatabase::CreatePreppedStmt(
"select obj.id from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ? AND icc.decal == ?" "select obj.id as objectId from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ? AND icc.decal == ?"
); );
stmt.bind(1, "character create shirt"); stmt.bind(1, "character create shirt");
stmt.bind(2, static_cast<int>(shirtColor)); stmt.bind(2, static_cast<int>(shirtColor));
stmt.bind(3, static_cast<int>(shirtStyle)); stmt.bind(3, static_cast<int>(shirtStyle));
auto tableData = stmt.execQuery(); auto tableData = stmt.execQuery();
auto shirtLOT = tableData.getIntField(0, 4069); auto shirtLOT = tableData.getIntField("objectId", 4069);
tableData.finalize(); tableData.finalize();
return shirtLOT; return shirtLOT;
} catch (const std::exception& ex) { } catch (const std::exception& ex) {
@@ -555,12 +555,12 @@ uint32_t FindCharShirtID(uint32_t shirtColor, uint32_t shirtStyle) {
uint32_t FindCharPantsID(uint32_t pantsColor) { uint32_t FindCharPantsID(uint32_t pantsColor) {
try { try {
auto stmt = CDClientDatabase::CreatePreppedStmt( auto stmt = CDClientDatabase::CreatePreppedStmt(
"select obj.id from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ?" "select obj.id as objectId from Objects as obj JOIN (select * from ComponentsRegistry as cr JOIN ItemComponent as ic on ic.id = cr.component_id where cr.component_type == 11) as icc on icc.id = obj.id where lower(obj._internalNotes) == ? AND icc.color1 == ?"
); );
stmt.bind(1, "cc pants"); stmt.bind(1, "cc pants");
stmt.bind(2, static_cast<int>(pantsColor)); stmt.bind(2, static_cast<int>(pantsColor));
auto tableData = stmt.execQuery(); auto tableData = stmt.execQuery();
auto pantsLOT = tableData.getIntField(0, 2508); auto pantsLOT = tableData.getIntField("objectId", 2508);
tableData.finalize(); tableData.finalize();
return pantsLOT; return pantsLOT;
} catch (const std::exception& ex) { } catch (const std::exception& ex) {

View File

@@ -377,10 +377,10 @@ void Behavior::PlayFx(std::u16string type, const LWOOBJID target, const LWOOBJID
return; return;
} }
const auto name = std::string(result.getStringField(0)); const auto name = std::string(result.getStringField("effectName"));
if (type.empty()) { if (type.empty()) {
const auto typeResult = result.getStringField(1); const auto typeResult = result.getStringField("effectType");
type = GeneralUtils::ASCIIToUTF16(typeResult); type = GeneralUtils::ASCIIToUTF16(typeResult);

View File

@@ -47,11 +47,11 @@ void SwitchMultipleBehavior::Load() {
auto result = query.execQuery(); auto result = query.execQuery();
while (!result.eof()) { while (!result.eof()) {
const auto behavior_id = static_cast<uint32_t>(result.getFloatField(1)); const auto behavior_id = static_cast<uint32_t>(result.getFloatField("behavior"));
auto* behavior = CreateBehavior(behavior_id); auto* behavior = CreateBehavior(behavior_id);
auto value = result.getFloatField(2); auto value = result.getFloatField("value");
this->m_behaviors.emplace_back(value, behavior); this->m_behaviors.emplace_back(value, behavior);

View File

@@ -45,20 +45,20 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
auto componentResult = componentQuery.execQuery(); auto componentResult = componentQuery.execQuery();
if (!componentResult.eof()) { if (!componentResult.eof()) {
if (!componentResult.fieldIsNull(0)) if (!componentResult.fieldIsNull("aggroRadius"))
m_AggroRadius = componentResult.getFloatField(0); m_AggroRadius = componentResult.getFloatField("aggroRadius");
if (!componentResult.fieldIsNull(1)) if (!componentResult.fieldIsNull("tetherSpeed"))
m_TetherSpeed = componentResult.getFloatField(1); m_TetherSpeed = componentResult.getFloatField("tetherSpeed");
if (!componentResult.fieldIsNull(2)) if (!componentResult.fieldIsNull("pursuitSpeed"))
m_PursuitSpeed = componentResult.getFloatField(2); m_PursuitSpeed = componentResult.getFloatField("pursuitSpeed");
if (!componentResult.fieldIsNull(3)) if (!componentResult.fieldIsNull("softTetherRadius"))
m_SoftTetherRadius = componentResult.getFloatField(3); m_SoftTetherRadius = componentResult.getFloatField("softTetherRadius");
if (!componentResult.fieldIsNull(4)) if (!componentResult.fieldIsNull("hardTetherRadius"))
m_HardTetherRadius = componentResult.getFloatField(4); m_HardTetherRadius = componentResult.getFloatField("hardTetherRadius");
} }
componentResult.finalize(); componentResult.finalize();
@@ -82,11 +82,11 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id):
auto result = skillQuery.execQuery(); auto result = skillQuery.execQuery();
while (!result.eof()) { while (!result.eof()) {
const auto skillId = static_cast<uint32_t>(result.getIntField(0)); const auto skillId = static_cast<uint32_t>(result.getIntField("skillID"));
const auto abilityCooldown = static_cast<float>(result.getFloatField(1)); const auto abilityCooldown = static_cast<float>(result.getFloatField("cooldown"));
const auto behaviorId = static_cast<uint32_t>(result.getIntField(2)); const auto behaviorId = static_cast<uint32_t>(result.getIntField("behaviorID"));
auto* behavior = Behavior::CreateBehavior(behaviorId); auto* behavior = Behavior::CreateBehavior(behaviorId);

View File

@@ -450,7 +450,7 @@ const std::vector<BuffParameter>& BuffComponent::GetBuffParameters(int32_t buffI
param.value = result.getFloatField("NumberValue"); param.value = result.getFloatField("NumberValue");
param.effectId = result.getIntField("EffectID"); param.effectId = result.getIntField("EffectID");
if (!result.fieldIsNull(3)) { if (!result.fieldIsNull("StringValue")) {
std::istringstream stream(result.getStringField("StringValue")); std::istringstream stream(result.getStringField("StringValue"));
std::string token; std::string token;

View File

@@ -47,6 +47,7 @@ set(DGAME_DCOMPONENTS_SOURCES
"TriggerComponent.cpp" "TriggerComponent.cpp"
"HavokVehiclePhysicsComponent.cpp" "HavokVehiclePhysicsComponent.cpp"
"VendorComponent.cpp" "VendorComponent.cpp"
"MiniGameControlComponent.cpp"
"ScriptComponent.cpp" "ScriptComponent.cpp"
) )

View File

@@ -389,9 +389,9 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
if (result.eof()) return; if (result.eof()) return;
if (result.fieldIsNull(0)) return; if (result.fieldIsNull("enemyList")) return;
const auto* list_string = result.getStringField(0); const auto* list_string = result.getStringField("enemyList");
std::stringstream ss(list_string); std::stringstream ss(list_string);
std::string token; std::string token;

View File

@@ -696,20 +696,22 @@ void InventoryComponent::UpdateXml(tinyxml2::XMLDocument& document) {
} }
void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool bIsInitialUpdate) { void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool bIsInitialUpdate) {
// LWOInventoryComponent_EquippedItem
outBitStream.Write(bIsInitialUpdate || m_Dirty);
if (bIsInitialUpdate || m_Dirty) { if (bIsInitialUpdate || m_Dirty) {
outBitStream.Write(true);
outBitStream.Write<uint32_t>(m_Equipped.size()); outBitStream.Write<uint32_t>(m_Equipped.size());
for (const auto& pair : m_Equipped) { for (const auto& pair : m_Equipped) {
const auto item = pair.second; const auto item = pair.second;
if (bIsInitialUpdate) AddItemSkills(item.lot); if (bIsInitialUpdate) {
AddItemSkills(item.lot);
}
outBitStream.Write(item.id); outBitStream.Write(item.id);
outBitStream.Write(item.lot); outBitStream.Write(item.lot);
outBitStream.Write0(); // subkey outBitStream.Write0();
outBitStream.Write(item.count > 0); outBitStream.Write(item.count > 0);
if (item.count > 0) outBitStream.Write(item.count); if (item.count > 0) outBitStream.Write(item.count);
@@ -717,7 +719,7 @@ void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool b
outBitStream.Write(item.slot != 0); outBitStream.Write(item.slot != 0);
if (item.slot != 0) outBitStream.Write<uint16_t>(item.slot); if (item.slot != 0) outBitStream.Write<uint16_t>(item.slot);
outBitStream.Write0(); // inventory type outBitStream.Write0();
bool flag = !item.config.empty(); bool flag = !item.config.empty();
outBitStream.Write(flag); outBitStream.Write(flag);
@@ -744,21 +746,11 @@ void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool b
} }
m_Dirty = false; m_Dirty = false;
} else {
outBitStream.Write(false);
} }
// EquippedModelTransform
outBitStream.Write(false); outBitStream.Write(false);
/*
outBitStream.Write(bIsInitialUpdate || m_Dirty); // Same dirty or different?
if (bIsInitialUpdate || m_Dirty) {
outBitStream.Write<uint32_t>(m_Equipped.size()); // Equiped models?
for (const auto& [location, item] : m_Equipped) {
outBitStream.Write(item.id);
outBitStream.Write(item.pos);
outBitStream.Write(item.rot);
}
}
*/
} }
void InventoryComponent::Update(float deltaTime) { void InventoryComponent::Update(float deltaTime) {
@@ -1102,7 +1094,7 @@ void InventoryComponent::CheckItemSet(const LOT lot) {
auto result = query.execQuery(); auto result = query.execQuery();
while (!result.eof()) { while (!result.eof()) {
const auto id = result.getIntField(0); const auto id = result.getIntField("setID");
bool found = false; bool found = false;

View File

@@ -2,16 +2,4 @@
void ItemComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) { void ItemComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) {
outBitStream.Write0(); outBitStream.Write0();
/*
outBitStream.Write(isConstruction || m_Dirty); // Same dirty or different?
if (isConstruction || m_Dirty) {
outBitStream.Write(m_parent->GetObjectID());
outBitStream.Write(moderationStatus);
outBitStream.Write(!description.empty());
if (!description.empty()) {
outBitStream.Write<uint32_t>(description.size());
outBitStream.Write(description) // u16string
}
}
*/
} }

View File

@@ -0,0 +1,5 @@
#include "MiniGameControlComponent.h"
void MiniGameControlComponent::Serialize(RakNet::BitStream& outBitStream, bool isConstruction) {
outBitStream.Write<uint32_t>(0x40000000);
}

View File

@@ -1,13 +1,15 @@
#ifndef __MINIGAMECONTROLCOMPONENT__H__ #ifndef __MINIGAMECONTROLCOMPONENT__H__
#define __MINIGAMECONTROLCOMPONENT__H__ #define __MINIGAMECONTROLCOMPONENT__H__
#include "ActivityComponent.h" #include "Component.h"
#include "eReplicaComponentType.h" #include "eReplicaComponentType.h"
class MiniGameControlComponent final : public ActivityComponent { class MiniGameControlComponent final : public Component {
public: public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MINI_GAME_CONTROL; static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::MINI_GAME_CONTROL;
MiniGameControlComponent(Entity* parent, LOT lot) : ActivityComponent(parent, lot) {}
MiniGameControlComponent(Entity* parent) : Component(parent) {}
void Serialize(RakNet::BitStream& outBitStream, bool isConstruction);
}; };
#endif //!__MINIGAMECONTROLCOMPONENT__H__ #endif //!__MINIGAMECONTROLCOMPONENT__H__

View File

@@ -466,8 +466,8 @@ bool MissionComponent::RequiresItem(const LOT lot) {
return false; return false;
} }
if (!result.fieldIsNull(0)) { if (!result.fieldIsNull("type")) {
const auto type = std::string(result.getStringField(0)); const auto type = std::string(result.getStringField("type"));
result.finalize(); result.finalize();

View File

@@ -2,6 +2,7 @@
#include "GameMessages.h" #include "GameMessages.h"
#include "BrickDatabase.h" #include "BrickDatabase.h"
#include "CDClientDatabase.h" #include "CDClientDatabase.h"
#include "CDTamingBuildPuzzleTable.h"
#include "ChatPackets.h" #include "ChatPackets.h"
#include "EntityManager.h" #include "EntityManager.h"
#include "Character.h" #include "Character.h"
@@ -32,7 +33,6 @@
#include "eMissionState.h" #include "eMissionState.h"
#include "dNavMesh.h" #include "dNavMesh.h"
std::unordered_map<LOT, PetComponent::PetPuzzleData> PetComponent::buildCache{};
std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::currentActivities{}; std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::currentActivities{};
std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{}; std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{};
@@ -40,7 +40,7 @@ std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{};
* Maps all the pet lots to a flag indicating that the player has caught it. All basic pets have been guessed by ObjID * Maps all the pet lots to a flag indicating that the player has caught it. All basic pets have been guessed by ObjID
* while the faction ones could be checked using their respective missions. * while the faction ones could be checked using their respective missions.
*/ */
std::map<LOT, int32_t> PetComponent::petFlags = { const std::map<LOT, int32_t> PetComponent::petFlags{
{ 3050, 801 }, // Elephant { 3050, 801 }, // Elephant
{ 3054, 803 }, // Cat { 3054, 803 }, // Cat
{ 3195, 806 }, // Triceratops { 3195, 806 }, // Triceratops
@@ -87,7 +87,6 @@ PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Compone
m_StartPosition = NiPoint3Constant::ZERO; m_StartPosition = NiPoint3Constant::ZERO;
m_MovementAI = nullptr; m_MovementAI = nullptr;
m_TresureTime = 0; m_TresureTime = 0;
m_Preconditions = nullptr;
std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(parentEntity->GetVar<std::u16string>(u"CheckPrecondition")); std::string checkPreconditions = GeneralUtils::UTF16ToWTF8(parentEntity->GetVar<std::u16string>(u"CheckPrecondition"));
@@ -152,96 +151,53 @@ void PetComponent::OnUse(Entity* originator) {
m_Tamer = LWOOBJID_EMPTY; m_Tamer = LWOOBJID_EMPTY;
} }
auto* inventoryComponent = originator->GetComponent<InventoryComponent>(); auto* const inventoryComponent = originator->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) { if (inventoryComponent == nullptr) {
return; return;
} }
if (m_Preconditions != nullptr && !m_Preconditions->Check(originator, true)) { if (m_Preconditions.has_value() && !m_Preconditions->Check(originator, true)) {
return; return;
} }
auto* movementAIComponent = m_Parent->GetComponent<MovementAIComponent>(); auto* const movementAIComponent = m_Parent->GetComponent<MovementAIComponent>();
if (movementAIComponent != nullptr) { if (movementAIComponent != nullptr) {
movementAIComponent->Stop(); movementAIComponent->Stop();
} }
inventoryComponent->DespawnPet(); inventoryComponent->DespawnPet();
const auto& cached = buildCache.find(m_Parent->GetLOT()); const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT());
int32_t imaginationCost = 0; if (!entry) {
ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet.");
std::string buildFile; return;
if (cached == buildCache.end()) {
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT ValidPiecesLXF, PuzzleModelLot, Timelimit, NumValidPieces, imagCostPerBuild FROM TamingBuildPuzzles WHERE NPCLot = ?;");
query.bind(1, static_cast<int>(m_Parent->GetLOT()));
auto result = query.execQuery();
if (result.eof()) {
ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to find the puzzle minigame for this pet.");
return;
}
if (result.fieldIsNull(0)) {
result.finalize();
return;
}
buildFile = std::string(result.getStringField(0));
PetPuzzleData data;
data.buildFile = buildFile;
data.puzzleModelLot = result.getIntField(1);
data.timeLimit = result.getFloatField(2);
data.numValidPieces = result.getIntField(3);
data.imaginationCost = result.getIntField(4);
if (data.timeLimit <= 0) data.timeLimit = 60;
imaginationCost = data.imaginationCost;
buildCache[m_Parent->GetLOT()] = data;
result.finalize();
} else {
buildFile = cached->second.buildFile;
imaginationCost = cached->second.imaginationCost;
} }
auto* destroyableComponent = originator->GetComponent<DestroyableComponent>(); const auto* const destroyableComponent = originator->GetComponent<DestroyableComponent>();
if (destroyableComponent == nullptr) { if (destroyableComponent == nullptr) {
return; return;
} }
auto imagination = destroyableComponent->GetImagination(); const auto imagination = destroyableComponent->GetImagination();
if (imagination < entry->imaginationCost) {
if (imagination < imaginationCost) {
return; return;
} }
const auto& bricks = BrickDatabase::GetBricks(buildFile); const auto& bricks = BrickDatabase::GetBricks(entry->validPieces);
if (bricks.empty()) { if (bricks.empty()) {
ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to load the puzzle minigame for this pet."); ChatPackets::SendSystemMessage(originator->GetSystemAddress(), u"Failed to load the puzzle minigame for this pet.");
LOG("Couldn't find %s for minigame!", buildFile.c_str()); LOG("Couldn't find %s for minigame!", entry->validPieces.c_str());
return; return;
} }
auto petPosition = m_Parent->GetPosition(); const auto petPosition = m_Parent->GetPosition();
auto originatorPosition = originator->GetPosition(); const auto originatorPosition = originator->GetPosition();
m_Parent->SetRotation(NiQuaternion::LookAt(petPosition, originatorPosition)); m_Parent->SetRotation(NiQuaternion::LookAt(petPosition, originatorPosition));
float interactionDistance = m_Parent->GetVar<float>(u"interaction_distance"); float interactionDistance = m_Parent->GetVar<float>(u"interaction_distance");
if (interactionDistance <= 0) { if (interactionDistance <= 0) {
interactionDistance = 15; interactionDistance = 15;
} }
@@ -477,9 +433,8 @@ void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) {
return; return;
} }
const auto& cached = buildCache.find(m_Parent->GetLOT()); const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT());
if (!entry) return;
if (cached == buildCache.end()) return;
auto* destroyableComponent = tamer->GetComponent<DestroyableComponent>(); auto* destroyableComponent = tamer->GetComponent<DestroyableComponent>();
@@ -487,14 +442,14 @@ void PetComponent::TryBuild(uint32_t numBricks, bool clientFailed) {
auto imagination = destroyableComponent->GetImagination(); auto imagination = destroyableComponent->GetImagination();
imagination -= cached->second.imaginationCost; imagination -= entry->imaginationCost;
destroyableComponent->SetImagination(imagination); destroyableComponent->SetImagination(imagination);
Game::entityManager->SerializeEntity(tamer); Game::entityManager->SerializeEntity(tamer);
if (clientFailed) { if (clientFailed) {
if (imagination < cached->second.imaginationCost) { if (imagination < entry->imaginationCost) {
ClientFailTamingMinigame(); ClientFailTamingMinigame();
} }
} else { } else {
@@ -517,17 +472,14 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
return; return;
} }
const auto& cached = buildCache.find(m_Parent->GetLOT()); const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT());
if (!entry) return;
if (cached == buildCache.end()) {
return;
}
GameMessages::SendPlayFXEffect(tamer, -1, u"petceleb", "", LWOOBJID_EMPTY, 1, 1, true); GameMessages::SendPlayFXEffect(tamer, -1, u"petceleb", "", LWOOBJID_EMPTY, 1, 1, true);
RenderComponent::PlayAnimation(tamer, u"rebuild-celebrate"); RenderComponent::PlayAnimation(tamer, u"rebuild-celebrate");
EntityInfo info{}; EntityInfo info{};
info.lot = cached->second.puzzleModelLot; info.lot = entry->puzzleModelLot;
info.pos = position; info.pos = position;
info.rot = NiQuaternionConstant::IDENTITY; info.rot = NiQuaternionConstant::IDENTITY;
info.spawnerID = tamer->GetObjectID(); info.spawnerID = tamer->GetObjectID();
@@ -731,13 +683,10 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) {
} }
void PetComponent::StartTimer() { void PetComponent::StartTimer() {
const auto& cached = buildCache.find(m_Parent->GetLOT()); const auto* const entry = CDClientManager::GetTable<CDTamingBuildPuzzleTable>()->GetByLOT(m_Parent->GetLOT());
if (!entry) return;
if (cached == buildCache.end()) { m_Timer = entry->timeLimit;
return;
}
m_Timer = cached->second.timeLimit;
} }
void PetComponent::ClientFailTamingMinigame() { void PetComponent::ClientFailTamingMinigame() {
@@ -1086,6 +1035,6 @@ void PetComponent::LoadPetNameFromModeration() {
} }
} }
void PetComponent::SetPreconditions(std::string& preconditions) { void PetComponent::SetPreconditions(const std::string& preconditions) {
m_Preconditions = new PreconditionExpression(preconditions); m_Preconditions = std::make_optional<PreconditionExpression>(preconditions);
} }

View File

@@ -165,7 +165,7 @@ public:
* Sets preconditions for the pet that need to be met before it can be tamed * Sets preconditions for the pet that need to be met before it can be tamed
* @param conditions the preconditions to set * @param conditions the preconditions to set
*/ */
void SetPreconditions(std::string& conditions); void SetPreconditions(const std::string& conditions);
/** /**
* Returns the entity that this component belongs to * Returns the entity that this component belongs to
@@ -250,15 +250,10 @@ private:
*/ */
static std::unordered_map<LWOOBJID, LWOOBJID> currentActivities; static std::unordered_map<LWOOBJID, LWOOBJID> currentActivities;
/**
* Cache of all the minigames and their information from the database
*/
static std::unordered_map<LOT, PetComponent::PetPuzzleData> buildCache;
/** /**
* Flags that indicate that a player has tamed a pet, indexed by the LOT of the pet * Flags that indicate that a player has tamed a pet, indexed by the LOT of the pet
*/ */
static std::map<LOT, int32_t> petFlags; static const std::map<LOT, int32_t> petFlags;
/** /**
* The ID of the component in the pet component table * The ID of the component in the pet component table
@@ -349,7 +344,7 @@ private:
/** /**
* Preconditions that need to be met before an entity can tame this pet * Preconditions that need to be met before an entity can tame this pet
*/ */
PreconditionExpression* m_Preconditions; std::optional<PreconditionExpression> m_Preconditions{};
/** /**
* Pet information loaded from the CDClientDatabase * Pet information loaded from the CDClientDatabase

View File

@@ -18,8 +18,8 @@ PossessableComponent::PossessableComponent(Entity* parent, uint32_t componentId)
// Should a result not exist for this default to attached visible // Should a result not exist for this default to attached visible
if (!result.eof()) { if (!result.eof()) {
m_PossessionType = static_cast<ePossessionType>(result.getIntField(0, 1)); // Default to Attached Visible m_PossessionType = static_cast<ePossessionType>(result.getIntField("possessionType", 1)); // Default to Attached Visible
m_DepossessOnHit = static_cast<bool>(result.getIntField(1, 0)); m_DepossessOnHit = static_cast<bool>(result.getIntField("depossessOnHit", 0));
} else { } else {
m_PossessionType = ePossessionType::ATTACHED_VISIBLE; m_PossessionType = ePossessionType::ATTACHED_VISIBLE;
m_DepossessOnHit = false; m_DepossessOnHit = false;

View File

@@ -49,11 +49,11 @@ PropertyManagementComponent::PropertyManagementComponent(Entity* parent) : Compo
auto result = query.execQuery(); auto result = query.execQuery();
if (result.eof() || result.fieldIsNull(0)) { if (result.eof() || result.fieldIsNull("id")) {
return; return;
} }
templateId = result.getIntField(0); templateId = result.getIntField("id");
auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId); auto propertyInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId);
@@ -105,7 +105,7 @@ std::vector<NiPoint3> PropertyManagementComponent::GetPaths() const {
std::vector<float> points; std::vector<float> points;
std::istringstream stream(result.getStringField(0)); std::istringstream stream(result.getStringField("path"));
std::string token; std::string token;
while (std::getline(stream, token, ' ')) { while (std::getline(stream, token, ' ')) {

View File

@@ -117,7 +117,7 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e
auto result = query.execQuery(); auto result = query.execQuery();
if (result.eof() || result.fieldIsNull(0)) { if (result.eof() || result.fieldIsNull("animation_length")) {
result.finalize(); result.finalize();
m_DurationCache[effectId] = 0; m_DurationCache[effectId] = 0;
@@ -127,7 +127,7 @@ void RenderComponent::PlayEffect(const int32_t effectId, const std::u16string& e
return; return;
} }
effect.time = static_cast<float>(result.getFloatField(0)); effect.time = static_cast<float>(result.getFloatField("animation_length"));
result.finalize(); result.finalize();

View File

@@ -27,12 +27,12 @@ RocketLaunchpadControlComponent::RocketLaunchpadControlComponent(Entity* parent,
auto result = query.execQuery(); auto result = query.execQuery();
if (!result.eof() && !result.fieldIsNull(0)) { if (!result.eof() && !result.fieldIsNull("targetZone")) {
m_TargetZone = result.getIntField(0); m_TargetZone = result.getIntField("targetZone");
m_DefaultZone = result.getIntField(1); m_DefaultZone = result.getIntField("defaultZoneID");
m_TargetScene = result.getStringField(2); m_TargetScene = result.getStringField("targetScene");
m_AltPrecondition = new PreconditionExpression(result.getStringField(3)); m_AltPrecondition = new PreconditionExpression(result.getStringField("altLandingPrecondition"));
m_AltLandingScene = result.getStringField(4); m_AltLandingScene = result.getStringField("altLandingSpawnPointName");
} }
result.finalize(); result.finalize();

View File

@@ -2,6 +2,11 @@
#include "EntityManager.h" #include "EntityManager.h"
#include "ScriptedActivityComponent.h" #include "ScriptedActivityComponent.h"
ShootingGalleryComponent::ShootingGalleryComponent(Entity* parent) : Component(parent) {
}
ShootingGalleryComponent::~ShootingGalleryComponent() = default;
void ShootingGalleryComponent::SetStaticParams(const StaticShootingGalleryParams& params) { void ShootingGalleryComponent::SetStaticParams(const StaticShootingGalleryParams& params) {
m_StaticParams = params; m_StaticParams = params;
} }
@@ -12,15 +17,20 @@ void ShootingGalleryComponent::SetDynamicParams(const DynamicShootingGalleryPara
Game::entityManager->SerializeEntity(m_Parent); Game::entityManager->SerializeEntity(m_Parent);
} }
void ShootingGalleryComponent::SetCurrentPlayerID(LWOOBJID playerID) {
m_CurrentPlayerID = playerID;
m_Dirty = true;
AddActivityPlayerData(playerID);
};
void ShootingGalleryComponent::Serialize(RakNet::BitStream& outBitStream, bool isInitialUpdate) { void ShootingGalleryComponent::Serialize(RakNet::BitStream& outBitStream, bool isInitialUpdate) {
ActivityComponent::Serialize(outBitStream, isInitialUpdate); // Start ScriptedActivityComponent
outBitStream.Write<bool>(true);
if (m_CurrentPlayerID == LWOOBJID_EMPTY) {
outBitStream.Write<uint32_t>(0);
} else {
outBitStream.Write<uint32_t>(1);
outBitStream.Write<LWOOBJID>(m_CurrentPlayerID);
for (size_t i = 0; i < 10; i++) {
outBitStream.Write<float_t>(0.0f);
}
}
// End ScriptedActivityComponent
if (isInitialUpdate) { if (isInitialUpdate) {
outBitStream.Write<float_t>(m_StaticParams.cameraPosition.GetX()); outBitStream.Write<float_t>(m_StaticParams.cameraPosition.GetX());

View File

@@ -2,7 +2,7 @@
#include "dCommonVars.h" #include "dCommonVars.h"
#include "NiPoint3.h" #include "NiPoint3.h"
#include "Entity.h" #include "Entity.h"
#include "ActivityComponent.h" #include "Component.h"
#include "eReplicaComponentType.h" #include "eReplicaComponentType.h"
/** /**
@@ -71,11 +71,12 @@ struct StaticShootingGalleryParams {
* A very ancient component that was used to guide shooting galleries, it's still kind of used but a lot of logic is * A very ancient component that was used to guide shooting galleries, it's still kind of used but a lot of logic is
* also in the related scripts. * also in the related scripts.
*/ */
class ShootingGalleryComponent final : public ActivityComponent { class ShootingGalleryComponent final : public Component {
public: public:
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SHOOTING_GALLERY; static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::SHOOTING_GALLERY;
explicit ShootingGalleryComponent(Entity* parent, LOT lot) : ActivityComponent(parent, lot) {} explicit ShootingGalleryComponent(Entity* parent);
~ShootingGalleryComponent();
void Serialize(RakNet::BitStream& outBitStream, bool isInitialUpdate) override; void Serialize(RakNet::BitStream& outBitStream, bool isInitialUpdate) override;
/** /**
@@ -106,8 +107,13 @@ public:
* Sets the entity that's currently playing the shooting gallery * Sets the entity that's currently playing the shooting gallery
* @param playerID the entity to set * @param playerID the entity to set
*/ */
void SetCurrentPlayerID(LWOOBJID playerID); void SetCurrentPlayerID(LWOOBJID playerID) { m_CurrentPlayerID = playerID; m_Dirty = true; };
/**
* Returns the player that's currently playing the shooting gallery
* @return the player that's currently playing the shooting gallery
*/
LWOOBJID GetCurrentPlayerID() const { return m_CurrentPlayerID; };
private: private:
/** /**

View File

@@ -99,7 +99,7 @@ void SkillComponent::SyncPlayerProjectile(const LWOOBJID projectileId, RakNet::B
return; return;
} }
const auto behavior_id = static_cast<uint32_t>(result.getIntField(0)); const auto behavior_id = static_cast<uint32_t>(result.getIntField("behaviorID"));
result.finalize(); result.finalize();
@@ -425,7 +425,7 @@ void SkillComponent::SyncProjectileCalculation(const ProjectileSyncEntry& entry)
return; return;
} }
const auto behaviorId = static_cast<uint32_t>(result.getIntField(0)); const auto behaviorId = static_cast<uint32_t>(result.getIntField("behaviorID"));
result.finalize(); result.finalize();
@@ -486,32 +486,6 @@ SkillComponent::~SkillComponent() {
void SkillComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { void SkillComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
if (bIsInitialUpdate) outBitStream.Write0(); if (bIsInitialUpdate) outBitStream.Write0();
/*
outBitStream.Write(bIsInitialUpdate && !m_managedBehaviors.empty());
if (bIsInitialUpdate && !m_managedBehaviors.empty()) {
outBitStream.Write<uint32_t>(m_managedBehaviors.size());
for (const auto& [id, skill] : m_managedBehaviors) {
outBitStream.Write(skill.skillUID);
outBitStream.Write(skill.skillID);
outBitStream.Write(skill.cast_type);
outBitStream.Write(skill.cancel_type);
outBitStream.Write(skill.behavior_count);
for (auto& index : skill.behavior_count) {
outBitStream.Write<uint32_t>(behaviorUID); // Maybe
outBitStream.Write<uint32_t>(action);
outBitStream.Write<uint32_t>(wait_time_ms);
outBitStream.Write<uint32_t>(template_id);
outBitStream.Write(casterObjId);
outBitStream.Write(originatorObjId);
outBitStream.Write(targetObjId);
outBitStream.Write<bool>(usedMouse);
outBitStream.Write(cooldown);
outBitStream.Write(charge_time);
outBitStream.Write(imagination_cost);
}
}
}
*/
} }
/// <summary> /// <summary>

View File

@@ -405,18 +405,18 @@ void Item::DisassembleModel(uint32_t numToDismantle) {
auto result = query.execQuery(); auto result = query.execQuery();
if (result.eof() || result.fieldIsNull(0)) { if (result.eof() || result.fieldIsNull("render_asset")) {
return; return;
} }
std::string renderAsset = std::string(result.getStringField(0)); std::string renderAsset = std::string(result.getStringField("render_asset"));
// normalize path slashes // normalize path slashes
for (auto& c : renderAsset) { for (auto& c : renderAsset) {
if (c == '\\') c = '/'; if (c == '\\') c = '/';
} }
std::string lxfmlFolderName = std::string(result.getStringField(1)); std::string lxfmlFolderName = std::string(result.getStringField("LXFMLFolder"));
if (!lxfmlFolderName.empty()) lxfmlFolderName.insert(0, "/"); if (!lxfmlFolderName.empty()) lxfmlFolderName.insert(0, "/");
std::vector<std::string> renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/'); std::vector<std::string> renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/');

View File

@@ -8,10 +8,13 @@
#include "MissionComponent.h" #include "MissionComponent.h"
#include "eMissionTaskType.h" #include "eMissionTaskType.h"
#include <algorithm> #include <algorithm>
#include <array>
#include "CDSkillBehaviorTable.h" #include "CDSkillBehaviorTable.h"
ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) { ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) {
using namespace std::string_view_literals;
this->m_ID = id; this->m_ID = id;
this->m_InventoryComponent = inventoryComponent; this->m_InventoryComponent = inventoryComponent;
@@ -27,14 +30,16 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) {
return; return;
} }
for (auto i = 0; i < 5; ++i) { constexpr std::array rowNames = { "skillSetWith2"sv, "skillSetWith3"sv, "skillSetWith4"sv, "skillSetWith5"sv, "skillSetWith6"sv };
if (result.fieldIsNull(i)) { for (auto i = 0; i < rowNames.size(); ++i) {
const auto rowName = rowNames[i];
if (result.fieldIsNull(rowName.data())) {
continue; continue;
} }
auto skillQuery = CDClientDatabase::CreatePreppedStmt( auto skillQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT SkillID FROM ItemSetSkills WHERE SkillSetID = ?;"); "SELECT SkillID FROM ItemSetSkills WHERE SkillSetID = ?;");
skillQuery.bind(1, result.getIntField(i)); skillQuery.bind(1, result.getIntField(rowName.data()));
auto skillResult = skillQuery.execQuery(); auto skillResult = skillQuery.execQuery();
@@ -43,13 +48,13 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) {
} }
while (!skillResult.eof()) { while (!skillResult.eof()) {
if (skillResult.fieldIsNull(0)) { if (skillResult.fieldIsNull("SkillID")) {
skillResult.nextRow(); skillResult.nextRow();
continue; continue;
} }
const auto skillId = skillResult.getIntField(0); const auto skillId = skillResult.getIntField("SkillID");
switch (i) { switch (i) {
case 0: case 0:
@@ -75,7 +80,7 @@ ItemSet::ItemSet(const uint32_t id, InventoryComponent* inventoryComponent) {
} }
} }
std::string ids = result.getStringField(5); std::string ids = result.getStringField("itemIDs");
ids.erase(std::remove_if(ids.begin(), ids.end(), ::isspace), ids.end()); ids.erase(std::remove_if(ids.begin(), ids.end(), ::isspace), ids.end());

View File

@@ -33,10 +33,10 @@ Precondition::Precondition(const uint32_t condition) {
return; return;
} }
this->type = static_cast<PreconditionType>(result.fieldIsNull(0) ? 0 : result.getIntField(0)); this->type = static_cast<PreconditionType>(result.fieldIsNull("type") ? 0 : result.getIntField("type"));
if (!result.fieldIsNull(1)) { if (!result.fieldIsNull("targetLOT")) {
std::istringstream stream(result.getStringField(1)); std::istringstream stream(result.getStringField("targetLOT"));
std::string token; std::string token;
while (std::getline(stream, token, ',')) { while (std::getline(stream, token, ',')) {
@@ -45,7 +45,7 @@ Precondition::Precondition(const uint32_t condition) {
} }
} }
this->count = result.fieldIsNull(2) ? 1 : result.getIntField(2); this->count = result.fieldIsNull("targetCount") ? 1 : result.getIntField("targetCount");
result.finalize(); result.finalize();
} }

View File

@@ -878,8 +878,26 @@ void SlashCommandHandler::Startup() {
}; };
RegisterCommand(TitleCommand); RegisterCommand(TitleCommand);
Command ShowAllCommand{
.help = "Show all online players across World Servers",
.info = "Usage: /showall (displayZoneData: Default 1) (displayIndividualPlayers: Default 1)",
.aliases = { "showall" },
.handle = GMGreaterThanZeroCommands::ShowAll,
.requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR
};
RegisterCommand(ShowAllCommand);
Command FindPlayerCommand{
.help = "Find the World Server a player is in if they are online",
.info = "Find the World Server a player is in if they are online",
.aliases = { "findplayer" },
.handle = GMGreaterThanZeroCommands::FindPlayer,
.requiredLevel = eGameMasterLevel::JUNIOR_MODERATOR
};
RegisterCommand(FindPlayerCommand);
// Register GM Zero Commands // Register GM Zero Commands
Command HelpCommand{ Command HelpCommand{
.help = "Display command info", .help = "Display command info",
.info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short desctiptions.", .info = "If a command is given, display detailed info on that command. Otherwise display a list of commands with short desctiptions.",

View File

@@ -704,7 +704,7 @@ namespace DEVGMCommands {
auto tables = query.execQuery(); auto tables = query.execQuery();
while (!tables.eof()) { while (!tables.eof()) {
std::string message = std::to_string(tables.getIntField(0)) + " - " + tables.getStringField(1); std::string message = std::to_string(tables.getIntField("id")) + " - " + tables.getStringField("name");
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(message, message.size())); ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(message, message.size()));
tables.nextRow(); tables.nextRow();
} }

View File

@@ -70,4 +70,4 @@ namespace DEVGMCommands {
void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args); void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args); void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args); void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args);
} }

View File

@@ -287,4 +287,39 @@ namespace GMGreaterThanZeroCommands {
std::string name = entity->GetCharacter()->GetName() + " - " + args; std::string name = entity->GetCharacter()->GetName() + " - " + args;
GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS); GameMessages::SendSetName(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(name), UNASSIGNED_SYSTEM_ADDRESS);
} }
void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
bool displayZoneData = true;
bool displayIndividualPlayers = true;
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (!splitArgs.empty() && !splitArgs.at(0).empty()) displayZoneData = splitArgs.at(0) == "1";
if (splitArgs.size() > 1) displayIndividualPlayers = splitArgs.at(1) == "1";
ShowAllRequest request {
.requestor = entity->GetObjectID(),
.displayZoneData = displayZoneData,
.displayIndividualPlayers = displayIndividualPlayers
};
CBITSTREAM;
request.Serialize(bitStream);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (args.empty()) {
GameMessages::SendSlashCommandFeedbackText(entity, u"No player Given");
return;
}
FindPlayerRequest request {
.requestor = entity->GetObjectID(),
.playerName = LUWString(args)
};
CBITSTREAM;
request.Serialize(bitStream);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
} }

View File

@@ -10,4 +10,6 @@ namespace GMGreaterThanZeroCommands {
void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args); void GmInvis(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args); void SetName(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args); void Title(Entity* entity, const SystemAddress& sysAddr, const std::string args);
} void ShowAll(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void FindPlayer(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}

View File

@@ -12,6 +12,30 @@
#include "eConnectionType.h" #include "eConnectionType.h"
#include "eChatMessageType.h" #include "eChatMessageType.h"
void ShowAllRequest::Serialize(RakNet::BitStream& bitStream) {
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::SHOW_ALL);
bitStream.Write(this->requestor);
bitStream.Write(this->displayZoneData);
bitStream.Write(this->displayIndividualPlayers);
}
void ShowAllRequest::Deserialize(RakNet::BitStream& inStream) {
inStream.Read(this->requestor);
inStream.Read(this->displayZoneData);
inStream.Read(this->displayIndividualPlayers);
}
void FindPlayerRequest::Serialize(RakNet::BitStream& bitStream) {
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::WHO);
bitStream.Write(this->requestor);
bitStream.Write(this->playerName);
}
void FindPlayerRequest::Deserialize(RakNet::BitStream& inStream) {
inStream.Read(this->requestor);
inStream.Read(this->playerName);
}
void ChatPackets::SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message) { void ChatPackets::SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message) {
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GENERAL_CHAT_MESSAGE); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::GENERAL_CHAT_MESSAGE);

View File

@@ -11,6 +11,21 @@ struct SystemAddress;
#include <string> #include <string>
#include "dCommonVars.h" #include "dCommonVars.h"
struct ShowAllRequest{
LWOOBJID requestor = LWOOBJID_EMPTY;
bool displayZoneData = true;
bool displayIndividualPlayers = true;
void Serialize(RakNet::BitStream& bitStream);
void Deserialize(RakNet::BitStream& inStream);
};
struct FindPlayerRequest{
LWOOBJID requestor = LWOOBJID_EMPTY;
LUWString playerName;
void Serialize(RakNet::BitStream& bitStream);
void Deserialize(RakNet::BitStream& inStream);
};
namespace ChatPackets { namespace ChatPackets {
void SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message); void SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message);
void SendSystemMessage(const SystemAddress& sysAddr, const std::u16string& message, bool broadcast = false); void SendSystemMessage(const SystemAddress& sysAddr, const std::u16string& message, bool broadcast = false);

View File

@@ -12,6 +12,15 @@
#include <iostream> #include <iostream>
void HTTPMonitorInfo::Serialize(RakNet::BitStream &bitStream) const {
bitStream.Write(port);
bitStream.Write<uint8_t>(openWeb);
bitStream.Write<uint8_t>(supportsSum);
bitStream.Write<uint8_t>(supportsDetail);
bitStream.Write<uint8_t>(supportsWho);
bitStream.Write<uint8_t>(supportsObjects);
}
void WorldPackets::SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum, LWOZONEID zone) { void WorldPackets::SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum, LWOZONEID zone) {
RakNet::BitStream bitStream; RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::LOAD_STATIC_ZONE); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::LOAD_STATIC_ZONE);
@@ -160,3 +169,18 @@ void WorldPackets::SendGMLevelChange(const SystemAddress& sysAddr, bool success,
SEND_PACKET; SEND_PACKET;
} }
void WorldPackets::SendHTTPMonitorInfo(const SystemAddress& sysAddr, const HTTPMonitorInfo& info) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::HTTP_MONITOR_INFO_RESPONSE);
info.Serialize(bitStream);
SEND_PACKET;
}
void WorldPackets::SendDebugOuput(const SystemAddress& sysAddr, const std::string& data){
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::DEBUG_OUTPUT);
bitStream.Write<uint32_t>(data.size());
bitStream.Write(data);
SEND_PACKET;
}

View File

@@ -10,6 +10,19 @@ struct SystemAddress;
enum class eGameMasterLevel : uint8_t; enum class eGameMasterLevel : uint8_t;
enum class eCharacterCreationResponse : uint8_t; enum class eCharacterCreationResponse : uint8_t;
enum class eRenameResponse : uint8_t; enum class eRenameResponse : uint8_t;
namespace RakNet {
class BitStream;
};
struct HTTPMonitorInfo {
uint16_t port = 80;
bool openWeb = false;
bool supportsSum = false;
bool supportsDetail = false;
bool supportsWho = false;
bool supportsObjects = false;
void Serialize(RakNet::BitStream &bitstream) const;
};
namespace WorldPackets { namespace WorldPackets {
void SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum, LWOZONEID zone); void SendLoadStaticZone(const SystemAddress& sysAddr, float x, float y, float z, uint32_t checksum, LWOZONEID zone);
@@ -21,6 +34,8 @@ namespace WorldPackets {
void SendCreateCharacter(const SystemAddress& sysAddr, int64_t reputation, LWOOBJID player, const std::string& xmlData, const std::u16string& username, eGameMasterLevel gm); void SendCreateCharacter(const SystemAddress& sysAddr, int64_t reputation, LWOOBJID player, const std::string& xmlData, const std::u16string& username, eGameMasterLevel gm);
void SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::vector<std::pair<uint8_t, uint8_t>> unacceptedItems); void SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::vector<std::pair<uint8_t, uint8_t>> unacceptedItems);
void SendGMLevelChange(const SystemAddress& sysAddr, bool success, eGameMasterLevel highestLevel, eGameMasterLevel prevLevel, eGameMasterLevel newLevel); void SendGMLevelChange(const SystemAddress& sysAddr, bool success, eGameMasterLevel highestLevel, eGameMasterLevel prevLevel, eGameMasterLevel newLevel);
void SendHTTPMonitorInfo(const SystemAddress& sysAddr, const HTTPMonitorInfo& info);
void SendDebugOuput(const SystemAddress& sysAddr, const std::string& data);
} }
#endif // WORLDPACKETS_H #endif // WORLDPACKETS_H

View File

@@ -90,7 +90,7 @@ void SGCannon::OnActivityStateChangeRequest(Entity* self, LWOOBJID senderID, int
auto* shootingGalleryComponent = self->GetComponent<ShootingGalleryComponent>(); auto* shootingGalleryComponent = self->GetComponent<ShootingGalleryComponent>();
if (shootingGalleryComponent != nullptr) { if (shootingGalleryComponent != nullptr) {
shootingGalleryComponent->AddActivityPlayerData(player->GetObjectID()); shootingGalleryComponent->SetCurrentPlayerID(player->GetObjectID());
LOG("Setting player ID"); LOG("Setting player ID");

151
docs/ChatWebAPI.yaml Normal file
View File

@@ -0,0 +1,151 @@
openapi: 3.0.3
info:
title: DLU Chat Server API
description: |-
This documents the available api endpoints for the DLU Chat Server Web API
contact:
name: DarkflameUniverse Github
url: https://github.com/DarkflameUniverse/DarkflameServer/issues
license:
name: GNU AGPL v3.0
url: https://github.com/DarkflameUniverse/DarkflameServer/blob/main/LICENSE
version: 1.0.0
externalDocs:
description: Find out more about Swagger
url: http://swagger.io
servers:
- url: http://localhost:2005/
description: localhost
tags:
- name: management
description: Server Management Utilities
- name: user
description: User Data Utilities
paths:
/announce:
post:
tags:
- management
summary: Send an announcement to the game server
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/Announce"
required: true
responses:
"200":
description: Successful operation
"400":
description: Missing Parameter
/players:
get:
tags:
- user
summary: Get all online Players
responses:
"200":
description: Successful operation
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Player"
"204":
description: No Data
/teams:
get:
tags:
- user
summary: Get all active Teams
responses:
"200":
description: Successful operation
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Team"
"204":
description: No Data
components:
schemas:
Player:
type: object
properties:
id:
type: integer
format: int64
example: 1152921508901824000
gm_level:
type: integer
format: uint8
example: 0
name:
type: string
example: thisisatestname
muted:
type: boolean
example: false
zone_id:
$ref: "#/components/schemas/ZoneID"
ZoneID:
type: object
properties:
map_id:
type: integer
format: uint16
example: 1200
instance_id:
type: integer
format: uint16
example: 2
clone_id:
type: integer
format: uint32
example: 0
Team:
type: object
properties:
id:
type: integer
format: int64
example: 1152921508901824000
loot_flag:
type: integer
format: uint8
example: 1
local:
type: boolean
example: false
leader:
$ref: "#/components/schemas/Player"
members:
type: array
items:
$ref: "#/components/schemas/Player"
Announce:
required:
- title
- message
type: object
properties:
title:
type: string
example: A Mythran has taken Action against you!
message:
type: string
example: Check your mailbox for details!

View File

@@ -6,3 +6,11 @@ max_number_of_best_friends=5
# Change the value below to what you would like this to be (50 is live accurate) # Change the value below to what you would like this to be (50 is live accurate)
# going over 50 will be allowed in some secnarios, but proper handling will require client modding # going over 50 will be allowed in some secnarios, but proper handling will require client modding
max_number_of_friends=50 max_number_of_friends=50
# Enable or disable the chat web API, disabled by default
# It will run on the same port the chat server is running on, defined in shardconfig.ini
enable_chat_web_api=0
# If that chat web api is enabled, it will only listen for connections on this ip address
# 127.0.0.1 is localhost
chat_web_api_listen_address=127.0.0.1

View File

@@ -15,12 +15,12 @@ TEST_F(EncodingTest, TestEncodingHello) {
originalWord = "Hello World!"; originalWord = "Hello World!";
originalWordSv = originalWord; originalWordSv = originalWord;
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'H'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'H');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'e'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'e');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'o'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'o');
EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), true); EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), true);
EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Hello World!"), u"Hello World!"); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Hello World!"), u"Hello World!");
}; };
@@ -29,15 +29,15 @@ TEST_F(EncodingTest, TestEncodingUmlaut) {
originalWord = reinterpret_cast<const char*>(u8"Frühling"); originalWord = reinterpret_cast<const char*>(u8"Frühling");
originalWordSv = originalWord; originalWordSv = originalWord;
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'F'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'F');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'r'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'r');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'ü'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'ü');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'h'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'h');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'l'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'l');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'i'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'i');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'n'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'n');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'g'); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'g');
EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false); EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), false);
EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Frühling"), u"Frühling"); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Frühling"), u"Frühling");
}; };
@@ -46,10 +46,10 @@ TEST_F(EncodingTest, TestEncodingChinese) {
originalWord = "中文字"; originalWord = "中文字";
originalWordSv = originalWord; originalWordSv = originalWord;
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U''); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U''); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U''); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'');
EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false); EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), false);
EXPECT_EQ(GeneralUtils::UTF8ToUTF16("中文字"), u"中文字"); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("中文字"), u"中文字");
}; };
@@ -58,11 +58,11 @@ TEST_F(EncodingTest, TestEncodingEmoji) {
originalWord = "👨‍⚖️"; originalWord = "👨‍⚖️";
originalWordSv = originalWord; originalWordSv = originalWord;
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x1F468); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x1F468);
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x200D); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x200D);
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x2696); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x2696);
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0xFE0F); GeneralUtils::details::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0xFE0F);
EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false); EXPECT_EQ(GeneralUtils::details::_NextUTF8Char(originalWordSv, out), false);
EXPECT_EQ(GeneralUtils::UTF8ToUTF16("👨‍⚖️"), u"👨‍⚖️"); EXPECT_EQ(GeneralUtils::UTF8ToUTF16("👨‍⚖️"), u"👨‍⚖️");
}; };