mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-05-16 03:21:18 +00:00
Merge branch 'main' into leaderboards-again
This commit is contained in:
commit
6e959492db
@ -1,9 +1,10 @@
|
||||
set(DCHATSERVER_SOURCES
|
||||
"ChatIgnoreList.cpp"
|
||||
"ChatPacketHandler.cpp"
|
||||
"PlayerContainer.cpp"
|
||||
"ChatWebAPI.cpp"
|
||||
"JSONUtils.cpp"
|
||||
"PlayerContainer.cpp"
|
||||
"TeamContainer.cpp"
|
||||
)
|
||||
|
||||
add_executable(ChatServer "ChatServer.cpp")
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "StringifiedEnum.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "ChatPackets.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
|
||||
//Get from the packet which player we want to do something with:
|
||||
@ -447,7 +448,7 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
|
||||
|
||||
switch (channel) {
|
||||
case eChatChannel::TEAM: {
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
auto* team = TeamContainer::GetTeam(playerID);
|
||||
if (team == nullptr) return;
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
@ -563,400 +564,6 @@ void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const P
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
|
||||
void ChatPacketHandler::HandleTeamInvite(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID;
|
||||
LUWString invitedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(invitedPlayer);
|
||||
|
||||
const auto& player = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (!player) return;
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team == nullptr) {
|
||||
team = Game::playerContainer.CreateTeam(playerID);
|
||||
}
|
||||
|
||||
const auto& other = Game::playerContainer.GetPlayerData(invitedPlayer.GetAsString());
|
||||
|
||||
if (!other) return;
|
||||
|
||||
if (Game::playerContainer.GetTeam(other.playerID) != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() > 3) {
|
||||
// no more teams greater than 4
|
||||
|
||||
LOG("Someone tried to invite a 5th player to a team");
|
||||
return;
|
||||
}
|
||||
|
||||
SendTeamInvite(other, player);
|
||||
|
||||
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str());
|
||||
|
||||
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) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
char declined = 0;
|
||||
inStream.Read(declined);
|
||||
LWOOBJID leaderID = LWOOBJID_EMPTY;
|
||||
inStream.Read(leaderID);
|
||||
|
||||
LOG("Invite reponse received: %llu -> %llu (%d)", playerID, leaderID, declined);
|
||||
|
||||
if (declined) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(leaderID);
|
||||
|
||||
if (team == nullptr) {
|
||||
LOG("Failed to find team for leader (%llu)", leaderID);
|
||||
|
||||
team = Game::playerContainer.GetTeam(playerID);
|
||||
}
|
||||
|
||||
if (team == nullptr) {
|
||||
LOG("Failed to find team for player (%llu)", playerID);
|
||||
return;
|
||||
}
|
||||
|
||||
Game::playerContainer.AddMember(team, playerID);
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamLeave(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
LOG("(%llu) leaving team", playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
Game::playerContainer.RemoveMember(team, playerID, false, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamKick(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
LUWString kickedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(kickedPlayer);
|
||||
|
||||
|
||||
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str());
|
||||
|
||||
const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString());
|
||||
|
||||
LWOOBJID kickedId = LWOOBJID_EMPTY;
|
||||
|
||||
if (kicked) {
|
||||
kickedId = kicked.playerID;
|
||||
} else {
|
||||
kickedId = Game::playerContainer.GetId(kickedPlayer.string);
|
||||
}
|
||||
|
||||
if (kickedId == LWOOBJID_EMPTY) return;
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID || team->leaderID == kickedId) return;
|
||||
|
||||
Game::playerContainer.RemoveMember(team, kickedId, false, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamPromote(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
LUWString promotedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(promotedPlayer);
|
||||
|
||||
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str());
|
||||
|
||||
const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString());
|
||||
|
||||
if (!promoted) return;
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID) return;
|
||||
|
||||
Game::playerContainer.PromoteMember(team, promoted.playerID);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamLootOption(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
char option;
|
||||
inStream.Read(option);
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID) return;
|
||||
|
||||
team->lootFlag = option;
|
||||
|
||||
Game::playerContainer.TeamStatusUpdate(team);
|
||||
|
||||
Game::playerContainer.UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
const auto& data = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (team != nullptr && data) {
|
||||
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, false, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() <= 1 && !team->local) {
|
||||
Game::playerContainer.DisbandTeam(team, LWOOBJID_EMPTY, u"");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!team->local) {
|
||||
ChatPacketHandler::SendTeamSetLeader(data, team->leaderID);
|
||||
} else {
|
||||
ChatPacketHandler::SendTeamSetLeader(data, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
Game::playerContainer.TeamStatusUpdate(team);
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(data.playerName);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (memberId == playerID) continue;
|
||||
|
||||
const auto memberName = Game::playerContainer.GetName(memberId);
|
||||
|
||||
if (otherMember) {
|
||||
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, data.playerID, data.zoneID);
|
||||
}
|
||||
ChatPacketHandler::SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
|
||||
}
|
||||
|
||||
Game::playerContainer.UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE);
|
||||
|
||||
bitStream.Write(LUWString(sender.playerName.c_str()));
|
||||
bitStream.Write(sender.playerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM);
|
||||
|
||||
bitStream.Write(bLeaderIsFreeTrial);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write(ucResponseCode);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE);
|
||||
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_LEADER);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER);
|
||||
|
||||
bitStream.Write(bIsFreeTrial);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(bNoLootOnDeath);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
bitStream.Write1();
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER);
|
||||
|
||||
bitStream.Write(bDisband);
|
||||
bitStream.Write(bIsKicked);
|
||||
bitStream.Write(bIsLeaving);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const PlayerData& playerData, uint8_t notifyType, uint8_t isBestFriend) {
|
||||
/*chat notification is displayed if log in / out and friend is updated in friends list
|
||||
[u8] - update type
|
||||
|
@ -35,13 +35,13 @@ enum class eChatChannel : uint8_t {
|
||||
|
||||
|
||||
enum class eChatMessageResponseCode : uint8_t {
|
||||
SENT = 0,
|
||||
NOTONLINE,
|
||||
GENERALERROR,
|
||||
RECEIVEDNEWWHISPER,
|
||||
NOTFRIENDS,
|
||||
SENDERFREETRIAL,
|
||||
RECEIVERFREETRIAL,
|
||||
SENT = 0,
|
||||
NOTONLINE,
|
||||
GENERALERROR,
|
||||
RECEIVEDNEWWHISPER,
|
||||
NOTFRIENDS,
|
||||
SENDERFREETRIAL,
|
||||
RECEIVERFREETRIAL,
|
||||
};
|
||||
|
||||
namespace ChatPacketHandler {
|
||||
@ -52,33 +52,14 @@ namespace ChatPacketHandler {
|
||||
void HandleGMLevelUpdate(Packet* packet);
|
||||
void HandleWho(Packet* packet);
|
||||
void HandleShowAll(Packet* packet);
|
||||
|
||||
void HandleChatMessage(Packet* packet);
|
||||
void HandlePrivateChatMessage(Packet* packet);
|
||||
void SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode);
|
||||
|
||||
void HandleTeamInvite(Packet* packet);
|
||||
void HandleTeamInviteResponse(Packet* packet);
|
||||
void HandleTeamLeave(Packet* packet);
|
||||
void HandleTeamKick(Packet* packet);
|
||||
void HandleTeamPromote(Packet* packet);
|
||||
void HandleTeamLootOption(Packet* packet);
|
||||
void HandleTeamStatusRequest(Packet* packet);
|
||||
void 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);
|
||||
|
||||
//FriendData is the player we're SENDING this stuff to. Player is the friend that changed state.
|
||||
void SendFriendUpdate(const PlayerData& friendData, const PlayerData& playerData, uint8_t notifyType, uint8_t isBestFriend);
|
||||
|
||||
void SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode);
|
||||
void SendFriendRequest(const PlayerData& receiver, const PlayerData& sender);
|
||||
void SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready = 0U, uint8_t isBestFriendRequest = 0U);
|
||||
void SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful);
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "MessageType/World.h"
|
||||
#include "ChatIgnoreList.h"
|
||||
#include "StringifiedEnum.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
#include "Game.h"
|
||||
#include "Server.h"
|
||||
@ -95,7 +96,7 @@ int main(int argc, char** argv) {
|
||||
// seyup the chat api web server
|
||||
bool web_server_enabled = Game::config->GetValue("web_server_enabled") == "1";
|
||||
ChatWebAPI chatwebapi;
|
||||
if (web_server_enabled && !chatwebapi.Startup()){
|
||||
if (web_server_enabled && !chatwebapi.Startup()) {
|
||||
// if we want the web api and it fails to start, exit
|
||||
LOG("Failed to start web server, shutting down.");
|
||||
Database::Destroy("ChatServer");
|
||||
@ -197,6 +198,7 @@ int main(int argc, char** argv) {
|
||||
std::this_thread::sleep_until(t);
|
||||
}
|
||||
Game::playerContainer.Shutdown();
|
||||
TeamContainer::Shutdown();
|
||||
//Delete our objects here:
|
||||
Database::Destroy("ChatServer");
|
||||
delete Game::server;
|
||||
@ -234,7 +236,7 @@ void HandlePacket(Packet* packet) {
|
||||
break;
|
||||
|
||||
case MessageType::Chat::CREATE_TEAM:
|
||||
Game::playerContainer.CreateTeamServer(packet);
|
||||
TeamContainer::CreateTeamServer(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::GET_FRIENDS_LIST:
|
||||
@ -254,7 +256,7 @@ void HandlePacket(Packet* packet) {
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_GET_STATUS:
|
||||
ChatPacketHandler::HandleTeamStatusRequest(packet);
|
||||
TeamContainer::HandleTeamStatusRequest(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::ADD_FRIEND_REQUEST:
|
||||
@ -284,27 +286,27 @@ void HandlePacket(Packet* packet) {
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_INVITE:
|
||||
ChatPacketHandler::HandleTeamInvite(packet);
|
||||
TeamContainer::HandleTeamInvite(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_INVITE_RESPONSE:
|
||||
ChatPacketHandler::HandleTeamInviteResponse(packet);
|
||||
TeamContainer::HandleTeamInviteResponse(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_LEAVE:
|
||||
ChatPacketHandler::HandleTeamLeave(packet);
|
||||
TeamContainer::HandleTeamLeave(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_SET_LEADER:
|
||||
ChatPacketHandler::HandleTeamPromote(packet);
|
||||
TeamContainer::HandleTeamPromote(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_KICK:
|
||||
ChatPacketHandler::HandleTeamKick(packet);
|
||||
TeamContainer::HandleTeamKick(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::TEAM_SET_LOOT:
|
||||
ChatPacketHandler::HandleTeamLootOption(packet);
|
||||
TeamContainer::HandleTeamLootOption(packet);
|
||||
break;
|
||||
case MessageType::Chat::GMLEVEL_UPDATE:
|
||||
ChatPacketHandler::HandleGMLevelUpdate(packet);
|
||||
|
@ -28,7 +28,7 @@ typedef struct mg_http_message mg_http_message;
|
||||
|
||||
namespace {
|
||||
const char* json_content_type = "Content-Type: application/json\r\n";
|
||||
std::map<std::pair<eHTTPMethod, std::string>, WebAPIHTTPRoute> Routes {};
|
||||
std::map<std::pair<eHTTPMethod, std::string>, WebAPIHTTPRoute> Routes{};
|
||||
}
|
||||
|
||||
bool ValidateAuthentication(const mg_http_message* http_msg) {
|
||||
@ -54,7 +54,7 @@ void HandlePlayersRequest(HTTPReply& reply, std::string body) {
|
||||
}
|
||||
|
||||
void HandleTeamsRequest(HTTPReply& reply, std::string body) {
|
||||
const json data = Game::playerContainer.GetTeamContainer();
|
||||
const json data = TeamContainer::GetTeamContainer();
|
||||
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
|
||||
reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump();
|
||||
}
|
||||
@ -106,7 +106,7 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms
|
||||
std::string body(http_msg->body.buf, http_msg->body.len);
|
||||
|
||||
|
||||
const auto routeItr = Routes.find({method, uri});
|
||||
const auto routeItr = Routes.find({ method, uri });
|
||||
|
||||
if (routeItr != Routes.end()) {
|
||||
const auto& [_, route] = *routeItr;
|
||||
@ -122,11 +122,11 @@ void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_ms
|
||||
|
||||
void HandleRequests(mg_connection* connection, int request, void* request_data) {
|
||||
switch (request) {
|
||||
case MG_EV_HTTP_MSG:
|
||||
HandleHTTPMessage(connection, static_cast<mg_http_message*>(request_data));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
case MG_EV_HTTP_MSG:
|
||||
HandleHTTPMessage(connection, static_cast<mg_http_message*>(request_data));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,19 +172,19 @@ bool ChatWebAPI::Startup() {
|
||||
.path = v1_route + "players",
|
||||
.method = eHTTPMethod::GET,
|
||||
.handle = HandlePlayersRequest
|
||||
});
|
||||
});
|
||||
|
||||
RegisterHTTPRoutes({
|
||||
.path = v1_route + "teams",
|
||||
.method = eHTTPMethod::GET,
|
||||
.handle = HandleTeamsRequest
|
||||
});
|
||||
});
|
||||
|
||||
RegisterHTTPRoutes({
|
||||
.path = v1_route + "announce",
|
||||
.method = eHTTPMethod::POST,
|
||||
.handle = HandleAnnounceRequest
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -18,19 +18,12 @@ void to_json(json& data, const PlayerData& playerData) {
|
||||
|
||||
void to_json(json& data, const PlayerContainer& playerContainer) {
|
||||
data = json::array();
|
||||
for(auto& playerData : playerContainer.GetAllPlayers()) {
|
||||
for (auto& playerData : playerContainer.GetAllPlayers()) {
|
||||
if (playerData.first == LWOOBJID_EMPTY) continue;
|
||||
data.push_back(playerData.second);
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(json& data, const TeamContainer& teamContainer) {
|
||||
for (auto& teamData : Game::playerContainer.GetTeams()) {
|
||||
if (!teamData) continue;
|
||||
data.push_back(*teamData);
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(json& data, const TeamData& teamData) {
|
||||
data["id"] = teamData.teamID;
|
||||
data["loot_flag"] = teamData.lootFlag;
|
||||
@ -48,6 +41,13 @@ void to_json(json& data, const TeamData& teamData) {
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::to_json(json& data, const TeamContainer::Data& teamContainer) {
|
||||
for (auto& teamData : TeamContainer::GetTeams()) {
|
||||
if (!teamData) continue;
|
||||
data.push_back(*teamData);
|
||||
}
|
||||
}
|
||||
|
||||
std::string JSONUtils::CheckRequiredData(const json& data, const std::vector<std::string>& requiredData) {
|
||||
json check;
|
||||
check["error"] = json::array();
|
||||
|
@ -3,12 +3,18 @@
|
||||
|
||||
#include "json_fwd.hpp"
|
||||
#include "PlayerContainer.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
/* Remember, to_json needs to be in the same namespace as the class its located in */
|
||||
|
||||
void to_json(nlohmann::json& data, const PlayerData& playerData);
|
||||
void to_json(nlohmann::json& data, const PlayerContainer& playerContainer);
|
||||
void to_json(nlohmann::json& data, const TeamContainer& teamData);
|
||||
void to_json(nlohmann::json& data, const TeamData& teamData);
|
||||
|
||||
namespace TeamContainer {
|
||||
void to_json(nlohmann::json& data, const TeamContainer::Data& teamData);
|
||||
};
|
||||
|
||||
namespace JSONUtils {
|
||||
// check required data for reqeust
|
||||
std::string CheckRequiredData(const nlohmann::json& data, const std::vector<std::string>& requiredData);
|
||||
|
@ -12,6 +12,7 @@
|
||||
#include "ChatPackets.h"
|
||||
#include "dConfig.h"
|
||||
#include "MessageType/Chat.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
void PlayerContainer::Initialize() {
|
||||
m_MaxNumberOfBestFriends =
|
||||
@ -99,7 +100,7 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
|
||||
if (fd) ChatPacketHandler::SendFriendUpdate(fd, player, 0, fr.isBestFriend);
|
||||
}
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
auto* team = TeamContainer::GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
const auto memberName = GeneralUtils::UTF8ToUTF16(player.playerName);
|
||||
@ -109,7 +110,7 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 });
|
||||
TeamContainer::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 });
|
||||
}
|
||||
}
|
||||
|
||||
@ -140,40 +141,6 @@ void PlayerContainer::MuteUpdate(Packet* packet) {
|
||||
BroadcastMuteUpdate(playerID, expire);
|
||||
}
|
||||
|
||||
void PlayerContainer::CreateTeamServer(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID;
|
||||
inStream.Read(playerID);
|
||||
size_t membersSize = 0;
|
||||
inStream.Read(membersSize);
|
||||
|
||||
if (membersSize >= 4) {
|
||||
LOG("Tried to create a team with more than 4 players");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<LWOOBJID> members;
|
||||
|
||||
members.reserve(membersSize);
|
||||
|
||||
for (size_t i = 0; i < membersSize; i++) {
|
||||
LWOOBJID member;
|
||||
inStream.Read(member);
|
||||
members.push_back(member);
|
||||
}
|
||||
|
||||
LWOZONEID zoneId;
|
||||
|
||||
inStream.Read(zoneId);
|
||||
|
||||
auto* team = CreateLocalTeam(members);
|
||||
|
||||
if (team != nullptr) {
|
||||
team->zoneId = zoneId;
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::GM_MUTE);
|
||||
@ -184,218 +151,6 @@ void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
|
||||
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
|
||||
}
|
||||
|
||||
TeamData* PlayerContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
|
||||
if (members.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TeamData* newTeam = nullptr;
|
||||
|
||||
for (const auto member : members) {
|
||||
auto* team = GetTeam(member);
|
||||
|
||||
if (team != nullptr) {
|
||||
RemoveMember(team, member, false, false, true);
|
||||
}
|
||||
|
||||
if (newTeam == nullptr) {
|
||||
newTeam = CreateTeam(member, true);
|
||||
} else {
|
||||
AddMember(newTeam, member);
|
||||
}
|
||||
}
|
||||
|
||||
newTeam->lootFlag = 1;
|
||||
|
||||
TeamStatusUpdate(newTeam);
|
||||
|
||||
return newTeam;
|
||||
}
|
||||
|
||||
TeamData* PlayerContainer::CreateTeam(LWOOBJID leader, bool local) {
|
||||
auto* team = new TeamData();
|
||||
|
||||
team->teamID = ++m_TeamIDCounter;
|
||||
team->leaderID = leader;
|
||||
team->local = local;
|
||||
|
||||
GetTeamsMut().push_back(team);
|
||||
|
||||
AddMember(team, leader);
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
TeamData* PlayerContainer::GetTeam(LWOOBJID playerID) {
|
||||
for (auto* team : GetTeams()) {
|
||||
if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue;
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
|
||||
if (team->memberIDs.size() >= 4) {
|
||||
LOG("Tried to add player to team that already had 4 players");
|
||||
const auto& player = GetPlayerData(playerID);
|
||||
if (!player) return;
|
||||
ChatPackets::SendSystemMessage(player.worldServerSysAddr, u"The teams is full! You have not been added to a team!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
|
||||
|
||||
if (index != team->memberIDs.end()) return;
|
||||
|
||||
team->memberIDs.push_back(playerID);
|
||||
|
||||
const auto& leader = GetPlayerData(team->leaderID);
|
||||
const auto& member = GetPlayerData(playerID);
|
||||
|
||||
if (!leader || !member) return;
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
|
||||
const auto memberName = GeneralUtils::UTF8ToUTF16(member.playerName);
|
||||
|
||||
ChatPacketHandler::SendTeamInviteConfirm(member, false, leader.playerID, leader.zoneID, team->lootFlag, 0, 0, leaderName);
|
||||
|
||||
if (!team->local) {
|
||||
ChatPacketHandler::SendTeamSetLeader(member, leader.playerID);
|
||||
} else {
|
||||
ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (otherMember == member) continue;
|
||||
|
||||
const auto otherMemberName = GetName(memberId);
|
||||
|
||||
ChatPacketHandler::SendTeamAddPlayer(member, false, team->local, false, memberId, otherMemberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
|
||||
|
||||
if (otherMember) {
|
||||
ChatPacketHandler::SendTeamAddPlayer(otherMember, false, team->local, false, member.playerID, memberName, member.zoneID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::RemoveMember(TeamData* team, LWOOBJID causingPlayerID, bool disband, bool kicked, bool leaving, bool silent) {
|
||||
LOG_DEBUG("Player %llu is leaving team %i", causingPlayerID, team->teamID);
|
||||
const auto index = std::ranges::find(team->memberIDs, causingPlayerID);
|
||||
|
||||
if (index == team->memberIDs.end()) return;
|
||||
|
||||
team->memberIDs.erase(index);
|
||||
|
||||
const auto& member = 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, 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);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) {
|
||||
team->leaderID = newLeader;
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
ChatPacketHandler::SendTeamSetLeader(otherMember, newLeader);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::DisbandTeam(TeamData* team, const 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;
|
||||
|
||||
ChatPacketHandler::SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
|
||||
ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, causingPlayerID, causingPlayerName);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, true);
|
||||
|
||||
GetTeamsMut().erase(index);
|
||||
|
||||
delete team;
|
||||
}
|
||||
|
||||
void PlayerContainer::TeamStatusUpdate(TeamData* team) {
|
||||
const auto index = std::find(GetTeams().begin(), GetTeams().end(), team);
|
||||
|
||||
if (index == GetTeams().end()) return;
|
||||
|
||||
const auto& leader = GetPlayerData(team->leaderID);
|
||||
|
||||
if (!leader) return;
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
if (!team->local) {
|
||||
ChatPacketHandler::SendTeamStatus(otherMember, team->leaderID, leader.zoneID, team->lootFlag, 0, leaderName);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
|
||||
void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
|
||||
|
||||
bitStream.Write(team->teamID);
|
||||
bitStream.Write(deleteTeam);
|
||||
|
||||
if (!deleteTeam) {
|
||||
bitStream.Write(team->lootFlag);
|
||||
bitStream.Write<char>(team->memberIDs.size());
|
||||
for (const auto memberID : team->memberIDs) {
|
||||
bitStream.Write(memberID);
|
||||
}
|
||||
}
|
||||
|
||||
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
|
||||
}
|
||||
|
||||
std::u16string PlayerContainer::GetName(LWOOBJID playerID) {
|
||||
const auto iter = m_Names.find(playerID);
|
||||
|
||||
@ -444,5 +199,4 @@ void PlayerContainer::Shutdown() {
|
||||
Database::Get()->UpdateActivityLog(id, eActivityType::PlayerLoggedOut, playerData.zoneID.GetMapID());
|
||||
m_Players.erase(m_Players.begin());
|
||||
}
|
||||
for (auto* team : GetTeams()) if (team) delete team;
|
||||
}
|
||||
|
@ -11,10 +11,6 @@ enum class eGameMasterLevel : uint8_t;
|
||||
|
||||
struct TeamData;
|
||||
|
||||
struct TeamContainer {
|
||||
std::vector<TeamData*> mTeams;
|
||||
};
|
||||
|
||||
struct IgnoreData {
|
||||
IgnoreData(const std::string& name, const LWOOBJID& id) : playerName{ name }, playerId{ id } {}
|
||||
inline bool operator==(const std::string& other) const noexcept {
|
||||
@ -73,7 +69,6 @@ public:
|
||||
void ScheduleRemovePlayer(Packet* packet);
|
||||
void RemovePlayer(const LWOOBJID playerID);
|
||||
void MuteUpdate(Packet* packet);
|
||||
void CreateTeamServer(Packet* packet);
|
||||
void BroadcastMuteUpdate(LWOOBJID player, time_t time);
|
||||
void Shutdown();
|
||||
|
||||
@ -81,34 +76,19 @@ public:
|
||||
const PlayerData& GetPlayerData(const std::string& playerName);
|
||||
PlayerData& GetPlayerDataMutable(const LWOOBJID& playerID);
|
||||
PlayerData& GetPlayerDataMutable(const std::string& playerName);
|
||||
std::u16string GetName(LWOOBJID playerID);
|
||||
LWOOBJID GetId(const std::u16string& playerName);
|
||||
void Update(const float deltaTime);
|
||||
|
||||
uint32_t GetPlayerCount() { return m_PlayerCount; };
|
||||
uint32_t GetSimCount() { return m_SimCount; };
|
||||
const std::map<LWOOBJID, PlayerData>& GetAllPlayers() const { return m_Players; };
|
||||
|
||||
TeamData* CreateLocalTeam(std::vector<LWOOBJID> members);
|
||||
TeamData* CreateTeam(LWOOBJID leader, bool local = false);
|
||||
TeamData* GetTeam(LWOOBJID playerID);
|
||||
void AddMember(TeamData* team, LWOOBJID playerID);
|
||||
void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false);
|
||||
void PromoteMember(TeamData* team, LWOOBJID newLeader);
|
||||
void DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName);
|
||||
void TeamStatusUpdate(TeamData* team);
|
||||
void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam);
|
||||
std::u16string GetName(LWOOBJID playerID);
|
||||
LWOOBJID GetId(const std::u16string& playerName);
|
||||
uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; }
|
||||
uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; }
|
||||
const TeamContainer& GetTeamContainer() { return m_TeamContainer; }
|
||||
std::vector<TeamData*>& GetTeamsMut() { return m_TeamContainer.mTeams; };
|
||||
const std::vector<TeamData*>& GetTeams() { return GetTeamsMut(); };
|
||||
|
||||
void Update(const float deltaTime);
|
||||
bool PlayerBeingRemoved(const LWOOBJID playerID) { return m_PlayersToRemove.contains(playerID); }
|
||||
|
||||
private:
|
||||
LWOOBJID m_TeamIDCounter = 0;
|
||||
std::map<LWOOBJID, PlayerData> m_Players;
|
||||
TeamContainer m_TeamContainer{};
|
||||
std::unordered_map<LWOOBJID, std::u16string> m_Names;
|
||||
std::map<LWOOBJID, float> m_PlayersToRemove;
|
||||
uint32_t m_MaxNumberOfBestFriends = 5;
|
||||
|
669
dChatServer/TeamContainer.cpp
Normal file
669
dChatServer/TeamContainer.cpp
Normal file
@ -0,0 +1,669 @@
|
||||
#include "TeamContainer.h"
|
||||
|
||||
#include "ChatPackets.h"
|
||||
|
||||
#include "MessageType/Chat.h"
|
||||
#include "MessageType/Game.h"
|
||||
|
||||
#include "ChatPacketHandler.h"
|
||||
#include "PlayerContainer.h"
|
||||
|
||||
namespace {
|
||||
TeamContainer::Data g_TeamContainer{};
|
||||
LWOOBJID g_TeamIDCounter = 0;
|
||||
}
|
||||
|
||||
const TeamContainer::Data& TeamContainer::GetTeamContainer() {
|
||||
return g_TeamContainer;
|
||||
}
|
||||
|
||||
std::vector<TeamData*>& TeamContainer::GetTeamsMut() {
|
||||
return g_TeamContainer.mTeams;
|
||||
}
|
||||
|
||||
const std::vector<TeamData*>& TeamContainer::GetTeams() {
|
||||
return GetTeamsMut();
|
||||
}
|
||||
|
||||
void TeamContainer::Shutdown() {
|
||||
for (auto* team : g_TeamContainer.mTeams) if (team) delete team;
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamInvite(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID;
|
||||
LUWString invitedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(invitedPlayer);
|
||||
|
||||
const auto& player = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (!player) return;
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
|
||||
if (team == nullptr) {
|
||||
team = CreateTeam(playerID);
|
||||
}
|
||||
|
||||
const auto& other = Game::playerContainer.GetPlayerData(invitedPlayer.GetAsString());
|
||||
|
||||
if (!other) return;
|
||||
|
||||
if (GetTeam(other.playerID) != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() > 3) {
|
||||
// no more teams greater than 4
|
||||
|
||||
LOG("Someone tried to invite a 5th player to a team");
|
||||
return;
|
||||
}
|
||||
|
||||
SendTeamInvite(other, player);
|
||||
|
||||
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str());
|
||||
|
||||
bool failed = false;
|
||||
for (const auto& ignore : other.ignoredPlayers) {
|
||||
if (ignore.playerId == player.playerID) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ChatPackets::TeamInviteInitialResponse response{};
|
||||
response.inviteFailedToSend = failed;
|
||||
response.playerName = invitedPlayer.string;
|
||||
ChatPackets::SendRoutedMsg(response, playerID, player.worldServerSysAddr);
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamInviteResponse(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
char declined = 0;
|
||||
inStream.Read(declined);
|
||||
LWOOBJID leaderID = LWOOBJID_EMPTY;
|
||||
inStream.Read(leaderID);
|
||||
|
||||
LOG("Invite reponse received: %llu -> %llu (%d)", playerID, leaderID, declined);
|
||||
|
||||
if (declined) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* team = GetTeam(leaderID);
|
||||
|
||||
if (team == nullptr) {
|
||||
LOG("Failed to find team for leader (%llu)", leaderID);
|
||||
|
||||
team = GetTeam(playerID);
|
||||
}
|
||||
|
||||
if (team == nullptr) {
|
||||
LOG("Failed to find team for player (%llu)", playerID);
|
||||
return;
|
||||
}
|
||||
|
||||
AddMember(team, playerID);
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamLeave(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
|
||||
LOG("(%llu) leaving team", playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
RemoveMember(team, playerID, false, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamKick(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
LUWString kickedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(kickedPlayer);
|
||||
|
||||
|
||||
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str());
|
||||
|
||||
const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString());
|
||||
|
||||
LWOOBJID kickedId = LWOOBJID_EMPTY;
|
||||
|
||||
if (kicked) {
|
||||
kickedId = kicked.playerID;
|
||||
} else {
|
||||
kickedId = Game::playerContainer.GetId(kickedPlayer.string);
|
||||
}
|
||||
|
||||
if (kickedId == LWOOBJID_EMPTY) return;
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID || team->leaderID == kickedId) return;
|
||||
|
||||
RemoveMember(team, kickedId, false, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamPromote(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
LUWString promotedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(promotedPlayer);
|
||||
|
||||
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str());
|
||||
|
||||
const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString());
|
||||
|
||||
if (!promoted) return;
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID) return;
|
||||
|
||||
PromoteMember(team, promoted.playerID);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamLootOption(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
char option;
|
||||
inStream.Read(option);
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID) return;
|
||||
|
||||
team->lootFlag = option;
|
||||
|
||||
TeamStatusUpdate(team);
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::HandleTeamStatusRequest(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
|
||||
auto* team = GetTeam(playerID);
|
||||
const auto& data = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (team != nullptr && data) {
|
||||
LOG_DEBUG("Player %llu is requesting team status", playerID);
|
||||
if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
|
||||
RemoveMember(team, playerID, false, false, false, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() <= 1 && !team->local) {
|
||||
DisbandTeam(team, LWOOBJID_EMPTY, u"");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!team->local) {
|
||||
SendTeamSetLeader(data, team->leaderID);
|
||||
} else {
|
||||
SendTeamSetLeader(data, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
TeamStatusUpdate(team);
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(data.playerName);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (memberId == playerID) continue;
|
||||
|
||||
const auto memberName = Game::playerContainer.GetName(memberId);
|
||||
|
||||
if (otherMember) {
|
||||
SendTeamSetOffWorldFlag(otherMember, data.playerID, data.zoneID);
|
||||
}
|
||||
SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE);
|
||||
|
||||
bitStream.Write(LUWString(sender.playerName.c_str()));
|
||||
bitStream.Write(sender.playerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM);
|
||||
|
||||
bitStream.Write(bLeaderIsFreeTrial);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write(ucResponseCode);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE);
|
||||
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_LEADER);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER);
|
||||
|
||||
bitStream.Write(bIsFreeTrial);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(bNoLootOnDeath);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
bitStream.Write1();
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER);
|
||||
|
||||
bitStream.Write(bDisband);
|
||||
bitStream.Write(bIsKicked);
|
||||
bitStream.Write(bIsLeaving);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void TeamContainer::CreateTeamServer(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID;
|
||||
inStream.Read(playerID);
|
||||
size_t membersSize = 0;
|
||||
inStream.Read(membersSize);
|
||||
|
||||
if (membersSize >= 4) {
|
||||
LOG("Tried to create a team with more than 4 players");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<LWOOBJID> members;
|
||||
|
||||
members.reserve(membersSize);
|
||||
|
||||
for (size_t i = 0; i < membersSize; i++) {
|
||||
LWOOBJID member;
|
||||
inStream.Read(member);
|
||||
members.push_back(member);
|
||||
}
|
||||
|
||||
LWOZONEID zoneId;
|
||||
|
||||
inStream.Read(zoneId);
|
||||
|
||||
auto* team = CreateLocalTeam(members);
|
||||
|
||||
if (team != nullptr) {
|
||||
team->zoneId = zoneId;
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
TeamData* TeamContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
|
||||
if (members.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TeamData* newTeam = nullptr;
|
||||
|
||||
for (const auto member : members) {
|
||||
auto* team = GetTeam(member);
|
||||
|
||||
if (team != nullptr) {
|
||||
RemoveMember(team, member, false, false, true);
|
||||
}
|
||||
|
||||
if (newTeam == nullptr) {
|
||||
newTeam = CreateTeam(member, true);
|
||||
} else {
|
||||
AddMember(newTeam, member);
|
||||
}
|
||||
}
|
||||
|
||||
newTeam->lootFlag = 1;
|
||||
|
||||
TeamStatusUpdate(newTeam);
|
||||
|
||||
return newTeam;
|
||||
}
|
||||
|
||||
TeamData* TeamContainer::CreateTeam(LWOOBJID leader, bool local) {
|
||||
auto* team = new TeamData();
|
||||
|
||||
team->teamID = ++g_TeamIDCounter;
|
||||
team->leaderID = leader;
|
||||
team->local = local;
|
||||
|
||||
GetTeamsMut().push_back(team);
|
||||
|
||||
AddMember(team, leader);
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
TeamData* TeamContainer::GetTeam(LWOOBJID playerID) {
|
||||
for (auto* team : GetTeams()) {
|
||||
if (std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID) == team->memberIDs.end()) continue;
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TeamContainer::AddMember(TeamData* team, LWOOBJID playerID) {
|
||||
if (team->memberIDs.size() >= 4) {
|
||||
LOG("Tried to add player to team that already had 4 players");
|
||||
const auto& player = Game::playerContainer.GetPlayerData(playerID);
|
||||
if (!player) return;
|
||||
ChatPackets::SendSystemMessage(player.worldServerSysAddr, u"The teams is full! You have not been added to a team!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
|
||||
|
||||
if (index != team->memberIDs.end()) return;
|
||||
|
||||
team->memberIDs.push_back(playerID);
|
||||
|
||||
const auto& leader = Game::playerContainer.GetPlayerData(team->leaderID);
|
||||
const auto& member = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (!leader || !member) return;
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
|
||||
const auto memberName = GeneralUtils::UTF8ToUTF16(member.playerName);
|
||||
|
||||
SendTeamInviteConfirm(member, false, leader.playerID, leader.zoneID, team->lootFlag, 0, 0, leaderName);
|
||||
|
||||
if (!team->local) {
|
||||
SendTeamSetLeader(member, leader.playerID);
|
||||
} else {
|
||||
SendTeamSetLeader(member, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (otherMember == member) continue;
|
||||
|
||||
const auto otherMemberName = Game::playerContainer.GetName(memberId);
|
||||
|
||||
SendTeamAddPlayer(member, false, team->local, false, memberId, otherMemberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
|
||||
|
||||
if (otherMember) {
|
||||
SendTeamAddPlayer(otherMember, false, team->local, false, member.playerID, memberName, member.zoneID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::RemoveMember(TeamData* team, LWOOBJID causingPlayerID, bool disband, bool kicked, bool leaving, bool silent) {
|
||||
LOG_DEBUG("Player %llu is leaving team %i", causingPlayerID, team->teamID);
|
||||
const auto index = std::ranges::find(team->memberIDs, causingPlayerID);
|
||||
|
||||
if (index == team->memberIDs.end()) return;
|
||||
|
||||
team->memberIDs.erase(index);
|
||||
|
||||
const auto& member = Game::playerContainer.GetPlayerData(causingPlayerID);
|
||||
|
||||
const auto causingMemberName = Game::playerContainer.GetName(causingPlayerID);
|
||||
|
||||
if (member && !silent) {
|
||||
SendTeamRemovePlayer(member, disband, kicked, leaving, team->local, LWOOBJID_EMPTY, causingPlayerID, causingMemberName);
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() <= 1) {
|
||||
DisbandTeam(team, causingPlayerID, causingMemberName);
|
||||
} else /* team has enough members to be a team still */ {
|
||||
team->leaderID = (causingPlayerID == team->leaderID) ? team->memberIDs[0] : team->leaderID;
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
if (silent && memberId == causingPlayerID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
SendTeamRemovePlayer(otherMember, disband, kicked, leaving, team->local, team->leaderID, causingPlayerID, causingMemberName);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) {
|
||||
team->leaderID = newLeader;
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
SendTeamSetLeader(otherMember, newLeader);
|
||||
}
|
||||
}
|
||||
|
||||
void TeamContainer::DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName) {
|
||||
const auto index = std::ranges::find(GetTeams(), team);
|
||||
|
||||
if (index == GetTeams().end()) return;
|
||||
LOG_DEBUG("Disbanding team %i", (*index)->teamID);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
|
||||
SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, causingPlayerID, causingPlayerName);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, true);
|
||||
|
||||
GetTeamsMut().erase(index);
|
||||
|
||||
delete team;
|
||||
}
|
||||
|
||||
void TeamContainer::TeamStatusUpdate(TeamData* team) {
|
||||
const auto index = std::find(GetTeams().begin(), GetTeams().end(), team);
|
||||
|
||||
if (index == GetTeams().end()) return;
|
||||
|
||||
const auto& leader = Game::playerContainer.GetPlayerData(team->leaderID);
|
||||
|
||||
if (!leader) return;
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
if (!team->local) {
|
||||
SendTeamStatus(otherMember, team->leaderID, leader.zoneID, team->lootFlag, 0, leaderName);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
|
||||
void TeamContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
|
||||
|
||||
bitStream.Write(team->teamID);
|
||||
bitStream.Write(deleteTeam);
|
||||
|
||||
if (!deleteTeam) {
|
||||
bitStream.Write(team->lootFlag);
|
||||
bitStream.Write<char>(team->memberIDs.size());
|
||||
for (const auto memberID : team->memberIDs) {
|
||||
bitStream.Write(memberID);
|
||||
}
|
||||
}
|
||||
|
||||
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
|
||||
}
|
59
dChatServer/TeamContainer.h
Normal file
59
dChatServer/TeamContainer.h
Normal file
@ -0,0 +1,59 @@
|
||||
// Darkflame Universe
|
||||
// Copyright 2025
|
||||
|
||||
#ifndef TEAMCONTAINER_H
|
||||
#define TEAMCONTAINER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "dCommonVars.h"
|
||||
|
||||
struct Packet;
|
||||
struct PlayerData;
|
||||
struct TeamData;
|
||||
|
||||
namespace TeamContainer {
|
||||
struct Data {
|
||||
std::vector<TeamData*> mTeams;
|
||||
};
|
||||
|
||||
void Shutdown();
|
||||
|
||||
void HandleTeamInvite(Packet* packet);
|
||||
void HandleTeamInviteResponse(Packet* packet);
|
||||
void HandleTeamLeave(Packet* packet);
|
||||
void HandleTeamKick(Packet* packet);
|
||||
void HandleTeamPromote(Packet* packet);
|
||||
void HandleTeamLootOption(Packet* packet);
|
||||
void HandleTeamStatusRequest(Packet* packet);
|
||||
|
||||
void SendTeamInvite(const PlayerData& receiver, const PlayerData& sender);
|
||||
void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName);
|
||||
void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName);
|
||||
void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID);
|
||||
void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID);
|
||||
|
||||
/* Sends a message to the provided `receiver` with information about the updated team. If `i64LeaderID` is not LWOOBJID_EMPTY, the client will update the leader to that new playerID. */
|
||||
void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName);
|
||||
void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);
|
||||
|
||||
void CreateTeamServer(Packet* packet);
|
||||
|
||||
TeamData* CreateLocalTeam(std::vector<LWOOBJID> members);
|
||||
TeamData* CreateTeam(LWOOBJID leader, bool local = false);
|
||||
TeamData* GetTeam(LWOOBJID playerID);
|
||||
void AddMember(TeamData* team, LWOOBJID playerID);
|
||||
void RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent = false);
|
||||
void PromoteMember(TeamData* team, LWOOBJID newLeader);
|
||||
void DisbandTeam(TeamData* team, const LWOOBJID causingPlayerID, const std::u16string& causingPlayerName);
|
||||
void TeamStatusUpdate(TeamData* team);
|
||||
void UpdateTeamsOnWorld(TeamData* team, bool deleteTeam);
|
||||
|
||||
const TeamContainer::Data& GetTeamContainer();
|
||||
std::vector<TeamData*>& GetTeamsMut();
|
||||
const std::vector<TeamData*>& GetTeams();
|
||||
};
|
||||
|
||||
#endif //!TEAMCONTAINER_H
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include "Database.h"
|
||||
#include "Game.h"
|
||||
#include "Sd0.h"
|
||||
#include "ZCompression.h"
|
||||
#include "Logger.h"
|
||||
|
||||
@ -44,10 +45,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[ZCompression::MAX_SD0_CHUNK_SIZE]);
|
||||
std::unique_ptr<uint8_t[]> uncompressedChunk(new uint8_t[Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE]);
|
||||
int32_t err{};
|
||||
int32_t actualUncompressedSize = ZCompression::Decompress(
|
||||
compressedChunk.get(), chunkSize, uncompressedChunk.get(), ZCompression::MAX_SD0_CHUNK_SIZE, err);
|
||||
compressedChunk.get(), chunkSize, uncompressedChunk.get(), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err);
|
||||
|
||||
if (actualUncompressedSize != -1) {
|
||||
uint32_t previousSize = completeUncompressedModel.size();
|
||||
@ -117,7 +118,7 @@ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
|
||||
}
|
||||
|
||||
std::string outputString(sd0ConvertedModel.get(), oldLxfmlSizeWithHeader);
|
||||
std::istringstream outputStringStream(outputString);
|
||||
std::stringstream outputStringStream(outputString);
|
||||
|
||||
try {
|
||||
Database::Get()->UpdateUgcModelData(model.id, outputStringStream);
|
||||
|
@ -16,6 +16,9 @@ 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.
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef _DEBUG
|
||||
# define DluAssert(expression) assert(expression)
|
||||
# define DluAssert(expression) do { assert(expression) } while(0)
|
||||
#else
|
||||
# define DluAssert(expression)
|
||||
#endif
|
||||
|
@ -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 {
|
||||
|
115
dCommon/Lxfml.cpp
Normal file
115
dCommon/Lxfml.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
#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;
|
||||
}
|
23
dCommon/Lxfml.h
Normal file
23
dCommon/Lxfml.h
Normal file
@ -0,0 +1,23 @@
|
||||
// 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
|
150
dCommon/Sd0.cpp
Normal file
150
dCommon/Sd0.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
#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;
|
||||
}
|
42
dCommon/Sd0.h
Normal file
42
dCommon/Sd0.h
Normal file
@ -0,0 +1,42 @@
|
||||
// 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
|
37
dCommon/TinyXmlUtils.cpp
Normal file
37
dCommon/TinyXmlUtils.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#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;
|
||||
}
|
66
dCommon/TinyXmlUtils.h
Normal file
66
dCommon/TinyXmlUtils.h
Normal file
@ -0,0 +1,66 @@
|
||||
// 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
|
@ -8,11 +8,5 @@ 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;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "Pack.h"
|
||||
|
||||
#include "BinaryIO.h"
|
||||
#include "Sd0.h"
|
||||
#include "ZCompression.h"
|
||||
|
||||
Pack::Pack(const std::filesystem::path& filePath) {
|
||||
@ -106,7 +107,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), ZCompression::MAX_SD0_CHUNK_SIZE, err);
|
||||
currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err);
|
||||
|
||||
free(chunk);
|
||||
}
|
||||
|
@ -102,7 +102,6 @@ DEFINE_TABLE_STORAGE(CDScriptComponentTable);
|
||||
DEFINE_TABLE_STORAGE(CDSkillBehaviorTable);
|
||||
DEFINE_TABLE_STORAGE(CDTamingBuildPuzzleTable);
|
||||
DEFINE_TABLE_STORAGE(CDVendorComponentTable);
|
||||
DEFINE_TABLE_STORAGE(CDZoneTableTable);
|
||||
|
||||
void CDClientManager::LoadValuesFromDatabase() {
|
||||
if (!CDClientDatabase::isConnected) {
|
||||
@ -149,7 +148,7 @@ void CDClientManager::LoadValuesFromDatabase() {
|
||||
CDSkillBehaviorTable::Instance().LoadValuesFromDatabase();
|
||||
CDTamingBuildPuzzleTable::Instance().LoadValuesFromDatabase();
|
||||
CDVendorComponentTable::Instance().LoadValuesFromDatabase();
|
||||
CDZoneTableTable::Instance().LoadValuesFromDatabase();
|
||||
CDZoneTableTable::LoadValuesFromDatabase();
|
||||
}
|
||||
|
||||
void CDClientManager::LoadValuesFromDefaults() {
|
||||
|
@ -1,67 +1,53 @@
|
||||
#include "CDZoneTableTable.h"
|
||||
|
||||
void CDZoneTableTable::LoadValuesFromDatabase() {
|
||||
namespace CDZoneTableTable {
|
||||
Table entries;
|
||||
|
||||
// First, get the size of the table
|
||||
uint32_t size = 0;
|
||||
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM ZoneTable");
|
||||
while (!tableSize.eof()) {
|
||||
size = tableSize.getIntField(0, 0);
|
||||
void LoadValuesFromDatabase() {
|
||||
// Get the data from the database
|
||||
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM ZoneTable");
|
||||
while (!tableData.eof()) {
|
||||
CDZoneTable entry;
|
||||
entry.zoneID = tableData.getIntField("zoneID", -1);
|
||||
entry.locStatus = tableData.getIntField("locStatus", -1);
|
||||
entry.zoneName = tableData.getStringField("zoneName", "");
|
||||
entry.scriptID = tableData.getIntField("scriptID", -1);
|
||||
entry.ghostdistance_min = tableData.getFloatField("ghostdistance_min", -1.0f);
|
||||
entry.ghostdistance = tableData.getFloatField("ghostdistance", -1.0f);
|
||||
entry.population_soft_cap = tableData.getIntField("population_soft_cap", -1);
|
||||
entry.population_hard_cap = tableData.getIntField("population_hard_cap", -1);
|
||||
UNUSED(entry.DisplayDescription = tableData.getStringField("DisplayDescription", ""));
|
||||
UNUSED(entry.mapFolder = tableData.getStringField("mapFolder", ""));
|
||||
entry.smashableMinDistance = tableData.getFloatField("smashableMinDistance", -1.0f);
|
||||
entry.smashableMaxDistance = tableData.getFloatField("smashableMaxDistance", -1.0f);
|
||||
UNUSED(entry.mixerProgram = tableData.getStringField("mixerProgram", ""));
|
||||
UNUSED(entry.clientPhysicsFramerate = tableData.getStringField("clientPhysicsFramerate", ""));
|
||||
entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", "");
|
||||
entry.zoneControlTemplate = tableData.getIntField("zoneControlTemplate", -1);
|
||||
entry.widthInChunks = tableData.getIntField("widthInChunks", -1);
|
||||
entry.heightInChunks = tableData.getIntField("heightInChunks", -1);
|
||||
entry.petsAllowed = tableData.getIntField("petsAllowed", -1) == 1 ? true : false;
|
||||
entry.localize = tableData.getIntField("localize", -1) == 1 ? true : false;
|
||||
entry.fZoneWeight = tableData.getFloatField("fZoneWeight", -1.0f);
|
||||
UNUSED(entry.thumbnail = tableData.getStringField("thumbnail", ""));
|
||||
entry.PlayerLoseCoinsOnDeath = tableData.getIntField("PlayerLoseCoinsOnDeath", -1) == 1 ? true : false;
|
||||
entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false;
|
||||
entry.teamRadius = tableData.getFloatField("teamRadius", -1.0f);
|
||||
UNUSED(entry.gate_version = tableData.getStringField("gate_version", ""));
|
||||
entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false;
|
||||
|
||||
tableSize.nextRow();
|
||||
entries[entry.zoneID] = entry;
|
||||
tableData.nextRow();
|
||||
}
|
||||
}
|
||||
|
||||
tableSize.finalize();
|
||||
//! Queries the table with a zoneID to find.
|
||||
const CDZoneTable* Query(uint32_t zoneID) {
|
||||
const auto& iter = entries.find(zoneID);
|
||||
if (iter != entries.end()) {
|
||||
return &iter->second;
|
||||
}
|
||||
|
||||
// Now get the data
|
||||
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM ZoneTable");
|
||||
auto& entries = GetEntriesMutable();
|
||||
while (!tableData.eof()) {
|
||||
CDZoneTable entry;
|
||||
entry.zoneID = tableData.getIntField("zoneID", -1);
|
||||
entry.locStatus = tableData.getIntField("locStatus", -1);
|
||||
entry.zoneName = tableData.getStringField("zoneName", "");
|
||||
entry.scriptID = tableData.getIntField("scriptID", -1);
|
||||
entry.ghostdistance_min = tableData.getFloatField("ghostdistance_min", -1.0f);
|
||||
entry.ghostdistance = tableData.getFloatField("ghostdistance", -1.0f);
|
||||
entry.population_soft_cap = tableData.getIntField("population_soft_cap", -1);
|
||||
entry.population_hard_cap = tableData.getIntField("population_hard_cap", -1);
|
||||
UNUSED(entry.DisplayDescription = tableData.getStringField("DisplayDescription", ""));
|
||||
UNUSED(entry.mapFolder = tableData.getStringField("mapFolder", ""));
|
||||
entry.smashableMinDistance = tableData.getFloatField("smashableMinDistance", -1.0f);
|
||||
entry.smashableMaxDistance = tableData.getFloatField("smashableMaxDistance", -1.0f);
|
||||
UNUSED(entry.mixerProgram = tableData.getStringField("mixerProgram", ""));
|
||||
UNUSED(entry.clientPhysicsFramerate = tableData.getStringField("clientPhysicsFramerate", ""));
|
||||
entry.serverPhysicsFramerate = tableData.getStringField("serverPhysicsFramerate", "");
|
||||
entry.zoneControlTemplate = tableData.getIntField("zoneControlTemplate", -1);
|
||||
entry.widthInChunks = tableData.getIntField("widthInChunks", -1);
|
||||
entry.heightInChunks = tableData.getIntField("heightInChunks", -1);
|
||||
entry.petsAllowed = tableData.getIntField("petsAllowed", -1) == 1 ? true : false;
|
||||
entry.localize = tableData.getIntField("localize", -1) == 1 ? true : false;
|
||||
entry.fZoneWeight = tableData.getFloatField("fZoneWeight", -1.0f);
|
||||
UNUSED(entry.thumbnail = tableData.getStringField("thumbnail", ""));
|
||||
entry.PlayerLoseCoinsOnDeath = tableData.getIntField("PlayerLoseCoinsOnDeath", -1) == 1 ? true : false;
|
||||
entry.disableSaveLoc = tableData.getIntField("disableSaveLoc", -1) == 1 ? true : false;
|
||||
entry.teamRadius = tableData.getFloatField("teamRadius", -1.0f);
|
||||
UNUSED(entry.gate_version = tableData.getStringField("gate_version", ""));
|
||||
entry.mountsAllowed = tableData.getIntField("mountsAllowed", -1) == 1 ? true : false;
|
||||
|
||||
entries.insert(std::make_pair(entry.zoneID, entry));
|
||||
tableData.nextRow();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
tableData.finalize();
|
||||
}
|
||||
|
||||
//! Queries the table with a zoneID to find.
|
||||
const CDZoneTable* CDZoneTableTable::Query(uint32_t zoneID) {
|
||||
auto& m_Entries = GetEntries();
|
||||
const auto& iter = m_Entries.find(zoneID);
|
||||
|
||||
if (iter != m_Entries.end()) {
|
||||
return &iter->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -33,8 +33,8 @@ struct CDZoneTable {
|
||||
bool mountsAllowed; //!< Whether or not mounts are allowed
|
||||
};
|
||||
|
||||
class CDZoneTableTable : public CDTable<CDZoneTableTable, std::map<uint32_t, CDZoneTable>> {
|
||||
public:
|
||||
namespace CDZoneTableTable {
|
||||
using Table = std::map<uint32_t, CDZoneTable>;
|
||||
void LoadValuesFromDatabase();
|
||||
|
||||
// Queries the table with a zoneID to find.
|
||||
|
@ -1,7 +1,7 @@
|
||||
add_subdirectory(CDClientDatabase)
|
||||
add_subdirectory(GameDatabase)
|
||||
|
||||
add_library(dDatabase STATIC "MigrationRunner.cpp")
|
||||
add_library(dDatabase STATIC "MigrationRunner.cpp" "ModelNormalizeMigration.cpp")
|
||||
|
||||
add_custom_target(conncpp_dylib
|
||||
${CMAKE_COMMAND} -E copy $<TARGET_FILE:MariaDB::ConnCpp> ${PROJECT_BINARY_DIR})
|
||||
|
@ -22,7 +22,7 @@ public:
|
||||
|
||||
// Inserts a new UGC model into the database.
|
||||
virtual void InsertNewUgcModel(
|
||||
std::istringstream& sd0Data,
|
||||
std::stringstream& sd0Data,
|
||||
const uint32_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const uint32_t characterId) = 0;
|
||||
@ -34,9 +34,17 @@ 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& propertyId, 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<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);
|
||||
}
|
||||
|
||||
// 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__
|
||||
|
@ -12,6 +12,7 @@ public:
|
||||
struct Model {
|
||||
std::stringstream lxfmlData;
|
||||
LWOOBJID id{};
|
||||
LWOOBJID modelID{};
|
||||
};
|
||||
|
||||
// Gets all UGC models for the given property id.
|
||||
@ -27,6 +28,6 @@ public:
|
||||
virtual void DeleteUgcModelData(const LWOOBJID& modelId) = 0;
|
||||
|
||||
// Inserts a new UGC model into the database.
|
||||
virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) = 0;
|
||||
virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) = 0;
|
||||
};
|
||||
#endif //!__IUGC__H__
|
||||
|
@ -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::istringstream& lxfml) override;
|
||||
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
|
||||
std::vector<IUgc::Model> GetAllUgcModels() override;
|
||||
void CreateMigrationHistoryTable() override;
|
||||
bool IsMigrationRun(const std::string_view str) override;
|
||||
@ -75,14 +75,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& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) 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 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::istringstream& sd0Data,
|
||||
std::stringstream& sd0Data,
|
||||
const uint32_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const uint32_t characterId) override;
|
||||
@ -127,6 +127,7 @@ 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:
|
||||
|
||||
|
@ -52,14 +52,39 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr
|
||||
}
|
||||
}
|
||||
|
||||
void MySQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
|
||||
void MySQLDatabase::UpdateModel(const LWOOBJID& modelID, 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, propertyId);
|
||||
behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, modelID);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId) {
|
||||
auto result = ExecuteSelect(
|
||||
"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;",
|
||||
"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;",
|
||||
propertyId);
|
||||
|
||||
std::vector<IUgc::Model> toReturn;
|
||||
@ -13,7 +13,8 @@ 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("id");
|
||||
model.id = result->getUInt64("ugcID");
|
||||
model.modelID = result->getUInt64("modelID");
|
||||
toReturn.push_back(std::move(model));
|
||||
}
|
||||
|
||||
@ -21,13 +22,14 @@ std::vector<IUgc::Model> MySQLDatabase::GetUgcModels(const LWOOBJID& propertyId)
|
||||
}
|
||||
|
||||
std::vector<IUgc::Model> MySQLDatabase::GetAllUgcModels() {
|
||||
auto result = ExecuteSelect("SELECT id, lxfml FROM ugc;");
|
||||
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;");
|
||||
|
||||
std::vector<IUgc::Model> models;
|
||||
models.reserve(result->rowsCount());
|
||||
while (result->next()) {
|
||||
IUgc::Model model;
|
||||
model.id = result->getInt64("id");
|
||||
model.id = result->getInt64("ugcID");
|
||||
model.modelID = result->getUInt64("modelID");
|
||||
|
||||
// blob is owned by the query, so we need to do a deep copy :/
|
||||
std::unique_ptr<std::istream> blob(result->getBlob("lxfml"));
|
||||
@ -43,7 +45,7 @@ void MySQLDatabase::RemoveUnreferencedUgcModels() {
|
||||
}
|
||||
|
||||
void MySQLDatabase::InsertNewUgcModel(
|
||||
std::istringstream& sd0Data, // cant be const sad
|
||||
std:: stringstream& sd0Data, // cant be const sad
|
||||
const uint32_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const uint32_t characterId) {
|
||||
@ -65,7 +67,7 @@ void MySQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId);
|
||||
}
|
||||
|
||||
void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
|
||||
void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {
|
||||
const std::istream stream(lxfml.rdbuf());
|
||||
ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId);
|
||||
}
|
||||
|
@ -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::istringstream& lxfml) override;
|
||||
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
|
||||
std::vector<IUgc::Model> GetAllUgcModels() override;
|
||||
void CreateMigrationHistoryTable() override;
|
||||
bool IsMigrationRun(const std::string_view str) override;
|
||||
@ -73,14 +73,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& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) 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 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::istringstream& sd0Data,
|
||||
std::stringstream& sd0Data,
|
||||
const uint32_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const uint32_t characterId) override;
|
||||
@ -125,6 +125,7 @@ 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);
|
||||
|
||||
|
@ -52,14 +52,41 @@ void SQLiteDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IP
|
||||
}
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
|
||||
void SQLiteDatabase::UpdateModel(const LWOOBJID& modelID, 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, propertyId);
|
||||
behaviors[0].first, behaviors[1].first, behaviors[2].first, behaviors[3].first, behaviors[4].first, modelID);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
std::vector<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId) {
|
||||
auto [_, result] = ExecuteSelect(
|
||||
"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;",
|
||||
"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;",
|
||||
propertyId);
|
||||
|
||||
std::vector<IUgc::Model> toReturn;
|
||||
@ -13,7 +13,8 @@ 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("id");
|
||||
model.id = result.getInt64Field("ugcID");
|
||||
model.modelID = result.getInt64Field("modelID");
|
||||
toReturn.push_back(std::move(model));
|
||||
result.nextRow();
|
||||
}
|
||||
@ -22,12 +23,13 @@ std::vector<IUgc::Model> SQLiteDatabase::GetUgcModels(const LWOOBJID& propertyId
|
||||
}
|
||||
|
||||
std::vector<IUgc::Model> SQLiteDatabase::GetAllUgcModels() {
|
||||
auto [_, result] = ExecuteSelect("SELECT id, lxfml FROM ugc;");
|
||||
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;");
|
||||
|
||||
std::vector<IUgc::Model> models;
|
||||
while (!result.eof()) {
|
||||
IUgc::Model model;
|
||||
model.id = result.getInt64Field("id");
|
||||
model.id = result.getInt64Field("ugcID");
|
||||
model.modelID = result.getInt64Field("modelID");
|
||||
|
||||
int blobSize{};
|
||||
const auto* blob = result.getBlobField("lxfml", blobSize);
|
||||
@ -44,7 +46,7 @@ void SQLiteDatabase::RemoveUnreferencedUgcModels() {
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertNewUgcModel(
|
||||
std::istringstream& sd0Data, // cant be const sad
|
||||
std::stringstream& sd0Data, // cant be const sad
|
||||
const uint32_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const uint32_t characterId) {
|
||||
@ -66,7 +68,7 @@ void SQLiteDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
|
||||
void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {
|
||||
const std::istream stream(lxfml.rdbuf());
|
||||
ExecuteUpdate("UPDATE ugc SET lxfml = ? WHERE id = ?;", &stream, modelId);
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ void TestSQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) {
|
||||
void TestSQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) {
|
||||
|
||||
}
|
||||
|
||||
@ -168,7 +168,7 @@ void TestSQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const I
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::UpdateModel(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
|
||||
void TestSQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
|
||||
|
||||
}
|
||||
|
||||
@ -192,7 +192,7 @@ void TestSQLDatabase::InsertNewMail(const MailInfo& mail) {
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::InsertNewUgcModel(std::istringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const uint32_t characterId) {
|
||||
void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint32_t blueprintId, const uint32_t accountId, const uint32_t characterId) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -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::istringstream& lxfml) override;
|
||||
void UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml) override;
|
||||
std::vector<IUgc::Model> GetAllUgcModels() override;
|
||||
void CreateMigrationHistoryTable() override;
|
||||
bool IsMigrationRun(const std::string_view str) override;
|
||||
@ -52,14 +52,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& propertyId, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) 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 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::istringstream& sd0Data,
|
||||
std::stringstream& sd0Data,
|
||||
const uint32_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const uint32_t characterId) override;
|
||||
@ -105,6 +105,7 @@ 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
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "GeneralUtils.h"
|
||||
#include "Logger.h"
|
||||
#include "BinaryPathFinder.h"
|
||||
#include "ModelNormalizeMigration.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
@ -45,6 +46,7 @@ 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);
|
||||
|
||||
@ -57,6 +59,8 @@ 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());
|
||||
}
|
||||
@ -64,7 +68,7 @@ void MigrationRunner::RunMigrations() {
|
||||
Database::Get()->InsertMigration(migration.name);
|
||||
}
|
||||
|
||||
if (finalSQL.empty() && !runSd0Migrations) {
|
||||
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations) {
|
||||
LOG("Server database is up to date.");
|
||||
return;
|
||||
}
|
||||
@ -88,6 +92,10 @@ void MigrationRunner::RunMigrations() {
|
||||
uint32_t numberOfTruncatedModels = BrickByBrickFix::TruncateBrokenBrickByBrickXml();
|
||||
LOG("%i models were truncated from the database.", numberOfTruncatedModels);
|
||||
}
|
||||
|
||||
if (runNormalizeMigrations) {
|
||||
ModelNormalizeMigration::Run();
|
||||
}
|
||||
}
|
||||
|
||||
void MigrationRunner::RunSQLiteMigrations() {
|
||||
|
30
dDatabase/ModelNormalizeMigration.cpp
Normal file
30
dDatabase/ModelNormalizeMigration.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#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);
|
||||
}
|
11
dDatabase/ModelNormalizeMigration.h
Normal file
11
dDatabase/ModelNormalizeMigration.h
Normal file
@ -0,0 +1,11 @@
|
||||
// Darkflame Universe
|
||||
// Copyright 2025
|
||||
|
||||
#ifndef MODELNORMALIZEMIGRATION_H
|
||||
#define MODELNORMALIZEMIGRATION_H
|
||||
|
||||
namespace ModelNormalizeMigration {
|
||||
void Run();
|
||||
};
|
||||
|
||||
#endif //!MODELNORMALIZEMIGRATION_H
|
@ -545,9 +545,8 @@ void Entity::Initialize() {
|
||||
|
||||
// ZoneControl script
|
||||
if (m_TemplateID == 2365) {
|
||||
CDZoneTableTable* zoneTable = CDClientManager::GetTable<CDZoneTableTable>();
|
||||
const auto zoneID = Game::zoneManager->GetZoneID();
|
||||
const CDZoneTable* zoneData = zoneTable->Query(zoneID.GetMapID());
|
||||
const CDZoneTable* zoneData = CDZoneTableTable::Query(zoneID.GetMapID());
|
||||
|
||||
if (zoneData != nullptr) {
|
||||
int zoneScriptID = zoneData->scriptID;
|
||||
|
@ -99,7 +99,7 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE
|
||||
}
|
||||
|
||||
// Exclude the zone control object from any flags
|
||||
if (!controller && info.lot != 14) {
|
||||
if (!controller) {
|
||||
|
||||
// The client flags means the client should render the entity
|
||||
GeneralUtils::SetBit(id, eObjectBits::CLIENT);
|
||||
|
@ -20,7 +20,7 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bi
|
||||
|
||||
//Handle player damage cooldown
|
||||
if (entity->IsPlayer() && !this->m_DontApplyImmune) {
|
||||
const float immunityTime = Game::zoneManager->GetWorldConfig()->globalImmunityTime;
|
||||
const float immunityTime = Game::zoneManager->GetWorldConfig().globalImmunityTime;
|
||||
destroyableComponent->SetDamageCooldownTimer(immunityTime);
|
||||
}
|
||||
}
|
||||
@ -214,7 +214,7 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
|
||||
|
||||
//Handle player damage cooldown
|
||||
if (isSuccess && targetEntity->IsPlayer() && !this->m_DontApplyImmune) {
|
||||
destroyableComponent->SetDamageCooldownTimer(Game::zoneManager->GetWorldConfig()->globalImmunityTime);
|
||||
destroyableComponent->SetDamageCooldownTimer(Game::zoneManager->GetWorldConfig().globalImmunityTime);
|
||||
}
|
||||
|
||||
eBasicAttackSuccessTypes successState = eBasicAttackSuccessTypes::FAILIMMUNE;
|
||||
|
@ -774,10 +774,10 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
|
||||
if (Game::zoneManager->GetPlayerLoseCoinOnDeath()) {
|
||||
auto* character = m_Parent->GetCharacter();
|
||||
uint64_t coinsTotal = character->GetCoins();
|
||||
const uint64_t minCoinsToLose = Game::zoneManager->GetWorldConfig()->coinsLostOnDeathMin;
|
||||
const uint64_t minCoinsToLose = Game::zoneManager->GetWorldConfig().coinsLostOnDeathMin;
|
||||
if (coinsTotal >= minCoinsToLose) {
|
||||
const uint64_t maxCoinsToLose = Game::zoneManager->GetWorldConfig()->coinsLostOnDeathMax;
|
||||
const float coinPercentageToLose = Game::zoneManager->GetWorldConfig()->coinsLostOnDeathPercent;
|
||||
const uint64_t maxCoinsToLose = Game::zoneManager->GetWorldConfig().coinsLostOnDeathMax;
|
||||
const float coinPercentageToLose = Game::zoneManager->GetWorldConfig().coinsLostOnDeathPercent;
|
||||
|
||||
uint64_t coinsToLose = std::max(static_cast<uint64_t>(coinsTotal * coinPercentageToLose), minCoinsToLose);
|
||||
coinsToLose = std::min(maxCoinsToLose, coinsToLose);
|
||||
|
@ -10,12 +10,50 @@
|
||||
#include "SimplePhysicsComponent.h"
|
||||
|
||||
#include "Database.h"
|
||||
#include "DluAssert.h"
|
||||
|
||||
ModelComponent::ModelComponent(Entity* parent) : Component(parent) {
|
||||
m_OriginalPosition = m_Parent->GetDefaultPosition();
|
||||
m_OriginalRotation = m_Parent->GetDefaultRotation();
|
||||
m_IsPaused = false;
|
||||
m_NumListeningInteract = 0;
|
||||
|
||||
m_userModelID = m_Parent->GetVarAs<LWOOBJID>(u"userModelID");
|
||||
RegisterMsg(MessageType::Game::REQUEST_USE, this, &ModelComponent::OnRequestUse);
|
||||
RegisterMsg(MessageType::Game::RESET_MODEL_TO_DEFAULTS, this, &ModelComponent::OnResetModelToDefaults);
|
||||
}
|
||||
|
||||
bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) {
|
||||
auto& reset = static_cast<GameMessages::ResetModelToDefaults&>(msg);
|
||||
for (auto& behavior : m_Behaviors) behavior.HandleMsg(reset);
|
||||
GameMessages::UnSmash unsmash;
|
||||
unsmash.target = GetParent()->GetObjectID();
|
||||
unsmash.duration = 0.0f;
|
||||
unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
m_NumListeningInteract = 0;
|
||||
m_NumActiveUnSmash = 0;
|
||||
m_Dirty = true;
|
||||
Game::entityManager->SerializeEntity(GetParent());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ModelComponent::OnRequestUse(GameMessages::GameMsg& msg) {
|
||||
bool toReturn = false;
|
||||
if (!m_IsPaused) {
|
||||
auto& requestUse = static_cast<GameMessages::RequestUse&>(msg);
|
||||
for (auto& behavior : m_Behaviors) behavior.HandleMsg(requestUse);
|
||||
toReturn = true;
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void ModelComponent::Update(float deltaTime) {
|
||||
if (m_IsPaused) return;
|
||||
|
||||
for (auto& behavior : m_Behaviors) {
|
||||
behavior.Update(deltaTime, *this);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelComponent::LoadBehaviors() {
|
||||
@ -45,6 +83,11 @@ void ModelComponent::LoadBehaviors() {
|
||||
}
|
||||
}
|
||||
|
||||
void ModelComponent::Resume() {
|
||||
m_Dirty = true;
|
||||
m_IsPaused = false;
|
||||
}
|
||||
|
||||
void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
|
||||
// ItemComponent Serialization. Pets do not get this serialization.
|
||||
if (!m_Parent->HasComponent(eReplicaComponentType::PET)) {
|
||||
@ -56,14 +99,14 @@ void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialU
|
||||
|
||||
//actual model component:
|
||||
outBitStream.Write1(); // Yes we are writing model info
|
||||
outBitStream.Write0(); // Is pickable
|
||||
outBitStream.Write(m_NumListeningInteract > 0); // Is pickable
|
||||
outBitStream.Write<uint32_t>(2); // Physics type
|
||||
outBitStream.Write(m_OriginalPosition); // Original position
|
||||
outBitStream.Write(m_OriginalRotation); // Original rotation
|
||||
|
||||
outBitStream.Write1(); // We are writing behavior info
|
||||
outBitStream.Write<uint32_t>(0); // Number of behaviors
|
||||
outBitStream.Write1(); // Is this model paused
|
||||
outBitStream.Write<uint32_t>(m_Behaviors.size()); // Number of behaviors
|
||||
outBitStream.Write(m_IsPaused); // Is this model paused
|
||||
if (bIsInitialUpdate) outBitStream.Write0(); // We are not writing model editing info
|
||||
}
|
||||
|
||||
@ -135,3 +178,28 @@ std::array<std::pair<int32_t, std::string>, 5> ModelComponent::GetBehaviorsForSa
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void ModelComponent::AddInteract() {
|
||||
LOG_DEBUG("Adding interact %i", m_NumListeningInteract);
|
||||
m_Dirty = true;
|
||||
m_NumListeningInteract++;
|
||||
}
|
||||
|
||||
void ModelComponent::RemoveInteract() {
|
||||
DluAssert(m_NumListeningInteract > 0);
|
||||
LOG_DEBUG("Removing interact %i", m_NumListeningInteract);
|
||||
m_Dirty = true;
|
||||
m_NumListeningInteract--;
|
||||
}
|
||||
|
||||
void ModelComponent::AddUnSmash() {
|
||||
LOG_DEBUG("Adding UnSmash %i", m_NumActiveUnSmash);
|
||||
m_NumActiveUnSmash++;
|
||||
}
|
||||
|
||||
void ModelComponent::RemoveUnSmash() {
|
||||
// Players can assign an UnSmash without a Smash so an assert would be bad here
|
||||
if (m_NumActiveUnSmash == 0) return;
|
||||
LOG_DEBUG("Removing UnSmash %i", m_NumActiveUnSmash);
|
||||
m_NumActiveUnSmash--;
|
||||
}
|
||||
|
@ -30,6 +30,10 @@ public:
|
||||
ModelComponent(Entity* parent);
|
||||
|
||||
void LoadBehaviors();
|
||||
void Update(float deltaTime) override;
|
||||
|
||||
bool OnRequestUse(GameMessages::GameMsg& msg);
|
||||
bool OnResetModelToDefaults(GameMessages::GameMsg& msg);
|
||||
|
||||
void Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) override;
|
||||
|
||||
@ -114,7 +118,30 @@ public:
|
||||
|
||||
std::array<std::pair<int32_t, std::string>, 5> GetBehaviorsForSave() const;
|
||||
|
||||
const std::vector<PropertyBehavior>& GetBehaviors() const { return m_Behaviors; };
|
||||
|
||||
void AddInteract();
|
||||
void RemoveInteract();
|
||||
|
||||
void Pause() { m_Dirty = true; m_IsPaused = true; }
|
||||
|
||||
void AddUnSmash();
|
||||
void RemoveUnSmash();
|
||||
bool IsUnSmashing() const { return m_NumActiveUnSmash != 0; }
|
||||
|
||||
void Resume();
|
||||
private:
|
||||
// Number of Actions that are awaiting an UnSmash to finish.
|
||||
uint32_t m_NumActiveUnSmash{};
|
||||
|
||||
// Whether or not this component needs to have its extra data serialized.
|
||||
bool m_Dirty{};
|
||||
|
||||
// The number of strips listening for a RequestUse GM to come in.
|
||||
uint32_t m_NumListeningInteract{};
|
||||
|
||||
// Whether or not the model is paused and should reject all interactions regarding behaviors.
|
||||
bool m_IsPaused{};
|
||||
/**
|
||||
* The behaviors of the model
|
||||
* Note: This is a vector because the order of the behaviors matters when serializing to the client.
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <vector>
|
||||
#include "CppScripts.h"
|
||||
#include <ranges>
|
||||
#include "dConfig.h"
|
||||
|
||||
PropertyManagementComponent* PropertyManagementComponent::instance = nullptr;
|
||||
|
||||
@ -151,7 +152,11 @@ void PropertyManagementComponent::SetPrivacyOption(PropertyPrivacyOption value)
|
||||
info.rejectionReason = rejectionReason;
|
||||
info.modApproved = 0;
|
||||
|
||||
Database::Get()->UpdatePropertyModerationInfo(info);
|
||||
if (models.empty() && Game::config->GetValue("auto_reject_empty_properties") == "1") {
|
||||
UpdateApprovedStatus(false, "Your property is empty. Please place a model to have a public property.");
|
||||
} else {
|
||||
Database::Get()->UpdatePropertyModerationInfo(info);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyManagementComponent::UpdatePropertyDetails(std::string name, std::string description) {
|
||||
@ -255,6 +260,18 @@ void PropertyManagementComponent::OnStartBuilding() {
|
||||
|
||||
// Push equipped items
|
||||
if (inventoryComponent) inventoryComponent->PushEquippedItems();
|
||||
|
||||
for (auto modelID : models | std::views::keys) {
|
||||
auto* model = Game::entityManager->GetEntity(modelID);
|
||||
if (model) {
|
||||
auto* modelComponent = model->GetComponent<ModelComponent>();
|
||||
if (modelComponent) modelComponent->Pause();
|
||||
Game::entityManager->SerializeEntity(model);
|
||||
GameMessages::ResetModelToDefaults reset;
|
||||
reset.target = modelID;
|
||||
model->HandleMsg(reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyManagementComponent::OnFinishBuilding() {
|
||||
@ -267,6 +284,18 @@ void PropertyManagementComponent::OnFinishBuilding() {
|
||||
UpdateApprovedStatus(false);
|
||||
|
||||
Save();
|
||||
|
||||
for (auto modelID : models | std::views::keys) {
|
||||
auto* model = Game::entityManager->GetEntity(modelID);
|
||||
if (model) {
|
||||
auto* modelComponent = model->GetComponent<ModelComponent>();
|
||||
if (modelComponent) modelComponent->Resume();
|
||||
Game::entityManager->SerializeEntity(model);
|
||||
GameMessages::ResetModelToDefaults reset;
|
||||
reset.target = modelID;
|
||||
model->HandleMsg(reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const NiPoint3 position, NiQuaternion rotation) {
|
||||
@ -318,6 +347,8 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N
|
||||
Entity* newEntity = Game::entityManager->CreateEntity(info);
|
||||
if (newEntity != nullptr) {
|
||||
Game::entityManager->ConstructEntity(newEntity);
|
||||
auto* modelComponent = newEntity->GetComponent<ModelComponent>();
|
||||
if (modelComponent) modelComponent->Pause();
|
||||
|
||||
// Make sure the propMgmt doesn't delete our model after the server dies
|
||||
// Trying to do this after the entity is constructed. Shouldn't really change anything but
|
||||
@ -363,6 +394,8 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N
|
||||
info.nodes[0]->config.push_back(new LDFData<int>(u"componentWhitelist", 1));
|
||||
|
||||
auto* model = spawner->Spawn();
|
||||
auto* modelComponent = model->GetComponent<ModelComponent>();
|
||||
if (modelComponent) modelComponent->Pause();
|
||||
|
||||
models.insert_or_assign(model->GetObjectID(), spawnerId);
|
||||
|
||||
@ -537,14 +570,14 @@ void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int delet
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyManagementComponent::UpdateApprovedStatus(const bool value) {
|
||||
void PropertyManagementComponent::UpdateApprovedStatus(const bool value, const std::string& rejectionReason) {
|
||||
if (owner == LWOOBJID_EMPTY) return;
|
||||
|
||||
IProperty::Info info;
|
||||
info.id = propertyId;
|
||||
info.modApproved = value;
|
||||
info.privacyOption = static_cast<uint32_t>(privacyOption);
|
||||
info.rejectionReason = "";
|
||||
info.rejectionReason = rejectionReason;
|
||||
|
||||
Database::Get()->UpdatePropertyModerationInfo(info);
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ public:
|
||||
* Updates whether or not this property is approved by a moderator
|
||||
* @param value true if the property should be approved, false otherwise
|
||||
*/
|
||||
void UpdateApprovedStatus(bool value);
|
||||
void UpdateApprovedStatus(bool value, const std::string& rejectionReason = "");
|
||||
|
||||
/**
|
||||
* Loads all the models on this property from the database
|
||||
|
@ -101,7 +101,7 @@ void VendorComponent::SetupConstants() {
|
||||
std::vector<CDVendorComponent> vendorComps = vendorComponentTable->Query([=](CDVendorComponent entry) { return (entry.id == componentID); });
|
||||
if (vendorComps.empty()) return;
|
||||
auto vendorData = vendorComps.at(0);
|
||||
if (vendorData.buyScalar == 0.0) m_BuyScalar = Game::zoneManager->GetWorldConfig()->vendorBuyMultiplier;
|
||||
if (vendorData.buyScalar == 0.0) m_BuyScalar = Game::zoneManager->GetWorldConfig().vendorBuyMultiplier;
|
||||
else m_BuyScalar = vendorData.buyScalar;
|
||||
m_SellScalar = vendorData.sellScalar;
|
||||
m_RefreshTimeSeconds = vendorData.refreshTimeSeconds;
|
||||
|
@ -45,6 +45,7 @@ namespace {
|
||||
using namespace GameMessages;
|
||||
using MessageCreator = std::function<std::unique_ptr<GameMessages::GameMsg>()>;
|
||||
std::map<MessageType::Game, MessageCreator> g_MessageHandlers = {
|
||||
{ REQUEST_USE, []() { return std::make_unique<RequestUse>(); }},
|
||||
{ REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique<RequestServerObjectInfo>(); } },
|
||||
{ SHOOTING_GALLERY_FIRE, []() { return std::make_unique<ShootingGalleryFire>(); } },
|
||||
};
|
||||
@ -118,11 +119,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
break;
|
||||
}
|
||||
|
||||
case MessageType::Game::REQUEST_USE: {
|
||||
GameMessages::HandleRequestUse(inStream, entity, sysAddr);
|
||||
break;
|
||||
}
|
||||
|
||||
case MessageType::Game::SET_FLAG: {
|
||||
GameMessages::HandleSetFlag(inStream, entity);
|
||||
break;
|
||||
|
@ -102,6 +102,8 @@
|
||||
#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;
|
||||
@ -2575,18 +2577,6 @@ 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.
|
||||
@ -2614,16 +2604,25 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
|
||||
LWOOBJID propertyId = LWOOBJID_EMPTY;
|
||||
if (propertyInfo) propertyId = propertyInfo->id;
|
||||
|
||||
//Insert into ugc:
|
||||
// Save the binary data to the Sd0 buffer
|
||||
std::string str(sd0Data.get(), sd0Size);
|
||||
std::istringstream sd0DataStream(str);
|
||||
Database::Get()->InsertNewUgcModel(sd0DataStream, blueprintIDSmall, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID());
|
||||
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());
|
||||
|
||||
//Insert into the db as a BBB model:
|
||||
IPropertyContents::Model model;
|
||||
model.id = newIDL;
|
||||
model.ugcId = blueprintIDSmall;
|
||||
model.position = NiPoint3Constant::ZERO;
|
||||
model.position = newCenter;
|
||||
model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
model.lot = 14;
|
||||
Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name");
|
||||
@ -2649,6 +2648,9 @@ 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);
|
||||
@ -2656,9 +2658,9 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
|
||||
bitStream.Write<uint32_t>(1);
|
||||
bitStream.Write(blueprintID);
|
||||
|
||||
bitStream.Write<uint32_t>(sd0Size);
|
||||
bitStream.Write(sd0Size);
|
||||
|
||||
bitStream.WriteAlignedBytes(reinterpret_cast<unsigned char*>(sd0Data.get()), sd0Size);
|
||||
for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
|
||||
|
||||
SEND_PACKET;
|
||||
|
||||
@ -2666,7 +2668,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
|
||||
|
||||
EntityInfo info;
|
||||
info.lot = 14;
|
||||
info.pos = {};
|
||||
info.pos = newCenter;
|
||||
info.rot = {};
|
||||
info.spawner = nullptr;
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
@ -4955,54 +4957,6 @@ void GameMessages::HandleQuickBuildCancel(RakNet::BitStream& inStream, Entity* e
|
||||
quickBuildComponent->CancelQuickBuild(Game::entityManager->GetEntity(userID), eQuickBuildFailReason::CANCELED_EARLY);
|
||||
}
|
||||
|
||||
void GameMessages::HandleRequestUse(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
|
||||
bool bIsMultiInteractUse = false;
|
||||
unsigned int multiInteractID;
|
||||
int multiInteractType;
|
||||
bool secondary;
|
||||
LWOOBJID objectID;
|
||||
|
||||
inStream.Read(bIsMultiInteractUse);
|
||||
inStream.Read(multiInteractID);
|
||||
inStream.Read(multiInteractType);
|
||||
inStream.Read(objectID);
|
||||
inStream.Read(secondary);
|
||||
|
||||
Entity* interactedObject = Game::entityManager->GetEntity(objectID);
|
||||
|
||||
if (interactedObject == nullptr) {
|
||||
LOG("Object %llu tried to interact, but doesn't exist!", objectID);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (interactedObject->GetLOT() == 9524) {
|
||||
entity->GetCharacter()->SetBuildMode(true);
|
||||
}
|
||||
|
||||
if (bIsMultiInteractUse) {
|
||||
if (multiInteractType == 0) {
|
||||
auto* missionOfferComponent = static_cast<MissionOfferComponent*>(interactedObject->GetComponent(eReplicaComponentType::MISSION_OFFER));
|
||||
|
||||
if (missionOfferComponent != nullptr) {
|
||||
missionOfferComponent->OfferMissions(entity, multiInteractID);
|
||||
}
|
||||
} else {
|
||||
interactedObject->OnUse(entity);
|
||||
}
|
||||
} else {
|
||||
interactedObject->OnUse(entity);
|
||||
}
|
||||
|
||||
//Perform use task if possible:
|
||||
auto missionComponent = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION));
|
||||
|
||||
if (missionComponent == nullptr) return;
|
||||
|
||||
missionComponent->Progress(eMissionTaskType::TALK_TO_NPC, interactedObject->GetLOT(), interactedObject->GetObjectID());
|
||||
missionComponent->Progress(eMissionTaskType::INTERACT, interactedObject->GetLOT(), interactedObject->GetObjectID());
|
||||
}
|
||||
|
||||
void GameMessages::HandlePlayEmote(RakNet::BitStream& inStream, Entity* entity) {
|
||||
int emoteID;
|
||||
LWOOBJID targetID;
|
||||
@ -6444,4 +6398,70 @@ namespace GameMessages {
|
||||
auto* handlingEntity = Game::entityManager->GetEntity(targetForReport);
|
||||
if (handlingEntity) handlingEntity->HandleMsg(*this);
|
||||
}
|
||||
|
||||
bool RequestUse::Deserialize(RakNet::BitStream& stream) {
|
||||
if (!stream.Read(bIsMultiInteractUse)) return false;
|
||||
if (!stream.Read(multiInteractID)) return false;
|
||||
if (!stream.Read(multiInteractType)) return false;
|
||||
if (!stream.Read(object)) return false;
|
||||
if (!stream.Read(secondary)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RequestUse::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
Entity* interactedObject = Game::entityManager->GetEntity(object);
|
||||
|
||||
if (interactedObject == nullptr) {
|
||||
LOG("Object %llu tried to interact, but doesn't exist!", object);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (interactedObject->GetLOT() == 9524) {
|
||||
entity.GetCharacter()->SetBuildMode(true);
|
||||
}
|
||||
|
||||
if (bIsMultiInteractUse) {
|
||||
if (multiInteractType == 0) {
|
||||
auto* missionOfferComponent = static_cast<MissionOfferComponent*>(interactedObject->GetComponent(eReplicaComponentType::MISSION_OFFER));
|
||||
|
||||
if (missionOfferComponent != nullptr) {
|
||||
missionOfferComponent->OfferMissions(&entity, multiInteractID);
|
||||
}
|
||||
} else {
|
||||
interactedObject->OnUse(&entity);
|
||||
}
|
||||
} else {
|
||||
interactedObject->OnUse(&entity);
|
||||
}
|
||||
|
||||
interactedObject->HandleMsg(*this);
|
||||
|
||||
//Perform use task if possible:
|
||||
auto missionComponent = entity.GetComponent<MissionComponent>();
|
||||
|
||||
if (!missionComponent) return;
|
||||
|
||||
missionComponent->Progress(eMissionTaskType::TALK_TO_NPC, interactedObject->GetLOT(), interactedObject->GetObjectID());
|
||||
missionComponent->Progress(eMissionTaskType::INTERACT, interactedObject->GetLOT(), interactedObject->GetObjectID());
|
||||
}
|
||||
|
||||
void Smash::Serialize(RakNet::BitStream& stream) const {
|
||||
stream.Write(bIgnoreObjectVisibility);
|
||||
stream.Write(force);
|
||||
stream.Write(ghostCapacity);
|
||||
stream.Write(killerID);
|
||||
}
|
||||
|
||||
void UnSmash::Serialize(RakNet::BitStream& stream) const {
|
||||
stream.Write(builderID != LWOOBJID_EMPTY);
|
||||
if (builderID != LWOOBJID_EMPTY) stream.Write(builderID);
|
||||
stream.Write(duration != 3.0f);
|
||||
if (builderID != 3.0f) stream.Write(duration);
|
||||
}
|
||||
|
||||
void PlayBehaviorSound::Serialize(RakNet::BitStream& stream) const {
|
||||
stream.Write(soundID != -1);
|
||||
if (soundID != -1) stream.Write(soundID);
|
||||
}
|
||||
}
|
||||
|
@ -631,7 +631,6 @@ namespace GameMessages {
|
||||
void HandleFireEventServerSide(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandleRequestPlatformResync(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandleQuickBuildCancel(RakNet::BitStream& inStream, Entity* entity);
|
||||
void HandleRequestUse(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandlePlayEmote(RakNet::BitStream& inStream, Entity* entity);
|
||||
void HandleModularBuildConvertModel(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
|
||||
void HandleSetFlag(RakNet::BitStream& inStream, Entity* entity);
|
||||
@ -782,6 +781,58 @@ namespace GameMessages {
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
};
|
||||
|
||||
struct RequestUse : public GameMsg {
|
||||
RequestUse() : GameMsg(MessageType::Game::REQUEST_USE) {}
|
||||
|
||||
bool Deserialize(RakNet::BitStream& stream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
|
||||
LWOOBJID object{};
|
||||
|
||||
bool secondary{ false };
|
||||
|
||||
// Set to true if this coming from a multi-interaction UI on the client.
|
||||
bool bIsMultiInteractUse{};
|
||||
|
||||
// Used only for multi-interaction
|
||||
unsigned int multiInteractID{};
|
||||
|
||||
// Used only for multi-interaction, is of the enum type InteractionType
|
||||
int multiInteractType{};
|
||||
};
|
||||
|
||||
struct Smash : public GameMsg {
|
||||
Smash() : GameMsg(MessageType::Game::SMASH) {}
|
||||
|
||||
void Serialize(RakNet::BitStream& stream) const;
|
||||
|
||||
bool bIgnoreObjectVisibility{};
|
||||
bool force{};
|
||||
float ghostCapacity{};
|
||||
LWOOBJID killerID{};
|
||||
};
|
||||
|
||||
struct UnSmash : public GameMsg {
|
||||
UnSmash() : GameMsg(MessageType::Game::UN_SMASH) {}
|
||||
|
||||
void Serialize(RakNet::BitStream& stream) const;
|
||||
|
||||
LWOOBJID builderID{ LWOOBJID_EMPTY };
|
||||
float duration{ 3.0f };
|
||||
};
|
||||
|
||||
struct PlayBehaviorSound : public GameMsg {
|
||||
PlayBehaviorSound() : GameMsg(MessageType::Game::PLAY_BEHAVIOR_SOUND) {}
|
||||
|
||||
void Serialize(RakNet::BitStream& stream) const;
|
||||
|
||||
int32_t soundID{ -1 };
|
||||
};
|
||||
|
||||
struct ResetModelToDefaults : public GameMsg {
|
||||
ResetModelToDefaults() : GameMsg(MessageType::Game::RESET_MODEL_TO_DEFAULTS) {}
|
||||
};
|
||||
};
|
||||
|
||||
#endif // GAMEMESSAGES_H
|
||||
|
@ -470,9 +470,9 @@ void Mission::YieldRewards() {
|
||||
int32_t coinsToSend = 0;
|
||||
if (info.LegoScore > 0) {
|
||||
eLootSourceType lootSource = info.isMission ? eLootSourceType::MISSION : eLootSourceType::ACHIEVEMENT;
|
||||
if (levelComponent->GetLevel() >= Game::zoneManager->GetWorldConfig()->levelCap) {
|
||||
if (levelComponent->GetLevel() >= Game::zoneManager->GetWorldConfig().levelCap) {
|
||||
// Since the character is at the level cap we reward them with coins instead of UScore.
|
||||
coinsToSend += info.LegoScore * Game::zoneManager->GetWorldConfig()->levelCapCurrencyConversion;
|
||||
coinsToSend += info.LegoScore * Game::zoneManager->GetWorldConfig().levelCapCurrencyConversion;
|
||||
} else {
|
||||
characterComponent->SetUScore(characterComponent->GetUScore() + info.LegoScore);
|
||||
GameMessages::SendModifyLEGOScore(entity, entity->GetSystemAddress(), info.LegoScore, lootSource);
|
||||
|
@ -4,9 +4,13 @@
|
||||
#include "BehaviorStates.h"
|
||||
#include "ControlBehaviorMsgs.h"
|
||||
#include "tinyxml2.h"
|
||||
#include "ModelComponent.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
PropertyBehavior::PropertyBehavior() {
|
||||
m_LastEditedState = BehaviorState::HOME_STATE;
|
||||
m_ActiveState = BehaviorState::HOME_STATE;
|
||||
}
|
||||
|
||||
template<>
|
||||
@ -84,6 +88,17 @@ void PropertyBehavior::HandleMsg(AddMessage& msg) {
|
||||
isLoot = m_BehaviorId != 7965;
|
||||
};
|
||||
|
||||
template<>
|
||||
void PropertyBehavior::HandleMsg(GameMessages::RequestUse& msg) {
|
||||
m_States[m_ActiveState].HandleMsg(msg);
|
||||
}
|
||||
|
||||
template<>
|
||||
void PropertyBehavior::HandleMsg(GameMessages::ResetModelToDefaults& msg) {
|
||||
m_ActiveState = BehaviorState::HOME_STATE;
|
||||
for (auto& state : m_States | std::views::values) state.HandleMsg(msg);
|
||||
}
|
||||
|
||||
void PropertyBehavior::SendBehaviorListToClient(AMFArrayValue& args) const {
|
||||
args.Insert("id", std::to_string(m_BehaviorId));
|
||||
args.Insert("name", m_Name);
|
||||
@ -153,3 +168,7 @@ void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) {
|
||||
m_States[static_cast<BehaviorState>(stateId)].Deserialize(*stateElement);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyBehavior::Update(float deltaTime, ModelComponent& modelComponent) {
|
||||
for (auto& state : m_States | std::views::values) state.Update(deltaTime, modelComponent);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ namespace tinyxml2 {
|
||||
enum class BehaviorState : uint32_t;
|
||||
|
||||
class AMFArrayValue;
|
||||
class ModelComponent;
|
||||
|
||||
/**
|
||||
* Represents the Entity of a Property Behavior and holds data associated with the behavior
|
||||
@ -31,7 +32,12 @@ public:
|
||||
|
||||
void Serialize(tinyxml2::XMLElement& behavior) const;
|
||||
void Deserialize(const tinyxml2::XMLElement& behavior);
|
||||
|
||||
void Update(float deltaTime, ModelComponent& modelComponent);
|
||||
|
||||
private:
|
||||
// The current active behavior state. Behaviors can only be in ONE state at a time.
|
||||
BehaviorState m_ActiveState;
|
||||
|
||||
// The states this behavior has.
|
||||
std::map<BehaviorState, State> m_States;
|
||||
|
@ -117,6 +117,16 @@ void State::HandleMsg(MigrateActionsMessage& msg) {
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
void State::HandleMsg(GameMessages::RequestUse& msg) {
|
||||
for (auto& strip : m_Strips) strip.HandleMsg(msg);
|
||||
}
|
||||
|
||||
template<>
|
||||
void State::HandleMsg(GameMessages::ResetModelToDefaults& msg) {
|
||||
for (auto& strip : m_Strips) strip.HandleMsg(msg);
|
||||
}
|
||||
|
||||
bool State::IsEmpty() const {
|
||||
for (const auto& strip : m_Strips) {
|
||||
if (!strip.IsEmpty()) return false;
|
||||
@ -152,3 +162,7 @@ void State::Deserialize(const tinyxml2::XMLElement& state) {
|
||||
strip.Deserialize(*stripElement);
|
||||
}
|
||||
}
|
||||
|
||||
void State::Update(float deltaTime, ModelComponent& modelComponent) {
|
||||
for (auto& strip : m_Strips) strip.Update(deltaTime, modelComponent);
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ namespace tinyxml2 {
|
||||
}
|
||||
|
||||
class AMFArrayValue;
|
||||
class ModelComponent;
|
||||
|
||||
class State {
|
||||
public:
|
||||
@ -19,7 +20,11 @@ public:
|
||||
|
||||
void Serialize(tinyxml2::XMLElement& state) const;
|
||||
void Deserialize(const tinyxml2::XMLElement& state);
|
||||
|
||||
void Update(float deltaTime, ModelComponent& modelComponent);
|
||||
private:
|
||||
|
||||
// The strips contained within this state.
|
||||
std::vector<Strip> m_Strips;
|
||||
};
|
||||
|
||||
|
@ -3,6 +3,11 @@
|
||||
#include "Amf3.h"
|
||||
#include "ControlBehaviorMsgs.h"
|
||||
#include "tinyxml2.h"
|
||||
#include "dEntity/EntityInfo.h"
|
||||
#include "ModelComponent.h"
|
||||
#include "PlayerManager.h"
|
||||
|
||||
#include "DluAssert.h"
|
||||
|
||||
template <>
|
||||
void Strip::HandleMsg(AddStripMessage& msg) {
|
||||
@ -75,7 +80,138 @@ void Strip::HandleMsg(MigrateActionsMessage& msg) {
|
||||
} else {
|
||||
m_Actions.insert(m_Actions.begin() + msg.GetDstActionIndex(), msg.GetMigratedActions().begin(), msg.GetMigratedActions().end());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template<>
|
||||
void Strip::HandleMsg(GameMessages::RequestUse& msg) {
|
||||
if (m_PausedTime > 0.0f) return;
|
||||
|
||||
if (m_Actions[m_NextActionIndex].GetType() == "OnInteract") {
|
||||
IncrementAction();
|
||||
m_WaitingForAction = false;
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Strip::HandleMsg(GameMessages::ResetModelToDefaults& msg) {
|
||||
m_WaitingForAction = false;
|
||||
m_PausedTime = 0.0f;
|
||||
m_NextActionIndex = 0;
|
||||
}
|
||||
|
||||
void Strip::IncrementAction() {
|
||||
if (m_Actions.empty()) return;
|
||||
m_NextActionIndex++;
|
||||
m_NextActionIndex %= m_Actions.size();
|
||||
}
|
||||
|
||||
void Strip::Spawn(LOT lot, Entity& entity) {
|
||||
EntityInfo info{};
|
||||
info.lot = lot;
|
||||
info.pos = entity.GetPosition();
|
||||
info.rot = NiQuaternionConstant::IDENTITY;
|
||||
info.spawnerID = entity.GetObjectID();
|
||||
Game::entityManager->ConstructEntity(Game::entityManager->CreateEntity(info, nullptr, &entity));
|
||||
}
|
||||
|
||||
// Spawns a specific drop for all
|
||||
void Strip::SpawnDrop(LOT dropLOT, Entity& entity) {
|
||||
for (auto* const player : PlayerManager::GetAllPlayers()) {
|
||||
GameMessages::SendDropClientLoot(player, entity.GetObjectID(), dropLOT, 0, entity.GetPosition());
|
||||
}
|
||||
}
|
||||
|
||||
void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
|
||||
auto& entity = *modelComponent.GetParent();
|
||||
auto& nextAction = GetNextAction();
|
||||
auto number = nextAction.GetValueParameterDouble();
|
||||
auto numberAsInt = static_cast<int32_t>(number);
|
||||
auto nextActionType = GetNextAction().GetType();
|
||||
if (nextActionType == "SpawnStromling") {
|
||||
Spawn(10495, entity); // Stromling property
|
||||
} else if (nextActionType == "SpawnPirate") {
|
||||
Spawn(10497, entity); // Maelstrom Pirate property
|
||||
} else if (nextActionType == "SpawnRonin") {
|
||||
Spawn(10498, entity); // Dark Ronin property
|
||||
} else if (nextActionType == "DropImagination") {
|
||||
for (; numberAsInt > 0; numberAsInt--) SpawnDrop(935, entity); // 1 Imagination powerup
|
||||
} else if (nextActionType == "DropHealth") {
|
||||
for (; numberAsInt > 0; numberAsInt--) SpawnDrop(177, entity); // 1 Life powerup
|
||||
} else if (nextActionType == "DropArmor") {
|
||||
for (; numberAsInt > 0; numberAsInt--) SpawnDrop(6431, entity); // 1 Armor powerup
|
||||
} else if (nextActionType == "Smash") {
|
||||
if (!modelComponent.IsUnSmashing()) {
|
||||
GameMessages::Smash smash{};
|
||||
smash.target = entity.GetObjectID();
|
||||
smash.killerID = entity.GetObjectID();
|
||||
smash.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
}
|
||||
} else if (nextActionType == "UnSmash") {
|
||||
GameMessages::UnSmash unsmash{};
|
||||
unsmash.target = entity.GetObjectID();
|
||||
unsmash.duration = number;
|
||||
unsmash.builderID = LWOOBJID_EMPTY;
|
||||
unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
modelComponent.AddUnSmash();
|
||||
|
||||
m_PausedTime = number;
|
||||
} else if (nextActionType == "Wait") {
|
||||
m_PausedTime = number;
|
||||
} else if (nextActionType == "PlaySound") {
|
||||
GameMessages::PlayBehaviorSound sound;
|
||||
sound.target = modelComponent.GetParent()->GetObjectID();
|
||||
sound.soundID = numberAsInt;
|
||||
sound.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
} else {
|
||||
static std::set<std::string> g_WarnedActions;
|
||||
if (!g_WarnedActions.contains(nextActionType.data())) {
|
||||
LOG("Tried to play action (%s) which is not supported.", nextActionType.data());
|
||||
g_WarnedActions.insert(nextActionType.data());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
IncrementAction();
|
||||
}
|
||||
|
||||
// Decrement references to the previous state if we have progressed to the next one.
|
||||
void Strip::RemoveStates(ModelComponent& modelComponent) const {
|
||||
const auto& prevAction = GetPreviousAction();
|
||||
const auto prevActionType = prevAction.GetType();
|
||||
|
||||
if (prevActionType == "OnInteract") {
|
||||
modelComponent.RemoveInteract();
|
||||
Game::entityManager->SerializeEntity(modelComponent.GetParent());
|
||||
} else if (prevActionType == "UnSmash") {
|
||||
modelComponent.RemoveUnSmash();
|
||||
}
|
||||
}
|
||||
|
||||
void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
|
||||
m_PausedTime -= deltaTime;
|
||||
if (m_PausedTime > 0.0f) return;
|
||||
|
||||
m_PausedTime = 0.0f;
|
||||
|
||||
if (m_WaitingForAction) return;
|
||||
|
||||
auto& entity = *modelComponent.GetParent();
|
||||
auto& nextAction = GetNextAction();
|
||||
|
||||
RemoveStates(modelComponent);
|
||||
|
||||
// Check for starting blocks and if not a starting block proc this blocks action
|
||||
if (m_NextActionIndex == 0) {
|
||||
if (nextAction.GetType() == "OnInteract") {
|
||||
modelComponent.AddInteract();
|
||||
Game::entityManager->SerializeEntity(entity);
|
||||
m_WaitingForAction = true;
|
||||
|
||||
}
|
||||
} else { // should be a normal block
|
||||
ProcNormalAction(deltaTime, modelComponent);
|
||||
}
|
||||
}
|
||||
|
||||
void Strip::SendBehaviorBlocksToClient(AMFArrayValue& args) const {
|
||||
m_Position.SendBehaviorBlocksToClient(args);
|
||||
@ -106,3 +242,13 @@ void Strip::Deserialize(const tinyxml2::XMLElement& strip) {
|
||||
action.Deserialize(*actionElement);
|
||||
}
|
||||
}
|
||||
|
||||
const Action& Strip::GetNextAction() const {
|
||||
DluAssert(m_NextActionIndex < m_Actions.size()); return m_Actions[m_NextActionIndex];
|
||||
}
|
||||
|
||||
const Action& Strip::GetPreviousAction() const {
|
||||
DluAssert(m_NextActionIndex < m_Actions.size());
|
||||
size_t index = m_NextActionIndex == 0 ? m_Actions.size() - 1 : m_NextActionIndex - 1;
|
||||
return m_Actions[index];
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ namespace tinyxml2 {
|
||||
}
|
||||
|
||||
class AMFArrayValue;
|
||||
class ModelComponent;
|
||||
|
||||
class Strip {
|
||||
public:
|
||||
@ -22,8 +23,30 @@ public:
|
||||
|
||||
void Serialize(tinyxml2::XMLElement& strip) const;
|
||||
void Deserialize(const tinyxml2::XMLElement& strip);
|
||||
|
||||
const Action& GetNextAction() const;
|
||||
const Action& GetPreviousAction() const;
|
||||
|
||||
void IncrementAction();
|
||||
void Spawn(LOT object, Entity& entity);
|
||||
void Update(float deltaTime, ModelComponent& modelComponent);
|
||||
void SpawnDrop(LOT dropLOT, Entity& entity);
|
||||
void ProcNormalAction(float deltaTime, ModelComponent& modelComponent);
|
||||
void RemoveStates(ModelComponent& modelComponent) const;
|
||||
private:
|
||||
// Indicates this Strip is waiting for an action to be taken upon it to progress to its actions
|
||||
bool m_WaitingForAction{ false };
|
||||
|
||||
// The amount of time this strip is paused for. Any interactions with this strip should be bounced if this is greater than 0.
|
||||
float m_PausedTime{ 0.0f };
|
||||
|
||||
// The index of the next action to be played. This should always be within range of [0, m_Actions.size()).
|
||||
size_t m_NextActionIndex{ 0 };
|
||||
|
||||
// The list of actions to be executed on this behavior.
|
||||
std::vector<Action> m_Actions;
|
||||
|
||||
// The location of this strip on the UGBehaviorEditor UI
|
||||
StripUiPosition m_Position;
|
||||
};
|
||||
|
||||
|
@ -81,7 +81,7 @@ namespace Mail {
|
||||
} else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) {
|
||||
response.status = eSendResponse::CannotMailSelf;
|
||||
} else {
|
||||
uint32_t mailCost = Game::zoneManager->GetWorldConfig()->mailBaseFee;
|
||||
uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee;
|
||||
uint32_t stackSize = 0;
|
||||
|
||||
auto inventoryComponent = player->GetComponent<InventoryComponent>();
|
||||
@ -92,7 +92,7 @@ namespace Mail {
|
||||
if (hasAttachment) {
|
||||
item = inventoryComponent->FindItemById(mailInfo.itemID);
|
||||
if (item) {
|
||||
mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig()->mailPercentAttachmentFee);
|
||||
mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig().mailPercentAttachmentFee);
|
||||
mailInfo.itemLOT = item->GetLot();
|
||||
}
|
||||
}
|
||||
|
@ -37,9 +37,9 @@ Instance* InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LW
|
||||
// If we are shutting down, return a nullptr so a new instance is not created.
|
||||
if (m_IsShuttingDown) {
|
||||
LOG("Tried to create a new instance map/instance/clone %i/%i/%i, but Master is shutting down.",
|
||||
mapID,
|
||||
m_LastInstanceID + 1,
|
||||
cloneID);
|
||||
mapID,
|
||||
m_LastInstanceID + 1,
|
||||
cloneID);
|
||||
return nullptr;
|
||||
}
|
||||
//TODO: Update this so that the IP is read from a configuration file instead
|
||||
@ -292,9 +292,9 @@ Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID clon
|
||||
|
||||
if (m_IsShuttingDown) {
|
||||
LOG("Tried to create a new private instance map/instance/clone %i/%i/%i, but Master is shutting down.",
|
||||
mapID,
|
||||
m_LastInstanceID + 1,
|
||||
cloneID);
|
||||
mapID,
|
||||
m_LastInstanceID + 1,
|
||||
cloneID);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -333,29 +333,17 @@ Instance* InstanceManager::FindPrivateInstance(const std::string& password) {
|
||||
}
|
||||
|
||||
int InstanceManager::GetSoftCap(LWOMAPID mapID) {
|
||||
CDZoneTableTable* zoneTable = CDClientManager::GetTable<CDZoneTableTable>();
|
||||
if (zoneTable) {
|
||||
const CDZoneTable* zone = zoneTable->Query(mapID);
|
||||
const CDZoneTable* zone = CDZoneTableTable::Query(mapID);
|
||||
|
||||
if (zone != nullptr) {
|
||||
return zone->population_soft_cap;
|
||||
}
|
||||
}
|
||||
|
||||
return 8;
|
||||
// Default to 8 which is the cap for most worlds.
|
||||
return zone ? zone->population_soft_cap : 8;
|
||||
}
|
||||
|
||||
int InstanceManager::GetHardCap(LWOMAPID mapID) {
|
||||
CDZoneTableTable* zoneTable = CDClientManager::GetTable<CDZoneTableTable>();
|
||||
if (zoneTable) {
|
||||
const CDZoneTable* zone = zoneTable->Query(mapID);
|
||||
const CDZoneTable* zone = CDZoneTableTable::Query(mapID);
|
||||
|
||||
if (zone != nullptr) {
|
||||
return zone->population_hard_cap;
|
||||
}
|
||||
}
|
||||
|
||||
return 12;
|
||||
// Default to 12 which is the cap for most worlds.
|
||||
return zone ? zone->population_hard_cap : 12;
|
||||
}
|
||||
|
||||
void Instance::SetShutdownComplete(const bool value) {
|
||||
|
@ -69,22 +69,19 @@ namespace {
|
||||
|
||||
void PerformanceManager::SelectProfile(LWOMAPID mapID) {
|
||||
// Try to get it from zoneTable
|
||||
CDZoneTableTable* zoneTable = CDClientManager::GetTable<CDZoneTableTable>();
|
||||
if (zoneTable) {
|
||||
const CDZoneTable* zone = zoneTable->Query(mapID);
|
||||
if (zone) {
|
||||
if (zone->serverPhysicsFramerate == "high") {
|
||||
m_CurrentProfile = highFrameDelta;
|
||||
return;
|
||||
}
|
||||
if (zone->serverPhysicsFramerate == "medium") {
|
||||
m_CurrentProfile = mediumFrameDelta;
|
||||
return;
|
||||
}
|
||||
if (zone->serverPhysicsFramerate == "low") {
|
||||
m_CurrentProfile = lowFrameDelta;
|
||||
return;
|
||||
}
|
||||
const CDZoneTable* zone = CDZoneTableTable::Query(mapID);
|
||||
if (zone) {
|
||||
if (zone->serverPhysicsFramerate == "high") {
|
||||
m_CurrentProfile = highFrameDelta;
|
||||
return;
|
||||
}
|
||||
if (zone->serverPhysicsFramerate == "medium") {
|
||||
m_CurrentProfile = mediumFrameDelta;
|
||||
return;
|
||||
}
|
||||
if (zone->serverPhysicsFramerate == "low") {
|
||||
m_CurrentProfile = lowFrameDelta;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1104,12 +1104,13 @@ void HandlePacket(Packet* packet) {
|
||||
bool complete = true;
|
||||
for (auto missionID : missions) {
|
||||
auto* mission = missionComponent->GetMission(missionID);
|
||||
if (!mission->IsComplete()) {
|
||||
if (!mission || !mission->IsComplete()) {
|
||||
complete = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (complete) missionComponent->CompleteMission(937 /* Nexus Force explorer */);
|
||||
levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE);
|
||||
[[fallthrough]];
|
||||
}
|
||||
case eCharacterVersion::UP_TO_DATE:
|
||||
@ -1153,6 +1154,8 @@ 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
|
||||
@ -1452,7 +1455,6 @@ 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());
|
||||
}
|
||||
|
||||
|
@ -253,8 +253,8 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) {
|
||||
//This is a little bit of a bodge, but because the alpha client (HF) doesn't store the
|
||||
//spawn position / rotation like the later versions do, we need to check the LOT for the spawn pos & set it.
|
||||
if (obj.lot == LOT_MARKER_PLAYER_START) {
|
||||
Game::zoneManager->GetZone()->SetSpawnPos(obj.position);
|
||||
Game::zoneManager->GetZone()->SetSpawnRot(obj.rotation);
|
||||
Game::zoneManager->GetZoneMut()->SetSpawnPos(obj.position);
|
||||
Game::zoneManager->GetZoneMut()->SetSpawnRot(obj.rotation);
|
||||
}
|
||||
|
||||
std::string sData = GeneralUtils::UTF16ToWTF8(ldfString);
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include <string>
|
||||
|
||||
struct WorldConfig {
|
||||
int32_t worldConfigID{}; //! Primary key for WorlcConfig table
|
||||
float peGravityValue{}; //! Unknown
|
||||
float peBroadphaseWorldSize{}; //! Unknown
|
||||
float peGameObjScaleFactor{}; //! Unknown
|
||||
@ -53,8 +52,8 @@ struct WorldConfig {
|
||||
float reputationPerVoteReceived{}; //! Unknown
|
||||
int32_t showcaseTopModelConsiderationBattles{}; //! Unknown
|
||||
float reputationPerBattlePromotion{}; //! Unknown
|
||||
float coinsLostOnDeathMinTimeout{}; //! Unknown
|
||||
float coinsLostOnDeathMaxTimeout{}; //! Unknown
|
||||
float coinsLostOnDeathMinTimeout{}; //! Minimum amount of time coins lost on death will remain on the playfield.
|
||||
float coinsLostOnDeathMaxTimeout{}; //! Maximum amount of time coins lost on death will remain on the playfield.
|
||||
int32_t mailBaseFee{}; //! The base fee to take when a player sends mail
|
||||
float mailPercentAttachmentFee{}; //! The scalar multiplied by an items base cost to determine how much that item costs to be mailed
|
||||
int32_t propertyReputationDelay{}; //! Unknown
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "Level.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <ranges>
|
||||
#include "Game.h"
|
||||
#include "Logger.h"
|
||||
#include "GeneralUtils.h"
|
||||
@ -20,8 +21,8 @@
|
||||
#include "eWaypointCommandType.h"
|
||||
#include "dNavMesh.h"
|
||||
|
||||
Zone::Zone(const LWOMAPID& mapID, const LWOINSTANCEID& instanceID, const LWOCLONEID& cloneID) :
|
||||
m_ZoneID(mapID, instanceID, cloneID) {
|
||||
Zone::Zone(const LWOZONEID zoneID) :
|
||||
m_ZoneID(zoneID) {
|
||||
m_NumberOfObjectsLoaded = 0;
|
||||
m_NumberOfSceneTransitionsLoaded = 0;
|
||||
m_CheckSum = 0;
|
||||
@ -31,9 +32,6 @@ Zone::Zone(const LWOMAPID& mapID, const LWOINSTANCEID& instanceID, const LWOCLON
|
||||
|
||||
Zone::~Zone() {
|
||||
LOG("Destroying zone %i", m_ZoneID.GetMapID());
|
||||
for (std::map<LWOSCENEID, SceneRef>::iterator it = m_Scenes.begin(); it != m_Scenes.end(); ++it) {
|
||||
if (it->second.level != nullptr) delete it->second.level;
|
||||
}
|
||||
}
|
||||
|
||||
void Zone::Initalize() {
|
||||
@ -153,23 +151,25 @@ void Zone::LoadZoneIntoMemory() {
|
||||
m_ZonePath = m_ZoneFilePath.substr(0, m_ZoneFilePath.rfind('/') + 1);
|
||||
}
|
||||
|
||||
std::string Zone::GetFilePathForZoneID() {
|
||||
std::string Zone::GetFilePathForZoneID() const {
|
||||
//We're gonna go ahead and presume we've got the db loaded already:
|
||||
CDZoneTableTable* zoneTable = CDClientManager::GetTable<CDZoneTableTable>();
|
||||
const CDZoneTable* zone = zoneTable->Query(this->GetZoneID().GetMapID());
|
||||
const CDZoneTable* zone = CDZoneTableTable::Query(this->GetZoneID().GetMapID());
|
||||
std::string toReturn("ERR");
|
||||
if (zone != nullptr) {
|
||||
std::string toReturn = "maps/" + zone->zoneName;
|
||||
toReturn = "maps/" + zone->zoneName;
|
||||
std::transform(toReturn.begin(), toReturn.end(), toReturn.begin(), ::tolower);
|
||||
|
||||
/* Normalize to one slash type */
|
||||
std::ranges::replace(toReturn, '\\', '/');
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
return std::string("ERR");
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
//Based off code from: https://www.liquisearch.com/fletchers_checksum/implementation/optimizations
|
||||
uint32_t Zone::CalculateChecksum() {
|
||||
uint32_t sum1 = 0xffff, sum2 = 0xffff;
|
||||
uint32_t Zone::CalculateChecksum() const {
|
||||
uint32_t sum1 = 0xffff;
|
||||
uint32_t sum2 = 0xffff;
|
||||
|
||||
for (const auto& [scene, sceneRevision] : m_MapRevisions) {
|
||||
uint32_t sceneID = scene.GetSceneID();
|
||||
@ -194,7 +194,7 @@ uint32_t Zone::CalculateChecksum() {
|
||||
void Zone::LoadLevelsIntoMemory() {
|
||||
for (auto& [sceneID, scene] : m_Scenes) {
|
||||
if (scene.level) continue;
|
||||
scene.level = new Level(this, m_ZonePath + scene.filename);
|
||||
scene.level = std::make_unique<Level>(this, m_ZonePath + scene.filename);
|
||||
|
||||
if (scene.level->m_ChunkHeaders.empty()) continue;
|
||||
|
||||
@ -241,7 +241,7 @@ void Zone::LoadScene(std::istream& file) {
|
||||
BinaryIO::BinaryRead(file, scene.color_g);
|
||||
}
|
||||
|
||||
m_Scenes.insert(std::make_pair(lwoSceneID, scene));
|
||||
m_Scenes[lwoSceneID] = std::move(scene);
|
||||
}
|
||||
|
||||
void Zone::LoadLUTriggers(std::string triggerFile, SceneRef& scene) {
|
||||
@ -299,7 +299,7 @@ void Zone::LoadLUTriggers(std::string triggerFile, SceneRef& scene) {
|
||||
}
|
||||
}
|
||||
|
||||
LUTriggers::Trigger* Zone::GetTrigger(uint32_t sceneID, uint32_t triggerID) {
|
||||
LUTriggers::Trigger* Zone::GetTrigger(uint32_t sceneID, uint32_t triggerID) const {
|
||||
auto scene = m_Scenes.find(sceneID);
|
||||
if (scene == m_Scenes.end()) return nullptr;
|
||||
|
||||
|
@ -31,7 +31,7 @@ struct SceneRef {
|
||||
uint8_t color_r{};
|
||||
uint8_t color_g{};
|
||||
uint8_t color_b{};
|
||||
Level* level;
|
||||
std::unique_ptr<Level> level;
|
||||
std::map<uint32_t, LUTriggers::Trigger*> triggers;
|
||||
};
|
||||
|
||||
@ -203,18 +203,18 @@ public:
|
||||
};
|
||||
|
||||
public:
|
||||
Zone(const LWOMAPID& mapID, const LWOINSTANCEID& instanceID, const LWOCLONEID& cloneID);
|
||||
Zone(const LWOZONEID zoneID);
|
||||
~Zone();
|
||||
|
||||
void Initalize();
|
||||
void LoadZoneIntoMemory();
|
||||
std::string GetFilePathForZoneID();
|
||||
uint32_t CalculateChecksum();
|
||||
std::string GetFilePathForZoneID() const;
|
||||
uint32_t CalculateChecksum() const;
|
||||
void LoadLevelsIntoMemory();
|
||||
void AddRevision(LWOSCENEID sceneID, uint32_t revision);
|
||||
const LWOZONEID& GetZoneID() const { return m_ZoneID; }
|
||||
const uint32_t GetChecksum() const { return m_CheckSum; }
|
||||
LUTriggers::Trigger* GetTrigger(uint32_t sceneID, uint32_t triggerID);
|
||||
LUTriggers::Trigger* GetTrigger(uint32_t sceneID, uint32_t triggerID) const;
|
||||
const Path* GetPath(std::string name) const;
|
||||
|
||||
uint32_t GetWorldID() const { return m_WorldID; }
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "eObjectBits.h"
|
||||
#include "CDZoneTableTable.h"
|
||||
#include "AssetManager.h"
|
||||
#include <ranges>
|
||||
|
||||
#include "ObjectIDManager.h"
|
||||
|
||||
@ -29,21 +30,18 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) {
|
||||
|
||||
LOT zoneControlTemplate = 2365;
|
||||
|
||||
CDZoneTableTable* zoneTable = CDClientManager::GetTable<CDZoneTableTable>();
|
||||
if (zoneTable != nullptr) {
|
||||
const CDZoneTable* zone = zoneTable->Query(zoneID.GetMapID());
|
||||
const CDZoneTable* zone = CDZoneTableTable::Query(zoneID.GetMapID());
|
||||
|
||||
if (zone != nullptr) {
|
||||
zoneControlTemplate = zone->zoneControlTemplate != -1 ? zone->zoneControlTemplate : 2365;
|
||||
const auto min = zone->ghostdistance_min != -1.0f ? zone->ghostdistance_min : 100;
|
||||
const auto max = zone->ghostdistance != -1.0f ? zone->ghostdistance : 100;
|
||||
Game::entityManager->SetGhostDistanceMax(max + min);
|
||||
Game::entityManager->SetGhostDistanceMin(max);
|
||||
m_PlayerLoseCoinsOnDeath = zone->PlayerLoseCoinsOnDeath;
|
||||
m_DisableSaveLocation = zone->disableSaveLoc;
|
||||
m_MountsAllowed = zone->mountsAllowed;
|
||||
m_PetsAllowed = zone->petsAllowed;
|
||||
}
|
||||
if (zone != nullptr) {
|
||||
zoneControlTemplate = zone->zoneControlTemplate != -1 ? zone->zoneControlTemplate : 2365;
|
||||
const auto min = zone->ghostdistance_min != -1.0f ? zone->ghostdistance_min : 100;
|
||||
const auto max = zone->ghostdistance != -1.0f ? zone->ghostdistance : 100;
|
||||
Game::entityManager->SetGhostDistanceMax(max + min);
|
||||
Game::entityManager->SetGhostDistanceMin(max);
|
||||
m_PlayerLoseCoinsOnDeath = zone->PlayerLoseCoinsOnDeath;
|
||||
m_DisableSaveLocation = zone->disableSaveLoc;
|
||||
m_MountsAllowed = zone->mountsAllowed;
|
||||
m_PetsAllowed = zone->petsAllowed;
|
||||
}
|
||||
|
||||
LOG("Creating zone control object %i", zoneControlTemplate);
|
||||
@ -56,7 +54,9 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) {
|
||||
Game::entityManager->Initialize();
|
||||
EntityInfo info;
|
||||
info.lot = zoneControlTemplate;
|
||||
info.id = 70368744177662;
|
||||
|
||||
/* Yep its hardcoded like this in the client too, this exact value. */
|
||||
info.id = 0x3FFF'FFFFFFFELL;
|
||||
Entity* zoneControl = Game::entityManager->CreateEntity(info, nullptr, nullptr, true);
|
||||
m_ZoneControlObject = zoneControl;
|
||||
|
||||
@ -74,16 +74,16 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) {
|
||||
dZoneManager::~dZoneManager() {
|
||||
if (m_pZone) delete m_pZone;
|
||||
|
||||
for (std::pair<LWOOBJID, Spawner*> p : m_Spawners) {
|
||||
if (p.second) {
|
||||
delete p.second;
|
||||
p.second = nullptr;
|
||||
for (auto* spawner : m_Spawners | std::views::values) {
|
||||
if (spawner) {
|
||||
delete spawner;
|
||||
spawner = nullptr;
|
||||
}
|
||||
}
|
||||
if (m_WorldConfig) delete m_WorldConfig;
|
||||
}
|
||||
|
||||
Zone* dZoneManager::GetZone() {
|
||||
Zone* dZoneManager::GetZoneMut() const {
|
||||
DluAssert(m_pZone);
|
||||
return m_pZone;
|
||||
}
|
||||
|
||||
@ -91,20 +91,20 @@ void dZoneManager::LoadZone(const LWOZONEID& zoneID) {
|
||||
if (m_pZone) delete m_pZone;
|
||||
|
||||
m_ZoneID = zoneID;
|
||||
m_pZone = new Zone(zoneID.GetMapID(), zoneID.GetInstanceID(), zoneID.GetCloneID());
|
||||
m_pZone = new Zone(zoneID);
|
||||
}
|
||||
|
||||
void dZoneManager::AddSpawner(LWOOBJID id, Spawner* spawner) {
|
||||
m_Spawners.insert_or_assign(id, spawner);
|
||||
m_Spawners[id] = spawner;
|
||||
}
|
||||
|
||||
LWOZONEID dZoneManager::GetZoneID() const {
|
||||
const LWOZONEID& dZoneManager::GetZoneID() const {
|
||||
return m_ZoneID;
|
||||
}
|
||||
|
||||
void dZoneManager::Update(float deltaTime) {
|
||||
for (auto spawner : m_Spawners) {
|
||||
spawner.second->Update(deltaTime);
|
||||
for (auto spawner : m_Spawners | std::views::values) {
|
||||
spawner->Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,12 +136,7 @@ LWOOBJID dZoneManager::MakeSpawner(SpawnerInfo info) {
|
||||
|
||||
Spawner* dZoneManager::GetSpawner(const LWOOBJID id) {
|
||||
const auto& index = m_Spawners.find(id);
|
||||
|
||||
if (index == m_Spawners.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return index->second;
|
||||
return index != m_Spawners.end() ? index->second : nullptr;
|
||||
}
|
||||
|
||||
void dZoneManager::RemoveSpawner(const LWOOBJID id) {
|
||||
@ -154,10 +149,11 @@ void dZoneManager::RemoveSpawner(const LWOOBJID id) {
|
||||
|
||||
auto* entity = Game::entityManager->GetEntity(id);
|
||||
|
||||
LOG("Destroying spawner (%llu)", id);
|
||||
|
||||
if (entity != nullptr) {
|
||||
entity->Kill();
|
||||
} else {
|
||||
|
||||
LOG("Failed to find spawner entity (%llu)", id);
|
||||
}
|
||||
|
||||
@ -165,7 +161,7 @@ void dZoneManager::RemoveSpawner(const LWOOBJID id) {
|
||||
|
||||
spawner->Deactivate();
|
||||
|
||||
LOG("Destroying spawner (%llu)", id);
|
||||
LOG("Destroyed spawner (%llu)", id);
|
||||
|
||||
m_Spawners.erase(id);
|
||||
|
||||
@ -173,24 +169,23 @@ void dZoneManager::RemoveSpawner(const LWOOBJID id) {
|
||||
}
|
||||
|
||||
|
||||
std::vector<Spawner*> dZoneManager::GetSpawnersByName(std::string spawnerName) {
|
||||
std::vector<Spawner*> dZoneManager::GetSpawnersByName(const std::string& spawnerName) {
|
||||
std::vector<Spawner*> spawners;
|
||||
for (const auto& spawner : m_Spawners) {
|
||||
if (spawner.second->GetName() == spawnerName) {
|
||||
spawners.push_back(spawner.second);
|
||||
for (const auto& spawner : m_Spawners | std::views::values) {
|
||||
if (spawner->GetName() == spawnerName) {
|
||||
spawners.push_back(spawner);
|
||||
}
|
||||
}
|
||||
|
||||
return spawners;
|
||||
}
|
||||
|
||||
std::vector<Spawner*> dZoneManager::GetSpawnersInGroup(std::string group) {
|
||||
std::vector<Spawner*> dZoneManager::GetSpawnersInGroup(const std::string& group) {
|
||||
std::vector<Spawner*> spawnersInGroup;
|
||||
for (auto spawner : m_Spawners) {
|
||||
for (std::string entityGroup : spawner.second->m_Info.groups) {
|
||||
if (entityGroup == group) {
|
||||
spawnersInGroup.push_back(spawner.second);
|
||||
}
|
||||
for (auto spawner : m_Spawners | std::views::values) {
|
||||
const auto& groups = spawner->m_Info.groups;
|
||||
if (std::ranges::find(groups, group) != groups.end()) {
|
||||
spawnersInGroup.push_back(spawner);
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,71 +195,83 @@ std::vector<Spawner*> dZoneManager::GetSpawnersInGroup(std::string group) {
|
||||
uint32_t dZoneManager::GetUniqueMissionIdStartingValue() {
|
||||
if (m_UniqueMissionIdStart == 0) {
|
||||
auto tableData = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM Missions WHERE isMission = 0 GROUP BY isMission;");
|
||||
m_UniqueMissionIdStart = tableData.getIntField(0, -1);
|
||||
tableData.finalize();
|
||||
m_UniqueMissionIdStart = tableData.getIntField(0, 1);
|
||||
}
|
||||
return m_UniqueMissionIdStart;
|
||||
}
|
||||
|
||||
bool dZoneManager::CheckIfAccessibleZone(LWOMAPID zoneID) {
|
||||
//We're gonna go ahead and presume we've got the db loaded already:
|
||||
CDZoneTableTable* zoneTable = CDClientManager::GetTable<CDZoneTableTable>();
|
||||
const CDZoneTable* zone = zoneTable->Query(zoneID);
|
||||
if (zone != nullptr) {
|
||||
return Game::assetManager->HasFile(("maps/" + zone->zoneName).c_str());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
const CDZoneTable* zone = CDZoneTableTable::Query(zoneID);
|
||||
return zone && Game::assetManager->HasFile("maps/" + zone->zoneName);
|
||||
}
|
||||
|
||||
void dZoneManager::LoadWorldConfig() {
|
||||
LOG("Loading WorldConfig into memory");
|
||||
// Already loaded
|
||||
if (m_WorldConfig) return;
|
||||
|
||||
auto worldConfig = CDClientDatabase::ExecuteQuery("SELECT * FROM WorldConfig;");
|
||||
LOG_DEBUG("Loading WorldConfig into memory");
|
||||
|
||||
if (!m_WorldConfig) m_WorldConfig = new WorldConfig();
|
||||
// This table only has 1 row and only should have 1 row.
|
||||
auto worldConfig = CDClientDatabase::ExecuteQuery("SELECT * FROM WorldConfig LIMIT 1;");
|
||||
|
||||
m_WorldConfig = WorldConfig();
|
||||
|
||||
if (worldConfig.eof()) {
|
||||
LOG("WorldConfig table is empty. Is this intended?");
|
||||
LOG("WorldConfig table is empty. Is this intended?");
|
||||
return;
|
||||
}
|
||||
|
||||
// Now read in the giant table
|
||||
m_WorldConfig->worldConfigID = worldConfig.getIntField("WorldConfigID");
|
||||
m_WorldConfig->peGravityValue = worldConfig.getFloatField("pegravityvalue");
|
||||
m_WorldConfig->peBroadphaseWorldSize = worldConfig.getFloatField("pebroadphaseworldsize");
|
||||
m_WorldConfig->peGameObjScaleFactor = worldConfig.getFloatField("pegameobjscalefactor");
|
||||
|
||||
m_WorldConfig->characterRotationSpeed = worldConfig.getFloatField("character_rotation_speed");
|
||||
|
||||
m_WorldConfig->characterWalkForwardSpeed = worldConfig.getFloatField("character_walk_forward_speed");
|
||||
m_WorldConfig->characterWalkBackwardSpeed = worldConfig.getFloatField("character_walk_backward_speed");
|
||||
m_WorldConfig->characterWalkStrafeSpeed = worldConfig.getFloatField("character_walk_strafe_speed");
|
||||
m_WorldConfig->characterWalkStrafeForwardSpeed = worldConfig.getFloatField("character_walk_strafe_forward_speed");
|
||||
m_WorldConfig->characterWalkStrafeBackwardSpeed = worldConfig.getFloatField("character_walk_strafe_backward_speed");
|
||||
|
||||
m_WorldConfig->characterRunBackwardSpeed = worldConfig.getFloatField("character_run_backward_speed");
|
||||
m_WorldConfig->characterRunStrafeSpeed = worldConfig.getFloatField("character_run_strafe_speed");
|
||||
m_WorldConfig->characterRunStrafeForwardSpeed = worldConfig.getFloatField("character_run_strafe_forward_speed");
|
||||
m_WorldConfig->characterRunStrafeBackwardSpeed = worldConfig.getFloatField("character_run_strafe_backward_speed");
|
||||
m_WorldConfig->globalCooldown = worldConfig.getFloatField("global_cooldown");
|
||||
|
||||
m_WorldConfig->characterGroundedTime = worldConfig.getFloatField("characterGroundedTime");
|
||||
m_WorldConfig->characterGroundedSpeed = worldConfig.getFloatField("characterGroundedSpeed");
|
||||
m_WorldConfig->globalImmunityTime = worldConfig.getFloatField("globalImmunityTime");
|
||||
|
||||
m_WorldConfig->characterVersion = worldConfig.getIntField("CharacterVersion");
|
||||
m_WorldConfig->characterEyeHeight = worldConfig.getFloatField("character_eye_height");
|
||||
m_WorldConfig->characterMaxSlope = worldConfig.getFloatField("character_max_slope");
|
||||
|
||||
m_WorldConfig->globalCooldown = worldConfig.getFloatField("global_cooldown");
|
||||
m_WorldConfig->globalImmunityTime = worldConfig.getFloatField("globalImmunityTime");
|
||||
|
||||
m_WorldConfig->defaultRespawnTime = worldConfig.getFloatField("defaultrespawntime");
|
||||
m_WorldConfig->missionTooltipTimeout = worldConfig.getFloatField("mission_tooltip_timeout");
|
||||
m_WorldConfig->vendorBuyMultiplier = worldConfig.getFloatField("vendor_buy_multiplier", 0.1);
|
||||
m_WorldConfig->petFollowRadius = worldConfig.getFloatField("pet_follow_radius");
|
||||
m_WorldConfig->characterEyeHeight = worldConfig.getFloatField("character_eye_height");
|
||||
|
||||
m_WorldConfig->flightVerticalVelocity = worldConfig.getFloatField("flight_vertical_velocity");
|
||||
m_WorldConfig->flightAirspeed = worldConfig.getFloatField("flight_airspeed");
|
||||
m_WorldConfig->flightFuelRatio = worldConfig.getFloatField("flight_fuel_ratio");
|
||||
m_WorldConfig->flightMaxAirspeed = worldConfig.getFloatField("flight_max_airspeed");
|
||||
m_WorldConfig->fReputationPerVote = worldConfig.getFloatField("fReputationPerVote");
|
||||
m_WorldConfig->propertyCloneLimit = worldConfig.getIntField("nPropertyCloneLimit");
|
||||
|
||||
m_WorldConfig->defaultHomespaceTemplate = worldConfig.getIntField("defaultHomespaceTemplate");
|
||||
|
||||
m_WorldConfig->coinsLostOnDeathPercent = worldConfig.getFloatField("coins_lost_on_death_percent");
|
||||
m_WorldConfig->coinsLostOnDeathMin = worldConfig.getIntField("coins_lost_on_death_min");
|
||||
m_WorldConfig->coinsLostOnDeathMax = worldConfig.getIntField("coins_lost_on_death_max");
|
||||
m_WorldConfig->coinsLostOnDeathMinTimeout = worldConfig.getFloatField("coins_lost_on_death_min_timeout");
|
||||
m_WorldConfig->coinsLostOnDeathMaxTimeout = worldConfig.getFloatField("coins_lost_on_death_max_timeout");
|
||||
|
||||
m_WorldConfig->characterVotesPerDay = worldConfig.getIntField("character_votes_per_day");
|
||||
|
||||
m_WorldConfig->defaultPropertyMaxHeight = worldConfig.getFloatField("defaultPropertyMaxHeight");
|
||||
m_WorldConfig->propertyCloneLimit = worldConfig.getIntField("nPropertyCloneLimit");
|
||||
m_WorldConfig->propertyReputationDelay = worldConfig.getIntField("propertyReputationDelay");
|
||||
m_WorldConfig->propertyModerationRequestApprovalCost = worldConfig.getIntField("property_moderation_request_approval_cost");
|
||||
m_WorldConfig->propertyModerationRequestReviewCost = worldConfig.getIntField("property_moderation_request_review_cost");
|
||||
m_WorldConfig->propertyModRequestsAllowedSpike = worldConfig.getIntField("propertyModRequestsAllowedSpike");
|
||||
@ -272,21 +279,22 @@ void dZoneManager::LoadWorldConfig() {
|
||||
m_WorldConfig->propertyModRequestsAllowedTotal = worldConfig.getIntField("propertyModRequestsAllowedTotal");
|
||||
m_WorldConfig->propertyModRequestsSpikeDuration = worldConfig.getIntField("propertyModRequestsSpikeDuration");
|
||||
m_WorldConfig->propertyModRequestsIntervalDuration = worldConfig.getIntField("propertyModRequestsIntervalDuration");
|
||||
|
||||
m_WorldConfig->modelModerateOnCreate = worldConfig.getIntField("modelModerateOnCreate") != 0;
|
||||
m_WorldConfig->defaultPropertyMaxHeight = worldConfig.getFloatField("defaultPropertyMaxHeight");
|
||||
|
||||
m_WorldConfig->fReputationPerVote = worldConfig.getFloatField("fReputationPerVote");
|
||||
m_WorldConfig->reputationPerVoteCast = worldConfig.getFloatField("reputationPerVoteCast");
|
||||
m_WorldConfig->reputationPerVoteReceived = worldConfig.getFloatField("reputationPerVoteReceived");
|
||||
m_WorldConfig->showcaseTopModelConsiderationBattles = worldConfig.getIntField("showcaseTopModelConsiderationBattles");
|
||||
m_WorldConfig->reputationPerBattlePromotion = worldConfig.getFloatField("reputationPerBattlePromotion");
|
||||
m_WorldConfig->coinsLostOnDeathMinTimeout = worldConfig.getFloatField("coins_lost_on_death_min_timeout");
|
||||
m_WorldConfig->coinsLostOnDeathMaxTimeout = worldConfig.getFloatField("coins_lost_on_death_max_timeout");
|
||||
|
||||
m_WorldConfig->showcaseTopModelConsiderationBattles = worldConfig.getIntField("showcaseTopModelConsiderationBattles");
|
||||
|
||||
m_WorldConfig->mailBaseFee = worldConfig.getIntField("mail_base_fee");
|
||||
m_WorldConfig->mailPercentAttachmentFee = worldConfig.getFloatField("mail_percent_attachment_fee");
|
||||
m_WorldConfig->propertyReputationDelay = worldConfig.getIntField("propertyReputationDelay");
|
||||
|
||||
m_WorldConfig->levelCap = worldConfig.getIntField("LevelCap");
|
||||
m_WorldConfig->levelUpBehaviorEffect = worldConfig.getStringField("LevelUpBehaviorEffect");
|
||||
m_WorldConfig->characterVersion = worldConfig.getIntField("CharacterVersion");
|
||||
m_WorldConfig->levelCapCurrencyConversion = worldConfig.getIntField("LevelCapCurrencyConversion");
|
||||
worldConfig.finalize();
|
||||
LOG("Loaded WorldConfig into memory");
|
||||
|
||||
LOG_DEBUG("Loaded WorldConfig into memory");
|
||||
}
|
||||
|
@ -1,11 +1,10 @@
|
||||
#pragma once
|
||||
#include "dZMCommon.h"
|
||||
#include "WorldConfig.h"
|
||||
#include "Zone.h"
|
||||
#include "Spawner.h"
|
||||
#include <map>
|
||||
|
||||
class WorldConfig;
|
||||
|
||||
class dZoneManager {
|
||||
public:
|
||||
enum class dZoneNotifier {
|
||||
@ -27,28 +26,36 @@ public:
|
||||
void Initialize(const LWOZONEID& zoneID);
|
||||
~dZoneManager();
|
||||
|
||||
Zone* GetZone(); //Gets a pointer to the currently loaded zone.
|
||||
/* Gets a pointer to the currently loaded zone. */
|
||||
Zone* GetZoneMut() const;
|
||||
const Zone* GetZone() const { return GetZoneMut(); };
|
||||
void LoadZone(const LWOZONEID& zoneID); //Discard the current zone (if any) and loads a new zone.
|
||||
|
||||
/* Adds a spawner to the zone with the specified ID. */
|
||||
void AddSpawner(LWOOBJID id, Spawner* spawner);
|
||||
LWOZONEID GetZoneID() const;
|
||||
const LWOZONEID& GetZoneID() const;
|
||||
|
||||
/* Creates a new spawner. Returns the finalized ID for the created spawner since some bits may be set to get it to function. */
|
||||
LWOOBJID MakeSpawner(SpawnerInfo info);
|
||||
Spawner* GetSpawner(LWOOBJID id);
|
||||
void RemoveSpawner(LWOOBJID id);
|
||||
std::vector<Spawner*> GetSpawnersByName(std::string spawnerName);
|
||||
std::vector<Spawner*> GetSpawnersInGroup(std::string group);
|
||||
std::vector<Spawner*> GetSpawnersByName(const std::string& spawnerName);
|
||||
std::vector<Spawner*> GetSpawnersInGroup(const std::string& group);
|
||||
void Update(float deltaTime);
|
||||
Entity* GetZoneControlObject() { return m_ZoneControlObject; }
|
||||
bool GetPlayerLoseCoinOnDeath() { return m_PlayerLoseCoinsOnDeath; }
|
||||
bool GetDisableSaveLocation() { return m_DisableSaveLocation; }
|
||||
bool GetMountsAllowed() { return m_MountsAllowed; }
|
||||
bool GetPetsAllowed() { return m_PetsAllowed; }
|
||||
|
||||
/* Gets the starting ID for missions in the player UI so they are ordered properly and show in the order accepted by the player. */
|
||||
uint32_t GetUniqueMissionIdStartingValue();
|
||||
bool CheckIfAccessibleZone(LWOMAPID zoneID);
|
||||
|
||||
// The world config should not be modified by a caller.
|
||||
const WorldConfig* GetWorldConfig() {
|
||||
const WorldConfig& GetWorldConfig() {
|
||||
if (!m_WorldConfig) LoadWorldConfig();
|
||||
return m_WorldConfig;
|
||||
return m_WorldConfig.value();
|
||||
};
|
||||
|
||||
private:
|
||||
@ -64,7 +71,7 @@ private:
|
||||
bool m_MountsAllowed = true;
|
||||
bool m_PetsAllowed = true;
|
||||
std::map<LWOOBJID, Spawner*> m_Spawners;
|
||||
WorldConfig* m_WorldConfig = nullptr;
|
||||
std::optional<WorldConfig> m_WorldConfig = std::nullopt;
|
||||
|
||||
Entity* m_ZoneControlObject = nullptr;
|
||||
};
|
||||
|
1
migrations/dlu/mysql/19_normalize_model_positions.sql
Normal file
1
migrations/dlu/mysql/19_normalize_model_positions.sql
Normal file
@ -0,0 +1 @@
|
||||
/* See ModelNormalizeMigration.cpp for details */
|
1
migrations/dlu/sqlite/2_normalize_model_positions.sql
Normal file
1
migrations/dlu/sqlite/2_normalize_model_positions.sql
Normal file
@ -0,0 +1 @@
|
||||
/* See ModelNormalizeMigration.cpp for details */
|
@ -77,3 +77,6 @@ allow_players_to_skip_cinematics=0
|
||||
# Customizable message for what to say when there is a cdclient fdb mismatch
|
||||
cdclient_mismatch_title=Version out of date
|
||||
cdclient_mismatch_message=We detected that your client is out of date. Please update your client to the latest version.
|
||||
|
||||
# Auto reject properties which contain no models | must be 1 in order to auto reject.
|
||||
auto_reject_empty_properties=0
|
||||
|
Loading…
x
Reference in New Issue
Block a user