Merge branch 'main' into leaderboards-again

This commit is contained in:
David Markowitz 2025-05-05 00:21:43 -07:00
commit 82524b4165
132 changed files with 51229 additions and 1114 deletions

View File

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

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "thirdparty/cpp-httplib"]
path = thirdparty/cpp-httplib
url = https://github.com/yhirose/cpp-httplib
[submodule "thirdparty/tinyxml2"] [submodule "thirdparty/tinyxml2"]
path = thirdparty/tinyxml2 path = thirdparty/tinyxml2
url = https://github.com/leethomason/tinyxml2 url = https://github.com/leethomason/tinyxml2

View File

@ -247,8 +247,9 @@ include_directories(
"thirdparty/recastnavigation" "thirdparty/recastnavigation"
"thirdparty/SQLite" "thirdparty/SQLite"
"thirdparty/cpplinq" "thirdparty/cpplinq"
"thirdparty/cpp-httplib"
"thirdparty/MD5" "thirdparty/MD5"
"thirdparty/nlohmann"
"thirdparty/mongoose"
) )
# Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux) # Add system specfic includes for Apple, Windows and Other Unix OS' (including Linux)

View File

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

View File

@ -78,7 +78,7 @@ git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer
### Windows packages ### 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. 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/) (version <font size="4">**CMake version 3.25**</font> or later!). 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>!).
### MacOS packages ### MacOS packages
Ensure you have [brew](https://brew.sh) installed. 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 #### Required CMake version
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. 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.
You can check your CMake version by using the following command in a terminal. You can check your CMake version by using the following command in a terminal.
```bash ```bash
cmake --version cmake --version
@ -354,6 +354,10 @@ Now follow the [build](#build-the-server) section for your system and your serve
## In-game commands ## In-game commands
* A list of all in-game commands can be found [here](./docs/Commands.md). * A list of all in-game commands can be found [here](./docs/Commands.md).
## Chat Web API
* The Chat server has an API that can be enabled via `chatconfig.ini`.
* You can view the OpenAPI doc for the API here [here](./docs/ChatWebAPI.yaml).
## Verifying your client files ## Verifying your client files
### LEGO® Universe 1.10.64 ### LEGO® Universe 1.10.64

View File

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

View File

@ -2,6 +2,8 @@ set(DCHATSERVER_SOURCES
"ChatIgnoreList.cpp" "ChatIgnoreList.cpp"
"ChatPacketHandler.cpp" "ChatPacketHandler.cpp"
"PlayerContainer.cpp" "PlayerContainer.cpp"
"ChatWebAPI.cpp"
"JSONUtils.cpp"
) )
add_executable(ChatServer "ChatServer.cpp") add_executable(ChatServer "ChatServer.cpp")
@ -12,5 +14,5 @@ add_library(dChatServer ${DCHATSERVER_SOURCES})
target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer") target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer")
target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter) target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter)
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer) target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer mongoose)

View File

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

View File

@ -5,17 +5,16 @@ struct Packet;
#include <cstdint> #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 { namespace ChatIgnoreList {
void GetIgnoreList(Packet* packet); void GetIgnoreList(Packet* packet);
void AddIgnore(Packet* packet); void AddIgnore(Packet* packet);
void RemoveIgnore(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 { enum class AddResponse : uint8_t {
SUCCESS, SUCCESS,
ALREADY_IGNORED, ALREADY_IGNORED,

View File

@ -29,7 +29,6 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
auto& player = Game::playerContainer.GetPlayerDataMutable(playerID); auto& player = Game::playerContainer.GetPlayerDataMutable(playerID);
if (!player) return; if (!player) return;
if (player.friends.empty()) {
auto friendsList = Database::Get()->GetFriendsList(playerID); auto friendsList = Database::Get()->GetFriendsList(playerID);
for (const auto& friendData : friendsList) { for (const auto& friendData : friendsList) {
FriendData fd; FriendData fd;
@ -50,7 +49,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
fd.zoneID = fr.zoneID; fd.zoneID = fr.zoneID;
//Since this friend is online, we need to update them on the fact that we've just logged in: //Since this friend is online, we need to update them on the fact that we've just logged in:
SendFriendUpdate(fr, player, 1, fd.isBestFriend); if (player.isLogin) SendFriendUpdate(fr, player, 1, fd.isBestFriend);
} else { } else {
fd.isOnline = false; fd.isOnline = false;
fd.zoneID = LWOZONEID(); fd.zoneID = LWOZONEID();
@ -58,7 +57,6 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
player.friends.push_back(fd); player.friends.push_back(fd);
} }
}
//Now, we need to send the friendlist to the server they came from: //Now, we need to send the friendlist to the server they came from:
CBITSTREAM; CBITSTREAM;
@ -75,7 +73,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
data.Serialize(bitStream); data.Serialize(bitStream);
} }
SystemAddress sysAddr = player.sysAddr; SystemAddress sysAddr = player.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -105,7 +103,8 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
return; return;
}; };
auto& requestee = Game::playerContainer.GetPlayerDataMutable(playerName); // Intentional copy
PlayerData requestee = Game::playerContainer.GetPlayerData(playerName);
// Check if player is online first // Check if player is online first
if (isBestFriendRequest && !requestee) { if (isBestFriendRequest && !requestee) {
@ -123,7 +122,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
requesteeFriendData.isOnline = false; requesteeFriendData.isOnline = false;
requesteeFriendData.zoneID = requestor.zoneID; requesteeFriendData.zoneID = requestor.zoneID;
requestee.friends.push_back(requesteeFriendData); requestee.friends.push_back(requesteeFriendData);
requestee.sysAddr = UNASSIGNED_SYSTEM_ADDRESS; requestee.worldServerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
break; break;
} }
} }
@ -190,24 +189,29 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus); Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus);
// Sent the best friend update here if the value is 3 // Sent the best friend update here if the value is 3
if (bestFriendStatus == 3U) { if (bestFriendStatus == 3U) {
requestee.countOfBestFriends += 1; if (requestee.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
requestor.countOfBestFriends += 1; 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) { for (auto& friendData : requestor.friends) {
if (friendData.friendID == requestee.playerID) { if (friendData.friendID == requestee.playerID) {
friendData.isBestFriend = true; friendData.isBestFriend = true;
} }
} }
for (auto& friendData : requestee.friends) { requestor.countOfBestFriends += 1;
auto& toModify = Game::playerContainer.GetPlayerDataMutable(playerName);
if (toModify) {
for (auto& friendData : toModify.friends) {
if (friendData.friendID == requestor.playerID) { if (friendData.friendID == requestor.playerID) {
friendData.isBestFriend = true; friendData.isBestFriend = true;
} }
} }
toModify.countOfBestFriends += 1;
}
} }
} }
} else { } else {
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true); if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true);
} }
} else { } else {
auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends(); auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends();
@ -380,7 +384,7 @@ void ChatPacketHandler::HandleWho(Packet* packet) {
bitStream.Write(player.zoneID.GetCloneID()); bitStream.Write(player.zoneID.GetCloneID());
bitStream.Write(request.playerName); bitStream.Write(request.playerName);
SystemAddress sysAddr = sender.sysAddr; SystemAddress sysAddr = sender.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -414,7 +418,7 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
} }
} }
} }
SystemAddress sysAddr = sender.sysAddr; SystemAddress sysAddr = sender.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -515,6 +519,28 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::GENERAL, eChatMessageResponseCode::NOTFRIENDS); 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) { void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {
CBITSTREAM; CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
@ -533,7 +559,7 @@ void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const P
bitStream.Write(responseCode); bitStream.Write(responseCode);
bitStream.Write(message); bitStream.Write(message);
SystemAddress sysAddr = routeTo.sysAddr; SystemAddress sysAddr = routeTo.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -576,6 +602,19 @@ void ChatPacketHandler::HandleTeamInvite(Packet* packet) {
SendTeamInvite(other, player); SendTeamInvite(other, player);
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str()); 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 ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) { void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
@ -589,7 +628,7 @@ void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
LWOOBJID leaderID = LWOOBJID_EMPTY; LWOOBJID leaderID = LWOOBJID_EMPTY;
inStream.Read(leaderID); inStream.Read(leaderID);
LOG("Accepted invite: %llu -> %llu (%d)", playerID, leaderID, declined); LOG("Invite reponse received: %llu -> %llu (%d)", playerID, leaderID, declined);
if (declined) { if (declined) {
return; return;
@ -718,14 +757,15 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
const auto& data = Game::playerContainer.GetPlayerData(playerID); const auto& data = Game::playerContainer.GetPlayerData(playerID);
if (team != nullptr && data) { 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()) { if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
Game::playerContainer.RemoveMember(team, playerID, false, false, true, true); Game::playerContainer.RemoveMember(team, playerID, false, false, false, true);
return; return;
} }
if (team->memberIDs.size() <= 1 && !team->local) { if (team->memberIDs.size() <= 1 && !team->local) {
Game::playerContainer.DisbandTeam(team); Game::playerContainer.DisbandTeam(team, LWOOBJID_EMPTY, u"");
return; return;
} }
@ -768,7 +808,7 @@ void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerD
bitStream.Write(LUWString(sender.playerName.c_str())); bitStream.Write(LUWString(sender.playerName.c_str()));
bitStream.Write(sender.playerID); bitStream.Write(sender.playerID);
SystemAddress sysAddr = receiver.sysAddr; SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -795,7 +835,7 @@ void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool b
bitStream.Write(character); bitStream.Write(character);
} }
SystemAddress sysAddr = receiver.sysAddr; SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -820,7 +860,7 @@ void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64L
bitStream.Write(character); bitStream.Write(character);
} }
SystemAddress sysAddr = receiver.sysAddr; SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -837,7 +877,7 @@ void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i
bitStream.Write(i64PlayerID); bitStream.Write(i64PlayerID);
SystemAddress sysAddr = receiver.sysAddr; SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -866,7 +906,7 @@ void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFr
} }
bitStream.Write(zoneID); bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.sysAddr; SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -892,7 +932,7 @@ void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bD
bitStream.Write(character); bitStream.Write(character);
} }
SystemAddress sysAddr = receiver.sysAddr; SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -913,7 +953,7 @@ void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOO
} }
bitStream.Write(zoneID); bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.sysAddr; SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -955,7 +995,7 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla
bitStream.Write<uint8_t>(isBestFriend); //isBFF bitStream.Write<uint8_t>(isBestFriend); //isBFF
bitStream.Write<uint8_t>(0); //isFTP bitStream.Write<uint8_t>(0); //isFTP
SystemAddress sysAddr = friendData.sysAddr; SystemAddress sysAddr = friendData.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -977,7 +1017,7 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
bitStream.Write(LUWString(sender.playerName)); 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. 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.sysAddr; SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -990,7 +1030,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_RESPONSE); BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_RESPONSE);
bitStream.Write(responseCode); bitStream.Write(responseCode);
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver. // 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.sysAddr != UNASSIGNED_SYSTEM_ADDRESS); bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS);
// Then write the player name // Then write the player name
bitStream.Write(LUWString(sender.playerName)); bitStream.Write(LUWString(sender.playerName));
// Then if this is an acceptance code, write the following extra info. // Then if this is an acceptance code, write the following extra info.
@ -1000,7 +1040,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
bitStream.Write(isBestFriendRequest); //isBFF bitStream.Write(isBestFriendRequest); //isBFF
bitStream.Write<uint8_t>(0); //isFTP bitStream.Write<uint8_t>(0); //isFTP
} }
SystemAddress sysAddr = receiver.sysAddr; SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }
@ -1014,6 +1054,6 @@ void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string
bitStream.Write<uint8_t>(isSuccessful); //isOnline bitStream.Write<uint8_t>(isSuccessful); //isOnline
bitStream.Write(LUWString(personToRemove)); bitStream.Write(LUWString(personToRemove));
SystemAddress sysAddr = receiver.sysAddr; SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET; SEND_PACKET;
} }

View File

@ -64,12 +64,15 @@ namespace ChatPacketHandler {
void HandleTeamPromote(Packet* packet); void HandleTeamPromote(Packet* packet);
void HandleTeamLootOption(Packet* packet); void HandleTeamLootOption(Packet* packet);
void HandleTeamStatusRequest(Packet* packet); void HandleTeamStatusRequest(Packet* packet);
void OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr);
void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender); 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 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 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 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 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 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 SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);

View File

@ -28,6 +28,8 @@
#include "RakNetDefines.h" #include "RakNetDefines.h"
#include "MessageIdentifiers.h" #include "MessageIdentifiers.h"
#include "ChatWebAPI.h"
namespace Game { namespace Game {
Logger* logger = nullptr; Logger* logger = nullptr;
dServer* server = nullptr; dServer* server = nullptr;
@ -74,7 +76,8 @@ int main(int argc, char** argv) {
Game::assetManager = new AssetManager(clientPath); Game::assetManager = new AssetManager(clientPath);
} catch (std::runtime_error& ex) { } catch (std::runtime_error& ex) {
LOG("Got an error while setting up assets: %s", ex.what()); LOG("Got an error while setting up assets: %s", ex.what());
delete Game::logger;
delete Game::config;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
@ -84,18 +87,32 @@ int main(int argc, char** argv) {
} catch (std::exception& ex) { } catch (std::exception& ex) {
LOG("Got an error while connecting to the database: %s", ex.what()); LOG("Got an error while connecting to the database: %s", ex.what());
Database::Destroy("ChatServer"); Database::Destroy("ChatServer");
delete Game::server;
delete Game::logger; delete Game::logger;
delete Game::config;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
// seyup the chat api web server
bool web_server_enabled = Game::config->GetValue("web_server_enabled") == "1";
ChatWebAPI chatwebapi;
if (web_server_enabled && !chatwebapi.Startup()){
// if we want the web api and it fails to start, exit
LOG("Failed to start web server, shutting down.");
Database::Destroy("ChatServer");
delete Game::logger;
delete Game::config;
return EXIT_FAILURE;
};
//Find out the master's IP: //Find out the master's IP:
std::string masterIP; std::string masterIP;
uint32_t masterPort = 1000; uint32_t masterPort = 1000;
std::string masterPassword;
auto masterInfo = Database::Get()->GetMasterInfo(); auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) { if (masterInfo) {
masterIP = masterInfo->ip; masterIP = masterInfo->ip;
masterPort = masterInfo->port; masterPort = masterInfo->port;
masterPassword = masterInfo->password;
} }
//It's safe to pass 'localhost' here, as the IP is only used as the external IP. //It's safe to pass 'localhost' here, as the IP is only used as the external IP.
std::string ourIP = "localhost"; std::string ourIP = "localhost";
@ -104,7 +121,7 @@ int main(int argc, char** argv) {
const auto externalIPString = Game::config->GetValue("external_ip"); const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString; if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal); Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal, masterPassword);
const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false); const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false);
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF); Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF);
@ -149,6 +166,11 @@ int main(int argc, char** argv) {
packet = nullptr; packet = nullptr;
} }
//Check and handle web requests:
if (web_server_enabled) {
chatwebapi.ReceiveRequests();
}
//Push our log every 30s: //Push our log every 30s:
if (framesSinceLastFlush >= logFlushTime) { if (framesSinceLastFlush >= logFlushTime) {
Game::logger->Flush(); Game::logger->Flush();
@ -202,6 +224,10 @@ void HandlePacket(Packet* packet) {
if (connection != eConnectionType::CHAT) return; if (connection != eConnectionType::CHAT) return;
inStream.Read(chatMessageID); inStream.Read(chatMessageID);
// Our packing byte wasnt there? Probably a false packet
if (inStream.GetNumberOfUnreadBits() < 8) return;
inStream.IgnoreBytes(1);
switch (chatMessageID) { switch (chatMessageID) {
case MessageType::Chat::GM_MUTE: case MessageType::Chat::GM_MUTE:
Game::playerContainer.MuteUpdate(packet); Game::playerContainer.MuteUpdate(packet);
@ -286,11 +312,10 @@ void HandlePacket(Packet* packet) {
case MessageType::Chat::LOGIN_SESSION_NOTIFY: case MessageType::Chat::LOGIN_SESSION_NOTIFY:
Game::playerContainer.InsertPlayer(packet); Game::playerContainer.InsertPlayer(packet);
break; break;
case MessageType::Chat::GM_ANNOUNCE: { case MessageType::Chat::GM_ANNOUNCE:
// we just forward this packet to every connected server // we just forward this packet to every connected server
inStream.ResetReadPointer(); inStream.ResetReadPointer();
Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin Game::server->Send(inStream, packet->systemAddress, true); // send to everyone except origin
}
break; break;
case MessageType::Chat::UNEXPECTED_DISCONNECT: case MessageType::Chat::UNEXPECTED_DISCONNECT:
Game::playerContainer.ScheduleRemovePlayer(packet); Game::playerContainer.ScheduleRemovePlayer(packet);
@ -301,6 +326,9 @@ void HandlePacket(Packet* packet) {
case MessageType::Chat::SHOW_ALL: case MessageType::Chat::SHOW_ALL:
ChatPacketHandler::HandleShowAll(packet); ChatPacketHandler::HandleShowAll(packet);
break; break;
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
ChatPacketHandler::OnAchievementNotify(inStream, packet->systemAddress);
break;
case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE: case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE:
case MessageType::Chat::WORLD_DISCONNECT_REQUEST: case MessageType::Chat::WORLD_DISCONNECT_REQUEST:
case MessageType::Chat::WORLD_PROXIMITY_RESPONSE: case MessageType::Chat::WORLD_PROXIMITY_RESPONSE:
@ -336,7 +364,6 @@ void HandlePacket(Packet* packet) {
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT: case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
case MessageType::Chat::UGCC_REQUEST: case MessageType::Chat::UGCC_REQUEST:
case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE: case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW: case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW:
case MessageType::Chat::PLAYER_READY: case MessageType::Chat::PLAYER_READY:
case MessageType::Chat::GET_DONATION_TOTAL: case MessageType::Chat::GET_DONATION_TOTAL:

197
dChatServer/ChatWebAPI.cpp Normal file
View File

@ -0,0 +1,197 @@
#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;
const std::string& listen_address = "http://localhost:" + 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__

62
dChatServer/JSONUtils.cpp Normal file
View File

@ -0,0 +1,62 @@
#include "JSONUtils.h"
#include "json.hpp"
using json = nlohmann::json;
void to_json(json& data, const PlayerData& playerData) {
data["id"] = playerData.playerID;
data["name"] = playerData.playerName;
data["gm_level"] = playerData.gmLevel;
data["muted"] = playerData.GetIsMuted();
auto& zoneID = data["zone_id"];
zoneID["map_id"] = playerData.zoneID.GetMapID();
zoneID["instance_id"] = playerData.zoneID.GetInstanceID();
zoneID["clone_id"] = playerData.zoneID.GetCloneID();
}
void to_json(json& data, const PlayerContainer& playerContainer) {
data = json::array();
for(auto& playerData : playerContainer.GetAllPlayers()) {
if (playerData.first == LWOOBJID_EMPTY) continue;
data.push_back(playerData.second);
}
}
void to_json(json& data, const TeamContainer& teamContainer) {
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;
data["local"] = teamData.local;
auto& leader = Game::playerContainer.GetPlayerData(teamData.leaderID);
data["leader"] = leader.playerName;
auto& members = data["members"];
for (auto& member : teamData.memberIDs) {
auto& playerData = Game::playerContainer.GetPlayerData(member);
if (!playerData) continue;
members.push_back(playerData);
}
}
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

@ -32,7 +32,10 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
return; return;
} }
auto isLogin = !m_Players.contains(playerId);
auto& data = m_Players[playerId]; auto& data = m_Players[playerId];
data = PlayerData();
data.isLogin = isLogin;
data.playerID = playerId; data.playerID = playerId;
uint32_t len; uint32_t len;
@ -49,7 +52,7 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
if (!inStream.Read(data.zoneID)) return; if (!inStream.Read(data.zoneID)) return;
if (!inStream.Read(data.muteExpire)) return; if (!inStream.Read(data.muteExpire)) return;
if (!inStream.Read(data.gmLevel)) return; if (!inStream.Read(data.gmLevel)) return;
data.sysAddr = packet->systemAddress; data.worldServerSysAddr = packet->systemAddress;
m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName); m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName);
m_PlayerCount++; m_PlayerCount++;
@ -216,7 +219,7 @@ TeamData* PlayerContainer::CreateTeam(LWOOBJID leader, bool local) {
team->leaderID = leader; team->leaderID = leader;
team->local = local; team->local = local;
mTeams.push_back(team); GetTeamsMut().push_back(team);
AddMember(team, leader); AddMember(team, leader);
@ -224,7 +227,7 @@ TeamData* PlayerContainer::CreateTeam(LWOOBJID leader, bool local) {
} }
TeamData* PlayerContainer::GetTeam(LWOOBJID playerID) { TeamData* PlayerContainer::GetTeam(LWOOBJID playerID) {
for (auto* team : mTeams) { for (auto* team : GetTeams()) {
if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue; if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue;
return team; return team;
@ -238,7 +241,7 @@ void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
LOG("Tried to add player to team that already had 4 players"); LOG("Tried to add player to team that already had 4 players");
const auto& player = GetPlayerData(playerID); const auto& player = GetPlayerData(playerID);
if (!player) return; if (!player) return;
ChatPackets::SendSystemMessage(player.sysAddr, u"The teams is full! You have not been added to a team!"); ChatPackets::SendSystemMessage(player.worldServerSysAddr, u"The teams is full! You have not been added to a team!");
return; return;
} }
@ -281,21 +284,28 @@ void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
} }
} }
void PlayerContainer::RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent) { void PlayerContainer::RemoveMember(TeamData* team, LWOOBJID causingPlayerID, bool disband, bool kicked, bool leaving, bool silent) {
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID); 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; if (index == team->memberIDs.end()) return;
const auto& member = GetPlayerData(playerID); team->memberIDs.erase(index);
const auto& member = GetPlayerData(causingPlayerID);
const auto causingMemberName = GetName(causingPlayerID);
if (member && !silent) { if (member && !silent) {
ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY); ChatPacketHandler::SendTeamRemovePlayer(member, disband, kicked, leaving, team->local, LWOOBJID_EMPTY, causingPlayerID, causingMemberName);
} }
const auto memberName = GetName(playerID); 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) { for (const auto memberId : team->memberIDs) {
if (silent && memberId == playerID) { if (silent && memberId == causingPlayerID) {
continue; continue;
} }
@ -303,19 +313,10 @@ void PlayerContainer::RemoveMember(TeamData* team, LWOOBJID playerID, bool disba
if (!otherMember) continue; if (!otherMember) continue;
ChatPacketHandler::SendTeamRemovePlayer(otherMember, disband, kicked, leaving, false, team->leaderID, playerID, memberName); ChatPacketHandler::SendTeamRemovePlayer(otherMember, disband, kicked, leaving, team->local, team->leaderID, causingPlayerID, causingMemberName);
} }
team->memberIDs.erase(index);
UpdateTeamsOnWorld(team, false); UpdateTeamsOnWorld(team, false);
if (team->memberIDs.size() <= 1) {
DisbandTeam(team);
} else {
if (playerID == team->leaderID) {
PromoteMember(team, team->memberIDs[0]);
}
} }
} }
@ -331,33 +332,32 @@ void PlayerContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) {
} }
} }
void PlayerContainer::DisbandTeam(TeamData* team) { void PlayerContainer::DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName) {
const auto index = std::find(mTeams.begin(), mTeams.end(), team); const auto index = std::ranges::find(GetTeams(), team);
if (index == mTeams.end()) return; if (index == GetTeams().end()) return;
LOG_DEBUG("Disbanding team %i", (*index)->teamID);
for (const auto memberId : team->memberIDs) { for (const auto memberId : team->memberIDs) {
const auto& otherMember = GetPlayerData(memberId); const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue; if (!otherMember) continue;
const auto memberName = GeneralUtils::UTF8ToUTF16(otherMember.playerName);
ChatPacketHandler::SendTeamSetLeader(otherMember, LWOOBJID_EMPTY); ChatPacketHandler::SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, otherMember.playerID, memberName); ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, causingPlayerID, causingPlayerName);
} }
UpdateTeamsOnWorld(team, true); UpdateTeamsOnWorld(team, true);
mTeams.erase(index); GetTeamsMut().erase(index);
delete team; delete team;
} }
void PlayerContainer::TeamStatusUpdate(TeamData* team) { void PlayerContainer::TeamStatusUpdate(TeamData* team) {
const auto index = std::find(mTeams.begin(), mTeams.end(), team); const auto index = std::find(GetTeams().begin(), GetTeams().end(), team);
if (index == mTeams.end()) return; if (index == GetTeams().end()) return;
const auto& leader = GetPlayerData(team->leaderID); const auto& leader = GetPlayerData(team->leaderID);
@ -444,5 +444,5 @@ void PlayerContainer::Shutdown() {
Database::Get()->UpdateActivityLog(id, eActivityType::PlayerLoggedOut, playerData.zoneID.GetMapID()); Database::Get()->UpdateActivityLog(id, eActivityType::PlayerLoggedOut, playerData.zoneID.GetMapID());
m_Players.erase(m_Players.begin()); m_Players.erase(m_Players.begin());
} }
for (auto* team : mTeams) if (team) delete team; for (auto* team : GetTeams()) if (team) delete team;
} }

View File

@ -9,6 +9,12 @@
enum class eGameMasterLevel : uint8_t; enum class eGameMasterLevel : uint8_t;
struct TeamData;
struct TeamContainer {
std::vector<TeamData*> mTeams;
};
struct IgnoreData { struct IgnoreData {
IgnoreData(const std::string& name, const LWOOBJID& id) : playerName{ name }, playerId{ id } {} IgnoreData(const std::string& name, const LWOOBJID& id) : playerName{ name }, playerId{ id } {}
inline bool operator==(const std::string& other) const noexcept { inline bool operator==(const std::string& other) const noexcept {
@ -36,7 +42,7 @@ struct PlayerData {
return muteExpire == 1 || muteExpire > time(NULL); return muteExpire == 1 || muteExpire > time(NULL);
} }
SystemAddress sysAddr{}; SystemAddress worldServerSysAddr{};
LWOZONEID zoneID{}; LWOZONEID zoneID{};
LWOOBJID playerID = LWOOBJID_EMPTY; LWOOBJID playerID = LWOOBJID_EMPTY;
time_t muteExpire = 0; time_t muteExpire = 0;
@ -46,8 +52,10 @@ struct PlayerData {
std::vector<IgnoreData> ignoredPlayers; std::vector<IgnoreData> ignoredPlayers;
eGameMasterLevel gmLevel = static_cast<eGameMasterLevel>(0); // CIVILLIAN eGameMasterLevel gmLevel = static_cast<eGameMasterLevel>(0); // CIVILLIAN
bool isFTP = false; bool isFTP = false;
bool isLogin = false;
}; };
struct TeamData { struct TeamData {
TeamData(); TeamData();
LWOOBJID teamID = LWOOBJID_EMPTY; // Internal use LWOOBJID teamID = LWOOBJID_EMPTY; // Internal use
@ -75,7 +83,7 @@ public:
PlayerData& GetPlayerDataMutable(const std::string& playerName); PlayerData& GetPlayerDataMutable(const std::string& playerName);
uint32_t GetPlayerCount() { return m_PlayerCount; }; uint32_t GetPlayerCount() { return m_PlayerCount; };
uint32_t GetSimCount() { return m_SimCount; }; uint32_t GetSimCount() { return m_SimCount; };
const std::map<LWOOBJID, PlayerData>& GetAllPlayers() { return m_Players; }; const std::map<LWOOBJID, PlayerData>& GetAllPlayers() const { return m_Players; };
TeamData* CreateLocalTeam(std::vector<LWOOBJID> members); TeamData* CreateLocalTeam(std::vector<LWOOBJID> members);
TeamData* CreateTeam(LWOOBJID leader, bool local = false); TeamData* CreateTeam(LWOOBJID leader, bool local = false);
@ -83,13 +91,16 @@ public:
void AddMember(TeamData* team, LWOOBJID playerID); void AddMember(TeamData* team, LWOOBJID playerID);
void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false); void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false);
void PromoteMember(TeamData* team, LWOOBJID newLeader); void PromoteMember(TeamData* team, LWOOBJID newLeader);
void DisbandTeam(TeamData* team); void DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName);
void TeamStatusUpdate(TeamData* team); void TeamStatusUpdate(TeamData* team);
void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam); void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam);
std::u16string GetName(LWOOBJID playerID); std::u16string GetName(LWOOBJID playerID);
LWOOBJID GetId(const std::u16string& playerName); LWOOBJID GetId(const std::u16string& playerName);
uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; } uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; }
uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; } uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; }
const 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); void Update(const float deltaTime);
bool PlayerBeingRemoved(const LWOOBJID playerID) { return m_PlayersToRemove.contains(playerID); } bool PlayerBeingRemoved(const LWOOBJID playerID) { return m_PlayersToRemove.contains(playerID); }
@ -97,7 +108,7 @@ public:
private: private:
LWOOBJID m_TeamIDCounter = 0; LWOOBJID m_TeamIDCounter = 0;
std::map<LWOOBJID, PlayerData> m_Players; std::map<LWOOBJID, PlayerData> m_Players;
std::vector<TeamData*> mTeams; TeamContainer m_TeamContainer{};
std::unordered_map<LWOOBJID, std::u16string> m_Names; std::unordered_map<LWOOBJID, std::u16string> m_Names;
std::map<LWOOBJID, float> m_PlayersToRemove; std::map<LWOOBJID, float> m_PlayersToRemove;
uint32_t m_MaxNumberOfBestFriends = 5; uint32_t m_MaxNumberOfBestFriends = 5;

View File

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

View File

@ -7,6 +7,10 @@
#include <filesystem> #include <filesystem>
#include <map> #include <map>
#include "json.hpp"
using json = nlohmann::json;
template <typename T> template <typename T>
static inline size_t MinSize(const size_t size, const std::basic_string_view<T> string) { static inline size_t MinSize(const size_t size, const std::basic_string_view<T> string) {
if (size == SIZE_MAX || size > string.size()) { if (size == SIZE_MAX || size > string.size()) {
@ -327,6 +331,17 @@ std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::stri
return sortedFiles; return sortedFiles;
} }
template<>
[[nodiscard]] std::optional<json> GeneralUtils::TryParse(std::string_view str) {
try {
return json::parse(str);
} catch (const std::exception& e) {
return std::nullopt;
} catch (...) {
return std::nullopt;
}
}
#if !(__GNUC__ >= 11 || _MSC_VER >= 1924) #if !(__GNUC__ >= 11 || _MSC_VER >= 1924)
// MacOS floating-point parse function specializations // MacOS floating-point parse function specializations

View File

@ -201,6 +201,10 @@ namespace GeneralUtils {
return isParsed ? static_cast<T>(result) : std::optional<T>{}; return isParsed ? static_cast<T>(result) : std::optional<T>{};
} }
template<typename T>
requires(!Numeric<T>)
[[nodiscard]] std::optional<T> TryParse(std::string_view str);
#if !(__GNUC__ >= 11 || _MSC_VER >= 1924) #if !(__GNUC__ >= 11 || _MSC_VER >= 1924)
// MacOS floating-point parse helper function specializations // MacOS floating-point parse helper function specializations

View File

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

View File

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

View File

@ -21,19 +21,20 @@ Pack::Pack(const std::filesystem::path& filePath) {
m_FileStream.seekg(recordCountPos, std::ios::beg); m_FileStream.seekg(recordCountPos, std::ios::beg);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_RecordCount); uint32_t recordCount = 0;
BinaryIO::BinaryRead<uint32_t>(m_FileStream, recordCount);
for (int i = 0; i < m_RecordCount; i++) { m_Records.reserve(recordCount);
std::generate_n(std::back_inserter(m_Records), recordCount, [&] {
PackRecord record; PackRecord record;
BinaryIO::BinaryRead<PackRecord>(m_FileStream, record); BinaryIO::BinaryRead<PackRecord>(m_FileStream, record);
return record;
m_Records.push_back(record); });
}
m_FileStream.close(); m_FileStream.close();
} }
bool Pack::HasFile(uint32_t crc) { bool Pack::HasFile(const uint32_t crc) const {
for (const auto& record : m_Records) { for (const auto& record : m_Records) {
if (record.m_Crc == crc) { if (record.m_Crc == crc) {
return true; return true;
@ -43,7 +44,7 @@ bool Pack::HasFile(uint32_t crc) {
return false; return false;
} }
bool Pack::ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) { bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const {
// Time for some wacky C file reading for speed reasons // Time for some wacky C file reading for speed reasons
PackRecord pkRecord{}; PackRecord pkRecord{};

View File

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

View File

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

View File

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

View File

@ -98,6 +98,7 @@ public:
constexpr LWOZONEID() noexcept = default; 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 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 LWOZONEID(const LWOZONEID& replacement) noexcept { *this = replacement; }
constexpr bool operator==(const LWOZONEID&) const = default;
private: private:
LWOMAPID m_MapID = LWOMAPID_INVALID; //1000 for VE, 1100 for AG, etc... LWOMAPID m_MapID = LWOMAPID_INVALID; //1000 for VE, 1100 for AG, etc...

View File

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

View File

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

View File

@ -0,0 +1,26 @@
#ifndef __EHTTPMETHODS__H__
#define __EHTTPMETHODS__H__
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma push_macro("DELETE")
#undef DELETE
#endif
enum class eHTTPMethod {
GET,
POST,
PUT,
DELETE,
HEAD,
CONNECT,
OPTIONS,
TRACE,
PATCH,
INVALID
};
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma pop_macro("DELETE")
#endif
#endif // __EHTTPMETHODS__H__

View File

@ -0,0 +1,72 @@
#ifndef __EHTTPSTATUSCODE__H__
#define __EHTTPSTATUSCODE__H__
#include <cstdint>
// verbose list of http codes
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
enum class eHTTPStatusCode : uint32_t {
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
EARLY_HINTS = 103,
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
MULTI_STATUS = 207,
ALREADY_REPORTED = 208,
IM_USED = 226,
MULTIPLE_CHOICES = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
USE_PROXY = 305,
SWITCH_PROXY = 306,
TEMPORARY_REDIRECT = 307,
PERMANENT_REDIRECT = 308,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
PAYLOAD_TOO_LARGE = 413,
URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
I_AM_A_TEAPOT = 418,
MISDIRECTED_REQUEST = 421,
UNPROCESSABLE_ENTITY = 422,
LOCKED = 423,
FAILED_DEPENDENCY = 424,
UPGRADE_REQUIRED = 426,
PRECONDITION_REQUIRED = 428,
TOO_MANY_REQUESTS = 429,
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
VARIANT_ALSO_NEGOTIATES = 506,
INSUFFICIENT_STORAGE = 507,
LOOP_DETECTED = 508,
NOT_EXTENDED = 510,
NETWORK_AUTHENTICATION_REQUIRED = 511
};
#endif // !__EHTTPSTATUSCODE__H__

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,27 +8,10 @@
#include "dCommonVars.h" #include "dCommonVars.h"
#include "NiQuaternion.h" #include "NiQuaternion.h"
#include "NiPoint3.h" #include "NiPoint3.h"
#include "MailInfo.h"
class IMail { class IMail {
public: 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. // Insert a new mail into the database.
virtual void InsertNewMail(const MailInfo& mail) = 0; virtual void InsertNewMail(const MailInfo& mail) = 0;

View File

@ -53,6 +53,9 @@ public:
// Update the property details for the given property id. // Update the property details for the given property id.
virtual void UpdatePropertyDetails(const IProperty::Info& info) = 0; 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. // Update the property performance cost for the given property id.
virtual void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) = 0; virtual void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) = 0;

View File

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

View File

@ -70,6 +70,7 @@ public:
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override; std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
void UpdatePropertyModerationInfo(const IProperty::Info& info) override; void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
void UpdatePropertyDetails(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; void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override;
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override; std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override; void RemoveUnreferencedUgcModels() override;
@ -79,14 +80,14 @@ public:
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override; void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override; void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const IMail::MailInfo& mail) override; void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel( void InsertNewUgcModel(
std::istringstream& sd0Data, std::istringstream& sd0Data,
const uint32_t blueprintId, const uint32_t blueprintId,
const uint32_t accountId, const uint32_t accountId,
const uint32_t characterId) override; const uint32_t characterId) override;
std::vector<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override; std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<IMail::MailInfo> GetMail(const uint64_t mailId) override; std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override; uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override; void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override; void DeleteMail(const uint64_t mailId) override;
@ -96,7 +97,7 @@ public:
void UpdateAccountBan(const uint32_t accountId, const bool banned) override; void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterIp(const std::string_view ip, const uint32_t port) override; void SetMasterInfo(const IServers::MasterInfo& info) override;
std::optional<uint32_t> GetCurrentPersistentId() override; std::optional<uint32_t> GetCurrentPersistentId() override;
void InsertDefaultPersistentId() override; void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override; void UpdatePersistentId(const uint32_t id) override;
@ -124,8 +125,9 @@ public:
void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override; 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 InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override;
void DeleteUgcBuild(const LWOOBJID bigId) override; void DeleteUgcBuild(const LWOOBJID bigId) override;
sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
uint32_t GetAccountCount() override; uint32_t GetAccountCount() override;
bool IsNameInUse(const std::string_view name) override;
sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
private: private:
// Generic query functions that can be used for any query. // Generic query functions that can be used for any query.

View File

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

View File

@ -173,6 +173,10 @@ void MySQLDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
ExecuteUpdate("UPDATE properties SET name = ?, description = ? WHERE id = ? LIMIT 1;", info.name, info.description, info.id); 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) { 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()); ExecuteUpdate("UPDATE properties SET performance_cost = ? WHERE zone_id = ? AND clone_id = ? LIMIT 1;", performanceCost, zoneId.GetMapID(), zoneId.GetCloneID());
} }

View File

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

View File

@ -68,6 +68,7 @@ public:
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override; std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
void UpdatePropertyModerationInfo(const IProperty::Info& info) override; void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
void UpdatePropertyDetails(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; void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override;
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override; std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override; void RemoveUnreferencedUgcModels() override;
@ -77,14 +78,14 @@ public:
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override; void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override; void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const IMail::MailInfo& mail) override; void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel( void InsertNewUgcModel(
std::istringstream& sd0Data, std::istringstream& sd0Data,
const uint32_t blueprintId, const uint32_t blueprintId,
const uint32_t accountId, const uint32_t accountId,
const uint32_t characterId) override; const uint32_t characterId) override;
std::vector<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override; std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<IMail::MailInfo> GetMail(const uint64_t mailId) override; std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override; uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override; void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override; void DeleteMail(const uint64_t mailId) override;
@ -94,7 +95,7 @@ public:
void UpdateAccountBan(const uint32_t accountId, const bool banned) override; void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterIp(const std::string_view ip, const uint32_t port) override; void SetMasterInfo(const IServers::MasterInfo& info) override;
std::optional<uint32_t> GetCurrentPersistentId() override; std::optional<uint32_t> GetCurrentPersistentId() override;
void InsertDefaultPersistentId() override; void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override; void UpdatePersistentId(const uint32_t id) override;
@ -123,6 +124,7 @@ public:
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override; void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override;
void DeleteUgcBuild(const LWOOBJID bigId) override; void DeleteUgcBuild(const LWOOBJID bigId) override;
uint32_t GetAccountCount() override; uint32_t GetAccountCount() override;
bool IsNameInUse(const std::string_view name) override;
private: private:
CppSQLite3Statement CreatePreppedStmt(const std::string& query); CppSQLite3Statement CreatePreppedStmt(const std::string& query);

View File

@ -77,3 +77,9 @@ void SQLiteDatabase::SetPendingCharacterName(const uint32_t characterId, const s
void SQLiteDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) { void SQLiteDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) {
ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ?;", static_cast<uint32_t>(time(NULL)), 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" #include "SQLiteDatabase.h"
void SQLiteDatabase::InsertNewMail(const IMail::MailInfo& mail) { void SQLiteDatabase::InsertNewMail(const MailInfo& mail) {
ExecuteInsert( ExecuteInsert(
"INSERT INTO `mail` " "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`)" "(`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 IMail::MailInfo& mail) {
mail.itemCount); mail.itemCount);
} }
std::vector<IMail::MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) { std::vector<MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
auto [_, res] = ExecuteSelect( auto [_, res] = ExecuteSelect(
"SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent" "SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent"
" FROM mail WHERE receiver_id=? limit ?;", " FROM mail WHERE receiver_id=? limit ?;",
characterId, numberOfMail); characterId, numberOfMail);
std::vector<IMail::MailInfo> toReturn; std::vector<MailInfo> toReturn;
while (!res.eof()) { while (!res.eof()) {
IMail::MailInfo mail; MailInfo mail;
mail.id = res.getInt64Field("id"); mail.id = res.getInt64Field("id");
mail.subject = res.getStringField("subject"); mail.subject = res.getStringField("subject");
mail.body = res.getStringField("body"); mail.body = res.getStringField("body");
@ -46,14 +46,14 @@ std::vector<IMail::MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t cha
return toReturn; return toReturn;
} }
std::optional<IMail::MailInfo> SQLiteDatabase::GetMail(const uint64_t mailId) { std::optional<MailInfo> SQLiteDatabase::GetMail(const uint64_t mailId) {
auto [_, res] = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId); auto [_, res] = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId);
if (res.eof()) { if (res.eof()) {
return std::nullopt; return std::nullopt;
} }
IMail::MailInfo toReturn; MailInfo toReturn;
toReturn.itemLOT = res.getIntField("attachment_lot"); toReturn.itemLOT = res.getIntField("attachment_lot");
toReturn.itemCount = res.getIntField("attachment_count"); toReturn.itemCount = res.getIntField("attachment_count");

View File

@ -175,6 +175,10 @@ void SQLiteDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
ExecuteUpdate("UPDATE properties SET name = ?, description = ? WHERE id = ?;", info.name, info.description, info.id); 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) { 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()); ExecuteUpdate("UPDATE properties SET performance_cost = ? WHERE zone_id = ? AND clone_id = ?;", performanceCost, zoneId.GetMapID(), zoneId.GetCloneID());
} }

View File

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

View File

@ -148,6 +148,10 @@ 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) { void TestSQLDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) {
} }
@ -184,7 +188,7 @@ void TestSQLDatabase::InsertCheatDetection(const IPlayerCheatDetections::Info& i
} }
void TestSQLDatabase::InsertNewMail(const IMail::MailInfo& mail) { void TestSQLDatabase::InsertNewMail(const MailInfo& mail) {
} }
@ -192,11 +196,11 @@ void TestSQLDatabase::InsertNewUgcModel(std::istringstream& sd0Data, const uint3
} }
std::vector<IMail::MailInfo> TestSQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) { std::vector<MailInfo> TestSQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
return {}; return {};
} }
std::optional<IMail::MailInfo> TestSQLDatabase::GetMail(const uint64_t mailId) { std::optional<MailInfo> TestSQLDatabase::GetMail(const uint64_t mailId) {
return {}; return {};
} }
@ -236,7 +240,7 @@ void TestSQLDatabase::InsertNewAccount(const std::string_view username, const st
} }
void TestSQLDatabase::SetMasterIp(const std::string_view ip, const uint32_t port) { void TestSQLDatabase::SetMasterInfo(const IServers::MasterInfo& info) {
} }

View File

@ -47,6 +47,7 @@ class TestSQLDatabase : public GameDatabase {
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override; std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
void UpdatePropertyModerationInfo(const IProperty::Info& info) override; void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
void UpdatePropertyDetails(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; void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override;
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override; std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override; void RemoveUnreferencedUgcModels() override;
@ -56,14 +57,14 @@ class TestSQLDatabase : public GameDatabase {
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override; void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override; void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override; void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const IMail::MailInfo& mail) override; void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel( void InsertNewUgcModel(
std::istringstream& sd0Data, std::istringstream& sd0Data,
const uint32_t blueprintId, const uint32_t blueprintId,
const uint32_t accountId, const uint32_t accountId,
const uint32_t characterId) override; const uint32_t characterId) override;
std::vector<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override; std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<IMail::MailInfo> GetMail(const uint64_t mailId) override; std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override; uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override; void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override; void DeleteMail(const uint64_t mailId) override;
@ -73,7 +74,7 @@ class TestSQLDatabase : public GameDatabase {
void UpdateAccountBan(const uint32_t accountId, const bool banned) override; void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override; void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override; void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterIp(const std::string_view ip, const uint32_t port) override; void SetMasterInfo(const IServers::MasterInfo& info) override;
std::optional<uint32_t> GetCurrentPersistentId() override; std::optional<uint32_t> GetCurrentPersistentId() override;
void InsertDefaultPersistentId() override; void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override; void UpdatePersistentId(const uint32_t id) override;
@ -102,6 +103,8 @@ class TestSQLDatabase : public GameDatabase {
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override {}; void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override {};
void DeleteUgcBuild(const LWOOBJID bigId) override {}; void DeleteUgcBuild(const LWOOBJID bigId) override {};
uint32_t GetAccountCount() override { return 0; }; uint32_t GetAccountCount() override { return 0; };
bool IsNameInUse(const std::string_view name) override { return false; };
}; };
#endif //!TESTSQLDATABASE_H #endif //!TESTSQLDATABASE_H

View File

@ -21,6 +21,7 @@
#include "eObjectBits.h" #include "eObjectBits.h"
#include "eGameMasterLevel.h" #include "eGameMasterLevel.h"
#include "ePlayerFlag.h" #include "ePlayerFlag.h"
#include "CDPlayerFlagsTable.h"
Character::Character(uint32_t id, User* parentUser) { Character::Character(uint32_t id, User* parentUser) {
//First load the name, etc: //First load the name, etc:
@ -203,6 +204,7 @@ void Character::DoQuickXMLDataParse() {
while (currentChild) { while (currentChild) {
const auto* temp = currentChild->Attribute("v"); const auto* temp = currentChild->Attribute("v");
const auto* id = currentChild->Attribute("id"); const auto* id = currentChild->Attribute("id");
const auto* si = currentChild->Attribute("si");
if (temp && id) { if (temp && id) {
uint32_t index = 0; uint32_t index = 0;
uint64_t value = 0; uint64_t value = 0;
@ -211,6 +213,9 @@ void Character::DoQuickXMLDataParse() {
value = std::stoull(temp); value = std::stoull(temp);
m_PlayerFlags.insert(std::make_pair(index, value)); 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(); currentChild = currentChild->NextSiblingElement();
} }
@ -231,6 +236,12 @@ void Character::SetBuildMode(bool buildMode) {
} }
void Character::SaveXMLToDatabase() { void Character::SaveXMLToDatabase() {
// Check that we can actually _save_ before saving
if (!m_OurEntity) {
LOG("%i:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str());
return;
}
//For metrics, we'll record the time it took to save: //For metrics, we'll record the time it took to save:
auto start = std::chrono::system_clock::now(); auto start = std::chrono::system_clock::now();
@ -277,33 +288,19 @@ void Character::SaveXMLToDatabase() {
} }
flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes
for (std::pair<uint32_t, uint64_t> flag : m_PlayerFlags) { for (const auto& [index, flagBucket] : m_PlayerFlags) {
auto* f = m_Doc.NewElement("f"); auto* f = flags->InsertNewChildElement("f");
f->SetAttribute("id", flag.first); f->SetAttribute("id", index);
f->SetAttribute("v", flagBucket);
//Because of the joy that is tinyxml2, it doesn't offer a function to set a uint64 as an attribute.
//Only signed 64-bits ints would work.
std::string v = std::to_string(flag.second);
f->SetAttribute("v", v.c_str());
flags->LinkEndChild(f);
} }
// Prevents the news feed from showing up on world transfers for (const auto& sessionFlag : m_SessionFlags) {
if (GetPlayerFlag(ePlayerFlag::IS_NEWS_SCREEN_VISIBLE)) { auto* s = flags->InsertNewChildElement("s");
auto* s = m_Doc.NewElement("s"); s->SetAttribute("si", sessionFlag);
s->SetAttribute("si", ePlayerFlag::IS_NEWS_SCREEN_VISIBLE);
flags->LinkEndChild(s);
} }
SaveXmlRespawnCheckpoints(); SaveXmlRespawnCheckpoints();
//Call upon the entity to update our xmlDoc:
if (!m_OurEntity) {
LOG("%i:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str());
return;
}
m_OurEntity->UpdateXMLDoc(m_Doc); m_OurEntity->UpdateXMLDoc(m_Doc);
WriteToDatabase(); WriteToDatabase();
@ -323,8 +320,8 @@ void Character::SetIsNewLogin() {
while (currentChild) { while (currentChild) {
auto* nextChild = currentChild->NextSiblingElement(); auto* nextChild = currentChild->NextSiblingElement();
if (currentChild->Attribute("si")) { 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); flags->DeleteChild(currentChild);
LOG("Removed isLoggedIn flag from character %i:%s, saving character to database", GetID(), GetName().c_str());
WriteToDatabase(); WriteToDatabase();
} }
currentChild = nextChild; currentChild = nextChild;
@ -357,6 +354,12 @@ void Character::SetPlayerFlag(const uint32_t flagId, const bool value) {
} }
} }
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 // Calculate the index first
auto flagIndex = uint32_t(std::floor(flagId / 64)); auto flagIndex = uint32_t(std::floor(flagId / 64));
@ -382,12 +385,20 @@ void Character::SetPlayerFlag(const uint32_t flagId, const bool value) {
m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue)); m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue));
} }
} }
}
// Notify the client that a flag has changed server-side // Notify the client that a flag has changed server-side
GameMessages::SendNotifyClientFlagChange(m_ObjectID, flagId, value, m_ParentUser->GetSystemAddress()); GameMessages::SendNotifyClientFlagChange(m_ObjectID, flagId, value, m_ParentUser->GetSystemAddress());
} }
bool Character::GetPlayerFlag(const uint32_t flagId) const { 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 // Calculate the index first
const auto flagIndex = uint32_t(std::floor(flagId / 64)); const auto flagIndex = uint32_t(std::floor(flagId / 64));
@ -396,10 +407,11 @@ bool Character::GetPlayerFlag(const uint32_t flagId) const {
auto it = m_PlayerFlags.find(flagIndex); auto it = m_PlayerFlags.find(flagIndex);
if (it != m_PlayerFlags.end()) { if (it != m_PlayerFlags.end()) {
// Don't set the data if we don't have to // Don't set the data if we don't have to
return (it->second & shiftedValue) != 0; toReturn = (it->second & shiftedValue) != 0;
}
} }
return false; //by def, return false. return toReturn;
} }
void Character::SetRetroactiveFlags() { void Character::SetRetroactiveFlags() {

View File

@ -620,6 +620,12 @@ private:
*/ */
uint64_t m_LastLogin{}; 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) * The gameplay flags this character has (not just true values)
*/ */

View File

@ -386,6 +386,9 @@ void Entity::Initialize() {
if (m_Character) { if (m_Character) {
comp->LoadFromXml(m_Character->GetXMLDoc()); comp->LoadFromXml(m_Character->GetXMLDoc());
} else { } else {
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
comp->SetIsSmashable(comp->GetIsSmashable() | isSmashable);
if (componentID > 0) { if (componentID > 0) {
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); }); std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
@ -420,9 +423,6 @@ void Entity::Initialize() {
comp->SetMinCoins(currencyValues[0].minvalue); comp->SetMinCoins(currencyValues[0].minvalue);
comp->SetMaxCoins(currencyValues[0].maxvalue); comp->SetMaxCoins(currencyValues[0].maxvalue);
} }
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
comp->SetIsSmashable(comp->GetIsSmashable() | isSmashable);
} }
} else { } else {
comp->SetHealth(1); comp->SetHealth(1);
@ -860,6 +860,9 @@ void Entity::Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, co
auto* destroyableComponent = GetComponent<DestroyableComponent>(); auto* destroyableComponent = GetComponent<DestroyableComponent>();
if (!destroyableComponent) return; if (!destroyableComponent) return;
destroyableComponent->Subscribe(scriptObjId, scriptToAdd); destroyableComponent->Subscribe(scriptObjId, scriptToAdd);
} else if (notificationName == "PlayerResurrectionFinished") {
LOG("Subscribing to PlayerResurrectionFinished");
m_Subscriptions[scriptObjId][notificationName] = scriptToAdd;
} }
} }
@ -868,6 +871,9 @@ void Entity::Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationNa
auto* destroyableComponent = GetComponent<DestroyableComponent>(); auto* destroyableComponent = GetComponent<DestroyableComponent>();
if (!destroyableComponent) return; if (!destroyableComponent) return;
destroyableComponent->Unsubscribe(scriptObjId); destroyableComponent->Unsubscribe(scriptObjId);
} else if (notificationName == "PlayerResurrectionFinished") {
LOG("Unsubscribing from PlayerResurrectionFinished");
m_Subscriptions[scriptObjId].erase(notificationName);
} }
} }
@ -1511,6 +1517,15 @@ void Entity::OnChildLoaded(GameMessages::ChildLoaded& childLoaded) {
GetScript()->OnChildLoaded(*this, childLoaded); GetScript()->OnChildLoaded(*this, childLoaded);
} }
void Entity::NotifyPlayerResurrectionFinished(GameMessages::PlayerResurrectionFinished& msg) {
for (const auto& [id, scriptList] : m_Subscriptions) {
auto it = scriptList.find("PlayerResurrectionFinished");
if (it == scriptList.end()) continue;
it->second->NotifyPlayerResurrectionFinished(*this, msg);
}
}
void Entity::RequestActivityExit(Entity* sender, LWOOBJID player, bool canceled) { void Entity::RequestActivityExit(Entity* sender, LWOOBJID player, bool canceled) {
GetScript()->OnRequestActivityExit(sender, player, canceled); GetScript()->OnRequestActivityExit(sender, player, canceled);
} }
@ -1550,7 +1565,7 @@ void Entity::Kill(Entity* murderer, const eKillType killType) {
m_DieCallbacks.clear(); m_DieCallbacks.clear();
//OMAI WA MOU, SHINDERIU //お前はもう死んでいる
GetScript()->OnDie(this, murderer); GetScript()->OnDie(this, murderer);
@ -2201,3 +2216,17 @@ int32_t Entity::GetCollisionGroup() const {
return 0; return 0;
} }
bool Entity::HandleMsg(GameMessages::GameMsg& msg) const {
bool handled = false;
const auto [beg, end] = m_MsgHandlers.equal_range(msg.msgId);
for (auto it = beg; it != end; ++it) {
if (it->second) handled |= it->second(msg);
}
return handled;
}
void Entity::RegisterMsg(const MessageType::Game msgId, std::function<bool(GameMessages::GameMsg&)> handler) {
m_MsgHandlers.emplace(msgId, handler);
}

View File

@ -14,11 +14,17 @@
#include "Observable.h" #include "Observable.h"
namespace GameMessages { namespace GameMessages {
struct GameMsg;
struct ActivityNotify; struct ActivityNotify;
struct ShootingGalleryFire; struct ShootingGalleryFire;
struct ChildLoaded; struct ChildLoaded;
struct PlayerResurrectionFinished;
}; };
namespace MessageType {
enum class Game : uint16_t;
}
namespace Loot { namespace Loot {
class Info; class Info;
}; };
@ -219,6 +225,7 @@ public:
void OnActivityNotify(GameMessages::ActivityNotify& notify); void OnActivityNotify(GameMessages::ActivityNotify& notify);
void OnShootingGalleryFire(GameMessages::ShootingGalleryFire& notify); void OnShootingGalleryFire(GameMessages::ShootingGalleryFire& notify);
void OnChildLoaded(GameMessages::ChildLoaded& childLoaded); void OnChildLoaded(GameMessages::ChildLoaded& childLoaded);
void NotifyPlayerResurrectionFinished(GameMessages::PlayerResurrectionFinished& msg);
void OnMessageBoxResponse(Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData); void OnMessageBoxResponse(Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData);
void OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier); void OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier);
@ -314,6 +321,10 @@ public:
// Scale will only be communicated to the client when the construction packet is sent // Scale will only be communicated to the client when the construction packet is sent
void SetScale(const float scale) { m_Scale = scale; }; void SetScale(const float scale) { m_Scale = scale; };
void RegisterMsg(const MessageType::Game msgId, std::function<bool(GameMessages::GameMsg&)> handler);
bool HandleMsg(GameMessages::GameMsg& msg) const;
/** /**
* @brief The observable for player entity position updates. * @brief The observable for player entity position updates.
*/ */
@ -372,6 +383,11 @@ protected:
* Collision * Collision
*/ */
std::vector<LWOOBJID> m_TargetsInPhantom; std::vector<LWOOBJID> m_TargetsInPhantom;
// objectID of receiver and map of notification name to script
std::map<LWOOBJID, std::map<std::string, CppScripts::Script*>> m_Subscriptions;
std::multimap<MessageType::Game, std::function<bool(GameMessages::GameMsg&)>> m_MsgHandlers;
}; };
/** /**

View File

@ -305,13 +305,13 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
LOT shirtLOT = FindCharShirtID(shirtColor, shirtStyle); LOT shirtLOT = FindCharShirtID(shirtColor, shirtStyle);
LOT pantsLOT = FindCharPantsID(pantsColor); LOT pantsLOT = FindCharPantsID(pantsColor);
if (!name.empty() && Database::Get()->GetCharacterInfo(name)) { if (!name.empty() && Database::Get()->IsNameInUse(name)) {
LOG("AccountID: %i chose unavailable name: %s", u->GetAccountID(), name.c_str()); LOG("AccountID: %i chose unavailable name: %s", u->GetAccountID(), name.c_str());
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::CUSTOM_NAME_IN_USE); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::CUSTOM_NAME_IN_USE);
return; return;
} }
if (Database::Get()->GetCharacterInfo(predefinedName)) { if (Database::Get()->IsNameInUse(predefinedName)) {
LOG("AccountID: %i chose unavailable predefined name: %s", u->GetAccountID(), predefinedName.c_str()); LOG("AccountID: %i chose unavailable predefined name: %s", u->GetAccountID(), predefinedName.c_str());
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::PREDEFINED_NAME_IN_USE); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::PREDEFINED_NAME_IN_USE);
return; return;
@ -324,7 +324,7 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
} }
//Now that the name is ok, we can get an objectID from Master: //Now that the name is ok, we can get an objectID from Master:
ObjectIDManager::RequestPersistentID([=, this](uint32_t objectID) mutable { ObjectIDManager::RequestPersistentID([=, this](uint32_t objectID) {
if (Database::Get()->GetCharacterInfo(objectID)) { if (Database::Get()->GetCharacterInfo(objectID)) {
LOG("Character object id unavailable, check object_id_tracker!"); LOG("Character object id unavailable, check object_id_tracker!");
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE); WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE);
@ -369,13 +369,14 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
// If predefined name is invalid, change it to be their object id // 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 // that way more than one player can create characters if the predefined name files are not provided
if (predefinedName == "INVALID") { auto assignedPredefinedName = predefinedName;
if (assignedPredefinedName == "INVALID") {
std::stringstream nameObjID; std::stringstream nameObjID;
nameObjID << "minifig" << objectID; nameObjID << "minifig" << objectID;
predefinedName = nameObjID.str(); assignedPredefinedName = nameObjID.str();
} }
std::string_view nameToAssign = !name.empty() && nameOk ? name : predefinedName; std::string_view nameToAssign = !name.empty() && nameOk ? name : assignedPredefinedName;
std::string pendingName = !name.empty() && !nameOk ? name : ""; std::string pendingName = !name.empty() && !nameOk ? name : "";
ICharInfo::Info info; ICharInfo::Info info;

View File

@ -22,6 +22,7 @@
#include "Mail.h" #include "Mail.h"
#include "ZoneInstanceManager.h" #include "ZoneInstanceManager.h"
#include "WorldPackets.h" #include "WorldPackets.h"
#include "MessageType/Game.h"
#include <ctime> #include <ctime>
CharacterComponent::CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress) : Component(parent) { CharacterComponent::CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress) : Component(parent) {
@ -47,6 +48,45 @@ CharacterComponent::CharacterComponent(Entity* parent, Character* character, con
m_CountryCode = 0; m_CountryCode = 0;
m_LastUpdateTimestamp = std::time(nullptr); m_LastUpdateTimestamp = std::time(nullptr);
m_SystemAddress = systemAddress; m_SystemAddress = systemAddress;
RegisterMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, this, &CharacterComponent::OnRequestServerObjectInfo);
}
bool CharacterComponent::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) {
auto& request = static_cast<GameMessages::RequestServerObjectInfo&>(msg);
AMFArrayValue response;
response.Insert("visible", true);
response.Insert("objectID", std::to_string(request.targetForReport));
response.Insert("serverInfo", true);
auto& data = *response.InsertArray("data");
auto& cmptType = data.PushDebug("Character");
cmptType.PushDebug<AMFIntValue>("Component ID") = GeneralUtils::ToUnderlying(ComponentType);
cmptType.PushDebug<AMFIntValue>("Character's account ID") = m_Character->GetParentUser()->GetAccountID();
cmptType.PushDebug<AMFBoolValue>("Last log out time") = m_Character->GetLastLogin();
cmptType.PushDebug<AMFDoubleValue>("Seconds played this session") = 0;
cmptType.PushDebug<AMFBoolValue>("Editor enabled") = false;
cmptType.PushDebug<AMFDoubleValue>("Total number of seconds played") = m_TotalTimePlayed;
cmptType.PushDebug<AMFStringValue>("Total currency") = std::to_string(m_Character->GetCoins());
cmptType.PushDebug<AMFStringValue>("Currency able to be picked up") = std::to_string(m_DroppedCoins);
cmptType.PushDebug<AMFStringValue>("Tooltip flags value") = "0";
// visited locations
cmptType.PushDebug<AMFBoolValue>("is a GM") = m_GMLevel > eGameMasterLevel::CIVILIAN;
cmptType.PushDebug<AMFBoolValue>("Has PVP flag turned on") = m_PvpEnabled;
cmptType.PushDebug<AMFIntValue>("GM Level") = GeneralUtils::ToUnderlying(m_GMLevel);
cmptType.PushDebug<AMFIntValue>("Editor level") = GeneralUtils::ToUnderlying(m_EditorLevel);
cmptType.PushDebug<AMFStringValue>("Guild ID") = "0";
cmptType.PushDebug<AMFStringValue>("Guild Name") = "";
cmptType.PushDebug<AMFDoubleValue>("Reputation") = m_Reputation;
cmptType.PushDebug<AMFIntValue>("Current Activity Type") = GeneralUtils::ToUnderlying(m_CurrentActivity);
cmptType.PushDebug<AMFDoubleValue>("Property Clone ID") = m_Character->GetPropertyCloneID();
GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, m_Parent->GetSystemAddress());
LOG("Handled!");
return true;
} }
bool CharacterComponent::LandingAnimDisabled(int zoneID) { bool CharacterComponent::LandingAnimDisabled(int zoneID) {
@ -55,15 +95,21 @@ bool CharacterComponent::LandingAnimDisabled(int zoneID) {
case 556: case 556:
case 1101: case 1101:
case 1202: case 1202:
case 1150:
case 1151:
case 1203: case 1203:
case 1204: case 1204:
case 1250:
case 1251:
case 1261: case 1261:
case 1301: case 1301:
case 1302: case 1302:
case 1303: case 1303:
case 1350:
case 1401: case 1401:
case 1402: case 1402:
case 1403: case 1403:
case 1450:
case 1603: case 1603:
case 2001: case 2001:
return true; return true;
@ -81,6 +127,8 @@ CharacterComponent::~CharacterComponent() {
void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) { void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
if (bIsInitialUpdate) { if (bIsInitialUpdate) {
if (!m_Character || !m_Character->GetParentUser()) return;
outBitStream.Write(m_ClaimCodes[0] != 0); outBitStream.Write(m_ClaimCodes[0] != 0);
if (m_ClaimCodes[0] != 0) outBitStream.Write(m_ClaimCodes[0]); if (m_ClaimCodes[0] != 0) outBitStream.Write(m_ClaimCodes[0]);
outBitStream.Write(m_ClaimCodes[1] != 0); outBitStream.Write(m_ClaimCodes[1] != 0);
@ -100,7 +148,7 @@ void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInit
outBitStream.Write(m_Character->GetEyebrows()); outBitStream.Write(m_Character->GetEyebrows());
outBitStream.Write(m_Character->GetEyes()); outBitStream.Write(m_Character->GetEyes());
outBitStream.Write(m_Character->GetMouth()); outBitStream.Write(m_Character->GetMouth());
outBitStream.Write<uint64_t>(0); //AccountID, trying out if 0 works. outBitStream.Write<uint64_t>(m_Character->GetParentUser()->GetAccountID());
outBitStream.Write(m_Character->GetLastLogin()); //Last login outBitStream.Write(m_Character->GetLastLogin()); //Last login
outBitStream.Write<uint64_t>(0); //"prop mod last display time" outBitStream.Write<uint64_t>(0); //"prop mod last display time"
outBitStream.Write<uint64_t>(m_Uscore); //u-score outBitStream.Write<uint64_t>(m_Uscore); //u-score
@ -786,7 +834,7 @@ void CharacterComponent::AwardClaimCodes() {
subject << "%[RewardCodes_" << rewardCode << "_subjectText]"; subject << "%[RewardCodes_" << rewardCode << "_subjectText]";
std::ostringstream body; std::ostringstream body;
body << "%[RewardCodes_" << rewardCode << "_bodyText]"; body << "%[RewardCodes_" << rewardCode << "_bodyText]";
Mail::SendMail(LWOOBJID_EMPTY, "%[MAIL_SYSTEM_NOTIFICATION]", m_Parent, subject.str(), body.str(), attachmentLOT, 1); Mail::SendMail(m_Parent, subject.str(), body.str(), attachmentLOT, 1);
} }
} }

View File

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

View File

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

View File

@ -150,11 +150,11 @@ uint32_t InventoryComponent::GetLotCount(const LOT lot) const {
return count; return count;
} }
uint32_t InventoryComponent::GetLotCountNonTransfer(LOT lot) const { uint32_t InventoryComponent::GetLotCountNonTransfer(LOT lot, bool includeVault) const {
uint32_t count = 0; uint32_t count = 0;
for (const auto& inventory : m_Inventories) { for (const auto& inventory : m_Inventories) {
if (IsTransferInventory(inventory.second->GetType())) continue; if (IsTransferInventory(inventory.second->GetType(), includeVault)) continue;
count += inventory.second->GetLotCount(lot); count += inventory.second->GetLotCount(lot);
} }
@ -274,7 +274,7 @@ void InventoryComponent::AddItem(
switch (sourceType) { switch (sourceType) {
case 0: case 0:
Mail::SendMail(LWOOBJID_EMPTY, "Darkflame Universe", m_Parent, "Lost Reward", "You received an item and didn&apos;t have room for it.", lot, size); Mail::SendMail(m_Parent, "%[MAIL_ACTIVITY_OVERFLOW_HEADER]", "%[MAIL_ACTIVITY_OVERFLOW_BODY]", lot, size);
break; break;
case 1: case 1:
@ -305,6 +305,7 @@ bool InventoryComponent::RemoveItem(const LOT lot, const uint32_t count, eInvent
LOG("Attempted to remove 0 of item (%i) from the inventory!", lot); LOG("Attempted to remove 0 of item (%i) from the inventory!", lot);
return false; return false;
} }
if (inventoryType != eInventoryType::ALL) {
if (inventoryType == INVALID) inventoryType = Inventory::FindInventoryTypeForLot(lot); if (inventoryType == INVALID) inventoryType = Inventory::FindInventoryTypeForLot(lot);
auto* inventory = GetInventory(inventoryType); auto* inventory = GetInventory(inventoryType);
if (!inventory) return false; if (!inventory) return false;
@ -320,6 +321,19 @@ bool InventoryComponent::RemoveItem(const LOT lot, const uint32_t count, eInvent
left -= delta; left -= delta;
} }
return true; return true;
} else {
auto left = count;
for (const auto& inventory : m_Inventories | std::views::values) {
while (left > 0 && inventory->GetLotCount(lot) > 0) {
auto* item = inventory->FindItemByLot(lot, false, ignoreBound);
if (!item) break;
const auto delta = std::min<uint32_t>(item->GetCount(), left);
item->SetCount(item->GetCount() - delta, silent);
left -= delta;
}
}
return left == 0;
}
} }
void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType inventory, const uint32_t count, const bool showFlyingLot, bool isModMoveAndEquip, const bool ignoreEquipped, const int32_t preferredSlot) { void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType inventory, const uint32_t count, const bool showFlyingLot, bool isModMoveAndEquip, const bool ignoreEquipped, const int32_t preferredSlot) {
@ -1318,8 +1332,8 @@ BehaviorSlot InventoryComponent::FindBehaviorSlot(const eItemType type) {
} }
} }
bool InventoryComponent::IsTransferInventory(eInventoryType type) { bool InventoryComponent::IsTransferInventory(eInventoryType type, bool includeVault) {
return type == VENDOR_BUYBACK || type == VAULT_ITEMS || type == VAULT_MODELS || type == TEMP_ITEMS || type == TEMP_MODELS || type == MODELS_IN_BBB; return type == VENDOR_BUYBACK || (includeVault && (type == VAULT_ITEMS || type == VAULT_MODELS)) || type == TEMP_ITEMS || type == TEMP_MODELS || type == MODELS_IN_BBB;
} }
uint32_t InventoryComponent::FindSkill(const LOT lot) { uint32_t InventoryComponent::FindSkill(const LOT lot) {

View File

@ -100,7 +100,7 @@ public:
* @param lot the lot to search for * @param lot the lot to search for
* @return the amount of items this entity possesses of the specified lot * @return the amount of items this entity possesses of the specified lot
*/ */
uint32_t GetLotCountNonTransfer(LOT lot) const; uint32_t GetLotCountNonTransfer(LOT lot, bool includeVault = true) const;
/** /**
* Returns the items that are currently equipped by this entity * Returns the items that are currently equipped by this entity
@ -373,7 +373,7 @@ public:
* @param type the inventory type to check * @param type the inventory type to check
* @return if the inventory type is a temp inventory * @return if the inventory type is a temp inventory
*/ */
static bool IsTransferInventory(eInventoryType type); static bool IsTransferInventory(eInventoryType type, bool includeVault = true);
/** /**
* Finds the skill related to the passed LOT from the ObjectSkills table * Finds the skill related to the passed LOT from the ObjectSkills table

View File

@ -99,16 +99,7 @@ void MissionComponent::AcceptMission(const uint32_t missionId, const bool skipCh
mission->Accept(); mission->Accept();
this->m_Missions.insert_or_assign(missionId, mission); this->m_Missions.insert_or_assign(missionId, mission);
if (missionId == 1728) {
//Needs to send a mail
auto address = m_Parent->GetSystemAddress();
Mail::HandleNotificationRequest(address, m_Parent->GetObjectID());
} }
}
void MissionComponent::CompleteMission(const uint32_t missionId, const bool skipChecks, const bool yieldRewards) { void MissionComponent::CompleteMission(const uint32_t missionId, const bool skipChecks, const bool yieldRewards) {
// Get the mission first // Get the mission first
@ -521,7 +512,7 @@ void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
auto* mission = new Mission(this, missionId); auto* mission = new Mission(this, missionId);
mission->LoadFromXml(*doneM); mission->LoadFromXmlDone(*doneM);
doneM = doneM->NextSiblingElement(); doneM = doneM->NextSiblingElement();
@ -536,9 +527,9 @@ void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
currentM->QueryAttribute("id", &missionId); currentM->QueryAttribute("id", &missionId);
auto* mission = new Mission(this, missionId); auto* mission = m_Missions.contains(missionId) ? m_Missions[missionId] : new Mission(this, missionId);
mission->LoadFromXml(*currentM); mission->LoadFromXmlCur(*currentM);
if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) { if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) {
mission->SetUniqueMissionOrderID(missionOrder); mission->SetUniqueMissionOrderID(missionOrder);
@ -574,20 +565,23 @@ void MissionComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
auto* mission = pair.second; auto* mission = pair.second;
if (mission) { if (mission) {
const auto complete = mission->IsComplete(); const auto completions = mission->GetCompletions();
auto* m = doc.NewElement("m"); auto* m = doc.NewElement("m");
if (complete) { if (completions > 0) {
mission->UpdateXml(*m); mission->UpdateXmlDone(*m);
done->LinkEndChild(m); done->LinkEndChild(m);
continue; if (mission->IsComplete()) continue;
m = doc.NewElement("m");
} }
if (mission->IsMission()) m->SetAttribute("o", mission->GetUniqueMissionOrderID()); if (mission->IsMission()) m->SetAttribute("o", mission->GetUniqueMissionOrderID());
mission->UpdateXml(*m); mission->UpdateXmlCur(*m);
cur->LinkEndChild(m); cur->LinkEndChild(m);
} }

View File

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

View File

@ -165,7 +165,9 @@ void PropertyManagementComponent::UpdatePropertyDetails(std::string name, std::s
info.id = propertyId; info.id = propertyId;
info.name = propertyName; info.name = propertyName;
info.description = propertyDescription; info.description = propertyDescription;
info.lastUpdatedTime = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
Database::Get()->UpdateLastSave(info);
Database::Get()->UpdatePropertyDetails(info); Database::Get()->UpdatePropertyDetails(info);
OnQueryPropertyData(GetOwner(), UNASSIGNED_SYSTEM_ADDRESS); OnQueryPropertyData(GetOwner(), UNASSIGNED_SYSTEM_ADDRESS);
@ -688,6 +690,10 @@ void PropertyManagementComponent::Save() {
Database::Get()->RemoveModel(model.id); Database::Get()->RemoveModel(model.id);
} }
IProperty::Info info;
info.id = propertyId;
info.lastUpdatedTime = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
Database::Get()->UpdateLastSave(info);
} }
void PropertyManagementComponent::AddModel(LWOOBJID modelId, LWOOBJID spawnerId) { void PropertyManagementComponent::AddModel(LWOOBJID modelId, LWOOBJID spawnerId) {

View File

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

View File

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

View File

@ -37,8 +37,19 @@
#include "ePlayerFlag.h" #include "ePlayerFlag.h"
#include "dConfig.h" #include "dConfig.h"
#include "GhostComponent.h" #include "GhostComponent.h"
#include "eGameMasterLevel.h"
#include "StringifiedEnum.h" #include "StringifiedEnum.h"
namespace {
using enum MessageType::Game;
using namespace GameMessages;
using MessageCreator = std::function<std::unique_ptr<GameMessages::GameMsg>()>;
std::map<MessageType::Game, MessageCreator> g_MessageHandlers = {
{ REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique<RequestServerObjectInfo>(); } },
{ SHOOTING_GALLERY_FIRE, []() { return std::make_unique<ShootingGalleryFire>(); } },
};
};
void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const SystemAddress& sysAddr, LWOOBJID objectID, MessageType::Game messageID) { void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const SystemAddress& sysAddr, LWOOBJID objectID, MessageType::Game messageID) {
CBITSTREAM; CBITSTREAM;
@ -55,6 +66,24 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
if (messageID != MessageType::Game::READY_FOR_UPDATES) LOG_DEBUG("Received GM with ID and name: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data()); if (messageID != MessageType::Game::READY_FOR_UPDATES) LOG_DEBUG("Received GM with ID and name: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
auto handler = g_MessageHandlers.find(messageID);
if (handler != g_MessageHandlers.end()) {
auto msg = handler->second();
// Verify that the system address user is able to use this message.
if (msg->requiredGmLevel > eGameMasterLevel::CIVILIAN) {
auto* usingEntity = Game::entityManager->GetEntity(usr->GetLoggedInChar());
if (!usingEntity || usingEntity->GetGMLevel() < msg->requiredGmLevel) {
LOG("User %s (%llu) does not have the required GM level to execute this command.", usingEntity->GetCharacter()->GetName().c_str(), usingEntity->GetObjectID());
return;
}
}
msg->Deserialize(inStream);
msg->Handle(*entity, sysAddr);
return;
}
switch (messageID) { switch (messageID) {
case MessageType::Game::UN_USE_BBB_MODEL: { case MessageType::Game::UN_USE_BBB_MODEL: {
@ -117,6 +146,7 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
} }
case MessageType::Game::PLAYER_LOADED: { case MessageType::Game::PLAYER_LOADED: {
GameMessages::SendPlayerReady(entity, sysAddr);
entity->SetPlayerReadyForUpdates(); entity->SetPlayerReadyForUpdates();
auto* ghostComponent = entity->GetComponent<GhostComponent>(); auto* ghostComponent = entity->GetComponent<GhostComponent>();
@ -183,7 +213,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
LOG("Player %s (%llu) loaded.", entity->GetCharacter()->GetName().c_str(), entity->GetObjectID()); LOG("Player %s (%llu) loaded.", entity->GetCharacter()->GetName().c_str(), entity->GetObjectID());
// After we've done our thing, tell the client they're ready // After we've done our thing, tell the client they're ready
GameMessages::SendPlayerReady(entity, sysAddr);
GameMessages::SendPlayerReady(Game::zoneManager->GetZoneControlObject(), sysAddr); GameMessages::SendPlayerReady(Game::zoneManager->GetZoneControlObject(), sysAddr);
if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1" if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1"
@ -704,12 +733,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
case MessageType::Game::UPDATE_INVENTORY_GROUP_CONTENTS: case MessageType::Game::UPDATE_INVENTORY_GROUP_CONTENTS:
GameMessages::HandleUpdateInventoryGroupContents(inStream, entity, sysAddr); GameMessages::HandleUpdateInventoryGroupContents(inStream, entity, sysAddr);
break; break;
case MessageType::Game::SHOOTING_GALLERY_FIRE: {
GameMessages::ShootingGalleryFire fire{};
fire.Deserialize(inStream);
fire.Handle(*entity, sysAddr);
break;
}
default: default:
LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data()); LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());

View File

@ -48,8 +48,6 @@
#include <chrono> #include <chrono>
#include "RakString.h" #include "RakString.h"
#include "httplib.h" //sorry not sorry.
//CDB includes: //CDB includes:
#include "CDClientManager.h" #include "CDClientManager.h"
#include "CDEmoteTable.h" #include "CDEmoteTable.h"
@ -845,8 +843,10 @@ void GameMessages::SendDieNoImplCode(Entity* entity, const LWOOBJID& killerID, c
bitStream.Write(entity->GetObjectID()); bitStream.Write(entity->GetObjectID());
bitStream.Write(MessageType::Game::DIE); bitStream.Write(MessageType::Game::DIE);
bitStream.Write(bClientDeath); bitStream.Write(bClientDeath);
bitStream.Write(bSpawnLoot); bitStream.Write(bSpawnLoot);
bitStream.Write<uint32_t>(deathType.size());
bitStream.Write(deathType); bitStream.Write(deathType);
bitStream.Write(directionRelative_AngleXZ); bitStream.Write(directionRelative_AngleXZ);
bitStream.Write(directionRelative_AngleY); bitStream.Write(directionRelative_AngleY);
@ -856,7 +856,10 @@ void GameMessages::SendDieNoImplCode(Entity* entity, const LWOOBJID& killerID, c
if (killType != eKillType::VIOLENT) bitStream.Write(killType); if (killType != eKillType::VIOLENT) bitStream.Write(killType);
bitStream.Write(killerID); bitStream.Write(killerID);
bitStream.Write(lootOwnerID != LWOOBJID_EMPTY);
if (lootOwnerID != LWOOBJID_EMPTY) {
bitStream.Write(lootOwnerID); bitStream.Write(lootOwnerID);
}
SEND_PACKET_BROADCAST; SEND_PACKET_BROADCAST;
} }
@ -968,6 +971,8 @@ void GameMessages::SendResurrect(Entity* entity) {
// and just make sure the client has time to be ready. // and just make sure the client has time to be ready.
constexpr float respawnTime = 3.66700005531311f + 0.5f; constexpr float respawnTime = 3.66700005531311f + 0.5f;
entity->AddCallbackTimer(respawnTime, [=]() { entity->AddCallbackTimer(respawnTime, [=]() {
GameMessages::PlayerResurrectionFinished msg;
entity->NotifyPlayerResurrectionFinished(msg);
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr && entity->GetLOT() == 1) { if (destroyableComponent != nullptr && entity->GetLOT() == 1) {
@ -6427,4 +6432,16 @@ namespace GameMessages {
void ShootingGalleryFire::Handle(Entity& entity, const SystemAddress& sysAddr) { void ShootingGalleryFire::Handle(Entity& entity, const SystemAddress& sysAddr) {
entity.OnShootingGalleryFire(*this); entity.OnShootingGalleryFire(*this);
} }
bool RequestServerObjectInfo::Deserialize(RakNet::BitStream& bitStream) {
if (!bitStream.Read(bVerbose)) return false;
if (!bitStream.Read(clientId)) return false;
if (!bitStream.Read(targetForReport)) return false;
return true;
}
void RequestServerObjectInfo::Handle(Entity& entity, const SystemAddress& sysAddr) {
auto* handlingEntity = Game::entityManager->GetEntity(targetForReport);
if (handlingEntity) handlingEntity->HandleMsg(*this);
}
} }

View File

@ -12,6 +12,7 @@
#include "eLootSourceType.h" #include "eLootSourceType.h"
#include "Brick.h" #include "Brick.h"
#include "MessageType/Game.h" #include "MessageType/Game.h"
#include "eGameMasterLevel.h"
class AMFBaseValue; class AMFBaseValue;
class Entity; class Entity;
@ -50,7 +51,8 @@ enum class eCameraTargetCyclingMode : int32_t {
namespace GameMessages { namespace GameMessages {
struct GameMsg { struct GameMsg {
GameMsg(MessageType::Game gmId) : msgId{ gmId } {} GameMsg(MessageType::Game gmId, eGameMasterLevel lvl) : msgId{ gmId }, requiredGmLevel{ lvl } {}
GameMsg(MessageType::Game gmId) : GameMsg(gmId, eGameMasterLevel::CIVILIAN) {}
virtual ~GameMsg() = default; virtual ~GameMsg() = default;
void Send(const SystemAddress& sysAddr) const; void Send(const SystemAddress& sysAddr) const;
virtual void Serialize(RakNet::BitStream& bitStream) const {} virtual void Serialize(RakNet::BitStream& bitStream) const {}
@ -58,6 +60,7 @@ namespace GameMessages {
virtual void Handle(Entity& entity, const SystemAddress& sysAddr) {}; virtual void Handle(Entity& entity, const SystemAddress& sysAddr) {};
MessageType::Game msgId; MessageType::Game msgId;
LWOOBJID target{ LWOOBJID_EMPTY }; LWOOBJID target{ LWOOBJID_EMPTY };
eGameMasterLevel requiredGmLevel;
}; };
class PropertyDataMessage; class PropertyDataMessage;
@ -765,6 +768,20 @@ namespace GameMessages {
LOT templateID{}; LOT templateID{};
LWOOBJID childID{}; LWOOBJID childID{};
}; };
struct PlayerResurrectionFinished : public GameMsg {
PlayerResurrectionFinished() : GameMsg(MessageType::Game::PLAYER_RESURRECTION_FINISHED) {}
};
struct RequestServerObjectInfo : public GameMsg {
bool bVerbose{};
LWOOBJID clientId{};
LWOOBJID targetForReport{};
RequestServerObjectInfo() : GameMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, eGameMasterLevel::DEVELOPER) {}
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
};
}; };
#endif // GAMEMESSAGES_H #endif // GAMEMESSAGES_H

View File

@ -27,6 +27,8 @@
#include "Character.h" #include "Character.h"
#include "CDMissionEmailTable.h" #include "CDMissionEmailTable.h"
#include "ChatPackets.h"
#include "PlayerManager.h"
Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) { Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) {
m_MissionComponent = missionComponent; m_MissionComponent = missionComponent;
@ -65,7 +67,7 @@ Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) {
} }
} }
void Mission::LoadFromXml(const tinyxml2::XMLElement& element) { void Mission::LoadFromXmlDone(const tinyxml2::XMLElement& element) {
// Start custom XML // Start custom XML
if (element.Attribute("state") != nullptr) { if (element.Attribute("state") != nullptr) {
m_State = static_cast<eMissionState>(std::stoul(element.Attribute("state"))); m_State = static_cast<eMissionState>(std::stoul(element.Attribute("state")));
@ -76,11 +78,15 @@ void Mission::LoadFromXml(const tinyxml2::XMLElement& element) {
m_Completions = std::stoul(element.Attribute("cct")); m_Completions = std::stoul(element.Attribute("cct"));
m_Timestamp = std::stoul(element.Attribute("cts")); m_Timestamp = std::stoul(element.Attribute("cts"));
}
}
if (IsComplete()) { void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) {
return; // Start custom XML
} if (element.Attribute("state") != nullptr) {
m_State = static_cast<eMissionState>(std::stoul(element.Attribute("state")));
} }
// End custom XML
auto* task = element.FirstChildElement(); auto* task = element.FirstChildElement();
@ -132,7 +138,7 @@ void Mission::LoadFromXml(const tinyxml2::XMLElement& element) {
} }
} }
void Mission::UpdateXml(tinyxml2::XMLElement& element) { void Mission::UpdateXmlDone(tinyxml2::XMLElement& element) {
// Start custom XML // Start custom XML
element.SetAttribute("state", static_cast<unsigned int>(m_State)); element.SetAttribute("state", static_cast<unsigned int>(m_State));
// End custom XML // End custom XML
@ -141,15 +147,21 @@ void Mission::UpdateXml(tinyxml2::XMLElement& element) {
element.SetAttribute("id", static_cast<unsigned int>(info.id)); element.SetAttribute("id", static_cast<unsigned int>(info.id));
if (m_Completions > 0) {
element.SetAttribute("cct", static_cast<unsigned int>(m_Completions)); element.SetAttribute("cct", static_cast<unsigned int>(m_Completions));
element.SetAttribute("cts", static_cast<unsigned int>(m_Timestamp)); element.SetAttribute("cts", static_cast<unsigned int>(m_Timestamp));
}
if (IsComplete()) { void Mission::UpdateXmlCur(tinyxml2::XMLElement& element) {
return; // Start custom XML
} element.SetAttribute("state", static_cast<unsigned int>(m_State));
} // End custom XML
element.DeleteChildren();
element.SetAttribute("id", static_cast<unsigned int>(info.id));
if (IsComplete()) return;
for (auto* task : m_Tasks) { for (auto* task : m_Tasks) {
if (task->GetType() == eMissionTaskType::COLLECTION || if (task->GetType() == eMissionTaskType::COLLECTION ||
@ -345,12 +357,25 @@ void Mission::Complete(const bool yieldRewards) {
for (const auto& email : missionEmails) { for (const auto& email : missionEmails) {
const auto missionEmailBase = "MissionEmail_" + std::to_string(email.ID) + "_"; const auto missionEmailBase = "MissionEmail_" + std::to_string(email.ID) + "_";
if (email.messageType == 1) { if (email.messageType == 1 /* Send an email to the player */) {
const auto subject = "%[" + missionEmailBase + "subjectText]"; const auto subject = "%[" + missionEmailBase + "subjectText]";
const auto body = "%[" + missionEmailBase + "bodyText]"; const auto body = "%[" + missionEmailBase + "bodyText]";
const auto sender = "%[" + missionEmailBase + "senderName]"; const auto sender = "%[" + missionEmailBase + "senderName]";
Mail::SendMail(LWOOBJID_EMPTY, sender, GetAssociate(), subject, body, email.attachmentLOT, 1); Mail::SendMail(LWOOBJID_EMPTY, sender, GetAssociate(), subject, body, email.attachmentLOT, 1);
} else if (email.messageType == 2 /* Send an announcement in chat */) {
auto* character = entity->GetCharacter();
ChatPackets::AchievementNotify notify{};
notify.missionEmailID = email.ID;
notify.earningPlayerID = entity->GetObjectID();
notify.earnerName.string = character ? GeneralUtils::ASCIIToUTF16(character->GetName()) : u"";
// Manual write since it's sent to chat server and not a game client
RakNet::BitStream bitstream;
notify.WriteHeader(bitstream);
notify.Serialize(bitstream);
Game::chatServer->Send(&bitstream, HIGH_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
} }
} }
} }

View File

@ -28,8 +28,13 @@ public:
Mission(MissionComponent* missionComponent, uint32_t missionId); Mission(MissionComponent* missionComponent, uint32_t missionId);
~Mission(); ~Mission();
void LoadFromXml(const tinyxml2::XMLElement& element); // XML functions to load and save completed mission state to xml
void UpdateXml(tinyxml2::XMLElement& element); void LoadFromXmlDone(const tinyxml2::XMLElement& element);
void UpdateXmlDone(tinyxml2::XMLElement& element);
// XML functions to load and save current mission state and task data to xml
void LoadFromXmlCur(const tinyxml2::XMLElement& element);
void UpdateXmlCur(tinyxml2::XMLElement& element);
/** /**
* Returns the ID of this mission * Returns the ID of this mission

View File

@ -26,12 +26,279 @@
#include "eMissionTaskType.h" #include "eMissionTaskType.h"
#include "eReplicaComponentType.h" #include "eReplicaComponentType.h"
#include "eConnectionType.h" #include "eConnectionType.h"
#include "User.h"
#include "StringifiedEnum.h"
namespace {
const std::string DefaultSender = "%[MAIL_SYSTEM_NOTIFICATION]";
}
namespace Mail {
std::map<eMessageID, std::function<std::unique_ptr<MailLUBitStream>()>> g_Handlers = {
{eMessageID::SendRequest, []() {
return std::make_unique<SendRequest>();
}},
{eMessageID::DataRequest, []() {
return std::make_unique<DataRequest>();
}},
{eMessageID::AttachmentCollectRequest, []() {
return std::make_unique<AttachmentCollectRequest>();
}},
{eMessageID::DeleteRequest, []() {
return std::make_unique<DeleteRequest>();
}},
{eMessageID::ReadRequest, []() {
return std::make_unique<ReadRequest>();
}},
{eMessageID::NotificationRequest, []() {
return std::make_unique<NotificationRequest>();
}},
};
void MailLUBitStream::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(messageID);
}
bool MailLUBitStream::Deserialize(RakNet::BitStream& bitstream) {
VALIDATE_READ(bitstream.Read(messageID));
return true;
}
bool SendRequest::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(mailInfo.Deserialize(bitStream));
return true;
}
void SendRequest::Handle() {
SendResponse response;
auto* character = player->GetCharacter();
if (character && !(character->HasPermission(ePermissionMap::RestrictedMailAccess) || character->GetParentUser()->GetIsMuted())) {
mailInfo.recipient = std::regex_replace(mailInfo.recipient, std::regex("[^0-9a-zA-Z]+"), "");
auto receiverID = Database::Get()->GetCharacterInfo(mailInfo.recipient);
if (!receiverID) {
response.status = eSendResponse::RecipientNotFound;
} else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) {
response.status = eSendResponse::CannotMailSelf;
} else {
uint32_t mailCost = Game::zoneManager->GetWorldConfig()->mailBaseFee;
uint32_t stackSize = 0;
auto inventoryComponent = player->GetComponent<InventoryComponent>();
Item* item = nullptr;
bool hasAttachment = mailInfo.itemID != 0 && mailInfo.itemCount > 0;
if (hasAttachment) {
item = inventoryComponent->FindItemById(mailInfo.itemID);
if (item) {
mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig()->mailPercentAttachmentFee);
mailInfo.itemLOT = item->GetLot();
}
}
if (hasAttachment && !item) {
response.status = eSendResponse::AttachmentNotFound;
} else if (player->GetCharacter()->GetCoins() - mailCost < 0) {
response.status = eSendResponse::NotEnoughCoins;
} else {
bool removeSuccess = true;
// Remove coins and items from the sender
player->GetCharacter()->SetCoins(player->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL);
if (inventoryComponent && hasAttachment && item) {
removeSuccess = inventoryComponent->RemoveItem(mailInfo.itemLOT, mailInfo.itemCount, INVALID, true);
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent && removeSuccess) missionComponent->Progress(eMissionTaskType::GATHER, mailInfo.itemLOT, LWOOBJID_EMPTY, "", -mailInfo.itemCount);
}
// we passed all the checks, now we can actully send the mail
if (removeSuccess) {
mailInfo.senderId = character->GetID();
mailInfo.senderUsername = character->GetName();
mailInfo.receiverId = receiverID->id;
mailInfo.itemSubkey = LWOOBJID_EMPTY;
//clear out the attachementID
mailInfo.itemID = 0;
Database::Get()->InsertNewMail(mailInfo);
response.status = eSendResponse::Success;
character->SaveXMLToDatabase();
} else {
response.status = eSendResponse::AttachmentNotFound;
}
}
}
} else {
response.status = eSendResponse::SenderAccountIsMuted;
}
response.Send(sysAddr);
}
void SendResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(status);
}
void NotificationResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(status);
bitStream.Write<uint64_t>(0); // unused
bitStream.Write<uint64_t>(0); // unused
bitStream.Write(auctionID);
bitStream.Write<uint64_t>(0); // unused
bitStream.Write(mailCount);
bitStream.Write<uint32_t>(0); // packing
}
void DataRequest::Handle() {
const auto* character = player->GetCharacter();
if (!character) return;
auto playerMail = Database::Get()->GetMailForPlayer(character->GetID(), 20);
DataResponse response;
response.playerMail = playerMail;
response.Send(sysAddr);
}
void DataResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(this->throttled);
bitStream.Write<uint16_t>(this->playerMail.size());
bitStream.Write<uint16_t>(0); // packing
for (const auto& mail : this->playerMail) {
mail.Serialize(bitStream);
}
}
bool AttachmentCollectRequest::Deserialize(RakNet::BitStream& bitStream) {
uint32_t unknown;
VALIDATE_READ(bitStream.Read(unknown));
VALIDATE_READ(bitStream.Read(mailID));
VALIDATE_READ(bitStream.Read(playerID));
return true;
}
void AttachmentCollectRequest::Handle() {
AttachmentCollectResponse response;
response.mailID = mailID;
auto inv = player->GetComponent<InventoryComponent>();
if (mailID > 0 && playerID == player->GetObjectID() && inv) {
auto playerMail = Database::Get()->GetMail(mailID);
if (!playerMail) {
response.status = eAttachmentCollectResponse::MailNotFound;
} else if (!inv->HasSpaceForLoot({ {playerMail->itemLOT, playerMail->itemCount} })) {
response.status = eAttachmentCollectResponse::NoSpaceInInventory;
} else {
inv->AddItem(playerMail->itemLOT, playerMail->itemCount, eLootSourceType::MAIL);
Database::Get()->ClaimMailItem(mailID);
response.status = eAttachmentCollectResponse::Success;
}
}
response.Send(sysAddr);
}
void AttachmentCollectResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(status);
bitStream.Write(mailID);
}
bool DeleteRequest::Deserialize(RakNet::BitStream& bitStream) {
int32_t unknown;
VALIDATE_READ(bitStream.Read(unknown));
VALIDATE_READ(bitStream.Read(mailID));
VALIDATE_READ(bitStream.Read(playerID));
return true;
}
void DeleteRequest::Handle() {
DeleteResponse response;
response.mailID = mailID;
auto mailData = Database::Get()->GetMail(mailID);
if (mailData && !(mailData->itemLOT != 0 && mailData->itemCount > 0)) {
Database::Get()->DeleteMail(mailID);
response.status = eDeleteResponse::Success;
} else if (mailData && mailData->itemLOT != 0 && mailData->itemCount > 0) {
response.status = eDeleteResponse::HasAttachments;
} else {
response.status = eDeleteResponse::NotFound;
}
response.Send(sysAddr);
}
void DeleteResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(status);
bitStream.Write(mailID);
}
bool ReadRequest::Deserialize(RakNet::BitStream& bitStream) {
int32_t unknown;
VALIDATE_READ(bitStream.Read(unknown));
VALIDATE_READ(bitStream.Read(mailID));
return true;
}
void ReadRequest::Handle() {
ReadResponse response;
response.mailID = mailID;
if (Database::Get()->GetMail(mailID)) {
response.status = eReadResponse::Success;
Database::Get()->MarkMailRead(mailID);
}
response.Send(sysAddr);
}
void ReadResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(status);
bitStream.Write(mailID);
}
void NotificationRequest::Handle() {
NotificationResponse response;
auto character = player->GetCharacter();
if (character) {
auto unreadMailCount = Database::Get()->GetUnreadMailCount(character->GetID());
response.status = eNotificationResponse::NewMail;
response.mailCount = unreadMailCount;
}
response.Send(sysAddr);
}
}
// Non Stuct Functions
void Mail::HandleMail(RakNet::BitStream& inStream, const SystemAddress& sysAddr, Entity* player) {
MailLUBitStream data;
if (!data.Deserialize(inStream)) {
LOG_DEBUG("Error Reading Mail header");
return;
}
auto it = g_Handlers.find(data.messageID);
if (it != g_Handlers.end()) {
auto request = it->second();
request->sysAddr = sysAddr;
request->player = player;
if (!request->Deserialize(inStream)) {
LOG_DEBUG("Error Reading Mail Request: %s", StringifiedEnum::ToString(data.messageID).data());
return;
}
request->Handle();
} else {
LOG_DEBUG("Unhandled Mail Request with ID: %i", data.messageID);
}
}
void Mail::SendMail(const Entity* recipient, const std::string& subject, const std::string& body, const LOT attachment, void Mail::SendMail(const Entity* recipient, const std::string& subject, const std::string& body, const LOT attachment,
const uint16_t attachmentCount) { const uint16_t attachmentCount) {
SendMail( SendMail(
LWOOBJID_EMPTY, LWOOBJID_EMPTY,
ServerName, DefaultSender,
recipient->GetObjectID(), recipient->GetObjectID(),
recipient->GetCharacter()->GetName(), recipient->GetCharacter()->GetName(),
subject, subject,
@ -46,7 +313,7 @@ void Mail::SendMail(const LWOOBJID recipient, const std::string& recipientName,
const std::string& body, const LOT attachment, const uint16_t attachmentCount, const SystemAddress& sysAddr) { const std::string& body, const LOT attachment, const uint16_t attachmentCount, const SystemAddress& sysAddr) {
SendMail( SendMail(
LWOOBJID_EMPTY, LWOOBJID_EMPTY,
ServerName, DefaultSender,
recipient, recipient,
recipientName, recipientName,
subject, subject,
@ -75,7 +342,7 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, const
void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJID recipient, void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJID recipient,
const std::string& recipientName, const std::string& subject, const std::string& body, const LOT attachment, const std::string& recipientName, const std::string& subject, const std::string& body, const LOT attachment,
const uint16_t attachmentCount, const SystemAddress& sysAddr) { const uint16_t attachmentCount, const SystemAddress& sysAddr) {
IMail::MailInfo mailInsert; MailInfo mailInsert;
mailInsert.senderUsername = senderName; mailInsert.senderUsername = senderName;
mailInsert.recipient = recipientName; mailInsert.recipient = recipientName;
mailInsert.subject = subject; mailInsert.subject = subject;
@ -90,316 +357,7 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJ
Database::Get()->InsertNewMail(mailInsert); Database::Get()->InsertNewMail(mailInsert);
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) return; // TODO: Echo to chat server if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) return; // TODO: Echo to chat server
NotificationResponse response;
SendNotification(sysAddr, 1); //Show the "one new mail" message response.status = eNotificationResponse::NewMail;
} response.Send(sysAddr);
void Mail::HandleMailStuff(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* entity) {
int mailStuffID = 0;
packet.Read(mailStuffID);
auto returnVal = std::async(std::launch::async, [&packet, &sysAddr, entity, mailStuffID]() {
Mail::MailMessageID stuffID = MailMessageID(mailStuffID);
switch (stuffID) {
case MailMessageID::AttachmentCollect:
Mail::HandleAttachmentCollect(packet, sysAddr, entity);
break;
case MailMessageID::DataRequest:
Mail::HandleDataRequest(packet, sysAddr, entity);
break;
case MailMessageID::MailDelete:
Mail::HandleMailDelete(packet, sysAddr);
break;
case MailMessageID::MailRead:
Mail::HandleMailRead(packet, sysAddr);
break;
case MailMessageID::NotificationRequest:
Mail::HandleNotificationRequest(sysAddr, entity->GetObjectID());
break;
case MailMessageID::Send:
Mail::HandleSendMail(packet, sysAddr, entity);
break;
default:
LOG("Unhandled and possibly undefined MailStuffID: %i", int(stuffID));
}
});
}
void Mail::HandleSendMail(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* entity) {
//std::string subject = GeneralUtils::WStringToString(ReadFromPacket(packet, 50));
//std::string body = GeneralUtils::WStringToString(ReadFromPacket(packet, 400));
//std::string recipient = GeneralUtils::WStringToString(ReadFromPacket(packet, 32));
// Check if the player has restricted mail access
auto* character = entity->GetCharacter();
if (!character) return;
if (character->HasPermission(ePermissionMap::RestrictedMailAccess)) {
// Send a message to the player
ChatPackets::SendSystemMessage(
sysAddr,
u"This character has restricted mail access."
);
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::AccountIsMuted);
return;
}
LUWString subjectRead(50);
packet.Read(subjectRead);
LUWString bodyRead(400);
packet.Read(bodyRead);
LUWString recipientRead(32);
packet.Read(recipientRead);
const std::string subject = subjectRead.GetAsString();
const std::string body = bodyRead.GetAsString();
//Cleanse recipient:
const std::string recipient = std::regex_replace(recipientRead.GetAsString(), std::regex("[^0-9a-zA-Z]+"), "");
uint64_t unknown64 = 0;
LWOOBJID attachmentID;
uint16_t attachmentCount;
packet.Read(unknown64);
packet.Read(attachmentID);
packet.Read(attachmentCount); //We don't care about the rest of the packet.
uint32_t itemID = static_cast<uint32_t>(attachmentID);
LOT itemLOT = 0;
//Inventory::InventoryType itemType;
int mailCost = Game::zoneManager->GetWorldConfig()->mailBaseFee;
int stackSize = 0;
auto inv = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY));
Item* item = nullptr;
if (itemID > 0 && attachmentCount > 0 && inv) {
item = inv->FindItemById(attachmentID);
if (item) {
mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig()->mailPercentAttachmentFee);
stackSize = item->GetCount();
itemLOT = item->GetLot();
} else {
Mail::SendSendResponse(sysAddr, MailSendResponse::AttachmentNotFound);
return;
}
}
//Check if we can even send this mail (negative coins bug):
if (entity->GetCharacter()->GetCoins() - mailCost < 0) {
Mail::SendSendResponse(sysAddr, MailSendResponse::NotEnoughCoins);
return;
}
//Get the receiver's id:
auto receiverID = Database::Get()->GetCharacterInfo(recipient);
if (!receiverID) {
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::RecipientNotFound);
return;
}
//Check if we have a valid receiver:
if (GeneralUtils::CaseInsensitiveStringCompare(recipient, character->GetName()) || receiverID->id == character->GetID()) {
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::CannotMailSelf);
return;
} else {
IMail::MailInfo mailInsert;
mailInsert.senderUsername = character->GetName();
mailInsert.recipient = recipient;
mailInsert.subject = subject;
mailInsert.body = body;
mailInsert.senderId = character->GetID();
mailInsert.receiverId = receiverID->id;
mailInsert.itemCount = attachmentCount;
mailInsert.itemID = itemID;
mailInsert.itemLOT = itemLOT;
mailInsert.itemSubkey = LWOOBJID_EMPTY;
Database::Get()->InsertNewMail(mailInsert);
}
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::Success);
entity->GetCharacter()->SetCoins(entity->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL);
LOG("Seeing if we need to remove item with ID/count/LOT: %i %i %i", itemID, attachmentCount, itemLOT);
if (inv && itemLOT != 0 && attachmentCount > 0 && item) {
LOG("Trying to remove item with ID/count/LOT: %i %i %i", itemID, attachmentCount, itemLOT);
inv->RemoveItem(itemLOT, attachmentCount, INVALID, true);
auto* missionCompoent = entity->GetComponent<MissionComponent>();
if (missionCompoent != nullptr) {
missionCompoent->Progress(eMissionTaskType::GATHER, itemLOT, LWOOBJID_EMPTY, "", -attachmentCount);
}
}
character->SaveXMLToDatabase();
}
void Mail::HandleDataRequest(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* player) {
auto playerMail = Database::Get()->GetMailForPlayer(player->GetCharacter()->GetID(), 20);
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
bitStream.Write(int(MailMessageID::MailData));
bitStream.Write(int(0)); // throttled
bitStream.Write<uint16_t>(playerMail.size()); // size
bitStream.Write<uint16_t>(0);
for (const auto& mail : playerMail) {
bitStream.Write(mail.id); //MailID
const LUWString subject(mail.subject, 50);
bitStream.Write(subject); //subject
const LUWString body(mail.body, 400);
bitStream.Write(body); //body
const LUWString sender(mail.senderUsername, 32);
bitStream.Write(sender); //sender
bitStream.Write(uint32_t(0)); // packing
bitStream.Write(uint64_t(0)); // attachedCurrency
bitStream.Write(mail.itemID); //Attachment ID
LOT lot = mail.itemLOT;
if (lot <= 0) bitStream.Write(LOT(-1));
else bitStream.Write(lot);
bitStream.Write(uint32_t(0)); // packing
bitStream.Write(mail.itemSubkey); // Attachment subKey
bitStream.Write<uint16_t>(mail.itemCount); // Attachment count
bitStream.Write(uint8_t(0)); // subject type (used for auction)
bitStream.Write(uint8_t(0)); // packing
bitStream.Write(uint32_t(0)); // packing
bitStream.Write<uint64_t>(mail.timeSent); // expiration date
bitStream.Write<uint64_t>(mail.timeSent);// send date
bitStream.Write<uint8_t>(mail.wasRead); //was read
bitStream.Write(uint8_t(0)); // isLocalized
bitStream.Write(uint16_t(0)); // packing
bitStream.Write(uint32_t(0)); // packing
}
Game::server->Send(bitStream, sysAddr, false);
}
void Mail::HandleAttachmentCollect(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* player) {
int unknown;
uint64_t mailID;
LWOOBJID playerID;
packet.Read(unknown);
packet.Read(mailID);
packet.Read(playerID);
if (mailID > 0 && playerID == player->GetObjectID()) {
auto playerMail = Database::Get()->GetMail(mailID);
LOT attachmentLOT = 0;
uint32_t attachmentCount = 0;
if (playerMail) {
attachmentLOT = playerMail->itemLOT;
attachmentCount = playerMail->itemCount;
}
auto inv = player->GetComponent<InventoryComponent>();
if (!inv) return;
inv->AddItem(attachmentLOT, attachmentCount, eLootSourceType::MAIL);
Mail::SendAttachmentRemoveConfirm(sysAddr, mailID);
Database::Get()->ClaimMailItem(mailID);
}
}
void Mail::HandleMailDelete(RakNet::BitStream& packet, const SystemAddress& sysAddr) {
int unknown;
uint64_t mailID;
LWOOBJID playerID;
packet.Read(unknown);
packet.Read(mailID);
packet.Read(playerID);
if (mailID > 0) Mail::SendDeleteConfirm(sysAddr, mailID, playerID);
}
void Mail::HandleMailRead(RakNet::BitStream& packet, const SystemAddress& sysAddr) {
int unknown;
uint64_t mailID;
packet.Read(unknown);
packet.Read(mailID);
if (mailID > 0) Mail::SendReadConfirm(sysAddr, mailID);
}
void Mail::HandleNotificationRequest(const SystemAddress& sysAddr, uint32_t objectID) {
auto unreadMailCount = Database::Get()->GetUnreadMailCount(objectID);
if (unreadMailCount > 0) Mail::SendNotification(sysAddr, unreadMailCount);
}
void Mail::SendSendResponse(const SystemAddress& sysAddr, MailSendResponse response) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
bitStream.Write(int(MailMessageID::SendResponse));
bitStream.Write(int(response));
Game::server->Send(bitStream, sysAddr, false);
}
void Mail::SendNotification(const SystemAddress& sysAddr, int mailCount) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
uint64_t messageType = 2;
uint64_t s1 = 0;
uint64_t s2 = 0;
uint64_t s3 = 0;
uint64_t s4 = 0;
bitStream.Write(messageType);
bitStream.Write(s1);
bitStream.Write(s2);
bitStream.Write(s3);
bitStream.Write(s4);
bitStream.Write(mailCount);
bitStream.Write(int(0)); //Unknown
Game::server->Send(bitStream, sysAddr, false);
}
void Mail::SendAttachmentRemoveConfirm(const SystemAddress& sysAddr, uint64_t mailID) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
bitStream.Write(int(MailMessageID::AttachmentCollectConfirm));
bitStream.Write(int(0)); //unknown
bitStream.Write(mailID);
Game::server->Send(bitStream, sysAddr, false);
}
void Mail::SendDeleteConfirm(const SystemAddress& sysAddr, uint64_t mailID, LWOOBJID playerID) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
bitStream.Write(int(MailMessageID::MailDeleteConfirm));
bitStream.Write(int(0)); //unknown
bitStream.Write(mailID);
Game::server->Send(bitStream, sysAddr, false);
Database::Get()->DeleteMail(mailID);
}
void Mail::SendReadConfirm(const SystemAddress& sysAddr, uint64_t mailID) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
bitStream.Write(int(MailMessageID::MailReadConfirm));
bitStream.Write(int(0)); //unknown
bitStream.Write(mailID);
Game::server->Send(bitStream, sysAddr, false);
Database::Get()->MarkMailRead(mailID);
} }

View File

@ -1,43 +1,210 @@
#pragma once #ifndef __MAIL_H__
#define __MAIL_H__
#include <cstdint>
#include "BitStream.h" #include "BitStream.h"
#include "RakNetTypes.h" #include "RakNetTypes.h"
#include "dCommonVars.h" #include "dCommonVars.h"
#include "BitStreamUtils.h"
#include "MailInfo.h"
class Entity; class Entity;
namespace Mail { namespace Mail {
enum class MailMessageID { enum class eMessageID : uint32_t {
Send = 0x00, SendRequest = 0,
SendResponse = 0x01, SendResponse,
DataRequest = 0x03, NotificationResponse,
MailData = 0x04, DataRequest,
AttachmentCollect = 0x05, DataResponse,
AttachmentCollectConfirm = 0x06, AttachmentCollectRequest,
MailDelete = 0x07, AttachmentCollectResponse,
MailDeleteConfirm = 0x08, DeleteRequest,
MailRead = 0x09, DeleteResponse,
MailReadConfirm = 0x0a, ReadRequest,
NotificationRequest = 0x0b ReadResponse,
NotificationRequest,
AuctionCreate,
AuctionCreationResponse,
AuctionCancel,
AuctionCancelResponse,
AuctionList,
AuctionListResponse,
AuctionBid,
AuctionBidResponse,
UnknownError
}; };
enum class MailSendResponse { enum class eSendResponse : uint32_t {
Success = 0, Success = 0,
NotEnoughCoins, NotEnoughCoins,
AttachmentNotFound, AttachmentNotFound,
ItemCannotBeMailed, ItemCannotBeMailed,
CannotMailSelf, CannotMailSelf,
RecipientNotFound, RecipientNotFound,
DifferentFaction, RecipientDifferentFaction,
Unknown, UnHandled7,
ModerationFailure, ModerationFailure,
AccountIsMuted, SenderAccountIsMuted,
UnknownFailure, UnHandled10,
RecipientIsIgnored, RecipientIsIgnored,
UnknownFailure3, UnHandled12,
RecipientIsFTP RecipientIsFTP,
UnknownError
}; };
const std::string ServerName = "Darkflame Universe"; enum class eDeleteResponse : uint32_t {
Success = 0,
HasAttachments,
NotFound,
Throttled,
UnknownError
};
enum class eAttachmentCollectResponse : uint32_t {
Success = 0,
AttachmentNotFound,
NoSpaceInInventory,
MailNotFound,
Throttled,
UnknownError
};
enum class eNotificationResponse : uint32_t {
NewMail = 0,
UnHandled,
AuctionWon,
AuctionSold,
AuctionOutbided,
AuctionExpired,
AuctionCancelled,
AuctionUpdated,
UnknownError
};
enum class eReadResponse : uint32_t {
Success = 0,
UnknownError
};
enum class eAuctionCreateResponse : uint32_t {
Success = 0,
NotEnoughMoney,
ItemNotFound,
ItemNotSellable,
UnknownError
};
enum class eAuctionCancelResponse : uint32_t {
NotFound = 0,
NotYours,
HasBid,
NoLongerExists,
UnknownError
};
struct MailLUBitStream : public LUBitStream {
eMessageID messageID = eMessageID::UnknownError;
SystemAddress sysAddr = UNASSIGNED_SYSTEM_ADDRESS;
Entity* player = nullptr;
MailLUBitStream() = default;
MailLUBitStream(eMessageID _messageID) : LUBitStream(eConnectionType::CLIENT, MessageType::Client::MAIL), messageID{_messageID} {};
virtual void Serialize(RakNet::BitStream& bitStream) const override;
virtual bool Deserialize(RakNet::BitStream& bitStream) override;
virtual void Handle() override {};
};
struct SendRequest : public MailLUBitStream {
MailInfo mailInfo;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle() override;
};
struct SendResponse :public MailLUBitStream {
eSendResponse status = eSendResponse::UnknownError;
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct NotificationResponse : public MailLUBitStream {
eNotificationResponse status = eNotificationResponse::UnknownError;
LWOOBJID auctionID = LWOOBJID_EMPTY;
uint32_t mailCount = 1;
NotificationResponse() : MailLUBitStream(eMessageID::NotificationResponse) {};
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct DataRequest : public MailLUBitStream {
bool Deserialize(RakNet::BitStream& bitStream) override { return true; };
void Handle() override;
};
struct DataResponse : public MailLUBitStream {
uint32_t throttled = 0;
std::vector<MailInfo> playerMail;
DataResponse() : MailLUBitStream(eMessageID::DataResponse) {};
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct AttachmentCollectRequest : public MailLUBitStream {
uint64_t mailID = 0;
LWOOBJID playerID = LWOOBJID_EMPTY;
AttachmentCollectRequest() : MailLUBitStream(eMessageID::AttachmentCollectRequest) {};
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle() override;
};
struct AttachmentCollectResponse : public MailLUBitStream {
eAttachmentCollectResponse status = eAttachmentCollectResponse::UnknownError;
uint64_t mailID = 0;
AttachmentCollectResponse() : MailLUBitStream(eMessageID::AttachmentCollectResponse) {};
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct DeleteRequest : public MailLUBitStream {
uint64_t mailID = 0;
LWOOBJID playerID = LWOOBJID_EMPTY;
DeleteRequest() : MailLUBitStream(eMessageID::DeleteRequest) {};
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle() override;
};
struct DeleteResponse : public MailLUBitStream {
eDeleteResponse status = eDeleteResponse::UnknownError;
uint64_t mailID = 0;
DeleteResponse() : MailLUBitStream(eMessageID::DeleteResponse) {};
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct ReadRequest : public MailLUBitStream {
uint64_t mailID = 0;
ReadRequest() : MailLUBitStream(eMessageID::ReadRequest) {};
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle() override;
};
struct ReadResponse : public MailLUBitStream {
uint64_t mailID = 0;
eReadResponse status = eReadResponse::UnknownError;
ReadResponse() : MailLUBitStream(eMessageID::ReadResponse) {};
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct NotificationRequest : public MailLUBitStream {
NotificationRequest() : MailLUBitStream(eMessageID::NotificationRequest) {};
bool Deserialize(RakNet::BitStream& bitStream) override { return true; };
void Handle() override;
};
void HandleMail(RakNet::BitStream& inStream, const SystemAddress& sysAddr, Entity* player);
void SendMail( void SendMail(
const Entity* recipient, const Entity* recipient,
@ -78,18 +245,6 @@ namespace Mail {
uint16_t attachmentCount, uint16_t attachmentCount,
const SystemAddress& sysAddr const SystemAddress& sysAddr
); );
void HandleMailStuff(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* entity);
void HandleSendMail(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* entity);
void HandleDataRequest(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* player);
void HandleAttachmentCollect(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* player);
void HandleMailDelete(RakNet::BitStream& packet, const SystemAddress& sysAddr);
void HandleMailRead(RakNet::BitStream& packet, const SystemAddress& sysAddr);
void HandleNotificationRequest(const SystemAddress& sysAddr, uint32_t objectID);
void SendSendResponse(const SystemAddress& sysAddr, MailSendResponse response);
void SendNotification(const SystemAddress& sysAddr, int mailCount);
void SendAttachmentRemoveConfirm(const SystemAddress& sysAddr, uint64_t mailID);
void SendDeleteConfirm(const SystemAddress& sysAddr, uint64_t mailID, LWOOBJID playerID);
void SendReadConfirm(const SystemAddress& sysAddr, uint64_t mailID);
}; };
#endif // !__MAIL_H__

View File

@ -777,7 +777,7 @@ void SlashCommandHandler::Startup() {
.info = "Crashes the server", .info = "Crashes the server",
.aliases = { "crash", "pumpkin" }, .aliases = { "crash", "pumpkin" },
.handle = DEVGMCommands::Crash, .handle = DEVGMCommands::Crash,
.requiredLevel = eGameMasterLevel::DEVELOPER .requiredLevel = eGameMasterLevel::OPERATOR
}; };
RegisterCommand(CrashCommand); RegisterCommand(CrashCommand);
@ -996,7 +996,7 @@ void SlashCommandHandler::Startup() {
Command RequestMailCountCommand{ Command RequestMailCountCommand{
.help = "Gets the players mail count", .help = "Gets the players mail count",
.info = "Sends notification with number of unread messages in the player's mailbox", .info = "Sends notification with number of unread messages in the player's mailbox",
.aliases = { "requestmailcount" }, .aliases = { "requestmailcount", "checkmail" },
.handle = GMZeroCommands::RequestMailCount, .handle = GMZeroCommands::RequestMailCount,
.requiredLevel = eGameMasterLevel::CIVILIAN .requiredLevel = eGameMasterLevel::CIVILIAN
}; };
@ -1444,4 +1444,13 @@ void SlashCommandHandler::Startup() {
.requiredLevel = eGameMasterLevel::CIVILIAN .requiredLevel = eGameMasterLevel::CIVILIAN
}; };
RegisterCommand(removeIgnoreCommand); RegisterCommand(removeIgnoreCommand);
Command shutdownCommand{
.help = "Shuts this world down",
.info = "Shuts this world down",
.aliases = {"shutdown"},
.handle = DEVGMCommands::Shutdown,
.requiredLevel = eGameMasterLevel::DEVELOPER
};
RegisterCommand(shutdownCommand);
} }

View File

@ -1622,4 +1622,10 @@ namespace DEVGMCommands {
} }
} }
} }
void Shutdown(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
auto* character = entity->GetCharacter();
if (character) LOG("Mythran (%s) has shutdown the world", character->GetName().c_str());
Game::OnSignal(-1);
}
}; };

View File

@ -73,6 +73,7 @@ namespace DEVGMCommands {
void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args); void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args); void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args); void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Shutdown(Entity* entity, const SystemAddress& sysAddr, const std::string args);
} }
#endif //!DEVGMCOMMANDS_H #endif //!DEVGMCOMMANDS_H

View File

@ -102,7 +102,7 @@ namespace GMGreaterThanZeroCommands {
return; return;
} }
IMail::MailInfo mailInsert; MailInfo mailInsert;
mailInsert.senderId = entity->GetObjectID(); mailInsert.senderId = entity->GetObjectID();
mailInsert.senderUsername = "Darkflame Universe"; mailInsert.senderUsername = "Darkflame Universe";
mailInsert.receiverId = receiverID; mailInsert.receiverId = receiverID;

View File

@ -12,6 +12,7 @@
#include "VanityUtilities.h" #include "VanityUtilities.h"
#include "WorldPackets.h" #include "WorldPackets.h"
#include "ZoneInstanceManager.h" #include "ZoneInstanceManager.h"
#include "Database.h"
// Components // Components
#include "BuffComponent.h" #include "BuffComponent.h"
@ -216,7 +217,10 @@ namespace GMZeroCommands {
} }
void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args) { void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
Mail::HandleNotificationRequest(entity->GetSystemAddress(), entity->GetObjectID()); Mail::NotificationResponse response;
response.status = Mail::eNotificationResponse::NewMail;
response.mailCount = Database::Get()->GetUnreadMailCount(entity->GetCharacter()->GetID());
response.Send(sysAddr);
} }
void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args) { void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args) {

View File

@ -273,6 +273,16 @@ Instance* InstanceManager::FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID
return nullptr; return nullptr;
} }
Instance* InstanceManager::FindInstanceWithPrivate(LWOMAPID mapID, LWOINSTANCEID instanceID) {
for (Instance* i : m_Instances) {
if (i && i->GetMapID() == mapID && i->GetInstanceID() == instanceID && !i->GetShutdownComplete() && !i->GetIsShuttingDown()) {
return i;
}
}
return nullptr;
}
Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password) { Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password) {
auto* instance = FindPrivateInstance(password); auto* instance = FindPrivateInstance(password);

View File

@ -76,25 +76,25 @@ public:
void Shutdown(); void Shutdown();
private: private:
std::string m_IP; std::string m_IP{};
uint32_t m_Port; uint32_t m_Port{};
LWOZONEID m_ZoneID; LWOZONEID m_ZoneID{};
int m_MaxClientsSoftCap; int m_MaxClientsSoftCap{};
int m_MaxClientsHardCap; int m_MaxClientsHardCap{};
int m_CurrentClientCount; int m_CurrentClientCount{};
std::vector<Player> m_Players; std::vector<Player> m_Players{};
SystemAddress m_SysAddr; SystemAddress m_SysAddr{};
bool m_Ready; bool m_Ready{};
bool m_IsShuttingDown; bool m_IsShuttingDown{};
std::vector<PendingInstanceRequest> m_PendingRequests; std::vector<PendingInstanceRequest> m_PendingRequests{};
std::vector<PendingInstanceRequest> m_PendingAffirmations; std::vector<PendingInstanceRequest> m_PendingAffirmations{};
uint32_t m_AffirmationTimeout; uint32_t m_AffirmationTimeout{};
bool m_IsPrivate; bool m_IsPrivate{};
std::string m_Password; std::string m_Password{};
bool m_Shutdown; bool m_Shutdown{};
//Private functions: //Private functions:
}; };
@ -125,6 +125,7 @@ public:
Instance* FindInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneId = 0); Instance* FindInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneId = 0);
Instance* FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID); Instance* FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID);
Instance* FindInstanceWithPrivate(LWOMAPID mapID, LWOINSTANCEID instanceID);
Instance* CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password); Instance* CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password);
Instance* FindPrivateInstance(const std::string& password); Instance* FindPrivateInstance(const std::string& password);

View File

@ -42,6 +42,14 @@
#include "Server.h" #include "Server.h"
#include "CDZoneTableTable.h" #include "CDZoneTableTable.h"
#include "eGameMasterLevel.h" #include "eGameMasterLevel.h"
#include "StringifiedEnum.h"
#ifdef DARKFLAME_PLATFORM_UNIX
#include <sys/types.h>
#include <sys/wait.h>
#endif
namespace Game { namespace Game {
Logger* logger = nullptr; Logger* logger = nullptr;
@ -62,6 +70,14 @@ std::map<uint32_t, std::string> activeSessions;
SystemAddress authServerMasterPeerSysAddr; SystemAddress authServerMasterPeerSysAddr;
SystemAddress chatServerMasterPeerSysAddr; SystemAddress chatServerMasterPeerSysAddr;
int GenerateBCryptPassword(const std::string& password, const int workFactor, char salt[BCRYPT_HASHSIZE], char hash[BCRYPT_HASHSIZE]) {
int32_t bcryptState = ::bcrypt_gensalt(workFactor, salt);
assert(bcryptState == 0);
bcryptState = ::bcrypt_hashpw(password.c_str(), salt, hash);
assert(bcryptState == 0);
return 0;
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
constexpr uint32_t masterFramerate = mediumFramerate; constexpr uint32_t masterFramerate = mediumFramerate;
constexpr uint32_t masterFrameDelta = mediumFrameDelta; constexpr uint32_t masterFrameDelta = mediumFrameDelta;
@ -197,6 +213,13 @@ int main(int argc, char** argv) {
// Run migrations should any need to be run. // Run migrations should any need to be run.
MigrationRunner::RunSQLiteMigrations(); MigrationRunner::RunSQLiteMigrations();
// Check for the --migrations-only flag
if ((argc > 1 &&
(strcmp(argv[1], "--migrations-only") == 0 || strcmp(argv[1], "-m") == 0))) {
LOG("Migrations only flag detected. Exiting.");
return EXIT_SUCCESS;
}
//If the first command line argument is -a or --account then make the user //If the first command line argument is -a or --account then make the user
//input a username and password, with the password being hidden. //input a username and password, with the password being hidden.
bool createAccount = Database::Get()->GetAccountCount() == 0 && Game::config->GetValue("skip_account_creation") != "1"; bool createAccount = Database::Get()->GetAccountCount() == 0 && Game::config->GetValue("skip_account_creation") != "1";
@ -238,10 +261,8 @@ int main(int argc, char** argv) {
// Regenerate hash based on new password // Regenerate hash based on new password
char salt[BCRYPT_HASHSIZE]; char salt[BCRYPT_HASHSIZE];
char hash[BCRYPT_HASHSIZE]; char hash[BCRYPT_HASHSIZE];
int32_t bcryptState = ::bcrypt_gensalt(12, salt); int res = GenerateBCryptPassword(password, 12, salt, hash);
assert(bcryptState == 0); assert(res == 0);
bcryptState = ::bcrypt_hashpw(password.c_str(), salt, hash);
assert(bcryptState == 0);
Database::Get()->UpdateAccountPassword(accountId->id, std::string(hash, BCRYPT_HASHSIZE)); Database::Get()->UpdateAccountPassword(accountId->id, std::string(hash, BCRYPT_HASHSIZE));
@ -279,10 +300,8 @@ int main(int argc, char** argv) {
//Generate new hash for bcrypt //Generate new hash for bcrypt
char salt[BCRYPT_HASHSIZE]; char salt[BCRYPT_HASHSIZE];
char hash[BCRYPT_HASHSIZE]; char hash[BCRYPT_HASHSIZE];
int32_t bcryptState = ::bcrypt_gensalt(12, salt); int res = GenerateBCryptPassword(password, 12, salt, hash);
assert(bcryptState == 0); assert(res == 0);
bcryptState = ::bcrypt_hashpw(password.c_str(), salt, hash);
assert(bcryptState == 0);
//Create account //Create account
try { try {
@ -318,15 +337,25 @@ int main(int argc, char** argv) {
const auto externalIPString = Game::config->GetValue("external_ip"); const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString; if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(ourIP, ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master, Game::config, &Game::lastSignal); char salt[BCRYPT_HASHSIZE];
char hash[BCRYPT_HASHSIZE];
const auto& cfgPassword = Game::config->GetValue("master_password");
int res = GenerateBCryptPassword(!cfgPassword.empty() ? cfgPassword : "3.25DARKFLAME1", 13, salt, hash);
assert(res == 0);
Game::server = new dServer(ourIP, ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServerType::Master, Game::config, &Game::lastSignal, hash);
std::string master_server_ip = "localhost"; std::string master_server_ip = "localhost";
const auto masterServerIPString = Game::config->GetValue("master_ip"); const auto masterServerIPString = Game::config->GetValue("master_ip");
if (!masterServerIPString.empty()) master_server_ip = masterServerIPString; if (!masterServerIPString.empty()) master_server_ip = masterServerIPString;
if (master_server_ip == "") master_server_ip = Game::server->GetIP(); if (master_server_ip == "") master_server_ip = Game::server->GetIP();
IServers::MasterInfo info;
info.ip = master_server_ip;
info.port = Game::server->GetPort();
info.password = hash;
Database::Get()->SetMasterIp(master_server_ip, Game::server->GetPort()); Database::Get()->SetMasterInfo(info);
//Create additional objects here: //Create additional objects here:
PersistentIDManager::Initialize(); PersistentIDManager::Initialize();
@ -441,6 +470,12 @@ int main(int argc, char** argv) {
} }
} }
#ifdef DARKFLAME_PLATFORM_UNIX
// kill off dead zombie instances
int status{};
waitpid(static_cast<pid_t>(-1), &status, WNOHANG);
#endif
t += std::chrono::milliseconds(masterFrameDelta); t += std::chrono::milliseconds(masterFrameDelta);
std::this_thread::sleep_until(t); std::this_thread::sleep_until(t);
} }
@ -529,7 +564,7 @@ void HandlePacket(Packet* packet) {
Instance* in = Game::im->GetInstance(zoneID, false, zoneClone); Instance* in = Game::im->GetInstance(zoneID, false, zoneClone);
for (auto* instance : Game::im->GetInstances()) { for (auto* instance : Game::im->GetInstances()) {
LOG("Instance: %i/%i/%i -> %i", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance == in); LOG("Instance: %i/%i/%i -> %i %s", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance == in, instance->GetSysAddr().ToString());
} }
if (in && !in->GetIsReady()) //Instance not ready, make a pending request if (in && !in->GetIsReady()) //Instance not ready, make a pending request
@ -570,15 +605,10 @@ void HandlePacket(Packet* packet) {
if (!Game::im->IsPortInUse(theirPort)) { if (!Game::im->IsPortInUse(theirPort)) {
Instance* in = new Instance(theirIP.string, theirPort, theirZoneID, theirInstanceID, 0, 12, 12); Instance* in = new Instance(theirIP.string, theirPort, theirZoneID, theirInstanceID, 0, 12, 12);
SystemAddress copy; in->SetSysAddr(packet->systemAddress);
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
in->SetSysAddr(copy);
Game::im->AddInstance(in); Game::im->AddInstance(in);
} else { } else {
auto instance = Game::im->FindInstance( auto* instance = Game::im->FindInstanceWithPrivate(theirZoneID, static_cast<LWOINSTANCEID>(theirInstanceID));
theirZoneID, static_cast<uint16_t>(theirInstanceID));
if (instance) { if (instance) {
instance->SetSysAddr(packet->systemAddress); instance->SetSysAddr(packet->systemAddress);
} }
@ -586,22 +616,14 @@ void HandlePacket(Packet* packet) {
} }
if (theirServerType == ServerType::Chat) { if (theirServerType == ServerType::Chat) {
SystemAddress copy; chatServerMasterPeerSysAddr = packet->systemAddress;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
chatServerMasterPeerSysAddr = copy;
} }
if (theirServerType == ServerType::Auth) { if (theirServerType == ServerType::Auth) {
SystemAddress copy; authServerMasterPeerSysAddr = packet->systemAddress;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
authServerMasterPeerSysAddr = copy;
} }
LOG("Received server info, instance: %i port: %i", theirInstanceID, theirPort); LOG("Received %s server info, instance: %i port: %i", StringifiedEnum::ToString(theirServerType).data(), theirInstanceID, theirPort);
break; break;
} }
@ -665,7 +687,7 @@ void HandlePacket(Packet* packet) {
if (instance) { if (instance) {
instance->AddPlayer(Player()); instance->AddPlayer(Player());
} else { } else {
printf("Instance missing? What?"); LOG("Instance missing? What?");
} }
break; break;
} }
@ -706,8 +728,8 @@ void HandlePacket(Packet* packet) {
inStream.Read<char>(character); inStream.Read<char>(character);
password += character; password += character;
} }
auto* newInst = Game::im->CreatePrivateInstance(mapId, cloneId, password.c_str());
Game::im->CreatePrivateInstance(mapId, cloneId, password.c_str()); LOG("Creating private zone %i/%i/%i with password %s", newInst->GetMapID(), newInst->GetCloneID(), newInst->GetInstanceID(), password.c_str());
break; break;
} }
@ -808,11 +830,10 @@ void HandlePacket(Packet* packet) {
} }
case MessageType::Master::SHUTDOWN_RESPONSE: { case MessageType::Master::SHUTDOWN_RESPONSE: {
RakNet::BitStream inStream(packet->data, packet->length, false); CINSTREAM_SKIP_HEADER;
uint64_t header = inStream.Read(header);
auto* instance = Game::im->GetInstanceBySysAddr(packet->systemAddress); auto* instance = Game::im->GetInstanceBySysAddr(packet->systemAddress);
LOG("Got shutdown response from %s", packet->systemAddress.ToString());
if (instance == nullptr) { if (instance == nullptr) {
return; return;
} }

View File

@ -82,7 +82,7 @@ void AuthPackets::SendHandshake(dServer* server, const SystemAddress& sysAddr, c
if (serverType == ServerType::Auth) bitStream.Write(ServiceId::Auth); if (serverType == ServerType::Auth) bitStream.Write(ServiceId::Auth);
else if (serverType == ServerType::World) bitStream.Write(ServiceId::World); else if (serverType == ServerType::World) bitStream.Write(ServiceId::World);
else bitStream.Write(ServiceId::General); else bitStream.Write(ServiceId::General);
bitStream.Write<uint64_t>(219818241584); bitStream.Write<uint64_t>(219818307120);
server->Send(bitStream, sysAddr, false); server->Send(bitStream, sysAddr, false);
} }

30
dNet/BitStreamUtils.cpp Normal file
View File

@ -0,0 +1,30 @@
#include "BitStreamUtils.h"
#include "dServer.h"
#include "BitStream.h"
#include "PacketUtils.h"
void LUBitStream::WriteHeader(RakNet::BitStream& bitStream) const {
bitStream.Write<MessageID>(ID_USER_PACKET_ENUM);
bitStream.Write(this->connectionType);
bitStream.Write(this->internalPacketID);
bitStream.Write<uint8_t>(0); // padding
}
bool LUBitStream::ReadHeader(RakNet::BitStream& bitStream) {
MessageID messageID;
bitStream.Read(messageID);
if (messageID != ID_USER_PACKET_ENUM) return false;
VALIDATE_READ(bitStream.Read(this->connectionType));
VALIDATE_READ(bitStream.Read(this->internalPacketID));
uint8_t padding;
VALIDATE_READ(bitStream.Read<uint8_t>(padding));
return true;
}
void LUBitStream::Send(const SystemAddress& sysAddr) const {
RakNet::BitStream bitStream;
this->WriteHeader(bitStream);
this->Serialize(bitStream);
Game::server->Send(bitStream, sysAddr, sysAddr == UNASSIGNED_SYSTEM_ADDRESS);
}

View File

@ -2,12 +2,13 @@
#define __BITSTREAMUTILS__H__ #define __BITSTREAMUTILS__H__
#include "GeneralUtils.h" #include "GeneralUtils.h"
#include "MessageIdentifiers.h"
#include "BitStream.h" #include "BitStream.h"
#include "MessageIdentifiers.h"
#include "eConnectionType.h"
#include <string> #include <string>
#include <algorithm> #include <algorithm>
enum class eConnectionType : uint16_t; #define VALIDATE_READ(x) do { if (!x) return false; } while (0)
struct LUString { struct LUString {
std::string string; std::string string;
@ -45,6 +46,28 @@ struct LUWString {
}; };
}; };
struct LUBitStream {
eConnectionType connectionType = eConnectionType::UNKNOWN;
uint32_t internalPacketID = 0xFFFFFFFF;
LUBitStream() = default;
template <typename T>
LUBitStream(eConnectionType connectionType, T internalPacketID) {
this->connectionType = connectionType;
this->internalPacketID = static_cast<uint32_t>(internalPacketID);
}
void WriteHeader(RakNet::BitStream& bitStream) const;
bool ReadHeader(RakNet::BitStream& bitStream);
void Send(const SystemAddress& sysAddr) const;
virtual void Serialize(RakNet::BitStream& bitStream) const {}
virtual bool Deserialize(RakNet::BitStream& bitStream) { return true; }
virtual void Handle() {};
};
namespace BitStreamUtils { namespace BitStreamUtils {
template<typename T> template<typename T>
void WriteHeader(RakNet::BitStream& bitStream, eConnectionType connectionType, T internalPacketID) { void WriteHeader(RakNet::BitStream& bitStream, eConnectionType connectionType, T internalPacketID) {
@ -53,7 +76,6 @@ namespace BitStreamUtils {
bitStream.Write(static_cast<uint32_t>(internalPacketID)); bitStream.Write(static_cast<uint32_t>(internalPacketID));
bitStream.Write<uint8_t>(0); bitStream.Write<uint8_t>(0);
} }
} }
namespace RakNet { namespace RakNet {

View File

@ -1,7 +1,9 @@
set(DNET_SOURCES "AuthPackets.cpp" set(DNET_SOURCES "AuthPackets.cpp"
"BitStreamUtils.cpp"
"ChatPackets.cpp" "ChatPackets.cpp"
"ClientPackets.cpp" "ClientPackets.cpp"
"dServer.cpp" "dServer.cpp"
"MailInfo.cpp"
"MasterPackets.cpp" "MasterPackets.cpp"
"PacketUtils.cpp" "PacketUtils.cpp"
"WorldPackets.cpp" "WorldPackets.cpp"

View File

@ -97,3 +97,54 @@ void ChatPackets::SendMessageFail(const SystemAddress& sysAddr) {
//docs say there's a wstring here-- no idea what it's for, or if it's even needed so leaving it as is for now. //docs say there's a wstring here-- no idea what it's for, or if it's even needed so leaving it as is for now.
SEND_PACKET; SEND_PACKET;
} }
void ChatPackets::Announcement::Send() {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::GM_ANNOUNCE);
bitStream.Write<uint32_t>(title.size());
bitStream.Write(title);
bitStream.Write<uint32_t>(message.size());
bitStream.Write(message);
SEND_PACKET_BROADCAST;
}
void ChatPackets::AchievementNotify::Serialize(RakNet::BitStream& bitstream) const {
bitstream.Write<uint64_t>(0); // Packing
bitstream.Write<uint32_t>(0); // Packing
bitstream.Write<uint8_t>(0); // Packing
bitstream.Write(earnerName);
bitstream.Write<uint64_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write<uint32_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write<uint16_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write<uint8_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write(missionEmailID);
bitstream.Write(earningPlayerID);
bitstream.Write(targetPlayerName);
}
bool ChatPackets::AchievementNotify::Deserialize(RakNet::BitStream& bitstream) {
bitstream.IgnoreBytes(13);
VALIDATE_READ(bitstream.Read(earnerName));
bitstream.IgnoreBytes(15);
VALIDATE_READ(bitstream.Read(missionEmailID));
VALIDATE_READ(bitstream.Read(earningPlayerID));
VALIDATE_READ(bitstream.Read(targetPlayerName));
return true;
}
void ChatPackets::TeamInviteInitialResponse::Serialize(RakNet::BitStream& bitstream) const {
bitstream.Write<uint8_t>(inviteFailedToSend);
bitstream.Write(playerName);
}
void ChatPackets::SendRoutedMsg(const LUBitStream& msg, const LWOOBJID targetID, const SystemAddress& sysAddr) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(targetID);
// Now write the actual packet
msg.WriteHeader(bitStream);
msg.Serialize(bitStream);
Game::server->Send(bitStream, sysAddr, sysAddr == UNASSIGNED_SYSTEM_ADDRESS);
}

View File

@ -10,6 +10,8 @@ struct SystemAddress;
#include <string> #include <string>
#include "dCommonVars.h" #include "dCommonVars.h"
#include "MessageType/Chat.h"
#include "BitStreamUtils.h"
struct ShowAllRequest{ struct ShowAllRequest{
LWOOBJID requestor = LWOOBJID_EMPTY; LWOOBJID requestor = LWOOBJID_EMPTY;
@ -27,9 +29,36 @@ struct FindPlayerRequest{
}; };
namespace ChatPackets { namespace ChatPackets {
struct Announcement {
std::string title;
std::string message;
void Send();
};
struct AchievementNotify : public LUBitStream {
LUWString targetPlayerName{};
uint32_t missionEmailID{};
LWOOBJID earningPlayerID{};
LUWString earnerName{};
AchievementNotify() : LUBitStream(eConnectionType::CHAT, MessageType::Chat::ACHIEVEMENT_NOTIFY) {}
void Serialize(RakNet::BitStream& bitstream) const override;
bool Deserialize(RakNet::BitStream& bitstream) override;
};
struct TeamInviteInitialResponse : public LUBitStream {
bool inviteFailedToSend{};
LUWString playerName{};
TeamInviteInitialResponse() : LUBitStream(eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE_INITIAL_RESPONSE) {}
void Serialize(RakNet::BitStream& bitstream) const override;
// No Deserialize needed on our end
};
void SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message); void SendChatMessage(const SystemAddress& sysAddr, char chatChannel, const std::string& senderName, LWOOBJID playerObjectID, bool senderMythran, const std::u16string& message);
void SendSystemMessage(const SystemAddress& sysAddr, const std::u16string& message, bool broadcast = false); void SendSystemMessage(const SystemAddress& sysAddr, const std::u16string& message, bool broadcast = false);
void SendMessageFail(const SystemAddress& sysAddr); void SendMessageFail(const SystemAddress& sysAddr);
void SendRoutedMsg(const LUBitStream& msg, const LWOOBJID targetID, const SystemAddress& sysAddr);
}; };
#endif // CHATPACKETS_H #endif // CHATPACKETS_H

63
dNet/MailInfo.cpp Normal file
View File

@ -0,0 +1,63 @@
#include "MailInfo.h"
#include "BitStream.h"
#include "DluAssert.h"
void MailInfo::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(id);
const LUWString subject(this->subject, 50);
bitStream.Write(subject);
const LUWString body(this->body, 400);
bitStream.Write(body);
const LUWString sender(this->senderUsername, 32);
bitStream.Write(sender);
bitStream.Write<uint32_t>(0); // packing
bitStream.Write<uint64_t>(0); // attachedCurrency
bitStream.Write(itemID);
LOT lot = itemLOT;
if (lot <= 0) bitStream.Write<LOT>(LOT_NULL);
else bitStream.Write(lot);
bitStream.Write<uint32_t>(0); // packing
bitStream.Write(itemSubkey);
bitStream.Write(itemCount);
bitStream.Write<uint8_t>(0); // subject type (used for auction)
bitStream.Write<uint8_t>(0); // packing
bitStream.Write<uint32_t>(0); // packing
bitStream.Write<uint64_t>(timeSent); // expiration date
bitStream.Write<uint64_t>(timeSent);// send date
bitStream.Write<uint8_t>(wasRead); // was read
bitStream.Write<uint8_t>(0); // isLocalized
bitStream.Write<uint16_t>(1033); // language code
bitStream.Write<uint32_t>(0); // packing
}
bool MailInfo::Deserialize(RakNet::BitStream& bitStream) {
LUWString subject(50);
VALIDATE_READ(bitStream.Read(subject));
this->subject = subject.GetAsString();
LUWString body(400);
VALIDATE_READ(bitStream.Read(body));
this->body = body.GetAsString();
LUWString recipientName(32);
VALIDATE_READ(bitStream.Read(recipientName));
this->recipient = recipientName.GetAsString();
uint64_t unknown;
VALIDATE_READ(bitStream.Read(unknown));
VALIDATE_READ(bitStream.Read(itemID));
VALIDATE_READ(bitStream.Read(itemCount));
VALIDATE_READ(bitStream.Read(languageCode));
bitStream.IgnoreBytes(4); // padding
DluAssert(bitStream.GetNumberOfUnreadBits() == 0);
return true;
}

34
dNet/MailInfo.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef __MAILINFO_H__
#define __MAILINFO_H__
#include <string>
#include <cstdint>
#include "dCommonVars.h"
namespace RakNet {
class BitStream;
}
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{};
uint16_t languageCode{};
struct {
LWOOBJID itemID{};
int16_t itemCount{};
LOT itemLOT{};
LWOOBJID itemSubkey{};
};
void Serialize(RakNet::BitStream& bitStream) const;
bool Deserialize(RakNet::BitStream& bitStream);
};
#endif // __MAILINFO_H__

View File

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

View File

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

View File

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

View File

@ -16,6 +16,7 @@ void DLUVanityTeleportingObject::OnStartup(Entity* self) {
void DLUVanityTeleportingObject::OnTimerDone(Entity* self, std::string timerName) { void DLUVanityTeleportingObject::OnTimerDone(Entity* self, std::string timerName) {
if (timerName == "setupTeleport") { if (timerName == "setupTeleport") {
RenderComponent::PlayAnimation(self, u"interact");
GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportBeam", "teleportBeam"); GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportBeam", "teleportBeam");
GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportRings", "teleportRings"); GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportRings", "teleportRings");
@ -27,7 +28,6 @@ void DLUVanityTeleportingObject::OnTimerDone(Entity* self, std::string timerName
} else if (timerName == "teleport") { } else if (timerName == "teleport") {
std::vector<VanityObjectLocation>& locations = m_Object->m_Locations[Game::server->GetZoneID()]; std::vector<VanityObjectLocation>& locations = m_Object->m_Locations[Game::server->GetZoneID()];
selectLocation:
VanityObjectLocation& newLocation = locations[GeneralUtils::GenerateRandomNumber<size_t>(0, locations.size() - 1)]; VanityObjectLocation& newLocation = locations[GeneralUtils::GenerateRandomNumber<size_t>(0, locations.size() - 1)];
// try to get not the same position, but if we get the same one twice, it's fine // try to get not the same position, but if we get the same one twice, it's fine

View File

@ -8,16 +8,11 @@ void AmTeapotServer::OnUse(Entity* self, Entity* user) {
auto* inventoryComponent = user->GetComponent<InventoryComponent>(); auto* inventoryComponent = user->GetComponent<InventoryComponent>();
if (!inventoryComponent) return; if (!inventoryComponent) return;
auto* blueFlowerItem = inventoryComponent->FindItemByLot(BLUE_FLOWER_LEAVES, eInventoryType::ITEMS);
if (!blueFlowerItem) {
blueFlowerItem = inventoryComponent->FindItemByLot(BLUE_FLOWER_LEAVES, eInventoryType::VAULT_ITEMS);
if (!blueFlowerItem) return;
}
// The client allows you to use the teapot only if you have a stack of 10 leaves in some inventory somewhere. // The client allows you to use the teapot only if you have a stack of 10 leaves in some inventory somewhere.
if (blueFlowerItem->GetCount() >= 10) { if (inventoryComponent->GetLotCountNonTransfer(BLUE_FLOWER_LEAVES, false) >= 10) {
blueFlowerItem->SetCount(blueFlowerItem->GetCount() - 10); inventoryComponent->RemoveItem(BLUE_FLOWER_LEAVES, 10, eInventoryType::ALL);
inventoryComponent->AddItem(WU_S_IMAGINATION_TEA, 1); inventoryComponent->AddItem(WU_S_IMAGINATION_TEA, 1);
} }
GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, self->GetObjectID()); GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, self->GetObjectID());
} }

View File

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

View File

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

View File

@ -503,7 +503,7 @@ void BaseSurvivalServer::ActivateSpawnerNetwork(SpawnerNetworkCollection& spawne
if (!possibleSpawners.empty()) { if (!possibleSpawners.empty()) {
auto* spawnerObject = possibleSpawners.at(0); auto* spawnerObject = possibleSpawners.at(0);
spawnerObject->Activate(); spawnerObject->Activate();
spawnerObject->Reset(); spawnerObject->SoftReset();
} }
} }
} }

View File

@ -334,6 +334,8 @@
#include "AgSpiderBossMessage.h" #include "AgSpiderBossMessage.h"
#include "GfRaceInstancer.h" #include "GfRaceInstancer.h"
#include "NsRaceServer.h" #include "NsRaceServer.h"
#include "TrialFactionArmorServer.h"
#include "ImaginationBackPack.h"
#include <map> #include <map>
#include <string> #include <string>
@ -700,6 +702,8 @@ namespace {
{"scripts\\ai\\RACING\\TRACK_GF\\GF_RACE_SERVER.lua", []() {return new GfRaceServer();}}, {"scripts\\ai\\RACING\\TRACK_GF\\GF_RACE_SERVER.lua", []() {return new GfRaceServer();}},
{"scripts\\ai\\RACING\\TRACK_FV\\FV_RACE_SERVER.lua", []() {return new FvRaceServer();}}, {"scripts\\ai\\RACING\\TRACK_FV\\FV_RACE_SERVER.lua", []() {return new FvRaceServer();}},
{"scripts\\ai\\RACING\\OBJECTS\\VEHICLE_DEATH_TRIGGER_WATER_SERVER.lua", []() {return new VehicleDeathTriggerWaterServer();}}, {"scripts\\ai\\RACING\\OBJECTS\\VEHICLE_DEATH_TRIGGER_WATER_SERVER.lua", []() {return new VehicleDeathTriggerWaterServer();}},
{"scripts\\equipmenttriggers\\L_TRIAL_FACTION_ARMOR_SERVER.lua", []() {return new TrialFactionArmorServer();}},
{"scripts\\equipmenttriggers\\ImaginationBackPack.lua", []() {return new ImaginationBackPack();}},
}; };

View File

@ -186,6 +186,8 @@ namespace CppScripts {
*/ */
virtual void NotifyHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) {}; virtual void NotifyHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) {};
virtual void NotifyPlayerResurrectionFinished(Entity& self, GameMessages::PlayerResurrectionFinished& msg) {};
/** /**
* Invoked when a player has responsed to a mission. * Invoked when a player has responsed to a mission.
* *

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