Compare commits

...

12 Commits

Author SHA1 Message Date
David Markowitz
4930fb93b3 fix: backwards names 2025-04-25 16:55:31 -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
22 changed files with 280 additions and 128 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

@@ -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

@@ -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

@@ -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(earnerName);
bitstream.Write<uint64_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write<uint32_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write<uint16_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write<uint8_t>(0); // Packing / No way to know meaning because of not enough data.
bitstream.Write(missionEmailID);
bitstream.Write(earningPlayerID);
bitstream.Write(targetPlayerName);
}
bool ChatPackets::AchievementNotify::Deserialize(RakNet::BitStream& bitstream) {
bitstream.IgnoreBytes(13);
VALIDATE_READ(bitstream.Read(earnerName));
bitstream.IgnoreBytes(15);
VALIDATE_READ(bitstream.Read(missionEmailID));
VALIDATE_READ(bitstream.Read(earningPlayerID));
VALIDATE_READ(bitstream.Read(targetPlayerName));
return true;
}
void ChatPackets::TeamInviteInitialResponse::Serialize(RakNet::BitStream& bitstream) const {
bitstream.Write<uint8_t>(inviteFailedToSend);
bitstream.Write(playerName);
}
void ChatPackets::SendRoutedMsg(const LUBitStream& msg, const LWOOBJID targetID, const SystemAddress& sysAddr) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(targetID);
// Now write the actual packet
msg.WriteHeader(bitStream);
msg.Serialize(bitStream);
Game::server->Send(bitStream, sysAddr, sysAddr == UNASSIGNED_SYSTEM_ADDRESS);
}

View File

@@ -10,6 +10,8 @@ struct SystemAddress;
#include <string>
#include "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

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