Compare commits

..

12 Commits

Author SHA1 Message Date
David Markowitz
7c8ca1c1cb chore: some cleanup work on WorldServer and PerformanceManager 2025-05-02 16:33:28 -07:00
David Markowitz
b31f9670d1 feat: shutdown command (#1780) 2025-04-24 15:41:26 -05:00
David Markowitz
1cc1782b35 fix: lock crash to operator (#1779) 2025-04-24 11:23:46 -07:00
David Markowitz
55d409eb82 Add invite initial response msg (#1775)
re-do team leave logic to send more accurate messages

Players are still able to leave the team with the same results as before, however now the correct messages are sent to team chats (no fixes for local teams).
2025-04-23 01:56:38 -07:00
David Markowitz
65f3c33ca5 chore: use client enum packet type instead (#1776)
Same values, different namespace and not duplicated
2025-04-23 01:55:52 -07:00
David Markowitz
93fa4e268f fix: buff station dying and rotating too soon (#1768)
Tested that the buff station now waits for a player to build it and is alive for 25 seconds before moving positions fixes #1767
2025-04-23 01:55:36 -07:00
David Markowitz
1fb1da101c fix: multiple progression for shark mission (#1769)
tested that mission progresses once and only once per death
2025-04-19 07:37:08 -05:00
David Markowitz
6f94043b33 feat: broadcast achievements in chat as in live (#1771)
* feat: broadcast achievements in chat as in live

Tested that everyone on the receiving players' friends list receives the announcement as it went out in live.  Only works for achievements that have an entry in the MissionEmail table.  This may have been sent out to everyone in your zone as well however we don't really have a way to verify this aside from questioning why the client checks for the receiver being in the ignore list.  This is the only hint to me that this may have been broadcast to more than friends but again, no proof.

* Add initial response msg and sending

* Revert "Add initial response msg and sending"

This reverts commit fb942e4692.
2025-04-19 07:36:53 -05:00
Wincent Holm
5785764a95 Cache build directory when using docker with BuildKit (#1772) 2025-04-18 17:38:24 -07:00
Wincent Holm
fa53fa7935 Migrations only flag (#1773) 2025-04-18 17:38:08 -07:00
David Markowitz
6b0f3a66e9 fix: session flags not being loaded every other world load (#1763)
tested that news screen no longer shows up on every other world load
2025-04-11 09:10:38 -05:00
David Markowitz
f5c212fb86 fix: sys addr for private zones (#1760)
* fix: sys addr for private zones

* Initialize variables in Instance
2025-04-11 09:05:31 -05:00
55 changed files with 506 additions and 908 deletions

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

View File

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

View File

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

View File

@@ -73,7 +73,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
data.Serialize(bitStream);
}
SystemAddress sysAddr = player.sysAddr;
SystemAddress sysAddr = player.worldServerSysAddr;
SEND_PACKET;
}
@@ -122,7 +122,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
requesteeFriendData.isOnline = false;
requesteeFriendData.zoneID = requestor.zoneID;
requestee.friends.push_back(requesteeFriendData);
requestee.sysAddr = UNASSIGNED_SYSTEM_ADDRESS;
requestee.worldServerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
break;
}
}
@@ -189,8 +189,8 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus);
// Sent the best friend update here if the value is 3
if (bestFriendStatus == 3U) {
if (requestee.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
if (requestee.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
for (auto& friendData : requestor.friends) {
if (friendData.friendID == requestee.playerID) {
@@ -211,7 +211,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
}
}
} 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 {
auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends();
@@ -384,7 +384,7 @@ void ChatPacketHandler::HandleWho(Packet* packet) {
bitStream.Write(player.zoneID.GetCloneID());
bitStream.Write(request.playerName);
SystemAddress sysAddr = sender.sysAddr;
SystemAddress sysAddr = sender.worldServerSysAddr;
SEND_PACKET;
}
@@ -418,7 +418,7 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
}
}
}
SystemAddress sysAddr = sender.sysAddr;
SystemAddress sysAddr = sender.worldServerSysAddr;
SEND_PACKET;
}
@@ -519,6 +519,28 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::GENERAL, eChatMessageResponseCode::NOTFRIENDS);
}
void ChatPacketHandler::OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr) {
ChatPackets::AchievementNotify notify{};
notify.Deserialize(bitstream);
const auto& playerData = Game::playerContainer.GetPlayerData(notify.earnerName.GetAsString());
if (!playerData) return;
for (const auto& myFriend : playerData.friends) {
auto& friendData = Game::playerContainer.GetPlayerData(myFriend.friendID);
if (friendData) {
notify.targetPlayerName.string = GeneralUtils::ASCIIToUTF16(friendData.playerName);
LOG_DEBUG("Sending achievement notify to %s", notify.targetPlayerName.GetAsString().c_str());
RakNet::BitStream worldStream;
BitStreamUtils::WriteHeader(worldStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
worldStream.Write(friendData.playerID);
notify.WriteHeader(worldStream);
notify.Serialize(worldStream);
Game::server->Send(worldStream, friendData.worldServerSysAddr, false);
}
}
}
void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
@@ -537,7 +559,7 @@ void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const P
bitStream.Write(responseCode);
bitStream.Write(message);
SystemAddress sysAddr = routeTo.sysAddr;
SystemAddress sysAddr = routeTo.worldServerSysAddr;
SEND_PACKET;
}
@@ -580,6 +602,19 @@ void ChatPacketHandler::HandleTeamInvite(Packet* packet) {
SendTeamInvite(other, player);
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str());
bool failed = false;
for (const auto& ignore : other.ignoredPlayers) {
if (ignore.playerId == player.playerID) {
failed = true;
break;
}
}
ChatPackets::TeamInviteInitialResponse response{};
response.inviteFailedToSend = failed;
response.playerName = invitedPlayer.string;
ChatPackets::SendRoutedMsg(response, playerID, player.worldServerSysAddr);
}
void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
@@ -593,7 +628,7 @@ void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
LWOOBJID leaderID = LWOOBJID_EMPTY;
inStream.Read(leaderID);
LOG("Accepted invite: %llu -> %llu (%d)", playerID, leaderID, declined);
LOG("Invite reponse received: %llu -> %llu (%d)", playerID, leaderID, declined);
if (declined) {
return;
@@ -722,14 +757,15 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
const auto& data = Game::playerContainer.GetPlayerData(playerID);
if (team != nullptr && data) {
LOG_DEBUG("Player %llu is requesting team status", playerID);
if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
Game::playerContainer.RemoveMember(team, playerID, false, false, true, true);
Game::playerContainer.RemoveMember(team, playerID, false, false, false, true);
return;
}
if (team->memberIDs.size() <= 1 && !team->local) {
Game::playerContainer.DisbandTeam(team);
Game::playerContainer.DisbandTeam(team, LWOOBJID_EMPTY, u"");
return;
}
@@ -772,7 +808,7 @@ void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerD
bitStream.Write(LUWString(sender.playerName.c_str()));
bitStream.Write(sender.playerID);
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@@ -799,7 +835,7 @@ void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool b
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@@ -824,7 +860,7 @@ void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64L
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@@ -841,7 +877,7 @@ void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i
bitStream.Write(i64PlayerID);
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@@ -870,7 +906,7 @@ void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFr
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@@ -896,7 +932,7 @@ void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bD
bitStream.Write(character);
}
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@@ -917,7 +953,7 @@ void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOO
}
bitStream.Write(zoneID);
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@@ -959,7 +995,7 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla
bitStream.Write<uint8_t>(isBestFriend); //isBFF
bitStream.Write<uint8_t>(0); //isFTP
SystemAddress sysAddr = friendData.sysAddr;
SystemAddress sysAddr = friendData.worldServerSysAddr;
SEND_PACKET;
}
@@ -981,7 +1017,7 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
bitStream.Write(LUWString(sender.playerName));
bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side.
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@@ -994,7 +1030,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_RESPONSE);
bitStream.Write(responseCode);
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.sysAddr != UNASSIGNED_SYSTEM_ADDRESS);
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS);
// Then write the player name
bitStream.Write(LUWString(sender.playerName));
// Then if this is an acceptance code, write the following extra info.
@@ -1004,7 +1040,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
bitStream.Write(isBestFriendRequest); //isBFF
bitStream.Write<uint8_t>(0); //isFTP
}
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}
@@ -1018,6 +1054,6 @@ void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string
bitStream.Write<uint8_t>(isSuccessful); //isOnline
bitStream.Write(LUWString(personToRemove));
SystemAddress sysAddr = receiver.sysAddr;
SystemAddress sysAddr = receiver.worldServerSysAddr;
SEND_PACKET;
}

View File

@@ -64,12 +64,15 @@ namespace ChatPacketHandler {
void HandleTeamPromote(Packet* packet);
void HandleTeamLootOption(Packet* packet);
void HandleTeamStatusRequest(Packet* packet);
void OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr);
void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender);
void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName);
void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName);
void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID);
void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID);
/* Sends a message to the provided `receiver` with information about the updated team. If `i64LeaderID` is not LWOOBJID_EMPTY, the client will update the leader to that new playerID. */
void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName);
void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);

View File

@@ -224,6 +224,10 @@ void HandlePacket(Packet* packet) {
if (connection != eConnectionType::CHAT) return;
inStream.Read(chatMessageID);
// Our packing byte wasnt there? Probably a false packet
if (inStream.GetNumberOfUnreadBits() < 8) return;
inStream.IgnoreBytes(1);
switch (chatMessageID) {
case MessageType::Chat::GM_MUTE:
Game::playerContainer.MuteUpdate(packet);
@@ -322,6 +326,9 @@ void HandlePacket(Packet* packet) {
case MessageType::Chat::SHOW_ALL:
ChatPacketHandler::HandleShowAll(packet);
break;
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
ChatPacketHandler::OnAchievementNotify(inStream, packet->systemAddress);
break;
case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE:
case MessageType::Chat::WORLD_DISCONNECT_REQUEST:
case MessageType::Chat::WORLD_PROXIMITY_RESPONSE:
@@ -357,7 +364,6 @@ void HandlePacket(Packet* packet) {
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
case MessageType::Chat::UGCC_REQUEST:
case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW:
case MessageType::Chat::PLAYER_READY:
case MessageType::Chat::GET_DONATION_TOTAL:

View File

@@ -52,7 +52,7 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
if (!inStream.Read(data.zoneID)) return;
if (!inStream.Read(data.muteExpire)) return;
if (!inStream.Read(data.gmLevel)) return;
data.sysAddr = packet->systemAddress;
data.worldServerSysAddr = packet->systemAddress;
m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName);
m_PlayerCount++;
@@ -241,7 +241,7 @@ void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
LOG("Tried to add player to team that already had 4 players");
const auto& player = GetPlayerData(playerID);
if (!player) return;
ChatPackets::SendSystemMessage(player.sysAddr, u"The teams is full! You have not been added to a team!");
ChatPackets::SendSystemMessage(player.worldServerSysAddr, u"The teams is full! You have not been added to a team!");
return;
}
@@ -284,41 +284,39 @@ void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
}
}
void PlayerContainer::RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent) {
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
void PlayerContainer::RemoveMember(TeamData* team, LWOOBJID causingPlayerID, bool disband, bool kicked, bool leaving, bool silent) {
LOG_DEBUG("Player %llu is leaving team %i", causingPlayerID, team->teamID);
const auto index = std::ranges::find(team->memberIDs, causingPlayerID);
if (index == team->memberIDs.end()) return;
const auto& member = GetPlayerData(playerID);
if (member && !silent) {
ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY);
}
const auto memberName = GetName(playerID);
for (const auto memberId : team->memberIDs) {
if (silent && memberId == playerID) {
continue;
}
const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue;
ChatPacketHandler::SendTeamRemovePlayer(otherMember, disband, kicked, leaving, false, team->leaderID, playerID, memberName);
}
team->memberIDs.erase(index);
UpdateTeamsOnWorld(team, false);
const auto& member = GetPlayerData(causingPlayerID);
const auto causingMemberName = GetName(causingPlayerID);
if (member && !silent) {
ChatPacketHandler::SendTeamRemovePlayer(member, disband, kicked, leaving, team->local, LWOOBJID_EMPTY, causingPlayerID, causingMemberName);
}
if (team->memberIDs.size() <= 1) {
DisbandTeam(team);
} else {
if (playerID == team->leaderID) {
PromoteMember(team, team->memberIDs[0]);
DisbandTeam(team, causingPlayerID, causingMemberName);
} else /* team has enough members to be a team still */ {
team->leaderID = (causingPlayerID == team->leaderID) ? team->memberIDs[0] : team->leaderID;
for (const auto memberId : team->memberIDs) {
if (silent && memberId == causingPlayerID) {
continue;
}
const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue;
ChatPacketHandler::SendTeamRemovePlayer(otherMember, disband, kicked, leaving, team->local, team->leaderID, causingPlayerID, causingMemberName);
}
UpdateTeamsOnWorld(team, false);
}
}
@@ -334,20 +332,19 @@ void PlayerContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) {
}
}
void PlayerContainer::DisbandTeam(TeamData* team) {
const auto index = std::find(GetTeams().begin(), GetTeams().end(), team);
void PlayerContainer::DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName) {
const auto index = std::ranges::find(GetTeams(), team);
if (index == GetTeams().end()) return;
LOG_DEBUG("Disbanding team %i", (*index)->teamID);
for (const auto memberId : team->memberIDs) {
const auto& otherMember = GetPlayerData(memberId);
if (!otherMember) continue;
const auto memberName = GeneralUtils::UTF8ToUTF16(otherMember.playerName);
ChatPacketHandler::SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, otherMember.playerID, memberName);
ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, causingPlayerID, causingPlayerName);
}
UpdateTeamsOnWorld(team, true);

View File

@@ -42,7 +42,7 @@ struct PlayerData {
return muteExpire == 1 || muteExpire > time(NULL);
}
SystemAddress sysAddr{};
SystemAddress worldServerSysAddr{};
LWOZONEID zoneID{};
LWOOBJID playerID = LWOOBJID_EMPTY;
time_t muteExpire = 0;
@@ -91,7 +91,7 @@ public:
void AddMember(TeamData* team, LWOOBJID playerID);
void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false);
void PromoteMember(TeamData* team, LWOOBJID newLeader);
void DisbandTeam(TeamData* team);
void DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName);
void TeamStatusUpdate(TeamData* team);
void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam);
std::u16string GetName(LWOOBJID playerID);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
#include "Pack.h"
#include "BinaryIO.h"
#include "Sd0.h"
#include "ZCompression.h"
Pack::Pack(const std::filesystem::path& filePath) {
@@ -107,7 +106,7 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons
pos += size; // Move pointer position the amount of bytes read to the right
int32_t err;
currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err);
currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), ZCompression::MAX_SD0_CHUNK_SIZE, err);
free(chunk);
}

View File

@@ -98,6 +98,7 @@ public:
constexpr LWOZONEID() noexcept = default;
constexpr LWOZONEID(const LWOMAPID& mapID, const LWOINSTANCEID& instanceID, const LWOCLONEID& cloneID) noexcept { m_MapID = mapID; m_InstanceID = instanceID; m_CloneID = cloneID; }
constexpr LWOZONEID(const LWOZONEID& replacement) noexcept { *this = replacement; }
constexpr bool operator==(const LWOZONEID&) const = default;
private:
LWOMAPID m_MapID = LWOMAPID_INVALID; //1000 for VE, 1100 for AG, etc...

View File

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

View File

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

View File

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

View File

@@ -48,7 +48,7 @@ public:
void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override;
void CreateMigrationHistoryTable() override;
bool IsMigrationRun(const std::string_view str) override;
@@ -74,14 +74,14 @@ public:
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel(
std::stringstream& sd0Data,
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) override;
@@ -126,7 +126,6 @@ public:
void DeleteUgcBuild(const LWOOBJID bigId) override;
uint32_t GetAccountCount() override;
bool IsNameInUse(const std::string_view name) override;
IPropertyContents::Model GetModel(const LWOOBJID modelID) override;
sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
private:

View File

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

View File

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

View File

@@ -46,7 +46,7 @@ public:
void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override;
void CreateMigrationHistoryTable() override;
bool IsMigrationRun(const std::string_view str) override;
@@ -72,14 +72,14 @@ public:
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel(
std::stringstream& sd0Data,
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) override;
@@ -124,7 +124,6 @@ public:
void DeleteUgcBuild(const LWOOBJID bigId) override;
uint32_t GetAccountCount() override;
bool IsNameInUse(const std::string_view name) override;
IPropertyContents::Model GetModel(const LWOOBJID modelID) override;
private:
CppSQLite3Statement CreatePreppedStmt(const std::string& query);

View File

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

View File

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

View File

@@ -60,7 +60,7 @@ void TestSQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
}
void TestSQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {
void TestSQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
}
@@ -164,7 +164,7 @@ void TestSQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const I
}
void TestSQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
void TestSQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
}
@@ -188,7 +188,7 @@ void TestSQLDatabase::InsertNewMail(const MailInfo& mail) {
}
void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const uint32_t characterId) {
void TestSQLDatabase::InsertNewUgcModel(std::istringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const uint32_t characterId) {
}

View File

@@ -25,7 +25,7 @@ class TestSQLDatabase : public GameDatabase {
void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override;
void CreateMigrationHistoryTable() override;
bool IsMigrationRun(const std::string_view str) override;
@@ -51,14 +51,14 @@ class TestSQLDatabase : public GameDatabase {
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel(
std::stringstream& sd0Data,
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) override;
@@ -104,7 +104,6 @@ class TestSQLDatabase : public GameDatabase {
uint32_t GetAccountCount() override { return 0; };
bool IsNameInUse(const std::string_view name) override { return false; };
IPropertyContents::Model GetModel(const LWOOBJID modelID) override { return {}; }
};
#endif //!TESTSQLDATABASE_H

View File

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

View File

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

View File

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

View File

@@ -204,6 +204,7 @@ void Character::DoQuickXMLDataParse() {
while (currentChild) {
const auto* temp = currentChild->Attribute("v");
const auto* id = currentChild->Attribute("id");
const auto* si = currentChild->Attribute("si");
if (temp && id) {
uint32_t index = 0;
uint64_t value = 0;
@@ -212,6 +213,9 @@ void Character::DoQuickXMLDataParse() {
value = std::stoull(temp);
m_PlayerFlags.insert(std::make_pair(index, value));
} else if (si) {
auto value = GeneralUtils::TryParse<uint32_t>(si);
if (value) m_SessionFlags.insert(value.value());
}
currentChild = currentChild->NextSiblingElement();
}

View File

@@ -99,7 +99,7 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE
}
// Exclude the zone control object from any flags
if (!controller) {
if (!controller && info.lot != 14) {
// The client flags means the client should render the entity
GeneralUtils::SetBit(id, eObjectBits::CLIENT);

View File

@@ -102,8 +102,6 @@
#include "CDComponentsRegistryTable.h"
#include "CDObjectsTable.h"
#include "eItemType.h"
#include "Lxfml.h"
#include "Sd0.h"
void GameMessages::SendFireEventClientSide(const LWOOBJID& objectID, const SystemAddress& sysAddr, std::u16string args, const LWOOBJID& object, int64_t param1, int param2, const LWOOBJID& sender) {
CBITSTREAM;
@@ -2576,6 +2574,18 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
TODO Apparently the bricks are supposed to be taken via MoveInventoryBatch?
*/
////Decompress the SD0 from the client so we can process the lxfml properly
//uint8_t* outData = new uint8_t[327680];
//int32_t error;
//int32_t size = ZCompression::Decompress(inData, lxfmlSize, outData, 327680, error);
//if (size == -1) {
// LOG("Failed to decompress LXFML: (%i)", error);
// return;
//}
//
//std::string lxfml(reinterpret_cast<char*>(outData), size); //std::string version of the decompressed data!
//Now, the cave of dragons:
//We runs this in async because the http library here is blocking, meaning it'll halt the thread.
@@ -2603,25 +2613,16 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
LWOOBJID propertyId = LWOOBJID_EMPTY;
if (propertyInfo) propertyId = propertyInfo->id;
// Save the binary data to the Sd0 buffer
//Insert into ugc:
std::string str(sd0Data.get(), sd0Size);
std::istringstream sd0DataStream(str);
Sd0 sd0(sd0DataStream);
// Uncompress the data and normalize the position
const auto asStr = sd0.GetAsStringUncompressed();
const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr);
// Recompress the data and save to the database
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
auto sd0AsStream = sd0.GetAsStream();
Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintIDSmall, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID());
Database::Get()->InsertNewUgcModel(sd0DataStream, blueprintIDSmall, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID());
//Insert into the db as a BBB model:
IPropertyContents::Model model;
model.id = newIDL;
model.ugcId = blueprintIDSmall;
model.position = newCenter;
model.position = NiPoint3Constant::ZERO;
model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f);
model.lot = 14;
Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name");
@@ -2647,9 +2648,6 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
//}
//Tell the client their model is saved: (this causes us to actually pop out of our current state):
const auto& newSd0 = sd0.GetAsVector();
uint32_t sd0Size{};
for (const auto& chunk : newSd0) sd0Size += chunk.size();
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
bitStream.Write(localId);
@@ -2657,9 +2655,9 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
bitStream.Write<uint32_t>(1);
bitStream.Write(blueprintID);
bitStream.Write(sd0Size);
bitStream.Write<uint32_t>(sd0Size);
for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
bitStream.WriteAlignedBytes(reinterpret_cast<unsigned char*>(sd0Data.get()), sd0Size);
SEND_PACKET;
@@ -2667,7 +2665,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
EntityInfo info;
info.lot = 14;
info.pos = newCenter;
info.pos = {};
info.rot = {};
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();

View File

@@ -27,6 +27,8 @@
#include "Character.h"
#include "CDMissionEmailTable.h"
#include "ChatPackets.h"
#include "PlayerManager.h"
Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) {
m_MissionComponent = missionComponent;
@@ -355,12 +357,25 @@ void Mission::Complete(const bool yieldRewards) {
for (const auto& email : missionEmails) {
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 body = "%[" + missionEmailBase + "bodyText]";
const auto sender = "%[" + missionEmailBase + "senderName]";
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

@@ -777,7 +777,7 @@ void SlashCommandHandler::Startup() {
.info = "Crashes the server",
.aliases = { "crash", "pumpkin" },
.handle = DEVGMCommands::Crash,
.requiredLevel = eGameMasterLevel::DEVELOPER
.requiredLevel = eGameMasterLevel::OPERATOR
};
RegisterCommand(CrashCommand);
@@ -1444,4 +1444,13 @@ void SlashCommandHandler::Startup() {
.requiredLevel = eGameMasterLevel::CIVILIAN
};
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 CastSkill(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

View File

@@ -273,6 +273,16 @@ Instance* InstanceManager::FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID
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) {
auto* instance = FindPrivateInstance(password);

View File

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

View File

@@ -42,6 +42,7 @@
#include "Server.h"
#include "CDZoneTableTable.h"
#include "eGameMasterLevel.h"
#include "StringifiedEnum.h"
#ifdef DARKFLAME_PLATFORM_UNIX
@@ -212,6 +213,13 @@ int main(int argc, char** argv) {
// Run migrations should any need to be run.
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
//input a username and password, with the password being hidden.
bool createAccount = Database::Get()->GetAccountCount() == 0 && Game::config->GetValue("skip_account_creation") != "1";
@@ -556,7 +564,7 @@ void HandlePacket(Packet* packet) {
Instance* in = Game::im->GetInstance(zoneID, false, zoneClone);
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
@@ -597,15 +605,10 @@ void HandlePacket(Packet* packet) {
if (!Game::im->IsPortInUse(theirPort)) {
Instance* in = new Instance(theirIP.string, theirPort, theirZoneID, theirInstanceID, 0, 12, 12);
SystemAddress copy;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
in->SetSysAddr(copy);
in->SetSysAddr(packet->systemAddress);
Game::im->AddInstance(in);
} else {
auto instance = Game::im->FindInstance(
theirZoneID, static_cast<uint16_t>(theirInstanceID));
auto* instance = Game::im->FindInstanceWithPrivate(theirZoneID, static_cast<LWOINSTANCEID>(theirInstanceID));
if (instance) {
instance->SetSysAddr(packet->systemAddress);
}
@@ -613,22 +616,14 @@ void HandlePacket(Packet* packet) {
}
if (theirServerType == ServerType::Chat) {
SystemAddress copy;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
chatServerMasterPeerSysAddr = copy;
chatServerMasterPeerSysAddr = packet->systemAddress;
}
if (theirServerType == ServerType::Auth) {
SystemAddress copy;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
authServerMasterPeerSysAddr = copy;
authServerMasterPeerSysAddr = packet->systemAddress;
}
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;
}
@@ -692,7 +687,7 @@ void HandlePacket(Packet* packet) {
if (instance) {
instance->AddPlayer(Player());
} else {
printf("Instance missing? What?");
LOG("Instance missing? What?");
}
break;
}
@@ -733,8 +728,8 @@ void HandlePacket(Packet* packet) {
inStream.Read<char>(character);
password += character;
}
Game::im->CreatePrivateInstance(mapId, cloneId, password.c_str());
auto* newInst = 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;
}
@@ -835,11 +830,10 @@ void HandlePacket(Packet* packet) {
}
case MessageType::Master::SHUTDOWN_RESPONSE: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
CINSTREAM_SKIP_HEADER;
auto* instance = Game::im->GetInstanceBySysAddr(packet->systemAddress);
LOG("Got shutdown response from %s", packet->systemAddress.ToString());
if (instance == nullptr) {
return;
}

View File

@@ -107,3 +107,44 @@ void ChatPackets::Announcement::Send() {
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(targetPlayerName);
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(earnerName);
}
bool ChatPackets::AchievementNotify::Deserialize(RakNet::BitStream& bitstream) {
bitstream.IgnoreBytes(13);
VALIDATE_READ(bitstream.Read(targetPlayerName));
bitstream.IgnoreBytes(15);
VALIDATE_READ(bitstream.Read(missionEmailID));
VALIDATE_READ(bitstream.Read(earningPlayerID));
VALIDATE_READ(bitstream.Read(earnerName));
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 "dCommonVars.h"
#include "MessageType/Chat.h"
#include "BitStreamUtils.h"
struct ShowAllRequest{
LWOOBJID requestor = LWOOBJID_EMPTY;
@@ -34,9 +36,29 @@ namespace ChatPackets {
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 SendSystemMessage(const SystemAddress& sysAddr, const std::u16string& message, bool broadcast = false);
void SendMessageFail(const SystemAddress& sysAddr);
void SendRoutedMsg(const LUBitStream& msg, const LWOOBJID targetID, const SystemAddress& sysAddr);
};
#endif // CHATPACKETS_H

View File

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

View File

@@ -9,9 +9,10 @@ void ActSharkPlayerDeathTrigger::OnFireEventServerSide(Entity* self, Entity* sen
auto missionComponent = sender->GetComponent<MissionComponent>();
if (!missionComponent) return;
missionComponent->Progress(eMissionTaskType::SCRIPT, 8419);
// This check is only needed because dlu doesnt have proper collision checks on rotated phantom physics
if (sender->GetIsDead() || !sender->GetPlayerReadyForUpdates()) return; //Don't kill already dead players or players not ready
missionComponent->Progress(eMissionTaskType::SCRIPT, 8419);
if (sender->GetCharacter()) {
sender->Smash(self->GetObjectID(), eKillType::VIOLENT, u"big-shark-death");

View File

@@ -3,69 +3,68 @@
#include "CDClientManager.h"
#include "UserManager.h"
#define SOCIAL { lowFrameDelta }
#define SOCIAL_HUB { mediumFrameDelta } //Added to compensate for the large playercounts in NS and NT
#define BATTLE { highFrameDelta }
#define BATTLE_INSTANCE { mediumFrameDelta }
#define RACE { highFrameDelta }
#define PROPERTY { lowFrameDelta }
#define SOCIAL lowFrameDelta
#define SOCIAL_HUB mediumFrameDelta //Added to compensate for the large playercounts in NS and NT
#define BATTLE highFrameDelta
#define BATTLE_INSTANCE mediumFrameDelta
#define RACE highFrameDelta
#define PROPERTY lowFrameDelta
PerformanceProfile PerformanceManager::m_CurrentProfile = SOCIAL;
namespace {
PerformanceProfile m_CurrentProfile = SOCIAL;
PerformanceProfile m_DefaultProfile = SOCIAL;
PerformanceProfile m_InactiveProfile = lowFrameDelta;
std::map<LWOMAPID, PerformanceProfile> m_Profiles = {
// VE
{ 1000, SOCIAL },
PerformanceProfile PerformanceManager::m_DefaultProfile = SOCIAL;
// AG
{ 1100, BATTLE },
{ 1101, BATTLE_INSTANCE },
{ 1102, BATTLE_INSTANCE },
{ 1150, PROPERTY },
{ 1151, PROPERTY },
PerformanceProfile PerformanceManager::m_InactiveProfile = { lowFrameDelta };
// NS
{ 1200, SOCIAL_HUB },
{ 1201, SOCIAL },
{ 1203, RACE },
{ 1204, BATTLE_INSTANCE },
{ 1250, PROPERTY },
{ 1251, PROPERTY },
std::map<LWOMAPID, PerformanceProfile> PerformanceManager::m_Profiles = {
// VE
{ 1000, SOCIAL },
// GF
{ 1300, BATTLE },
{ 1302, BATTLE_INSTANCE },
{ 1303, BATTLE_INSTANCE },
{ 1350, PROPERTY },
// AG
{ 1100, BATTLE },
{ 1101, BATTLE_INSTANCE },
{ 1102, BATTLE_INSTANCE },
{ 1150, PROPERTY },
{ 1151, PROPERTY },
// FV
{ 1400, BATTLE },
{ 1402, BATTLE_INSTANCE },
{ 1403, RACE },
{ 1450, PROPERTY },
// NS
{ 1200, SOCIAL_HUB },
{ 1201, SOCIAL },
{ 1203, RACE },
{ 1204, BATTLE_INSTANCE },
{ 1250, PROPERTY },
{ 1251, PROPERTY },
// LUP
{ 1600, SOCIAL },
{ 1601, SOCIAL },
{ 1602, SOCIAL },
{ 1603, SOCIAL },
{ 1604, SOCIAL },
// GF
{ 1300, BATTLE },
{ 1302, BATTLE_INSTANCE },
{ 1303, BATTLE_INSTANCE },
{ 1350, PROPERTY },
// LEGO Club
{ 1700, SOCIAL },
// FV
{ 1400, BATTLE },
{ 1402, BATTLE_INSTANCE },
{ 1403, RACE },
{ 1450, PROPERTY },
// AM
{ 1800, BATTLE },
// LUP
{ 1600, SOCIAL },
{ 1601, SOCIAL },
{ 1602, SOCIAL },
{ 1603, SOCIAL },
{ 1604, SOCIAL },
// NT
{ 1900, SOCIAL_HUB },
// LEGO Club
{ 1700, SOCIAL },
// AM
{ 1800, BATTLE },
// NT
{ 1900, SOCIAL_HUB },
// NJ
{ 2000, BATTLE },
{ 2001, BATTLE_INSTANCE },
// NJ
{ 2000, BATTLE },
{ 2001, BATTLE_INSTANCE },
};
};
void PerformanceManager::SelectProfile(LWOMAPID mapID) {
@@ -74,16 +73,16 @@ void PerformanceManager::SelectProfile(LWOMAPID mapID) {
if (zoneTable) {
const CDZoneTable* zone = zoneTable->Query(mapID);
if (zone) {
if (zone->serverPhysicsFramerate == "high"){
m_CurrentProfile = { highFrameDelta };
if (zone->serverPhysicsFramerate == "high") {
m_CurrentProfile = highFrameDelta;
return;
}
if (zone->serverPhysicsFramerate == "medium"){
m_CurrentProfile = { mediumFrameDelta };
if (zone->serverPhysicsFramerate == "medium") {
m_CurrentProfile = mediumFrameDelta;
return;
}
if (zone->serverPhysicsFramerate == "low"){
m_CurrentProfile = { lowFrameDelta };
if (zone->serverPhysicsFramerate == "low") {
m_CurrentProfile = lowFrameDelta;
return;
}
}
@@ -91,18 +90,9 @@ void PerformanceManager::SelectProfile(LWOMAPID mapID) {
// Fall back to hardcoded list and defaults
const auto pair = m_Profiles.find(mapID);
if (pair == m_Profiles.end()) {
m_CurrentProfile = m_DefaultProfile;
return;
}
m_CurrentProfile = pair->second;
m_CurrentProfile = pair == m_Profiles.end() ? m_DefaultProfile : pair->second;
}
uint32_t PerformanceManager::GetServerFrameDelta() {
if (UserManager::Instance()->GetUserCount() == 0) {
return m_InactiveProfile.serverFrameDelta;
}
return m_CurrentProfile.serverFrameDelta;
return UserManager::Instance()->GetUserCount() == 0 ? m_CurrentProfile : m_InactiveProfile;
}

View File

@@ -1,22 +1,16 @@
#pragma once
#include <cstdint>
#include <map>
#include "dCommonVars.h"
struct PerformanceProfile {
uint32_t serverFrameDelta;
};
class PerformanceManager {
public:
static void SelectProfile(LWOMAPID mapID);
static uint32_t GetServerFrameDelta();
private:
static PerformanceProfile m_CurrentProfile;
static PerformanceProfile m_DefaultProfile;
static PerformanceProfile m_InactiveProfile;
static std::map<LWOMAPID, PerformanceProfile> m_Profiles;
using PerformanceProfile = uint32_t;
namespace PerformanceManager {
/* Sets a performance profile for a given world. */
void SelectProfile(LWOMAPID mapID);
/* Gets the frame millisecond delta. Will return a higher value if the zone is empty. */
uint32_t GetServerFrameDelta();
};

View File

@@ -98,9 +98,22 @@ namespace Game {
std::string projectVersion = PROJECT_VERSION;
} // namespace Game
bool chatDisabled = false;
bool chatConnected = false;
bool worldShutdownSequenceComplete = false;
namespace {
struct TempSessionInfo {
SystemAddress sysAddr;
std::string hash;
};
std::map<std::string, TempSessionInfo> g_PendingUsers;
uint32_t g_InstanceID = 0;
uint32_t g_CloneID = 0;
std::string g_DatabaseChecksum = "";
bool g_ChatDisabled = false;
bool g_ChatConnected = false;
bool g_WorldShutdownSequenceComplete = false;
}; // namespace anonymous
void WorldShutdownSequence();
void WorldShutdownProcess(uint32_t zoneId);
void FinalizeShutdown();
@@ -110,16 +123,6 @@ void HandlePacketChat(Packet* packet);
void HandleMasterPacket(Packet* packet);
void HandlePacket(Packet* packet);
struct tempSessionInfo {
SystemAddress sysAddr;
std::string hash;
};
std::map<std::string, tempSessionInfo> m_PendingUsers;
uint32_t instanceID = 0;
uint32_t g_CloneID = 0;
std::string databaseChecksum = "";
int main(int argc, char** argv) {
Diagnostics::SetProcessName("World");
Diagnostics::SetProcessFileName(argv[0]);
@@ -137,27 +140,31 @@ int main(int argc, char** argv) {
uint32_t ourPort = 2007;
//Check our arguments:
for (int32_t i = 0; i < argc; ++i) {
for (int32_t i = 0; (i + 1) < argc; i++) {
std::string argument(argv[i]);
const auto valOptional = GeneralUtils::TryParse<uint32_t>(argv[i + 1]);
if (!valOptional) {
continue;
}
if (argument == "-zone") zoneID = atoi(argv[i + 1]);
if (argument == "-instance") instanceID = atoi(argv[i + 1]);
if (argument == "-clone") cloneID = atoi(argv[i + 1]);
if (argument == "-maxclients") maxClients = atoi(argv[i + 1]);
if (argument == "-port") ourPort = atoi(argv[i + 1]);
if (argument == "-zone") zoneID = valOptional.value_or(1000);
else if (argument == "-instance") g_InstanceID = valOptional.value_or(0);
else if (argument == "-clone") cloneID = valOptional.value_or(0);
else if (argument == "-maxclients") maxClients = valOptional.value_or(8);
else if (argument == "-port") ourPort = valOptional.value_or(2007);
}
Game::config = new dConfig("worldconfig.ini");
//Create all the objects we need to run our service:
Server::SetupLogger("WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(instanceID));
Server::SetupLogger("WorldServer_" + std::to_string(zoneID) + "_" + std::to_string(g_InstanceID));
if (!Game::logger) return EXIT_FAILURE;
LOG("Starting World server...");
LOG("Version: %s", Game::projectVersion.c_str());
LOG("Compiled on: %s", __TIMESTAMP__);
if (Game::config->GetValue("disable_chat") == "1") chatDisabled = true;
g_ChatDisabled = Game::config->GetValue("disable_chat") == "1";
try {
std::string clientPathStr = Game::config->GetValue("client_location");
@@ -167,7 +174,7 @@ int main(int argc, char** argv) {
clientPath = BinaryPathFinder::GetBinaryDir() / clientPath;
}
Game::assetManager = new AssetManager(clientPath);
} catch (std::runtime_error& ex) {
} catch (const std::exception& ex) {
LOG("Got an error while setting up assets: %s", ex.what());
return EXIT_FAILURE;
@@ -176,11 +183,14 @@ int main(int argc, char** argv) {
// Connect to CDClient
try {
CDClientDatabase::Connect((BinaryPathFinder::GetBinaryDir() / "resServer" / "CDServer.sqlite").string());
} catch (CppSQLite3Exception& e) {
} catch (const CppSQLite3Exception& e) {
LOG("Unable to connect to CDServer SQLite Database");
LOG("Error: %s", e.errorMessage());
LOG("Error Code: %i", e.errorCode());
return EXIT_FAILURE;
} catch (const std::exception& e) {
LOG("Caught generic exception %s when connecting to CDClient", e.what());
return EXIT_FAILURE;
}
CDClientManager::LoadValuesFromDatabase();
@@ -194,7 +204,7 @@ int main(int argc, char** argv) {
//Connect to the MySQL Database:
try {
Database::Connect();
} catch (std::exception& ex) {
} catch (const std::exception& ex) {
LOG("Got an error while connecting to the database: %s", ex.what());
return EXIT_FAILURE;
}
@@ -203,7 +213,7 @@ int main(int argc, char** argv) {
std::string masterIP = "localhost";
uint32_t masterPort = 1000;
std::string masterPassword;
auto masterInfo = Database::Get()->GetMasterInfo();
const auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
@@ -216,13 +226,25 @@ int main(int argc, char** argv) {
const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false);
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF);
Game::server = new dServer(masterIP, ourPort, instanceID, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::World, Game::config, &Game::lastSignal, masterPassword, zoneID);
Game::server = new dServer(masterIP,
ourPort,
g_InstanceID,
maxClients,
false /* Is internal */,
true /* Use encryption */,
Game::logger,
masterIP,
masterPort,
ServerType::World,
Game::config,
&Game::lastSignal,
masterPassword,
zoneID);
//Connect to the chat server:
uint32_t chatPort = 1501;
if (Game::config->GetValue("chat_server_port") != "") chatPort = std::atoi(Game::config->GetValue("chat_server_port").c_str());
uint32_t chatPort = GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("chat_server_port")).value_or(1501);
auto chatSock = SocketDescriptor(static_cast<uint16_t>(ourPort + 2), 0);
auto chatSock = SocketDescriptor(static_cast<uint16_t>(ourPort + 2), NULL);
Game::chatServer = RakNetworkFactory::GetRakPeerInterface();
Game::chatServer->Startup(1, 30, &chatSock, 1);
Game::chatServer->Connect(masterIP.c_str(), chatPort, NET_PASSWORD_EXTERNAL, strnlen(NET_PASSWORD_EXTERNAL, sizeof(NET_PASSWORD_EXTERNAL)));
@@ -260,9 +282,8 @@ int main(int argc, char** argv) {
//Load our level:
if (zoneID != 0) {
dpWorld::Initialize(zoneID);
Game::zoneManager->Initialize(LWOZONEID(zoneID, instanceID, cloneID));
Game::zoneManager->Initialize(LWOZONEID(zoneID, g_InstanceID, cloneID));
g_CloneID = cloneID;
} else {
Game::entityManager->Initialize();
}
@@ -286,11 +307,11 @@ int main(int argc, char** argv) {
const char* nullTerminateBuffer = "\0";
md5.update(nullTerminateBuffer, 1); // null terminate the data
md5.finalize();
databaseChecksum = md5.hexdigest();
g_DatabaseChecksum = md5.hexdigest();
LOG("FDB Checksum calculated as: %s", databaseChecksum.c_str());
LOG("FDB Checksum calculated as: %s", g_DatabaseChecksum.c_str());
}
if (databaseChecksum.empty()) {
if (g_DatabaseChecksum.empty()) {
LOG("check_fdb is on but no fdb file found.");
return EXIT_FAILURE;
}
@@ -334,7 +355,7 @@ int main(int argc, char** argv) {
float_t ratioBeforeToAfter = static_cast<float>(currentFrameDelta) / static_cast<float>(newFrameDelta);
currentFrameDelta = newFrameDelta;
currentFramerate = MS_TO_FRAMES(newFrameDelta);
LOG_DEBUG("Framerate for zone/instance/clone %i/%i/%i is now %i", zoneID, instanceID, cloneID, currentFramerate);
LOG_DEBUG("Framerate for zone/instance/clone %i/%i/%i is now %i", zoneID, g_InstanceID, cloneID, currentFramerate);
logFlushTime = 15 * currentFramerate; // 15 seconds in frames
framesSinceLastFlush *= ratioBeforeToAfter;
shutdownTimeout = 10 * 60 * currentFramerate; // 10 minutes in frames
@@ -367,7 +388,7 @@ int main(int argc, char** argv) {
} else framesSinceMasterDisconnect = 0;
// Check if we're still connected to chat:
if (!chatConnected) {
if (!g_ChatConnected) {
framesSinceChatDisconnect++;
if (framesSinceChatDisconnect >= chatReconnectionTime) {
@@ -404,16 +425,18 @@ int main(int argc, char** argv) {
//Check for packets here:
packet = Game::server->ReceiveFromMaster();
if (packet) { //We can get messages not handle-able by the dServer class, so handle them if we returned anything.
while (packet) { //We can get messages not handle-able by the dServer class, so handle them if we returned anything.
HandleMasterPacket(packet);
Game::server->DeallocateMasterPacket(packet);
packet = Game::server->ReceiveFromMaster();
}
//Handle our chat packets:
packet = Game::chatServer->Receive();
if (packet) {
while (packet) {
HandlePacketChat(packet);
Game::chatServer->DeallocatePacket(packet);
packet = Game::chatServer->Receive();
}
//Handle world-specific packets:
@@ -421,7 +444,6 @@ int main(int argc, char** argv) {
UserManager::Instance()->DeletePendingRemovals();
auto t1 = std::chrono::high_resolution_clock::now();
for (uint32_t curPacket = 0; curPacket < maxPacketsToProcess && timeSpent < maxPacketProcessingTime; curPacket++) {
packet = Game::server->Receive();
if (packet) {
@@ -497,20 +519,14 @@ int main(int argc, char** argv) {
Metrics::EndMeasurement(MetricVariable::Sleep);
if (!ready && Game::server->GetIsConnectedToMaster()) {
// Some delay is required here or else we crash the client?
LOG("Finished loading world with zone (%i), ready up!", Game::server->GetZoneID());
framesSinceMasterStatus++;
MasterPackets::SendWorldReady(Game::server, Game::server->GetZoneID(), Game::server->GetInstanceID());
if (framesSinceMasterStatus >= 200) {
LOG("Finished loading world with zone (%i), ready up!", Game::server->GetZoneID());
MasterPackets::SendWorldReady(Game::server, Game::server->GetZoneID(), Game::server->GetInstanceID());
ready = true;
}
ready = true;
}
if (Game::ShouldShutdown() && !worldShutdownSequenceComplete) {
if (Game::ShouldShutdown() && !g_WorldShutdownSequenceComplete) {
WorldShutdownProcess(zoneID);
break;
}
@@ -527,14 +543,14 @@ void HandlePacketChat(Packet* packet) {
if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) {
LOG("Lost our connection to chat, zone(%i), instance(%i)", Game::server->GetZoneID(), Game::server->GetInstanceID());
chatConnected = false;
g_ChatConnected = false;
}
if (packet->data[0] == ID_CONNECTION_REQUEST_ACCEPTED) {
LOG("Established connection to chat, zone(%i), instance (%i)", Game::server->GetZoneID(), Game::server->GetInstanceID());
Game::chatSysAddr = packet->systemAddress;
chatConnected = true;
g_ChatConnected = true;
}
if (packet->data[0] == ID_USER_PACKET_ENUM && packet->length >= 4) {
@@ -634,13 +650,13 @@ void HandlePacketChat(Packet* packet) {
inStream.Read(lootOption);
inStream.Read(memberCount);
LOG("Updating team (%llu), (%i), (%i)", teamID, lootOption, memberCount);
LOG("Updating team ID:(%llu), Loot:(%i), #Members:(%i)", teamID, lootOption, memberCount);
for (char i = 0; i < memberCount; i++) {
LWOOBJID member = LWOOBJID_EMPTY;
inStream.Read(member);
members.push_back(member);
LOG("Updating team member (%llu)", member);
LOG("Added member (%llu) to the team", member);
}
TeamManager::Instance()->UpdateTeam(teamID, lootOption, members);
@@ -648,7 +664,7 @@ void HandlePacketChat(Packet* packet) {
break;
}
default:
LOG("Received an unknown chat: %i", int(packet->data[3]));
LOG("Received an unknown chat: %s", StringifiedEnum::ToString(static_cast<MessageType::Chat>(packet->data[3])).data());
}
}
}
@@ -677,8 +693,8 @@ void HandleMasterPacket(Packet* packet) {
inStream.Read(username);
//Find them:
auto it = m_PendingUsers.find(username.GetAsString());
if (it == m_PendingUsers.end()) return;
auto it = g_PendingUsers.find(username.GetAsString());
if (it == g_PendingUsers.end()) return;
//Convert our key:
std::string userHash = std::to_string(sessionKey);
@@ -718,14 +734,14 @@ void HandleMasterPacket(Packet* packet) {
UserManager::Instance()->RequestCharacterList(it->second.sysAddr);
}
m_PendingUsers.erase(username.GetAsString());
g_PendingUsers.erase(username.GetAsString());
//Notify master:
{
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, MessageType::Master::PLAYER_ADDED);
bitStream.Write<LWOMAPID>(Game::server->GetZoneID());
bitStream.Write<LWOINSTANCEID>(instanceID);
bitStream.Write<LWOINSTANCEID>(g_InstanceID);
Game::server->SendToMaster(bitStream);
}
}
@@ -828,7 +844,7 @@ void HandlePacket(Packet* packet) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::MASTER, MessageType::Master::PLAYER_REMOVED);
bitStream.Write<LWOMAPID>(Game::server->GetZoneID());
bitStream.Write<LWOINSTANCEID>(instanceID);
bitStream.Write<LWOINSTANCEID>(g_InstanceID);
Game::server->SendToMaster(bitStream);
}
@@ -859,7 +875,7 @@ void HandlePacket(Packet* packet) {
inStream.Read(clientDatabaseChecksum);
// If the check is turned on, validate the client's database checksum.
if (Game::config->GetValue("check_fdb") == "1" && !databaseChecksum.empty()) {
if (Game::config->GetValue("check_fdb") == "1" && !g_DatabaseChecksum.empty()) {
auto accountInfo = Database::Get()->GetAccountInfo(username.GetAsString());
if (!accountInfo) {
LOG("Client's account does not exist in the database, aborting connection.");
@@ -868,7 +884,7 @@ void HandlePacket(Packet* packet) {
}
// Developers may skip this check
if (clientDatabaseChecksum.string != databaseChecksum) {
if (clientDatabaseChecksum.string != g_DatabaseChecksum) {
if (accountInfo->maxGmLevel < eGameMasterLevel::DEVELOPER) {
LOG("Client's database checksum does not match the server's, aborting connection.");
@@ -900,10 +916,10 @@ void HandlePacket(Packet* packet) {
Game::server->SendToMaster(bitStream);
//Insert info into our pending list
tempSessionInfo info;
info.sysAddr = SystemAddress(packet->systemAddress);
TempSessionInfo info;
info.sysAddr = packet->systemAddress;
info.hash = sessionKey.GetAsString();
m_PendingUsers.insert(std::make_pair(username.GetAsString(), info));
g_PendingUsers[username.GetAsString()] = info;
break;
}
@@ -918,8 +934,8 @@ void HandlePacket(Packet* packet) {
//This loops prevents users who aren't authenticated to double-request the char list, which
//would make the login screen freeze sometimes.
if (m_PendingUsers.size() > 0) {
for (auto it : m_PendingUsers) {
if (g_PendingUsers.size() > 0) {
for (const auto& it : g_PendingUsers) {
if (it.second.sysAddr == packet->systemAddress) {
return;
}
@@ -1118,8 +1134,6 @@ void HandlePacket(Packet* packet) {
GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER);
GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT);
// Workaround for not having a UGC server to get model LXFML onto the client so it
// can generate the physics and nif for the object.
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
bitStream.Write<LWOOBJID>(LWOOBJID_EMPTY); //always zero so that a check on the client passes
@@ -1305,7 +1319,7 @@ void HandlePacket(Packet* packet) {
}
case MessageType::World::GENERAL_CHAT_MESSAGE: {
if (chatDisabled) {
if (g_ChatDisabled) {
ChatPackets::SendMessageFail(packet->systemAddress);
} else {
auto chatMessage = ClientPackets::HandleChatMessage(packet);
@@ -1398,7 +1412,7 @@ void HandlePacket(Packet* packet) {
}
void WorldShutdownProcess(uint32_t zoneId) {
LOG("Saving map %i instance %i", zoneId, instanceID);
LOG("Saving map %i instance %i", zoneId, g_InstanceID);
for (auto i = 0; i < Game::server->GetReplicaManager()->GetParticipantCount(); ++i) {
const auto& player = Game::server->GetReplicaManager()->GetParticipantAtIndex(i);
@@ -1419,10 +1433,11 @@ void WorldShutdownProcess(uint32_t zoneId) {
if (PropertyManagementComponent::Instance() != nullptr) {
LOG("Saving ALL property data for zone %i clone %i!", zoneId, PropertyManagementComponent::Instance()->GetCloneId());
PropertyManagementComponent::Instance()->Save();
Database::Get()->RemoveUnreferencedUgcModels();
LOG("ALL property data saved for zone %i clone %i!", zoneId, PropertyManagementComponent::Instance()->GetCloneId());
}
LOG("ALL DATA HAS BEEN SAVED FOR ZONE %i INSTANCE %i!", zoneId, instanceID);
LOG("ALL DATA HAS BEEN SAVED FOR ZONE %i INSTANCE %i!", zoneId, g_InstanceID);
while (Game::server->GetReplicaManager()->GetParticipantCount() > 0) {
const auto& player = Game::server->GetReplicaManager()->GetParticipantAtIndex(0);
@@ -1433,7 +1448,7 @@ void WorldShutdownProcess(uint32_t zoneId) {
}
void WorldShutdownSequence() {
bool shouldShutdown = Game::ShouldShutdown() || worldShutdownSequenceComplete;
bool shouldShutdown = Game::ShouldShutdown() || g_WorldShutdownSequenceComplete;
Game::lastSignal = -1;
#ifndef DARKFLAME_PLATFORM_WIN32
if (shouldShutdown)
@@ -1444,13 +1459,13 @@ void WorldShutdownSequence() {
if (!Game::logger) return;
LOG("Zone (%i) instance (%i) shutting down outside of main loop!", Game::server->GetZoneID(), instanceID);
LOG("Zone (%i) instance (%i) shutting down outside of main loop!", Game::server->GetZoneID(), g_InstanceID);
WorldShutdownProcess(Game::server->GetZoneID());
FinalizeShutdown();
}
void FinalizeShutdown() {
LOG("Shutdown complete, zone (%i), instance (%i)", Game::server->GetZoneID(), instanceID);
LOG("Shutdown complete, zone (%i), instance (%i)", Game::server->GetZoneID(), g_InstanceID);
//Delete our objects here:
Metrics::Clear();
@@ -1469,7 +1484,7 @@ void FinalizeShutdown() {
if (Game::logger) delete Game::logger;
Game::logger = nullptr;
worldShutdownSequenceComplete = true;
g_WorldShutdownSequenceComplete = true;
exit(EXIT_SUCCESS);
}

View File

@@ -1 +0,0 @@
/* See ModelNormalizeMigration.cpp for details */

View File

@@ -1 +0,0 @@
/* See ModelNormalizeMigration.cpp for details */

View File

@@ -12,4 +12,5 @@ namespace Game {
SystemAddress chatSysAddr;
EntityManager* entityManager = nullptr;
std::string projectVersion;
Game::signal_t lastSignal = 0;
}

View File

@@ -52,9 +52,9 @@ public:
virtual ~CppSQLite3Exception();
const int errorCode() { return mnErrCode; }
const int errorCode() const { return mnErrCode; }
const char* errorMessage() { return mpszErrMess; }
const char* errorMessage() const { return mpszErrMess; }
const char* what() const noexcept override { return mpszErrMess; }