mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-12-20 11:59:40 -06:00
Compare commits
59 Commits
dCinema
...
reset-enti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e76d70c9c4 | ||
|
|
1f580491c7 | ||
|
|
2618e9a864 | ||
|
|
0f0d0a6dee | ||
|
|
f63a9a6bea | ||
|
|
f0f98a6108 | ||
|
|
4ed7bd6767 | ||
|
|
9f92f48a0f | ||
|
|
48e3471831 | ||
|
|
3c244cce27 | ||
|
|
8ba35be64d | ||
|
|
f7c9267ba4 | ||
|
|
b6e9d6872d | ||
|
|
c83797984a | ||
|
|
04487efa25 | ||
|
|
2f315d9288 | ||
|
|
6ae1c7a376 | ||
|
|
c19ee04c8a | ||
|
|
2858345269 | ||
|
|
37e14979a4 | ||
|
|
b509fd4f10 | ||
|
|
820c0f0083 | ||
|
|
68eb20966f | ||
|
|
92155a3cb4 | ||
|
|
437362cce6 | ||
|
|
34665f6f5c | ||
|
|
32487dcd5f | ||
|
|
891b176b4f | ||
|
|
e42df5b02e | ||
| 61921cfb62 | |||
|
|
91f6b2bf81 | ||
|
|
01917841cb | ||
|
|
e18c504ee4 | ||
|
|
b6f7b4c092 | ||
|
|
522299c9ec | ||
|
|
0e551429d3 | ||
|
|
c77e9ce33a | ||
|
|
3ebc6709db | ||
|
|
841b754b01 | ||
|
|
62c3f489fe | ||
|
|
5ccb8357fd | ||
|
|
4bacb8a2ee | ||
|
|
89678c4a05 | ||
|
|
4f97ecc073 | ||
|
|
c9e4cde68d | ||
|
|
0a12672889 | ||
|
|
00a69909f8 | ||
|
|
7c8ca1c1cb | ||
|
|
4930fb93b3 | ||
|
|
b31f9670d1 | ||
|
|
1cc1782b35 | ||
|
|
55d409eb82 | ||
|
|
65f3c33ca5 | ||
|
|
93fa4e268f | ||
|
|
1fb1da101c | ||
|
|
6f94043b33 | ||
|
|
5785764a95 | ||
|
|
fa53fa7935 | ||
|
|
3d595ce4ac |
5
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
5
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -16,7 +16,10 @@ body:
|
||||
I have validated that this issue is not a syntax error of either MySQL or SQLite.
|
||||
required: true
|
||||
- label: >
|
||||
I have pulled the latest version of the main branch of DarkflameServer and have confirmed that the issue exists there.
|
||||
I have downloaded/pulled the latest version of the main branch of DarkflameServer and have confirmed that the issue exists there.
|
||||
required: true
|
||||
- label: >
|
||||
I have verified that my boot.cfg is configured as per the [README](https://github.com/DarkflameUniverse/DarkflameServer?tab=readme-ov-file#allowing-a-user-to-connect-to-your-server).
|
||||
required: true
|
||||
- type: input
|
||||
id: server-version
|
||||
|
||||
@@ -235,6 +235,8 @@ include_directories(
|
||||
|
||||
"dNet"
|
||||
|
||||
"dWeb"
|
||||
|
||||
"tests"
|
||||
"tests/dCommonTests"
|
||||
"tests/dGameTests"
|
||||
@@ -301,6 +303,7 @@ add_subdirectory(dZoneManager)
|
||||
add_subdirectory(dNavigation)
|
||||
add_subdirectory(dPhysics)
|
||||
add_subdirectory(dServer)
|
||||
add_subdirectory(dWeb)
|
||||
|
||||
# Create a list of common libraries shared between all binaries
|
||||
set(COMMON_LIBRARIES "dCommon" "dDatabase" "dNet" "raknet" "magic_enum")
|
||||
|
||||
@@ -324,13 +324,15 @@ While a character has a gmlevel of anything but `0`, some gameplay behavior will
|
||||
Some changes to the client `boot.cfg` file are needed to play on your server.
|
||||
|
||||
## Allowing a user to connect to your server
|
||||
**ALL OF THESE CHANGES ARE REQUIRED. PLEASE FULLY READ THIS SECTION**
|
||||
|
||||
To connect to a server follow these steps:
|
||||
* In the client directory, locate `boot.cfg`
|
||||
* Open it in a text editor and locate where it says `AUTHSERVERIP=0:`
|
||||
* Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers
|
||||
* Next locate the line `UGCUSE3DSERVICES=7:`
|
||||
* Open `boot.cfg` in a text editor and locate the line `UGCUSE3DSERVICES=7:`
|
||||
* Ensure the number after the 7 is a `0`
|
||||
* Alternatively, remove the line with `UGCUSE3DSERVICES` altogether
|
||||
* Next locate where it says `AUTHSERVERIP=0:`
|
||||
* Replace the contents after to `:` and the following `,` with what you configured as the server's public facing IP. For example `AUTHSERVERIP=0:localhost` for locally hosted servers
|
||||
* Launch `legouniverse.exe`, through `wine` if on a Unix-like operating system
|
||||
* Note that if you are on WSL2, you will need to configure the public IP in the server and client to be the IP of the WSL2 instance and not localhost, which can be found by running `ifconfig` in the terminal. Windows defaults to WSL1, so this will not apply to most users.
|
||||
As an example, here is what the boot.cfg is required to contain for a server with the ip 12.34.56.78
|
||||
|
||||
@@ -105,7 +105,7 @@ void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool allowLis
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList) {
|
||||
std::set<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList) {
|
||||
if (gmLevel > eGameMasterLevel::FORUM_MODERATOR) return { }; //If anything but a forum mod, return true.
|
||||
if (message.empty()) return { };
|
||||
if (!allowList && m_DeniedWords.empty()) return { { 0, message.length() } };
|
||||
@@ -114,7 +114,7 @@ std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::
|
||||
std::string segment;
|
||||
std::regex reg("(!*|\\?*|\\;*|\\.*|\\,*)");
|
||||
|
||||
std::vector<std::pair<uint8_t, uint8_t>> listOfBadSegments = std::vector<std::pair<uint8_t, uint8_t>>();
|
||||
std::set<std::pair<uint8_t, uint8_t>> listOfBadSegments;
|
||||
|
||||
uint32_t position = 0;
|
||||
|
||||
@@ -127,17 +127,17 @@ std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::
|
||||
size_t hash = CalculateHash(segment);
|
||||
|
||||
if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && allowList) {
|
||||
listOfBadSegments.emplace_back(position, originalSegment.length());
|
||||
listOfBadSegments.emplace(position, originalSegment.length());
|
||||
}
|
||||
|
||||
if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && allowList) {
|
||||
m_UserUnapprovedWordCache.push_back(hash);
|
||||
listOfBadSegments.emplace_back(position, originalSegment.length());
|
||||
listOfBadSegments.emplace(position, originalSegment.length());
|
||||
}
|
||||
|
||||
if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !allowList) {
|
||||
m_UserUnapprovedWordCache.push_back(hash);
|
||||
listOfBadSegments.emplace_back(position, originalSegment.length());
|
||||
listOfBadSegments.emplace(position, originalSegment.length());
|
||||
}
|
||||
|
||||
position += originalSegment.length() + 1;
|
||||
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
void ReadWordlistPlaintext(const std::string& filepath, bool allowList);
|
||||
bool ReadWordlistDCF(const std::string& filepath, bool allowList);
|
||||
void ExportWordlistToDCF(const std::string& filepath, bool allowList);
|
||||
std::vector<std::pair<uint8_t, uint8_t>> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList = true);
|
||||
std::set<std::pair<uint8_t, uint8_t>> IsSentenceOkay(const std::string& message, eGameMasterLevel gmLevel, bool allowList = true);
|
||||
|
||||
private:
|
||||
bool m_DontGenerateDCF;
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
set(DCHATSERVER_SOURCES
|
||||
"ChatIgnoreList.cpp"
|
||||
"ChatPacketHandler.cpp"
|
||||
"ChatJSONUtils.cpp"
|
||||
"ChatWeb.cpp"
|
||||
"PlayerContainer.cpp"
|
||||
"ChatWebAPI.cpp"
|
||||
"JSONUtils.cpp"
|
||||
"TeamContainer.cpp"
|
||||
)
|
||||
|
||||
add_executable(ChatServer "ChatServer.cpp")
|
||||
target_include_directories(ChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dChatFilter")
|
||||
target_include_directories(ChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dChatFilter" "${PROJECT_SOURCE_DIR}/dWeb")
|
||||
add_compile_definitions(ChatServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}\"")
|
||||
|
||||
add_library(dChatServer ${DCHATSERVER_SOURCES})
|
||||
target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer")
|
||||
target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer" "${PROJECT_SOURCE_DIR}/dChatFilter")
|
||||
|
||||
target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter)
|
||||
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer mongoose)
|
||||
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer mongoose dWeb)
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
// not allowing teams, rejecting DMs, friends requets etc.
|
||||
// The only thing not auto-handled is instance activities force joining the team on the server.
|
||||
|
||||
void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const ChatIgnoreList::Response type) {
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const MessageType::Client type) {
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receivingPlayer);
|
||||
|
||||
//portion that will get routed:
|
||||
@@ -48,9 +48,9 @@ void ChatIgnoreList::GetIgnoreList(Packet* packet) {
|
||||
}
|
||||
|
||||
CBITSTREAM;
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::GET_IGNORE);
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::GET_IGNORE_LIST_RESPONSE);
|
||||
|
||||
bitStream.Write<uint8_t>(false); // Probably is Is Free Trial, but we don't care about that
|
||||
bitStream.Write<uint8_t>(false); // Is Free Trial, but we don't care about that
|
||||
bitStream.Write<uint16_t>(0); // literally spacing due to struct alignment
|
||||
|
||||
bitStream.Write<uint16_t>(receiver.ignoredPlayers.size());
|
||||
@@ -86,7 +86,7 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
|
||||
std::string toIgnoreStr = toIgnoreName.GetAsString();
|
||||
|
||||
CBITSTREAM;
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::ADD_IGNORE);
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::ADD_IGNORE_RESPONSE);
|
||||
|
||||
// Check if the player exists
|
||||
LWOOBJID ignoredPlayerId = LWOOBJID_EMPTY;
|
||||
@@ -161,7 +161,7 @@ void ChatIgnoreList::RemoveIgnore(Packet* packet) {
|
||||
receiver.ignoredPlayers.erase(toRemove, receiver.ignoredPlayers.end());
|
||||
|
||||
CBITSTREAM;
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, ChatIgnoreList::Response::REMOVE_IGNORE);
|
||||
WriteOutgoingReplyHeader(bitStream, receiver.playerID, MessageType::Client::REMOVE_IGNORE_RESPONSE);
|
||||
|
||||
bitStream.Write<int8_t>(0);
|
||||
LUWString playerNameSend(removedIgnoreStr, 33);
|
||||
|
||||
@@ -5,17 +5,16 @@ struct Packet;
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief The ignore list allows players to ignore someone silently. Requests will generally be blocked by the client, but they should also be checked
|
||||
* on the server as well so the sender can get a generic error code in response.
|
||||
*
|
||||
*/
|
||||
namespace ChatIgnoreList {
|
||||
void GetIgnoreList(Packet* packet);
|
||||
void AddIgnore(Packet* packet);
|
||||
void RemoveIgnore(Packet* packet);
|
||||
|
||||
enum class Response : uint8_t {
|
||||
ADD_IGNORE = 32,
|
||||
REMOVE_IGNORE = 33,
|
||||
GET_IGNORE = 34,
|
||||
};
|
||||
|
||||
enum class AddResponse : uint8_t {
|
||||
SUCCESS,
|
||||
ALREADY_IGNORED,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "JSONUtils.h"
|
||||
#include "ChatJSONUtils.h"
|
||||
|
||||
#include "json.hpp"
|
||||
|
||||
@@ -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,15 +41,9 @@ void to_json(json& data, const TeamData& teamData) {
|
||||
}
|
||||
}
|
||||
|
||||
std::string JSONUtils::CheckRequiredData(const json& data, const std::vector<std::string>& requiredData) {
|
||||
json check;
|
||||
check["error"] = json::array();
|
||||
for (const auto& required : requiredData) {
|
||||
if (!data.contains(required)) {
|
||||
check["error"].push_back("Missing Parameter: " + required);
|
||||
} else if (data[required] == "") {
|
||||
check["error"].push_back("Empty Parameter: " + required);
|
||||
}
|
||||
void TeamContainer::to_json(json& data, const TeamContainer::Data& teamContainer) {
|
||||
for (auto& teamData : TeamContainer::GetTeams()) {
|
||||
if (!teamData) continue;
|
||||
data.push_back(*teamData);
|
||||
}
|
||||
return check["error"].empty() ? "" : check.dump();
|
||||
}
|
||||
18
dChatServer/ChatJSONUtils.h
Normal file
18
dChatServer/ChatJSONUtils.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#ifndef __CHATJSONUTILS_H__
|
||||
#define __CHATJSONUTILS_H__
|
||||
|
||||
#include "json_fwd.hpp"
|
||||
#include "PlayerContainer.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
/* Remember, to_json needs to be in the same namespace as the class its located in */
|
||||
|
||||
void to_json(nlohmann::json& data, const PlayerData& playerData);
|
||||
void to_json(nlohmann::json& data, const PlayerContainer& playerContainer);
|
||||
void to_json(nlohmann::json& data, const TeamData& teamData);
|
||||
|
||||
namespace TeamContainer {
|
||||
void to_json(nlohmann::json& data, const TeamContainer::Data& teamData);
|
||||
};
|
||||
|
||||
#endif // !__CHATJSONUTILS_H__
|
||||
@@ -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:
|
||||
@@ -73,7 +74,7 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
|
||||
data.Serialize(bitStream);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = player.sysAddr;
|
||||
SystemAddress sysAddr = player.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@@ -122,7 +123,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
||||
requesteeFriendData.isOnline = false;
|
||||
requesteeFriendData.zoneID = requestor.zoneID;
|
||||
requestee.friends.push_back(requesteeFriendData);
|
||||
requestee.sysAddr = UNASSIGNED_SYSTEM_ADDRESS;
|
||||
requestee.worldServerSysAddr = UNASSIGNED_SYSTEM_ADDRESS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -189,8 +190,8 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
||||
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee.playerID, bestFriendStatus);
|
||||
// Sent the best friend update here if the value is 3
|
||||
if (bestFriendStatus == 3U) {
|
||||
if (requestee.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
|
||||
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
|
||||
if (requestee.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestee, requestor, eAddFriendResponseType::ACCEPTED, false, true);
|
||||
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::ACCEPTED, false, true);
|
||||
|
||||
for (auto& friendData : requestor.friends) {
|
||||
if (friendData.friendID == requestee.playerID) {
|
||||
@@ -211,7 +212,7 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (requestor.sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true);
|
||||
if (requestor.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee, eAddFriendResponseType::WAITINGAPPROVAL, true, true);
|
||||
}
|
||||
} else {
|
||||
auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends();
|
||||
@@ -384,7 +385,7 @@ void ChatPacketHandler::HandleWho(Packet* packet) {
|
||||
bitStream.Write(player.zoneID.GetCloneID());
|
||||
bitStream.Write(request.playerName);
|
||||
|
||||
SystemAddress sysAddr = sender.sysAddr;
|
||||
SystemAddress sysAddr = sender.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@@ -418,7 +419,7 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
|
||||
}
|
||||
}
|
||||
}
|
||||
SystemAddress sysAddr = sender.sysAddr;
|
||||
SystemAddress sysAddr = sender.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -519,6 +520,28 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
|
||||
SendPrivateChatMessage(sender, receiver, sender, message, eChatChannel::GENERAL, eChatMessageResponseCode::NOTFRIENDS);
|
||||
}
|
||||
|
||||
void ChatPacketHandler::OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr) {
|
||||
ChatPackets::AchievementNotify notify{};
|
||||
notify.Deserialize(bitstream);
|
||||
const auto& playerData = Game::playerContainer.GetPlayerData(notify.earnerName.GetAsString());
|
||||
if (!playerData) return;
|
||||
|
||||
for (const auto& myFriend : playerData.friends) {
|
||||
auto& friendData = Game::playerContainer.GetPlayerData(myFriend.friendID);
|
||||
if (friendData) {
|
||||
notify.targetPlayerName.string = GeneralUtils::ASCIIToUTF16(friendData.playerName);
|
||||
LOG_DEBUG("Sending achievement notify to %s", notify.targetPlayerName.GetAsString().c_str());
|
||||
|
||||
RakNet::BitStream worldStream;
|
||||
BitStreamUtils::WriteHeader(worldStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
worldStream.Write(friendData.playerID);
|
||||
notify.WriteHeader(worldStream);
|
||||
notify.Serialize(worldStream);
|
||||
Game::server->Send(worldStream, friendData.worldServerSysAddr, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const PlayerData& receiver, const PlayerData& routeTo, const LUWString& message, const eChatChannel channel, const eChatMessageResponseCode responseCode) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
@@ -537,387 +560,7 @@ void ChatPacketHandler::SendPrivateChatMessage(const PlayerData& sender, const P
|
||||
bitStream.Write(responseCode);
|
||||
bitStream.Write(message);
|
||||
|
||||
SystemAddress sysAddr = routeTo.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
|
||||
void ChatPacketHandler::HandleTeamInvite(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID;
|
||||
LUWString invitedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(invitedPlayer);
|
||||
|
||||
const auto& player = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (!player) return;
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team == nullptr) {
|
||||
team = Game::playerContainer.CreateTeam(playerID);
|
||||
}
|
||||
|
||||
const auto& other = Game::playerContainer.GetPlayerData(invitedPlayer.GetAsString());
|
||||
|
||||
if (!other) return;
|
||||
|
||||
if (Game::playerContainer.GetTeam(other.playerID) != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() > 3) {
|
||||
// no more teams greater than 4
|
||||
|
||||
LOG("Someone tried to invite a 5th player to a team");
|
||||
return;
|
||||
}
|
||||
|
||||
SendTeamInvite(other, player);
|
||||
|
||||
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.GetAsString().c_str());
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
char declined = 0;
|
||||
inStream.Read(declined);
|
||||
LWOOBJID leaderID = LWOOBJID_EMPTY;
|
||||
inStream.Read(leaderID);
|
||||
|
||||
LOG("Accepted invite: %llu -> %llu (%d)", playerID, leaderID, declined);
|
||||
|
||||
if (declined) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(leaderID);
|
||||
|
||||
if (team == nullptr) {
|
||||
LOG("Failed to find team for leader (%llu)", leaderID);
|
||||
|
||||
team = Game::playerContainer.GetTeam(playerID);
|
||||
}
|
||||
|
||||
if (team == nullptr) {
|
||||
LOG("Failed to find team for player (%llu)", playerID);
|
||||
return;
|
||||
}
|
||||
|
||||
Game::playerContainer.AddMember(team, playerID);
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamLeave(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
LOG("(%llu) leaving team", playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
Game::playerContainer.RemoveMember(team, playerID, false, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamKick(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
LUWString kickedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(kickedPlayer);
|
||||
|
||||
|
||||
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.GetAsString().c_str());
|
||||
|
||||
const auto& kicked = Game::playerContainer.GetPlayerData(kickedPlayer.GetAsString());
|
||||
|
||||
LWOOBJID kickedId = LWOOBJID_EMPTY;
|
||||
|
||||
if (kicked) {
|
||||
kickedId = kicked.playerID;
|
||||
} else {
|
||||
kickedId = Game::playerContainer.GetId(kickedPlayer.string);
|
||||
}
|
||||
|
||||
if (kickedId == LWOOBJID_EMPTY) return;
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID || team->leaderID == kickedId) return;
|
||||
|
||||
Game::playerContainer.RemoveMember(team, kickedId, false, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamPromote(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
LUWString promotedPlayer;
|
||||
|
||||
inStream.Read(playerID);
|
||||
inStream.IgnoreBytes(4);
|
||||
inStream.Read(promotedPlayer);
|
||||
|
||||
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.GetAsString().c_str());
|
||||
|
||||
const auto& promoted = Game::playerContainer.GetPlayerData(promotedPlayer.GetAsString());
|
||||
|
||||
if (!promoted) return;
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID) return;
|
||||
|
||||
Game::playerContainer.PromoteMember(team, promoted.playerID);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamLootOption(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
uint32_t size = 0;
|
||||
inStream.Read(size);
|
||||
|
||||
char option;
|
||||
inStream.Read(option);
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
|
||||
if (team != nullptr) {
|
||||
if (team->leaderID != playerID) return;
|
||||
|
||||
team->lootFlag = option;
|
||||
|
||||
Game::playerContainer.TeamStatusUpdate(team);
|
||||
|
||||
Game::playerContainer.UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
|
||||
CINSTREAM_SKIP_HEADER;
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
inStream.Read(playerID);
|
||||
|
||||
auto* team = Game::playerContainer.GetTeam(playerID);
|
||||
const auto& data = Game::playerContainer.GetPlayerData(playerID);
|
||||
|
||||
if (team != nullptr && data) {
|
||||
if (team->local && data.zoneID.GetMapID() != team->zoneId.GetMapID() && data.zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
|
||||
Game::playerContainer.RemoveMember(team, playerID, false, false, true, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (team->memberIDs.size() <= 1 && !team->local) {
|
||||
Game::playerContainer.DisbandTeam(team);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!team->local) {
|
||||
ChatPacketHandler::SendTeamSetLeader(data, team->leaderID);
|
||||
} else {
|
||||
ChatPacketHandler::SendTeamSetLeader(data, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
Game::playerContainer.TeamStatusUpdate(team);
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(data.playerName);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = Game::playerContainer.GetPlayerData(memberId);
|
||||
|
||||
if (memberId == playerID) continue;
|
||||
|
||||
const auto memberName = Game::playerContainer.GetName(memberId);
|
||||
|
||||
if (otherMember) {
|
||||
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, data.playerID, data.zoneID);
|
||||
}
|
||||
ChatPacketHandler::SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
|
||||
}
|
||||
|
||||
Game::playerContainer.UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE);
|
||||
|
||||
bitStream.Write(LUWString(sender.playerName.c_str()));
|
||||
bitStream.Write(sender.playerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_INVITE_CONFIRM);
|
||||
|
||||
bitStream.Write(bLeaderIsFreeTrial);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write(ucResponseCode);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_GET_STATUS_RESPONSE);
|
||||
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64LeaderZoneID);
|
||||
bitStream.Write<uint32_t>(0); // BinaryBuffe, no clue what's in here
|
||||
bitStream.Write(ucLootFlag);
|
||||
bitStream.Write(ucNumOfOtherPlayers);
|
||||
bitStream.Write<uint32_t>(wsLeaderName.size());
|
||||
for (const auto character : wsLeaderName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_LEADER);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_ADD_PLAYER);
|
||||
|
||||
bitStream.Write(bIsFreeTrial);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(bNoLootOnDeath);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
bitStream.Write1();
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_REMOVE_PLAYER);
|
||||
|
||||
bitStream.Write(bDisband);
|
||||
bitStream.Write(bIsKicked);
|
||||
bitStream.Write(bIsLeaving);
|
||||
bitStream.Write(bLocal);
|
||||
bitStream.Write(i64LeaderID);
|
||||
bitStream.Write(i64PlayerID);
|
||||
bitStream.Write<uint32_t>(wsPlayerName.size());
|
||||
for (const auto character : wsPlayerName) {
|
||||
bitStream.Write(character);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
void ChatPacketHandler::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
|
||||
bitStream.Write(receiver.playerID);
|
||||
|
||||
//portion that will get routed:
|
||||
CMSGHEADER;
|
||||
|
||||
bitStream.Write(receiver.playerID);
|
||||
bitStream.Write(MessageType::Game::TEAM_SET_OFF_WORLD_FLAG);
|
||||
|
||||
bitStream.Write(i64PlayerID);
|
||||
if (receiver.zoneID.GetCloneID() == zoneID.GetCloneID()) {
|
||||
zoneID = LWOZONEID(zoneID.GetMapID(), zoneID.GetInstanceID(), 0);
|
||||
}
|
||||
bitStream.Write(zoneID);
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SystemAddress sysAddr = routeTo.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@@ -959,7 +602,7 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla
|
||||
bitStream.Write<uint8_t>(isBestFriend); //isBFF
|
||||
bitStream.Write<uint8_t>(0); //isFTP
|
||||
|
||||
SystemAddress sysAddr = friendData.sysAddr;
|
||||
SystemAddress sysAddr = friendData.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@@ -981,7 +624,7 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
|
||||
bitStream.Write(LUWString(sender.playerName));
|
||||
bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side.
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@@ -994,7 +637,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_RESPONSE);
|
||||
bitStream.Write(responseCode);
|
||||
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
|
||||
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.sysAddr != UNASSIGNED_SYSTEM_ADDRESS);
|
||||
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS);
|
||||
// Then write the player name
|
||||
bitStream.Write(LUWString(sender.playerName));
|
||||
// Then if this is an acceptance code, write the following extra info.
|
||||
@@ -1004,7 +647,7 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
|
||||
bitStream.Write(isBestFriendRequest); //isBFF
|
||||
bitStream.Write<uint8_t>(0); //isFTP
|
||||
}
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@@ -1018,6 +661,6 @@ void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string
|
||||
bitStream.Write<uint8_t>(isSuccessful); //isOnline
|
||||
bitStream.Write(LUWString(personToRemove));
|
||||
|
||||
SystemAddress sysAddr = receiver.sysAddr;
|
||||
SystemAddress sysAddr = receiver.worldServerSysAddr;
|
||||
SEND_PACKET;
|
||||
}
|
||||
|
||||
@@ -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,30 +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 SendTeamInvite(const PlayerData& receiver, const PlayerData& sender);
|
||||
void SendTeamInviteConfirm(const PlayerData& receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName);
|
||||
void SendTeamStatus(const PlayerData& receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName);
|
||||
void SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID);
|
||||
void SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID);
|
||||
void SendTeamRemovePlayer(const PlayerData& receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName);
|
||||
void SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID);
|
||||
void OnAchievementNotify(RakNet::BitStream& bitstream, const SystemAddress& sysAddr);
|
||||
|
||||
//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"
|
||||
@@ -28,7 +29,7 @@
|
||||
#include "RakNetDefines.h"
|
||||
#include "MessageIdentifiers.h"
|
||||
|
||||
#include "ChatWebAPI.h"
|
||||
#include "ChatWeb.h"
|
||||
|
||||
namespace Game {
|
||||
Logger* logger = nullptr;
|
||||
@@ -92,17 +93,18 @@ int main(int argc, char** argv) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// seyup the chat api web server
|
||||
bool web_server_enabled = Game::config->GetValue("web_server_enabled") == "1";
|
||||
ChatWebAPI chatwebapi;
|
||||
if (web_server_enabled && !chatwebapi.Startup()){
|
||||
// if we want the web api and it fails to start, exit
|
||||
// setup the chat api web server
|
||||
const uint32_t web_server_port = GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("web_server_port")).value_or(2005);
|
||||
if (Game::config->GetValue("web_server_enabled") == "1" && !Game::web.Startup("localhost", web_server_port)) {
|
||||
// if we want the web server and it fails to start, exit
|
||||
LOG("Failed to start web server, shutting down.");
|
||||
Database::Destroy("ChatServer");
|
||||
delete Game::logger;
|
||||
delete Game::config;
|
||||
return EXIT_FAILURE;
|
||||
};
|
||||
}
|
||||
|
||||
if (Game::web.IsEnabled()) ChatWeb::RegisterRoutes();
|
||||
|
||||
//Find out the master's IP:
|
||||
std::string masterIP;
|
||||
@@ -166,10 +168,8 @@ int main(int argc, char** argv) {
|
||||
packet = nullptr;
|
||||
}
|
||||
|
||||
//Check and handle web requests:
|
||||
if (web_server_enabled) {
|
||||
chatwebapi.ReceiveRequests();
|
||||
}
|
||||
// Check and handle web requests:
|
||||
if (Game::web.IsEnabled()) Game::web.ReceiveRequests();
|
||||
|
||||
//Push our log every 30s:
|
||||
if (framesSinceLastFlush >= logFlushTime) {
|
||||
@@ -197,6 +197,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;
|
||||
@@ -224,13 +225,17 @@ void HandlePacket(Packet* packet) {
|
||||
if (connection != eConnectionType::CHAT) return;
|
||||
inStream.Read(chatMessageID);
|
||||
|
||||
// Our packing byte wasnt there? Probably a false packet
|
||||
if (inStream.GetNumberOfUnreadBits() < 8) return;
|
||||
inStream.IgnoreBytes(1);
|
||||
|
||||
switch (chatMessageID) {
|
||||
case MessageType::Chat::GM_MUTE:
|
||||
Game::playerContainer.MuteUpdate(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::CREATE_TEAM:
|
||||
Game::playerContainer.CreateTeamServer(packet);
|
||||
TeamContainer::CreateTeamServer(packet);
|
||||
break;
|
||||
|
||||
case MessageType::Chat::GET_FRIENDS_LIST:
|
||||
@@ -250,7 +255,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:
|
||||
@@ -280,27 +285,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);
|
||||
@@ -322,6 +327,9 @@ void HandlePacket(Packet* packet) {
|
||||
case MessageType::Chat::SHOW_ALL:
|
||||
ChatPacketHandler::HandleShowAll(packet);
|
||||
break;
|
||||
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
|
||||
ChatPacketHandler::OnAchievementNotify(inStream, packet->systemAddress);
|
||||
break;
|
||||
case MessageType::Chat::USER_CHANNEL_CHAT_MESSAGE:
|
||||
case MessageType::Chat::WORLD_DISCONNECT_REQUEST:
|
||||
case MessageType::Chat::WORLD_PROXIMITY_RESPONSE:
|
||||
@@ -357,7 +365,6 @@ void HandlePacket(Packet* packet) {
|
||||
case MessageType::Chat::UGCMANIFEST_REPORT_DONE_BLUEPRINT:
|
||||
case MessageType::Chat::UGCC_REQUEST:
|
||||
case MessageType::Chat::WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE:
|
||||
case MessageType::Chat::ACHIEVEMENT_NOTIFY:
|
||||
case MessageType::Chat::GM_CLOSE_PRIVATE_CHAT_WINDOW:
|
||||
case MessageType::Chat::PLAYER_READY:
|
||||
case MessageType::Chat::GET_DONATION_TOTAL:
|
||||
|
||||
133
dChatServer/ChatWeb.cpp
Normal file
133
dChatServer/ChatWeb.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include "ChatWeb.h"
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Game.h"
|
||||
#include "json.hpp"
|
||||
#include "dCommonVars.h"
|
||||
#include "MessageType/Chat.h"
|
||||
#include "dServer.h"
|
||||
#include "dConfig.h"
|
||||
#include "PlayerContainer.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "eHTTPMethod.h"
|
||||
#include "magic_enum.hpp"
|
||||
#include "ChatPackets.h"
|
||||
#include "StringifiedEnum.h"
|
||||
#include "Database.h"
|
||||
#include "ChatJSONUtils.h"
|
||||
#include "JSONUtils.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "dChatFilter.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
void HandleHTTPPlayersRequest(HTTPReply& reply, std::string body) {
|
||||
const json data = Game::playerContainer;
|
||||
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
|
||||
reply.message = data.empty() ? "{\"error\":\"No Players Online\"}" : data.dump();
|
||||
}
|
||||
|
||||
void HandleHTTPTeamsRequest(HTTPReply& reply, std::string body) {
|
||||
const json data = TeamContainer::GetTeamContainer();
|
||||
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
|
||||
reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump();
|
||||
}
|
||||
|
||||
void HandleHTTPAnnounceRequest(HTTPReply& reply, std::string body) {
|
||||
auto data = GeneralUtils::TryParse<json>(body);
|
||||
if (!data) {
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid JSON\"}";
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& good_data = data.value();
|
||||
auto check = JSONUtils::CheckRequiredData(good_data, { "title", "message" });
|
||||
if (!check.empty()) {
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = check;
|
||||
} else {
|
||||
|
||||
ChatPackets::Announcement announcement;
|
||||
announcement.title = good_data["title"];
|
||||
announcement.message = good_data["message"];
|
||||
announcement.Broadcast();
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = "{\"status\":\"Announcement Sent\"}";
|
||||
}
|
||||
}
|
||||
|
||||
void HandleWSChat(mg_connection* connection, json data) {
|
||||
auto check = JSONUtils::CheckRequiredData(data, { "user", "message", "gmlevel", "zone" });
|
||||
if (!check.empty()) {
|
||||
LOG_DEBUG("Received invalid websocket message: %s", check.c_str());
|
||||
} else {
|
||||
const auto user = data["user"].get<std::string>();
|
||||
const auto message = data["message"].get<std::string>();
|
||||
const auto gmlevel = GeneralUtils::TryParse<eGameMasterLevel>(data["gmlevel"].get<std::string>()).value_or(eGameMasterLevel::CIVILIAN);
|
||||
const auto zone = data["zone"].get<uint32_t>();
|
||||
|
||||
const auto filter_check = Game::chatFilter->IsSentenceOkay(message, gmlevel);
|
||||
if (!filter_check.empty()) {
|
||||
LOG_DEBUG("Chat message \"%s\" from %s was not allowed", message.c_str(), user.c_str());
|
||||
data["error"] = "Chat message blocked by filter";
|
||||
data["filtered"] = json::array();
|
||||
for (const auto& [start, len] : filter_check) {
|
||||
data["filtered"].push_back(message.substr(start, len));
|
||||
}
|
||||
mg_ws_send(connection, data.dump().c_str(), data.dump().size(), WEBSOCKET_OP_TEXT);
|
||||
return;
|
||||
}
|
||||
LOG("%s: %s", user.c_str(), message.c_str());
|
||||
|
||||
// TODO: Implement chat message handling from websocket message
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
namespace ChatWeb {
|
||||
void RegisterRoutes() {
|
||||
|
||||
// REST API v1 routes
|
||||
|
||||
std::string v1_route = "/api/v1/";
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = v1_route + "players",
|
||||
.method = eHTTPMethod::GET,
|
||||
.handle = HandleHTTPPlayersRequest
|
||||
});
|
||||
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = v1_route + "teams",
|
||||
.method = eHTTPMethod::GET,
|
||||
.handle = HandleHTTPTeamsRequest
|
||||
});
|
||||
|
||||
Game::web.RegisterHTTPRoute({
|
||||
.path = v1_route + "announce",
|
||||
.method = eHTTPMethod::POST,
|
||||
.handle = HandleHTTPAnnounceRequest
|
||||
});
|
||||
|
||||
// WebSocket Events Handlers
|
||||
|
||||
// Game::web.RegisterWSEvent({
|
||||
// .name = "chat",
|
||||
// .handle = HandleWSChat
|
||||
// });
|
||||
|
||||
// WebSocket subscriptions
|
||||
|
||||
Game::web.RegisterWSSubscription("player");
|
||||
}
|
||||
|
||||
void SendWSPlayerUpdate(const PlayerData& player, eActivityType activityType) {
|
||||
json data;
|
||||
data["player_data"] = player;
|
||||
data["update_type"] = magic_enum::enum_name(activityType);
|
||||
Game::web.SendWSMessage("player", data);
|
||||
}
|
||||
}
|
||||
|
||||
19
dChatServer/ChatWeb.h
Normal file
19
dChatServer/ChatWeb.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef __CHATWEB_H__
|
||||
#define __CHATWEB_H__
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include "Web.h"
|
||||
#include "PlayerContainer.h"
|
||||
#include "IActivityLog.h"
|
||||
#include "ChatPacketHandler.h"
|
||||
|
||||
namespace ChatWeb {
|
||||
void RegisterRoutes();
|
||||
void SendWSPlayerUpdate(const PlayerData& player, eActivityType activityType);
|
||||
};
|
||||
|
||||
|
||||
#endif // __CHATWEB_H__
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
#include "ChatWebAPI.h"
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Game.h"
|
||||
#include "json.hpp"
|
||||
#include "dCommonVars.h"
|
||||
#include "MessageType/Chat.h"
|
||||
#include "dServer.h"
|
||||
#include "dConfig.h"
|
||||
#include "PlayerContainer.h"
|
||||
#include "JSONUtils.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "eHTTPMethod.h"
|
||||
#include "magic_enum.hpp"
|
||||
#include "ChatPackets.h"
|
||||
#include "StringifiedEnum.h"
|
||||
#include "Database.h"
|
||||
|
||||
#ifdef DARKFLAME_PLATFORM_WIN32
|
||||
#pragma push_macro("DELETE")
|
||||
#undef DELETE
|
||||
#endif
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
typedef struct mg_connection mg_connection;
|
||||
typedef struct mg_http_message mg_http_message;
|
||||
|
||||
namespace {
|
||||
const char* json_content_type = "Content-Type: application/json\r\n";
|
||||
std::map<std::pair<eHTTPMethod, std::string>, WebAPIHTTPRoute> Routes {};
|
||||
}
|
||||
|
||||
bool ValidateAuthentication(const mg_http_message* http_msg) {
|
||||
// TO DO: This is just a placeholder for now
|
||||
// use tokens or something at a later point if we want to implement authentication
|
||||
// bit using the listen bind address to limit external access is good enough to start with
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ValidateJSON(std::optional<json> data, HTTPReply& reply) {
|
||||
if (!data) {
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid JSON\"}";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HandlePlayersRequest(HTTPReply& reply, std::string body) {
|
||||
const json data = Game::playerContainer;
|
||||
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
|
||||
reply.message = data.empty() ? "{\"error\":\"No Players Online\"}" : data.dump();
|
||||
}
|
||||
|
||||
void HandleTeamsRequest(HTTPReply& reply, std::string body) {
|
||||
const json data = Game::playerContainer.GetTeamContainer();
|
||||
reply.status = data.empty() ? eHTTPStatusCode::NO_CONTENT : eHTTPStatusCode::OK;
|
||||
reply.message = data.empty() ? "{\"error\":\"No Teams Online\"}" : data.dump();
|
||||
}
|
||||
|
||||
void HandleAnnounceRequest(HTTPReply& reply, std::string body) {
|
||||
auto data = GeneralUtils::TryParse<json>(body);
|
||||
if (!ValidateJSON(data, reply)) return;
|
||||
|
||||
const auto& good_data = data.value();
|
||||
auto check = JSONUtils::CheckRequiredData(good_data, { "title", "message" });
|
||||
if (!check.empty()) {
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = check;
|
||||
} else {
|
||||
|
||||
ChatPackets::Announcement announcement;
|
||||
announcement.title = good_data["title"];
|
||||
announcement.message = good_data["message"];
|
||||
announcement.Send();
|
||||
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.message = "{\"status\":\"Announcement Sent\"}";
|
||||
}
|
||||
}
|
||||
|
||||
void HandleInvalidRoute(HTTPReply& reply) {
|
||||
reply.status = eHTTPStatusCode::NOT_FOUND;
|
||||
reply.message = "{\"error\":\"Invalid Route\"}";
|
||||
}
|
||||
|
||||
void HandleHTTPMessage(mg_connection* connection, const mg_http_message* http_msg) {
|
||||
HTTPReply reply;
|
||||
|
||||
if (!http_msg) {
|
||||
reply.status = eHTTPStatusCode::BAD_REQUEST;
|
||||
reply.message = "{\"error\":\"Invalid Request\"}";
|
||||
} else if (ValidateAuthentication(http_msg)) {
|
||||
|
||||
// convert method from cstring to std string
|
||||
std::string method_string(http_msg->method.buf, http_msg->method.len);
|
||||
// get mehtod from mg to enum
|
||||
const eHTTPMethod method = magic_enum::enum_cast<eHTTPMethod>(method_string).value_or(eHTTPMethod::INVALID);
|
||||
|
||||
// convert uri from cstring to std string
|
||||
std::string uri(http_msg->uri.buf, http_msg->uri.len);
|
||||
std::transform(uri.begin(), uri.end(), uri.begin(), ::tolower);
|
||||
|
||||
// convert body from cstring to std string
|
||||
std::string body(http_msg->body.buf, http_msg->body.len);
|
||||
|
||||
|
||||
const auto routeItr = Routes.find({method, uri});
|
||||
|
||||
if (routeItr != Routes.end()) {
|
||||
const auto& [_, route] = *routeItr;
|
||||
route.handle(reply, body);
|
||||
} else HandleInvalidRoute(reply);
|
||||
} else {
|
||||
reply.status = eHTTPStatusCode::UNAUTHORIZED;
|
||||
reply.message = "{\"error\":\"Unauthorized\"}";
|
||||
}
|
||||
mg_http_reply(connection, static_cast<int>(reply.status), json_content_type, reply.message.c_str());
|
||||
}
|
||||
|
||||
|
||||
void HandleRequests(mg_connection* connection, int request, void* request_data) {
|
||||
switch (request) {
|
||||
case MG_EV_HTTP_MSG:
|
||||
HandleHTTPMessage(connection, static_cast<mg_http_message*>(request_data));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ChatWebAPI::RegisterHTTPRoutes(WebAPIHTTPRoute route) {
|
||||
auto [_, success] = Routes.try_emplace({ route.method, route.path }, route);
|
||||
if (!success) {
|
||||
LOG_DEBUG("Failed to register route %s", route.path.c_str());
|
||||
} else {
|
||||
LOG_DEBUG("Registered route %s", route.path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ChatWebAPI::ChatWebAPI() {
|
||||
mg_log_set(MG_LL_NONE);
|
||||
mg_mgr_init(&mgr); // Initialize event manager
|
||||
}
|
||||
|
||||
ChatWebAPI::~ChatWebAPI() {
|
||||
mg_mgr_free(&mgr);
|
||||
}
|
||||
|
||||
bool ChatWebAPI::Startup() {
|
||||
// Make listen address
|
||||
// std::string listen_ip = Game::config->GetValue("web_server_listen_ip");
|
||||
// if (listen_ip == "localhost") listen_ip = "127.0.0.1";
|
||||
|
||||
const std::string& listen_port = Game::config->GetValue("web_server_listen_port");
|
||||
// const std::string& listen_address = "http://" + listen_ip + ":" + listen_port;
|
||||
const std::string& listen_address = "http://localhost:" + listen_port;
|
||||
LOG("Starting web server on %s", listen_address.c_str());
|
||||
|
||||
// Create HTTP listener
|
||||
if (!mg_http_listen(&mgr, listen_address.c_str(), HandleRequests, NULL)) {
|
||||
LOG("Failed to create web server listener on %s", listen_port.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register routes
|
||||
|
||||
// API v1 routes
|
||||
std::string v1_route = "/api/v1/";
|
||||
RegisterHTTPRoutes({
|
||||
.path = v1_route + "players",
|
||||
.method = eHTTPMethod::GET,
|
||||
.handle = HandlePlayersRequest
|
||||
});
|
||||
|
||||
RegisterHTTPRoutes({
|
||||
.path = v1_route + "teams",
|
||||
.method = eHTTPMethod::GET,
|
||||
.handle = HandleTeamsRequest
|
||||
});
|
||||
|
||||
RegisterHTTPRoutes({
|
||||
.path = v1_route + "announce",
|
||||
.method = eHTTPMethod::POST,
|
||||
.handle = HandleAnnounceRequest
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void ChatWebAPI::ReceiveRequests() {
|
||||
mg_mgr_poll(&mgr, 15);
|
||||
}
|
||||
|
||||
#ifdef DARKFLAME_PLATFORM_WIN32
|
||||
#pragma pop_macro("DELETE")
|
||||
#endif
|
||||
@@ -1,36 +0,0 @@
|
||||
#ifndef __CHATWEBAPI_H__
|
||||
#define __CHATWEBAPI_H__
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
#include "mongoose.h"
|
||||
#include "eHTTPStatusCode.h"
|
||||
|
||||
enum class eHTTPMethod;
|
||||
|
||||
typedef struct mg_mgr mg_mgr;
|
||||
|
||||
struct HTTPReply {
|
||||
eHTTPStatusCode status = eHTTPStatusCode::NOT_FOUND;
|
||||
std::string message = "{\"error\":\"Not Found\"}";
|
||||
};
|
||||
|
||||
struct WebAPIHTTPRoute {
|
||||
std::string path;
|
||||
eHTTPMethod method;
|
||||
std::function<void(HTTPReply&, const std::string&)> handle;
|
||||
};
|
||||
|
||||
class ChatWebAPI {
|
||||
public:
|
||||
ChatWebAPI();
|
||||
~ChatWebAPI();
|
||||
void ReceiveRequests();
|
||||
void RegisterHTTPRoutes(WebAPIHTTPRoute route);
|
||||
bool Startup();
|
||||
private:
|
||||
mg_mgr mgr;
|
||||
|
||||
};
|
||||
|
||||
#endif // __CHATWEBAPI_H__
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef __JSONUTILS_H__
|
||||
#define __JSONUTILS_H__
|
||||
|
||||
#include "json_fwd.hpp"
|
||||
#include "PlayerContainer.h"
|
||||
|
||||
void to_json(nlohmann::json& data, const PlayerData& playerData);
|
||||
void to_json(nlohmann::json& data, const PlayerContainer& playerContainer);
|
||||
void to_json(nlohmann::json& data, const TeamContainer& teamData);
|
||||
void to_json(nlohmann::json& data, const TeamData& teamData);
|
||||
|
||||
namespace JSONUtils {
|
||||
// check required data for reqeust
|
||||
std::string CheckRequiredData(const nlohmann::json& data, const std::vector<std::string>& requiredData);
|
||||
}
|
||||
|
||||
#endif // __JSONUTILS_H__
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "ChatPackets.h"
|
||||
#include "dConfig.h"
|
||||
#include "MessageType/Chat.h"
|
||||
#include "ChatWeb.h"
|
||||
#include "TeamContainer.h"
|
||||
|
||||
void PlayerContainer::Initialize() {
|
||||
m_MaxNumberOfBestFriends =
|
||||
@@ -52,14 +54,15 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
|
||||
if (!inStream.Read(data.zoneID)) return;
|
||||
if (!inStream.Read(data.muteExpire)) return;
|
||||
if (!inStream.Read(data.gmLevel)) return;
|
||||
data.sysAddr = packet->systemAddress;
|
||||
data.worldServerSysAddr = packet->systemAddress;
|
||||
|
||||
m_Names[data.playerID] = GeneralUtils::UTF8ToUTF16(data.playerName);
|
||||
m_PlayerCount++;
|
||||
|
||||
LOG("Added user: %s (%llu), zone: %i", data.playerName.c_str(), data.playerID, data.zoneID.GetMapID());
|
||||
ChatWeb::SendWSPlayerUpdate(data, isLogin ? eActivityType::PlayerLoggedIn : eActivityType::PlayerChangedZone);
|
||||
|
||||
Database::Get()->UpdateActivityLog(data.playerID, eActivityType::PlayerLoggedIn, data.zoneID.GetMapID());
|
||||
Database::Get()->UpdateActivityLog(data.playerID, isLogin ? eActivityType::PlayerLoggedIn : eActivityType::PlayerChangedZone, data.zoneID.GetMapID());
|
||||
m_PlayersToRemove.erase(playerId);
|
||||
}
|
||||
|
||||
@@ -99,7 +102,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,10 +112,12 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 });
|
||||
TeamContainer::SendTeamSetOffWorldFlag(otherMember, playerID, { 0, 0, 0 });
|
||||
}
|
||||
}
|
||||
|
||||
ChatWeb::SendWSPlayerUpdate(player, eActivityType::PlayerLoggedOut);
|
||||
|
||||
m_PlayerCount--;
|
||||
LOG("Removed user: %llu", playerID);
|
||||
m_Players.erase(playerID);
|
||||
@@ -140,40 +145,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,221 +155,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.sysAddr, u"The teams is full! You have not been added to a team!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
|
||||
|
||||
if (index != team->memberIDs.end()) return;
|
||||
|
||||
team->memberIDs.push_back(playerID);
|
||||
|
||||
const auto& leader = GetPlayerData(team->leaderID);
|
||||
const auto& member = GetPlayerData(playerID);
|
||||
|
||||
if (!leader || !member) return;
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
|
||||
const auto memberName = GeneralUtils::UTF8ToUTF16(member.playerName);
|
||||
|
||||
ChatPacketHandler::SendTeamInviteConfirm(member, false, leader.playerID, leader.zoneID, team->lootFlag, 0, 0, leaderName);
|
||||
|
||||
if (!team->local) {
|
||||
ChatPacketHandler::SendTeamSetLeader(member, leader.playerID);
|
||||
} else {
|
||||
ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (otherMember == member) continue;
|
||||
|
||||
const auto otherMemberName = GetName(memberId);
|
||||
|
||||
ChatPacketHandler::SendTeamAddPlayer(member, false, team->local, false, memberId, otherMemberName, otherMember ? otherMember.zoneID : LWOZONEID(0, 0, 0));
|
||||
|
||||
if (otherMember) {
|
||||
ChatPacketHandler::SendTeamAddPlayer(otherMember, false, team->local, false, member.playerID, memberName, member.zoneID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::RemoveMember(TeamData* team, LWOOBJID playerID, bool disband, bool kicked, bool leaving, bool silent) {
|
||||
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
|
||||
|
||||
if (index == team->memberIDs.end()) return;
|
||||
|
||||
const auto& member = GetPlayerData(playerID);
|
||||
|
||||
if (member && !silent) {
|
||||
ChatPacketHandler::SendTeamSetLeader(member, LWOOBJID_EMPTY);
|
||||
}
|
||||
|
||||
const auto memberName = GetName(playerID);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
if (silent && memberId == playerID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
ChatPacketHandler::SendTeamRemovePlayer(otherMember, disband, kicked, leaving, false, team->leaderID, playerID, memberName);
|
||||
}
|
||||
|
||||
team->memberIDs.erase(index);
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
|
||||
if (team->memberIDs.size() <= 1) {
|
||||
DisbandTeam(team);
|
||||
} else {
|
||||
if (playerID == team->leaderID) {
|
||||
PromoteMember(team, team->memberIDs[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::PromoteMember(TeamData* team, LWOOBJID newLeader) {
|
||||
team->leaderID = newLeader;
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
ChatPacketHandler::SendTeamSetLeader(otherMember, newLeader);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerContainer::DisbandTeam(TeamData* team) {
|
||||
const auto index = std::find(GetTeams().begin(), GetTeams().end(), team);
|
||||
|
||||
if (index == GetTeams().end()) return;
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
const auto memberName = GeneralUtils::UTF8ToUTF16(otherMember.playerName);
|
||||
|
||||
ChatPacketHandler::SendTeamSetLeader(otherMember, LWOOBJID_EMPTY);
|
||||
ChatPacketHandler::SendTeamRemovePlayer(otherMember, true, false, false, team->local, team->leaderID, otherMember.playerID, memberName);
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, true);
|
||||
|
||||
GetTeamsMut().erase(index);
|
||||
|
||||
delete team;
|
||||
}
|
||||
|
||||
void PlayerContainer::TeamStatusUpdate(TeamData* team) {
|
||||
const auto index = std::find(GetTeams().begin(), GetTeams().end(), team);
|
||||
|
||||
if (index == GetTeams().end()) return;
|
||||
|
||||
const auto& leader = GetPlayerData(team->leaderID);
|
||||
|
||||
if (!leader) return;
|
||||
|
||||
const auto leaderName = GeneralUtils::UTF8ToUTF16(leader.playerName);
|
||||
|
||||
for (const auto memberId : team->memberIDs) {
|
||||
const auto& otherMember = GetPlayerData(memberId);
|
||||
|
||||
if (!otherMember) continue;
|
||||
|
||||
if (!team->local) {
|
||||
ChatPacketHandler::SendTeamStatus(otherMember, team->leaderID, leader.zoneID, team->lootFlag, 0, leaderName);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateTeamsOnWorld(team, false);
|
||||
}
|
||||
|
||||
void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
|
||||
CBITSTREAM;
|
||||
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
|
||||
|
||||
bitStream.Write(team->teamID);
|
||||
bitStream.Write(deleteTeam);
|
||||
|
||||
if (!deleteTeam) {
|
||||
bitStream.Write(team->lootFlag);
|
||||
bitStream.Write<char>(team->memberIDs.size());
|
||||
for (const auto memberID : team->memberIDs) {
|
||||
bitStream.Write(memberID);
|
||||
}
|
||||
}
|
||||
|
||||
Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
|
||||
}
|
||||
|
||||
std::u16string PlayerContainer::GetName(LWOOBJID playerID) {
|
||||
const auto iter = m_Names.find(playerID);
|
||||
|
||||
@@ -447,5 +203,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 {
|
||||
@@ -42,7 +38,7 @@ struct PlayerData {
|
||||
return muteExpire == 1 || muteExpire > time(NULL);
|
||||
}
|
||||
|
||||
SystemAddress sysAddr{};
|
||||
SystemAddress worldServerSysAddr{};
|
||||
LWOZONEID zoneID{};
|
||||
LWOOBJID playerID = LWOOBJID_EMPTY;
|
||||
time_t muteExpire = 0;
|
||||
@@ -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);
|
||||
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,11 @@ set(DCOMMON_SOURCES
|
||||
"BrickByBrickFix.cpp"
|
||||
"BinaryPathFinder.cpp"
|
||||
"FdbToSqlite.cpp"
|
||||
"JSONUtils.cpp"
|
||||
"TinyXmlUtils.cpp"
|
||||
"Sd0.cpp"
|
||||
"Lxfml.cpp"
|
||||
"LxfmlBugged.cpp"
|
||||
)
|
||||
|
||||
# Workaround for compiler bug where the optimized code could result in a memcpy of 0 bytes, even though that isnt possible.
|
||||
|
||||
@@ -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
|
||||
|
||||
17
dCommon/JSONUtils.cpp
Normal file
17
dCommon/JSONUtils.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#include "JSONUtils.h"
|
||||
#include "json.hpp"
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
std::string JSONUtils::CheckRequiredData(const json& data, const std::vector<std::string>& requiredData) {
|
||||
json check;
|
||||
check["error"] = json::array();
|
||||
for (const auto& required : requiredData) {
|
||||
if (!data.contains(required)) {
|
||||
check["error"].push_back("Missing Parameter: " + required);
|
||||
} else if (data[required] == "") {
|
||||
check["error"].push_back("Empty Parameter: " + required);
|
||||
}
|
||||
}
|
||||
return check["error"].empty() ? "" : check.dump();
|
||||
}
|
||||
11
dCommon/JSONUtils.h
Normal file
11
dCommon/JSONUtils.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef _JSONUTILS_H_
|
||||
#define _JSONUTILS_H_
|
||||
|
||||
#include "json_fwd.hpp"
|
||||
|
||||
namespace JSONUtils {
|
||||
// check required fields in json data
|
||||
std::string CheckRequiredData(const nlohmann::json& data, const std::vector<std::string>& requiredData);
|
||||
}
|
||||
|
||||
#endif // _JSONUTILS_H_
|
||||
@@ -83,6 +83,12 @@ public:
|
||||
this->value = value;
|
||||
}
|
||||
|
||||
//! Initializer
|
||||
LDFData(const std::string& key, const T& value) {
|
||||
this->key = GeneralUtils::ASCIIToUTF16(key);
|
||||
this->value = value;
|
||||
}
|
||||
|
||||
//! Destructor
|
||||
~LDFData(void) override {}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
117
dCommon/Lxfml.cpp
Normal file
117
dCommon/Lxfml.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "Lxfml.h"
|
||||
|
||||
#include "GeneralUtils.h"
|
||||
#include "StringifiedEnum.h"
|
||||
#include "TinyXmlUtils.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
|
||||
Result toReturn;
|
||||
tinyxml2::XMLDocument doc;
|
||||
const auto err = doc.Parse(data.data());
|
||||
if (err != tinyxml2::XML_SUCCESS) {
|
||||
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
TinyXmlUtils::DocumentReader reader(doc);
|
||||
std::map<std::string/* refID */, std::string> transformations;
|
||||
|
||||
auto lxfml = reader["LXFML"];
|
||||
if (!lxfml) {
|
||||
LOG("Failed to find LXFML element.");
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
// First get all the positions of bricks
|
||||
for (const auto& brick : lxfml["Bricks"]) {
|
||||
const auto* part = brick.FirstChildElement("Part");
|
||||
while (part) {
|
||||
const auto* bone = part->FirstChildElement("Bone");
|
||||
if (bone) {
|
||||
auto* transformation = bone->Attribute("transformation");
|
||||
if (transformation) {
|
||||
auto* refID = bone->Attribute("refID");
|
||||
if (refID) transformations[refID] = transformation;
|
||||
}
|
||||
}
|
||||
part = part->NextSiblingElement("Part");
|
||||
}
|
||||
}
|
||||
|
||||
// These points are well out of bounds for an actual player
|
||||
NiPoint3 lowest{ 10'000.0f, 10'000.0f, 10'000.0f };
|
||||
NiPoint3 highest{ -10'000.0f, -10'000.0f, -10'000.0f };
|
||||
|
||||
// Calculate the lowest and highest points on the entire model
|
||||
for (const auto& transformation : transformations | std::views::values) {
|
||||
auto split = GeneralUtils::SplitString(transformation, ',');
|
||||
if (split.size() < 12) {
|
||||
LOG("Not enough in the split?");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto x = GeneralUtils::TryParse<float>(split[9]).value();
|
||||
auto y = GeneralUtils::TryParse<float>(split[10]).value();
|
||||
auto z = GeneralUtils::TryParse<float>(split[11]).value();
|
||||
if (x < lowest.x) lowest.x = x;
|
||||
if (y < lowest.y) lowest.y = y;
|
||||
if (z < lowest.z) lowest.z = z;
|
||||
|
||||
if (highest.x < x) highest.x = x;
|
||||
if (highest.y < y) highest.y = y;
|
||||
if (highest.z < z) highest.z = z;
|
||||
}
|
||||
|
||||
auto delta = (highest - lowest) / 2.0f;
|
||||
auto newRootPos = lowest + delta;
|
||||
|
||||
// Clamp the Y to the lowest point on the model
|
||||
newRootPos.y = lowest.y;
|
||||
|
||||
// Adjust all positions to account for the new origin
|
||||
for (auto& transformation : transformations | std::views::values) {
|
||||
auto split = GeneralUtils::SplitString(transformation, ',');
|
||||
if (split.size() < 12) {
|
||||
LOG("Not enough in the split?");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
|
||||
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
|
||||
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
|
||||
std::stringstream stream;
|
||||
for (int i = 0; i < 9; i++) {
|
||||
stream << split[i];
|
||||
stream << ',';
|
||||
}
|
||||
stream << x << ',' << y << ',' << z;
|
||||
transformation = stream.str();
|
||||
}
|
||||
|
||||
// Finally write the new transformation back into the lxfml
|
||||
for (auto& brick : lxfml["Bricks"]) {
|
||||
auto* part = brick.FirstChildElement("Part");
|
||||
while (part) {
|
||||
auto* bone = part->FirstChildElement("Bone");
|
||||
if (bone) {
|
||||
auto* transformation = bone->Attribute("transformation");
|
||||
if (transformation) {
|
||||
auto* refID = bone->Attribute("refID");
|
||||
if (refID) {
|
||||
bone->SetAttribute("transformation", transformations[refID].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
part = part->NextSiblingElement("Part");
|
||||
}
|
||||
}
|
||||
|
||||
tinyxml2::XMLPrinter printer;
|
||||
doc.Print(&printer);
|
||||
|
||||
toReturn.lxfml = printer.CStr();
|
||||
toReturn.center = newRootPos;
|
||||
return toReturn;
|
||||
}
|
||||
27
dCommon/Lxfml.h
Normal file
27
dCommon/Lxfml.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// Darkflame Universe
|
||||
// Copyright 2025
|
||||
|
||||
#ifndef LXFML_H
|
||||
#define LXFML_H
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "NiPoint3.h"
|
||||
|
||||
namespace Lxfml {
|
||||
struct Result {
|
||||
std::string lxfml;
|
||||
NiPoint3 center;
|
||||
};
|
||||
|
||||
// Normalizes a LXFML model to be positioned relative to its local 0, 0, 0 rather than a game worlds 0, 0, 0.
|
||||
// Returns a struct of its new center and the updated LXFML containing these edits.
|
||||
[[nodiscard]] Result NormalizePosition(const std::string_view data);
|
||||
|
||||
// these are only for the migrations due to a bug in one of the implementations.
|
||||
[[nodiscard]] Result NormalizePositionOnlyFirstPart(const std::string_view data);
|
||||
[[nodiscard]] Result NormalizePositionAfterFirstPart(const std::string_view data, const NiPoint3& position);
|
||||
};
|
||||
|
||||
#endif //!LXFML_H
|
||||
210
dCommon/LxfmlBugged.cpp
Normal file
210
dCommon/LxfmlBugged.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
#include "Lxfml.h"
|
||||
|
||||
#include "GeneralUtils.h"
|
||||
#include "StringifiedEnum.h"
|
||||
#include "TinyXmlUtils.h"
|
||||
|
||||
#include <ranges>
|
||||
|
||||
// this file should not be touched
|
||||
|
||||
Lxfml::Result Lxfml::NormalizePositionOnlyFirstPart(const std::string_view data) {
|
||||
Result toReturn;
|
||||
tinyxml2::XMLDocument doc;
|
||||
const auto err = doc.Parse(data.data());
|
||||
if (err != tinyxml2::XML_SUCCESS) {
|
||||
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
TinyXmlUtils::DocumentReader reader(doc);
|
||||
std::map<std::string/* refID */, std::string> transformations;
|
||||
|
||||
auto lxfml = reader["LXFML"];
|
||||
if (!lxfml) {
|
||||
LOG("Failed to find LXFML element.");
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
// First get all the positions of bricks
|
||||
for (const auto& brick : lxfml["Bricks"]) {
|
||||
const auto* part = brick.FirstChildElement("Part");
|
||||
if (part) {
|
||||
const auto* bone = part->FirstChildElement("Bone");
|
||||
if (bone) {
|
||||
auto* transformation = bone->Attribute("transformation");
|
||||
if (transformation) {
|
||||
auto* refID = bone->Attribute("refID");
|
||||
if (refID) transformations[refID] = transformation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// These points are well out of bounds for an actual player
|
||||
NiPoint3 lowest{ 10'000.0f, 10'000.0f, 10'000.0f };
|
||||
NiPoint3 highest{ -10'000.0f, -10'000.0f, -10'000.0f };
|
||||
|
||||
// Calculate the lowest and highest points on the entire model
|
||||
for (const auto& transformation : transformations | std::views::values) {
|
||||
auto split = GeneralUtils::SplitString(transformation, ',');
|
||||
if (split.size() < 12) {
|
||||
LOG("Not enough in the split?");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto x = GeneralUtils::TryParse<float>(split[9]).value();
|
||||
auto y = GeneralUtils::TryParse<float>(split[10]).value();
|
||||
auto z = GeneralUtils::TryParse<float>(split[11]).value();
|
||||
if (x < lowest.x) lowest.x = x;
|
||||
if (y < lowest.y) lowest.y = y;
|
||||
if (z < lowest.z) lowest.z = z;
|
||||
|
||||
if (highest.x < x) highest.x = x;
|
||||
if (highest.y < y) highest.y = y;
|
||||
if (highest.z < z) highest.z = z;
|
||||
}
|
||||
|
||||
auto delta = (highest - lowest) / 2.0f;
|
||||
auto newRootPos = lowest + delta;
|
||||
|
||||
// Clamp the Y to the lowest point on the model
|
||||
newRootPos.y = lowest.y;
|
||||
|
||||
// Adjust all positions to account for the new origin
|
||||
for (auto& transformation : transformations | std::views::values) {
|
||||
auto split = GeneralUtils::SplitString(transformation, ',');
|
||||
if (split.size() < 12) {
|
||||
LOG("Not enough in the split?");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
|
||||
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
|
||||
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
|
||||
std::stringstream stream;
|
||||
for (int i = 0; i < 9; i++) {
|
||||
stream << split[i];
|
||||
stream << ',';
|
||||
}
|
||||
stream << x << ',' << y << ',' << z;
|
||||
transformation = stream.str();
|
||||
}
|
||||
|
||||
// Finally write the new transformation back into the lxfml
|
||||
for (auto& brick : lxfml["Bricks"]) {
|
||||
auto* part = brick.FirstChildElement("Part");
|
||||
if (part) {
|
||||
auto* bone = part->FirstChildElement("Bone");
|
||||
if (bone) {
|
||||
auto* transformation = bone->Attribute("transformation");
|
||||
if (transformation) {
|
||||
auto* refID = bone->Attribute("refID");
|
||||
if (refID) {
|
||||
bone->SetAttribute("transformation", transformations[refID].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tinyxml2::XMLPrinter printer;
|
||||
doc.Print(&printer);
|
||||
|
||||
toReturn.lxfml = printer.CStr();
|
||||
toReturn.center = newRootPos;
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
Lxfml::Result Lxfml::NormalizePositionAfterFirstPart(const std::string_view data, const NiPoint3& position) {
|
||||
Result toReturn;
|
||||
tinyxml2::XMLDocument doc;
|
||||
const auto err = doc.Parse(data.data());
|
||||
if (err != tinyxml2::XML_SUCCESS) {
|
||||
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
TinyXmlUtils::DocumentReader reader(doc);
|
||||
std::map<std::string/* refID */, std::string> transformations;
|
||||
|
||||
auto lxfml = reader["LXFML"];
|
||||
if (!lxfml) {
|
||||
LOG("Failed to find LXFML element.");
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
// First get all the positions of bricks
|
||||
for (const auto& brick : lxfml["Bricks"]) {
|
||||
const auto* part = brick.FirstChildElement("Part");
|
||||
bool firstPart = true;
|
||||
while (part) {
|
||||
if (firstPart) {
|
||||
firstPart = false;
|
||||
} else {
|
||||
LOG("Found extra bricks");
|
||||
const auto* bone = part->FirstChildElement("Bone");
|
||||
if (bone) {
|
||||
auto* transformation = bone->Attribute("transformation");
|
||||
if (transformation) {
|
||||
auto* refID = bone->Attribute("refID");
|
||||
if (refID) transformations[refID] = transformation;
|
||||
}
|
||||
}
|
||||
}
|
||||
part = part->NextSiblingElement("Part");
|
||||
}
|
||||
}
|
||||
|
||||
auto newRootPos = position;
|
||||
|
||||
// Adjust all positions to account for the new origin
|
||||
for (auto& transformation : transformations | std::views::values) {
|
||||
auto split = GeneralUtils::SplitString(transformation, ',');
|
||||
if (split.size() < 12) {
|
||||
LOG("Not enough in the split?");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x;
|
||||
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y;
|
||||
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z;
|
||||
std::stringstream stream;
|
||||
for (int i = 0; i < 9; i++) {
|
||||
stream << split[i];
|
||||
stream << ',';
|
||||
}
|
||||
stream << x << ',' << y << ',' << z;
|
||||
transformation = stream.str();
|
||||
}
|
||||
|
||||
// Finally write the new transformation back into the lxfml
|
||||
for (auto& brick : lxfml["Bricks"]) {
|
||||
auto* part = brick.FirstChildElement("Part");
|
||||
bool firstPart = true;
|
||||
while (part) {
|
||||
if (firstPart) {
|
||||
firstPart = false;
|
||||
} else {
|
||||
auto* bone = part->FirstChildElement("Bone");
|
||||
if (bone) {
|
||||
auto* transformation = bone->Attribute("transformation");
|
||||
if (transformation) {
|
||||
auto* refID = bone->Attribute("refID");
|
||||
if (refID) {
|
||||
bone->SetAttribute("transformation", transformations[refID].c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
part = part->NextSiblingElement("Part");
|
||||
}
|
||||
}
|
||||
|
||||
tinyxml2::XMLPrinter printer;
|
||||
doc.Print(&printer);
|
||||
|
||||
toReturn.lxfml = printer.CStr();
|
||||
toReturn.center = newRootPos;
|
||||
return toReturn;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
#ifndef __DCOMMONVARS__H__
|
||||
#define __DCOMMONVARS__H__
|
||||
|
||||
#include <compare>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include "BitStream.h"
|
||||
#include "eConnectionType.h"
|
||||
#include "MessageType/Client.h"
|
||||
#include "BitStreamUtils.h"
|
||||
#include "MessageType/Client.h"
|
||||
#include "eConnectionType.h"
|
||||
|
||||
#pragma warning (disable:4251) //Disables SQL warnings
|
||||
|
||||
@@ -98,6 +99,8 @@ public:
|
||||
constexpr LWOZONEID() noexcept = default;
|
||||
constexpr LWOZONEID(const LWOMAPID& mapID, const LWOINSTANCEID& instanceID, const LWOCLONEID& cloneID) noexcept { m_MapID = mapID; m_InstanceID = instanceID; m_CloneID = cloneID; }
|
||||
constexpr LWOZONEID(const LWOZONEID& replacement) noexcept { *this = replacement; }
|
||||
constexpr bool operator==(const LWOZONEID&) const = default;
|
||||
constexpr auto operator<=>(const LWOZONEID&) const = default;
|
||||
|
||||
private:
|
||||
LWOMAPID m_MapID = LWOMAPID_INVALID; //1000 for VE, 1100 for AG, etc...
|
||||
|
||||
@@ -16,7 +16,9 @@ enum class eCharacterVersion : uint32_t {
|
||||
VAULT_SIZE,
|
||||
// Fixes speed base value in level component
|
||||
SPEED_BASE,
|
||||
UP_TO_DATE, // will become NJ_JAYMISSIONS
|
||||
// Fixes nexus force explorer missions
|
||||
NJ_JAYMISSIONS,
|
||||
UP_TO_DATE, // will become NEXUS_FORCE_EXPLORER
|
||||
};
|
||||
|
||||
#endif //!__ECHARACTERVERSION__H__
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#ifndef __EHTTPMETHODS__H__
|
||||
#define __EHTTPMETHODS__H__
|
||||
|
||||
#include "dPlatforms.h"
|
||||
|
||||
#ifdef DARKFLAME_PLATFORM_WIN32
|
||||
#pragma push_macro("DELETE")
|
||||
#undef DELETE
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
enum class eActivityType : uint32_t {
|
||||
PlayerLoggedIn,
|
||||
PlayerLoggedOut,
|
||||
PlayerChangedZone
|
||||
};
|
||||
|
||||
class IActivityLog {
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
class IBehaviors {
|
||||
public:
|
||||
struct Info {
|
||||
int32_t behaviorId{};
|
||||
LWOOBJID behaviorId{};
|
||||
uint32_t characterId{};
|
||||
std::string behaviorInfo;
|
||||
};
|
||||
|
||||
// This Add also takes care of updating if it exists.
|
||||
virtual void AddBehavior(const Info& info) = 0;
|
||||
virtual std::string GetBehavior(const int32_t behaviorId) = 0;
|
||||
virtual void RemoveBehavior(const int32_t behaviorId) = 0;
|
||||
virtual std::string GetBehavior(const LWOOBJID behaviorId) = 0;
|
||||
virtual void RemoveBehavior(const LWOOBJID behaviorId) = 0;
|
||||
};
|
||||
|
||||
#endif //!IBEHAVIORS_H
|
||||
|
||||
@@ -53,6 +53,9 @@ public:
|
||||
// Update the property details for the given property id.
|
||||
virtual void UpdatePropertyDetails(const IProperty::Info& info) = 0;
|
||||
|
||||
// Update the last updated time for the given property id.
|
||||
virtual void UpdateLastSave(const IProperty::Info& info) = 0;
|
||||
|
||||
// Update the property performance cost for the given property id.
|
||||
virtual void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) = 0;
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ public:
|
||||
LWOOBJID id{};
|
||||
LOT lot{};
|
||||
uint32_t ugcId{};
|
||||
std::array<int32_t, 5> behaviors{};
|
||||
std::array<LWOOBJID, 5> behaviors{};
|
||||
};
|
||||
|
||||
// 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<LWOOBJID, std::string>, 5>& behaviors) = 0;
|
||||
virtual void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<LWOOBJID, 5> behaviorIDs) {
|
||||
std::array<std::pair<LWOOBJID, std::string>, 5> behaviors;
|
||||
for (int32_t i = 0; i < behaviors.size(); i++) behaviors[i].first = behaviorIDs[i];
|
||||
UpdateModel(modelID, position, rotation, behaviors);
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -70,18 +70,19 @@ public:
|
||||
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
|
||||
void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
|
||||
void UpdatePropertyDetails(const IProperty::Info& info) override;
|
||||
void UpdateLastSave(const IProperty::Info& info) override;
|
||||
void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override;
|
||||
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
|
||||
void RemoveUnreferencedUgcModels() override;
|
||||
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
|
||||
void UpdateModel(const LWOOBJID& 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<LWOOBJID, 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;
|
||||
@@ -109,8 +110,8 @@ public:
|
||||
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
|
||||
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
|
||||
void AddBehavior(const IBehaviors::Info& info) override;
|
||||
std::string GetBehavior(const int32_t behaviorId) override;
|
||||
void RemoveBehavior(const int32_t characterId) override;
|
||||
std::string GetBehavior(const LWOOBJID behaviorId) override;
|
||||
void RemoveBehavior(const LWOOBJID characterId) override;
|
||||
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
||||
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
|
||||
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
|
||||
@@ -126,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:
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ void MySQLDatabase::AddBehavior(const IBehaviors::Info& info) {
|
||||
);
|
||||
}
|
||||
|
||||
void MySQLDatabase::RemoveBehavior(const int32_t behaviorId) {
|
||||
void MySQLDatabase::RemoveBehavior(const LWOOBJID behaviorId) {
|
||||
ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId);
|
||||
}
|
||||
|
||||
std::string MySQLDatabase::GetBehavior(const int32_t behaviorId) {
|
||||
std::string MySQLDatabase::GetBehavior(const LWOOBJID behaviorId) {
|
||||
auto result = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId);
|
||||
return result->next() ? result->getString("behavior_info").c_str() : "";
|
||||
}
|
||||
|
||||
@@ -173,6 +173,10 @@ void MySQLDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
|
||||
ExecuteUpdate("UPDATE properties SET name = ?, description = ? WHERE id = ? LIMIT 1;", info.name, info.description, info.id);
|
||||
}
|
||||
|
||||
void MySQLDatabase::UpdateLastSave(const IProperty::Info& info) {
|
||||
ExecuteUpdate("UPDATE properties SET last_updated = ? WHERE id = ?;", info.lastUpdatedTime, info.id);
|
||||
}
|
||||
|
||||
void MySQLDatabase::UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) {
|
||||
ExecuteUpdate("UPDATE properties SET performance_cost = ? WHERE zone_id = ? AND clone_id = ? LIMIT 1;", performanceCost, zoneId.GetMapID(), zoneId.GetCloneID());
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ std::vector<IPropertyContents::Model> MySQLDatabase::GetPropertyModels(const LWO
|
||||
model.rotation.y = result->getFloat("ry");
|
||||
model.rotation.z = result->getFloat("rz");
|
||||
model.ugcId = result->getUInt64("ugc_id");
|
||||
model.behaviors[0] = result->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");
|
||||
model.behaviors[0] = result->getUInt64("behavior_1");
|
||||
model.behaviors[1] = result->getUInt64("behavior_2");
|
||||
model.behaviors[2] = result->getUInt64("behavior_3");
|
||||
model.behaviors[3] = result->getUInt64("behavior_4");
|
||||
model.behaviors[4] = result->getUInt64("behavior_5");
|
||||
|
||||
toReturn.push_back(std::move(model));
|
||||
}
|
||||
@@ -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<LWOOBJID, 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->getUInt64("behavior_1");
|
||||
model.behaviors[1] = result->getUInt64("behavior_2");
|
||||
model.behaviors[2] = result->getUInt64("behavior_3");
|
||||
model.behaviors[3] = result->getUInt64("behavior_4");
|
||||
model.behaviors[4] = result->getUInt64("behavior_5");
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -68,18 +68,19 @@ public:
|
||||
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
|
||||
void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
|
||||
void UpdatePropertyDetails(const IProperty::Info& info) override;
|
||||
void UpdateLastSave(const IProperty::Info& info) override;
|
||||
void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override;
|
||||
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
|
||||
void RemoveUnreferencedUgcModels() override;
|
||||
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
|
||||
void UpdateModel(const LWOOBJID& 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<LWOOBJID, 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;
|
||||
@@ -107,8 +108,8 @@ public:
|
||||
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
|
||||
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
|
||||
void AddBehavior(const IBehaviors::Info& info) override;
|
||||
std::string GetBehavior(const int32_t behaviorId) override;
|
||||
void RemoveBehavior(const int32_t characterId) override;
|
||||
std::string GetBehavior(const LWOOBJID behaviorId) override;
|
||||
void RemoveBehavior(const LWOOBJID characterId) override;
|
||||
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
||||
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override;
|
||||
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
|
||||
@@ -124,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);
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ void SQLiteDatabase::AddBehavior(const IBehaviors::Info& info) {
|
||||
);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::RemoveBehavior(const int32_t behaviorId) {
|
||||
void SQLiteDatabase::RemoveBehavior(const LWOOBJID behaviorId) {
|
||||
ExecuteDelete("DELETE FROM behaviors WHERE behavior_id = ?", behaviorId);
|
||||
}
|
||||
|
||||
std::string SQLiteDatabase::GetBehavior(const int32_t behaviorId) {
|
||||
std::string SQLiteDatabase::GetBehavior(const LWOOBJID behaviorId) {
|
||||
auto [_, result] = ExecuteSelect("SELECT behavior_info FROM behaviors WHERE behavior_id = ?", behaviorId);
|
||||
return !result.eof() ? result.getStringField("behavior_info") : "";
|
||||
}
|
||||
|
||||
@@ -175,6 +175,10 @@ void SQLiteDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
|
||||
ExecuteUpdate("UPDATE properties SET name = ?, description = ? WHERE id = ?;", info.name, info.description, info.id);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateLastSave(const IProperty::Info& info) {
|
||||
ExecuteUpdate("UPDATE properties SET last_updated = ? WHERE id = ?;", info.lastUpdatedTime, info.id);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) {
|
||||
ExecuteUpdate("UPDATE properties SET performance_cost = ? WHERE zone_id = ? AND clone_id = ?;", performanceCost, zoneId.GetMapID(), zoneId.GetCloneID());
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ std::vector<IPropertyContents::Model> SQLiteDatabase::GetPropertyModels(const LW
|
||||
model.rotation.y = result.getFloatField("ry");
|
||||
model.rotation.z = result.getFloatField("rz");
|
||||
model.ugcId = result.getInt64Field("ugc_id");
|
||||
model.behaviors[0] = result.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");
|
||||
model.behaviors[0] = result.getInt64Field("behavior_1");
|
||||
model.behaviors[1] = result.getInt64Field("behavior_2");
|
||||
model.behaviors[2] = result.getInt64Field("behavior_3");
|
||||
model.behaviors[3] = result.getInt64Field("behavior_4");
|
||||
model.behaviors[4] = result.getInt64Field("behavior_5");
|
||||
|
||||
toReturn.push_back(std::move(model));
|
||||
result.nextRow();
|
||||
@@ -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<LWOOBJID, 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.getInt64Field("behavior_1");
|
||||
model.behaviors[1] = result.getInt64Field("behavior_2");
|
||||
model.behaviors[2] = result.getInt64Field("behavior_3");
|
||||
model.behaviors[3] = result.getInt64Field("behavior_4");
|
||||
model.behaviors[4] = result.getInt64Field("behavior_5");
|
||||
} while (result.nextRow());
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
|
||||
@@ -148,6 +148,10 @@ void TestSQLDatabase::UpdatePropertyDetails(const IProperty::Info& info) {
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::UpdateLastSave(const IProperty::Info& info) {
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) {
|
||||
|
||||
}
|
||||
@@ -164,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<LWOOBJID, std::string>, 5>& behaviors) {
|
||||
|
||||
}
|
||||
|
||||
@@ -188,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) {
|
||||
|
||||
}
|
||||
|
||||
@@ -288,11 +292,11 @@ void TestSQLDatabase::AddBehavior(const IBehaviors::Info& info) {
|
||||
|
||||
}
|
||||
|
||||
std::string TestSQLDatabase::GetBehavior(const int32_t behaviorId) {
|
||||
std::string TestSQLDatabase::GetBehavior(const LWOOBJID behaviorId) {
|
||||
return {};
|
||||
}
|
||||
|
||||
void TestSQLDatabase::RemoveBehavior(const int32_t behaviorId) {
|
||||
void TestSQLDatabase::RemoveBehavior(const LWOOBJID behaviorId) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -47,18 +47,19 @@ class TestSQLDatabase : public GameDatabase {
|
||||
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
|
||||
void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
|
||||
void UpdatePropertyDetails(const IProperty::Info& info) override;
|
||||
void UpdateLastSave(const IProperty::Info& info) override;
|
||||
void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override;
|
||||
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
|
||||
void RemoveUnreferencedUgcModels() override;
|
||||
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
|
||||
void UpdateModel(const LWOOBJID& 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<LWOOBJID, 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;
|
||||
@@ -86,8 +87,8 @@ class TestSQLDatabase : public GameDatabase {
|
||||
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
|
||||
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
|
||||
void AddBehavior(const IBehaviors::Info& info) override;
|
||||
std::string GetBehavior(const int32_t behaviorId) override;
|
||||
void RemoveBehavior(const int32_t behaviorId) override;
|
||||
std::string GetBehavior(const LWOOBJID behaviorId) override;
|
||||
void RemoveBehavior(const LWOOBJID behaviorId) override;
|
||||
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
|
||||
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
|
||||
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override { return {}; };
|
||||
@@ -104,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>
|
||||
|
||||
@@ -35,7 +36,7 @@ void MigrationRunner::RunMigrations() {
|
||||
Database::Get()->CreateMigrationHistoryTable();
|
||||
|
||||
// has to be here because when moving the files to the new folder, the migration_history table is not updated so it will run them all again.
|
||||
|
||||
|
||||
const auto migrationFolder = Database::GetMigrationFolder();
|
||||
if (!Database::Get()->IsMigrationRun("17_migration_for_migrations.sql") && migrationFolder == "mysql") {
|
||||
LOG("Running migration: 17_migration_for_migrations.sql");
|
||||
@@ -45,6 +46,8 @@ void MigrationRunner::RunMigrations() {
|
||||
|
||||
std::string finalSQL = "";
|
||||
bool runSd0Migrations = false;
|
||||
bool runNormalizeMigrations = false;
|
||||
bool runNormalizeAfterFirstPartMigrations = false;
|
||||
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/" / migrationFolder).string())) {
|
||||
auto migration = LoadMigration("dlu/" + migrationFolder + "/", entry);
|
||||
|
||||
@@ -57,6 +60,10 @@ void MigrationRunner::RunMigrations() {
|
||||
LOG("Running migration: %s", migration.name.c_str());
|
||||
if (migration.name == "5_brick_model_sd0.sql") {
|
||||
runSd0Migrations = true;
|
||||
} else if (migration.name.ends_with("_normalize_model_positions.sql")) {
|
||||
runNormalizeMigrations = true;
|
||||
} else if (migration.name.ends_with("_normalize_model_positions_after_first_part.sql")) {
|
||||
runNormalizeAfterFirstPartMigrations = true;
|
||||
} else {
|
||||
finalSQL.append(migration.data.c_str());
|
||||
}
|
||||
@@ -64,7 +71,7 @@ void MigrationRunner::RunMigrations() {
|
||||
Database::Get()->InsertMigration(migration.name);
|
||||
}
|
||||
|
||||
if (finalSQL.empty() && !runSd0Migrations) {
|
||||
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations && !runNormalizeAfterFirstPartMigrations) {
|
||||
LOG("Server database is up to date.");
|
||||
return;
|
||||
}
|
||||
@@ -88,6 +95,14 @@ void MigrationRunner::RunMigrations() {
|
||||
uint32_t numberOfTruncatedModels = BrickByBrickFix::TruncateBrokenBrickByBrickXml();
|
||||
LOG("%i models were truncated from the database.", numberOfTruncatedModels);
|
||||
}
|
||||
|
||||
if (runNormalizeMigrations) {
|
||||
ModelNormalizeMigration::Run();
|
||||
}
|
||||
|
||||
if (runNormalizeAfterFirstPartMigrations) {
|
||||
ModelNormalizeMigration::RunAfterFirstPart();
|
||||
}
|
||||
}
|
||||
|
||||
void MigrationRunner::RunSQLiteMigrations() {
|
||||
|
||||
50
dDatabase/ModelNormalizeMigration.cpp
Normal file
50
dDatabase/ModelNormalizeMigration.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#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::NormalizePositionOnlyFirstPart(asStr);
|
||||
if (newCenter == NiPoint3Constant::ZERO) {
|
||||
LOG("Failed to update model %llu due to failure reading xml.");
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z);
|
||||
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
|
||||
auto asStream = sd0.GetAsStream();
|
||||
Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors);
|
||||
Database::Get()->UpdateUgcModelData(id, asStream);
|
||||
}
|
||||
Database::Get()->SetAutoCommit(oldCommit);
|
||||
}
|
||||
|
||||
void ModelNormalizeMigration::RunAfterFirstPart() {
|
||||
const auto oldCommit = Database::Get()->GetAutoCommit();
|
||||
Database::Get()->SetAutoCommit(false);
|
||||
for (auto& [lxfmlData, id, modelID] : Database::Get()->GetAllUgcModels()) {
|
||||
const auto model = Database::Get()->GetModel(modelID);
|
||||
// only BBB models (lot 14) need to have their position fixed from the above blunder
|
||||
if (model.lot != 14) continue;
|
||||
|
||||
Sd0 sd0(lxfmlData);
|
||||
const auto asStr = sd0.GetAsStringUncompressed();
|
||||
const auto [newLxfml, newCenter] = Lxfml::NormalizePositionAfterFirstPart(asStr, model.position);
|
||||
|
||||
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
|
||||
auto asStream = sd0.GetAsStream();
|
||||
Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors);
|
||||
Database::Get()->UpdateUgcModelData(id, asStream);
|
||||
}
|
||||
Database::Get()->SetAutoCommit(oldCommit);
|
||||
}
|
||||
12
dDatabase/ModelNormalizeMigration.h
Normal file
12
dDatabase/ModelNormalizeMigration.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Darkflame Universe
|
||||
// Copyright 2025
|
||||
|
||||
#ifndef MODELNORMALIZEMIGRATION_H
|
||||
#define MODELNORMALIZEMIGRATION_H
|
||||
|
||||
namespace ModelNormalizeMigration {
|
||||
void Run();
|
||||
void RunAfterFirstPart();
|
||||
};
|
||||
|
||||
#endif //!MODELNORMALIZEMIGRATION_H
|
||||
@@ -16,7 +16,7 @@ include_directories(
|
||||
add_library(dGameBase OBJECT ${DGAME_SOURCES})
|
||||
target_precompile_headers(dGameBase PRIVATE ${HEADERS_DGAME})
|
||||
target_include_directories(dGameBase PUBLIC "." "dEntity"
|
||||
PRIVATE "dComponents" "dGameMessages" "dBehaviors" "dMission" "dUtilities" "dInventory" "dCinema"
|
||||
PRIVATE "dComponents" "dGameMessages" "dBehaviors" "dMission" "dUtilities" "dInventory"
|
||||
$<TARGET_PROPERTY:dPropertyBehaviors,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
"${PROJECT_SOURCE_DIR}/dCommon"
|
||||
"${PROJECT_SOURCE_DIR}/dCommon/dEnums"
|
||||
@@ -40,7 +40,6 @@ add_subdirectory(dInventory)
|
||||
add_subdirectory(dMission)
|
||||
add_subdirectory(dPropertyBehaviors)
|
||||
add_subdirectory(dUtilities)
|
||||
add_subdirectory(dCinema)
|
||||
|
||||
add_library(dGame STATIC
|
||||
$<TARGET_OBJECTS:dGameBase>
|
||||
@@ -52,7 +51,6 @@ add_library(dGame STATIC
|
||||
$<TARGET_OBJECTS:dMission>
|
||||
$<TARGET_OBJECTS:dPropertyBehaviors>
|
||||
$<TARGET_OBJECTS:dUtilities>
|
||||
$<TARGET_OBJECTS:dCinema>
|
||||
)
|
||||
target_link_libraries(dGame INTERFACE dNet)
|
||||
target_include_directories(dGame INTERFACE
|
||||
@@ -65,5 +63,4 @@ target_include_directories(dGame INTERFACE
|
||||
$<TARGET_PROPERTY:dMission,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
$<TARGET_PROPERTY:dPropertyBehaviors,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
$<TARGET_PROPERTY:dUtilities,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
$<TARGET_PROPERTY:dCinema,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
)
|
||||
|
||||
582
dGame/Entity.cpp
582
dGame/Entity.cpp
File diff suppressed because it is too large
Load Diff
@@ -34,7 +34,6 @@ namespace tinyxml2 {
|
||||
};
|
||||
|
||||
class Player;
|
||||
class EntityInfo;
|
||||
class User;
|
||||
class Spawner;
|
||||
class ScriptComponent;
|
||||
@@ -45,6 +44,7 @@ class Item;
|
||||
class Character;
|
||||
class EntityCallbackTimer;
|
||||
class PositionUpdate;
|
||||
struct EntityInfo;
|
||||
enum class eTriggerEventType;
|
||||
enum class eGameMasterLevel : uint8_t;
|
||||
enum class eReplicaComponentType : uint32_t;
|
||||
@@ -60,7 +60,7 @@ namespace CppScripts {
|
||||
*/
|
||||
class Entity {
|
||||
public:
|
||||
explicit Entity(const LWOOBJID& objectID, EntityInfo info, User* parentUser = nullptr, Entity* parentEntity = nullptr);
|
||||
Entity(const LWOOBJID& objectID, const EntityInfo& info, User* parentUser = nullptr, Entity* parentEntity = nullptr);
|
||||
~Entity();
|
||||
|
||||
void Initialize();
|
||||
@@ -113,7 +113,7 @@ public:
|
||||
|
||||
float GetDefaultScale() const;
|
||||
|
||||
const NiPoint3& GetPosition() const;
|
||||
NiPoint3 GetPosition() const;
|
||||
|
||||
const NiQuaternion& GetRotation() const;
|
||||
|
||||
@@ -124,6 +124,8 @@ public:
|
||||
// then return the collision group from that.
|
||||
int32_t GetCollisionGroup() const;
|
||||
|
||||
const NiPoint3& GetVelocity() const;
|
||||
|
||||
/**
|
||||
* Setters
|
||||
*/
|
||||
@@ -144,9 +146,11 @@ public:
|
||||
|
||||
void SetRotation(const NiQuaternion& rotation);
|
||||
|
||||
void SetRespawnPos(const NiPoint3& position);
|
||||
void SetRespawnPos(const NiPoint3& position) const;
|
||||
|
||||
void SetRespawnRot(const NiQuaternion& rotation);
|
||||
void SetRespawnRot(const NiQuaternion& rotation) const;
|
||||
|
||||
void SetVelocity(const NiPoint3& velocity);
|
||||
|
||||
/**
|
||||
* Component management
|
||||
@@ -165,7 +169,7 @@ public:
|
||||
void AddComponent(eReplicaComponentType componentId, Component* component);
|
||||
|
||||
// This is expceted to never return nullptr, an assert checks this.
|
||||
CppScripts::Script* const GetScript();
|
||||
CppScripts::Script* const GetScript() const;
|
||||
|
||||
void Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, const std::string& notificationName);
|
||||
void Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationName);
|
||||
@@ -178,8 +182,8 @@ public:
|
||||
void RemoveParent();
|
||||
|
||||
// Adds a timer to start next frame with the given name and time.
|
||||
void AddTimer(std::string name, float time);
|
||||
void AddCallbackTimer(float time, std::function<void()> callback);
|
||||
void AddTimer(const std::string& name, float time);
|
||||
void AddCallbackTimer(float time, const std::function<void()> callback);
|
||||
bool HasTimer(const std::string& name);
|
||||
void CancelCallbackTimers();
|
||||
void CancelAllTimers();
|
||||
@@ -191,7 +195,7 @@ public:
|
||||
std::unordered_map<eReplicaComponentType, Component*>& GetComponents() { return m_Components; } // TODO: Remove
|
||||
|
||||
void WriteBaseReplicaData(RakNet::BitStream& outBitStream, eReplicaPacketType packetType);
|
||||
void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType);
|
||||
void WriteComponents(RakNet::BitStream& outBitStream, eReplicaPacketType packetType) const;
|
||||
void UpdateXMLDoc(tinyxml2::XMLDocument& doc);
|
||||
void Update(float deltaTime);
|
||||
|
||||
@@ -238,21 +242,21 @@ public:
|
||||
void AddDieCallback(const std::function<void()>& callback);
|
||||
void Resurrect();
|
||||
|
||||
void AddLootItem(const Loot::Info& info);
|
||||
void PickupItem(const LWOOBJID& objectID);
|
||||
void AddLootItem(const Loot::Info& info) const;
|
||||
void PickupItem(const LWOOBJID& objectID) const;
|
||||
|
||||
bool CanPickupCoins(uint64_t count);
|
||||
void RegisterCoinDrop(uint64_t count);
|
||||
bool PickupCoins(uint64_t count) const;
|
||||
void RegisterCoinDrop(uint64_t count) const;
|
||||
|
||||
void ScheduleKillAfterUpdate(Entity* murderer = nullptr);
|
||||
void TriggerEvent(eTriggerEventType event, Entity* optionalTarget = nullptr);
|
||||
void TriggerEvent(eTriggerEventType event, Entity* optionalTarget = nullptr) const;
|
||||
void ScheduleDestructionAfterUpdate() { m_ShouldDestroyAfterUpdate = true; }
|
||||
|
||||
const NiPoint3& GetRespawnPosition() const;
|
||||
const NiQuaternion& GetRespawnRotation() const;
|
||||
|
||||
void Sleep();
|
||||
void Wake();
|
||||
void Sleep() const;
|
||||
void Wake() const;
|
||||
bool IsSleeping() const;
|
||||
|
||||
/*
|
||||
@@ -262,7 +266,7 @@ public:
|
||||
* Retroactively corrects the model vault size due to incorrect initialization in a previous patch.
|
||||
*
|
||||
*/
|
||||
void RetroactiveVaultSize();
|
||||
void RetroactiveVaultSize() const;
|
||||
bool GetBoolean(const std::u16string& name) const;
|
||||
int32_t GetI32(const std::u16string& name) const;
|
||||
int64_t GetI64(const std::u16string& name) const;
|
||||
@@ -329,8 +333,9 @@ public:
|
||||
* @brief The observable for player entity position updates.
|
||||
*/
|
||||
static Observable<Entity*, const PositionUpdate&> OnPlayerPositionUpdate;
|
||||
|
||||
protected:
|
||||
|
||||
private:
|
||||
void WriteLDFData(const std::vector<LDFBaseData*>& ldf, RakNet::BitStream& outBitStream) const;
|
||||
LWOOBJID m_ObjectID;
|
||||
|
||||
LOT m_TemplateID;
|
||||
@@ -353,7 +358,6 @@ protected:
|
||||
Entity* m_ParentEntity; //For spawners and the like
|
||||
std::vector<Entity*> m_ChildEntities;
|
||||
eGameMasterLevel m_GMLevel;
|
||||
uint16_t m_CollectibleID;
|
||||
std::vector<std::string> m_Groups;
|
||||
uint16_t m_NetworkID;
|
||||
std::vector<std::function<void()>> m_DieCallbacks;
|
||||
@@ -379,6 +383,8 @@ protected:
|
||||
|
||||
bool m_IsParentChildDirty = true;
|
||||
|
||||
bool m_IsSleeping = false;
|
||||
|
||||
/*
|
||||
* Collision
|
||||
*/
|
||||
@@ -387,7 +393,7 @@ protected:
|
||||
// objectID of receiver and map of notification name to script
|
||||
std::map<LWOOBJID, std::map<std::string, CppScripts::Script*>> m_Subscriptions;
|
||||
|
||||
std::multimap<MessageType::Game, std::function<bool(GameMessages::GameMsg&)>> m_MsgHandlers;
|
||||
std::unordered_multimap<MessageType::Game, std::function<bool(GameMessages::GameMsg&)>> m_MsgHandlers;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -435,7 +441,7 @@ const T& Entity::GetVar(const std::u16string& name) const {
|
||||
template<typename T>
|
||||
T Entity::GetVarAs(const std::u16string& name) const {
|
||||
const auto data = GetVarAsString(name);
|
||||
|
||||
|
||||
return GeneralUtils::TryParse<T>(data).value_or(LDFData<T>::Default);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "eReplicaPacketType.h"
|
||||
#include "PlayerManager.h"
|
||||
#include "GhostComponent.h"
|
||||
#include "ServerPreconditions.h"
|
||||
#include <ranges>
|
||||
|
||||
// Configure which zones have ghosting disabled, mostly small worlds.
|
||||
@@ -100,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);
|
||||
@@ -321,7 +320,7 @@ const std::unordered_map<std::string, LWOOBJID>& EntityManager::GetSpawnPointEnt
|
||||
return m_SpawnPoints;
|
||||
}
|
||||
|
||||
void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr, const bool skipChecks) {
|
||||
void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr) {
|
||||
if (!entity) {
|
||||
LOG("Attempted to construct null entity");
|
||||
return;
|
||||
@@ -364,16 +363,14 @@ void EntityManager::ConstructEntity(Entity* entity, const SystemAddress& sysAddr
|
||||
entity->WriteComponents(stream, eReplicaPacketType::CONSTRUCTION);
|
||||
|
||||
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) {
|
||||
if (skipChecks) {
|
||||
Game::server->Send(stream, UNASSIGNED_SYSTEM_ADDRESS, true);
|
||||
} else {
|
||||
for (auto* player : PlayerManager::GetAllPlayers()) {
|
||||
if (player->GetPlayerReadyForUpdates()) {
|
||||
Game::server->Send(stream, player->GetSystemAddress(), false);
|
||||
} else {
|
||||
auto* ghostComponent = player->GetComponent<GhostComponent>();
|
||||
if (ghostComponent) ghostComponent->AddLimboConstruction(entity->GetObjectID());
|
||||
}
|
||||
for (auto* player : PlayerManager::GetAllPlayers()) {
|
||||
// Don't need to construct the player to themselves
|
||||
if (entity->GetObjectID() == player->GetObjectID()) continue;
|
||||
if (player->GetPlayerReadyForUpdates()) {
|
||||
Game::server->Send(stream, player->GetSystemAddress(), false);
|
||||
} else {
|
||||
auto* ghostComponent = player->GetComponent<GhostComponent>();
|
||||
if (ghostComponent) ghostComponent->AddLimboConstruction(entity->GetObjectID());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -397,7 +394,7 @@ void EntityManager::ConstructAllEntities(const SystemAddress& sysAddr) {
|
||||
}
|
||||
}
|
||||
|
||||
UpdateGhosting(PlayerManager::GetPlayer(sysAddr));
|
||||
UpdateGhosting(PlayerManager::GetPlayer(sysAddr), true);
|
||||
}
|
||||
|
||||
void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr) {
|
||||
@@ -420,7 +417,7 @@ void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr)
|
||||
|
||||
void EntityManager::SerializeEntity(Entity* entity) {
|
||||
if (!entity) return;
|
||||
|
||||
|
||||
EntityManager::SerializeEntity(*entity);
|
||||
}
|
||||
|
||||
@@ -466,7 +463,7 @@ void EntityManager::UpdateGhosting() {
|
||||
m_PlayersToUpdateGhosting.clear();
|
||||
}
|
||||
|
||||
void EntityManager::UpdateGhosting(Entity* player) {
|
||||
void EntityManager::UpdateGhosting(Entity* player, const bool constructAll) {
|
||||
if (!player) return;
|
||||
|
||||
auto* missionComponent = player->GetComponent<MissionComponent>();
|
||||
@@ -494,15 +491,13 @@ void EntityManager::UpdateGhosting(Entity* player) {
|
||||
ghostingDistanceMax = ghostingDistanceMin;
|
||||
}
|
||||
|
||||
auto condition = ServerPreconditions::CheckPreconditions(entity, player);
|
||||
|
||||
if (observed && ((distance > ghostingDistanceMax && !isOverride) || !condition)) {
|
||||
if (observed && distance > ghostingDistanceMax && !isOverride) {
|
||||
ghostComponent->GhostEntity(id);
|
||||
|
||||
DestructEntity(entity, player->GetSystemAddress());
|
||||
|
||||
entity->SetObservers(entity->GetObservers() - 1);
|
||||
} else if (!observed && ghostingDistanceMin > distance && condition) {
|
||||
} else if (!observed && ghostingDistanceMin > distance) {
|
||||
// Check collectables, don't construct if it has been collected
|
||||
uint32_t collectionId = entity->GetCollectibleID();
|
||||
|
||||
@@ -516,9 +511,12 @@ void EntityManager::UpdateGhosting(Entity* player) {
|
||||
|
||||
ghostComponent->ObserveEntity(id);
|
||||
|
||||
ConstructEntity(entity, player->GetSystemAddress());
|
||||
|
||||
entity->SetObservers(entity->GetObservers() + 1);
|
||||
|
||||
// TODO: figure out if zone control should be ghosted at all
|
||||
if (constructAll && entity->GetObjectID() == GetZoneControlEntity()->GetObjectID()) continue;
|
||||
|
||||
ConstructEntity(entity, player->GetSystemAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -542,15 +540,13 @@ void EntityManager::CheckGhosting(Entity* entity) {
|
||||
|
||||
const auto distance = NiPoint3::DistanceSquared(referencePoint, entityPoint);
|
||||
|
||||
const auto precondition = ServerPreconditions::CheckPreconditions(entity, player);
|
||||
|
||||
if (observed && (distance > m_GhostDistanceMaxSquared || !precondition)) {
|
||||
if (observed && distance > m_GhostDistanceMaxSquared) {
|
||||
ghostComponent->GhostEntity(id);
|
||||
|
||||
DestructEntity(entity, player->GetSystemAddress());
|
||||
|
||||
entity->SetObservers(entity->GetObservers() - 1);
|
||||
} else if (!observed && (m_GhostDistanceMinSqaured > distance && precondition)) {
|
||||
} else if (!observed && m_GhostDistanceMinSqaured > distance) {
|
||||
ghostComponent->ObserveEntity(id);
|
||||
|
||||
ConstructEntity(entity, player->GetSystemAddress());
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "dCommonVars.h"
|
||||
|
||||
class Entity;
|
||||
class EntityInfo;
|
||||
struct EntityInfo;
|
||||
class Player;
|
||||
class User;
|
||||
enum class eReplicaComponentType : uint32_t;
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
const std::unordered_map<LWOOBJID, Entity*> GetAllEntities() const { return m_Entities; }
|
||||
#endif
|
||||
|
||||
void ConstructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS, bool skipChecks = false);
|
||||
void ConstructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
|
||||
void DestructEntity(Entity* entity, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
|
||||
void SerializeEntity(Entity* entity);
|
||||
void SerializeEntity(const Entity& entity);
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
void SetGhostDistanceMin(float value);
|
||||
void QueueGhostUpdate(LWOOBJID playerID);
|
||||
void UpdateGhosting();
|
||||
void UpdateGhosting(Entity* player);
|
||||
void UpdateGhosting(Entity* player, const bool constructAll = false);
|
||||
void CheckGhosting(Entity* entity);
|
||||
Entity* GetGhostCandidate(LWOOBJID id) const;
|
||||
bool GetGhostingEnabled() const;
|
||||
|
||||
@@ -24,12 +24,13 @@ namespace LeaderboardManager {
|
||||
std::map<GameID, Leaderboard::Type> leaderboardCache;
|
||||
}
|
||||
|
||||
Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type leaderboardType) {
|
||||
Leaderboard::Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const uint32_t numResults, const Leaderboard::Type leaderboardType) {
|
||||
this->gameID = gameID;
|
||||
this->weekly = weekly;
|
||||
this->infoType = infoType;
|
||||
this->leaderboardType = leaderboardType;
|
||||
this->relatedPlayer = relatedPlayer;
|
||||
this->numResults = numResults;
|
||||
}
|
||||
|
||||
Leaderboard::~Leaderboard() {
|
||||
@@ -144,7 +145,7 @@ void QueryToLdf(Leaderboard& leaderboard, const std::vector<ILeaderboard::Entry>
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> FilterTo10(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer, const Leaderboard::InfoType infoType) {
|
||||
std::vector<ILeaderboard::Entry> FilterToNumResults(const std::vector<ILeaderboard::Entry>& leaderboard, const uint32_t relatedPlayer, const Leaderboard::InfoType infoType, const uint32_t numResults) {
|
||||
std::vector<ILeaderboard::Entry> toReturn;
|
||||
|
||||
int32_t index = 0;
|
||||
@@ -155,18 +156,19 @@ std::vector<ILeaderboard::Entry> FilterTo10(const std::vector<ILeaderboard::Entr
|
||||
}
|
||||
}
|
||||
|
||||
if (leaderboard.size() < 10) {
|
||||
if (leaderboard.size() < numResults) {
|
||||
toReturn.assign(leaderboard.begin(), leaderboard.end());
|
||||
index = 0;
|
||||
} else if (index < 10) {
|
||||
toReturn.assign(leaderboard.begin(), leaderboard.begin() + 10); // get the top 10 since we are in the top 10
|
||||
} else if (index < numResults) {
|
||||
toReturn.assign(leaderboard.begin(), leaderboard.begin() + numResults); // get the top 10 since we are in the top 10
|
||||
index = 0;
|
||||
} else if (index > leaderboard.size() - 10) {
|
||||
toReturn.assign(leaderboard.end() - 10, leaderboard.end()); // get the bottom 10 since we are in the bottom 10
|
||||
index = leaderboard.size() - 10;
|
||||
} else if (index > leaderboard.size() - numResults) {
|
||||
toReturn.assign(leaderboard.end() - numResults, leaderboard.end()); // get the bottom 10 since we are in the bottom 10
|
||||
index = leaderboard.size() - numResults;
|
||||
} else {
|
||||
toReturn.assign(leaderboard.begin() + index - 5, leaderboard.begin() + index + 5); // get the 5 above and below
|
||||
index -= 5;
|
||||
auto half = numResults / 2;
|
||||
toReturn.assign(leaderboard.begin() + index - half, leaderboard.begin() + index + half); // get the 5 above and below
|
||||
index -= half;
|
||||
}
|
||||
|
||||
int32_t i = index;
|
||||
@@ -178,14 +180,16 @@ std::vector<ILeaderboard::Entry> FilterTo10(const std::vector<ILeaderboard::Entr
|
||||
}
|
||||
|
||||
std::vector<ILeaderboard::Entry> FilterWeeklies(const std::vector<ILeaderboard::Entry>& leaderboard) {
|
||||
using namespace std::chrono;
|
||||
// Filter the leaderboard to only include entries from the last week
|
||||
const auto currentTime = std::chrono::system_clock::now();
|
||||
auto epochTime = currentTime.time_since_epoch().count();
|
||||
constexpr auto SECONDS_IN_A_WEEK = 60 * 60 * 24 * 7; // if you think im taking leap seconds into account thats cute.
|
||||
const auto epochTime = system_clock::now();
|
||||
constexpr auto oneWeek = weeks(1);
|
||||
|
||||
std::vector<ILeaderboard::Entry> weeklyLeaderboard;
|
||||
for (const auto& entry : leaderboard) {
|
||||
if (epochTime - entry.lastPlayedTimestamp < SECONDS_IN_A_WEEK) {
|
||||
const sys_time<seconds> asSysTime(seconds(entry.lastPlayedTimestamp));
|
||||
const auto timeDiff = epochTime - asSysTime;
|
||||
if (timeDiff < oneWeek) {
|
||||
weeklyLeaderboard.push_back(entry);
|
||||
}
|
||||
}
|
||||
@@ -213,14 +217,15 @@ std::vector<ILeaderboard::Entry> ProcessLeaderboard(
|
||||
const std::vector<ILeaderboard::Entry>& leaderboard,
|
||||
const bool weekly,
|
||||
const Leaderboard::InfoType infoType,
|
||||
const uint32_t relatedPlayer) {
|
||||
const uint32_t relatedPlayer,
|
||||
const uint32_t numResults) {
|
||||
std::vector<ILeaderboard::Entry> toReturn;
|
||||
|
||||
if (infoType == Leaderboard::InfoType::Friends) {
|
||||
const auto friendsLeaderboard = FilterFriends(leaderboard, relatedPlayer);
|
||||
toReturn = FilterTo10(weekly ? FilterWeeklies(friendsLeaderboard) : friendsLeaderboard, relatedPlayer, infoType);
|
||||
toReturn = FilterToNumResults(weekly ? FilterWeeklies(friendsLeaderboard) : friendsLeaderboard, relatedPlayer, infoType, numResults);
|
||||
} else {
|
||||
toReturn = FilterTo10(weekly ? FilterWeeklies(leaderboard) : leaderboard, relatedPlayer, infoType);
|
||||
toReturn = FilterToNumResults(weekly ? FilterWeeklies(leaderboard) : leaderboard, relatedPlayer, infoType, numResults);
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
@@ -255,7 +260,7 @@ void Leaderboard::SetupLeaderboard(bool weekly) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto processedLeaderboard = ProcessLeaderboard(leaderboardRes, weekly, infoType, relatedPlayer);
|
||||
const auto processedLeaderboard = ProcessLeaderboard(leaderboardRes, weekly, infoType, relatedPlayer, numResults);
|
||||
|
||||
QueryToLdf(*this, processedLeaderboard);
|
||||
}
|
||||
@@ -301,8 +306,8 @@ void LeaderboardManager::SaveScore(const LWOOBJID& playerID, const GameID activi
|
||||
}
|
||||
}
|
||||
|
||||
void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID) {
|
||||
Leaderboard leaderboard(gameID, infoType, weekly, playerID, GetLeaderboardType(gameID));
|
||||
void LeaderboardManager::SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t numResults) {
|
||||
Leaderboard leaderboard(gameID, infoType, weekly, playerID, numResults, GetLeaderboardType(gameID));
|
||||
leaderboard.SetupLeaderboard(weekly);
|
||||
leaderboard.Send(targetID);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ public:
|
||||
None
|
||||
};
|
||||
Leaderboard() = delete;
|
||||
Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const Leaderboard::Type = None);
|
||||
Leaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, LWOOBJID relatedPlayer, const uint32_t numResults, const Leaderboard::Type = None);
|
||||
|
||||
~Leaderboard();
|
||||
|
||||
@@ -79,6 +79,7 @@ private:
|
||||
InfoType infoType;
|
||||
Leaderboard::Type leaderboardType;
|
||||
bool weekly;
|
||||
uint32_t numResults;
|
||||
public:
|
||||
LeaderboardEntry& PushBackEntry() {
|
||||
return entries.emplace_back();
|
||||
@@ -90,7 +91,7 @@ public:
|
||||
};
|
||||
|
||||
namespace LeaderboardManager {
|
||||
void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID);
|
||||
void SendLeaderboard(const GameID gameID, const Leaderboard::InfoType infoType, const bool weekly, const LWOOBJID playerID, const LWOOBJID targetID, const uint32_t numResults);
|
||||
|
||||
void SaveScore(const LWOOBJID& playerID, const GameID activityId, const float primaryScore, const float secondaryScore = 0, const float tertiaryScore = 0);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
#include "MessageType/Chat.h"
|
||||
#include "BitStreamUtils.h"
|
||||
#include "CheatDetection.h"
|
||||
#include "dConfig.h"
|
||||
#include "CharacterComponent.h"
|
||||
|
||||
UserManager* UserManager::m_Address = nullptr;
|
||||
|
||||
@@ -341,7 +341,10 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
|
||||
|
||||
xml << "<char acct=\"" << u->GetAccountID() << "\" cc=\"0\" gm=\"0\" ft=\"0\" llog=\"" << time(NULL) << "\" ";
|
||||
xml << "ls=\"0\" lzx=\"-626.5847\" lzy=\"613.3515\" lzz=\"-28.6374\" lzrx=\"0.0\" lzry=\"0.7015\" lzrz=\"0.0\" lzrw=\"0.7126\" ";
|
||||
xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\"></char>";
|
||||
xml << "stt=\"0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;\">";
|
||||
xml << "<vl><l id=\"1000\" cid=\"0\"/></vl>";
|
||||
|
||||
xml << "</char>";
|
||||
|
||||
xml << "<dest hm=\"4\" hc=\"4\" im=\"0\" ic=\"0\" am=\"0\" ac=\"0\" d=\"0\"/>";
|
||||
|
||||
@@ -518,18 +521,18 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID
|
||||
Database::Get()->UpdateLastLoggedInCharacter(playerID);
|
||||
|
||||
uint32_t zoneID = character->GetZoneID();
|
||||
if (zoneID == LWOZONEID_INVALID) {
|
||||
const std::string& defaultWorld = Game::config->GetValue("default_world");
|
||||
if (!defaultWorld.empty()) {
|
||||
zoneID = std::stoul(defaultWorld);
|
||||
} else {
|
||||
zoneID = 1000; //Send char to VE
|
||||
}
|
||||
}
|
||||
if (zoneID == LWOZONEID_INVALID) zoneID = 1000; //Send char to VE
|
||||
|
||||
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, zoneID, character->GetZoneClone(), false, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
|
||||
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", character->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
|
||||
if (character) {
|
||||
auto* entity = Game::entityManager->GetEntity(character->GetObjectID());
|
||||
if (entity) {
|
||||
auto* characterComponent = entity->GetComponent<CharacterComponent>();
|
||||
if (characterComponent) {
|
||||
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
|
||||
}
|
||||
}
|
||||
character->SetZoneID(zoneID);
|
||||
character->SetZoneInstance(zoneInstance);
|
||||
character->SetZoneClone(zoneClone);
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
#include "Game.h"
|
||||
#include "Logger.h"
|
||||
|
||||
#include "Recorder.h"
|
||||
|
||||
void AttackDelayBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bitStream, const BehaviorBranchContext branch) {
|
||||
uint32_t handle{};
|
||||
|
||||
@@ -13,8 +11,6 @@ void AttackDelayBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bi
|
||||
LOG("Unable to read handle from bitStream, aborting Handle! %i", bitStream.GetNumberOfUnreadBits());
|
||||
return;
|
||||
};
|
||||
|
||||
Cinema::Recording::Recorder::RegisterEffectForActor(context->originator, this->m_effectId);
|
||||
|
||||
for (auto i = 0u; i < this->m_numIntervals; ++i) {
|
||||
context->RegisterSyncBehavior(handle, this, branch, this->m_delay * i, m_ignoreInterrupts);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -63,7 +63,6 @@ target_include_directories(dBehaviors PUBLIC "."
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dUtilities" # Preconditions.h via QuickBuildComponent.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dEntity" # via dZoneManager.h, Spawner.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dInventory" # via CharacterComponent.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dCinema"
|
||||
"${PROJECT_SOURCE_DIR}/dZoneManager" # via BasicAttackBehavior.cpp
|
||||
)
|
||||
target_precompile_headers(dBehaviors REUSE_FROM dGameBase)
|
||||
|
||||
@@ -3,17 +3,13 @@
|
||||
#include "BehaviorContext.h"
|
||||
#include "BehaviorBranchContext.h"
|
||||
|
||||
#include "Recorder.h"
|
||||
|
||||
void PlayEffectBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bitStream, BehaviorBranchContext branch) {
|
||||
const auto& target = branch.target == LWOOBJID_EMPTY ? context->originator : branch.target;
|
||||
|
||||
Cinema::Recording::Recorder::RegisterEffectForActor(target, this->m_effectId);
|
||||
|
||||
// On managed behaviors this is handled by the client
|
||||
if (!context->unmanaged)
|
||||
return;
|
||||
|
||||
const auto& target = branch.target == LWOOBJID_EMPTY ? context->originator : branch.target;
|
||||
|
||||
PlayFx(u"", target);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,10 +42,15 @@ void PropertyTeleportBehavior::Handle(BehaviorContext* context, RakNet::BitStrea
|
||||
|
||||
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
|
||||
if (entity->GetCharacter()) {
|
||||
auto* characterComponent = entity->GetComponent<CharacterComponent>();
|
||||
if (characterComponent) {
|
||||
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
|
||||
characterComponent->SetLastRocketConfig(u"");
|
||||
}
|
||||
|
||||
entity->GetCharacter()->SetZoneID(zoneID);
|
||||
entity->GetCharacter()->SetZoneInstance(zoneInstance);
|
||||
entity->GetCharacter()->SetZoneClone(zoneClone);
|
||||
entity->GetComponent<CharacterComponent>()->SetLastRocketConfig(u"");
|
||||
}
|
||||
|
||||
entity->GetCharacter()->SaveXMLToDatabase();
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
set(DGAME_DCINEMA_SOURCES "Recorder.cpp"
|
||||
"Prefab.cpp"
|
||||
"Scene.cpp"
|
||||
"Play.cpp")
|
||||
|
||||
add_library(dCinema STATIC ${DGAME_DCINEMA_SOURCES})
|
||||
target_precompile_headers(dCinema REUSE_FROM dGameBase)
|
||||
target_link_libraries(dCinema
|
||||
PUBLIC dPhysics dDatabase
|
||||
INTERFACE dUtilities dCommon dBehaviors dChatFilter dMission dInventory dComponents
|
||||
)
|
||||
|
||||
target_include_directories(dCinema PUBLIC "."
|
||||
PRIVATE
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dComponents" # direct MissionComponent.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dUtilities" # direct SlashCommandHandler.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dPropertyBehaviors" # direct ControlBehaviors.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dMission" # via MissionComponent.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dBehaviors" # via InventoryComponent.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dInventory" # via InventoryComponent.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dEntity" # via dZoneManager/Spawner.h
|
||||
"${PROJECT_SOURCE_DIR}/dGame/dGameMessages"
|
||||
"${PROJECT_SOURCE_DIR}/dZoneManager" # via GameMessages.cpp, GameMessageHandler.cpp
|
||||
)
|
||||
@@ -1,87 +0,0 @@
|
||||
#include "Play.h"
|
||||
|
||||
#include "Scene.h"
|
||||
|
||||
#include "EntityManager.h"
|
||||
|
||||
using namespace Cinema;
|
||||
|
||||
void Play::Conclude() {
|
||||
auto* player = Game::entityManager->GetEntity(this->player);
|
||||
|
||||
if (player == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
scene->Conclude(player);
|
||||
}
|
||||
|
||||
void Play::SetupCheckForAudience() {
|
||||
if (m_CheckForAudience) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_CheckForAudience = true;
|
||||
|
||||
CheckForAudience();
|
||||
}
|
||||
|
||||
void Play::CheckForAudience() {
|
||||
auto* player = Game::entityManager->GetEntity(this->player);
|
||||
|
||||
if (player == nullptr) {
|
||||
CleanUp();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (scene->IsPlayerInBounds(player)) {
|
||||
SignalBarrier("audience");
|
||||
|
||||
m_PlayerHasBeenInsideBounds = true;
|
||||
}
|
||||
|
||||
if (!scene->IsPlayerInMaximumShowingDistance(player)) {
|
||||
if (m_PlayerHasBeenInsideBounds) {
|
||||
Conclude();
|
||||
}
|
||||
|
||||
CleanUp();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// As the scene isn't associated with a specifc objects, we'll use the zone control entity to setup a callback.
|
||||
Game::entityManager->GetZoneControlEntity()->AddCallbackTimer(1.0f, [this]() {
|
||||
CheckForAudience();
|
||||
});
|
||||
}
|
||||
|
||||
void Play::CleanUp() {
|
||||
LOG("Cleaning up play with %d entities", entities.size());
|
||||
|
||||
for (const auto& entity : entities) {
|
||||
Game::entityManager->DestroyEntity(entity);
|
||||
}
|
||||
}
|
||||
|
||||
void Play::SetupBarrier(const std::string& barrier, const std::function<void()>& callback) {
|
||||
// Add the callback to the barrier
|
||||
m_Barriers[barrier].push_back(callback);
|
||||
}
|
||||
|
||||
void Play::SignalBarrier(const std::string& barrier) {
|
||||
const auto& it = m_Barriers.find(barrier);
|
||||
|
||||
if (it == m_Barriers.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& callback : it->second) {
|
||||
callback();
|
||||
}
|
||||
|
||||
m_Barriers.erase(it);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "dCommonVars.h"
|
||||
|
||||
namespace Cinema
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A collection of variables and other information to be shared between scenes and acts.
|
||||
*/
|
||||
struct Play
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief The variables in the collection.
|
||||
*/
|
||||
std::unordered_map<std::string, std::string> variables;
|
||||
|
||||
/**
|
||||
* @brief Associated player which is watching the scene.
|
||||
*/
|
||||
LWOOBJID player;
|
||||
|
||||
/**
|
||||
* @brief A set of all entities involved in the scene.
|
||||
*/
|
||||
std::unordered_set<LWOOBJID> entities;
|
||||
|
||||
/**
|
||||
* @brief The scene that is currently being performed.
|
||||
*/
|
||||
class Scene* scene;
|
||||
|
||||
/**
|
||||
* @brief Conclude the play. Accepting/completing missions. This will not remove the entities.
|
||||
*/
|
||||
void Conclude();
|
||||
|
||||
/**
|
||||
* @brief Setup a check for if the audience is still present.
|
||||
*
|
||||
* If the audience is no longer present, the play will be concluded and the entities will be removed.
|
||||
*/
|
||||
void SetupCheckForAudience();
|
||||
|
||||
/**
|
||||
* @brief Clean up the play.
|
||||
*/
|
||||
void CleanUp();
|
||||
|
||||
/**
|
||||
* @brief Setup a barrier. A callback is given when a signal is sent with the given barrier name.
|
||||
*
|
||||
* @param barrier The name of the barrier.
|
||||
* @param callback The callback to call when the barrier is signaled.
|
||||
*/
|
||||
void SetupBarrier(const std::string& barrier, const std::function<void()>& callback);
|
||||
|
||||
/**
|
||||
* @brief Signal a barrier.
|
||||
*
|
||||
* @param barrier The name of the barrier to signal.
|
||||
*/
|
||||
void SignalBarrier(const std::string& barrier);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief Check if the audience is still present.
|
||||
*/
|
||||
void CheckForAudience();
|
||||
|
||||
bool m_CheckForAudience = false;
|
||||
|
||||
bool m_PlayerHasBeenInsideBounds = false;
|
||||
|
||||
std::unordered_map<std::string, std::vector<std::function<void()>>> m_Barriers;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
#include "Prefab.h"
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include "ObjectIDManager.h"
|
||||
#include "EntityManager.h"
|
||||
#include "EntityInfo.h"
|
||||
#include "RenderComponent.h"
|
||||
|
||||
using namespace Cinema;
|
||||
|
||||
struct PrefabInstance
|
||||
{
|
||||
std::vector<LWOOBJID> m_Entities;
|
||||
};
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
|
||||
std::unordered_map<std::string, Prefab> m_Prefabs;
|
||||
std::unordered_map<size_t, PrefabInstance> m_Instances;
|
||||
|
||||
float m_AngleToRadians = 0.0174532925f;
|
||||
}
|
||||
|
||||
size_t Prefab::AddObject(LOT lot, NiPoint3 position, NiQuaternion rotation, float scale) {
|
||||
const auto id = ObjectIDManager::GenerateRandomObjectID();
|
||||
|
||||
m_Pieces.emplace(id, Prefab::Piece {
|
||||
lot,
|
||||
position,
|
||||
rotation,
|
||||
scale,
|
||||
std::vector<int32_t>()
|
||||
});
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
void Prefab::AddEffect(size_t id, int32_t effect) {
|
||||
m_Pieces[id].m_Effects.push_back(effect);
|
||||
}
|
||||
|
||||
void Prefab::RemoveObject(size_t id) {
|
||||
m_Pieces.erase(id);
|
||||
}
|
||||
|
||||
const Prefab& Prefab::LoadFromFile(std::string file) {
|
||||
if (m_Prefabs.find(file) != m_Prefabs.end()) {
|
||||
return m_Prefabs[file];
|
||||
}
|
||||
|
||||
Prefab prefab;
|
||||
|
||||
tinyxml2::XMLDocument doc;
|
||||
doc.LoadFile(file.c_str());
|
||||
|
||||
tinyxml2::XMLElement* root = doc.FirstChildElement("Prefab");
|
||||
if (!root) {
|
||||
LOG("Failed to load prefab from file: %s", file.c_str());
|
||||
|
||||
m_Prefabs.emplace(file, prefab);
|
||||
|
||||
return m_Prefabs[file];
|
||||
}
|
||||
|
||||
for (tinyxml2::XMLElement* element = root->FirstChildElement("Object"); element; element = element->NextSiblingElement("Object")) {
|
||||
const auto lot = element->UnsignedAttribute("lot");
|
||||
const auto position = NiPoint3(element->FloatAttribute("x"), element->FloatAttribute("y"), element->FloatAttribute("z"));
|
||||
|
||||
NiQuaternion rotation;
|
||||
// Check if the qx attribute exists, if so the rotation is a quaternion, otherwise it's a vector
|
||||
if (!element->Attribute("qx")) {
|
||||
rotation = NiQuaternion::FromEulerAngles( {
|
||||
element->FloatAttribute("rx") * m_AngleToRadians,
|
||||
element->FloatAttribute("ry") * m_AngleToRadians,
|
||||
element->FloatAttribute("rz") * m_AngleToRadians
|
||||
} );
|
||||
}
|
||||
else {
|
||||
rotation = NiQuaternion(element->FloatAttribute("qx"), element->FloatAttribute("qy"), element->FloatAttribute("qz"), element->FloatAttribute("qw"));
|
||||
}
|
||||
|
||||
float scale = 1.0f;
|
||||
|
||||
if (element->Attribute("scale")) {
|
||||
scale = element->FloatAttribute("scale");
|
||||
}
|
||||
|
||||
const auto id = prefab.AddObject(lot, position, rotation, scale);
|
||||
|
||||
for (tinyxml2::XMLElement* effect = element->FirstChildElement("Effect"); effect; effect = effect->NextSiblingElement("Effect")) {
|
||||
prefab.AddEffect(id, effect->IntAttribute("id"));
|
||||
}
|
||||
}
|
||||
|
||||
m_Prefabs.emplace(file, prefab);
|
||||
|
||||
return m_Prefabs[file];
|
||||
}
|
||||
|
||||
void Prefab::SaveToFile(std::string file) {
|
||||
tinyxml2::XMLDocument doc;
|
||||
|
||||
tinyxml2::XMLElement* root = doc.NewElement("Prefab");
|
||||
doc.InsertFirstChild(root);
|
||||
|
||||
for (const auto& [id, piece] : m_Pieces) {
|
||||
tinyxml2::XMLElement* object = doc.NewElement("Object");
|
||||
object->SetAttribute("lot", piece.m_Lot);
|
||||
object->SetAttribute("x", piece.m_Position.x);
|
||||
object->SetAttribute("y", piece.m_Position.y);
|
||||
object->SetAttribute("z", piece.m_Position.z);
|
||||
object->SetAttribute("qx", piece.m_Rotation.x);
|
||||
object->SetAttribute("qy", piece.m_Rotation.y);
|
||||
object->SetAttribute("qz", piece.m_Rotation.z);
|
||||
object->SetAttribute("qw", piece.m_Rotation.w);
|
||||
object->SetAttribute("scale", piece.m_Scale);
|
||||
|
||||
for (const auto& effect : piece.m_Effects) {
|
||||
tinyxml2::XMLElement* effectElement = doc.NewElement("Effect");
|
||||
effectElement->SetAttribute("id", effect);
|
||||
object->InsertEndChild(effectElement);
|
||||
}
|
||||
|
||||
root->InsertEndChild(object);
|
||||
}
|
||||
|
||||
doc.SaveFile(file.c_str());
|
||||
}
|
||||
|
||||
size_t Prefab::Instantiate(NiPoint3 position, float scale) const {
|
||||
if (m_Pieces.empty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto id = ObjectIDManager::GenerateRandomObjectID();
|
||||
|
||||
std::vector<LWOOBJID> entities;
|
||||
|
||||
for (const auto& [_, piece] : m_Pieces) {
|
||||
EntityInfo info;
|
||||
info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID();
|
||||
info.lot = piece.m_Lot;
|
||||
info.pos = (piece.m_Position * scale) + position;
|
||||
info.rot = piece.m_Rotation;
|
||||
info.scale = piece.m_Scale * scale;
|
||||
|
||||
const auto entity = Game::entityManager->CreateEntity(info);
|
||||
|
||||
for (const auto& effect : piece.m_Effects) {
|
||||
auto* renderComponent = entity->GetComponent<RenderComponent>();
|
||||
|
||||
if (!renderComponent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate random name
|
||||
std::string effectName = "Effect_";
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
effectName += std::to_string(GeneralUtils::GenerateRandomNumber<size_t>(0, 10));
|
||||
}
|
||||
|
||||
renderComponent->PlayEffect(effect, u"create", effectName);
|
||||
}
|
||||
|
||||
entities.push_back(entity->GetObjectID());
|
||||
|
||||
Game::entityManager->ConstructEntity(entity);
|
||||
}
|
||||
|
||||
m_Instances.emplace(id, PrefabInstance { entities });
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
const std::vector<LWOOBJID>& Cinema::Prefab::GetEntities(size_t instanceID) {
|
||||
return m_Instances[instanceID].m_Entities;
|
||||
}
|
||||
|
||||
void Prefab::DestroyInstance(size_t id) {
|
||||
const auto& instance = m_Instances[id];
|
||||
|
||||
for (const auto& entity : instance.m_Entities) {
|
||||
Game::entityManager->DestroyEntity(entity);
|
||||
}
|
||||
|
||||
m_Instances.erase(id);
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "dCommonVars.h"
|
||||
#include "Entity.h"
|
||||
#include <unordered_map>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Cinema
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A prefab is a collection of objects that can be placed in the world together.
|
||||
*
|
||||
* Can be saved and loaded from XML.
|
||||
*/
|
||||
class Prefab
|
||||
{
|
||||
public:
|
||||
Prefab() = default;
|
||||
|
||||
~Prefab() = default;
|
||||
|
||||
/**
|
||||
* @brief Adds an object to the prefab.
|
||||
*
|
||||
* @param lot The LOT of the object to add.
|
||||
* @param position The position of the object to add.
|
||||
* @param rotation The rotation of the object to add.
|
||||
* @param scale The scale of the object to add.
|
||||
*
|
||||
* @return The ID of the object that was added.
|
||||
*/
|
||||
size_t AddObject(LOT lot, NiPoint3 position, NiQuaternion rotation, float scale = 1.0f);
|
||||
|
||||
/**
|
||||
* @brief Adds an effect to the prefab.
|
||||
*
|
||||
* @param objectID The ID of the object to add the effect to.
|
||||
* @param effectID The ID of the effect to add.
|
||||
*/
|
||||
void AddEffect(size_t objectID, int32_t effectID);
|
||||
|
||||
/**
|
||||
* @brief Removes an object from the prefab.
|
||||
*
|
||||
* @param objectID The ID of the object to remove.
|
||||
*/
|
||||
void RemoveObject(size_t objectID);
|
||||
|
||||
/**
|
||||
* @brief Loads a prefab from the given file.
|
||||
*
|
||||
* @param file The file to load the prefab from.
|
||||
* @return The prefab that was loaded.
|
||||
*/
|
||||
static const Prefab& LoadFromFile(std::string file);
|
||||
|
||||
/**
|
||||
* @brief Saves the prefab to the given file.
|
||||
*
|
||||
* @param file The file to save the prefab to.
|
||||
*/
|
||||
void SaveToFile(std::string file);
|
||||
|
||||
/**
|
||||
* @brief Instantiates the prefab in the world.
|
||||
*
|
||||
* @param position The position to instantiate the prefab at.
|
||||
* @param scale The scale to instantiate the prefab with.
|
||||
*
|
||||
* @return The ID of the instance that was created.
|
||||
*/
|
||||
size_t Instantiate(NiPoint3 position, float scale = 1.0f) const;
|
||||
|
||||
/**
|
||||
* @brief Get the list of entities in the instance with the given ID.
|
||||
*
|
||||
* @param instanceID The ID of the instance to get the entities for.
|
||||
*
|
||||
* @return The list of entities in the instance.
|
||||
*/
|
||||
static const std::vector<LWOOBJID>& GetEntities(size_t instanceID);
|
||||
|
||||
/**
|
||||
* @brief Destroys the instance with the given ID.
|
||||
*
|
||||
* @param instanceID The ID of the instance to destroy.
|
||||
*/
|
||||
static void DestroyInstance(size_t instanceID);
|
||||
|
||||
|
||||
private:
|
||||
struct Piece
|
||||
{
|
||||
LOT m_Lot;
|
||||
NiPoint3 m_Position;
|
||||
NiQuaternion m_Rotation;
|
||||
float m_Scale;
|
||||
std::vector<int32_t> m_Effects;
|
||||
};
|
||||
|
||||
std::unordered_map<size_t, Piece> m_Pieces;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,352 +0,0 @@
|
||||
# Darkflame Cinema
|
||||
A more emersive way to experience story in Darkflame Universe.
|
||||
|
||||
## What is Darkflame Cinema?
|
||||
Darkflame Cinema is a way to create interactive — acted — experiences in Darkflame Universe. It aims to provide a complement to the ordinary mission structure with static NPCs and mission texts to convey story. Insperation is drawn from games likes of World of Warcraft, where NPCs frequently act out scenes, alone and in combination with both the player and other NPCs.
|
||||
|
||||
## Top-level design
|
||||
Cinema works with a few concepts:
|
||||
* **Actors** are the non player characters that act and perform actions.
|
||||
* **Props** are objects in the world which complement the actors.
|
||||
* **Scenes** are a collection of actors and props that perform an interactive play.
|
||||
* **Plays** are scenes which are performed to a player.
|
||||
|
||||
## Play isolation
|
||||
When a play is performed to a player, it is always isolated from the rest of the players in the world. This is to ensure that the player can experience the play without being disturbed by others, and others can't be disturbed by the play. This is achieved by ghosting all NPCs and props in the play to all players except the one experiencing the play.
|
||||
|
||||
<img src="media/isolation.png" width="800">
|
||||
|
||||
## How to create a scene
|
||||
A play is created is a couple of steps:
|
||||
1. Acts out the scene in the world as how the NPCs should reenact it.
|
||||
2. Create prefabs of props in the scene.
|
||||
3. Put the NPCs and props in a scene file.
|
||||
4. Edit the performed acts to add synchronization, conditions and additional actions.
|
||||
5. Setting up the scene to be performed automatically.
|
||||
6. Hiding zone objects while performing.
|
||||
|
||||
### 1. Acts out the scene
|
||||
See <a href="./media/acting.mp4">media/acting.mp4</a> for an example of how to act out a scene.
|
||||
|
||||
To start acting type: `/record-start (clear/copy)` in the chat. This will start recording your actions in the world. The actions recorded include:
|
||||
* Movement
|
||||
* Talking
|
||||
* Equipping and unequipping items
|
||||
* Explicit animations
|
||||
|
||||
By appending `clear` to the command, all gear on the NPC will be removed before reenactment. By appending `copy` to the command, all gear on the NPC will be copied from the player before reenactment. Which, if either, of these options to use depends on which NPC object will be acting. Usually you want to use `copy`. This can be edited manually later.
|
||||
|
||||
This acting is the core of the scene. Later we will learn how to add some interactivity to the scene.
|
||||
|
||||
When you are done acting, type `/record-stop` in the chat. This will stop recording your actions in the world.
|
||||
|
||||
Save your amazing performance by typing `/record-save <file.xml>` in the chat. **Do not forget to do this before starting your next recording!** This will save your performance to a file relative to where the server is running.
|
||||
|
||||
If your scene has multiple NPCs acting, now is the time to switch to the next NPC and repeat the steps above.
|
||||
|
||||
#### 1.1. Previewing your performances
|
||||
See <a href="./media/preview.mp4">media/preview.mp4</a> for an example of how to preview a performance.
|
||||
|
||||
To show a preview of the performance you can type `/record-act (lot) (name)`, this will play the performance. If a lot is specified, the actor will be of that object. If a name is specified, the actor will have that name. If neither is specified, the actor will be a predetermined NPC.
|
||||
|
||||
You may load a performance by typing `/record-load <file.xml>` in the chat. This will load a performance from a file relative to where the server is running. You may than type `/record-act` to show a preview of the performance.
|
||||
|
||||
### 2. Create prefabs of props in the scene
|
||||
Prefabs are a collection of objects that can be instantiated together. This is currently done exclusively by writing xml files.
|
||||
|
||||
Here is an example of a prefab:
|
||||
```xml
|
||||
<Prefab>
|
||||
<!--
|
||||
Lot is the id of the object to spawn.
|
||||
|
||||
Position is defined in either coordinates (x,y,z).
|
||||
These are relative to the position of the prefab.
|
||||
|
||||
Rotation is defined in either euler angles (rx,ry,rz);
|
||||
or in a quaternion (qx,qy,qz,qw).
|
||||
|
||||
Use /pos and /rot in the chat to get your current position and rotation.
|
||||
-->
|
||||
<Object lot="359" x="0" y="0" z="0" rx="0" ry="0" rz="0"/>
|
||||
<Object lot="53" x="0" y="5" z="0" rx="0" rz="0">
|
||||
<Effect id="60073"/> <!-- Custom effect -->
|
||||
</Object>
|
||||
<Object lot="359" x="0" y="5" z="0" rx="0" ry="0" rz="0"/>
|
||||
<Object lot="359" x="0" y="10" z="0" rx="0" ry="0" rz="0"/>
|
||||
<Object lot="53" x="0" y="0" z="0" rx="0" rz="0"/>
|
||||
<Object lot="53" x="0" y="10" z="0" rx="0" rz="0"/>
|
||||
<Object lot="53" x="0" y="15" z="0" rx="0" rz="0"/>
|
||||
</Prefab>
|
||||
```
|
||||
|
||||
This creates the following prefab:
|
||||
|
||||
<img src="media/image.png" width="800">
|
||||
|
||||
You can spawn a prefab by typing `/prefab-spawn <file.xml>`. This will spawn the prefab at your current position.
|
||||
|
||||
### 3. Put the NPCs and props in a scene file
|
||||
A scene file is a collection of actors and props that perform an interactive play. This is currently done exclusively by writing xml files.
|
||||
|
||||
Here is an example of a scene:
|
||||
```xml
|
||||
<!--
|
||||
The scene is defined by a collection of actors and props.
|
||||
These are defined by their name and prefab file.
|
||||
-->
|
||||
<Scene>
|
||||
<!--
|
||||
Props are defined by their file name and a position.
|
||||
|
||||
See media/SFlagSmall.xml for this file.
|
||||
-->
|
||||
<Prefab file="vanity/prefabs/SFlagSmall.xml" x="-655.564" y="1123.76" z="75.993"/>
|
||||
|
||||
<!--
|
||||
Actors are defined by their name and the file which holds the performance.
|
||||
|
||||
See media/my-act.xml and media/my-act-2.xml for these files.
|
||||
-->
|
||||
<NPC name="John" act="vanity/acts/my-act.xml"/>
|
||||
<NPC name="Bill" act="vanity/acts/my-act-2.xml"/>
|
||||
</Scene>
|
||||
```
|
||||
|
||||
See <a href="./media/scene.mp4">media/scene.mp4</a> for how the above scene looks in the world.
|
||||
|
||||
You preview a scene by typing `/scene-act <file.xml>` in the chat. This will act out the scene for you.
|
||||
|
||||
### 4. Edit the performed acts
|
||||
When you preview a newly created scene the NPCs will act out exactly what you did when you recorded the performance. This is not always what you want. You may want to add some interactivity to the scene. This is done by editing the performed acts.
|
||||
|
||||
An act is made out of a collection of records which will be evaluated in order. A record either performs some visual action or provides for flow control.
|
||||
|
||||
Here is an example of a slightly modified act which adds some interactivity:
|
||||
```xml
|
||||
<!--
|
||||
See media/my-act-mod.xml for this file.
|
||||
-->
|
||||
<Recorder>
|
||||
<!-- The following three records were added automaticly because I appended 'copy' to the command when I acted, but these may be modified freely here. -->
|
||||
<ClearEquippedRecord t="0"/>
|
||||
<EquipRecord item="9856" t="0"/>
|
||||
<EquipRecord item="6500" t="0"/>
|
||||
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.39199999"/>
|
||||
|
||||
...
|
||||
|
||||
<!--
|
||||
The following records will assure that before the act continues, the player is within 10 units of the NPC.
|
||||
|
||||
If the player is not within 10 units of them within 5 seconds, the NPC will ask the player to come closer. The NPC will continue to ask the player to come closer every 5 seconds until the player is within 10 units of them.
|
||||
-->
|
||||
|
||||
<!--
|
||||
This is flow-control, the act will jump to the record with the name "check-player". This makes sure that if the player is already close enough, the NPC does not tell them to come closer.
|
||||
-->
|
||||
<JumpRecord label="check-player" t="0" />
|
||||
|
||||
<!--
|
||||
This is a manually added speak record, which will have the NPC speak, telling the player to come closer.
|
||||
-->
|
||||
<SpeakRecord name="join-me" text="Join me!" t="3.0" />
|
||||
|
||||
<!--
|
||||
This is a player proximity record, it can branch two different ways. If the player comes within 10 units of the NPC within 5 seconds, the act will continue to the record. If however, the player is not within 10 units within 5 seconds, the act will jump to the record named "join-me", the record above this one.
|
||||
-->
|
||||
<PlayerProximityRecord name="check-player" distance="10" timeout="5" timeoutLabel="join-me" t="0" />
|
||||
|
||||
<SpeakRecord text="Welcome!" t="4.026"/>
|
||||
|
||||
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="2.046"/>
|
||||
|
||||
...
|
||||
|
||||
<SpeakRecord text="Great!"" t="2.3759999"/>
|
||||
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="3.6960001"/>
|
||||
</Recorder>
|
||||
```
|
||||
|
||||
See <a href="./media/interactive.mp4">media/interactive.mp4</a> for how the above scene looks in the world.
|
||||
|
||||
The list of available records is extensive.
|
||||
|
||||
#### 4.1. Records
|
||||
Each record specifies a time `t`. This is how long after this record is evaluated that the next record will be evaluated.
|
||||
|
||||
A record may also specify a `name`. This is used for flow control. If a record specifies a name, it may be jumped to by for example `JumpRecord` or `ForkRecord`.
|
||||
|
||||
#### 4.1. MovementRecord
|
||||
This record sets the properties of the actors controllable physics component. This is the only record which isn't meant to be edited manually.
|
||||
|
||||
#### 4.2. SpeakRecord
|
||||
This record makes the actor speak.
|
||||
```xml
|
||||
<SpeakRecord text="Hello!" t="3.0" />
|
||||
```
|
||||
|
||||
#### 4.3. EquipRecord
|
||||
This record makes the actor equip an item.
|
||||
```xml
|
||||
<EquipRecord item="9856" t="0"/>
|
||||
```
|
||||
|
||||
#### 4.4. UnequipRecord
|
||||
This record makes the actor unequip an item.
|
||||
```xml
|
||||
<UnequipRecord item="9856" t="0"/>
|
||||
```
|
||||
|
||||
#### 4.5. ClearEquippedRecord
|
||||
This record makes the actor unequip all items.
|
||||
```xml
|
||||
<ClearEquippedRecord t="0"/>
|
||||
```
|
||||
|
||||
#### 4.6. AnimationRecord
|
||||
This record makes the actor play an animation.
|
||||
```xml
|
||||
<AnimationRecord animation="salute" t="2.0"/>
|
||||
```
|
||||
|
||||
#### 4.7. WaitRecord
|
||||
This record makes the actor wait for a specified amount of time.
|
||||
```xml
|
||||
<WaitRecord t="5.5"/>
|
||||
```
|
||||
|
||||
#### 4.8. JumpRecord
|
||||
This record makes the act jump to a record with a specified name.
|
||||
```xml
|
||||
<JumpRecord label="check-player" t="0" />
|
||||
```
|
||||
|
||||
#### 4.9. ForkRecord
|
||||
This record makes a decision based on a condition, and jumps accordingly. The condition may either be a scene variable or a precondtion.
|
||||
```xml
|
||||
<!-- Jump to 'jump-true' if my-variable=1, else jump to 'jump-false'. -->
|
||||
<ForkRecord variable="my-variable" value="1" success="jump-true" failure="jump-false" t="0" />
|
||||
|
||||
<!-- Jump to 'jump-true' if the given precondition holds for the player, else jump to 'jump-false'. -->
|
||||
<ForkRecord precondition="42,99" success="jump-true" failure="jump-false" t="0" />
|
||||
```
|
||||
|
||||
#### 4.10. PlayerProximityRecord
|
||||
This record checks if the player is within a specified distance of the actor. If the player is within the distance, the act will continue to the next record. If the player is not within the distance, the act will jump to a record with a specified name.
|
||||
```xml
|
||||
<!-- Specifying the timeout is optional. If it is not specified, the act will wait forever for the player to come within the distance. -->
|
||||
<PlayerProximityRecord name="check-player" distance="10" timeout="5" timeoutLabel="join-me" t="0" />
|
||||
```
|
||||
|
||||
#### 4.11. SetVariableRecord
|
||||
This record sets a scene variable to a specified value. These are shared between all actors in the scene.
|
||||
```xml
|
||||
<SetVariableRecord variable="my-variable" value="1" t="0" />
|
||||
```
|
||||
|
||||
#### 4.12. BarrierRecord
|
||||
This record makes the act wait until it gets a signal to continue from another act, or until an optional timeout is reached. This is useful for synchronizing multiple actors, for example in a conversation.
|
||||
```xml
|
||||
<!-- Specifying the timeout is optional. If it is not specified, the act will wait forever for the signal. -->
|
||||
<BarrierRecord signal="my-signal" timeout="5" timeoutLabel="do-something-else" t="0" />
|
||||
```
|
||||
|
||||
#### 4.13. SignalRecord
|
||||
This record sends a signal to all acts waiting for it.
|
||||
```xml
|
||||
<SignalRecord signal="my-signal" t="0" />
|
||||
```
|
||||
|
||||
#### 4.14. ConcludeRecord
|
||||
This record concludes the play.
|
||||
```xml
|
||||
<ConcludeRecord t="0" />
|
||||
```
|
||||
|
||||
#### 4.15. VisiblityRecord
|
||||
This record makes the actor visible or invisible.
|
||||
```xml
|
||||
<VisiblityRecord visible="false" t="0" />
|
||||
```
|
||||
|
||||
#### 4.16. PlayEffectRecord
|
||||
This record plays an effect.
|
||||
```xml
|
||||
<PlayEffectRecord effect="5307" t="0" />
|
||||
```
|
||||
|
||||
### 5. Setting up the scene to be performed automatically
|
||||
Scenes can be appended with metadata to describe when they should be performed and what consequences they have. This is done by editing the scene file.
|
||||
|
||||
#### 5.1. Scene metadata
|
||||
There attributes can be added to the `Scene` tag:
|
||||
|
||||
| Attribute | Description |
|
||||
| --- | --- |
|
||||
| `x y z` | The center of where the following two attributes <br> are measured. |
|
||||
| `showingDistance` | The distance at which the scene will <br> be loaded for a player.<br><br> If the player exits this area the scene is unloaded. <br> If the scene has been registered as having been<br>viewed by the player, is is concluded. |
|
||||
| `performingDistance` | The scene is registred as having been <br>viewed by the player. This doesn't mean <br>it can't be viewed again.<br><br>A signal named `"audiance"` will be <br> sent when the player enters this area. <br>This can be used to trigger the main <br>part of the scene. |
|
||||
| `acceptMission` | The mission with the given id will be <br> accepted when the scene is concluded. |
|
||||
| `completeMission` | The mission with the given id will be <br> completed when the scene is concluded. |
|
||||
|
||||
Here is an example of a scene with metadata:
|
||||
```xml
|
||||
<Scene x="-368.272" y="1161.89" z="-5.25745" performingDistance="50" showingDistance="200">
|
||||
...
|
||||
</Scene>
|
||||
```
|
||||
|
||||
#### 5.2. Automatic scene setup
|
||||
In either the worldconfig.ini or sharedconfig.ini file, add the following:
|
||||
```
|
||||
# Path to where scenes are located.
|
||||
scenes_directory=vanity/scenes/
|
||||
```
|
||||
|
||||
Now move the scene into a subdirectory of the scenes directory. The name of the subdirectory should be **the zone id** of the zone the scene is located in.
|
||||
|
||||
For example:
|
||||
```
|
||||
build/
|
||||
├── vanity/
|
||||
│ ├── scenes/
|
||||
│ │ ├── 1900/
|
||||
│ │ │ ├── my-scene.xml
|
||||
```
|
||||
|
||||
Now the scene will be setup automatically and loaded when the player enters the `showingDistance` of the scene.
|
||||
|
||||
#### 5.3. Adding conditions
|
||||
Conditions can be added to the scene to make it only performable when the player fulfills specified preconditions. This is done by editing the scene file.
|
||||
|
||||
Here is an example of a scene with conditions:
|
||||
```xml
|
||||
<Scene x="-368.272" y="1161.89" z="-5.25745" performingDistance="50" showingDistance="200">
|
||||
<Precondition expression="42,99"/> <!-- The player must fulfill preconditions 42 and 99. -->
|
||||
<Precondition expression="666" not="1"/> <!-- The player cannot fulfill precondition 666. -->
|
||||
...
|
||||
</Scene>
|
||||
```
|
||||
|
||||
### 6. Hiding zone objects while performing
|
||||
When a scene should be performed, you might want to hide some objects in the zone. This is done by adding server preconditions. This is a seperate file.
|
||||
|
||||
In either the worldconfig.ini or sharedconfig.ini file, add the following:
|
||||
```
|
||||
# Path to where server preconditions are located.
|
||||
server_preconditions_directory=vanity/server-preconditions.xml
|
||||
```
|
||||
|
||||
Now create the server preconditions file in the directory specified.
|
||||
|
||||
Here is an example of a server preconditions file:
|
||||
```xml
|
||||
<Preconditions>
|
||||
<Entity lot="12261">
|
||||
<Precondition not="1">1006</Precondition>
|
||||
</Entity>
|
||||
</Preconditions>
|
||||
```
|
||||
|
||||
This will hide the objects with lot 12261 for players who fulfill precondition 1006.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,473 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Game.h"
|
||||
#include "EntityManager.h"
|
||||
#include "tinyxml2.h"
|
||||
#include "Play.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace Cinema::Recording
|
||||
{
|
||||
|
||||
class Record
|
||||
{
|
||||
public:
|
||||
virtual void Act(Entity* actor) = 0;
|
||||
|
||||
virtual void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) = 0;
|
||||
|
||||
virtual void Deserialize(tinyxml2::XMLElement* element) = 0;
|
||||
|
||||
float m_Delay;
|
||||
|
||||
std::string m_Name;
|
||||
};
|
||||
|
||||
class Recorder
|
||||
{
|
||||
public:
|
||||
Recorder();
|
||||
|
||||
~Recorder();
|
||||
|
||||
void AddRecord(Record* record);
|
||||
|
||||
void Act(Entity* actor, Play* variables = nullptr);
|
||||
|
||||
Entity* ActFor(Entity* actorTemplate, Entity* player, Play* variables = nullptr);
|
||||
|
||||
void StopActingFor(Entity* actor, Entity* actorTemplate, LWOOBJID playerID);
|
||||
|
||||
bool IsRecording() const;
|
||||
|
||||
void SaveToFile(const std::string& filename);
|
||||
|
||||
float GetDuration() const;
|
||||
|
||||
static Recorder* LoadFromFile(const std::string& filename);
|
||||
|
||||
static void AddRecording(LWOOBJID actorID, Recorder* recorder);
|
||||
|
||||
static void StartRecording(LWOOBJID actorID);
|
||||
|
||||
static void StopRecording(LWOOBJID actorID);
|
||||
|
||||
static Recorder* GetRecorder(LWOOBJID actorID);
|
||||
|
||||
static void RegisterEffectForActor(LWOOBJID actorID, const int32_t& effectId);
|
||||
|
||||
static void LoadRecords(tinyxml2::XMLElement* root, std::vector<Record*>& records);
|
||||
|
||||
static void ActingDispatch(Entity* actor, const std::vector<Record*>& records, size_t index, Play* variables);
|
||||
|
||||
private:
|
||||
static void PlayerProximityDispatch(Entity* actor, const std::vector<Record*>& records, size_t index, Play* variables, std::shared_ptr<bool> actionTaken);
|
||||
|
||||
static void PathFindDispatch(Entity* actor, const std::vector<Record*>& records, size_t index, Play* variables);
|
||||
|
||||
std::vector<Record*> m_Records;
|
||||
|
||||
bool m_IsRecording;
|
||||
|
||||
std::chrono::milliseconds m_StartTime;
|
||||
|
||||
std::chrono::milliseconds m_LastRecordTime;
|
||||
};
|
||||
|
||||
class MovementRecord : public Record
|
||||
{
|
||||
public:
|
||||
NiPoint3 position;
|
||||
NiQuaternion rotation;
|
||||
NiPoint3 velocity;
|
||||
NiPoint3 angularVelocity;
|
||||
bool onGround;
|
||||
bool dirtyVelocity;
|
||||
bool dirtyAngularVelocity;
|
||||
|
||||
MovementRecord() = default;
|
||||
|
||||
MovementRecord(
|
||||
const NiPoint3& position,
|
||||
const NiQuaternion& rotation,
|
||||
const NiPoint3& velocity,
|
||||
const NiPoint3& angularVelocity,
|
||||
bool onGround, bool dirtyVelocity, bool dirtyAngularVelocity
|
||||
);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class SpeakRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::string text;
|
||||
|
||||
SpeakRecord() = default;
|
||||
|
||||
SpeakRecord(const std::string& text);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class AnimationRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::string animation;
|
||||
|
||||
AnimationRecord() = default;
|
||||
|
||||
AnimationRecord(const std::string& animation);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class EquipRecord : public Record
|
||||
{
|
||||
public:
|
||||
LOT item = LOT_NULL;
|
||||
|
||||
EquipRecord() = default;
|
||||
|
||||
EquipRecord(LOT item);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class UnequipRecord : public Record
|
||||
{
|
||||
public:
|
||||
LOT item = LOT_NULL;
|
||||
|
||||
UnequipRecord() = default;
|
||||
|
||||
UnequipRecord(LOT item);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class ClearEquippedRecord : public Record
|
||||
{
|
||||
public:
|
||||
ClearEquippedRecord() = default;
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class WaitRecord : public Record
|
||||
{
|
||||
public:
|
||||
WaitRecord() = default;
|
||||
|
||||
WaitRecord(float delay);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class ForkRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::string variable;
|
||||
std::string value;
|
||||
|
||||
std::string precondition;
|
||||
|
||||
std::string success;
|
||||
std::string failure;
|
||||
|
||||
ForkRecord() = default;
|
||||
|
||||
ForkRecord(const std::string& variable, const std::string& value, const std::string& success, const std::string& failure);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class JumpRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::string label;
|
||||
|
||||
JumpRecord() = default;
|
||||
|
||||
JumpRecord(const std::string& label);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class SetVariableRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::string variable;
|
||||
std::string value;
|
||||
|
||||
SetVariableRecord() = default;
|
||||
|
||||
SetVariableRecord(const std::string& variable, const std::string& value);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class BarrierRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::string signal;
|
||||
|
||||
float timeout = 0.0f;
|
||||
|
||||
std::string timeoutLabel;
|
||||
|
||||
BarrierRecord() = default;
|
||||
|
||||
BarrierRecord(const std::string& signal, float timeout, const std::string& timeoutLabel);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class SignalRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::string signal;
|
||||
|
||||
SignalRecord() = default;
|
||||
|
||||
SignalRecord(const std::string& signal);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class ConcludeRecord : public Record
|
||||
{
|
||||
public:
|
||||
bool cleanUp = false;
|
||||
|
||||
ConcludeRecord() = default;
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class PlayerProximityRecord : public Record
|
||||
{
|
||||
public:
|
||||
float distance = 0.0f;
|
||||
|
||||
float timeout = 0.0f;
|
||||
|
||||
std::string timeoutLabel;
|
||||
|
||||
PlayerProximityRecord() = default;
|
||||
|
||||
PlayerProximityRecord(float distance, float timeout, const std::string& timeoutLabel);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class VisibilityRecord : public Record
|
||||
{
|
||||
public:
|
||||
bool visible = false;
|
||||
|
||||
VisibilityRecord() = default;
|
||||
|
||||
VisibilityRecord(bool visible);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class PlayEffectRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::string effect;
|
||||
|
||||
PlayEffectRecord() = default;
|
||||
|
||||
PlayEffectRecord(const std::string& effect);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class CoroutineRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::vector<Record*> records;
|
||||
|
||||
CoroutineRecord() = default;
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class PathFindRecord : public Record
|
||||
{
|
||||
public:
|
||||
NiPoint3 position;
|
||||
|
||||
float speed = 1.0f;
|
||||
|
||||
PathFindRecord() = default;
|
||||
|
||||
PathFindRecord(const NiPoint3& position, float speed);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class CombatAIRecord : public Record
|
||||
{
|
||||
public:
|
||||
bool enabled = false;
|
||||
|
||||
CombatAIRecord() = default;
|
||||
|
||||
CombatAIRecord(bool enabled);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class CompanionRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::vector<Record*> records;
|
||||
|
||||
CompanionRecord() = default;
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
|
||||
static void SetCompanion(Entity* actor, LWOOBJID player);
|
||||
};
|
||||
|
||||
class SpawnRecord : public Record
|
||||
{
|
||||
public:
|
||||
LOT lot = LOT_NULL;
|
||||
|
||||
NiPoint3 position;
|
||||
|
||||
NiQuaternion rotation;
|
||||
|
||||
std::vector<Record*> onSpawnRecords;
|
||||
|
||||
std::vector<Record*> onDespawnRecords;
|
||||
|
||||
SpawnRecord() = default;
|
||||
|
||||
SpawnRecord(LOT lot, const NiPoint3& position, const NiQuaternion& rotation);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class MissionRecord : public Record
|
||||
{
|
||||
public:
|
||||
int32_t mission;
|
||||
|
||||
MissionRecord() = default;
|
||||
|
||||
MissionRecord(const int32_t& mission);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
class CinematicRecord : public Record
|
||||
{
|
||||
public:
|
||||
std::string cinematic;
|
||||
|
||||
CinematicRecord() = default;
|
||||
|
||||
CinematicRecord(const std::string& cinematic);
|
||||
|
||||
void Act(Entity* actor) override;
|
||||
|
||||
void Serialize(tinyxml2::XMLDocument& document, tinyxml2::XMLElement* parent) override;
|
||||
|
||||
void Deserialize(tinyxml2::XMLElement* element) override;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,742 +0,0 @@
|
||||
#include "Scene.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include "ServerPreconditions.h"
|
||||
#include "EntityManager.h"
|
||||
#include "EntityInfo.h"
|
||||
#include "MissionComponent.h"
|
||||
#include "dConfig.h"
|
||||
#include "PlayerManager.h"
|
||||
#include "SlashCommandHandler.h"
|
||||
#include "ChatPackets.h"
|
||||
#include "InventoryComponent.h"
|
||||
#include "MovementAIComponent.h"
|
||||
#include "Recorder.h"
|
||||
#include "GhostComponent.h"
|
||||
|
||||
using namespace Cinema;
|
||||
|
||||
std::unordered_map<std::string, Scene> Scene::m_Scenes;
|
||||
|
||||
void Cinema::Scene::AddObject(LOT lot, NiPoint3 position, NiQuaternion rotation) {
|
||||
m_Objects.push_back(std::make_pair(lot, std::make_pair(position, rotation)));
|
||||
}
|
||||
|
||||
void Scene::AddPrefab(const Prefab& prefab, NiPoint3 position) {
|
||||
m_Prefabs.push_back(std::make_pair(prefab, position));
|
||||
}
|
||||
|
||||
void Cinema::Scene::AddNPC(LOT npc, const std::string& name, Recording::Recorder* act) {
|
||||
m_NPCs.push_back(std::make_pair(npc, std::make_pair(act, name)));
|
||||
}
|
||||
|
||||
void Cinema::Scene::Rehearse() {
|
||||
CheckForShowings();
|
||||
}
|
||||
|
||||
void Cinema::Scene::Conclude(Entity* player) {
|
||||
if (player == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_Audience.find(player->GetObjectID()) == m_Audience.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* missionComponent = player->GetComponent<MissionComponent>();
|
||||
|
||||
if (missionComponent == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_CompleteMission != 0) {
|
||||
missionComponent->CompleteMission(m_CompleteMission);
|
||||
}
|
||||
|
||||
if (m_AcceptMission != 0) {
|
||||
missionComponent->AcceptMission(m_AcceptMission);
|
||||
}
|
||||
|
||||
// Remove the player from the audience
|
||||
m_Audience.erase(player->GetObjectID());
|
||||
m_HasBeenOutside.erase(player->GetObjectID());
|
||||
|
||||
m_VisitedPlayers.emplace(player->GetObjectID());
|
||||
}
|
||||
|
||||
bool Cinema::Scene::IsPlayerInBounds(Entity* player) const {
|
||||
if (player == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_Bounds == 0.0f) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& position = player->GetPosition();
|
||||
|
||||
auto distance = NiPoint3::Distance(position, m_Center);
|
||||
|
||||
return distance <= m_Bounds;
|
||||
}
|
||||
|
||||
bool Cinema::Scene::IsPlayerInShowingDistance(Entity* player) const {
|
||||
if (player == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_ShowingDistance == 0.0f) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& position = player->GetPosition();
|
||||
|
||||
auto distance = NiPoint3::Distance(position, m_Center);
|
||||
|
||||
return distance <= m_ShowingDistance;
|
||||
}
|
||||
|
||||
bool Cinema::Scene::IsPlayerInMaximumShowingDistance(Entity* player) const {
|
||||
if (player == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_MaximumShowingDistance == 0.0f) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& position = player->GetPosition();
|
||||
|
||||
auto distance = NiPoint3::Distance(position, m_Center);
|
||||
|
||||
return distance <= m_MaximumShowingDistance;
|
||||
}
|
||||
|
||||
void Cinema::Scene::AutoLoadScenesForZone(LWOMAPID zone) {
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "record-act" },
|
||||
.handle = CommandRecordAct,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "record-start" },
|
||||
.handle = CommandRecordStart,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "record-stop" },
|
||||
.handle = CommandRecordStop,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "record-save" },
|
||||
.handle = CommandRecordSave,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "record-load" },
|
||||
.handle = CommandRecordLoad,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "prefab-spawn" },
|
||||
.handle = CommandPrefabSpawn,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "prefab-destroy" },
|
||||
.handle = CommandPrefabDestroy,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "scene-act" },
|
||||
.handle = CommandSceneAct,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "scene-setup" },
|
||||
.handle = CommandSceneSetup,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "companion" },
|
||||
.handle = CommandCompanion,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "cinematic" },
|
||||
.handle = CommandCinematic,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
SlashCommandHandler::RegisterCommand(Command{
|
||||
.help = "",
|
||||
.info = "",
|
||||
.aliases = { "ghost" },
|
||||
.handle = CommandGhostReference,
|
||||
.requiredLevel = eGameMasterLevel::LEAD_MODERATOR
|
||||
});
|
||||
|
||||
const auto& scenesRoot = Game::config->GetValue("scenes_directory");
|
||||
|
||||
if (scenesRoot.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto path = std::filesystem::path(scenesRoot) / std::to_string(zone);
|
||||
|
||||
if (!std::filesystem::exists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Recursively iterate through the directory
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(path)) {
|
||||
if (!entry.is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that extension is .xml
|
||||
if (entry.path().extension() != ".xml") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& file = entry.path().string();
|
||||
|
||||
auto& scene = LoadFromFile(file);
|
||||
|
||||
if (scene.m_ChanceToPlay != 1.0f) {
|
||||
const auto chance = GeneralUtils::GenerateRandomNumber<float>(0.0f, 1.0f);
|
||||
|
||||
if (chance > scene.m_ChanceToPlay) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
scene.Rehearse();
|
||||
}
|
||||
}
|
||||
|
||||
void Cinema::Scene::CheckForShowings() {
|
||||
auto audience = m_Audience;
|
||||
auto hasBeenOutside = m_HasBeenOutside;
|
||||
|
||||
for (const auto& member : audience) {
|
||||
if (Game::entityManager->GetEntity(member) == nullptr) {
|
||||
m_Audience.erase(member);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& member : hasBeenOutside) {
|
||||
if (Game::entityManager->GetEntity(member) == nullptr) {
|
||||
m_HasBeenOutside.erase(member);
|
||||
}
|
||||
}
|
||||
|
||||
m_Audience = audience;
|
||||
m_HasBeenOutside = hasBeenOutside;
|
||||
|
||||
// I don't care
|
||||
Game::entityManager->GetZoneControlEntity()->AddCallbackTimer(1.0f, [this]() {
|
||||
for (auto* player : PlayerManager::GetAllPlayers()) {
|
||||
if (m_Audience.find(player->GetObjectID()) != m_Audience.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_Repeatable && m_VisitedPlayers.find(player->GetObjectID()) != m_VisitedPlayers.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CheckTicket(player);
|
||||
}
|
||||
|
||||
CheckForShowings();
|
||||
});
|
||||
}
|
||||
|
||||
void Cinema::Scene::CheckTicket(Entity* player) {
|
||||
if (m_Audience.find(player->GetObjectID()) != m_Audience.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& [expression, invert] : m_Preconditions) {
|
||||
if (expression.Check(player) == invert) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_ShowingDistance != 0.0f) {
|
||||
if (!IsPlayerInShowingDistance(player)) {
|
||||
m_HasBeenOutside.emplace(player->GetObjectID());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_HasBeenOutside.find(player->GetObjectID()) == m_HasBeenOutside.end()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_Audience.emplace(player->GetObjectID());
|
||||
|
||||
Act(player);
|
||||
}
|
||||
|
||||
Play* Cinema::Scene::Act(Entity* player) {
|
||||
auto* play = new Play();
|
||||
|
||||
if (player != nullptr) {
|
||||
play->player = player->GetObjectID();
|
||||
}
|
||||
|
||||
play->scene = this;
|
||||
|
||||
for (const auto& [lot, transform] : m_Objects) {
|
||||
const auto& [position, rotation] = transform;
|
||||
EntityInfo info;
|
||||
info.pos = position;
|
||||
info.rot = rotation;
|
||||
info.lot = lot;
|
||||
info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID();
|
||||
|
||||
auto* entity = Game::entityManager->CreateEntity(info);
|
||||
|
||||
Game::entityManager->ConstructEntity(entity);
|
||||
|
||||
if (player != nullptr) {
|
||||
ServerPreconditions::AddSoloActor(entity->GetObjectID(), player->GetObjectID());
|
||||
}
|
||||
|
||||
LOG("Spawing object %d", entity->GetObjectID());
|
||||
}
|
||||
|
||||
for (const auto& [prefab, position] : m_Prefabs) {
|
||||
const auto instanceId = prefab.Instantiate(position);
|
||||
|
||||
const auto& entities = prefab.GetEntities(instanceId);
|
||||
|
||||
if (player != nullptr) {
|
||||
for (const auto& entity : entities) {
|
||||
ServerPreconditions::AddSoloActor(entity, player->GetObjectID());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& entity : entities) {
|
||||
play->entities.emplace(entity);
|
||||
}
|
||||
|
||||
LOG("Spawing prefab %d", instanceId);
|
||||
}
|
||||
|
||||
for (const auto& [npc, meta] : m_NPCs) {
|
||||
const auto& [act, name] = meta;
|
||||
EntityInfo info;
|
||||
info.pos = NiPoint3();
|
||||
info.rot = NiQuaternion();
|
||||
info.lot = npc;
|
||||
info.spawnerID = Game::entityManager->GetZoneControlEntity()->GetObjectID();
|
||||
info.settings = {
|
||||
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
|
||||
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
|
||||
};
|
||||
|
||||
auto* entity = Game::entityManager->CreateEntity(info);
|
||||
|
||||
play->entities.emplace(entity->GetObjectID());
|
||||
|
||||
Game::entityManager->ConstructEntity(entity);
|
||||
|
||||
Entity* actor;
|
||||
|
||||
if (player != nullptr) {
|
||||
actor = act->ActFor(entity, player, play);
|
||||
}
|
||||
else
|
||||
{
|
||||
act->Act(entity, play);
|
||||
|
||||
actor = entity;
|
||||
}
|
||||
|
||||
if (actor != nullptr) {
|
||||
actor->SetVar(u"npcName", name);
|
||||
}
|
||||
|
||||
play->entities.emplace(actor->GetObjectID());
|
||||
|
||||
LOG("Spawing npc %d", entity->GetObjectID());
|
||||
}
|
||||
|
||||
if (player != nullptr) {
|
||||
play->SetupCheckForAudience();
|
||||
}
|
||||
|
||||
return play;
|
||||
}
|
||||
|
||||
Scene& Cinema::Scene::LoadFromFile(std::string file) {
|
||||
if (m_Scenes.find(file) != m_Scenes.end()) {
|
||||
return m_Scenes[file];
|
||||
}
|
||||
|
||||
Scene scene;
|
||||
|
||||
tinyxml2::XMLDocument doc;
|
||||
doc.LoadFile(file.c_str());
|
||||
|
||||
tinyxml2::XMLElement* root = doc.FirstChildElement("Scene");
|
||||
if (!root) {
|
||||
LOG("Failed to load scene from file: %s", file.c_str());
|
||||
|
||||
m_Scenes.emplace(file, scene);
|
||||
|
||||
return m_Scenes[file];
|
||||
}
|
||||
|
||||
// Load center and bounds
|
||||
if (root->Attribute("x")) {
|
||||
scene.m_Center = NiPoint3(root->FloatAttribute("x"), root->FloatAttribute("y"), root->FloatAttribute("z"));
|
||||
}
|
||||
|
||||
if (root->Attribute("performingDistance")) {
|
||||
scene.m_Bounds = root->FloatAttribute("performingDistance");
|
||||
}
|
||||
|
||||
if (root->Attribute("showingDistance")) {
|
||||
scene.m_ShowingDistance = root->FloatAttribute("showingDistance");
|
||||
} else {
|
||||
scene.m_ShowingDistance = scene.m_Bounds * 2.0f;
|
||||
}
|
||||
|
||||
if (root->Attribute("maximumShowingDistance")) {
|
||||
scene.m_MaximumShowingDistance = root->FloatAttribute("maximumShowingDistance");
|
||||
} else {
|
||||
scene.m_MaximumShowingDistance = scene.m_ShowingDistance * 2.0f;
|
||||
}
|
||||
|
||||
if (root->Attribute("chanceToPlay")) {
|
||||
scene.m_ChanceToPlay = root->FloatAttribute("chanceToPlay");
|
||||
}
|
||||
|
||||
if (root->Attribute("repeatable")) {
|
||||
scene.m_Repeatable = root->BoolAttribute("repeatable");
|
||||
}
|
||||
|
||||
// Load accept and complete mission
|
||||
if (root->Attribute("acceptMission")) {
|
||||
scene.m_AcceptMission = root->IntAttribute("acceptMission");
|
||||
}
|
||||
|
||||
if (root->Attribute("completeMission")) {
|
||||
scene.m_CompleteMission = root->IntAttribute("completeMission");
|
||||
}
|
||||
|
||||
// Load preconditions
|
||||
for (tinyxml2::XMLElement* element = root->FirstChildElement("Precondition"); element; element = element->NextSiblingElement("Precondition")) {
|
||||
scene.m_Preconditions.push_back(std::make_pair(Preconditions::CreateExpression(element->Attribute("expression")), element->BoolAttribute("not")));
|
||||
}
|
||||
|
||||
for (tinyxml2::XMLElement* element = root->FirstChildElement("Object"); element; element = element->NextSiblingElement("Object")) {
|
||||
const auto lot = element->UnsignedAttribute("lot");
|
||||
const auto position = NiPoint3(element->FloatAttribute("x"), element->FloatAttribute("y"), element->FloatAttribute("z"));
|
||||
const auto rotation = NiQuaternion(element->FloatAttribute("qx"), element->FloatAttribute("qy"), element->FloatAttribute("qz"), element->FloatAttribute("qw"));
|
||||
|
||||
scene.AddObject(lot, position, rotation);
|
||||
}
|
||||
|
||||
for (tinyxml2::XMLElement* element = root->FirstChildElement("Prefab"); element; element = element->NextSiblingElement("Prefab")) {
|
||||
const auto prefab = element->Attribute("file");
|
||||
const auto position = NiPoint3(element->FloatAttribute("x"), element->FloatAttribute("y"), element->FloatAttribute("z"));
|
||||
|
||||
scene.AddPrefab(Prefab::LoadFromFile(prefab), position);
|
||||
}
|
||||
|
||||
for (tinyxml2::XMLElement* element = root->FirstChildElement("NPC"); element; element = element->NextSiblingElement("NPC")) {
|
||||
LOT npc = 2097253;
|
||||
if (element->Attribute("lot")) {
|
||||
npc = element->UnsignedAttribute("lot");
|
||||
}
|
||||
|
||||
std::string name = "";
|
||||
|
||||
if (element->Attribute("name")) {
|
||||
name = element->Attribute("name");
|
||||
}
|
||||
|
||||
const auto act = element->Attribute("act");
|
||||
|
||||
scene.AddNPC(npc, name, Recording::Recorder::LoadFromFile(act));
|
||||
}
|
||||
|
||||
LOG("Loaded scene from file: %s", file.c_str());
|
||||
|
||||
m_Scenes.emplace(file, scene);
|
||||
|
||||
return m_Scenes[file];
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandRecordAct(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.empty()) return;
|
||||
|
||||
EntityInfo info;
|
||||
info.lot = 0;
|
||||
info.pos = entity->GetPosition();
|
||||
info.rot = entity->GetRotation();
|
||||
info.scale = 1;
|
||||
info.spawner = nullptr;
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
info.spawnerNodeID = 0;
|
||||
info.settings = {
|
||||
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
|
||||
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
|
||||
};
|
||||
|
||||
// If there is an argument, set the lot
|
||||
const auto lotOptional = GeneralUtils::TryParse<LOT>(splitArgs[0]);
|
||||
if (lotOptional) {
|
||||
info.lot = lotOptional.value();
|
||||
} else {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn it
|
||||
auto* actor = Game::entityManager->CreateEntity(info);
|
||||
|
||||
// If there is an argument, set the actors name
|
||||
if (args.size() > 1) {
|
||||
actor->SetVar(u"npcName", args[1]);
|
||||
}
|
||||
|
||||
// Construct it
|
||||
Game::entityManager->ConstructEntity(actor);
|
||||
|
||||
auto* record = Cinema::Recording::Recorder::GetRecorder(entity->GetObjectID());
|
||||
|
||||
if (record) {
|
||||
record->Act(actor);
|
||||
} else {
|
||||
LOG("Failed to get recorder for objectID: %llu", entity->GetObjectID());
|
||||
}
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandRecordStart(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
|
||||
Cinema::Recording::Recorder::StartRecording(entity->GetObjectID());
|
||||
|
||||
auto* record = Cinema::Recording::Recorder::GetRecorder(entity->GetObjectID());
|
||||
|
||||
if (record) {
|
||||
if (args.size() > 0 && splitArgs[0] == "clear") {
|
||||
record->AddRecord(new Cinema::Recording::ClearEquippedRecord());
|
||||
}
|
||||
else if (args.size() > 0 && splitArgs[0] == "copy") {
|
||||
record->AddRecord(new Cinema::Recording::ClearEquippedRecord());
|
||||
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
if (inventoryComponent) {
|
||||
for (const auto& [k, v] : inventoryComponent->GetEquippedItems()) {
|
||||
auto* equipmentRecord = new Cinema::Recording::EquipRecord();
|
||||
equipmentRecord->item = v.lot;
|
||||
|
||||
record->AddRecord(equipmentRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG("Failed to get recorder for objectID: %llu", entity->GetObjectID());
|
||||
}
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandRecordStop(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
Cinema::Recording::Recorder::StopRecording(entity->GetObjectID());
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandRecordSave(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.empty()) return;
|
||||
|
||||
auto* record = Cinema::Recording::Recorder::GetRecorder(entity->GetObjectID());
|
||||
|
||||
if (record) {
|
||||
record->SaveToFile(splitArgs[0]);
|
||||
} else {
|
||||
LOG("Failed to get recorder for objectID: %llu", entity->GetObjectID());
|
||||
}
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandRecordLoad(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.empty()) return;
|
||||
|
||||
auto* record = Cinema::Recording::Recorder::LoadFromFile(splitArgs[0]);
|
||||
|
||||
if (record) {
|
||||
Cinema::Recording::Recorder::AddRecording(entity->GetObjectID(), record);
|
||||
} else {
|
||||
LOG("Failed to load recording from file: %s", splitArgs[0].c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandPrefabSpawn(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.empty()) return;
|
||||
|
||||
const auto& prefab = Cinema::Prefab::LoadFromFile(splitArgs[0]);
|
||||
|
||||
float scale = 1.0f;
|
||||
|
||||
if (args.size() >= 2) {
|
||||
const auto scaleOptional = GeneralUtils::TryParse<float>(splitArgs[1]);
|
||||
if (scaleOptional) {
|
||||
scale = scaleOptional.value();
|
||||
}
|
||||
}
|
||||
|
||||
size_t id = prefab.Instantiate(entity->GetPosition(), scale);
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Spawned prefab with ID: " + GeneralUtils::to_u16string(id));
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandPrefabDestroy(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.empty()) return;
|
||||
|
||||
size_t id = GeneralUtils::TryParse<size_t>(splitArgs[0]).value_or(0);
|
||||
|
||||
if (id == 0) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid prefab ID.");
|
||||
return;
|
||||
}
|
||||
|
||||
Cinema::Prefab::DestroyInstance(id);
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Destroyed prefab with ID: " + GeneralUtils::to_u16string(id));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandSceneAct(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.empty()) return;
|
||||
|
||||
auto& scene = Cinema::Scene::LoadFromFile(splitArgs[0]);
|
||||
|
||||
scene.Act(entity);
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandSceneSetup(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.empty()) return;
|
||||
|
||||
auto& scene = Cinema::Scene::LoadFromFile(splitArgs[0]);
|
||||
|
||||
scene.Rehearse();
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandCompanion(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.empty()) return;
|
||||
|
||||
EntityInfo info;
|
||||
info.lot = 0;
|
||||
info.pos = entity->GetPosition();
|
||||
info.rot = entity->GetRotation();
|
||||
info.scale = 1;
|
||||
info.spawner = nullptr;
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
info.spawnerNodeID = 0;
|
||||
info.settings = {
|
||||
new LDFData<std::vector<std::u16string>>(u"syncLDF", { u"custom_script_client" }),
|
||||
new LDFData<std::u16string>(u"custom_script_client", u"scripts\\ai\\SPEC\\MISSION_MINIGAME_CLIENT.lua")
|
||||
};
|
||||
|
||||
// If there is an argument, set the lot
|
||||
const auto lotOptional = GeneralUtils::TryParse<LOT>(splitArgs[0]);
|
||||
if (lotOptional) {
|
||||
info.lot = lotOptional.value();
|
||||
} else {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Invalid lot.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Spawn it
|
||||
auto* actor = Game::entityManager->CreateEntity(info);
|
||||
|
||||
// If there is an argument, set the actors name
|
||||
if (args.size() > 1) {
|
||||
actor->SetVar(u"npcName", args[1]);
|
||||
}
|
||||
|
||||
// Construct it
|
||||
Game::entityManager->ConstructEntity(actor);
|
||||
|
||||
const auto follow = entity->GetObjectID();
|
||||
|
||||
actor->AddCallbackTimer(1.0f, [actor, follow]() {
|
||||
Cinema::Recording::CompanionRecord::SetCompanion(actor, follow);
|
||||
});
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandCinematic(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
|
||||
if (splitArgs.empty()) return;
|
||||
|
||||
const auto path = splitArgs[0];
|
||||
|
||||
GameMessages::SendPlayCinematic(entity->GetObjectID(), GeneralUtils::UTF8ToUTF16(path), sysAddr);
|
||||
}
|
||||
|
||||
void Cinema::Scene::CommandGhostReference(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto* ghostComponent = entity->GetComponent<GhostComponent>();
|
||||
|
||||
if (ghostComponent == nullptr) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Entity does not have a ghost component.");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& ref = ghostComponent->GetGhostReferencePoint();
|
||||
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "<" << ref.x << ", " << ref.y << ", " << ref.z << ">";
|
||||
|
||||
ChatPackets::SendSystemMessage(sysAddr, GeneralUtils::UTF8ToUTF16(ss.str()));
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "Prefab.h"
|
||||
#include "Recorder.h"
|
||||
#include "Preconditions.h"
|
||||
|
||||
namespace Cinema
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A scene is a collection of prefabs, npcs and behaviors that can be loaded into the world.
|
||||
*/
|
||||
class Scene
|
||||
{
|
||||
public:
|
||||
Scene() = default;
|
||||
|
||||
/**
|
||||
* @brief Adds an object to the scene.
|
||||
*
|
||||
* @param lot The LOT of the object to add.
|
||||
* @param position The position of the object to add.
|
||||
* @param rotation The rotation of the object to add.
|
||||
*/
|
||||
void AddObject(LOT lot, NiPoint3 position, NiQuaternion rotation);
|
||||
|
||||
/**
|
||||
* @brief Adds a prefab to the scene.
|
||||
*
|
||||
* @param prefab The prefab to add.
|
||||
* @param position The position to add the prefab at.
|
||||
*/
|
||||
void AddPrefab(const Prefab& prefab, NiPoint3 position);
|
||||
|
||||
/**
|
||||
* @brief Adds an NPC to the scene.
|
||||
*
|
||||
* @param npc The NPC to add.
|
||||
* @param act The act to set for the NPC.
|
||||
*/
|
||||
void AddNPC(LOT npc, const std::string& name, Recording::Recorder* act);
|
||||
|
||||
/**
|
||||
* @brief Set up the scene to be acted when a player enters the theater and meets the preconditions.
|
||||
*/
|
||||
void Rehearse();
|
||||
|
||||
/**
|
||||
* @brief Conclude the scene for a given player.
|
||||
*
|
||||
* @param player The player to conclude the scene for (not nullptr).
|
||||
*/
|
||||
void Conclude(Entity* player);
|
||||
|
||||
/**
|
||||
* @brief Checks if a given player is within the bounds of the scene.
|
||||
*
|
||||
* @param player The player to check.
|
||||
*/
|
||||
bool IsPlayerInBounds(Entity* player) const;
|
||||
|
||||
/**
|
||||
* @brief Checks if a given player is within the showing distance of the scene.
|
||||
*
|
||||
* @param player The player to check.
|
||||
*/
|
||||
bool IsPlayerInShowingDistance(Entity* player) const;
|
||||
|
||||
/**
|
||||
* @brief Checks if a given player is within the maximum showing distance of the scene.
|
||||
*
|
||||
* @param player The player to check.
|
||||
*/
|
||||
bool IsPlayerInMaximumShowingDistance(Entity* player) const;
|
||||
|
||||
/**
|
||||
* @brief Act the scene.
|
||||
*
|
||||
* @param player The player to act the scene for (or nullptr to act for all players).
|
||||
* @return The variables that were set by the scene.
|
||||
*/
|
||||
Play* Act(Entity* player);
|
||||
|
||||
/**
|
||||
* @brief Loads a scene from the given file.
|
||||
*
|
||||
* @param file The file to load the scene from.
|
||||
* @return The scene that was loaded.
|
||||
*/
|
||||
static Scene& LoadFromFile(std::string file);
|
||||
|
||||
/**
|
||||
* @brief Automatically loads the scenes for a given zone.
|
||||
*
|
||||
* @param zone The zone to load the scenes for.
|
||||
*/
|
||||
static void AutoLoadScenesForZone(LWOMAPID zone);
|
||||
|
||||
private:
|
||||
void CheckForShowings();
|
||||
|
||||
void CheckTicket(Entity* player);
|
||||
|
||||
std::vector<std::pair<LOT, std::pair<NiPoint3, NiQuaternion>>> m_Objects;
|
||||
std::vector<std::pair<Prefab, NiPoint3>> m_Prefabs;
|
||||
std::vector<std::pair<LOT, std::pair<Recording::Recorder*, std::string>>> m_NPCs;
|
||||
|
||||
NiPoint3 m_Center;
|
||||
float m_Bounds = 0.0f;
|
||||
float m_ShowingDistance = 0.0f;
|
||||
float m_MaximumShowingDistance = 0.0f;
|
||||
float m_ChanceToPlay = 1.0f;
|
||||
bool m_Repeatable = true;
|
||||
|
||||
std::vector<std::pair<PreconditionExpression, bool>> m_Preconditions;
|
||||
|
||||
int32_t m_AcceptMission = 0;
|
||||
int32_t m_CompleteMission = 0;
|
||||
|
||||
std::unordered_set<LWOOBJID> m_Audience;
|
||||
std::unordered_set<LWOOBJID> m_HasBeenOutside;
|
||||
|
||||
std::unordered_set<LWOOBJID> m_VisitedPlayers;
|
||||
|
||||
static std::unordered_map<std::string, Scene> m_Scenes;
|
||||
|
||||
static void CommandRecordAct(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
static void CommandRecordStart(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
static void CommandRecordStop(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
static void CommandRecordSave(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
static void CommandRecordLoad(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
static void CommandPrefabSpawn(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
static void CommandPrefabDestroy(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
static void CommandSceneAct(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
static void CommandSceneSetup(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
static void CommandCompanion(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
static void CommandCinematic(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
static void CommandGhostReference(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
};
|
||||
|
||||
} // namespace Cinema
|
||||
@@ -1,11 +0,0 @@
|
||||
<Prefab>
|
||||
<Object lot="359" x="0" y="0" z="0" rx="0" ry="0" rz="0"/>
|
||||
<Object lot="53" x="0" y="5" z="0" rx="0" rz="0">
|
||||
<Effect id="60073"/>
|
||||
</Object>
|
||||
<Object lot="359" x="0" y="5" z="0" rx="0" ry="0" rz="0"/>
|
||||
<Object lot="359" x="0" y="10" z="0" rx="0" ry="0" rz="0"/>
|
||||
<Object lot="53" x="0" y="0" z="0" rx="0" rz="0"/>
|
||||
<Object lot="53" x="0" y="10" z="0" rx="0" rz="0"/>
|
||||
<Object lot="53" x="0" y="15" z="0" rx="0" rz="0"/>
|
||||
</Prefab>
|
||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 338 KiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
@@ -1,90 +0,0 @@
|
||||
<Recorder>
|
||||
<MovementRecord x="-666.4151" y="1123.7156" z="-5.6110449" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.028999999"/>
|
||||
<MovementRecord x="-666.3476" y="1123.7156" z="-5.2612491" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.3501134" vy="0.0033733833" vz="6.9959145" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.89099997"/>
|
||||
<MovementRecord x="-666.01447" y="1123.717" z="-3.5337467" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-665.68048" y="1123.7185" z="-1.8018351" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-665.3465" y="1123.72" z="-0.069923788" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-664.98468" y="1123.7216" z="1.8063134" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-664.6507" y="1123.723" z="3.5382249" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-664.28888" y="1123.7246" z="5.4144602" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-663.92706" y="1123.7262" z="7.2906947" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-663.62091" y="1123.7275" z="8.8782806" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
|
||||
<MovementRecord x="-663.25909" y="1123.7291" z="10.754521" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-662.92511" y="1123.7306" z="12.486436" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-662.56329" y="1123.7322" z="14.362677" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-662.20148" y="1123.7338" z="16.238916" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-661.86749" y="1123.7352" z="17.970819" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-661.53351" y="1123.7367" z="19.702723" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-661.19952" y="1123.7382" z="21.434626" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755801" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-660.86554" y="1123.7396" z="23.166529" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-660.50372" y="1123.7412" z="25.042757" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-660.16974" y="1123.7427" z="26.77466" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-659.80792" y="1123.7443" z="28.650888" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.004175582" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-659.50177" y="1123.7456" z="30.238466" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-659.16779" y="1123.7471" z="31.970369" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-658.80597" y="1123.7487" z="33.8466" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-658.44415" y="1123.7502" z="35.722828" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-658.11017" y="1123.7517" z="37.454731" qx="0" qy="0.095176868" qz="0" qw="0.99546039" vx="1.6711727" vy="0.0041755815" vz="8.6595554" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-657.75244" y="1123.7533" z="39.331692" qx="0" qy="0.081039831" qz="0" qw="0.99671084" vx="1.4247334" vy="0.0041755605" vz="8.703496" avx="0" avy="-1.7030833" avz="0" g="true" dv="true" dav="true" t="0.198"/>
|
||||
<MovementRecord x="-657.45453" y="1123.7382" z="41.2188" qx="0" qy="0.078293413" qz="0" qw="0.99693036" vx="1.3767529" vy="0.0041751657" vz="8.711215" avx="0" avy="0.020716017" avz="0" g="true" dv="true" dav="true" t="0.198"/>
|
||||
<MovementRecord x="-657.17914" y="1123.7396" z="42.961048" qx="0" qy="0.078293413" qz="0" qw="0.99693036" vx="1.3767529" vy="0.0041751661" vz="8.711215" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-656.9267" y="1123.741" z="44.558109" qx="0" qy="0.078293413" qz="0" qw="0.99693036" vx="1.3767529" vy="0.0041751661" vz="8.711215" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-656.63043" y="1123.7426" z="46.445869" qx="0" qy="0.075789861" qz="0" qw="0.99712384" vx="1.3329875" vy="0.0041746967" vz="8.7180195" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-656.36383" y="1123.744" z="48.189491" qx="0" qy="0.075789861" qz="0" qw="0.99712384" vx="1.3329875" vy="0.0041746967" vz="8.7180195" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-656.10962" y="1123.7456" z="50.083252" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="1.0742536" vy="0.0041697873" vz="8.7536678" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-655.89502" y="1123.7471" z="51.833969" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="1.0742536" vy="0.0041697873" vz="8.7536678" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-655.66541" y="1123.7485" z="53.707008" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="0.90067899" vy="0.0034960457" vz="7.3392773" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-655.63428" y="1123.7485" z="53.960564" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="0.20638135" vy="0.0008010827" vz="1.6817197" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-655.63373" y="1123.7485" z="53.965019" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.198"/>
|
||||
<SpeakRecord text="Hi!" t="1.584"/>
|
||||
<MovementRecord x="-655.63373" y="1123.7485" z="53.965019" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="4.5869999"/>
|
||||
<AnimationRecord animation="salute" t="4.0939999"/>
|
||||
<MovementRecord x="-655.63373" y="1123.7485" z="53.965019" qx="0" qy="0.061016988" qz="0" qw="0.9981367" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="1.978"/>
|
||||
<MovementRecord x="-655.63373" y="1123.7485" z="53.965019" qx="0" qy="0.0056772209" qz="0" qw="0.99998391" vx="0" vy="0" vz="0" avx="0" avy="-1.02" avz="0" g="true" dv="false" dav="true" t="1.914"/>
|
||||
<MovementRecord x="-655.91522" y="1123.7493" z="55.306641" qx="0" qy="-0.16600978" qz="0" qw="0.9861241" vx="-2.8875616" vy="0.0036441826" vz="8.3332291" avx="0" avy="-2.2004657" avz="0" g="true" dv="true" dav="true" t="0.198"/>
|
||||
<MovementRecord x="-656.8963" y="1123.7493" z="56.298153" qx="0" qy="-0.60278374" qz="0" qw="0.79790461" vx="-8.4835682" vy="0.00041772827" vz="2.4103563" avx="0" avy="-5.2572789" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
|
||||
<MovementRecord x="-658.90973" y="1123.7493" z="56.085861" qx="0" qy="-0.81736016" qz="0" qw="0.57612699" vx="-8.3061113" vy="-0.0020730058" vz="-2.964668" avx="0" avy="-0.59541434" avz="0" g="true" dv="true" dav="true" t="0.198"/>
|
||||
<MovementRecord x="-660.45416" y="1123.7493" z="55.155827" qx="0" qy="0.90611506" qz="0" qw="-0.42303139" vx="-6.7611709" vy="-0.0032021664" vz="-5.6627994" avx="0" avy="-1.0992103" avz="0" g="true" dv="true" dav="true" t="0.198"/>
|
||||
<MovementRecord x="-661.45642" y="1123.7483" z="53.758194" qx="0" qy="0.97830099" qz="0" qw="-0.20718889" vx="-3.5752378" vy="-0.0040555298" vz="-8.0621586" avx="0" avy="-2.5476563" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
|
||||
<MovementRecord x="-661.73975" y="1123.7469" z="51.782887" qx="0" qy="0.99977756" qz="0" qw="-0.021090925" vx="-0.37193322" vy="-0.0041383081" vz="-8.811492" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-661.96057" y="1123.7456" z="50.093998" qx="0" qy="0.99636191" qz="0" qw="-0.085222892" vx="-1.49775" vy="-0.0041759168" vz="-8.6912289" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-662.29297" y="1123.744" z="48.224056" qx="0" qy="0.99613321" qz="0" qw="-0.087855853" vx="-1.5436686" vy="-0.0041759908" vz="-8.6831913" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-662.57861" y="1123.7426" z="46.483566" qx="0" qy="0.99748456" qz="0" qw="-0.070884049" vx="-1.2471558" vy="-0.004173473" vz="-8.7307119" avx="0" avy="-0.30132243" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
|
||||
<MovementRecord x="-662.83026" y="1123.741" z="44.589401" qx="0" qy="0.99784493" qz="0" qw="-0.065616116" vx="-1.1548871" vy="-0.0041717072" vz="-8.7433949" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
|
||||
<MovementRecord x="-663.065" y="1123.7395" z="42.841267" qx="0" qy="0.99736089" qz="0" qw="-0.07260374" vx="-1.2772541" vy="-0.0041739475" vz="-8.7263594" avx="0" avy="0.30980942" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
|
||||
<MovementRecord x="-663.34338" y="1123.7379" z="40.95084" qx="0" qy="0.99732316" qz="0" qw="-0.073120199" vx="-1.286291" vy="-0.0041740802" vz="-8.7250319" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-663.62146" y="1123.7363" z="39.060375" qx="0" qy="0.99733573" qz="0" qw="-0.072948046" vx="-1.2832787" vy="-0.0041740369" vz="-8.7254753" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-663.89001" y="1123.7351" z="37.317089" qx="0" qy="0.99676812" qz="0" qw="-0.080332734" vx="-1.4123834" vy="-0.0036389432" vz="-8.7055092" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-664.20569" y="1123.7341" z="35.432461" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036370354" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-664.52148" y="1123.7332" z="33.547852" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.003687297" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-664.81299" y="1123.7324" z="31.808212" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0037204367" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-665.10449" y="1123.7316" z="30.068573" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036486434" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-665.396" y="1123.7307" z="28.328934" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036817831" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-665.6875" y="1123.7299" z="26.589294" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036099749" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-666.0033" y="1123.729" z="24.704685" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036602514" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-666.31311" y="1123.728" z="22.855684" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036934214" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-666.604" y="1123.7273" z="21.119551" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036277049" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-666.9198" y="1123.7263" z="19.234941" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036779814" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-667.2356" y="1123.7255" z="17.350332" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0037282431" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-667.52557" y="1123.7247" z="15.619577" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036350512" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-667.84137" y="1123.7238" z="13.734968" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036328458" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-668.15717" y="1123.7228" z="11.850359" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036831074" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-668.47296" y="1123.7219" z="9.9657497" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.003733384" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-668.76447" y="1123.7212" z="8.2261105" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036444538" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-669.05597" y="1123.7202" z="6.4864712" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036947303" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-669.37177" y="1123.7194" z="4.601862" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.003744992" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-669.66327" y="1123.7186" z="2.8622227" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036560618" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-669.93048" y="1123.7178" z="1.2675533" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.003689202" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-670.24628" y="1123.7169" z="-0.61705661" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036174082" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-670.56207" y="1123.7159" z="-2.5016661" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036152028" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-670.85358" y="1123.7151" z="-4.2413054" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0037179464" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-671.16937" y="1123.7142" z="-6.1259146" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036986046" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-671.46088" y="1123.7134" z="-7.8655539" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036268109" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-671.77667" y="1123.7124" z="-9.7501631" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036770874" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-672.04388" y="1123.7118" z="-11.344832" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036406391" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-672.35968" y="1123.7108" z="-13.229442" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0036384189" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-672.65118" y="1123.71" z="-14.969081" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-1.4567257" vy="-0.0037411624" vz="-8.6981993" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-672.89941" y="1123.7094" z="-16.451059" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-0.27985972" vy="-0.00032467872" vz="-1.6710598" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-672.90015" y="1123.7094" z="-16.455486" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-3.193491e-08" vy="0.00038383523" vz="-1.7891979e-07" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-672.90015" y="1123.7094" z="-16.455486" qx="0" qy="0.99656022" qz="0" qw="-0.082872093" vx="-3.193491e-08" vy="0.00038383523" vz="-1.7891979e-07" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="4.8179998"/>
|
||||
</Recorder>
|
||||
@@ -1,58 +0,0 @@
|
||||
<Recorder>
|
||||
<ClearEquippedRecord t="0"/>
|
||||
<EquipRecord item="9856" t="0"/>
|
||||
<EquipRecord item="6500" t="0"/>
|
||||
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.39199999"/>
|
||||
|
||||
<JumpRecord label="check-player" t="0" />
|
||||
|
||||
<SpeakRecord name="join-me" text="Join me!" t="3.0" />
|
||||
|
||||
<PlayerProximityRecord name="check-player" distance="10" timeout="5" timeoutLabel="join-me" t="0" />
|
||||
|
||||
<SpeakRecord text="Welcome!" t="4.026"/>
|
||||
|
||||
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="2.046"/>
|
||||
<MovementRecord x="-557.55542" y="1128.7369" z="50.891663" qx="0" qy="0.77823329" qz="0" qw="0.62797529" vx="-1.4221454" vy="3.3353501e-07" vz="0.090151384" avx="0" avy="188.49556" avz="0" g="true" dv="true" dav="true" t="0.46200001"/>
|
||||
<MovementRecord x="-559.65845" y="1128.7369" z="51.024967" qx="0" qy="0.97620082" qz="0" qw="-0.21686859" vx="-16.871138" vy="3.9568704e-06" vz="1.0694356" avx="0" avy="80.788872" avz="0" g="true" dv="true" dav="true" t="0.198"/>
|
||||
<MovementRecord x="-563.03271" y="1128.7369" z="51.238667" qx="0" qy="-0.68442959" qz="0" qw="0.72907895" vx="-16.871298" vy="3.9620759e-06" vz="1.0669085" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-566.12579" y="1128.7369" z="51.43425" qx="0" qy="-0.68442959" qz="0" qw="0.72907895" vx="-16.871298" vy="3.9620759e-06" vz="1.0669085" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-569.76996" y="1128.7369" z="51.779564" qx="0" qy="-0.65352851" qz="0" qw="0.75690192" vx="-16.724352" vy="1.0602414e-06" vz="2.4647665" avx="0" avy="0.29643202" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
|
||||
<MovementRecord x="-573.31696" y="1128.7369" z="52.304493" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-576.93988" y="1128.7369" z="52.840672" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-580.00543" y="1128.7369" z="53.294361" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-583.6225" y="1128.7369" z="53.869492" qx="0" qy="-0.64578879" qz="0" qw="0.76351613" vx="-16.6707" vy="3.4790952e-07" vz="2.8047707" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-587.23431" y="1128.7369" z="54.47718" qx="0" qy="-0.64578879" qz="0" qw="0.76351613" vx="-16.6707" vy="3.4790952e-07" vz="2.8047707" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-590.78857" y="1128.7369" z="55.154781" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-594.10425" y="1128.7369" z="55.814648" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-597.41992" y="1128.7369" z="56.474514" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-600.37299" y="1129.1835" z="57.067936" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.550844" vy="-0.02764282" vz="3.293468" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-603.67401" y="1129.1776" z="57.794476" qx="0" qy="-0.60967135" qz="0" qw="0.79265428" vx="-16.338949" vy="-0.027341407" vz="4.3378429" avx="0" avy="0.68940604" avz="0" g="true" dv="true" dav="true" t="0.264"/>
|
||||
<MovementRecord x="-607.30255" y="1128.5726" z="58.873291" qx="0" qy="-0.58935732" qz="0" qw="0.80787253" vx="-16.122616" vy="-15" vz="5.0832543" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-610.39264" y="1126.5226" z="59.848522" qx="0" qy="-0.58935732" qz="0" qw="0.80787253" vx="-13.727316" vy="-8.8299694" vz="4.4012947" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-612.91327" y="1124.842" z="60.493713" qx="0" qy="-0.64014912" qz="0" qw="0.7682507" vx="-13.939328" vy="-9.2160997" vz="2.5568676" avx="0" avy="-0.90714848" avz="0" g="true" dv="true" dav="true" t="0.495"/>
|
||||
<MovementRecord x="-615.60901" y="1123.9226" z="60.914558" qx="0" qy="-0.65074474" qz="0" qw="0.7592966" vx="-16.705799" vy="0" vz="2.5875225" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
|
||||
<MovementRecord x="-619.20642" y="1123.9226" z="61.475426" qx="0" qy="-0.64724869" qz="0" qw="0.76227891" vx="-16.681313" vy="0" vz="2.7409482" avx="0" avy="0.31377235" avz="0" g="true" dv="true" dav="true" t="0.264"/>
|
||||
<MovementRecord x="-622.5293" y="1123.9226" z="62.100258" qx="0" qy="-0.63752168" qz="0" qw="0.77043241" vx="-16.606367" vy="0" vz="3.163466" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
|
||||
<MovementRecord x="-626.12823" y="1123.7505" z="62.781879" qx="0" qy="-0.64108664" qz="0" qw="0.76746851" vx="-16.634985" vy="4.6775982e-05" vz="3.0093575" avx="0" avy="-0.30132243" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
|
||||
<MovementRecord x="-629.71057" y="1123.7505" z="63.393055" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-16.665606" vy="-4.7832516e-05" vz="2.8348761" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-633.59003" y="1124.8367" z="64.052917" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-16.896" vy="20.297354" vz="2.8736222" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-637.22083" y="1127.7493" z="64.670441" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-19.241386" vy="8.9419651" vz="3.2726972" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-641.23969" y="1128.4006" z="65.354004" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-20.97077" vy="-2.3861933" vz="3.5669582" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-645.92755" y="1128.7273" z="66.15139" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-22.068434" vy="11.44259" vz="3.7537298" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-650.06995" y="1129.8671" z="66.85601" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-22.994522" vy="0.9925909" vz="3.9113069" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-655.15118" y="1128.7443" z="67.720322" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-23.803282" vy="-11.357409" vz="4.0489192" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-659.56635" y="1124.9456" z="68.471329" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-7.8723621" vy="-23.707413" vz="1.3390911" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-660.31464" y="1123.7504" z="68.5979" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-1.6779066" vy="1.007928e-05" vz="0.28236043" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-660.31915" y="1123.7504" z="68.598663" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-660.29572" y="1123.7504" z="68.594681" qx="0" qy="-0.5861969" qz="0" qw="0.81016856" vx="1.4048207" vy="2.2637594e-05" vz="-0.23896487" avx="0" avy="188.49556" avz="0" g="true" dv="true" dav="true" t="0.29699999"/>
|
||||
<MovementRecord x="-658.21826" y="1123.7504" z="68.241364" qx="0" qy="0.26788518" qz="0" qw="0.96345085" vx="16.665703" vy="8.9682879e-05" vz="-2.8343182" avx="0" avy="80.787056" avz="0" g="true" dv="true" dav="true" t="0.198"/>
|
||||
<MovementRecord x="-655.93872" y="1123.7504" z="67.853683" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="4.0222421" vy="4.9844002e-05" vz="-0.6840589" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-655.87488" y="1123.7504" z="67.842827" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="1.2125849" vy="3.9280545e-05" vz="-0.20622317" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-655.87488" y="1123.7504" z="67.842827" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-655.04889" y="1123.7504" z="67.242249" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="4.6101446" vy="-0.0011672372" vz="-3.3521008" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="1.1525371" vy="-0.00029180956" vz="-0.83802569" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.198"/>
|
||||
<SpeakRecord text="Great!"" t="2.3759999"/>
|
||||
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="3.6960001"/>
|
||||
</Recorder>
|
||||
@@ -1,50 +0,0 @@
|
||||
<Recorder>
|
||||
<ClearEquippedRecord t="0"/>
|
||||
<EquipRecord item="9856" t="0"/>
|
||||
<EquipRecord item="6500" t="0"/>
|
||||
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.39199999"/>
|
||||
<SpeakRecord text="Welcome!" t="4.026"/>
|
||||
<MovementRecord x="-557.53174" y="1128.7369" z="50.89016" qx="0" qy="0.72913098" qz="0" qw="0.68437415" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="2.046"/>
|
||||
<MovementRecord x="-557.55542" y="1128.7369" z="50.891663" qx="0" qy="0.77823329" qz="0" qw="0.62797529" vx="-1.4221454" vy="3.3353501e-07" vz="0.090151384" avx="0" avy="188.49556" avz="0" g="true" dv="true" dav="true" t="0.46200001"/>
|
||||
<MovementRecord x="-559.65845" y="1128.7369" z="51.024967" qx="0" qy="0.97620082" qz="0" qw="-0.21686859" vx="-16.871138" vy="3.9568704e-06" vz="1.0694356" avx="0" avy="80.788872" avz="0" g="true" dv="true" dav="true" t="0.198"/>
|
||||
<MovementRecord x="-563.03271" y="1128.7369" z="51.238667" qx="0" qy="-0.68442959" qz="0" qw="0.72907895" vx="-16.871298" vy="3.9620759e-06" vz="1.0669085" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-566.12579" y="1128.7369" z="51.43425" qx="0" qy="-0.68442959" qz="0" qw="0.72907895" vx="-16.871298" vy="3.9620759e-06" vz="1.0669085" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-569.76996" y="1128.7369" z="51.779564" qx="0" qy="-0.65352851" qz="0" qw="0.75690192" vx="-16.724352" vy="1.0602414e-06" vz="2.4647665" avx="0" avy="0.29643202" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
|
||||
<MovementRecord x="-573.31696" y="1128.7369" z="52.304493" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-576.93988" y="1128.7369" z="52.840672" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-580.00543" y="1128.7369" z="53.294361" qx="0" qy="-0.65330213" qz="0" qw="0.7570973" vx="-16.722874" vy="1.0393246e-06" vz="2.4747682" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-583.6225" y="1128.7369" z="53.869492" qx="0" qy="-0.64578879" qz="0" qw="0.76351613" vx="-16.6707" vy="3.4790952e-07" vz="2.8047707" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-587.23431" y="1128.7369" z="54.47718" qx="0" qy="-0.64578879" qz="0" qw="0.76351613" vx="-16.6707" vy="3.4790952e-07" vz="2.8047707" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-590.78857" y="1128.7369" z="55.154781" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-594.10425" y="1128.7369" z="55.814648" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-597.41992" y="1128.7369" z="56.474514" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.579926" vy="-6.9270078e-07" vz="3.2992506" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-600.37299" y="1129.1835" z="57.067936" qx="0" qy="-0.63436413" qz="0" qw="0.77303439" vx="-16.550844" vy="-0.02764282" vz="3.293468" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-603.67401" y="1129.1776" z="57.794476" qx="0" qy="-0.60967135" qz="0" qw="0.79265428" vx="-16.338949" vy="-0.027341407" vz="4.3378429" avx="0" avy="0.68940604" avz="0" g="true" dv="true" dav="true" t="0.264"/>
|
||||
<MovementRecord x="-607.30255" y="1128.5726" z="58.873291" qx="0" qy="-0.58935732" qz="0" qw="0.80787253" vx="-16.122616" vy="-15" vz="5.0832543" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-610.39264" y="1126.5226" z="59.848522" qx="0" qy="-0.58935732" qz="0" qw="0.80787253" vx="-13.727316" vy="-8.8299694" vz="4.4012947" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-612.91327" y="1124.842" z="60.493713" qx="0" qy="-0.64014912" qz="0" qw="0.7682507" vx="-13.939328" vy="-9.2160997" vz="2.5568676" avx="0" avy="-0.90714848" avz="0" g="true" dv="true" dav="true" t="0.495"/>
|
||||
<MovementRecord x="-615.60901" y="1123.9226" z="60.914558" qx="0" qy="-0.65074474" qz="0" qw="0.7592966" vx="-16.705799" vy="0" vz="2.5875225" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
|
||||
<MovementRecord x="-619.20642" y="1123.9226" z="61.475426" qx="0" qy="-0.64724869" qz="0" qw="0.76227891" vx="-16.681313" vy="0" vz="2.7409482" avx="0" avy="0.31377235" avz="0" g="true" dv="true" dav="true" t="0.264"/>
|
||||
<MovementRecord x="-622.5293" y="1123.9226" z="62.100258" qx="0" qy="-0.63752168" qz="0" qw="0.77043241" vx="-16.606367" vy="0" vz="3.163466" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.16500001"/>
|
||||
<MovementRecord x="-626.12823" y="1123.7505" z="62.781879" qx="0" qy="-0.64108664" qz="0" qw="0.76746851" vx="-16.634985" vy="4.6775982e-05" vz="3.0093575" avx="0" avy="-0.30132243" avz="0" g="true" dv="true" dav="true" t="0.23100001"/>
|
||||
<MovementRecord x="-629.71057" y="1123.7505" z="63.393055" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-16.665606" vy="-4.7832516e-05" vz="2.8348761" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-633.59003" y="1124.8367" z="64.052917" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-16.896" vy="20.297354" vz="2.8736222" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-637.22083" y="1127.7493" z="64.670441" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-19.241386" vy="8.9419651" vz="3.2726972" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-641.23969" y="1128.4006" z="65.354004" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-20.97077" vy="-2.3861933" vz="3.5669582" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-645.92755" y="1128.7273" z="66.15139" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-22.068434" vy="11.44259" vz="3.7537298" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-650.06995" y="1129.8671" z="66.85601" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-22.994522" vy="0.9925909" vz="3.9113069" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-655.15118" y="1128.7443" z="67.720322" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-23.803282" vy="-11.357409" vz="4.0489192" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-659.56635" y="1124.9456" z="68.471329" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-7.8723621" vy="-23.707413" vz="1.3390911" avx="0" avy="0" avz="0" g="false" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-660.31464" y="1123.7504" z="68.5979" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="-1.6779066" vy="1.007928e-05" vz="0.28236043" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-660.31915" y="1123.7504" z="68.598663" qx="0" qy="-0.6450991" qz="0" qw="0.76409894" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-660.29572" y="1123.7504" z="68.594681" qx="0" qy="-0.5861969" qz="0" qw="0.81016856" vx="1.4048207" vy="2.2637594e-05" vz="-0.23896487" avx="0" avy="188.49556" avz="0" g="true" dv="true" dav="true" t="0.29699999"/>
|
||||
<MovementRecord x="-658.21826" y="1123.7504" z="68.241364" qx="0" qy="0.26788518" qz="0" qw="0.96345085" vx="16.665703" vy="8.9682879e-05" vz="-2.8343182" avx="0" avy="80.787056" avz="0" g="true" dv="true" dav="true" t="0.198"/>
|
||||
<MovementRecord x="-655.93872" y="1123.7504" z="67.853683" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="4.0222421" vy="4.9844002e-05" vz="-0.6840589" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-655.87488" y="1123.7504" z="67.842827" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="1.2125849" vy="3.9280545e-05" vz="-0.20622317" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-655.87488" y="1123.7504" z="67.842827" qx="0" qy="0.47554708" qz="0" qw="0.87969029" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.23100001"/>
|
||||
<MovementRecord x="-655.04889" y="1123.7504" z="67.242249" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="4.6101446" vy="-0.0011672372" vz="-3.3521008" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="1.1525371" vy="-0.00029180956" vz="-0.83802569" avx="0" avy="0" avz="0" g="true" dv="true" dav="false" t="0.198"/>
|
||||
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="0.198"/>
|
||||
<SpeakRecord text="Great!"" t="2.3759999"/>
|
||||
<MovementRecord x="-654.93365" y="1123.7504" z="67.158447" qx="0" qy="0.85166776" qz="0" qw="0.52408218" vx="0" vy="0" vz="0" avx="0" avy="0" avz="0" g="true" dv="false" dav="false" t="3.6960001"/>
|
||||
</Recorder>
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,5 +0,0 @@
|
||||
<Scene>
|
||||
<Prefab file="vanity/prefabs/SFlagSmall.xml" x="-655.564" y="1123.76" z="75.993"/>
|
||||
<NPC name="John" act="vanity/acts/my-act.xml"/>
|
||||
<NPC name="Bill" act="vanity/acts/my-act-2.xml"/>
|
||||
</Scene>
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "CDActivityRewardsTable.h"
|
||||
#include "CDActivitiesTable.h"
|
||||
#include "LeaderboardManager.h"
|
||||
#include "CharacterComponent.h"
|
||||
|
||||
ActivityComponent::ActivityComponent(Entity* parent, int32_t activityID) : Component(parent) {
|
||||
/*
|
||||
@@ -333,7 +334,7 @@ bool ActivityComponent::IsPlayedBy(LWOOBJID playerID) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ActivityComponent::TakeCost(Entity* player) const {
|
||||
bool ActivityComponent::CheckCost(Entity* player) const {
|
||||
if (m_ActivityInfo.optionalCostLOT <= 0 || m_ActivityInfo.optionalCostCount <= 0)
|
||||
return true;
|
||||
|
||||
@@ -344,11 +345,19 @@ bool ActivityComponent::TakeCost(Entity* player) const {
|
||||
if (inventoryComponent->GetLotCount(m_ActivityInfo.optionalCostLOT) < m_ActivityInfo.optionalCostCount)
|
||||
return false;
|
||||
|
||||
inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ActivityComponent::TakeCost(Entity* player) const{
|
||||
|
||||
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
|
||||
if (CheckCost(player)) {
|
||||
inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount);
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
void ActivityComponent::PlayerReady(Entity* player, bool bReady) {
|
||||
for (Lobby* lobby : m_Queue) {
|
||||
for (LobbyPlayer* lobbyPlayer : lobby->players) {
|
||||
@@ -381,7 +390,7 @@ ActivityInstance* ActivityComponent::NewInstance() {
|
||||
void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const {
|
||||
for (LobbyPlayer* player : lobby) {
|
||||
auto* entity = player->GetEntity();
|
||||
if (entity == nullptr || !TakeCost(entity)) {
|
||||
if (entity == nullptr || !CheckCost(entity)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -526,6 +535,11 @@ void ActivityInstance::StartZone() {
|
||||
|
||||
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", player->GetCharacter()->GetName().c_str(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);
|
||||
if (player->GetCharacter()) {
|
||||
auto* characterComponent = player->GetComponent<CharacterComponent>();
|
||||
if (characterComponent) {
|
||||
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
|
||||
}
|
||||
|
||||
player->GetCharacter()->SetZoneID(zoneID);
|
||||
player->GetCharacter()->SetZoneInstance(zoneInstance);
|
||||
player->GetCharacter()->SetZoneClone(zoneClone);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user