Compare commits

..

4 Commits

Author SHA1 Message Date
David Markowitz
4d664ecd89 Use simpler conversion 2025-01-21 01:41:38 -08:00
David Markowitz
b5ef41e6ef Add better debug information 2025-01-21 01:39:52 -08:00
David Markowitz
60971409c1 debug stuff 2025-01-20 19:20:58 -08:00
David Markowitz
af2ba5b287 Add FlagComponent and msg handlers 2025-01-20 18:01:28 -08:00
241 changed files with 3666 additions and 6065 deletions

View File

@@ -16,10 +16,7 @@ body:
I have validated that this issue is not a syntax error of either MySQL or SQLite.
required: true
- label: >
I have downloaded/pulled the latest version of the main branch of DarkflameServer and have confirmed that the issue exists there.
required: true
- label: >
I have verified that my boot.cfg is configured as per the [README](https://github.com/DarkflameUniverse/DarkflameServer?tab=readme-ov-file#allowing-a-user-to-connect-to-your-server).
I have pulled the latest version of the main branch of DarkflameServer and have confirmed that the issue exists there.
required: true
- type: input
id: server-version

View File

@@ -16,12 +16,12 @@ jobs:
os: [ windows-2022, ubuntu-22.04, macos-13 ]
steps:
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
- uses: actions/checkout@v4
with:
submodules: true
- name: Add msbuild to PATH (Windows only)
if: ${{ matrix.os == 'windows-2022' }}
uses: microsoft/setup-msbuild@767f00a3f09872d96a0cb9fcd5e6a4ff33311330
uses: microsoft/setup-msbuild@v2
with:
vs-version: '[17,18)'
msbuild-architecture: x64
@@ -30,16 +30,12 @@ jobs:
run: |
brew install openssl@3
sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
- name: Get CMake 3.x
uses: lukka/get-cmake@28983e0d3955dba2bb0a6810caae0c6cf268ec0c
with:
cmakeVersion: "~3.25.0" # <--= optional, use most recent 3.25.x version
- name: cmake
uses: lukka/run-cmake@67c73a83a46f86c4e0b96b741ac37ff495478c38
uses: lukka/run-cmake@v10
with:
workflowPreset: "ci-${{matrix.os}}"
- name: artifacts
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
uses: actions/upload-artifact@v4
with:
name: build-${{matrix.os}}
path: |

View File

@@ -235,8 +235,6 @@ include_directories(
"dNet"
"dWeb"
"tests"
"tests/dCommonTests"
"tests/dGameTests"
@@ -303,7 +301,6 @@ add_subdirectory(dZoneManager)
add_subdirectory(dNavigation)
add_subdirectory(dPhysics)
add_subdirectory(dServer)
add_subdirectory(dWeb)
# Create a list of common libraries shared between all binaries
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "magic_enum")

View File

@@ -11,12 +11,7 @@ COPY --chmod=0500 ./build.sh /app/
RUN sed -i 's/MARIADB_CONNECTOR_COMPILE_JOBS__=.*/MARIADB_CONNECTOR_COMPILE_JOBS__=2/' /app/CMakeVariables.txt
RUN --mount=type=cache,target=/app/build,id=build-cache \
mkdir -p /app/build /tmp/persisted-build && \
cd /app/build && \
cmake .. && \
make -j$(nproc --ignore 1) && \
cp -r /app/build/* /tmp/persisted-build/
RUN ./build.sh
FROM debian:12 as runtime
@@ -28,23 +23,23 @@ RUN --mount=type=cache,id=build-apt-cache,target=/var/cache/apt \
rm -rf /var/lib/apt/lists/*
# Grab libraries and load them
COPY --from=build /tmp/persisted-build/mariadbcpp/libmariadbcpp.so /usr/local/lib/
COPY --from=build /app/build/mariadbcpp/libmariadbcpp.so /usr/local/lib/
RUN ldconfig
# Server bins
COPY --from=build /tmp/persisted-build/*Server /app/
COPY --from=build /app/build/*Server /app/
# Necessary suplimentary files
COPY --from=build /tmp/persisted-build/*.ini /app/configs/
COPY --from=build /tmp/persisted-build/vanity/*.* /app/vanity/
COPY --from=build /tmp/persisted-build/navmeshes /app/navmeshes
COPY --from=build /tmp/persisted-build/migrations /app/migrations
COPY --from=build /tmp/persisted-build/*.dcf /app/
COPY --from=build /app/build/*.ini /app/configs/
COPY --from=build /app/build/vanity/*.* /app/vanity/
COPY --from=build /app/build/navmeshes /app/navmeshes
COPY --from=build /app/build/migrations /app/migrations
COPY --from=build /app/build/*.dcf /app/
# backup of config and vanity files to copy to the host incase
# of a mount clobbering the copy from above
COPY --from=build /tmp/persisted-build/*.ini /app/default-configs/
COPY --from=build /tmp/persisted-build/vanity/*.* /app/default-vanity/
COPY --from=build /app/build/*.ini /app/default-configs/
COPY --from=build /app/build/vanity/*.* /app/default-vanity/
# needed as the container runs with the root user
# and therefore sudo doesn't exist

View File

@@ -78,7 +78,7 @@ git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer
### Windows packages
Ensure that you have either the [MSVC C++ compiler](https://visualstudio.microsoft.com/vs/features/cplusplus/) (recommended) or the [Clang compiler](https://github.com/llvm/llvm-project/releases/) installed.
You'll also need to download and install [CMake](https://cmake.org/download/) (<font size="4">**version 3.25**</font> up to <font size="4">**version 3.31**</font>!).
You'll also need to download and install [CMake](https://cmake.org/download/) (version <font size="4">**CMake version 3.25**</font> or later!).
### MacOS packages
Ensure you have [brew](https://brew.sh) installed.
@@ -100,7 +100,7 @@ sudo apt install build-essential gcc zlib1g-dev libssl-dev openssl mariadb-serve
```
#### Required CMake version
This project uses <font size="4">**CMake version 3.25**</font> up to <font size="4">**version 3.31**</font> and as such you will need to ensure you have this version installed.
This project uses <font size="4">**CMake version 3.25**</font> or higher and as such you will need to ensure you have this version installed.
You can check your CMake version by using the following command in a terminal.
```bash
cmake --version
@@ -324,15 +324,13 @@ While a character has a gmlevel of anything but `0`, some gameplay behavior will
Some changes to the client `boot.cfg` file are needed to play on your server.
## Allowing a user to connect to your server
**ALL OF THESE CHANGES ARE REQUIRED. PLEASE FULLY READ THIS SECTION**
To connect to a server follow these steps:
* In the client directory, locate `boot.cfg`
* Open `boot.cfg` in a text editor and locate the line `UGCUSE3DSERVICES=7:`
* Open it in a text editor and locate where it says `AUTHSERVERIP=0:`
* Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers
* Next locate the line `UGCUSE3DSERVICES=7:`
* Ensure the number after the 7 is a `0`
* Alternatively, remove the line with `UGCUSE3DSERVICES` altogether
* Next locate where it says `AUTHSERVERIP=0:`
* Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers
* Launch `legouniverse.exe`, through `wine` if on a Unix-like operating system
* Note that if you are on WSL2, you will need to configure the public IP in the server and client to be the IP of the WSL2 instance and not localhost, which can be found by running `ifconfig` in the terminal. Windows defaults to WSL1, so this will not apply to most users.
As an example, here is what the boot.cfg is required to contain for a server with the ip 12.34.56.78

View File

@@ -105,7 +105,7 @@ void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool allowLis
}
}
std::set<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList) {
std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList) {
if (gmLevel > eGameMasterLevel::FORUM_MODERATOR) return { }; //If anything but a forum mod, return true.
if (message.empty()) return { };
if (!allowList && m_DeniedWords.empty()) return { { 0, message.length() } };
@@ -114,7 +114,7 @@ std::set<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::str
std::string segment;
std::regex reg("(!*|\\?*|\\;*|\\.*|\\,*)");
std::set<std::pair<uint8_t, uint8_t>> listOfBadSegments;
std::vector<std::pair<uint8_t, uint8_t>> listOfBadSegments = std::vector<std::pair<uint8_t, uint8_t>>();
uint32_t position = 0;
@@ -127,17 +127,17 @@ std::set<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::str
size_t hash = CalculateHash(segment);
if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && allowList) {
listOfBadSegments.emplace(position, originalSegment.length());
listOfBadSegments.emplace_back(position, originalSegment.length());
}
if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && allowList) {
m_UserUnapprovedWordCache.push_back(hash);
listOfBadSegments.emplace(position, originalSegment.length());
listOfBadSegments.emplace_back(position, originalSegment.length());
}
if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !allowList) {
m_UserUnapprovedWordCache.push_back(hash);
listOfBadSegments.emplace(position, originalSegment.length());
listOfBadSegments.emplace_back(position, originalSegment.length());
}
position += originalSegment.length() + 1;

View File

@@ -24,7 +24,7 @@ public:
void ReadWordlistPlaintext(const std::string& filepath, bool allowList);
bool ReadWordlistDCF(const std::string& filepath, bool allowList);
void ExportWordlistToDCF(const std::string& filepath, bool allowList);
std::set<std::pair<uint8_t, uint8_t>> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList = true);
std::vector<std::pair<uint8_t, uint8_t>> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList = true);
private:
bool m_DontGenerateDCF;

View File

@@ -1,19 +1,18 @@
set(DCHATSERVER_SOURCES
"ChatIgnoreList.cpp"
"ChatPacketHandler.cpp"
"ChatJSONUtils.cpp"
"ChatWeb.cpp"
"PlayerContainer.cpp"
"TeamContainer.cpp"
"ChatWebAPI.cpp"
"JSONUtils.cpp"
)
add_executable(ChatServer "ChatServer.cpp")
target_include_directories(ChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dChatFilter" "${PROJECT_SOURCE_DIR}/dWeb")
target_include_directories(ChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dChatFilter")
add_compile_definitions(ChatServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"")
add_library(dChatServer ${DCHATSERVER_SOURCES})
target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer" "${PROJECT_SOURCE_DIR}/dChatFilter")
target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer")
target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter)
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer mongoose dWeb)
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer mongoose)

View File

@@ -12,8 +12,8 @@
// not allowing teams, rejecting DMs, friends requets etc.
// The only thing not auto-handled is instance activities force joining the team on the server.
void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const MessageType::Client type) {
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const ChatIgnoreList::Response type) {
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receivingPlayer);
//portion that will get routed:
@@ -48,9 +48,9 @@ void ChatIgnoreList::GetIgnoreList(Packet* packet) {
}
CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::GET_IGNORE_LIST_RESPONSE);
WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::GET_IGNORE);
bitStream.Write<uint8_t>(false); // Is Free Trial, but we don't care about that
bitStream.Write<uint8_t>(false); // Probably is Is Free Trial, but we don't care about that
bitStream.Write<uint16_t>(0); // literally spacing due to struct alignment
bitStream.Write<uint16_t>(receiver.ignoredPlayers.size());
@@ -86,7 +86,7 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
std::string toIgnoreStr = toIgnoreName.GetAsString();
CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::ADD_IGNORE_RESPONSE);
WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::ADD_IGNORE);
// Check if the player exists
LWOOBJID ignoredPlayerId = LWOOBJID_EMPTY;
@@ -161,7 +161,7 @@ void ChatIgnoreList::RemoveIgnore(Packet* packet) {
receiver.ignoredPlayers.erase(toRemove, receiver.ignoredPlayers.end());
CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::REMOVE_IGNORE_RESPONSE);
WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::REMOVE_IGNORE);
bitStream.Write<int8_t>(0);
LUWString playerNameSend(removedIgnoreStr, 33);

View File

@@ -5,16 +5,17 @@ struct Packet;
#include <cstdint>
/**
* @brief The ignore list allows players to ignore someone silently. Requests will generally be blocked by the client, but they should also be checked
* on the server as well so the sender can get a generic error code in response.
*
*/
namespace ChatIgnoreList {
void GetIgnoreList(Packet* packet);
void AddIgnore(Packet* packet);
void RemoveIgnore(Packet* packet);
enum class Response : uint8_t {
ADD_IGNORE = 32,
REMOVE_IGNORE = 33,
GET_IGNORE = 34,
};
enum class AddResponse : uint8_t {
SUCCESS,
ALREADY_IGNORED,

View File

@@ -1,18 +0,0 @@
#ifndef __CHATJSONUTILS_H__
#define __CHATJSONUTILS_H__
#include "json_fwd.hpp"
#include "PlayerContainer.h"
#include "TeamContainer.h"
/* Remember, to_json needs to be in the same namespace as the class its located in */
void to_json(nlohmann::json& data, const PlayerData& playerData);
void to_json(nlohmann::json& data, const PlayerContainer& playerContainer);
void to_json(nlohmann::json& data, const TeamData& teamData);
namespace TeamContainer {
void to_json(nlohmann::json& data, const TeamContainer::Data& teamData);
};
#endif // !__CHATJSONUTILS_H__

View File

@@ -19,7 +19,6 @@
#include "StringifiedEnum.h"
#include "eGameMasterLevel.h"
#include "ChatPackets.h"
#include "TeamContainer.h"
void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//Get from the packet which player we want to do something with:
@@ -74,7 +73,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
data.Serialize(bitStream);
}
SystemAddress sysAddr = player.worldServerSysAddr;
SystemAddress sysAddr = player.sysAddr;
SEND_PACKET;
}
@@ -123,7 +122,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
requesteeFriendData.isOnline = false;
requesteeFriendData.zoneID = requestor.zoneID;
requestee.friends.push_back(requesteeFriendData);
requestee.worldServerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
requestee.sysAddr = UNASSIGNED_SYSTEM_ADDRESS;
break;
}
}
@@ -190,8 +189,8 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus);
// Sent the best friend update here if the value is 3
if (bestFriendStatus == 3U) {
if (requestee.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
if (requestee.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
for (auto& friendData : requestor.friends) {
if (friendData.friendID == requestee.playerID) {
@@ -212,7 +211,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
}
}
} else {
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true);
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true);
}
} else {
auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends();
@@ -385,7 +384,7 @@ void ChatPacketHandler::HandleWho(Packet* packet) {
bitStream.Write(player.zoneID.GetCloneID());
bitStream.Write(request.playerName);
SystemAddress sysAddr = sender.worldServerSysAddr;
SystemAddress sysAddr = sender.sysAddr;
SEND_PACKET;
}
@@ -419,7 +418,7 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
}
}
}
SystemAddress sysAddr = sender.worldServerSysAddr;
SystemAddress sysAddr = sender.sysAddr;
SEND_PACKET;
}
@@ -448,7 +447,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
switch (channel) {
case eChatChannel::TEAM: {
auto* team = TeamContainer::GetTeam(playerID);
auto* team = Game::playerContainer.GetTeam(playerID);
if (team == nullptr) return;
for (const auto memberId : team->memberIDs) {
@@ -520,28 +519,6 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::GENERAL, eChatMessageResponseCode::NOTFRIENDS);
}
void ChatPacketHandler::OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr) {
ChatPackets::AchievementNotify notify{};
notify.Deserialize(bitstream);
const auto& playerData = Game::playerContainer.GetPlayerData(notify.earnerName.GetAsString());
if (!playerData) return;
for (const auto& myFriend : playerData.friends) {
auto& friendData = Game::playerContainer.GetPlayerData(myFriend.friendID);
if (friendData) {
notify.targetPlayerName.string = GeneralUtils::ASCIIToUTF16(friendData.playerName);
LOG_DEBUG("Sending achievement notify to %s", notify.targetPlayerName.GetAsString().c_str());
RakNet::BitStream worldStream;
BitStreamUtils::WriteHeader(worldStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
worldStream.Write(friendData.playerID);
notify.WriteHeader(worldStream);
notify.Serialize(worldStream);
Game::server->Send(worldStream, friendData.worldServerSysAddr, false);
}
}
}
void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
@@ -560,7 +537,387 @@ void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const P
bitStream.Write(responseCode);
bitStream.Write(message);
SystemAddress sysAddr = routeTo.worldServerSysAddr;
SystemAddress sysAddr = routeTo.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::HandleTeamInvite(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID;
LUWString invitedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(invitedPlayer);
const auto& player = Game::playerContainer.GetPlayerData(playerID);
if (!player) return;
auto* team = Game::playerContainer.GetTeam(playerID);
if (team == nullptr) {
team = Game::playerContainer.CreateTeam(playerID);
}
const auto& other = Game::playerContainer.GetPlayerData(invitedPlayer.GetAsString());
if (!other) return;
if (Game::playerContainer.GetTeam(other.playerID) != nullptr) {
return;
}
if (team->memberIDs.size() > 3) {
// no more teams greater than 4
LOG("Someone tried to invite a 5th player to a team");
return;
}
SendTeamInvite(other, player);
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str());
}
void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
char declined = 0;
inStream.Read(declined);
LWOOBJID leaderID = LWOOBJID_EMPTY;
inStream.Read(leaderID);
LOG("Accepted invite: %llu -> %llu (%d)", playerID, leaderID, declined);
if (declined) {
return;
}
auto* team = Game::playerContainer.GetTeam(leaderID);
if (team == nullptr) {
LOG("Failed to find team for leader (%llu)", leaderID);
team = Game::playerContainer.GetTeam(playerID);
}
if (team == nullptr) {
LOG("Failed to find team for player (%llu)", playerID);
return;
}
Game::playerContainer.AddMember(team, playerID);
}
void ChatPacketHandler::HandleTeamLeave(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
auto* team = Game::playerContainer.GetTeam(playerID);
LOG("(%llu) leaving team", playerID);
if (team != nullptr) {
Game::playerContainer.RemoveMember(team, playerID, false, false, true);
}
}
void ChatPacketHandler::HandleTeamKick(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
LUWString kickedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(kickedPlayer);
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str());
const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString());
LWOOBJID kickedId = LWOOBJID_EMPTY;
if (kicked) {
kickedId = kicked.playerID;
} else {
kickedId = Game::playerContainer.GetId(kickedPlayer.string);
}
if (kickedId == LWOOBJID_EMPTY) return;
auto* team = Game::playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID || team->leaderID == kickedId) return;
Game::playerContainer.RemoveMember(team, kickedId, false, true, false);
}
}
void ChatPacketHandler::HandleTeamPromote(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
LUWString promotedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(promotedPlayer);
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str());
const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString());
if (!promoted) return;
auto* team = Game::playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
Game::playerContainer.PromoteMember(team, promoted.playerID);
}
}
void ChatPacketHandler::HandleTeamLootOption(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
char option;
inStream.Read(option);
auto* team = Game::playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
team->lootFlag = option;
Game::playerContainer.TeamStatusUpdate(team);
Game::playerContainer.UpdateTeamsOnWorld(team, false);
}
}
void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
auto* team = Game::playerContainer.GetTeam(playerID);
const auto& data = Game::playerContainer.GetPlayerData(playerID);
if (team != nullptr && data) {
if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
Game::playerContainer.RemoveMember(team, playerID, false, false, true, true);
return;
}
if (team->memberIDs.size() <= 1 && !team->local) {
Game::playerContainer.DisbandTeam(team);
return;
}
if (!team->local) {
ChatPacketHandler::SendTeamSetLeader(data, team->leaderID);
} else {
ChatPacketHandler::SendTeamSetLeader(data, LWOOBJID_EMPTY);
}
Game::playerContainer.TeamStatusUpdate(team);
const auto leaderName = GeneralUtils::UTF8ToUTF16(data.playerName);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (memberId == playerID) continue;
const auto memberName = Game::playerContainer.GetName(memberId);
if (otherMember) {
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, data.playerID, data.zoneID);
}
ChatPacketHandler::SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
}
Game::playerContainer.UpdateTeamsOnWorld(team, false);
}
}
void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE);
bitStream.Write(LUWString(sender.playerName.c_str()));
bitStream.Write(sender.playerID);
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM);
bitStream.Write(bLeaderIsFreeTrial);
bitStream.Write(i64LeaderID);
bitStream.Write(i64LeaderZoneID);
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write(ucResponseCode);
bitStream.Write<uint32_t>(wsLeaderName.size());
for (const auto character : wsLeaderName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE);
bitStream.Write(i64LeaderID);
bitStream.Write(i64LeaderZoneID);
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write<uint32_t>(wsLeaderName.size());
for (const auto character : wsLeaderName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_SET_LEADER);
bitStream.Write(i64PlayerID);
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER);
bitStream.Write(bIsFreeTrial);
bitStream.Write(bLocal);
bitStream.Write(bNoLootOnDeath);
bitStream.Write(i64PlayerID);
bitStream.Write<uint32_t>(wsPlayerName.size());
for (const auto character : wsPlayerName) {
bitStream.Write(character);
}
bitStream.Write1();
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER);
bitStream.Write(bDisband);
bitStream.Write(bIsKicked);
bitStream.Write(bIsLeaving);
bitStream.Write(bLocal);
bitStream.Write(i64LeaderID);
bitStream.Write(i64PlayerID);
bitStream.Write<uint32_t>(wsPlayerName.size());
for (const auto character : wsPlayerName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG);
bitStream.Write(i64PlayerID);
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
@@ -602,7 +959,7 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla
bitStream.Write<uint8_t>(isBestFriend); //isBFF
bitStream.Write<uint8_t>(0); //isFTP
SystemAddress sysAddr = friendData.worldServerSysAddr;
SystemAddress sysAddr = friendData.sysAddr;
SEND_PACKET;
}
@@ -624,7 +981,7 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
bitStream.Write(LUWString(sender.playerName));
bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side.
SystemAddress sysAddr = receiver.worldServerSysAddr;
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
@@ -637,7 +994,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_RESPONSE);
bitStream.Write(responseCode);
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS);
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.sysAddr != UNASSIGNED_SYSTEM_ADDRESS);
// Then write the player name
bitStream.Write(LUWString(sender.playerName));
// Then if this is an acceptance code, write the following extra info.
@@ -647,7 +1004,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
bitStream.Write(isBestFriendRequest); //isBFF
bitStream.Write<uint8_t>(0); //isFTP
}
SystemAddress sysAddr = receiver.worldServerSysAddr;
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}
@@ -661,6 +1018,6 @@ void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string
bitStream.Write<uint8_t>(isSuccessful); //isOnline
bitStream.Write(LUWString(personToRemove));
SystemAddress sysAddr = receiver.worldServerSysAddr;
SystemAddress sysAddr = receiver.sysAddr;
SEND_PACKET;
}

View File

@@ -35,13 +35,13 @@ enum class eChatChannel : uint8_t {
enum class eChatMessageResponseCode : uint8_t {
SENT = 0,
NOTONLINE,
GENERALERROR,
RECEIVEDNEWWHISPER,
NOTFRIENDS,
SENDERFREETRIAL,
RECEIVERFREETRIAL,
SENT = 0,
NOTONLINE,
GENERALERROR,
RECEIVEDNEWWHISPER,
NOTFRIENDS,
SENDERFREETRIAL,
RECEIVERFREETRIAL,
};
namespace ChatPacketHandler {
@@ -52,14 +52,30 @@ namespace ChatPacketHandler {
void HandleGMLevelUpdate(Packet* packet);
void HandleWho(Packet* packet);
void HandleShowAll(Packet* packet);
void HandleChatMessage(Packet* packet);
void HandlePrivateChatMessage(Packet* packet);
void SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode);
void OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr);
void HandleTeamInvite(Packet* packet);
void HandleTeamInviteResponse(Packet* packet);
void HandleTeamLeave(Packet* packet);
void HandleTeamKick(Packet* packet);
void HandleTeamPromote(Packet* packet);
void HandleTeamLootOption(Packet* packet);
void HandleTeamStatusRequest(Packet* packet);
void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender);
void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName);
void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName);
void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID);
void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID);
void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName);
void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);
//FriendData is the player we're SENDING this stuff to. Player is the friend that changed state.
void SendFriendUpdate(const PlayerData& friendData, const PlayerData& playerData, uint8_t notifyType, uint8_t isBestFriend);
void SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode);
void SendFriendRequest(const PlayerData& receiver, const PlayerData& sender);
void SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready = 0U, uint8_t isBestFriendRequest = 0U);
void SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful);

View File

@@ -20,7 +20,6 @@
#include "MessageType/World.h"
#include "ChatIgnoreList.h"
#include "StringifiedEnum.h"
#include "TeamContainer.h"
#include "Game.h"
#include "Server.h"
@@ -29,7 +28,7 @@
#include "RakNetDefines.h"
#include "MessageIdentifiers.h"
#include "ChatWeb.h"
#include "ChatWebAPI.h"
namespace Game {
Logger* logger = nullptr;
@@ -93,18 +92,17 @@ int main(int argc, char** argv) {
return EXIT_FAILURE;
}
// setup the chat api web server
const uint32_t web_server_port = GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("web_server_port")).value_or(2005);
if (Game::config->GetValue("web_server_enabled") == "1" && !Game::web.Startup("localhost", web_server_port)) {
// if we want the web server and it fails to start, exit
// seyup the chat api web server
bool web_server_enabled = Game::config->GetValue("web_server_enabled") == "1";
ChatWebAPI chatwebapi;
if (web_server_enabled && !chatwebapi.Startup()){
// if we want the web api and it fails to start, exit
LOG("Failed to start web server, shutting down.");
Database::Destroy("ChatServer");
delete Game::logger;
delete Game::config;
return EXIT_FAILURE;
}
if (Game::web.IsEnabled()) ChatWeb::RegisterRoutes();
};
//Find out the master's IP:
std::string masterIP;
@@ -168,8 +166,10 @@ int main(int argc, char** argv) {
packet = nullptr;
}
// Check and handle web requests:
if (Game::web.IsEnabled()) Game::web.ReceiveRequests();
//Check and handle web requests:
if (web_server_enabled) {
chatwebapi.ReceiveRequests();
}
//Push our log every 30s:
if (framesSinceLastFlush >= logFlushTime) {
@@ -197,7 +197,6 @@ int main(int argc, char** argv) {
std::this_thread::sleep_until(t);
}
Game::playerContainer.Shutdown();
TeamContainer::Shutdown();
//Delete our objects here:
Database::Destroy("ChatServer");
delete Game::server;
@@ -225,17 +224,13 @@ void HandlePacket(Packet* packet) {
if (connection != eConnectionType::CHAT) return;
inStream.Read(chatMessageID);
// Our packing byte wasnt there? Probably a false packet
if (inStream.GetNumberOfUnreadBits() < 8) return;
inStream.IgnoreBytes(1);
switch (chatMessageID) {
case MessageType::Chat::GM_MUTE:
Game::playerContainer.MuteUpdate(packet);
break;
case MessageType::Chat::CREATE_TEAM:
TeamContainer::CreateTeamServer(packet);
Game::playerContainer.CreateTeamServer(packet);
break;
case MessageType::Chat::GET_FRIENDS_LIST:
@@ -255,7 +250,7 @@ void HandlePacket(Packet* packet) {
break;
case MessageType::Chat::TEAM_GET_STATUS:
TeamContainer::HandleTeamStatusRequest(packet);
ChatPacketHandler::HandleTeamStatusRequest(packet);
break;
case MessageType::Chat::ADD_FRIEND_REQUEST:
@@ -285,27 +280,27 @@ void HandlePacket(Packet* packet) {
break;
case MessageType::Chat::TEAM_INVITE:
TeamContainer::HandleTeamInvite(packet);
ChatPacketHandler::HandleTeamInvite(packet);
break;
case MessageType::Chat::TEAM_INVITE_RESPONSE:
TeamContainer::HandleTeamInviteResponse(packet);
ChatPacketHandler::HandleTeamInviteResponse(packet);
break;
case MessageType::Chat::TEAM_LEAVE:
TeamContainer::HandleTeamLeave(packet);
ChatPacketHandler::HandleTeamLeave(packet);
break;
case MessageType::Chat::TEAM_SET_LEADER:
TeamContainer::HandleTeamPromote(packet);
ChatPacketHandler::HandleTeamPromote(packet);
break;
case MessageType::Chat::TEAM_KICK:
TeamContainer::HandleTeamKick(packet);
ChatPacketHandler::HandleTeamKick(packet);
break;
case MessageType::Chat::TEAM_SET_LOOT:
TeamContainer::HandleTeamLootOption(packet);
ChatPacketHandler::HandleTeamLootOption(packet);
break;
case MessageType::Chat::GMLEVEL_UPDATE:
ChatPacketHandler::HandleGMLevelUpdate(packet);
@@ -327,9 +322,6 @@ void HandlePacket(Packet* packet) {
case MessageType::Chat::SHOW_ALL:
ChatPacketHandler::HandleShowAll(packet);
break;
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
ChatPacketHandler::OnAchievementNotify(inStream, packet->systemAddress);
break;
case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE:
case MessageType::Chat::WORLD_DISCONNECT_REQUEST:
case MessageType::Chat::WORLD_PROXIMITY_RESPONSE:
@@ -365,6 +357,7 @@ void HandlePacket(Packet* packet) {
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
case MessageType::Chat::UGCC_REQUEST:
case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW:
case MessageType::Chat::PLAYER_READY:
case MessageType::Chat::GET_DONATION_TOTAL:

View File

@@ -1,133 +0,0 @@
#include "ChatWeb.h"
#include "Logger.h"
#include "Game.h"
#include "json.hpp"
#include "dCommonVars.h"
#include "MessageType/Chat.h"
#include "dServer.h"
#include "dConfig.h"
#include "PlayerContainer.h"
#include "GeneralUtils.h"
#include "eHTTPMethod.h"
#include "magic_enum.hpp"
#include "ChatPackets.h"
#include "StringifiedEnum.h"
#include "Database.h"
#include "ChatJSONUtils.h"
#include "JSONUtils.h"
#include "eGameMasterLevel.h"
#include "dChatFilter.h"
#include "TeamContainer.h"
using json = nlohmann::json;
void HandleHTTPPlayersRequest(HTTPReply& reply, std::string body) {
const json data = Game::playerContainer;
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
reply.message = data.empty() ? "{\"error\":\"No Players Online\"}" : data.dump();
}
void HandleHTTPTeamsRequest(HTTPReply& reply, std::string body) {
const json data = TeamContainer::GetTeamContainer();
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump();
}
void HandleHTTPAnnounceRequest(HTTPReply& reply, std::string body) {
auto data = GeneralUtils::TryParse<json>(body);
if (!data) {
reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = "{\"error\":\"Invalid JSON\"}";
return;
}
const auto& good_data = data.value();
auto check = JSONUtils::CheckRequiredData(good_data, { "title", "message" });
if (!check.empty()) {
reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = check;
} else {
ChatPackets::Announcement announcement;
announcement.title = good_data["title"];
announcement.message = good_data["message"];
announcement.Broadcast();
reply.status = eHTTPStatusCode::OK;
reply.message = "{\"status\":\"Announcement Sent\"}";
}
}
void HandleWSChat(mg_connection* connection, json data) {
auto check = JSONUtils::CheckRequiredData(data, { "user", "message", "gmlevel", "zone" });
if (!check.empty()) {
LOG_DEBUG("Received invalid websocket message: %s", check.c_str());
} else {
const auto user = data["user"].get<std::string>();
const auto message = data["message"].get<std::string>();
const auto gmlevel = GeneralUtils::TryParse<eGameMasterLevel>(data["gmlevel"].get<std::string>()).value_or(eGameMasterLevel::CIVILIAN);
const auto zone = data["zone"].get<uint32_t>();
const auto filter_check = Game::chatFilter->IsSentenceOkay(message, gmlevel);
if (!filter_check.empty()) {
LOG_DEBUG("Chat message \"%s\" from %s was not allowed", message.c_str(), user.c_str());
data["error"] = "Chat message blocked by filter";
data["filtered"] = json::array();
for (const auto& [start, len] : filter_check) {
data["filtered"].push_back(message.substr(start, len));
}
mg_ws_send(connection, data.dump().c_str(), data.dump().size(), WEBSOCKET_OP_TEXT);
return;
}
LOG("%s: %s", user.c_str(), message.c_str());
// TODO: Implement chat message handling from websocket message
}
}
namespace ChatWeb {
void RegisterRoutes() {
// REST API v1 routes
std::string v1_route = "/api/v1/";
Game::web.RegisterHTTPRoute({
.path = v1_route + "players",
.method = eHTTPMethod::GET,
.handle = HandleHTTPPlayersRequest
});
Game::web.RegisterHTTPRoute({
.path = v1_route + "teams",
.method = eHTTPMethod::GET,
.handle = HandleHTTPTeamsRequest
});
Game::web.RegisterHTTPRoute({
.path = v1_route + "announce",
.method = eHTTPMethod::POST,
.handle = HandleHTTPAnnounceRequest
});
// WebSocket Events Handlers
// Game::web.RegisterWSEvent({
// .name = "chat",
// .handle = HandleWSChat
// });
// WebSocket subscriptions
Game::web.RegisterWSSubscription("player");
}
void SendWSPlayerUpdate(const PlayerData& player, eActivityType activityType) {
json data;
data["player_data"] = player;
data["update_type"] = magic_enum::enum_name(activityType);
Game::web.SendWSMessage("player", data);
}
}

View File

@@ -1,19 +0,0 @@
#ifndef __CHATWEB_H__
#define __CHATWEB_H__
#include <string>
#include <functional>
#include "Web.h"
#include "PlayerContainer.h"
#include "IActivityLog.h"
#include "ChatPacketHandler.h"
namespace ChatWeb {
void RegisterRoutes();
void SendWSPlayerUpdate(const PlayerData& player, eActivityType activityType);
};
#endif // __CHATWEB_H__

196
dChatServer/ChatWebAPI.cpp Normal file
View File

@@ -0,0 +1,196 @@
#include "ChatWebAPI.h"
#include "Logger.h"
#include "Game.h"
#include "json.hpp"
#include "dCommonVars.h"
#include "MessageType/Chat.h"
#include "dServer.h"
#include "dConfig.h"
#include "PlayerContainer.h"
#include "JSONUtils.h"
#include "GeneralUtils.h"
#include "eHTTPMethod.h"
#include "magic_enum.hpp"
#include "ChatPackets.h"
#include "StringifiedEnum.h"
#include "Database.h"
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma push_macro("DELETE")
#undef DELETE
#endif
using json = nlohmann::json;
typedef struct mg_connection mg_connection;
typedef struct mg_http_message mg_http_message;
namespace {
const char* json_content_type = "Content-Type: application/json\r\n";
std::map<std::pair<eHTTPMethod, std::string>, WebAPIHTTPRoute> Routes {};
}
bool ValidateAuthentication(const mg_http_message* http_msg) {
// TO DO: This is just a placeholder for now
// use tokens or something at a later point if we want to implement authentication
// bit using the listen bind address to limit external access is good enough to start with
return true;
}
bool ValidateJSON(std::optional<json> data, HTTPReply& reply) {
if (!data) {
reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = "{\"error\":\"Invalid JSON\"}";
return false;
}
return true;
}
void HandlePlayersRequest(HTTPReply& reply, std::string body) {
const json data = Game::playerContainer;
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
reply.message = data.empty() ? "{\"error\":\"No Players Online\"}" : data.dump();
}
void HandleTeamsRequest(HTTPReply& reply, std::string body) {
const json data = Game::playerContainer.GetTeamContainer();
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump();
}
void HandleAnnounceRequest(HTTPReply& reply, std::string body) {
auto data = GeneralUtils::TryParse<json>(body);
if (!ValidateJSON(data, reply)) return;
const auto& good_data = data.value();
auto check = JSONUtils::CheckRequiredData(good_data, { "title", "message" });
if (!check.empty()) {
reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = check;
} else {
ChatPackets::Announcement announcement;
announcement.title = good_data["title"];
announcement.message = good_data["message"];
announcement.Send();
reply.status = eHTTPStatusCode::OK;
reply.message = "{\"status\":\"Announcement Sent\"}";
}
}
void HandleInvalidRoute(HTTPReply& reply) {
reply.status = eHTTPStatusCode::NOT_FOUND;
reply.message = "{\"error\":\"Invalid Route\"}";
}
void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_msg) {
HTTPReply reply;
if (!http_msg) {
reply.status = eHTTPStatusCode::BAD_REQUEST;
reply.message = "{\"error\":\"Invalid Request\"}";
} else if (ValidateAuthentication(http_msg)) {
// convert method from cstring to std string
std::string method_string(http_msg->method.buf, http_msg->method.len);
// get mehtod from mg to enum
const eHTTPMethod method = magic_enum::enum_cast<eHTTPMethod>(method_string).value_or(eHTTPMethod::INVALID);
// convert uri from cstring to std string
std::string uri(http_msg->uri.buf, http_msg->uri.len);
std::transform(uri.begin(), uri.end(), uri.begin(), ::tolower);
// convert body from cstring to std string
std::string body(http_msg->body.buf, http_msg->body.len);
const auto routeItr = Routes.find({method, uri});
if (routeItr != Routes.end()) {
const auto& [_, route] = *routeItr;
route.handle(reply, body);
} else HandleInvalidRoute(reply);
} else {
reply.status = eHTTPStatusCode::UNAUTHORIZED;
reply.message = "{\"error\":\"Unauthorized\"}";
}
mg_http_reply(connection, static_cast<int>(reply.status), json_content_type, reply.message.c_str());
}
void HandleRequests(mg_connection* connection, int request, void* request_data) {
switch (request) {
case MG_EV_HTTP_MSG:
HandleHTTPMessage(connection, static_cast<mg_http_message*>(request_data));
break;
default:
break;
}
}
void ChatWebAPI::RegisterHTTPRoutes(WebAPIHTTPRoute route) {
auto [_, success] = Routes.try_emplace({ route.method, route.path }, route);
if (!success) {
LOG_DEBUG("Failed to register route %s", route.path.c_str());
} else {
LOG_DEBUG("Registered route %s", route.path.c_str());
}
}
ChatWebAPI::ChatWebAPI() {
mg_log_set(MG_LL_NONE);
mg_mgr_init(&mgr); // Initialize event manager
}
ChatWebAPI::~ChatWebAPI() {
mg_mgr_free(&mgr);
}
bool ChatWebAPI::Startup() {
// Make listen address
std::string listen_ip = Game::config->GetValue("web_server_listen_ip");
if (listen_ip == "localhost") listen_ip = "127.0.0.1";
const std::string& listen_port = Game::config->GetValue("web_server_listen_port");
const std::string& listen_address = "http://" + listen_ip + ":" + listen_port;
LOG("Starting web server on %s", listen_address.c_str());
// Create HTTP listener
if (!mg_http_listen(&mgr, listen_address.c_str(), HandleRequests, NULL)) {
LOG("Failed to create web server listener on %s", listen_port.c_str());
return false;
}
// Register routes
// API v1 routes
std::string v1_route = "/api/v1/";
RegisterHTTPRoutes({
.path = v1_route + "players",
.method = eHTTPMethod::GET,
.handle = HandlePlayersRequest
});
RegisterHTTPRoutes({
.path = v1_route + "teams",
.method = eHTTPMethod::GET,
.handle = HandleTeamsRequest
});
RegisterHTTPRoutes({
.path = v1_route + "announce",
.method = eHTTPMethod::POST,
.handle = HandleAnnounceRequest
});
return true;
}
void ChatWebAPI::ReceiveRequests() {
mg_mgr_poll(&mgr, 15);
}
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma pop_macro("DELETE")
#endif

36
dChatServer/ChatWebAPI.h Normal file
View File

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

View File

@@ -1,4 +1,4 @@
#include "ChatJSONUtils.h"
#include "JSONUtils.h"
#include "json.hpp"
@@ -18,12 +18,19 @@ void to_json(json& data, const PlayerData& playerData) {
void to_json(json& data, const PlayerContainer& playerContainer) {
data = json::array();
for (auto& playerData : playerContainer.GetAllPlayers()) {
for(auto& playerData : playerContainer.GetAllPlayers()) {
if (playerData.first == LWOOBJID_EMPTY) continue;
data.push_back(playerData.second);
}
}
void to_json(json& data, const TeamContainer& teamContainer) {
for (auto& teamData : Game::playerContainer.GetTeams()) {
if (!teamData) continue;
data.push_back(*teamData);
}
}
void to_json(json& data, const TeamData& teamData) {
data["id"] = teamData.teamID;
data["loot_flag"] = teamData.lootFlag;
@@ -41,9 +48,15 @@ void to_json(json& data, const TeamData& teamData) {
}
}
void TeamContainer::to_json(json& data, const TeamContainer::Data& teamContainer) {
for (auto& teamData : TeamContainer::GetTeams()) {
if (!teamData) continue;
data.push_back(*teamData);
std::string JSONUtils::CheckRequiredData(const json& data, const std::vector<std::string>& requiredData) {
json check;
check["error"] = json::array();
for (const auto& required : requiredData) {
if (!data.contains(required)) {
check["error"].push_back("Missing Parameter: " + required);
} else if (data[required] == "") {
check["error"].push_back("Empty Parameter: " + required);
}
}
return check["error"].empty() ? "" : check.dump();
}

17
dChatServer/JSONUtils.h Normal file
View File

@@ -0,0 +1,17 @@
#ifndef __JSONUTILS_H__
#define __JSONUTILS_H__
#include "json_fwd.hpp"
#include "PlayerContainer.h"
void to_json(nlohmann::json& data, const PlayerData& playerData);
void to_json(nlohmann::json& data, const PlayerContainer& playerContainer);
void to_json(nlohmann::json& data, const TeamContainer& teamData);
void to_json(nlohmann::json& data, const TeamData& teamData);
namespace JSONUtils {
// check required data for reqeust
std::string CheckRequiredData(const nlohmann::json& data, const std::vector<std::string>& requiredData);
}
#endif // __JSONUTILS_H__

View File

@@ -12,8 +12,6 @@
#include "ChatPackets.h"
#include "dConfig.h"
#include "MessageType/Chat.h"
#include "ChatWeb.h"
#include "TeamContainer.h"
void PlayerContainer::Initialize() {
m_MaxNumberOfBestFriends =
@@ -54,15 +52,14 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
if (!inStream.Read(data.zoneID)) return;
if (!inStream.Read(data.muteExpire)) return;
if (!inStream.Read(data.gmLevel)) return;
data.worldServerSysAddr = packet->systemAddress;
data.sysAddr = packet->systemAddress;
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());
ChatWeb::SendWSPlayerUpdate(data, isLogin ? eActivityType::PlayerLoggedIn : eActivityType::PlayerChangedZone);
Database::Get()->UpdateActivityLog(data.playerID, isLogin ? eActivityType::PlayerLoggedIn : eActivityType::PlayerChangedZone, data.zoneID.GetMapID());
Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID());
m_PlayersToRemove.erase(playerId);
}
@@ -102,7 +99,7 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
if (fd) ChatPacketHandler::SendFriendUpdate(fd, player, 0, fr.isBestFriend);
}
auto* team = TeamContainer::GetTeam(playerID);
auto* team = GetTeam(playerID);
if (team != nullptr) {
const auto memberName = GeneralUtils::UTF8ToUTF16(player.playerName);
@@ -112,12 +109,10 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
if (!otherMember) continue;
TeamContainer::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 });
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 });
}
}
ChatWeb::SendWSPlayerUpdate(player, eActivityType::PlayerLoggedOut);
m_PlayerCount--;
LOG("Removed user: %llu", playerID);
m_Players.erase(playerID);
@@ -145,6 +140,40 @@ void PlayerContainer::MuteUpdate(Packet* packet) {
BroadcastMuteUpdate(playerID, expire);
}
void PlayerContainer::CreateTeamServer(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID;
inStream.Read(playerID);
size_t membersSize = 0;
inStream.Read(membersSize);
if (membersSize >= 4) {
LOG("Tried to create a team with more than 4 players");
return;
}
std::vector<LWOOBJID> members;
members.reserve(membersSize);
for (size_t i = 0; i < membersSize; i++) {
LWOOBJID member;
inStream.Read(member);
members.push_back(member);
}
LWOZONEID zoneId;
inStream.Read(zoneId);
auto* team = CreateLocalTeam(members);
if (team != nullptr) {
team->zoneId = zoneId;
UpdateTeamsOnWorld(team, false);
}
}
void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::GM_MUTE);
@@ -155,6 +184,221 @@ void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
}
TeamData* PlayerContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
if (members.empty()) {
return nullptr;
}
TeamData* newTeam = nullptr;
for (const auto member : members) {
auto* team = GetTeam(member);
if (team != nullptr) {
RemoveMember(team, member, false, false, true);
}
if (newTeam == nullptr) {
newTeam = CreateTeam(member, true);
} else {
AddMember(newTeam, member);
}
}
newTeam->lootFlag = 1;
TeamStatusUpdate(newTeam);
return newTeam;
}
TeamData* PlayerContainer::CreateTeam(LWOOBJID leader, bool local) {
auto* team = new TeamData();
team->teamID = ++m_TeamIDCounter;
team->leaderID = leader;
team->local = local;
GetTeamsMut().push_back(team);
AddMember(team, leader);
return team;
}
TeamData* PlayerContainer::GetTeam(LWOOBJID playerID) {
for (auto* team : GetTeams()) {
if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue;
return team;
}
return nullptr;
}
void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
if (team->memberIDs.size() >= 4) {
LOG("Tried to add player to team that already had 4 players");
const auto& player = GetPlayerData(playerID);
if (!player) return;
ChatPackets::SendSystemMessage(player.sysAddr, u"The teams is full! You have not been added to a team!");
return;
}
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
if (index != team->memberIDs.end()) return;
team->memberIDs.push_back(playerID);
const auto& leader = GetPlayerData(team->leaderID);
const auto& member = GetPlayerData(playerID);
if (!leader || !member) return;
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
const auto memberName = GeneralUtils::UTF8ToUTF16(member.playerName);
ChatPacketHandler::SendTeamInviteConfirm(member, false, leader.playerID, leader.zoneID, team->lootFlag, 0, 0, leaderName);
if (!team->local) {
ChatPacketHandler::SendTeamSetLeader(member, leader.playerID);
} else {
ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY);
}
UpdateTeamsOnWorld(team, false);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = GetPlayerData(memberId);
if (otherMember == member) continue;
const auto otherMemberName = GetName(memberId);
ChatPacketHandler::SendTeamAddPlayer(member, false, team->local, false, memberId, otherMemberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
if (otherMember) {
ChatPacketHandler::SendTeamAddPlayer(otherMember, false, team->local, false, member.playerID, memberName, member.zoneID);
}
}
}
void PlayerContainer::RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent) {
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
if (index == team->memberIDs.end()) return;
const auto& member = GetPlayerData(playerID);
if (member && !silent) {
ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY);
}
const auto memberName = GetName(playerID);
for (const auto memberId : team->memberIDs) {
if (silent && memberId == playerID) {
continue;
}
const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue;
ChatPacketHandler::SendTeamRemovePlayer(otherMember, disband, kicked, leaving, false, team->leaderID, playerID, memberName);
}
team->memberIDs.erase(index);
UpdateTeamsOnWorld(team, false);
if (team->memberIDs.size() <= 1) {
DisbandTeam(team);
} else {
if (playerID == team->leaderID) {
PromoteMember(team, team->memberIDs[0]);
}
}
}
void PlayerContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) {
team->leaderID = newLeader;
for (const auto memberId : team->memberIDs) {
const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue;
ChatPacketHandler::SendTeamSetLeader(otherMember, newLeader);
}
}
void PlayerContainer::DisbandTeam(TeamData* team) {
const auto index = std::find(GetTeams().begin(), GetTeams().end(), team);
if (index == GetTeams().end()) return;
for (const auto memberId : team->memberIDs) {
const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue;
const auto memberName = GeneralUtils::UTF8ToUTF16(otherMember.playerName);
ChatPacketHandler::SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, otherMember.playerID, memberName);
}
UpdateTeamsOnWorld(team, true);
GetTeamsMut().erase(index);
delete team;
}
void PlayerContainer::TeamStatusUpdate(TeamData* team) {
const auto index = std::find(GetTeams().begin(), GetTeams().end(), team);
if (index == GetTeams().end()) return;
const auto& leader = GetPlayerData(team->leaderID);
if (!leader) return;
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue;
if (!team->local) {
ChatPacketHandler::SendTeamStatus(otherMember, team->leaderID, leader.zoneID, team->lootFlag, 0, leaderName);
}
}
UpdateTeamsOnWorld(team, false);
}
void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
bitStream.Write(team->teamID);
bitStream.Write(deleteTeam);
if (!deleteTeam) {
bitStream.Write(team->lootFlag);
bitStream.Write<char>(team->memberIDs.size());
for (const auto memberID : team->memberIDs) {
bitStream.Write(memberID);
}
}
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
}
std::u16string PlayerContainer::GetName(LWOOBJID playerID) {
const auto iter = m_Names.find(playerID);
@@ -203,4 +447,5 @@ void PlayerContainer::Shutdown() {
Database::Get()->UpdateActivityLog(id, eActivityType::PlayerLoggedOut, playerData.zoneID.GetMapID());
m_Players.erase(m_Players.begin());
}
for (auto* team : GetTeams()) if (team) delete team;
}

View File

@@ -11,6 +11,10 @@ enum class eGameMasterLevel : uint8_t;
struct TeamData;
struct TeamContainer {
std::vector<TeamData*> mTeams;
};
struct IgnoreData {
IgnoreData(const std::string& name, const LWOOBJID& id) : playerName{ name }, playerId{ id } {}
inline bool operator==(const std::string& other) const noexcept {
@@ -38,7 +42,7 @@ struct PlayerData {
return muteExpire == 1 || muteExpire > time(NULL);
}
SystemAddress worldServerSysAddr{};
SystemAddress sysAddr{};
LWOZONEID zoneID{};
LWOOBJID playerID = LWOOBJID_EMPTY;
time_t muteExpire = 0;
@@ -69,6 +73,7 @@ public:
void ScheduleRemovePlayer(Packet* packet);
void RemovePlayer(const LWOOBJID playerID);
void MuteUpdate(Packet* packet);
void CreateTeamServer(Packet* packet);
void BroadcastMuteUpdate(LWOOBJID player, time_t time);
void Shutdown();
@@ -76,19 +81,34 @@ public:
const PlayerData& GetPlayerData(const std::string& playerName);
PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID);
PlayerData& GetPlayerDataMutable(const std::string& playerName);
std::u16string GetName(LWOOBJID playerID);
LWOOBJID GetId(const std::u16string& playerName);
void Update(const float deltaTime);
uint32_t GetPlayerCount() { return m_PlayerCount; };
uint32_t GetSimCount() { return m_SimCount; };
const std::map<LWOOBJID, PlayerData>& GetAllPlayers() const { return m_Players; };
TeamData* CreateLocalTeam(std::vector<LWOOBJID> members);
TeamData* CreateTeam(LWOOBJID leader, bool local = false);
TeamData* GetTeam(LWOOBJID playerID);
void AddMember(TeamData* team, LWOOBJID playerID);
void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false);
void PromoteMember(TeamData* team, LWOOBJID newLeader);
void DisbandTeam(TeamData* team);
void TeamStatusUpdate(TeamData* team);
void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam);
std::u16string GetName(LWOOBJID playerID);
LWOOBJID GetId(const std::u16string& playerName);
uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; }
uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; }
const TeamContainer& GetTeamContainer() { return m_TeamContainer; }
std::vector<TeamData*>& GetTeamsMut() { return m_TeamContainer.mTeams; };
const std::vector<TeamData*>& GetTeams() { return GetTeamsMut(); };
void Update(const float deltaTime);
bool PlayerBeingRemoved(const LWOOBJID playerID) { return m_PlayersToRemove.contains(playerID); }
private:
LWOOBJID m_TeamIDCounter = 0;
std::map<LWOOBJID, PlayerData> m_Players;
TeamContainer m_TeamContainer{};
std::unordered_map<LWOOBJID, std::u16string> m_Names;
std::map<LWOOBJID, float> m_PlayersToRemove;
uint32_t m_MaxNumberOfBestFriends = 5;

View File

@@ -1,669 +0,0 @@
#include "TeamContainer.h"
#include "ChatPackets.h"
#include "MessageType/Chat.h"
#include "MessageType/Game.h"
#include "ChatPacketHandler.h"
#include "PlayerContainer.h"
namespace {
TeamContainer::Data g_TeamContainer{};
LWOOBJID g_TeamIDCounter = 0;
}
const TeamContainer::Data& TeamContainer::GetTeamContainer() {
return g_TeamContainer;
}
std::vector<TeamData*>& TeamContainer::GetTeamsMut() {
return g_TeamContainer.mTeams;
}
const std::vector<TeamData*>& TeamContainer::GetTeams() {
return GetTeamsMut();
}
void TeamContainer::Shutdown() {
for (auto* team : g_TeamContainer.mTeams) if (team) delete team;
}
void TeamContainer::HandleTeamInvite(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID;
LUWString invitedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(invitedPlayer);
const auto& player = Game::playerContainer.GetPlayerData(playerID);
if (!player) return;
auto* team = GetTeam(playerID);
if (team == nullptr) {
team = CreateTeam(playerID);
}
const auto& other = Game::playerContainer.GetPlayerData(invitedPlayer.GetAsString());
if (!other) return;
if (GetTeam(other.playerID) != nullptr) {
return;
}
if (team->memberIDs.size() > 3) {
// no more teams greater than 4
LOG("Someone tried to invite a 5th player to a team");
return;
}
SendTeamInvite(other, player);
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str());
bool failed = false;
for (const auto& ignore : other.ignoredPlayers) {
if (ignore.playerId == player.playerID) {
failed = true;
break;
}
}
ChatPackets::TeamInviteInitialResponse response{};
response.inviteFailedToSend = failed;
response.playerName = invitedPlayer.string;
ChatPackets::SendRoutedMsg(response, playerID, player.worldServerSysAddr);
}
void TeamContainer::HandleTeamInviteResponse(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
char declined = 0;
inStream.Read(declined);
LWOOBJID leaderID = LWOOBJID_EMPTY;
inStream.Read(leaderID);
LOG("Invite reponse received: %llu -> %llu (%d)", playerID, leaderID, declined);
if (declined) {
return;
}
auto* team = GetTeam(leaderID);
if (team == nullptr) {
LOG("Failed to find team for leader (%llu)", leaderID);
team = GetTeam(playerID);
}
if (team == nullptr) {
LOG("Failed to find team for player (%llu)", playerID);
return;
}
AddMember(team, playerID);
}
void TeamContainer::HandleTeamLeave(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
auto* team = GetTeam(playerID);
LOG("(%llu) leaving team", playerID);
if (team != nullptr) {
RemoveMember(team, playerID, false, false, true);
}
}
void TeamContainer::HandleTeamKick(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
LUWString kickedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(kickedPlayer);
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str());
const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString());
LWOOBJID kickedId = LWOOBJID_EMPTY;
if (kicked) {
kickedId = kicked.playerID;
} else {
kickedId = Game::playerContainer.GetId(kickedPlayer.string);
}
if (kickedId == LWOOBJID_EMPTY) return;
auto* team = GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID || team->leaderID == kickedId) return;
RemoveMember(team, kickedId, false, true, false);
}
}
void TeamContainer::HandleTeamPromote(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
LUWString promotedPlayer;
inStream.Read(playerID);
inStream.IgnoreBytes(4);
inStream.Read(promotedPlayer);
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str());
const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString());
if (!promoted) return;
auto* team = GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
PromoteMember(team, promoted.playerID);
}
}
void TeamContainer::HandleTeamLootOption(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
uint32_t size = 0;
inStream.Read(size);
char option;
inStream.Read(option);
auto* team = GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
team->lootFlag = option;
TeamStatusUpdate(team);
UpdateTeamsOnWorld(team, false);
}
}
void TeamContainer::HandleTeamStatusRequest(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
auto* team = GetTeam(playerID);
const auto& data = Game::playerContainer.GetPlayerData(playerID);
if (team != nullptr && data) {
LOG_DEBUG("Player %llu is requesting team status", playerID);
if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
RemoveMember(team, playerID, false, false, false, true);
return;
}
if (team->memberIDs.size() <= 1 && !team->local) {
DisbandTeam(team, LWOOBJID_EMPTY, u"");
return;
}
if (!team->local) {
SendTeamSetLeader(data, team->leaderID);
} else {
SendTeamSetLeader(data, LWOOBJID_EMPTY);
}
TeamStatusUpdate(team);
const auto leaderName = GeneralUtils::UTF8ToUTF16(data.playerName);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (memberId == playerID) continue;
const auto memberName = Game::playerContainer.GetName(memberId);
if (otherMember) {
SendTeamSetOffWorldFlag(otherMember, data.playerID, data.zoneID);
}
SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
}
UpdateTeamsOnWorld(team, false);
}
}
void TeamContainer::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE);
bitStream.Write(LUWString(sender.playerName.c_str()));
bitStream.Write(sender.playerID);
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM);
bitStream.Write(bLeaderIsFreeTrial);
bitStream.Write(i64LeaderID);
bitStream.Write(i64LeaderZoneID);
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write(ucResponseCode);
bitStream.Write<uint32_t>(wsLeaderName.size());
for (const auto character : wsLeaderName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE);
bitStream.Write(i64LeaderID);
bitStream.Write(i64LeaderZoneID);
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
bitStream.Write(ucLootFlag);
bitStream.Write(ucNumOfOtherPlayers);
bitStream.Write<uint32_t>(wsLeaderName.size());
for (const auto character : wsLeaderName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_SET_LEADER);
bitStream.Write(i64PlayerID);
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER);
bitStream.Write(bIsFreeTrial);
bitStream.Write(bLocal);
bitStream.Write(bNoLootOnDeath);
bitStream.Write(i64PlayerID);
bitStream.Write<uint32_t>(wsPlayerName.size());
for (const auto character : wsPlayerName) {
bitStream.Write(character);
}
bitStream.Write1();
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER);
bitStream.Write(bDisband);
bitStream.Write(bIsKicked);
bitStream.Write(bIsLeaving);
bitStream.Write(bLocal);
bitStream.Write(i64LeaderID);
bitStream.Write(i64PlayerID);
bitStream.Write<uint32_t>(wsPlayerName.size());
for (const auto character : wsPlayerName) {
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
CMSGHEADER;
bitStream.Write(receiver.playerID);
bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG);
bitStream.Write(i64PlayerID);
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
void TeamContainer::CreateTeamServer(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID;
inStream.Read(playerID);
size_t membersSize = 0;
inStream.Read(membersSize);
if (membersSize >= 4) {
LOG("Tried to create a team with more than 4 players");
return;
}
std::vector<LWOOBJID> members;
members.reserve(membersSize);
for (size_t i = 0; i < membersSize; i++) {
LWOOBJID member;
inStream.Read(member);
members.push_back(member);
}
LWOZONEID zoneId;
inStream.Read(zoneId);
auto* team = CreateLocalTeam(members);
if (team != nullptr) {
team->zoneId = zoneId;
UpdateTeamsOnWorld(team, false);
}
}
TeamData* TeamContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
if (members.empty()) {
return nullptr;
}
TeamData* newTeam = nullptr;
for (const auto member : members) {
auto* team = GetTeam(member);
if (team != nullptr) {
RemoveMember(team, member, false, false, true);
}
if (newTeam == nullptr) {
newTeam = CreateTeam(member, true);
} else {
AddMember(newTeam, member);
}
}
newTeam->lootFlag = 1;
TeamStatusUpdate(newTeam);
return newTeam;
}
TeamData* TeamContainer::CreateTeam(LWOOBJID leader, bool local) {
auto* team = new TeamData();
team->teamID = ++g_TeamIDCounter;
team->leaderID = leader;
team->local = local;
GetTeamsMut().push_back(team);
AddMember(team, leader);
return team;
}
TeamData* TeamContainer::GetTeam(LWOOBJID playerID) {
for (auto* team : GetTeams()) {
if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue;
return team;
}
return nullptr;
}
void TeamContainer::AddMember(TeamData* team, LWOOBJID playerID) {
if (team->memberIDs.size() >= 4) {
LOG("Tried to add player to team that already had 4 players");
const auto& player = Game::playerContainer.GetPlayerData(playerID);
if (!player) return;
ChatPackets::SendSystemMessage(player.worldServerSysAddr, u"The teams is full! You have not been added to a team!");
return;
}
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
if (index != team->memberIDs.end()) return;
team->memberIDs.push_back(playerID);
const auto& leader = Game::playerContainer.GetPlayerData(team->leaderID);
const auto& member = Game::playerContainer.GetPlayerData(playerID);
if (!leader || !member) return;
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
const auto memberName = GeneralUtils::UTF8ToUTF16(member.playerName);
SendTeamInviteConfirm(member, false, leader.playerID, leader.zoneID, team->lootFlag, 0, 0, leaderName);
if (!team->local) {
SendTeamSetLeader(member, leader.playerID);
} else {
SendTeamSetLeader(member, LWOOBJID_EMPTY);
}
UpdateTeamsOnWorld(team, false);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (otherMember == member) continue;
const auto otherMemberName = Game::playerContainer.GetName(memberId);
SendTeamAddPlayer(member, false, team->local, false, memberId, otherMemberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
if (otherMember) {
SendTeamAddPlayer(otherMember, false, team->local, false, member.playerID, memberName, member.zoneID);
}
}
}
void TeamContainer::RemoveMember(TeamData* team, LWOOBJID causingPlayerID, bool disband, bool kicked, bool leaving, bool silent) {
LOG_DEBUG("Player %llu is leaving team %i", causingPlayerID, team->teamID);
const auto index = std::ranges::find(team->memberIDs, causingPlayerID);
if (index == team->memberIDs.end()) return;
team->memberIDs.erase(index);
const auto& member = Game::playerContainer.GetPlayerData(causingPlayerID);
const auto causingMemberName = Game::playerContainer.GetName(causingPlayerID);
if (member && !silent) {
SendTeamRemovePlayer(member, disband, kicked, leaving, team->local, LWOOBJID_EMPTY, causingPlayerID, causingMemberName);
}
if (team->memberIDs.size() <= 1) {
DisbandTeam(team, causingPlayerID, causingMemberName);
} else /* team has enough members to be a team still */ {
team->leaderID = (causingPlayerID == team->leaderID) ? team->memberIDs[0] : team->leaderID;
for (const auto memberId : team->memberIDs) {
if (silent && memberId == causingPlayerID) {
continue;
}
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (!otherMember) continue;
SendTeamRemovePlayer(otherMember, disband, kicked, leaving, team->local, team->leaderID, causingPlayerID, causingMemberName);
}
UpdateTeamsOnWorld(team, false);
}
}
void TeamContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) {
team->leaderID = newLeader;
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (!otherMember) continue;
SendTeamSetLeader(otherMember, newLeader);
}
}
void TeamContainer::DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName) {
const auto index = std::ranges::find(GetTeams(), team);
if (index == GetTeams().end()) return;
LOG_DEBUG("Disbanding team %i", (*index)->teamID);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (!otherMember) continue;
SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, causingPlayerID, causingPlayerName);
}
UpdateTeamsOnWorld(team, true);
GetTeamsMut().erase(index);
delete team;
}
void TeamContainer::TeamStatusUpdate(TeamData* team) {
const auto index = std::find(GetTeams().begin(), GetTeams().end(), team);
if (index == GetTeams().end()) return;
const auto& leader = Game::playerContainer.GetPlayerData(team->leaderID);
if (!leader) return;
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
if (!otherMember) continue;
if (!team->local) {
SendTeamStatus(otherMember, team->leaderID, leader.zoneID, team->lootFlag, 0, leaderName);
}
}
UpdateTeamsOnWorld(team, false);
}
void TeamContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
bitStream.Write(team->teamID);
bitStream.Write(deleteTeam);
if (!deleteTeam) {
bitStream.Write(team->lootFlag);
bitStream.Write<char>(team->memberIDs.size());
for (const auto memberID : team->memberIDs) {
bitStream.Write(memberID);
}
}
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
}

View File

@@ -1,59 +0,0 @@
// Darkflame Universe
// Copyright 2025
#ifndef TEAMCONTAINER_H
#define TEAMCONTAINER_H
#include <cstdint>
#include <string>
#include <vector>
#include "dCommonVars.h"
struct Packet;
struct PlayerData;
struct TeamData;
namespace TeamContainer {
struct Data {
std::vector<TeamData*> mTeams;
};
void Shutdown();
void HandleTeamInvite(Packet* packet);
void HandleTeamInviteResponse(Packet* packet);
void HandleTeamLeave(Packet* packet);
void HandleTeamKick(Packet* packet);
void HandleTeamPromote(Packet* packet);
void HandleTeamLootOption(Packet* packet);
void HandleTeamStatusRequest(Packet* packet);
void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender);
void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName);
void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName);
void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID);
void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID);
/* Sends a message to the provided `receiver` with information about the updated team. If `i64LeaderID` is not LWOOBJID_EMPTY, the client will update the leader to that new playerID. */
void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName);
void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);
void CreateTeamServer(Packet* packet);
TeamData* CreateLocalTeam(std::vector<LWOOBJID> members);
TeamData* CreateTeam(LWOOBJID leader, bool local = false);
TeamData* GetTeam(LWOOBJID playerID);
void AddMember(TeamData* team, LWOOBJID playerID);
void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false);
void PromoteMember(TeamData* team, LWOOBJID newLeader);
void DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName);
void TeamStatusUpdate(TeamData* team);
void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam);
const TeamContainer::Data& GetTeamContainer();
std::vector<TeamData*>& GetTeamsMut();
const std::vector<TeamData*>& GetTeams();
};
#endif //!TEAMCONTAINER_H

View File

@@ -8,7 +8,6 @@
#include "Database.h"
#include "Game.h"
#include "Sd0.h"
#include "ZCompression.h"
#include "Logger.h"
@@ -45,10 +44,10 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
}
// Ignore the valgrind warning about uninitialized values. These are discarded later when we know the actual uncompressed size.
std::unique_ptr<uint8_t[]> uncompressedChunk(new uint8_t[Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE]);
std::unique_ptr<uint8_t[]> uncompressedChunk(new uint8_t[ZCompression::MAX_SD0_CHUNK_SIZE]);
int32_t err{};
int32_t actualUncompressedSize = ZCompression::Decompress(
compressedChunk.get(), chunkSize, uncompressedChunk.get(), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err);
compressedChunk.get(), chunkSize, uncompressedChunk.get(), ZCompression::MAX_SD0_CHUNK_SIZE, err);
if (actualUncompressedSize != -1) {
uint32_t previousSize = completeUncompressedModel.size();
@@ -118,7 +117,7 @@ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
}
std::string outputString(sd0ConvertedModel.get(), oldLxfmlSizeWithHeader);
std::stringstream outputStringStream(outputString);
std::istringstream outputStringStream(outputString);
try {
Database::Get()->UpdateUgcModelData(model.id, outputStringStream);

View File

@@ -16,11 +16,6 @@ set(DCOMMON_SOURCES
"BrickByBrickFix.cpp"
"BinaryPathFinder.cpp"
"FdbToSqlite.cpp"
"JSONUtils.cpp"
"TinyXmlUtils.cpp"
"Sd0.cpp"
"Lxfml.cpp"
"LxfmlBugged.cpp"
)
# Workaround for compiler bug where the optimized code could result in a memcpy of 0 bytes, even though that isnt possible.

View File

@@ -4,7 +4,7 @@
#include <assert.h>
#ifdef _DEBUG
# define DluAssert(expression) do { assert(expression); } while(0)
# define DluAssert(expression) assert(expression)
#else
# define DluAssert(expression)
#endif

View File

@@ -1,17 +0,0 @@
#include "JSONUtils.h"
#include "json.hpp"
using json = nlohmann::json;
std::string JSONUtils::CheckRequiredData(const json& data, const std::vector<std::string>& requiredData) {
json check;
check["error"] = json::array();
for (const auto& required : requiredData) {
if (!data.contains(required)) {
check["error"].push_back("Missing Parameter: " + required);
} else if (data[required] == "") {
check["error"].push_back("Empty Parameter: " + required);
}
}
return check["error"].empty() ? "" : check.dump();
}

View File

@@ -1,11 +0,0 @@
#ifndef _JSONUTILS_H_
#define _JSONUTILS_H_
#include "json_fwd.hpp"
namespace JSONUtils {
// check required fields in json data
std::string CheckRequiredData(const nlohmann::json& data, const std::vector<std::string>& requiredData);
}
#endif // _JSONUTILS_H_

View File

@@ -83,12 +83,6 @@ public:
this->value = value;
}
//! Initializer
LDFData(const std::string& key, const T& value) {
this->key = GeneralUtils::ASCIIToUTF16(key);
this->value = value;
}
//! Destructor
~LDFData(void) override {}

View File

@@ -29,8 +29,8 @@ constexpr const char* GetFileNameFromAbsolutePath(const char* path) {
// they will not be valid constexpr and will be evaluated at runtime instead of compile time!
// The full string is still stored in the binary, however the offset of the filename in the absolute paths
// is used in the instruction instead of the start of the absolute path.
#define LOG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->Log(str_, message, ##__VA_ARGS__); } while(0)
#define LOG_DEBUG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->LogDebug(str_, message, ##__VA_ARGS__); } while(0)
#define LOG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->Log(str, message, ##__VA_ARGS__); } while(0)
#define LOG_DEBUG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->LogDebug(str, message, ##__VA_ARGS__); } while(0)
// Writer class for writing data to files.
class Writer {

View File

@@ -1,119 +0,0 @@
#include "Lxfml.h"
#include "GeneralUtils.h"
#include "StringifiedEnum.h"
#include "TinyXmlUtils.h"
#include <ranges>
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
Result toReturn;
tinyxml2::XMLDocument doc;
const auto err = doc.Parse(data.data());
if (err != tinyxml2::XML_SUCCESS) {
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
return toReturn;
}
TinyXmlUtils::DocumentReader reader(doc);
std::map<std::string/* refID */, std::string> transformations;
auto lxfml = reader["LXFML"];
if (!lxfml) {
LOG("Failed to find LXFML element.");
return toReturn;
}
// First get all the positions of bricks
for (const auto& brick : lxfml["Bricks"]) {
const auto* part = brick.FirstChildElement("Part");
while (part) {
const auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) transformations[refID] = transformation;
}
}
part = part->NextSiblingElement("Part");
}
}
// These points are well out of bounds for an actual player
NiPoint3 lowest{ 10'000.0f, 10'000.0f, 10'000.0f };
NiPoint3 highest{ -10'000.0f, -10'000.0f, -10'000.0f };
// Calculate the lowest and highest points on the entire model
for (const auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value();
auto y = GeneralUtils::TryParse<float>(split[10]).value();
auto z = GeneralUtils::TryParse<float>(split[11]).value();
if (x < lowest.x) lowest.x = x;
if (y < lowest.y) lowest.y = y;
if (z < lowest.z) lowest.z = z;
if (highest.x < x) highest.x = x;
if (highest.y < y) highest.y = y;
if (highest.z < z) highest.z = z;
}
auto delta = (highest - lowest) / 2.0f;
auto newRootPos = lowest + delta;
// Clamp the Y to the lowest point on the model
newRootPos.y = lowest.y;
// newRootPos = newRootPos - NiPoint3{-0.4f, 0, 0.4};
const auto posToReturn = newRootPos;
// Adjust all positions to account for the new origin
for (auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
std::stringstream stream;
for (int i = 0; i < 9; i++) {
stream << split[i];
stream << ',';
}
stream << x << ',' << y << ',' << z;
transformation = stream.str();
}
// Finally write the new transformation back into the lxfml
for (auto& brick : lxfml["Bricks"]) {
auto* part = brick.FirstChildElement("Part");
while (part) {
auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) {
bone->SetAttribute("transformation", transformations[refID].c_str());
}
}
}
part = part->NextSiblingElement("Part");
}
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
toReturn.lxfml = printer.CStr();
toReturn.center = posToReturn;
return toReturn;
}

View File

@@ -1,27 +0,0 @@
// Darkflame Universe
// Copyright 2025
#ifndef LXFML_H
#define LXFML_H
#include <string>
#include <string_view>
#include "NiPoint3.h"
namespace Lxfml {
struct Result {
std::string lxfml;
NiPoint3 center;
};
// Normalizes a LXFML model to be positioned relative to its local 0, 0, 0 rather than a game worlds 0, 0, 0.
// Returns a struct of its new center and the updated LXFML containing these edits.
[[nodiscard]] Result NormalizePosition(const std::string_view data);
// these are only for the migrations due to a bug in one of the implementations.
[[nodiscard]] Result NormalizePositionOnlyFirstPart(const std::string_view data);
[[nodiscard]] Result NormalizePositionAfterFirstPart(const std::string_view data, const NiPoint3& position);
};
#endif //!LXFML_H

View File

@@ -1,215 +0,0 @@
#include "Lxfml.h"
#include "GeneralUtils.h"
#include "StringifiedEnum.h"
#include "TinyXmlUtils.h"
#include <ranges>
// this file should not be touched
Lxfml::Result Lxfml::NormalizePositionOnlyFirstPart(const std::string_view data) {
// this is bugged
return;
Result toReturn;
tinyxml2::XMLDocument doc;
const auto err = doc.Parse(data.data());
if (err != tinyxml2::XML_SUCCESS) {
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
return toReturn;
}
TinyXmlUtils::DocumentReader reader(doc);
std::map<std::string/* refID */, std::string> transformations;
auto lxfml = reader["LXFML"];
if (!lxfml) {
LOG("Failed to find LXFML element.");
return toReturn;
}
// First get all the positions of bricks
for (const auto& brick : lxfml["Bricks"]) {
const auto* part = brick.FirstChildElement("Part");
if (part) {
const auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) transformations[refID] = transformation;
}
}
}
}
// These points are well out of bounds for an actual player
NiPoint3 lowest{ 10'000.0f, 10'000.0f, 10'000.0f };
NiPoint3 highest{ -10'000.0f, -10'000.0f, -10'000.0f };
// Calculate the lowest and highest points on the entire model
for (const auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value();
auto y = GeneralUtils::TryParse<float>(split[10]).value();
auto z = GeneralUtils::TryParse<float>(split[11]).value();
if (x < lowest.x) lowest.x = x;
if (y < lowest.y) lowest.y = y;
if (z < lowest.z) lowest.z = z;
if (highest.x < x) highest.x = x;
if (highest.y < y) highest.y = y;
if (highest.z < z) highest.z = z;
}
auto delta = (highest - lowest) / 2.0f;
auto newRootPos = lowest + delta;
// Clamp the Y to the lowest point on the model
newRootPos.y = lowest.y;
// Adjust all positions to account for the new origin
for (auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
std::stringstream stream;
for (int i = 0; i < 9; i++) {
stream << split[i];
stream << ',';
}
stream << x << ',' << y << ',' << z;
transformation = stream.str();
}
// Finally write the new transformation back into the lxfml
for (auto& brick : lxfml["Bricks"]) {
auto* part = brick.FirstChildElement("Part");
if (part) {
auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) {
bone->SetAttribute("transformation", transformations[refID].c_str());
}
}
}
}
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
toReturn.lxfml = printer.CStr();
toReturn.center = newRootPos;
return toReturn;
}
Lxfml::Result Lxfml::NormalizePositionAfterFirstPart(const std::string_view data, const NiPoint3& position) {
// this is bugged
return;
Result toReturn;
tinyxml2::XMLDocument doc;
const auto err = doc.Parse(data.data());
if (err != tinyxml2::XML_SUCCESS) {
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
return toReturn;
}
TinyXmlUtils::DocumentReader reader(doc);
std::map<std::string/* refID */, std::string> transformations;
auto lxfml = reader["LXFML"];
if (!lxfml) {
LOG("Failed to find LXFML element.");
return toReturn;
}
// First get all the positions of bricks
for (const auto& brick : lxfml["Bricks"]) {
const auto* part = brick.FirstChildElement("Part");
bool firstPart = true;
while (part) {
if (firstPart) {
firstPart = false;
} else {
LOG("Found extra bricks");
const auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) transformations[refID] = transformation;
}
}
}
part = part->NextSiblingElement("Part");
}
}
auto newRootPos = position;
// Adjust all positions to account for the new origin
for (auto& transformation : transformations | std::views::values) {
auto split = GeneralUtils::SplitString(transformation, ',');
if (split.size() < 12) {
LOG("Not enough in the split?");
continue;
}
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
std::stringstream stream;
for (int i = 0; i < 9; i++) {
stream << split[i];
stream << ',';
}
stream << x << ',' << y << ',' << z;
transformation = stream.str();
}
// Finally write the new transformation back into the lxfml
for (auto& brick : lxfml["Bricks"]) {
auto* part = brick.FirstChildElement("Part");
bool firstPart = true;
while (part) {
if (firstPart) {
firstPart = false;
} else {
auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
if (transformation) {
auto* refID = bone->Attribute("refID");
if (refID) {
bone->SetAttribute("transformation", transformations[refID].c_str());
}
}
}
}
part = part->NextSiblingElement("Part");
}
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
toReturn.lxfml = printer.CStr();
toReturn.center = newRootPos;
return toReturn;
}

View File

@@ -1,150 +0,0 @@
#include "Sd0.h"
#include <array>
#include <ranges>
#include "BinaryIO.h"
#include "Game.h"
#include "Logger.h"
#include "ZCompression.h"
// Insert header if on first buffer
void WriteHeader(Sd0::BinaryBuffer& chunk) {
chunk.push_back(Sd0::SD0_HEADER[0]);
chunk.push_back(Sd0::SD0_HEADER[1]);
chunk.push_back(Sd0::SD0_HEADER[2]);
chunk.push_back(Sd0::SD0_HEADER[3]);
chunk.push_back(Sd0::SD0_HEADER[4]);
}
// Write the size of the buffer to a chunk
void WriteSize(Sd0::BinaryBuffer& chunk, uint32_t chunkSize) {
for (int i = 0; i < 4; i++) {
char toPush = chunkSize & 0xff;
chunkSize = chunkSize >> 8;
chunk.push_back(toPush);
}
}
int32_t GetDataOffset(bool firstBuffer) {
return firstBuffer ? 9 : 4;
}
Sd0::Sd0(std::istream& buffer) {
char header[5]{};
// Check if this is an sd0 buffer. It's possible we may be handed a zlib buffer directly due to old code so check for that too.
if (!BinaryIO::BinaryRead(buffer, header) || memcmp(header, SD0_HEADER, sizeof(header)) != 0) {
LOG("Failed to read SD0 header %i %i %i %i %i %i %i", buffer.good(), buffer.tellg(), header[0], header[1], header[2], header[3], header[4]);
LOG_DEBUG("This may be a zlib buffer directly? Trying again assuming its a zlib buffer.");
auto& firstChunk = m_Chunks.emplace_back();
WriteHeader(firstChunk);
buffer.seekg(0, std::ios::end);
uint32_t bufferSize = buffer.tellg();
buffer.seekg(0, std::ios::beg);
WriteSize(firstChunk, bufferSize);
firstChunk.resize(firstChunk.size() + bufferSize);
auto* dataStart = reinterpret_cast<char*>(firstChunk.data() + GetDataOffset(true));
if (!buffer.read(dataStart, bufferSize)) {
m_Chunks.pop_back();
LOG("Failed to read %u bytes from chunk %i", bufferSize, m_Chunks.size() - 1);
}
return;
}
while (buffer && buffer.peek() != std::istream::traits_type::eof()) {
uint32_t chunkSize{};
if (!BinaryIO::BinaryRead(buffer, chunkSize)) {
LOG("Failed to read chunk size from stream %lld %zu", buffer.tellg(), m_Chunks.size());
break;
}
auto& chunk = m_Chunks.emplace_back();
bool firstBuffer = m_Chunks.size() == 1;
auto dataOffset = GetDataOffset(firstBuffer);
// Insert header if on first buffer
if (firstBuffer) {
WriteHeader(chunk);
}
WriteSize(chunk, chunkSize);
chunk.resize(chunkSize + dataOffset);
auto* dataStart = reinterpret_cast<char*>(chunk.data() + dataOffset);
if (!buffer.read(dataStart, chunkSize)) {
m_Chunks.pop_back();
LOG("Failed to read %u bytes from chunk %i", chunkSize, m_Chunks.size() - 1);
break;
}
}
}
void Sd0::FromData(const uint8_t* data, size_t bufferSize) {
const auto originalBufferSize = bufferSize;
if (bufferSize == 0) return;
m_Chunks.clear();
while (bufferSize > 0) {
const auto numToCopy = std::min(MAX_UNCOMPRESSED_CHUNK_SIZE, bufferSize);
const auto* startOffset = data + originalBufferSize - bufferSize;
bufferSize -= numToCopy;
std::array<uint8_t, MAX_UNCOMPRESSED_CHUNK_SIZE> compressedChunk;
const auto compressedSize = ZCompression::Compress(
startOffset, numToCopy,
compressedChunk.data(), compressedChunk.size());
auto& chunk = m_Chunks.emplace_back();
bool firstBuffer = m_Chunks.size() == 1;
auto dataOffset = GetDataOffset(firstBuffer);
if (firstBuffer) {
WriteHeader(chunk);
}
WriteSize(chunk, compressedSize);
chunk.resize(compressedSize + dataOffset);
memcpy(chunk.data() + dataOffset, compressedChunk.data(), compressedSize);
}
}
std::string Sd0::GetAsStringUncompressed() const {
std::string toReturn;
bool first = true;
uint32_t totalSize{};
for (const auto& chunk : m_Chunks) {
auto dataOffset = GetDataOffset(first);
first = false;
const auto chunkSize = chunk.size();
auto oldSize = toReturn.size();
toReturn.resize(oldSize + MAX_UNCOMPRESSED_CHUNK_SIZE);
int32_t error{};
const auto uncompressedSize = ZCompression::Decompress(
chunk.data() + dataOffset, chunkSize - dataOffset,
reinterpret_cast<uint8_t*>(toReturn.data()) + oldSize, MAX_UNCOMPRESSED_CHUNK_SIZE,
error);
totalSize += uncompressedSize;
}
toReturn.resize(totalSize);
return toReturn;
}
std::stringstream Sd0::GetAsStream() const {
std::stringstream toReturn;
for (const auto& chunk : m_Chunks) {
toReturn.write(reinterpret_cast<const char*>(chunk.data()), chunk.size());
}
return toReturn;
}
const std::vector<Sd0::BinaryBuffer>& Sd0::GetAsVector() const {
return m_Chunks;
}

View File

@@ -1,42 +0,0 @@
// Darkflame Universe
// Copyright 2025
#ifndef SD0_H
#define SD0_H
#include <fstream>
#include <vector>
// Sd0 is comprised of multiple zlib compressed buffers stored in a row.
// The format starts with a SD0 header (see SD0_HEADER) followed by the size of a zlib buffer, and then the zlib buffer itself.
// This repeats until end of file
class Sd0 {
public:
using BinaryBuffer = std::vector<uint8_t>;
static inline const char* SD0_HEADER = "sd0\x01\xff";
/**
* @brief Max size of an inflated sd0 zlib chunk
*/
static constexpr inline size_t MAX_UNCOMPRESSED_CHUNK_SIZE = 1024 * 256;
// Read the input buffer into an internal chunk stream to be used later
Sd0(std::istream& buffer);
// Uncompresses the entire Sd0 buffer and returns it as a string
[[nodiscard]] std::string GetAsStringUncompressed() const;
// Gets the Sd0 buffer as a stream in its raw compressed form
[[nodiscard]] std::stringstream GetAsStream() const;
// Gets the Sd0 buffer as a vector in its raw compressed form
[[nodiscard]] const std::vector<BinaryBuffer>& GetAsVector() const;
// Compress data into a Sd0 buffer
void FromData(const uint8_t* data, size_t bufferSize);
private:
std::vector<BinaryBuffer> m_Chunks{};
};
#endif //!SD0_H

View File

@@ -1,37 +0,0 @@
#include "TinyXmlUtils.h"
#include <tinyxml2.h>
using namespace TinyXmlUtils;
Element DocumentReader::operator[](const std::string_view elem) const {
return Element(m_Doc.FirstChildElement(elem.empty() ? nullptr : elem.data()), elem);
}
Element::Element(tinyxml2::XMLElement* xmlElem, const std::string_view elem) :
m_IteratedName{ elem },
m_Elem{ xmlElem } {
}
Element Element::operator[](const std::string_view elem) const {
const auto* usedElem = elem.empty() ? nullptr : elem.data();
auto* toReturn = m_Elem ? m_Elem->FirstChildElement(usedElem) : nullptr;
return Element(toReturn, m_IteratedName);
}
ElementIterator Element::begin() {
return ElementIterator(m_Elem ? m_Elem->FirstChildElement() : nullptr);
}
ElementIterator Element::end() {
return ElementIterator(nullptr);
}
ElementIterator::ElementIterator(tinyxml2::XMLElement* elem) :
m_CurElem{ elem } {
}
ElementIterator& ElementIterator::operator++() {
if (m_CurElem) m_CurElem = m_CurElem->NextSiblingElement();
return *this;
}

View File

@@ -1,66 +0,0 @@
// Darkflame Universe
// Copyright 2025
#ifndef TINYXMLUTILS_H
#define TINYXMLUTILS_H
#include <string>
#include "DluAssert.h"
#include <tinyxml2.h>
namespace TinyXmlUtils {
// See cstdlib for iterator technicalities
struct ElementIterator {
ElementIterator(tinyxml2::XMLElement* elem);
ElementIterator& operator++();
[[nodiscard]] tinyxml2::XMLElement* operator->() { DluAssert(m_CurElem); return m_CurElem; }
[[nodiscard]] tinyxml2::XMLElement& operator*() { DluAssert(m_CurElem); return *m_CurElem; }
bool operator==(const ElementIterator& other) const { return other.m_CurElem == m_CurElem; }
private:
tinyxml2::XMLElement* m_CurElem{ nullptr };
};
// Wrapper class to act as an iterator over xml elements.
// All the normal rules that apply to Iterators in the std library apply here.
class Element {
public:
Element(tinyxml2::XMLElement* xmlElem, const std::string_view elem);
// The first child element of this element.
[[nodiscard]] ElementIterator begin();
// Always returns an ElementIterator which points to nullptr.
// TinyXml2 return NULL when you've reached the last child element so
// you can't do any funny one past end logic here.
[[nodiscard]] ElementIterator end();
// Get a child element
[[nodiscard]] Element operator[](const std::string_view elem) const;
[[nodiscard]] Element operator[](const char* elem) const { return operator[](std::string_view(elem)); };
// Whether or not data exists for this element
operator bool() const { return m_Elem != nullptr; }
[[nodiscard]] const tinyxml2::XMLElement* operator->() const { return m_Elem; }
private:
const char* GetElementName() const { return m_IteratedName.empty() ? nullptr : m_IteratedName.c_str(); }
const std::string m_IteratedName;
tinyxml2::XMLElement* m_Elem;
};
class DocumentReader {
public:
DocumentReader(tinyxml2::XMLDocument& doc) : m_Doc{ doc } {}
[[nodiscard]] Element operator[](const std::string_view elem) const;
private:
tinyxml2::XMLDocument& m_Doc;
};
};
#endif //!TINYXMLUTILS_H

View File

@@ -8,5 +8,11 @@ namespace ZCompression {
int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst);
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr);
/**
* @brief Max size of an inflated sd0 zlib chunk
*
*/
constexpr uint32_t MAX_SD0_CHUNK_SIZE = 1024 * 256;
}

View File

@@ -6,9 +6,6 @@
#include "zlib.h"
constexpr uint32_t CRC32_INIT = 0xFFFFFFFF;
constexpr auto NULL_TERMINATOR = std::string_view{"\0\0\0", 4};
AssetManager::AssetManager(const std::filesystem::path& path) {
if (!std::filesystem::is_directory(path)) {
throw std::runtime_error("Attempted to load asset bundle (" + path.string() + ") however it is not a valid directory.");
@@ -21,20 +18,12 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
m_RootPath = m_Path;
m_ResPath = (m_Path / "client" / "res");
} else if (std::filesystem::exists(m_Path / "res" / "pack")) {
if (!std::filesystem::exists(m_Path / ".." / "versions")) {
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
}
} else if (std::filesystem::exists(m_Path / ".." / "versions") && std::filesystem::exists(m_Path / "res")) {
m_AssetBundleType = eAssetBundleType::Packed;
m_RootPath = (m_Path / "..");
m_ResPath = (m_Path / "res");
} else if (std::filesystem::exists(m_Path / "pack")) {
if (!std::filesystem::exists(m_Path / ".." / ".." / "versions")) {
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
}
} else if (std::filesystem::exists(m_Path / "pack") && std::filesystem::exists(m_Path / ".." / ".." / "versions")) {
m_AssetBundleType = eAssetBundleType::Packed;
m_RootPath = (m_Path / ".." / "..");
@@ -59,7 +48,6 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
break;
}
case eAssetBundleType::None:
[[fallthrough]];
case eAssetBundleType::Unpacked: {
break;
}
@@ -67,10 +55,19 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
}
void AssetManager::LoadPackIndex() {
m_PackIndex = PackIndex(m_RootPath);
m_PackIndex = new PackIndex(m_RootPath);
}
bool AssetManager::HasFile(std::string fixedName) const {
std::filesystem::path AssetManager::GetResPath() {
return m_ResPath;
}
eAssetBundleType AssetManager::GetAssetBundleType() {
return m_AssetBundleType;
}
bool AssetManager::HasFile(const char* name) {
auto fixedName = std::string(name);
std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); });
// Special case for unpacked client have BrickModels in upper case
@@ -84,7 +81,8 @@ bool AssetManager::HasFile(std::string fixedName) const {
std::replace(fixedName.begin(), fixedName.end(), '/', '\\');
if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName;
const auto crc = crc32b(crc32b(CRC32_INIT, fixedName), NULL_TERMINATOR);
uint32_t crc = crc32b(0xFFFFFFFF, reinterpret_cast<uint8_t*>(const_cast<char*>(fixedName.c_str())), fixedName.size());
crc = crc32b(crc, reinterpret_cast<Bytef*>(const_cast<char*>("\0\0\0\0")), 4);
for (const auto& item : this->m_PackIndex->GetPackFileIndices()) {
if (item.m_Crc == crc) {
@@ -95,7 +93,8 @@ bool AssetManager::HasFile(std::string fixedName) const {
return false;
}
bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) const {
bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
auto fixedName = std::string(name);
std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); });
std::replace(fixedName.begin(), fixedName.end(), '\\', '/'); // On the off chance someone has the wrong slashes, force forward slashes
@@ -130,7 +129,8 @@ bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) co
fixedName = "client\\res\\" + fixedName;
}
int32_t packIndex = -1;
auto crc = crc32b(crc32b(CRC32_INIT, fixedName), NULL_TERMINATOR);
uint32_t crc = crc32b(0xFFFFFFFF, reinterpret_cast<uint8_t*>(const_cast<char*>(fixedName.c_str())), fixedName.size());
crc = crc32b(crc, reinterpret_cast<Bytef*>(const_cast<char*>("\0\0\0\0")), 4);
for (const auto& item : this->m_PackIndex->GetPackFileIndices()) {
if (item.m_Crc == crc) {
@@ -144,13 +144,15 @@ bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) co
return false;
}
const auto& pack = this->m_PackIndex->GetPacks().at(packIndex);
const bool success = pack.ReadFileFromPack(crc, data, len);
auto packs = this->m_PackIndex->GetPacks();
auto* pack = packs.at(packIndex);
bool success = pack->ReadFileFromPack(crc, data, len);
return success;
}
AssetStream AssetManager::GetFile(const char* name) const {
AssetStream AssetManager::GetFile(const char* name) {
char* buf; uint32_t len;
bool success = this->GetFile(name, &buf, &len);
@@ -158,15 +160,23 @@ AssetStream AssetManager::GetFile(const char* name) const {
return AssetStream(buf, len, success);
}
uint32_t AssetManager::crc32b(uint32_t crc, const std::string_view message) {
for (const auto byte : message) {
uint32_t AssetManager::crc32b(uint32_t base, uint8_t* message, size_t l) {
size_t i, j;
uint32_t crc, msb;
crc = base;
for (i = 0; i < l; i++) {
// xor next byte to upper bits of crc
crc ^= (static_cast<uint32_t>(std::bit_cast<uint8_t>(byte)) << 24);
for (size_t _ = 0; _ < 8; _++) { // Do eight times.
const uint32_t msb = crc >> 31;
crc ^= (static_cast<unsigned int>(message[i]) << 24);
for (j = 0; j < 8; j++) { // Do eight times.
msb = crc >> 31;
crc <<= 1;
crc ^= (0 - msb) & 0x04C11DB7;
}
}
return crc; // don't complement crc on output
}
AssetManager::~AssetManager() {
delete m_PackIndex;
}

View File

@@ -61,32 +61,23 @@ struct AssetStream : std::istream {
class AssetManager {
public:
AssetManager(const std::filesystem::path& path);
~AssetManager();
[[nodiscard]]
const std::filesystem::path& GetResPath() const {
return m_ResPath;
}
[[nodiscard]]
eAssetBundleType GetAssetBundleType() const {
return m_AssetBundleType;
}
std::filesystem::path GetResPath();
eAssetBundleType GetAssetBundleType();
[[nodiscard]]
bool HasFile(std::string name) const;
[[nodiscard]]
bool GetFile(std::string name, char** data, uint32_t* len) const;
[[nodiscard]]
AssetStream GetFile(const char* name) const;
bool HasFile(const char* name);
bool GetFile(const char* name, char** data, uint32_t* len);
AssetStream GetFile(const char* name);
private:
void LoadPackIndex();
// Modified crc algorithm (mpeg2)
// Reference: https://stackoverflow.com/questions/54339800/how-to-modify-crc-32-to-crc-32-mpeg-2
static inline uint32_t crc32b(uint32_t crc, std::string_view message);
inline uint32_t crc32b(uint32_t base, uint8_t* message, size_t l);
bool m_SuccessfullyLoaded;
std::filesystem::path m_Path;
std::filesystem::path m_RootPath;
@@ -94,5 +85,5 @@ private:
eAssetBundleType m_AssetBundleType = eAssetBundleType::None;
std::optional<PackIndex> m_PackIndex;
PackIndex* m_PackIndex;
};

View File

@@ -1,7 +1,6 @@
#include "Pack.h"
#include "BinaryIO.h"
#include "Sd0.h"
#include "ZCompression.h"
Pack::Pack(const std::filesystem::path& filePath) {
@@ -22,20 +21,19 @@ Pack::Pack(const std::filesystem::path& filePath) {
m_FileStream.seekg(recordCountPos, std::ios::beg);
uint32_t recordCount = 0;
BinaryIO::BinaryRead<uint32_t>(m_FileStream, recordCount);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_RecordCount);
m_Records.reserve(recordCount);
std::generate_n(std::back_inserter(m_Records), recordCount, [&] {
for (int i = 0; i < m_RecordCount; i++) {
PackRecord record;
BinaryIO::BinaryRead<PackRecord>(m_FileStream, record);
return record;
});
m_Records.push_back(record);
}
m_FileStream.close();
}
bool Pack::HasFile(const uint32_t crc) const {
bool Pack::HasFile(uint32_t crc) {
for (const auto& record : m_Records) {
if (record.m_Crc == crc) {
return true;
@@ -45,7 +43,7 @@ bool Pack::HasFile(const uint32_t crc) const {
return false;
}
bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const {
bool Pack::ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) {
// Time for some wacky C file reading for speed reasons
PackRecord pkRecord{};
@@ -107,7 +105,7 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons
pos += size; // Move pointer position the amount of bytes read to the right
int32_t err;
currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err);
currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), ZCompression::MAX_SD0_CHUNK_SIZE, err);
free(chunk);
}

View File

@@ -24,17 +24,16 @@ struct PackRecord {
class Pack {
public:
Pack(const std::filesystem::path& filePath);
~Pack() = default;
[[nodiscard]]
bool HasFile(uint32_t crc) const;
[[nodiscard]]
bool ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) const;
bool HasFile(uint32_t crc);
bool ReadFileFromPack(uint32_t crc, char** data, uint32_t* len);
private:
std::ifstream m_FileStream;
std::filesystem::path m_FilePath;
char m_Version[7];
uint32_t m_RecordCount;
std::vector<PackRecord> m_Records;
};

View File

@@ -6,32 +6,38 @@
PackIndex::PackIndex(const std::filesystem::path& filePath) {
m_FileStream = std::ifstream(filePath / "versions" / "primary.pki", std::ios::in | std::ios::binary);
uint32_t packPathCount = 0;
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_Version);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, packPathCount);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackPathCount);
m_PackPaths.resize(packPathCount);
m_PackPaths.resize(m_PackPathCount);
for (auto& item : m_PackPaths) {
BinaryIO::ReadString<uint32_t>(m_FileStream, item, BinaryIO::ReadType::String);
}
uint32_t packFileIndexCount = 0;
BinaryIO::BinaryRead<uint32_t>(m_FileStream, packFileIndexCount);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackFileIndexCount);
m_PackFileIndices.reserve(packFileIndexCount);
std::generate_n(std::back_inserter(m_PackFileIndices), packFileIndexCount, [&] {
for (int i = 0; i < m_PackFileIndexCount; i++) {
PackFileIndex packFileIndex;
BinaryIO::BinaryRead<PackFileIndex>(m_FileStream, packFileIndex);
return packFileIndex;
});
m_PackFileIndices.push_back(packFileIndex);
}
LOG("Loaded pack catalog with %i pack files and %i files", m_PackPaths.size(), m_PackFileIndices.size());
m_Packs.reserve(m_PackPaths.size());
for (auto& item : m_PackPaths) {
std::replace(item.begin(), item.end(), '\\', '/');
m_Packs.emplace_back(filePath / item);
auto* pack = new Pack(filePath / item);
m_Packs.push_back(pack);
}
m_FileStream.close();
}
PackIndex::~PackIndex() {
for (const auto* item : m_Packs) {
delete item;
}
}

View File

@@ -21,23 +21,20 @@ struct PackFileIndex {
class PackIndex {
public:
PackIndex(const std::filesystem::path& filePath);
~PackIndex();
[[nodiscard]]
const std::vector<std::string>& GetPackPaths() const { return m_PackPaths; }
[[nodiscard]]
const std::vector<PackFileIndex>& GetPackFileIndices() const { return m_PackFileIndices; }
[[nodiscard]]
const std::vector<Pack>& GetPacks() const { return m_Packs; }
const std::vector<std::string>& GetPackPaths() { return m_PackPaths; }
const std::vector<PackFileIndex>& GetPackFileIndices() { return m_PackFileIndices; }
const std::vector<Pack*>& GetPacks() { return m_Packs; }
private:
std::ifstream m_FileStream;
uint32_t m_Version;
uint32_t m_PackPathCount;
std::vector<std::string> m_PackPaths;
uint32_t m_PackFileIndexCount;
std::vector<PackFileIndex> m_PackFileIndices;
std::vector<Pack> m_Packs;
std::vector<Pack*> m_Packs;
};

View File

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

View File

@@ -3,14 +3,13 @@
#ifndef __DCOMMONVARS__H__
#define __DCOMMONVARS__H__
#include <compare>
#include <cstdint>
#include <set>
#include <string>
#include <set>
#include "BitStream.h"
#include "BitStreamUtils.h"
#include "MessageType/Client.h"
#include "eConnectionType.h"
#include "MessageType/Client.h"
#include "BitStreamUtils.h"
#pragma warning (disable:4251) //Disables SQL warnings
@@ -99,8 +98,6 @@ public:
constexpr LWOZONEID() noexcept = default;
constexpr LWOZONEID(const LWOMAPID& mapID, const LWOINSTANCEID& instanceID, const LWOCLONEID& cloneID) noexcept { m_MapID = mapID; m_InstanceID = instanceID; m_CloneID = cloneID; }
constexpr LWOZONEID(const LWOZONEID& replacement) noexcept { *this = replacement; }
constexpr bool operator==(const LWOZONEID&) const = default;
constexpr auto operator<=>(const LWOZONEID&) const = default;
private:
LWOMAPID m_MapID = LWOMAPID_INVALID; //1000 for VE, 1100 for AG, etc...

View File

@@ -16,9 +16,7 @@ enum class eCharacterVersion : uint32_t {
VAULT_SIZE,
// Fixes speed base value in level component
SPEED_BASE,
// Fixes nexus force explorer missions
NJ_JAYMISSIONS,
UP_TO_DATE, // will become NEXUS_FORCE_EXPLORER
UP_TO_DATE, // will become NJ_JAYMISSIONS
};
#endif //!__ECHARACTERVERSION__H__

View File

@@ -7,8 +7,7 @@ enum class eConnectionType : uint16_t {
CHAT,
WORLD = 4,
CLIENT,
MASTER,
UNKNOWN
MASTER
};
#endif //!__ECONNECTIONTYPE__H__

View File

@@ -1,8 +1,6 @@
#ifndef __EHTTPMETHODS__H__
#define __EHTTPMETHODS__H__
#include "dPlatforms.h"
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma push_macro("DELETE")
#undef DELETE

View File

@@ -28,8 +28,7 @@ enum eInventoryType : uint32_t {
DONATION,
VAULT_MODELS,
ITEM_SETS, //internal, technically this is BankBehaviors.
INVALID, // made up, for internal use!!!, Technically this called the ALL inventory.
ALL, // Use this to search all inventories instead of a specific one.
INVALID // made up, for internal use!!!, Technically this called the ALL inventory.
};
class InventoryType {

View File

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

View File

@@ -102,6 +102,7 @@ DEFINE_TABLE_STORAGE(CDScriptComponentTable);
DEFINE_TABLE_STORAGE(CDSkillBehaviorTable);
DEFINE_TABLE_STORAGE(CDTamingBuildPuzzleTable);
DEFINE_TABLE_STORAGE(CDVendorComponentTable);
DEFINE_TABLE_STORAGE(CDZoneTableTable);
void CDClientManager::LoadValuesFromDatabase() {
if (!CDClientDatabase::isConnected) {
@@ -148,7 +149,7 @@ void CDClientManager::LoadValuesFromDatabase() {
CDSkillBehaviorTable::Instance().LoadValuesFromDatabase();
CDTamingBuildPuzzleTable::Instance().LoadValuesFromDatabase();
CDVendorComponentTable::Instance().LoadValuesFromDatabase();
CDZoneTableTable::LoadValuesFromDatabase();
CDZoneTableTable::Instance().LoadValuesFromDatabase();
}
void CDClientManager::LoadValuesFromDefaults() {

View File

@@ -1,53 +1,67 @@
#include "CDZoneTableTable.h"
namespace CDZoneTableTable {
Table entries;
void CDZoneTableTable::LoadValuesFromDatabase() {
void LoadValuesFromDatabase() {
// Get the data from the database
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM ZoneTable");
while (!tableData.eof()) {
CDZoneTable entry;
entry.zoneID = tableData.getIntField("zoneID", -1);
entry.locStatus = tableData.getIntField("locStatus", -1);
entry.zoneName = tableData.getStringField("zoneName", "");
entry.scriptID = tableData.getIntField("scriptID", -1);
entry.ghostdistance_min = tableData.getFloatField("ghostdistance_min", -1.0f);
entry.ghostdistance = tableData.getFloatField("ghostdistance", -1.0f);
entry.population_soft_cap = tableData.getIntField("population_soft_cap", -1);
entry.population_hard_cap = tableData.getIntField("population_hard_cap", -1);
UNUSED(entry.DisplayDescription = tableData.getStringField("DisplayDescription", ""));
UNUSED(entry.mapFolder = tableData.getStringField("mapFolder", ""));
entry.smashableMinDistance = tableData.getFloatField("smashableMinDistance", -1.0f);
entry.smashableMaxDistance = tableData.getFloatField("smashableMaxDistance", -1.0f);
UNUSED(entry.mixerProgram = tableData.getStringField("mixerProgram", ""));
UNUSED(entry.clientPhysicsFramerate = tableData.getStringField("clientPhysicsFramerate", ""));
entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", "");
entry.zoneControlTemplate = tableData.getIntField("zoneControlTemplate", -1);
entry.widthInChunks = tableData.getIntField("widthInChunks", -1);
entry.heightInChunks = tableData.getIntField("heightInChunks", -1);
entry.petsAllowed = tableData.getIntField("petsAllowed", -1) == 1 ? true : false;
entry.localize = tableData.getIntField("localize", -1) == 1 ? true : false;
entry.fZoneWeight = tableData.getFloatField("fZoneWeight", -1.0f);
UNUSED(entry.thumbnail = tableData.getStringField("thumbnail", ""));
entry.PlayerLoseCoinsOnDeath = tableData.getIntField("PlayerLoseCoinsOnDeath", -1) == 1 ? true : false;
entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false;
entry.teamRadius = tableData.getFloatField("teamRadius", -1.0f);
UNUSED(entry.gate_version = tableData.getStringField("gate_version", ""));
entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false;
// First, get the size of the table
uint32_t size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM ZoneTable");
while (!tableSize.eof()) {
size = tableSize.getIntField(0, 0);
entries[entry.zoneID] = entry;
tableData.nextRow();
}
tableSize.nextRow();
}
//! Queries the table with a zoneID to find.
const CDZoneTable* Query(uint32_t zoneID) {
const auto& iter = entries.find(zoneID);
if (iter != entries.end()) {
return &iter->second;
}
tableSize.finalize();
return nullptr;
// Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM ZoneTable");
auto& entries = GetEntriesMutable();
while (!tableData.eof()) {
CDZoneTable entry;
entry.zoneID = tableData.getIntField("zoneID", -1);
entry.locStatus = tableData.getIntField("locStatus", -1);
entry.zoneName = tableData.getStringField("zoneName", "");
entry.scriptID = tableData.getIntField("scriptID", -1);
entry.ghostdistance_min = tableData.getFloatField("ghostdistance_min", -1.0f);
entry.ghostdistance = tableData.getFloatField("ghostdistance", -1.0f);
entry.population_soft_cap = tableData.getIntField("population_soft_cap", -1);
entry.population_hard_cap = tableData.getIntField("population_hard_cap", -1);
UNUSED(entry.DisplayDescription = tableData.getStringField("DisplayDescription", ""));
UNUSED(entry.mapFolder = tableData.getStringField("mapFolder", ""));
entry.smashableMinDistance = tableData.getFloatField("smashableMinDistance", -1.0f);
entry.smashableMaxDistance = tableData.getFloatField("smashableMaxDistance", -1.0f);
UNUSED(entry.mixerProgram = tableData.getStringField("mixerProgram", ""));
UNUSED(entry.clientPhysicsFramerate = tableData.getStringField("clientPhysicsFramerate", ""));
entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", "");
entry.zoneControlTemplate = tableData.getIntField("zoneControlTemplate", -1);
entry.widthInChunks = tableData.getIntField("widthInChunks", -1);
entry.heightInChunks = tableData.getIntField("heightInChunks", -1);
entry.petsAllowed = tableData.getIntField("petsAllowed", -1) == 1 ? true : false;
entry.localize = tableData.getIntField("localize", -1) == 1 ? true : false;
entry.fZoneWeight = tableData.getFloatField("fZoneWeight", -1.0f);
UNUSED(entry.thumbnail = tableData.getStringField("thumbnail", ""));
entry.PlayerLoseCoinsOnDeath = tableData.getIntField("PlayerLoseCoinsOnDeath", -1) == 1 ? true : false;
entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false;
entry.teamRadius = tableData.getFloatField("teamRadius", -1.0f);
UNUSED(entry.gate_version = tableData.getStringField("gate_version", ""));
entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false;
entries.insert(std::make_pair(entry.zoneID, entry));
tableData.nextRow();
}
tableData.finalize();
}
//! Queries the table with a zoneID to find.
const CDZoneTable* CDZoneTableTable::Query(uint32_t zoneID) {
auto& m_Entries = GetEntries();
const auto& iter = m_Entries.find(zoneID);
if (iter != m_Entries.end()) {
return &iter->second;
}
return nullptr;
}

View File

@@ -33,8 +33,8 @@ struct CDZoneTable {
bool mountsAllowed; //!< Whether or not mounts are allowed
};
namespace CDZoneTableTable {
using Table = std::map<uint32_t, CDZoneTable>;
class CDZoneTableTable : public CDTable<CDZoneTableTable, std::map<uint32_t, CDZoneTable>> {
public:
void LoadValuesFromDatabase();
// Queries the table with a zoneID to find.

View File

@@ -1,7 +1,7 @@
add_subdirectory(CDClientDatabase)
add_subdirectory(GameDatabase)
add_library(dDatabase STATIC "MigrationRunner.cpp" "ModelNormalizeMigration.cpp")
add_library(dDatabase STATIC "MigrationRunner.cpp")
add_custom_target(conncpp_dylib
${CMAKE_COMMAND} -E copy $<TARGET_FILE:MariaDB::ConnCpp> ${PROJECT_BINARY_DIR})

View File

@@ -8,7 +8,6 @@
enum class eActivityType : uint32_t {
PlayerLoggedIn,
PlayerLoggedOut,
PlayerChangedZone
};
class IActivityLog {

View File

@@ -8,15 +8,15 @@
class IBehaviors {
public:
struct Info {
LWOOBJID behaviorId{};
int32_t behaviorId{};
uint32_t characterId{};
std::string behaviorInfo;
};
// This Add also takes care of updating if it exists.
virtual void AddBehavior(const Info& info) = 0;
virtual std::string GetBehavior(const LWOOBJID behaviorId) = 0;
virtual void RemoveBehavior(const LWOOBJID behaviorId) = 0;
virtual std::string GetBehavior(const int32_t behaviorId) = 0;
virtual void RemoveBehavior(const int32_t behaviorId) = 0;
};
#endif //!IBEHAVIORS_H

View File

@@ -44,8 +44,6 @@ public:
// Updates the given character ids last login to be right now.
virtual void UpdateLastLoggedInCharacter(const uint32_t characterId) = 0;
virtual bool IsNameInUse(const std::string_view name) = 0;
};
#endif //!__ICHARINFO__H__

View File

@@ -8,10 +8,27 @@
#include "dCommonVars.h"
#include "NiQuaternion.h"
#include "NiPoint3.h"
#include "MailInfo.h"
class IMail {
public:
struct MailInfo {
std::string senderUsername;
std::string recipient;
std::string subject;
std::string body;
uint64_t id{};
uint32_t senderId{};
uint32_t receiverId{};
uint64_t timeSent{};
bool wasRead{};
struct {
LWOOBJID itemID{};
int32_t itemCount{};
LOT itemLOT{};
LWOOBJID itemSubkey{};
};
};
// Insert a new mail into the database.
virtual void InsertNewMail(const MailInfo& mail) = 0;

View File

@@ -53,9 +53,6 @@ public:
// Update the property details for the given property id.
virtual void UpdatePropertyDetails(const IProperty::Info& info) = 0;
// Update the last updated time for the given property id.
virtual void UpdateLastSave(const IProperty::Info& info) = 0;
// Update the property performance cost for the given property id.
virtual void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) = 0;

View File

@@ -17,12 +17,12 @@ public:
LWOOBJID id{};
LOT lot{};
uint32_t ugcId{};
std::array<LWOOBJID, 5> behaviors{};
std::array<int32_t, 5> behaviors{};
};
// Inserts a new UGC model into the database.
virtual void InsertNewUgcModel(
std::stringstream& sd0Data,
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) = 0;
@@ -34,17 +34,9 @@ public:
virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0;
// Update the model position and rotation for the given property id.
virtual void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) = 0;
virtual void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<LWOOBJID, 5> behaviorIDs) {
std::array<std::pair<LWOOBJID, std::string>, 5> behaviors;
for (int32_t i = 0; i < behaviors.size(); i++) behaviors[i].first = behaviorIDs[i];
UpdateModel(modelID, position, rotation, behaviors);
}
virtual void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) = 0;
// Remove the model for the given property id.
virtual void RemoveModel(const LWOOBJID& modelId) = 0;
// Gets a model by ID
virtual Model GetModel(const LWOOBJID modelID) = 0;
};
#endif //!__IPROPERTIESCONTENTS__H__

View File

@@ -12,7 +12,6 @@ public:
struct Model {
std::stringstream lxfmlData;
LWOOBJID id{};
LWOOBJID modelID{};
};
// Gets all UGC models for the given property id.
@@ -28,6 +27,6 @@ public:
virtual void DeleteUgcModelData(const LWOOBJID& modelId) = 0;
// Inserts a new UGC model into the database.
virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) = 0;
virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) = 0;
};
#endif //!__IUGC__H__

View File

@@ -48,7 +48,7 @@ public:
void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override;
void CreateMigrationHistoryTable() override;
bool IsMigrationRun(const std::string_view str) override;
@@ -70,24 +70,23 @@ public:
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
void UpdatePropertyDetails(const IProperty::Info& info) override;
void UpdateLastSave(const IProperty::Info& info) override;
void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override;
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) override;
void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const MailInfo& mail) override;
void InsertNewMail(const IMail::MailInfo& mail) override;
void InsertNewUgcModel(
std::stringstream& sd0Data,
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) override;
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
std::vector<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<IMail::MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override;
@@ -110,8 +109,8 @@ public:
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID characterId) override;
std::string GetBehavior(const int32_t behaviorId) override;
void RemoveBehavior(const int32_t characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
@@ -125,10 +124,8 @@ public:
void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override;
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override;
void DeleteUgcBuild(const LWOOBJID bigId) override;
uint32_t GetAccountCount() override;
bool IsNameInUse(const std::string_view name) override;
IPropertyContents::Model GetModel(const LWOOBJID modelID) override;
sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
uint32_t GetAccountCount() override;
private:
// Generic query functions that can be used for any query.

View File

@@ -9,11 +9,11 @@ void MySQLDatabase::AddBehavior(const IBehaviors::Info& info) {
);
}
void MySQLDatabase::RemoveBehavior(const LWOOBJID behaviorId) {
void MySQLDatabase::RemoveBehavior(const int32_t behaviorId) {
ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId);
}
std::string MySQLDatabase::GetBehavior(const LWOOBJID behaviorId) {
std::string MySQLDatabase::GetBehavior(const int32_t behaviorId) {
auto result = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId);
return result->next() ? result->getString("behavior_info").c_str() : "";
}

View File

@@ -76,9 +76,3 @@ void MySQLDatabase::SetPendingCharacterName(const uint32_t characterId, const st
void MySQLDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) {
ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ? LIMIT 1", static_cast<uint32_t>(time(NULL)), characterId);
}
bool MySQLDatabase::IsNameInUse(const std::string_view name) {
auto result = ExecuteSelect("SELECT name FROM charinfo WHERE name = ? or pending_name = ? LIMIT 1;", name, name);
return result->next();
}

View File

@@ -1,7 +1,6 @@
#include "MySQLDatabase.h"
void MySQLDatabase::InsertNewMail(const MailInfo& mail) {
void MySQLDatabase::InsertNewMail(const IMail::MailInfo& mail) {
ExecuteInsert(
"INSERT INTO `mail` "
"(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`)"
@@ -19,17 +18,17 @@ void MySQLDatabase::InsertNewMail(const MailInfo& mail) {
mail.itemCount);
}
std::vector<MailInfo> MySQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
std::vector<IMail::MailInfo> MySQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
auto res = ExecuteSelect(
"SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent"
" FROM mail WHERE receiver_id=? limit ?;",
characterId, numberOfMail);
std::vector<MailInfo> toReturn;
std::vector<IMail::MailInfo> toReturn;
toReturn.reserve(res->rowsCount());
while (res->next()) {
MailInfo mail;
IMail::MailInfo mail;
mail.id = res->getUInt64("id");
mail.subject = res->getString("subject").c_str();
mail.body = res->getString("body").c_str();
@@ -47,14 +46,14 @@ std::vector<MailInfo> MySQLDatabase::GetMailForPlayer(const uint32_t characterId
return toReturn;
}
std::optional<MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) {
std::optional<IMail::MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) {
auto res = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId);
if (!res->next()) {
return std::nullopt;
}
MailInfo toReturn;
IMail::MailInfo toReturn;
toReturn.itemLOT = res->getInt("attachment_lot");
toReturn.itemCount = res->getInt("attachment_count");

View File

@@ -173,10 +173,6 @@ void MySQLDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
ExecuteUpdate("UPDATE properties SET name = ?, description = ? WHERE id = ? LIMIT 1;", info.name, info.description, info.id);
}
void MySQLDatabase::UpdateLastSave(const IProperty::Info& info) {
ExecuteUpdate("UPDATE properties SET last_updated = ? WHERE id = ?;", info.lastUpdatedTime, info.id);
}
void MySQLDatabase::UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) {
ExecuteUpdate("UPDATE properties SET performance_cost = ? WHERE zone_id = ? AND clone_id = ? LIMIT 1;", performanceCost, zoneId.GetMapID(), zoneId.GetCloneID());
}

View File

@@ -20,11 +20,11 @@ std::vector<IPropertyContents::Model> MySQLDatabase::GetPropertyModels(const LWO
model.rotation.y = result->getFloat("ry");
model.rotation.z = result->getFloat("rz");
model.ugcId = result->getUInt64("ugc_id");
model.behaviors[0] = result->getUInt64("behavior_1");
model.behaviors[1] = result->getUInt64("behavior_2");
model.behaviors[2] = result->getUInt64("behavior_3");
model.behaviors[3] = result->getUInt64("behavior_4");
model.behaviors[4] = result->getUInt64("behavior_5");
model.behaviors[0] = result->getInt("behavior_1");
model.behaviors[1] = result->getInt("behavior_2");
model.behaviors[2] = result->getInt("behavior_3");
model.behaviors[3] = result->getInt("behavior_4");
model.behaviors[4] = result->getInt("behavior_5");
toReturn.push_back(std::move(model));
}
@@ -52,39 +52,14 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr
}
}
void MySQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) {
void MySQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
ExecuteUpdate(
"UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, "
"behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;",
position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w,
behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, modelID);
behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, propertyId);
}
void MySQLDatabase::RemoveModel(const LWOOBJID& modelId) {
ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId);
}
IPropertyContents::Model MySQLDatabase::GetModel(const LWOOBJID modelID) {
auto result = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID);
IPropertyContents::Model model{};
while (result->next()) {
model.id = result->getUInt64("id");
model.lot = static_cast<LOT>(result->getUInt("lot"));
model.position.x = result->getFloat("x");
model.position.y = result->getFloat("y");
model.position.z = result->getFloat("z");
model.rotation.w = result->getFloat("rw");
model.rotation.x = result->getFloat("rx");
model.rotation.y = result->getFloat("ry");
model.rotation.z = result->getFloat("rz");
model.ugcId = result->getUInt64("ugc_id");
model.behaviors[0] = result->getUInt64("behavior_1");
model.behaviors[1] = result->getUInt64("behavior_2");
model.behaviors[2] = result->getUInt64("behavior_3");
model.behaviors[3] = result->getUInt64("behavior_4");
model.behaviors[4] = result->getUInt64("behavior_5");
}
return model;
}

View File

@@ -2,7 +2,7 @@
std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId) {
auto result = ExecuteSelect(
"SELECT lxfml, u.id as ugcID, pc.id as modelID FROM ugc AS u JOIN properties_contents AS pc ON u.id = pc.ugc_id WHERE lot = 14 AND property_id = ? AND pc.ugc_id IS NOT NULL;",
"SELECT lxfml, u.id FROM ugc AS u JOIN properties_contents AS pc ON u.id = pc.ugc_id WHERE lot = 14 AND property_id = ? AND pc.ugc_id IS NOT NULL;",
propertyId);
std::vector<IUgc::Model> toReturn;
@@ -13,8 +13,7 @@ std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId)
// blob is owned by the query, so we need to do a deep copy :/
std::unique_ptr<std::istream> blob(result->getBlob("lxfml"));
model.lxfmlData << blob->rdbuf();
model.id = result->getUInt64("ugcID");
model.modelID = result->getUInt64("modelID");
model.id = result->getUInt64("id");
toReturn.push_back(std::move(model));
}
@@ -22,14 +21,13 @@ std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId)
}
std::vector<IUgc::Model> MySQLDatabase::GetAllUgcModels() {
auto result = ExecuteSelect("SELECT u.id AS ugcID, lxfml, pc.id AS modelID FROM ugc AS u JOIN properties_contents AS pc ON pc.ugc_id = u.id WHERE pc.lot = 14 AND pc.ugc_id IS NOT NULL;");
auto result = ExecuteSelect("SELECT id, lxfml FROM ugc;");
std::vector<IUgc::Model> models;
models.reserve(result->rowsCount());
while (result->next()) {
IUgc::Model model;
model.id = result->getInt64("ugcID");
model.modelID = result->getUInt64("modelID");
model.id = result->getInt64("id");
// blob is owned by the query, so we need to do a deep copy :/
std::unique_ptr<std::istream> blob(result->getBlob("lxfml"));
@@ -45,7 +43,7 @@ void MySQLDatabase::RemoveUnreferencedUgcModels() {
}
void MySQLDatabase::InsertNewUgcModel(
std:: stringstream& sd0Data, // cant be const sad
std::istringstream& sd0Data, // cant be const sad
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) {
@@ -67,7 +65,7 @@ void MySQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId);
}
void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {
void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
const std::istream stream(lxfml.rdbuf());
ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId);
}

View File

@@ -46,7 +46,7 @@ public:
void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override;
void CreateMigrationHistoryTable() override;
bool IsMigrationRun(const std::string_view str) override;
@@ -68,24 +68,23 @@ public:
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
void UpdatePropertyDetails(const IProperty::Info& info) override;
void UpdateLastSave(const IProperty::Info& info) override;
void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override;
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) override;
void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const MailInfo& mail) override;
void InsertNewMail(const IMail::MailInfo& mail) override;
void InsertNewUgcModel(
std::stringstream& sd0Data,
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) override;
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
std::vector<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<IMail::MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override;
@@ -108,8 +107,8 @@ public:
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID characterId) override;
std::string GetBehavior(const int32_t behaviorId) override;
void RemoveBehavior(const int32_t characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
@@ -124,8 +123,6 @@ public:
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override;
void DeleteUgcBuild(const LWOOBJID bigId) override;
uint32_t GetAccountCount() override;
bool IsNameInUse(const std::string_view name) override;
IPropertyContents::Model GetModel(const LWOOBJID modelID) override;
private:
CppSQLite3Statement CreatePreppedStmt(const std::string& query);

View File

@@ -9,11 +9,11 @@ void SQLiteDatabase::AddBehavior(const IBehaviors::Info& info) {
);
}
void SQLiteDatabase::RemoveBehavior(const LWOOBJID behaviorId) {
void SQLiteDatabase::RemoveBehavior(const int32_t behaviorId) {
ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId);
}
std::string SQLiteDatabase::GetBehavior(const LWOOBJID behaviorId) {
std::string SQLiteDatabase::GetBehavior(const int32_t behaviorId) {
auto [_, result] = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId);
return !result.eof() ? result.getStringField("behavior_info") : "";
}

View File

@@ -77,9 +77,3 @@ void SQLiteDatabase::SetPendingCharacterName(const uint32_t characterId, const s
void SQLiteDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) {
ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ?;", static_cast<uint32_t>(time(NULL)), characterId);
}
bool SQLiteDatabase::IsNameInUse(const std::string_view name) {
auto [_, result] = ExecuteSelect("SELECT name FROM charinfo WHERE name = ? or pending_name = ? LIMIT 1;", name, name);
return !result.eof();
}

View File

@@ -1,6 +1,6 @@
#include "SQLiteDatabase.h"
void SQLiteDatabase::InsertNewMail(const MailInfo& mail) {
void SQLiteDatabase::InsertNewMail(const IMail::MailInfo& mail) {
ExecuteInsert(
"INSERT INTO `mail` "
"(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`)"
@@ -18,16 +18,16 @@ void SQLiteDatabase::InsertNewMail(const MailInfo& mail) {
mail.itemCount);
}
std::vector<MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
std::vector<IMail::MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
auto [_, res] = ExecuteSelect(
"SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent"
" FROM mail WHERE receiver_id=? limit ?;",
characterId, numberOfMail);
std::vector<MailInfo> toReturn;
std::vector<IMail::MailInfo> toReturn;
while (!res.eof()) {
MailInfo mail;
IMail::MailInfo mail;
mail.id = res.getInt64Field("id");
mail.subject = res.getStringField("subject");
mail.body = res.getStringField("body");
@@ -46,14 +46,14 @@ std::vector<MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t characterI
return toReturn;
}
std::optional<MailInfo> SQLiteDatabase::GetMail(const uint64_t mailId) {
std::optional<IMail::MailInfo> SQLiteDatabase::GetMail(const uint64_t mailId) {
auto [_, res] = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId);
if (res.eof()) {
return std::nullopt;
}
MailInfo toReturn;
IMail::MailInfo toReturn;
toReturn.itemLOT = res.getIntField("attachment_lot");
toReturn.itemCount = res.getIntField("attachment_count");

View File

@@ -175,10 +175,6 @@ void SQLiteDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
ExecuteUpdate("UPDATE properties SET name = ?, description = ? WHERE id = ?;", info.name, info.description, info.id);
}
void SQLiteDatabase::UpdateLastSave(const IProperty::Info& info) {
ExecuteUpdate("UPDATE properties SET last_updated = ? WHERE id = ?;", info.lastUpdatedTime, info.id);
}
void SQLiteDatabase::UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) {
ExecuteUpdate("UPDATE properties SET performance_cost = ? WHERE zone_id = ? AND clone_id = ?;", performanceCost, zoneId.GetMapID(), zoneId.GetCloneID());
}

View File

@@ -19,11 +19,11 @@ std::vector<IPropertyContents::Model> SQLiteDatabase::GetPropertyModels(const LW
model.rotation.y = result.getFloatField("ry");
model.rotation.z = result.getFloatField("rz");
model.ugcId = result.getInt64Field("ugc_id");
model.behaviors[0] = result.getInt64Field("behavior_1");
model.behaviors[1] = result.getInt64Field("behavior_2");
model.behaviors[2] = result.getInt64Field("behavior_3");
model.behaviors[3] = result.getInt64Field("behavior_4");
model.behaviors[4] = result.getInt64Field("behavior_5");
model.behaviors[0] = result.getIntField("behavior_1");
model.behaviors[1] = result.getIntField("behavior_2");
model.behaviors[2] = result.getIntField("behavior_3");
model.behaviors[3] = result.getIntField("behavior_4");
model.behaviors[4] = result.getIntField("behavior_5");
toReturn.push_back(std::move(model));
result.nextRow();
@@ -52,41 +52,14 @@ void SQLiteDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IP
}
}
void SQLiteDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) {
void SQLiteDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
ExecuteUpdate(
"UPDATE properties_contents SET x = ?, y = ?, z = ?, rx = ?, ry = ?, rz = ?, rw = ?, "
"behavior_1 = ?, behavior_2 = ?, behavior_3 = ?, behavior_4 = ?, behavior_5 = ? WHERE id = ?;",
position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w,
behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, modelID);
behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, propertyId);
}
void SQLiteDatabase::RemoveModel(const LWOOBJID& modelId) {
ExecuteDelete("DELETE FROM properties_contents WHERE id = ?;", modelId);
}
IPropertyContents::Model SQLiteDatabase::GetModel(const LWOOBJID modelID) {
auto [_, result] = ExecuteSelect("SELECT * FROM properties_contents WHERE id = ?", modelID);
IPropertyContents::Model model{};
if (!result.eof()) {
do {
model.id = result.getInt64Field("id");
model.lot = static_cast<LOT>(result.getIntField("lot"));
model.position.x = result.getFloatField("x");
model.position.y = result.getFloatField("y");
model.position.z = result.getFloatField("z");
model.rotation.w = result.getFloatField("rw");
model.rotation.x = result.getFloatField("rx");
model.rotation.y = result.getFloatField("ry");
model.rotation.z = result.getFloatField("rz");
model.ugcId = result.getInt64Field("ugc_id");
model.behaviors[0] = result.getInt64Field("behavior_1");
model.behaviors[1] = result.getInt64Field("behavior_2");
model.behaviors[2] = result.getInt64Field("behavior_3");
model.behaviors[3] = result.getInt64Field("behavior_4");
model.behaviors[4] = result.getInt64Field("behavior_5");
} while (result.nextRow());
}
return model;
}

View File

@@ -2,7 +2,7 @@
std::vector<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId) {
auto [_, result] = ExecuteSelect(
"SELECT lxfml, u.id AS ugcID, pc.id AS modelID FROM ugc AS u JOIN properties_contents AS pc ON u.id = pc.ugc_id WHERE lot = 14 AND property_id = ? AND pc.ugc_id IS NOT NULL;",
"SELECT lxfml, u.id FROM ugc AS u JOIN properties_contents AS pc ON u.id = pc.ugc_id WHERE lot = 14 AND property_id = ? AND pc.ugc_id IS NOT NULL;",
propertyId);
std::vector<IUgc::Model> toReturn;
@@ -13,8 +13,7 @@ std::vector<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId
int blobSize{};
const auto* blob = result.getBlobField("lxfml", blobSize);
model.lxfmlData << std::string(reinterpret_cast<const char*>(blob), blobSize);
model.id = result.getInt64Field("ugcID");
model.modelID = result.getInt64Field("modelID");
model.id = result.getInt64Field("id");
toReturn.push_back(std::move(model));
result.nextRow();
}
@@ -23,13 +22,12 @@ std::vector<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId
}
std::vector<IUgc::Model> SQLiteDatabase::GetAllUgcModels() {
auto [_, result] = ExecuteSelect("SELECT u.id AS ugcID, pc.id AS modelID, lxfml FROM ugc AS u JOIN properties_contents AS pc ON pc.id = u.id;");
auto [_, result] = ExecuteSelect("SELECT id, lxfml FROM ugc;");
std::vector<IUgc::Model> models;
while (!result.eof()) {
IUgc::Model model;
model.id = result.getInt64Field("ugcID");
model.modelID = result.getInt64Field("modelID");
model.id = result.getInt64Field("id");
int blobSize{};
const auto* blob = result.getBlobField("lxfml", blobSize);
@@ -46,7 +44,7 @@ void SQLiteDatabase::RemoveUnreferencedUgcModels() {
}
void SQLiteDatabase::InsertNewUgcModel(
std::stringstream& sd0Data, // cant be const sad
std::istringstream& sd0Data, // cant be const sad
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) {
@@ -68,7 +66,7 @@ void SQLiteDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId);
}
void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {
void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
const std::istream stream(lxfml.rdbuf());
ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId);
}

View File

@@ -60,7 +60,7 @@ void TestSQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
}
void TestSQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {
void TestSQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
}
@@ -148,10 +148,6 @@ void TestSQLDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
}
void TestSQLDatabase::UpdateLastSave(const IProperty::Info& info) {
}
void TestSQLDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) {
}
@@ -168,7 +164,7 @@ void TestSQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const I
}
void TestSQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) {
void TestSQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
}
@@ -188,19 +184,19 @@ void TestSQLDatabase::InsertCheatDetection(const IPlayerCheatDetections::Info& i
}
void TestSQLDatabase::InsertNewMail(const MailInfo& mail) {
void TestSQLDatabase::InsertNewMail(const IMail::MailInfo& mail) {
}
void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const uint32_t characterId) {
void TestSQLDatabase::InsertNewUgcModel(std::istringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const uint32_t characterId) {
}
std::vector<MailInfo> TestSQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
std::vector<IMail::MailInfo> TestSQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
return {};
}
std::optional<MailInfo> TestSQLDatabase::GetMail(const uint64_t mailId) {
std::optional<IMail::MailInfo> TestSQLDatabase::GetMail(const uint64_t mailId) {
return {};
}
@@ -292,11 +288,11 @@ void TestSQLDatabase::AddBehavior(const IBehaviors::Info& info) {
}
std::string TestSQLDatabase::GetBehavior(const LWOOBJID behaviorId) {
std::string TestSQLDatabase::GetBehavior(const int32_t behaviorId) {
return {};
}
void TestSQLDatabase::RemoveBehavior(const LWOOBJID behaviorId) {
void TestSQLDatabase::RemoveBehavior(const int32_t behaviorId) {
}

View File

@@ -25,7 +25,7 @@ class TestSQLDatabase : public GameDatabase {
void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override;
void CreateMigrationHistoryTable() override;
bool IsMigrationRun(const std::string_view str) override;
@@ -47,24 +47,23 @@ class TestSQLDatabase : public GameDatabase {
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
void UpdatePropertyDetails(const IProperty::Info& info) override;
void UpdateLastSave(const IProperty::Info& info) override;
void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override;
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) override;
void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const MailInfo& mail) override;
void InsertNewMail(const IMail::MailInfo& mail) override;
void InsertNewUgcModel(
std::stringstream& sd0Data,
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) override;
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
std::vector<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<IMail::MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override;
@@ -87,8 +86,8 @@ class TestSQLDatabase : public GameDatabase {
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
void AddBehavior(const IBehaviors::Info& info) override;
std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID behaviorId) override;
std::string GetBehavior(const int32_t behaviorId) override;
void RemoveBehavior(const int32_t behaviorId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override { return {}; };
@@ -103,9 +102,6 @@ class TestSQLDatabase : public GameDatabase {
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override {};
void DeleteUgcBuild(const LWOOBJID bigId) override {};
uint32_t GetAccountCount() override { return 0; };
bool IsNameInUse(const std::string_view name) override { return false; };
IPropertyContents::Model GetModel(const LWOOBJID modelID) override { return {}; }
};
#endif //!TESTSQLDATABASE_H

View File

@@ -7,7 +7,6 @@
#include "GeneralUtils.h"
#include "Logger.h"
#include "BinaryPathFinder.h"
#include "ModelNormalizeMigration.h"
#include <fstream>
@@ -36,7 +35,7 @@ void MigrationRunner::RunMigrations() {
Database::Get()->CreateMigrationHistoryTable();
// has to be here because when moving the files to the new folder, the migration_history table is not updated so it will run them all again.
const auto migrationFolder = Database::GetMigrationFolder();
if (!Database::Get()->IsMigrationRun("17_migration_for_migrations.sql") && migrationFolder == "mysql") {
LOG("Running migration: 17_migration_for_migrations.sql");
@@ -46,8 +45,6 @@ void MigrationRunner::RunMigrations() {
std::string finalSQL = "";
bool runSd0Migrations = false;
bool runNormalizeMigrations = false;
bool runNormalizeAfterFirstPartMigrations = false;
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/" / migrationFolder).string())) {
auto migration = LoadMigration("dlu/" + migrationFolder + "/", entry);
@@ -60,10 +57,6 @@ void MigrationRunner::RunMigrations() {
LOG("Running migration: %s", migration.name.c_str());
if (migration.name == "5_brick_model_sd0.sql") {
runSd0Migrations = true;
} else if (migration.name.ends_with("_normalize_model_positions.sql")) {
runNormalizeMigrations = true;
} else if (migration.name.ends_with("_normalize_model_positions_after_first_part.sql")) {
runNormalizeAfterFirstPartMigrations = true;
} else {
finalSQL.append(migration.data.c_str());
}
@@ -71,7 +64,7 @@ void MigrationRunner::RunMigrations() {
Database::Get()->InsertMigration(migration.name);
}
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations && !runNormalizeAfterFirstPartMigrations) {
if (finalSQL.empty() && !runSd0Migrations) {
LOG("Server database is up to date.");
return;
}
@@ -95,14 +88,6 @@ void MigrationRunner::RunMigrations() {
uint32_t numberOfTruncatedModels = BrickByBrickFix::TruncateBrokenBrickByBrickXml();
LOG("%i models were truncated from the database.", numberOfTruncatedModels);
}
if (runNormalizeMigrations) {
ModelNormalizeMigration::Run();
}
if (runNormalizeAfterFirstPartMigrations) {
ModelNormalizeMigration::RunAfterFirstPart();
}
}
void MigrationRunner::RunSQLiteMigrations() {

View File

@@ -1,54 +0,0 @@
#include "ModelNormalizeMigration.h"
#include "Database.h"
#include "Lxfml.h"
#include "Sd0.h"
void ModelNormalizeMigration::Run() {
// Take this out when model position normalization works (never)
return;
const auto oldCommit = Database::Get()->GetAutoCommit();
Database::Get()->SetAutoCommit(false);
for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) {
const auto model = Database::Get()->GetModel(modelID);
// only BBB models (lot 14) and models with a position of NiPoint3::ZERO need to have their position fixed.
if (model.position != NiPoint3Constant::ZERO || model.lot != 14) continue;
Sd0 sd0(lxfmlData);
const auto asStr = sd0.GetAsStringUncompressed();
const auto [newLxfml, newCenter] = Lxfml::NormalizePositionOnlyFirstPart(asStr);
if (newCenter == NiPoint3Constant::ZERO) {
LOG("Failed to update model %llu due to failure reading xml.");
continue;
}
LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z);
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
auto asStream = sd0.GetAsStream();
Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors);
Database::Get()->UpdateUgcModelData(id, asStream);
}
Database::Get()->SetAutoCommit(oldCommit);
}
void ModelNormalizeMigration::RunAfterFirstPart() {
// Take this out when model position normalization works (never)
return;
const auto oldCommit = Database::Get()->GetAutoCommit();
Database::Get()->SetAutoCommit(false);
for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) {
const auto model = Database::Get()->GetModel(modelID);
// only BBB models (lot 14) need to have their position fixed from the above blunder
if (model.lot != 14) continue;
Sd0 sd0(lxfmlData);
const auto asStr = sd0.GetAsStringUncompressed();
const auto [newLxfml, newCenter] = Lxfml::NormalizePositionAfterFirstPart(asStr, model.position);
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
auto asStream = sd0.GetAsStream();
Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors);
Database::Get()->UpdateUgcModelData(id, asStream);
}
Database::Get()->SetAutoCommit(oldCommit);
}

View File

@@ -1,12 +0,0 @@
// Darkflame Universe
// Copyright 2025
#ifndef MODELNORMALIZEMIGRATION_H
#define MODELNORMALIZEMIGRATION_H
namespace ModelNormalizeMigration {
void Run();
void RunAfterFirstPart();
};
#endif //!MODELNORMALIZEMIGRATION_H

View File

@@ -22,6 +22,7 @@
#include "eGameMasterLevel.h"
#include "ePlayerFlag.h"
#include "CDPlayerFlagsTable.h"
#include "FlagComponent.h"
Character::Character(uint32_t id, User* parentUser) {
//First load the name, etc:
@@ -37,7 +38,7 @@ Character::~Character() {
m_ParentUser = nullptr;
}
void Character::UpdateInfoFromDatabase() {
void Character::UpdateInfoFromDatabase(bool clearSessionFlags) {
auto charInfo = Database::Get()->GetCharacterInfo(m_ID);
if (charInfo) {
@@ -65,10 +66,17 @@ void Character::UpdateInfoFromDatabase() {
m_OurEntity = nullptr;
m_BuildMode = false;
// This is not done through a game message because our current implementation does this at a point
// in time where an Entity does not exist, so there is no FlagComponent to handle the msg.
if (clearSessionFlags) {
FlagComponent::ClearSessionFlags(m_Doc);
WriteToDatabase();
}
}
void Character::UpdateFromDatabase() {
UpdateInfoFromDatabase();
void Character::UpdateFromDatabase(bool clearSessionFlags) {
UpdateInfoFromDatabase(clearSessionFlags);
}
void Character::DoQuickXMLDataParse() {
@@ -197,29 +205,6 @@ void Character::DoQuickXMLDataParse() {
character->QueryAttribute("lzrz", &m_OriginalRotation.z);
character->QueryAttribute("lzrw", &m_OriginalRotation.w);
}
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (flags) {
auto* currentChild = flags->FirstChildElement();
while (currentChild) {
const auto* temp = currentChild->Attribute("v");
const auto* id = currentChild->Attribute("id");
const auto* si = currentChild->Attribute("si");
if (temp && id) {
uint32_t index = 0;
uint64_t value = 0;
index = std::stoul(id);
value = std::stoull(temp);
m_PlayerFlags.insert(std::make_pair(index, value));
} else if (si) {
auto value = GeneralUtils::TryParse<uint32_t>(si);
if (value) m_SessionFlags.insert(value.value());
}
currentChild = currentChild->NextSiblingElement();
}
}
}
void Character::UnlockEmote(int emoteID) {
@@ -280,25 +265,6 @@ void Character::SaveXMLToDatabase() {
character->LinkEndChild(emotes);
}
//Export our flags:
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (!flags) {
flags = m_Doc.NewElement("flag"); //Create a flags tag if we don't have one
m_Doc.FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time
}
flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes
for (const auto& [index, flagBucket] : m_PlayerFlags) {
auto* f = flags->InsertNewChildElement("f");
f->SetAttribute("id", index);
f->SetAttribute("v", flagBucket);
}
for (const auto& sessionFlag : m_SessionFlags) {
auto* s = flags->InsertNewChildElement("s");
s->SetAttribute("si", sessionFlag);
}
SaveXmlRespawnCheckpoints();
m_OurEntity->UpdateXMLDoc(m_Doc);
@@ -311,23 +277,6 @@ void Character::SaveXMLToDatabase() {
LOG("%i:%s Saved character to Database in: %fs", this->GetID(), this->GetName().c_str(), elapsed.count());
}
void Character::SetIsNewLogin() {
// If we dont have a flag element, then we cannot have a s element as a child of flag.
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (!flags) return;
auto* currentChild = flags->FirstChildElement();
while (currentChild) {
auto* nextChild = currentChild->NextSiblingElement();
if (currentChild->Attribute("si")) {
LOG("Removed session flag (%s) from character %i:%s, saving character to database", currentChild->Attribute("si"), GetID(), GetName().c_str());
flags->DeleteChild(currentChild);
WriteToDatabase();
}
currentChild = nextChild;
}
}
void Character::WriteToDatabase() {
//Dump our xml into m_XMLData:
tinyxml2::XMLPrinter printer(0, true, 0);
@@ -337,90 +286,6 @@ void Character::WriteToDatabase() {
Database::Get()->UpdateCharacterXml(m_ID, printer.CStr());
}
void Character::SetPlayerFlag(const uint32_t flagId, const bool value) {
// If the flag is already set, we don't have to recalculate it
if (GetPlayerFlag(flagId) == value) return;
if (value) {
// Update the mission component:
auto* player = Game::entityManager->GetEntity(m_ObjectID);
if (player != nullptr) {
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::PLAYER_FLAG, flagId);
}
}
}
const auto flagEntry = CDPlayerFlagsTable::GetEntry(flagId);
if (flagEntry && flagEntry->sessionOnly) {
if (value) m_SessionFlags.insert(flagId);
else m_SessionFlags.erase(flagId);
} else {
// Calculate the index first
auto flagIndex = uint32_t(std::floor(flagId / 64));
const auto shiftedValue = 1ULL << flagId % 64;
auto it = m_PlayerFlags.find(flagIndex);
// Check if flag index exists
if (it != m_PlayerFlags.end()) {
// Update the value
if (value) {
it->second |= shiftedValue;
} else {
it->second &= ~shiftedValue;
}
} else {
if (value) {
// Otherwise, insert the value
uint64_t flagValue = 0;
flagValue |= shiftedValue;
m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue));
}
}
}
// Notify the client that a flag has changed server-side
GameMessages::SendNotifyClientFlagChange(m_ObjectID, flagId, value, m_ParentUser->GetSystemAddress());
}
bool Character::GetPlayerFlag(const uint32_t flagId) const {
using enum ePlayerFlag;
bool toReturn = false; //by def, return false.
const auto flagEntry = CDPlayerFlagsTable::GetEntry(flagId);
if (flagEntry && flagEntry->sessionOnly) {
toReturn = m_SessionFlags.contains(flagId);
} else {
// Calculate the index first
const auto flagIndex = uint32_t(std::floor(flagId / 64));
const auto shiftedValue = 1ULL << flagId % 64;
auto it = m_PlayerFlags.find(flagIndex);
if (it != m_PlayerFlags.end()) {
// Don't set the data if we don't have to
toReturn = (it->second & shiftedValue) != 0;
}
}
return toReturn;
}
void Character::SetRetroactiveFlags() {
// Retroactive check for if player has joined a faction to set their 'joined a faction' flag to true.
if (GetPlayerFlag(ePlayerFlag::VENTURE_FACTION) || GetPlayerFlag(ePlayerFlag::ASSEMBLY_FACTION) || GetPlayerFlag(ePlayerFlag::PARADOX_FACTION) || GetPlayerFlag(ePlayerFlag::SENTINEL_FACTION)) {
SetPlayerFlag(ePlayerFlag::JOINED_A_FACTION, true);
}
}
void Character::SaveXmlRespawnCheckpoints() {
//Export our respawn points:
auto* points = m_Doc.FirstChildElement("obj")->FirstChildElement("res");
@@ -477,7 +342,11 @@ void Character::OnZoneLoad() {
if (missionComponent != nullptr) {
// Fix the monument race flag
if (missionComponent->GetMissionState(319) >= eMissionState::READY_TO_COMPLETE) {
SetPlayerFlag(ePlayerFlag::AG_FINISH_LINE_BUILT, true);
GameMessages::SetFlag setFlag{};
setFlag.target = m_ObjectID;
setFlag.iFlagId = ePlayerFlag::AG_FINISH_LINE_BUILT;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,7 @@ namespace tinyxml2 {
};
class Player;
class EntityInfo;
class User;
class Spawner;
class ScriptComponent;
@@ -44,7 +45,6 @@ class Item;
class Character;
class EntityCallbackTimer;
class PositionUpdate;
struct EntityInfo;
enum class eTriggerEventType;
enum class eGameMasterLevel : uint8_t;
enum class eReplicaComponentType : uint32_t;
@@ -60,7 +60,7 @@ namespace CppScripts {
*/
class Entity {
public:
Entity(const LWOOBJID& objectID, const EntityInfo& info, User* parentUser = nullptr, Entity* parentEntity = nullptr);
explicit Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser = nullptr, Entity* parentEntity = nullptr);
~Entity();
void Initialize();
@@ -113,7 +113,7 @@ public:
float GetDefaultScale() const;
NiPoint3 GetPosition() const;
const NiPoint3& GetPosition() const;
const NiQuaternion& GetRotation() const;
@@ -124,8 +124,6 @@ public:
// then return the collision group from that.
int32_t GetCollisionGroup() const;
const NiPoint3& GetVelocity() const;
/**
* Setters
*/
@@ -146,11 +144,9 @@ public:
void SetRotation(const NiQuaternion& rotation);
void SetRespawnPos(const NiPoint3& position) const;
void SetRespawnPos(const NiPoint3& position);
void SetRespawnRot(const NiQuaternion& rotation) const;
void SetVelocity(const NiPoint3& velocity);
void SetRespawnRot(const NiQuaternion& rotation);
/**
* Component management
@@ -169,7 +165,7 @@ public:
void AddComponent(eReplicaComponentType componentId, Component* component);
// This is expceted to never return nullptr, an assert checks this.
CppScripts::Script* const GetScript() const;
CppScripts::Script* const GetScript();
void Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, const std::string& notificationName);
void Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationName);
@@ -182,8 +178,8 @@ public:
void RemoveParent();
// Adds a timer to start next frame with the given name and time.
void AddTimer(const std::string& name, float time);
void AddCallbackTimer(float time, const std::function<void()> callback);
void AddTimer(std::string name, float time);
void AddCallbackTimer(float time, std::function<void()> callback);
bool HasTimer(const std::string& name);
void CancelCallbackTimers();
void CancelAllTimers();
@@ -195,7 +191,7 @@ public:
std::unordered_map<eReplicaComponentType, Component*>& GetComponents() { return m_Components; } // TODO: Remove
void WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacketType packetType);
void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType) const;
void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType);
void UpdateXMLDoc(tinyxml2::XMLDocument& doc);
void Update(float deltaTime);
@@ -242,21 +238,21 @@ public:
void AddDieCallback(const std::function<void()>& callback);
void Resurrect();
void AddLootItem(const Loot::Info& info) const;
void PickupItem(const LWOOBJID& objectID) const;
void AddLootItem(const Loot::Info& info);
void PickupItem(const LWOOBJID& objectID);
bool PickupCoins(uint64_t count) const;
void RegisterCoinDrop(uint64_t count) const;
bool CanPickupCoins(uint64_t count);
void RegisterCoinDrop(uint64_t count);
void ScheduleKillAfterUpdate(Entity* murderer = nullptr);
void TriggerEvent(eTriggerEventType event, Entity* optionalTarget = nullptr) const;
void TriggerEvent(eTriggerEventType event, Entity* optionalTarget = nullptr);
void ScheduleDestructionAfterUpdate() { m_ShouldDestroyAfterUpdate = true; }
const NiPoint3& GetRespawnPosition() const;
const NiQuaternion& GetRespawnRotation() const;
void Sleep() const;
void Wake() const;
void Sleep();
void Wake();
bool IsSleeping() const;
/*
@@ -266,7 +262,7 @@ public:
* Retroactively corrects the model vault size due to incorrect initialization in a previous patch.
*
*/
void RetroactiveVaultSize() const;
void RetroactiveVaultSize();
bool GetBoolean(const std::u16string& name) const;
int32_t GetI32(const std::u16string& name) const;
int64_t GetI64(const std::u16string& name) const;
@@ -329,13 +325,18 @@ public:
bool HandleMsg(GameMessages::GameMsg& msg) const;
bool OnRequestServerObjectInfo(GameMessages::GameMsg& msg);
void RegisterMsg(const MessageType::Game msgId, auto* self, const auto handler) {
RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1));
}
/**
* @brief The observable for player entity position updates.
*/
static Observable<Entity*, const PositionUpdate&> OnPlayerPositionUpdate;
private:
void WriteLDFData(const std::vector<LDFBaseData*>& ldf, RakNet::BitStream& outBitStream) const;
protected:
LWOOBJID m_ObjectID;
LOT m_TemplateID;
@@ -358,6 +359,7 @@ private:
Entity* m_ParentEntity; //For spawners and the like
std::vector<Entity*> m_ChildEntities;
eGameMasterLevel m_GMLevel;
uint16_t m_CollectibleID;
std::vector<std::string> m_Groups;
uint16_t m_NetworkID;
std::vector<std::function<void()>> m_DieCallbacks;
@@ -383,8 +385,6 @@ private:
bool m_IsParentChildDirty = true;
bool m_IsSleeping = false;
/*
* Collision
*/
@@ -393,7 +393,7 @@ private:
// objectID of receiver and map of notification name to script
std::map<LWOOBJID, std::map<std::string, CppScripts::Script*>> m_Subscriptions;
std::unordered_multimap<MessageType::Game, std::function<bool(GameMessages::GameMsg&)>> m_MsgHandlers;
std::multimap<MessageType::Game, std::function<bool(GameMessages::GameMsg&)>> m_MsgHandlers;
};
/**
@@ -441,7 +441,7 @@ const T& Entity::GetVar(const std::u16string& name) const {
template<typename T>
T Entity::GetVarAs(const std::u16string& name) const {
const auto data = GetVarAsString(name);
return GeneralUtils::TryParse<T>(data).value_or(LDFData<T>::Default);
}

View File

@@ -99,7 +99,7 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE
}
// Exclude the zone control object from any flags
if (!controller) {
if (!controller && info.lot != 14) {
// The client flags means the client should render the entity
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
@@ -320,7 +320,7 @@ const std::unordered_map<std::string, LWOOBJID>& EntityManager::GetSpawnPointEnt
return m_SpawnPoints;
}
void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr) {
void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr, const bool skipChecks) {
if (!entity) {
LOG("Attempted to construct null entity");
return;
@@ -363,14 +363,16 @@ void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr
entity->WriteComponents(stream, eReplicaPacketType::CONSTRUCTION);
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) {
for (auto* player : PlayerManager::GetAllPlayers()) {
// Don't need to construct the player to themselves
if (entity->GetObjectID() == player->GetObjectID()) continue;
if (player->GetPlayerReadyForUpdates()) {
Game::server->Send(stream, player->GetSystemAddress(), false);
} else {
auto* ghostComponent = player->GetComponent<GhostComponent>();
if (ghostComponent) ghostComponent->AddLimboConstruction(entity->GetObjectID());
if (skipChecks) {
Game::server->Send(stream, UNASSIGNED_SYSTEM_ADDRESS, true);
} else {
for (auto* player : PlayerManager::GetAllPlayers()) {
if (player->GetPlayerReadyForUpdates()) {
Game::server->Send(stream, player->GetSystemAddress(), false);
} else {
auto* ghostComponent = player->GetComponent<GhostComponent>();
if (ghostComponent) ghostComponent->AddLimboConstruction(entity->GetObjectID());
}
}
}
} else {
@@ -394,7 +396,7 @@ void EntityManager::ConstructAllEntities(const SystemAddress& sysAddr) {
}
}
UpdateGhosting(PlayerManager::GetPlayer(sysAddr), true);
UpdateGhosting(PlayerManager::GetPlayer(sysAddr));
}
void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr) {
@@ -463,7 +465,7 @@ void EntityManager::UpdateGhosting() {
m_PlayersToUpdateGhosting.clear();
}
void EntityManager::UpdateGhosting(Entity* player, const bool constructAll) {
void EntityManager::UpdateGhosting(Entity* player) {
if (!player) return;
auto* missionComponent = player->GetComponent<MissionComponent>();
@@ -511,12 +513,9 @@ void EntityManager::UpdateGhosting(Entity* player, const bool constructAll) {
ghostComponent->ObserveEntity(id);
entity->SetObservers(entity->GetObservers() + 1);
// TODO: figure out if zone control should be ghosted at all
if (constructAll && entity->GetObjectID() == GetZoneControlEntity()->GetObjectID()) continue;
ConstructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() + 1);
}
}
}
@@ -605,3 +604,23 @@ void EntityManager::FireEventServerSide(Entity* origin, std::string args) {
bool EntityManager::IsExcludedFromGhosting(LOT lot) {
return std::find(m_GhostingExcludedLOTs.begin(), m_GhostingExcludedLOTs.end(), lot) != m_GhostingExcludedLOTs.end();
}
#ifndef _DEBUG
bool EntityManager::SendMsg(GameMessages::GameMsg& msg, std::source_location location) {
if (msg.target == LWOOBJID_EMPTY) {
LOG("Attempted to send message to empty target from funcion %s:%d", location.function_name(), location.line());
}
bool bRet = false;
auto* entity = GetEntity(msg.target);
if (entity) bRet = entity->HandleMsg(msg);
return bRet;
}
#else
bool EntityManager::SendMsg(GameMessages::GameMsg& msg) {
bool bRet = false;
auto* entity = GetEntity(msg.target);
if (entity) bRet = entity->HandleMsg(msg);
return bRet;
}
#endif

View File

@@ -2,14 +2,22 @@
#define ENTITYMANAGER_H
#include <map>
#include <source_location>
#include <stack>
#include <vector>
#include <unordered_map>
#include "dCommonVars.h"
// Convenience macro to send a message to the entity manager
#define SEND_ENTITY_MSG(msg) Game::entityManager->SendMsg(msg)
namespace GameMessages {
struct GameMsg;
};
class Entity;
struct EntityInfo;
class EntityInfo;
class Player;
class User;
enum class eReplicaComponentType : uint32_t;
@@ -42,7 +50,7 @@ public:
const std::unordered_map<LWOOBJID, Entity*> GetAllEntities() const { return m_Entities; }
#endif
void ConstructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
void ConstructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS, bool skipChecks = false);
void DestructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
void SerializeEntity(Entity* entity);
void SerializeEntity(const Entity& entity);
@@ -54,7 +62,7 @@ public:
void SetGhostDistanceMin(float value);
void QueueGhostUpdate(LWOOBJID playerID);
void UpdateGhosting();
void UpdateGhosting(Entity* player, const bool constructAll = false);
void UpdateGhosting(Entity* player);
void CheckGhosting(Entity* entity);
Entity* GetGhostCandidate(LWOOBJID id) const;
bool GetGhostingEnabled() const;
@@ -72,6 +80,14 @@ public:
const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; };
const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; };
// Sends a message to be handled by the receiving entity
#ifndef _DEBUG
bool SendMsg(GameMessages::GameMsg& msg, std::source_location location = std::source_location::current());
#else
bool SendMsg(GameMessages::GameMsg& msg);
#endif
private:
void SerializeEntities();
void KillEntities();

View File

@@ -24,13 +24,12 @@ namespace LeaderboardManager {
std::map<GameID, Leaderboard::Type> leaderboardCache;
}
Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const uint32_t numResults, const Leaderboard::Type leaderboardType) {
Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type leaderboardType) {
this->gameID = gameID;
this->weekly = weekly;
this->infoType = infoType;
this->leaderboardType = leaderboardType;
this->relatedPlayer = relatedPlayer;
this->numResults = numResults;
}
Leaderboard::~Leaderboard() {
@@ -145,7 +144,7 @@ void QueryToLdf(Leaderboard& leaderboard, const std::vector<ILeaderboard::Entry>
}
}
std::vector<ILeaderboard::Entry> FilterToNumResults(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer, const Leaderboard::InfoType infoType, const uint32_t numResults) {
std::vector<ILeaderboard::Entry> FilterTo10(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer, const Leaderboard::InfoType infoType) {
std::vector<ILeaderboard::Entry> toReturn;
int32_t index = 0;
@@ -156,19 +155,18 @@ std::vector<ILeaderboard::Entry> FilterToNumResults(const std::vector<ILeaderboa
}
}
if (leaderboard.size() < numResults) {
if (leaderboard.size() < 10) {
toReturn.assign(leaderboard.begin(), leaderboard.end());
index = 0;
} else if (index < numResults) {
toReturn.assign(leaderboard.begin(), leaderboard.begin() + numResults); // get the top 10 since we are in the top 10
} else if (index < 10) {
toReturn.assign(leaderboard.begin(), leaderboard.begin() + 10); // get the top 10 since we are in the top 10
index = 0;
} else if (index > leaderboard.size() - numResults) {
toReturn.assign(leaderboard.end() - numResults, leaderboard.end()); // get the bottom 10 since we are in the bottom 10
index = leaderboard.size() - numResults;
} else if (index > leaderboard.size() - 10) {
toReturn.assign(leaderboard.end() - 10, leaderboard.end()); // get the bottom 10 since we are in the bottom 10
index = leaderboard.size() - 10;
} else {
auto half = numResults / 2;
toReturn.assign(leaderboard.begin() + index - half, leaderboard.begin() + index + half); // get the 5 above and below
index -= half;
toReturn.assign(leaderboard.begin() + index - 5, leaderboard.begin() + index + 5); // get the 5 above and below
index -= 5;
}
int32_t i = index;
@@ -180,16 +178,14 @@ std::vector<ILeaderboard::Entry> FilterToNumResults(const std::vector<ILeaderboa
}
std::vector<ILeaderboard::Entry> FilterWeeklies(const std::vector<ILeaderboard::Entry>& leaderboard) {
using namespace std::chrono;
// Filter the leaderboard to only include entries from the last week
const auto epochTime = system_clock::now();
constexpr auto oneWeek = weeks(1);
const auto currentTime = std::chrono::system_clock::now();
auto epochTime = currentTime.time_since_epoch().count();
constexpr auto SECONDS_IN_A_WEEK = 60 * 60 * 24 * 7; // if you think im taking leap seconds into account thats cute.
std::vector<ILeaderboard::Entry> weeklyLeaderboard;
for (const auto& entry : leaderboard) {
const sys_time<seconds> asSysTime(seconds(entry.lastPlayedTimestamp));
const auto timeDiff = epochTime - asSysTime;
if (timeDiff < oneWeek) {
if (epochTime - entry.lastPlayedTimestamp < SECONDS_IN_A_WEEK) {
weeklyLeaderboard.push_back(entry);
}
}
@@ -217,15 +213,14 @@ std::vector<ILeaderboard::Entry> ProcessLeaderboard(
const std::vector<ILeaderboard::Entry>& leaderboard,
const bool weekly,
const Leaderboard::InfoType infoType,
const uint32_t relatedPlayer,
const uint32_t numResults) {
const uint32_t relatedPlayer) {
std::vector<ILeaderboard::Entry> toReturn;
if (infoType == Leaderboard::InfoType::Friends) {
const auto friendsLeaderboard = FilterFriends(leaderboard, relatedPlayer);
toReturn = FilterToNumResults(weekly ? FilterWeeklies(friendsLeaderboard) : friendsLeaderboard, relatedPlayer, infoType, numResults);
toReturn = FilterTo10(weekly ? FilterWeeklies(friendsLeaderboard) : friendsLeaderboard, relatedPlayer, infoType);
} else {
toReturn = FilterToNumResults(weekly ? FilterWeeklies(leaderboard) : leaderboard, relatedPlayer, infoType, numResults);
toReturn = FilterTo10(weekly ? FilterWeeklies(leaderboard) : leaderboard, relatedPlayer, infoType);
}
return toReturn;
@@ -260,7 +255,7 @@ void Leaderboard::SetupLeaderboard(bool weekly) {
break;
}
const auto processedLeaderboard = ProcessLeaderboard(leaderboardRes, weekly, infoType, relatedPlayer, numResults);
const auto processedLeaderboard = ProcessLeaderboard(leaderboardRes, weekly, infoType, relatedPlayer);
QueryToLdf(*this, processedLeaderboard);
}
@@ -306,8 +301,8 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi
}
}
void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t numResults) {
Leaderboard leaderboard(gameID, infoType, weekly, playerID, numResults, GetLeaderboardType(gameID));
void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID) {
Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID));
leaderboard.SetupLeaderboard(weekly);
leaderboard.Send(targetID);
}

View File

@@ -37,7 +37,7 @@ public:
None
};
Leaderboard() = delete;
Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const uint32_t numResults, const Leaderboard::Type = None);
Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type = None);
~Leaderboard();
@@ -79,7 +79,6 @@ private:
InfoType infoType;
Leaderboard::Type leaderboardType;
bool weekly;
uint32_t numResults;
public:
LeaderboardEntry& PushBackEntry() {
return entries.emplace_back();
@@ -91,7 +90,7 @@ public:
};
namespace LeaderboardManager {
void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t numResults);
void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID);
void SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0);

View File

@@ -29,7 +29,6 @@
#include "MessageType/Chat.h"
#include "BitStreamUtils.h"
#include "CheatDetection.h"
#include "CharacterComponent.h"
UserManager* UserManager::m_Address = nullptr;
@@ -198,6 +197,10 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) {
skillComponent->Reset();
}
GameMessages::ClearSessionFlags msg{};
msg.target = chars[i]->GetObjectID();
SEND_ENTITY_MSG(msg);
Game::entityManager->DestroyEntity(chars[i]->GetEntity());
chars[i]->SaveXMLToDatabase();
@@ -211,8 +214,7 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) {
for (const auto& characterId : Database::Get()->GetAccountCharacterIds(u->GetAccountID())) {
Character* character = new Character(characterId, u);
character->UpdateFromDatabase();
character->SetIsNewLogin();
character->UpdateFromDatabase(true);
chars.push_back(character);
}
@@ -306,13 +308,13 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
LOT shirtLOT = FindCharShirtID(shirtColor, shirtStyle);
LOT pantsLOT = FindCharPantsID(pantsColor);
if (!name.empty() && Database::Get()->IsNameInUse(name)) {
if (!name.empty() && Database::Get()->GetCharacterInfo(name)) {
LOG("AccountID: %i chose unavailable name: %s", u->GetAccountID(), name.c_str());
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::CUSTOM_NAME_IN_USE);
return;
}
if (Database::Get()->IsNameInUse(predefinedName)) {
if (Database::Get()->GetCharacterInfo(predefinedName)) {
LOG("AccountID: %i chose unavailable predefined name: %s", u->GetAccountID(), predefinedName.c_str());
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::PREDEFINED_NAME_IN_USE);
return;
@@ -325,7 +327,7 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
}
//Now that the name is ok, we can get an objectID from Master:
ObjectIDManager::RequestPersistentID([=, this](uint32_t objectID) {
ObjectIDManager::RequestPersistentID([=, this](uint32_t objectID) mutable {
if (Database::Get()->GetCharacterInfo(objectID)) {
LOG("Character object id unavailable, check object_id_tracker!");
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE);
@@ -341,10 +343,7 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
xml << "<char acct=\"" << u->GetAccountID() << "\" cc=\"0\" gm=\"0\" ft=\"0\" llog=\"" << time(NULL) << "\" ";
xml << "ls=\"0\" lzx=\"-626.5847\" lzy=\"613.3515\" lzz=\"-28.6374\" lzrx=\"0.0\" lzry=\"0.7015\" lzrz=\"0.0\" lzrw=\"0.7126\" ";
xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\">";
xml << "<vl><l id=\"1000\" cid=\"0\"/></vl>";
xml << "</char>";
xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\"></char>";
xml << "<dest hm=\"4\" hc=\"4\" im=\"0\" ic=\"0\" am=\"0\" ac=\"0\" d=\"0\"/>";
@@ -373,14 +372,13 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
// If predefined name is invalid, change it to be their object id
// that way more than one player can create characters if the predefined name files are not provided
auto assignedPredefinedName = predefinedName;
if (assignedPredefinedName == "INVALID") {
if (predefinedName == "INVALID") {
std::stringstream nameObjID;
nameObjID << "minifig" << objectID;
assignedPredefinedName = nameObjID.str();
predefinedName = nameObjID.str();
}
std::string_view nameToAssign = !name.empty() && nameOk ? name : assignedPredefinedName;
std::string_view nameToAssign = !name.empty() && nameOk ? name : predefinedName;
std::string pendingName = !name.empty() && !nameOk ? name : "";
ICharInfo::Info info;
@@ -526,13 +524,6 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, zoneID, character->GetZoneClone(), false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
if (character) {
auto* entity = character->GetEntity();
if (entity) {
auto* characterComponent = entity->GetComponent<CharacterComponent>();
if (characterComponent) {
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
}
}
character->SetZoneID(zoneID);
character->SetZoneInstance(zoneInstance);
character->SetZoneClone(zoneClone);

View File

@@ -20,7 +20,7 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bi
//Handle player damage cooldown
if (entity->IsPlayer() && !this->m_DontApplyImmune) {
const float immunityTime = Game::zoneManager->GetWorldConfig().globalImmunityTime;
const float immunityTime = Game::zoneManager->GetWorldConfig()->globalImmunityTime;
destroyableComponent->SetDamageCooldownTimer(immunityTime);
}
}
@@ -214,7 +214,7 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
//Handle player damage cooldown
if (isSuccess && targetEntity->IsPlayer() && !this->m_DontApplyImmune) {
destroyableComponent->SetDamageCooldownTimer(Game::zoneManager->GetWorldConfig().globalImmunityTime);
destroyableComponent->SetDamageCooldownTimer(Game::zoneManager->GetWorldConfig()->globalImmunityTime);
}
eBasicAttackSuccessTypes successState = eBasicAttackSuccessTypes::FAILIMMUNE;

View File

@@ -42,15 +42,10 @@ void PropertyTeleportBehavior::Handle(BehaviorContext* context, RakNet::BitStrea
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
if (entity->GetCharacter()) {
auto* characterComponent = entity->GetComponent<CharacterComponent>();
if (characterComponent) {
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
characterComponent->SetLastRocketConfig(u"");
}
entity->GetCharacter()->SetZoneID(zoneID);
entity->GetCharacter()->SetZoneInstance(zoneInstance);
entity->GetCharacter()->SetZoneClone(zoneClone);
entity->GetComponent<CharacterComponent>()->SetLastRocketConfig(u"");
}
entity->GetCharacter()->SaveXMLToDatabase();

View File

@@ -27,7 +27,6 @@
#include "CDActivityRewardsTable.h"
#include "CDActivitiesTable.h"
#include "LeaderboardManager.h"
#include "CharacterComponent.h"
ActivityComponent::ActivityComponent(Entity* parent, int32_t activityID) : Component(parent) {
/*
@@ -334,7 +333,7 @@ bool ActivityComponent::IsPlayedBy(LWOOBJID playerID) const {
return false;
}
bool ActivityComponent::CheckCost(Entity* player) const {
bool ActivityComponent::TakeCost(Entity* player) const {
if (m_ActivityInfo.optionalCostLOT <= 0 || m_ActivityInfo.optionalCostCount <= 0)
return true;
@@ -345,17 +344,9 @@ bool ActivityComponent::CheckCost(Entity* player) const {
if (inventoryComponent->GetLotCount(m_ActivityInfo.optionalCostLOT) < m_ActivityInfo.optionalCostCount)
return false;
return true;
}
inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount);
bool ActivityComponent::TakeCost(Entity* player) const{
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
if (CheckCost(player)) {
inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount);
return true;
}
else return false;
return true;
}
void ActivityComponent::PlayerReady(Entity* player, bool bReady) {
@@ -390,7 +381,7 @@ ActivityInstance* ActivityComponent::NewInstance() {
void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const {
for (LobbyPlayer* player : lobby) {
auto* entity = player->GetEntity();
if (entity == nullptr || !CheckCost(entity)) {
if (entity == nullptr || !TakeCost(entity)) {
continue;
}
@@ -535,11 +526,6 @@ void ActivityInstance::StartZone() {
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", player->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
if (player->GetCharacter()) {
auto* characterComponent = player->GetComponent<CharacterComponent>();
if (characterComponent) {
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
}
player->GetCharacter()->SetZoneID(zoneID);
player->GetCharacter()->SetZoneInstance(zoneInstance);
player->GetCharacter()->SetZoneClone(zoneClone);

View File

@@ -234,17 +234,10 @@ public:
*/
bool IsPlayedBy(LWOOBJID playerID) const;
/**
* Checks if the entity has enough cost to play this activity
* @param player the entity to check
* @return true if the entity has enough cost to play this activity, false otherwise
*/
bool CheckCost(Entity* player) const;
/**
* Removes the cost of the activity (e.g. green imaginate) for the entity that plays this activity
* @param player the entity to take cost for
* @return true if the cost was taken, false otherwise
* @return true if the cost was successfully deducted, false otherwise
*/
bool TakeCost(Entity* player) const;

View File

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

View File

@@ -49,19 +49,13 @@ CharacterComponent::CharacterComponent(Entity* parent, Character* character, con
m_LastUpdateTimestamp = std::time(nullptr);
m_SystemAddress = systemAddress;
RegisterMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, this, &CharacterComponent::OnRequestServerObjectInfo);
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &CharacterComponent::OnGetObjectReportInfo);
}
bool CharacterComponent::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) {
auto& request = static_cast<GameMessages::RequestServerObjectInfo&>(msg);
AMFArrayValue response;
bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& request = static_cast<GameMessages::GetObjectReportInfo&>(msg);
response.Insert("visible", true);
response.Insert("objectID", std::to_string(request.targetForReport));
response.Insert("serverInfo", true);
auto& data = *response.InsertArray("data");
auto& cmptType = data.PushDebug("Character");
auto& cmptType = request.info->PushDebug("Character");
cmptType.PushDebug<AMFIntValue>("Component ID") = GeneralUtils::ToUnderlying(ComponentType);
cmptType.PushDebug<AMFIntValue>("Character's account ID") = m_Character->GetParentUser()->GetAccountID();
@@ -83,9 +77,6 @@ bool CharacterComponent::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) {
cmptType.PushDebug<AMFIntValue>("Current Activity Type") = GeneralUtils::ToUnderlying(m_CurrentActivity);
cmptType.PushDebug<AMFDoubleValue>("Property Clone ID") = m_Character->GetPropertyCloneID();
GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, m_Parent->GetSystemAddress());
LOG("Handled!");
return true;
}
@@ -95,21 +86,15 @@ bool CharacterComponent::LandingAnimDisabled(int zoneID) {
case 556:
case 1101:
case 1202:
case 1150:
case 1151:
case 1203:
case 1204:
case 1250:
case 1251:
case 1261:
case 1301:
case 1302:
case 1303:
case 1350:
case 1401:
case 1402:
case 1403:
case 1450:
case 1603:
case 2001:
return true;
@@ -245,8 +230,6 @@ void CharacterComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
SetReputation(0);
}
auto* vl = character->FirstChildElement("vl");
if (vl) LoadVisitedLevelsXml(*vl);
character->QueryUnsigned64Attribute("co", &m_ClaimCodes[0]);
character->QueryUnsigned64Attribute("co1", &m_ClaimCodes[1]);
character->QueryUnsigned64Attribute("co2", &m_ClaimCodes[2]);
@@ -376,10 +359,6 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
return;
}
auto* vl = character->FirstChildElement("vl");
if (!vl) vl = character->InsertNewChildElement("vl");
UpdateVisitedLevelsXml(*vl);
if (m_ClaimCodes[0] != 0) character->SetAttribute("co", m_ClaimCodes[0]);
if (m_ClaimCodes[1] != 0) character->SetAttribute("co1", m_ClaimCodes[1]);
if (m_ClaimCodes[2] != 0) character->SetAttribute("co2", m_ClaimCodes[2]);
@@ -840,7 +819,7 @@ void CharacterComponent::AwardClaimCodes() {
subject << "%[RewardCodes_" << rewardCode << "_subjectText]";
std::ostringstream body;
body << "%[RewardCodes_" << rewardCode << "_bodyText]";
Mail::SendMail(m_Parent, subject.str(), body.str(), attachmentLOT, 1);
Mail::SendMail(LWOOBJID_EMPTY, "%[MAIL_SYSTEM_NOTIFICATION]", m_Parent, subject.str(), body.str(), attachmentLOT, 1);
}
}
@@ -861,9 +840,8 @@ void CharacterComponent::SendToZone(LWOMAPID zoneId, LWOCLONEID cloneId) const {
character->SetZoneID(zoneID);
character->SetZoneInstance(zoneInstance);
character->SetZoneClone(zoneClone);
characterComponent->SetLastRocketConfig(u"");
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
character->SaveXMLToDatabase();
}
@@ -890,30 +868,3 @@ void CharacterComponent::SetRespawnPos(const NiPoint3& position) {
void CharacterComponent::SetRespawnRot(const NiQuaternion& rotation) {
m_respawnRot = rotation;
}
void CharacterComponent::AddVisitedLevel(const LWOZONEID zoneID) {
LWOZONEID toInsert(zoneID.GetMapID(), LWOINSTANCEID_INVALID, zoneID.GetCloneID());
m_VisitedLevels.insert(toInsert);
}
void CharacterComponent::UpdateVisitedLevelsXml(tinyxml2::XMLElement& vl) {
vl.DeleteChildren();
// <vl>
for (const auto zoneID : m_VisitedLevels) {
// <l id=\"1100\" cid=\"0\"/>
auto* l = vl.InsertNewChildElement("l");
l->SetAttribute("id", zoneID.GetMapID());
l->SetAttribute("cid", zoneID.GetCloneID());
}
// </vl>
}
void CharacterComponent::LoadVisitedLevelsXml(const tinyxml2::XMLElement& vl) {
// <vl>
for (const auto* l = vl.FirstChildElement("l"); l != nullptr; l = l->NextSiblingElement("l")) {
// <l id=\"1100\" cid=\"0\"/>
LWOZONEID toInsert(l->IntAttribute("id"), LWOINSTANCEID_INVALID, l->IntAttribute("cid"));
m_VisitedLevels.insert(toInsert);
}
// </vl>
}

View File

@@ -10,7 +10,6 @@
#include "tinyxml2.h"
#include "eReplicaComponentType.h"
#include <array>
#include <set>
#include "Loot.h"
enum class eGameActivity : uint32_t;
@@ -322,16 +321,9 @@ public:
* Character info regarding this character, including clothing styles, etc.
*/
Character* m_Character;
/* Saves the provided zoneID as a visited level. Ignores InstanceID */
void AddVisitedLevel(const LWOZONEID zoneID);
/* Updates the VisitedLevels (vl) node of the charxml */
void UpdateVisitedLevelsXml(tinyxml2::XMLElement& doc);
/* Reads the VisitedLevels (vl) node of the charxml */
void LoadVisitedLevelsXml(const tinyxml2::XMLElement& doc);
private:
bool OnRequestServerObjectInfo(GameMessages::GameMsg& msg);
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
/**
* The map of active venture vision effects
@@ -627,8 +619,6 @@ private:
std::map<LWOOBJID, Loot::Info> m_DroppedLoot;
uint64_t m_DroppedCoins = 0;
std::set<LWOZONEID> m_VisitedLevels;
};
#endif // CHARACTERCOMPONENT_H

View File

@@ -774,10 +774,10 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
if (Game::zoneManager->GetPlayerLoseCoinOnDeath()) {
auto* character = m_Parent->GetCharacter();
uint64_t coinsTotal = character->GetCoins();
const uint64_t minCoinsToLose = Game::zoneManager->GetWorldConfig().coinsLostOnDeathMin;
const uint64_t minCoinsToLose = Game::zoneManager->GetWorldConfig()->coinsLostOnDeathMin;
if (coinsTotal >= minCoinsToLose) {
const uint64_t maxCoinsToLose = Game::zoneManager->GetWorldConfig().coinsLostOnDeathMax;
const float coinPercentageToLose = Game::zoneManager->GetWorldConfig().coinsLostOnDeathPercent;
const uint64_t maxCoinsToLose = Game::zoneManager->GetWorldConfig()->coinsLostOnDeathMax;
const float coinPercentageToLose = Game::zoneManager->GetWorldConfig()->coinsLostOnDeathPercent;
uint64_t coinsToLose = std::max(static_cast<uint64_t>(coinsTotal * coinPercentageToLose), minCoinsToLose);
coinsToLose = std::min(maxCoinsToLose, coinsToLose);

View File

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

Some files were not shown because too many files have changed in this diff Show More