Compare commits

...

67 Commits

Author SHA1 Message Date
David Markowitz
bcbc551ebd fix: use after free in TCPInterface
lol
2025-09-18 22:56:17 -07:00
David Markowitz
68f2e2dee2 Fix FetchContent_Declare speed (#1875) 2025-09-12 03:32:15 -05:00
HailStorm32
b798da8ef8 fix: Update mute expiry from database (#1871)
* Update mute expiry from database

* Address review comments

* Address review comment

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

---------

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>
2025-09-08 23:07:08 -07:00
Copilot
154112050f feat: Implement Minecraft-style execute command with relative positioning (#1864)
* Initial plan

* Implement Minecraft-style execute command

Co-authored-by: aronwk-aaron <26027722+aronwk-aaron@users.noreply.github.com>

* Add relative positioning support to execute command using ~ syntax

Co-authored-by: aronwk-aaron <26027722+aronwk-aaron@users.noreply.github.com>

* update the parsing and fix chat response

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: aronwk-aaron <26027722+aronwk-aaron@users.noreply.github.com>
Co-authored-by: Aaron Kimbrell <aronwk.aaron@gmail.com>
2025-09-08 22:35:18 -07:00
David Markowitz
6d3bf2fdc3 fix: need to create account twice due to commit latency?? (#1873)
idk fixes the issue
2025-09-08 22:50:22 -05:00
David Markowitz
566a18df38 Show git download progress with FetchContent (#1869)
Some downloads are slower and showing progress is better than sitting there doing nothing
2025-09-07 20:03:00 -05:00
David Markowitz
f6c13d9ee6 Replace Quaternion with glm math (#1868) 2025-09-06 19:18:03 -07:00
David Markowitz
8198ad70f6 fix: zero out component in destructor (#1863) 2025-09-01 19:06:00 -05:00
Gie "Max" Vanommeslaeghe
4c3bace601 Merge pull request #1862 from DarkflameUniverse/fix-item-exploits
fix: item exploits
2025-09-01 22:33:07 +02:00
David Markowitz
6d2a21450b fix item exploits
Update VendorComponent.cpp

Update Mail.cpp
2025-09-01 13:17:44 -07:00
David Markowitz
f9e74e6994 Add more logging around rewarding items (#1861) 2025-08-31 20:33:16 -07:00
jadebenn
21a2ddcfd9 Merge pull request #1856 from DarkflameUniverse/revert-uint8_t
Revert uint8_t in game message tests
2025-08-21 08:57:35 -05:00
jadebenn
50e6cf9059 revert ServiceType test change 2025-08-21 08:00:20 -05:00
jadebenn
3364884126 Consolidate serviceID enums into one enum (#1855)
* merge ServerType and ServiceID enums

* rename eConnectionType to ServiceType in preparation for enum unification

* unify ServiceID and ServiceType enums

* shrink ServiceType to an 8-bit integer

* fix linux compilation error and update gamemsg test

* return to uint16_t

* Update dNet/AuthPackets.cpp

Use cast instead of padding

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

* Add default case to MasterServer.cpp

* move ref back to type

* Another formatting fix

* Fix comment to be more accurate

---------

Co-authored-by: jadebenn <9892985+jadebenn@users.noreply.github.com>
Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>
2025-08-20 20:26:48 -07:00
David Markowitz
3890c0a86c fix: clang warnings (#1854) 2025-08-01 13:28:09 -07:00
David Markowitz
c083f21e44 feat: OnAttack behavior (#1853)
Adds the `OnAttack` property behavior starting node.
Tested that having the node allows the model to be attacked to trigger the start of behaviors
2025-08-01 03:09:16 -05:00
HailStorm32
c9e95839ee Update deprecated MYSQL command (#1852) 2025-07-29 09:59:32 -05:00
Terrev
dd957ed0c7 crux prime ninjago ruins ATM (#1851)
i forgor to do this apparently
2025-07-26 20:58:49 -07:00
David Markowitz
12296ce553 feat: activity component debug stuff and fix issues with duplicates in debug ui (#1850)
Tested that duplicate data in ui is no longer hidden and that activity debug stuff is shown
2025-07-24 06:26:51 -05:00
David Markowitz
24f4c9d413 feat: Destroyable component debug info (#1849)
tested that the ui now shows server and client info together if configured to do so
2025-07-23 04:08:39 -05:00
David Markowitz
ba964932b7 feat: debug for all physics components and fix inspect by ldf key (#1848) 2025-07-20 00:08:18 -05:00
David Markowitz
4c42eea819 feat: add despawn command (#1847) 2025-07-19 18:25:14 -05:00
David Markowitz
6b52cf67a0 feat: debug features and implement ObjectDebugger (#1846)
Move the -s and base features of inspect to the object debugger (this file is present in an unmodified, live client)
2025-07-19 05:11:32 -05:00
David Markowitz
71f708f1b5 fix: Let's not ghost zone control (#1845)
* Let's not ghost zone control

* remove zonecontrol stuff
2025-07-18 10:15:45 -07:00
David Markowitz
49aa632d42 feat: WaypointReached notification for MovementAI and don't move the AI when its not built (#1844) 2025-07-12 22:21:45 -07:00
David Markowitz
5ec4142ca1 fix: multiple collection tasks in one mission and remove sanity check on mission rewards (#1843) 2025-07-12 21:04:41 -05:00
David Markowitz
5e9fe40bec feat: Add GetComponents(Mut) functions to Entity (#1842)
Allows for a really neat way of getting components using structured binding.  Tested that powerups still function

do it again because its neat
2025-07-01 07:26:05 -05:00
David Markowitz
9524198044 Update DEVGMCommands.cpp (#1840) 2025-06-29 17:23:11 -04:00
David Markowitz
a5d0788488 feat: barfight (#1839) 2025-06-29 17:18:59 -04:00
David Markowitz
a1ba5b8f12 feat: remove instance pointer management by migrating to unique_ptr (#1838)
Tested that i can join clones, zones and private instances and that the expected zones are loaded into
2025-06-29 05:41:03 -04:00
David Markowitz
48510b7315 feat: add messaging system for manager and add example usage to Loot (#1837)
tested that loot still drops at the entities position
2025-06-29 03:22:41 -04:00
David Markowitz
c697f8ad97 feat: Add Restart Behavior (#1836)
Resets the model to the default state at the end of the models frame.  Will see if in the future designers want this to be more strict on the resetting timing.
2025-06-29 03:22:20 -04:00
David Markowitz
55d181ea4b fix: lxfml normalization (#1835) 2025-06-27 22:31:48 -07:00
David Markowitz
ecbb465020 fix: get entity from manager vs from character (#1833)
fixes a possible crash due to null entity
2025-06-26 07:04:05 -04:00
David Markowitz
ec9927acbb fix: multiplied speeds with Speed Behaviors (#1832) 2025-06-26 07:03:25 -04:00
David Markowitz
1f580491c7 feat: add speed behavior (#1831) 2025-06-25 05:04:25 -04:00
David Markowitz
2618e9a864 fix: specifiy width of integer being written (#1830) 2025-06-25 00:58:11 -04:00
David Markowitz
0f0d0a6dee optimizations (#1829) 2025-06-24 22:13:48 -05:00
David Markowitz
f63a9a6bea fix: don't construct zone control twice on player loadin (#1828)
checked that the logs no longer have an error about zone control mis matched pointers

Update EntityManager.cpp
2025-06-24 22:03:13 -05:00
David Markowitz
f0f98a6108 fix: consuming items not decrementing mission progress (#1827)
tested that consuming water no longer leaves a mission unable to be completed
2025-06-24 22:01:59 -05:00
David Markowitz
4ed7bd6767 fix: some mail features (#1826) 2025-06-23 23:58:55 -04:00
David Markowitz
9f92f48a0f fix: models with multiple parts not being normalized properly (#1825)
Tested that models are migrated to the new format a-ok
Tested that the new logic works as expected.
Old code needs to be kept so that models in both states can be brought to modern standards
2025-06-23 03:08:16 -04:00
David Markowitz
48e3471831 fix: imaginite not being taken when starting shooting gallery (#1823) 2025-06-23 03:07:52 -04:00
David Markowitz
3c244cce27 fix: large inventories and inspect not printing objectID (#1824) 2025-06-23 03:07:34 -04:00
David Markowitz
8ba35be64d feat: add saving behaviors to the inventory (#1822)
* change behavior id to LWOOBJID

Convert behavior ID to LWOOBJID length

missed header

fix sqlite field names

sqlite brother

* feat: add saving behaviors to the inventory

consolidate copied code

consolidate copied code

Update ModelComponent.cpp

remove ability to save loot behaviors
2025-06-22 20:45:49 -05:00
David Markowitz
f7c9267ba4 change behavior id to LWOOBJID (#1821)
Convert behavior ID to LWOOBJID length

missed header

fix sqlite field names

sqlite brother
2025-06-22 15:05:09 -05:00
David Markowitz
b6e9d6872d fix: not checking OnChat block node (#1820) 2025-06-19 18:39:36 -07:00
David Markowitz
c83797984a check for null on property management instance (#1819) 2025-06-18 00:02:53 -05:00
David Markowitz
04487efa25 feat: add chat behaviors (#1818)
* Move in all directions is functional

* feat: add movement behaviors

the following behaviors will function
MoveRight
MoveLeft
FlyUp
FlyDown
MoveForward
MoveBackward

The behavior of the behaviors is once a move in an axis is active, that behavior must finish its movement before another one on that axis can do another movement on it.

* feat: add chat behaviors

Tested that models can correctly send chat messages, silently and publically.  Tested as well that the filter is used by the client for behaviors and added a security check to not broadcast messages that fail the check if words are removed.
2025-06-17 17:34:52 -05:00
David Markowitz
2f315d9288 feat: Movement behaviors (#1815)
* Move in all directions is functional

* feat: add movement behaviors

the following behaviors will function
MoveRight
MoveLeft
FlyUp
FlyDown
MoveForward
MoveBackward

The behavior of the behaviors is once a move in an axis is active, that behavior must finish its movement before another one on that axis can do another movement on it.
2025-06-11 12:52:15 -07:00
David Markowitz
6ae1c7a376 Add null check for character (#1814) 2025-06-10 12:41:08 -05:00
David Markowitz
c19ee04c8a fix: property behavior crashes (#1813) 2025-06-08 21:41:43 -05:00
David Markowitz
2858345269 fix: destroy enemies on entering build mode (#1812) 2025-06-08 21:41:19 -05:00
David Markowitz
37e14979a4 fix: winter race orbs (#1810)
Tested that script is loaded
2025-06-08 14:14:35 -05:00
David Markowitz
b509fd4f10 fix: Constructing player to themself (#1808)
tested that I can see other players leave and join a world and that i no longer see a white screen when loading between worlds
2025-06-07 18:30:22 -05:00
David Markowitz
820c0f0083 fix: ghost mis-matched pointer causing objects to not destruct (#1797) 2025-06-05 16:07:07 -05:00
David Markowitz
68eb20966f fix: Remove hard coded pet flags (#1805)
Tested that taming the cat pet (3054) sets flag 807
2025-06-04 23:07:29 -07:00
David Markowitz
92155a3cb4 fix: invert instructions so people read the important ones first (#1802)
* fix: invert instructions so people read the important ones first

* more emphasis
2025-05-30 08:56:58 -05:00
David Markowitz
437362cce6 Add extra checkbox to bug report field (#1803) 2025-05-30 08:56:47 -05:00
ElectScholar
34665f6f5c Fix: Double imaginite issue resolved on mini survival games (#1801)
* Add checkcost as replacement for just inventory checks

* Create headers for cost methods

* clean comments
2025-05-22 20:42:39 -07:00
David Markowitz
32487dcd5f fix: reverse bounce paths (#1798) 2025-05-18 19:48:40 -07:00
ElectScholar
891b176b4f fix: playing an emote not showing on all clients (#1800)
* Fix emote broadcast failure with adding new GameMsg

* Remove PlayAnimation ()function in place of EmotePlayed()

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>

* Change int casting methodology to explicit int32_t for consistency

* Set default behavior for EmotePlayed struct

This is to avoid undefined behavior when using method

---------

Co-authored-by: David Markowitz <39972741+EmosewaMC@users.noreply.github.com>
2025-05-16 21:50:40 -07:00
David Markowitz
e42df5b02e feat: Add implementation for visited levels (#1795)
* feat: Add implementation for visited levels

* update to working code
2025-05-14 22:49:35 -05:00
61921cfb62 feat: refactor web server to be generic and add websockets framework (#1786)
* Break out changes into a smaller subset

* NL@EOF

* fix windows bs
add player ws updates
add websocket docs

* tested everything to make sure it works

* Address Feedback
2025-05-14 22:38:38 -05:00
David Markowitz
91f6b2bf81 fix: weekly leaderboards and shooting gallery high score (#1719)
* fix them again

* name

* Update GameMessages.cpp

* Update SGCannon.cpp

* Use chrono library instead
2025-05-14 03:58:16 -05:00
David Markowitz
01917841cb Invert frame rates (use inactive if 0 players in world, not the other way around) (#1796) 2025-05-14 03:57:29 -05:00
David Markowitz
e18c504ee4 feat: auto reject empty properties (#1794)
Tested that having the config option set to 1 and having an empty property auto-rejected it.  Tested that having a model on the property or having the new config option set to 0 auto approved the property (as per live)
2025-05-07 23:15:10 -05:00
258 changed files with 3912 additions and 1880 deletions

View File

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

View File

@@ -19,6 +19,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Export the compile commands for debuggi
set(CMAKE_POLICY_DEFAULT_CMP0063 NEW) # Set CMAKE visibility policy to NEW on project and subprojects
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) # Set C and C++ symbol visibility to hide inlined functions
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
set(FETCHCONTENT_QUIET FALSE) # GLM takes a long time to clone, this will at least show _something_ while its downloading
# Read variables from file
FILE(READ "${CMAKE_SOURCE_DIR}/CMakeVariables.txt" variables)
@@ -235,6 +236,8 @@ include_directories(
"dNet"
"dWeb"
"tests"
"tests/dCommonTests"
"tests/dGameTests"
@@ -301,9 +304,10 @@ 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")
set(COMMON_LIBRARIES glm::glm "dCommon" "dDatabase" "dNet" "raknet" "magic_enum")
# Add platform specific common libraries
if(UNIX)

View File

@@ -187,7 +187,8 @@ Now that you are logged in, run the following commands.
```bash
# Creates a user for this computer which uses a password and grant said user all privileges.
# Change mydarkflameuser to a custom username and password to a custom password.
GRANT ALL ON *.* TO 'mydarkflameuser'@'localhost' IDENTIFIED BY 'password' WITH GRANT OPTION;
CREATE USER 'mydarkflameuser'@'localhost' IDENTIFIED BY 'password';
GRANT ALL ON *.* TO 'mydarkflameuser'@'localhost' WITH GRANT OPTION;
FLUSH PRIVILEGES;
# Then create a database for Darkflame Universe to use.
@@ -324,13 +325,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

View File

@@ -6,6 +6,8 @@ FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.12.1
GIT_PROGRESS TRUE
GIT_SHALLOW 1
)
# For Windows: Prevent overriding the parent project's compiler/linker settings

View File

@@ -20,7 +20,7 @@
//Auth includes:
#include "AuthPackets.h"
#include "eConnectionType.h"
#include "ServiceType.h"
#include "MessageType/Server.h"
#include "MessageType/Auth.h"
@@ -92,7 +92,7 @@ int main(int argc, char** argv) {
const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::lastSignal, masterPassword);
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServiceType::AUTH, Game::config, &Game::lastSignal, masterPassword);
//Run it until server gets a kill message from Master:
auto t = std::chrono::high_resolution_clock::now();
@@ -167,11 +167,11 @@ void HandlePacket(Packet* packet) {
if (packet->length < 4) return;
if (packet->data[0] == ID_USER_PACKET_ENUM) {
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::SERVER) {
if (static_cast<ServiceType>(packet->data[1]) == ServiceType::COMMON) {
if (static_cast<MessageType::Server>(packet->data[3]) == MessageType::Server::VERSION_CONFIRM) {
AuthPackets::HandleHandshake(Game::server, packet);
}
} else if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::AUTH) {
} else if (static_cast<ServiceType>(packet->data[1]) == ServiceType::AUTH) {
if (static_cast<MessageType::Auth>(packet->data[3]) == MessageType::Auth::LOGIN_REQUEST) {
AuthPackets::HandleLoginRequest(Game::server, packet);
}

View File

@@ -1,4 +1,4 @@
set(DCHATFILTER_SOURCES "dChatFilter.cpp")
add_library(dChatFilter STATIC ${DCHATFILTER_SOURCES})
target_link_libraries(dChatFilter dDatabase)
target_link_libraries(dChatFilter dDatabase glm::glm)

View File

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

View File

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

View File

@@ -1,19 +1,19 @@
set(DCHATSERVER_SOURCES
"ChatIgnoreList.cpp"
"ChatPacketHandler.cpp"
"ChatWebAPI.cpp"
"JSONUtils.cpp"
"ChatJSONUtils.cpp"
"ChatWeb.cpp"
"PlayerContainer.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(dChatServer ${COMMON_LIBRARIES} dChatFilter glm::glm)
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer mongoose dWeb)

View File

@@ -13,11 +13,11 @@
// 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 MessageType::Client type) {
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receivingPlayer);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, type);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, type);
}
void ChatIgnoreList::GetIgnoreList(Packet* packet) {

View File

@@ -1,4 +1,4 @@
#include "JSONUtils.h"
#include "ChatJSONUtils.h"
#include "json.hpp"
@@ -47,16 +47,3 @@ void TeamContainer::to_json(json& data, const TeamContainer::Data& teamContainer
data.push_back(*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);
}
}
return check["error"].empty() ? "" : check.dump();
}

View File

@@ -1,5 +1,5 @@
#ifndef __JSONUTILS_H__
#define __JSONUTILS_H__
#ifndef __CHATJSONUTILS_H__
#define __CHATJSONUTILS_H__
#include "json_fwd.hpp"
#include "PlayerContainer.h"
@@ -15,9 +15,4 @@ namespace TeamContainer {
void to_json(nlohmann::json& data, const TeamContainer::Data& teamData);
};
namespace JSONUtils {
// check required data for reqeust
std::string CheckRequiredData(const nlohmann::json& data, const std::vector<std::string>& requiredData);
}
#endif // __JSONUTILS_H__
#endif // !__CHATJSONUTILS_H__

View File

@@ -12,7 +12,7 @@
#include "RakString.h"
#include "dConfig.h"
#include "eObjectBits.h"
#include "eConnectionType.h"
#include "ServiceType.h"
#include "MessageType/Chat.h"
#include "MessageType/Client.h"
#include "MessageType/Game.h"
@@ -61,11 +61,11 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//Now, we need to send the friendlist to the server they came from:
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::GET_FRIENDS_LIST_RESPONSE);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::GET_FRIENDS_LIST_RESPONSE);
bitStream.Write<uint8_t>(0);
bitStream.Write<uint16_t>(1); //Length of packet -- just writing one as it doesn't matter, client skips it.
bitStream.Write<uint16_t>(player.friends.size());
@@ -375,10 +375,10 @@ void ChatPacketHandler::HandleWho(Packet* packet) {
bool online = player;
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(request.requestor);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::WHO_RESPONSE);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::WHO_RESPONSE);
bitStream.Write<uint8_t>(online);
bitStream.Write(player.zoneID.GetMapID());
bitStream.Write(player.zoneID.GetInstanceID());
@@ -398,10 +398,10 @@ void ChatPacketHandler::HandleShowAll(Packet* packet) {
if (!sender) return;
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(request.requestor);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::SHOW_ALL_RESPONSE);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::SHOW_ALL_RESPONSE);
bitStream.Write<uint8_t>(!request.displayZoneData && !request.displayIndividualPlayers);
bitStream.Write(Game::playerContainer.GetPlayerCount());
bitStream.Write(Game::playerContainer.GetSimCount());
@@ -533,7 +533,7 @@ void ChatPacketHandler::OnAchievementNotify(RakNet::BitStream& bitstream, const
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);
BitStreamUtils::WriteHeader(worldStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
worldStream.Write(friendData.playerID);
notify.WriteHeader(worldStream);
notify.Serialize(worldStream);
@@ -544,10 +544,10 @@ void ChatPacketHandler::OnAchievementNotify(RakNet::BitStream& bitstream, const
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);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(routeTo.playerID);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::PRIVATE_CHAT_MESSAGE);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::PRIVATE_CHAT_MESSAGE);
bitStream.Write(sender.playerID);
bitStream.Write(channel);
bitStream.Write<uint32_t>(0); // not used
@@ -579,11 +579,11 @@ void ChatPacketHandler::SendFriendUpdate(const PlayerData& friendData, const Pla
[bool] - is FTP*/
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(friendData.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::UPDATE_FRIEND_NOTIFY);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::UPDATE_FRIEND_NOTIFY);
bitStream.Write<uint8_t>(notifyType);
std::string playerName = playerData.playerName.c_str();
@@ -616,11 +616,11 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
}
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_REQUEST);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::ADD_FRIEND_REQUEST);
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.
@@ -630,11 +630,11 @@ void ChatPacketHandler::SendFriendRequest(const PlayerData& receiver, const Play
void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const PlayerData& sender, eAddFriendResponseType responseCode, uint8_t isBestFriendsAlready, uint8_t isBestFriendRequest) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
// Portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::ADD_FRIEND_RESPONSE);
BitStreamUtils::WriteHeader(bitStream, ServiceType::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.worldServerSysAddr != UNASSIGNED_SYSTEM_ADDRESS);
@@ -653,11 +653,11 @@ void ChatPacketHandler::SendFriendResponse(const PlayerData& receiver, const Pla
void ChatPacketHandler::SendRemoveFriend(const PlayerData& receiver, std::string& personToRemove, bool isSuccessful) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::REMOVE_FRIEND_RESPONSE);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::REMOVE_FRIEND_RESPONSE);
bitStream.Write<uint8_t>(isSuccessful); //isOnline
bitStream.Write(LUWString(personToRemove));

View File

@@ -13,7 +13,7 @@
#include "Diagnostics.h"
#include "AssetManager.h"
#include "BinaryPathFinder.h"
#include "eConnectionType.h"
#include "ServiceType.h"
#include "PlayerContainer.h"
#include "ChatPacketHandler.h"
#include "MessageType/Chat.h"
@@ -29,7 +29,7 @@
#include "RakNetDefines.h"
#include "MessageIdentifiers.h"
#include "ChatWebAPI.h"
#include "ChatWeb.h"
namespace Game {
Logger* logger = nullptr;
@@ -93,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;
@@ -122,7 +123,7 @@ int main(int argc, char** argv) {
const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString;
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::lastSignal, masterPassword);
Game::server = new dServer(ourIP, ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServiceType::CHAT, Game::config, &Game::lastSignal, masterPassword);
const bool dontGenerateDCF = GeneralUtils::TryParse<bool>(Game::config->GetValue("dont_generate_dcf")).value_or(false);
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", dontGenerateDCF);
@@ -167,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) {
@@ -219,11 +218,11 @@ void HandlePacket(Packet* packet) {
CINSTREAM;
inStream.SetReadOffset(BYTES_TO_BITS(1));
eConnectionType connection;
MessageType::Chat chatMessageID;
ServiceType connection;
inStream.Read(connection);
if (connection != eConnectionType::CHAT) return;
if (connection != ServiceType::CHAT) return;
MessageType::Chat chatMessageID;
inStream.Read(chatMessageID);
// Our packing byte wasnt there? Probably a false packet

133
dChatServer/ChatWeb.cpp Normal file
View 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
View 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__

View File

@@ -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 = TeamContainer::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

View File

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

View File

@@ -8,10 +8,11 @@
#include "GeneralUtils.h"
#include "BitStreamUtils.h"
#include "Database.h"
#include "eConnectionType.h"
#include "ServiceType.h"
#include "ChatPackets.h"
#include "dConfig.h"
#include "MessageType/Chat.h"
#include "ChatWeb.h"
#include "TeamContainer.h"
void PlayerContainer::Initialize() {
@@ -59,8 +60,9 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
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);
}
@@ -114,6 +116,8 @@ void PlayerContainer::RemovePlayer(const LWOOBJID playerID) {
}
}
ChatWeb::SendWSPlayerUpdate(player, eActivityType::PlayerLoggedOut);
m_PlayerCount--;
LOG("Removed user: %llu", playerID);
m_Players.erase(playerID);
@@ -143,7 +147,7 @@ void PlayerContainer::MuteUpdate(Packet* packet) {
void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::GM_MUTE);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::GM_MUTE);
bitStream.Write(player);
bitStream.Write(time);

View File

@@ -264,11 +264,11 @@ void TeamContainer::HandleTeamStatusRequest(Packet* packet) {
void TeamContainer::SendTeamInvite(const PlayerData& receiver, const PlayerData& sender) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::TEAM_INVITE);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::TEAM_INVITE);
bitStream.Write(LUWString(sender.playerName.c_str()));
bitStream.Write(sender.playerID);
@@ -279,7 +279,7 @@ void TeamContainer::SendTeamInvite(const PlayerData& receiver, const PlayerData&
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);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -306,7 +306,7 @@ void TeamContainer::SendTeamInviteConfirm(const PlayerData& receiver, bool bLead
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);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -331,7 +331,7 @@ void TeamContainer::SendTeamStatus(const PlayerData& receiver, LWOOBJID i64Leade
void TeamContainer::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64PlayerID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -348,7 +348,7 @@ void TeamContainer::SendTeamSetLeader(const PlayerData& receiver, LWOOBJID i64Pl
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);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -377,7 +377,7 @@ void TeamContainer::SendTeamAddPlayer(const PlayerData& receiver, bool bIsFreeTr
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);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -403,7 +403,7 @@ void TeamContainer::SendTeamRemovePlayer(const PlayerData& receiver, bool bDisba
void TeamContainer::SendTeamSetOffWorldFlag(const PlayerData& receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::WORLD_ROUTE_PACKET);
bitStream.Write(receiver.playerID);
//portion that will get routed:
@@ -652,7 +652,7 @@ void TeamContainer::TeamStatusUpdate(TeamData* team) {
void TeamContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::TEAM_GET_STATUS);
bitStream.Write(team->teamID);
bitStream.Write(deleteTeam);

View File

@@ -16,9 +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.
@@ -52,6 +54,8 @@ elseif (WIN32)
zlib
URL https://github.com/madler/zlib/archive/refs/tags/v1.2.11.zip
URL_HASH MD5=9d6a627693163bbbf3f26403a3a0b0b1
GIT_PROGRESS TRUE
GIT_SHALLOW 1
)
# Disable warning about no project version.
@@ -72,5 +76,6 @@ else ()
endif ()
target_link_libraries(dCommon
PUBLIC glm::glm
PRIVATE ZLIB::ZLIB bcrypt tinyxml2
INTERFACE dDatabase)

View File

@@ -4,7 +4,7 @@
#include <assert.h>
#ifdef _DEBUG
# define DluAssert(expression) do { assert(expression) } while(0)
# define DluAssert(expression) do { assert(expression); } while(0)
#else
# define DluAssert(expression)
#endif

View File

@@ -3,6 +3,7 @@
// C++
#include <charconv>
#include <cstdint>
#include <cmath>
#include <ctime>
#include <functional>
#include <optional>
@@ -19,6 +20,8 @@
#include "Game.h"
#include "Logger.h"
#include <glm/ext/vector_float3.hpp>
enum eInventoryType : uint32_t;
enum class eObjectBits : size_t;
enum class eReplicaComponentType : uint32_t;
@@ -145,7 +148,7 @@ namespace GeneralUtils {
template <typename... Bases>
struct overload : Bases... {
using is_transparent = void;
using Bases::operator() ... ;
using Bases::operator() ...;
};
struct char_pointer_hash {
@@ -202,7 +205,7 @@ namespace GeneralUtils {
}
template<typename T>
requires(!Numeric<T>)
requires(!Numeric<T>)
[[nodiscard]] std::optional<T> TryParse(std::string_view str);
#if !(__GNUC__ >= 11 || _MSC_VER >= 1924)
@@ -221,7 +224,7 @@ namespace GeneralUtils {
*/
template <std::floating_point T>
[[nodiscard]] std::optional<T> TryParse(std::string_view str) noexcept
try {
try {
while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1);
size_t parseNum;
@@ -243,7 +246,7 @@ namespace GeneralUtils {
* @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters
*/
template <typename T>
[[nodiscard]] std::optional<NiPoint3> TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ) {
[[nodiscard]] std::optional<T> TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ) {
const auto x = TryParse<float>(strX);
if (!x) return std::nullopt;
@@ -251,7 +254,7 @@ namespace GeneralUtils {
if (!y) return std::nullopt;
const auto z = TryParse<float>(strZ);
return z ? std::make_optional<NiPoint3>(x.value(), y.value(), z.value()) : std::nullopt;
return z ? std::make_optional<T>(x.value(), y.value(), z.value()) : std::nullopt;
}
/**
@@ -260,8 +263,8 @@ namespace GeneralUtils {
* @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters
*/
template <typename T>
[[nodiscard]] std::optional<NiPoint3> TryParse(const std::span<const std::string> str) {
return (str.size() == 3) ? TryParse<NiPoint3>(str[0], str[1], str[2]) : std::nullopt;
[[nodiscard]] std::optional<T> TryParse(const std::span<const std::string> str) {
return (str.size() == 3) ? TryParse<T>(str[0], str[1], str[2]) : std::nullopt;
}
template <typename T>
@@ -299,6 +302,12 @@ namespace GeneralUtils {
return T();
}
template<typename Container>
inline Container::value_type GetRandomElement(const Container& container) {
DluAssert(!container.empty());
return container[GenerateRandomNumber<typename Container::value_type>(0, container.size() - 1)];
}
/**
* Casts the value of an enum entry to its underlying type
* @param entry Enum entry to cast
@@ -323,4 +332,28 @@ namespace GeneralUtils {
return GenerateRandomNumber<T>(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
}
// https://www.quora.com/How-do-you-round-to-specific-increments-like-0-5-in-C
// Rounds to the nearest floating point value specified.
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
T RountToNearestEven(const T value, const T modulus) {
const auto modulo = std::fmod(value, modulus);
const auto abs_modulo_2 = std::abs(modulo * 2);
const auto abs_modulus = std::abs(modulus);
bool round_away_from_zero = false;
if (abs_modulo_2 > abs_modulus) {
round_away_from_zero = true;
} else if (abs_modulo_2 == abs_modulus) {
const auto trunc_quot = std::floor(std::abs(value / modulus));
const auto odd = std::fmod(trunc_quot, T{ 2 }) != 0;
round_away_from_zero = odd;
}
if (round_away_from_zero) {
return value + (std::copysign(modulus, value) - modulo);
} else {
return value - modulo;
}
}
}

17
dCommon/JSONUtils.cpp Normal file
View 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
View 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_

View File

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

View File

@@ -6,7 +6,7 @@
#include <ranges>
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoint3& curPosition) {
Result toReturn;
tinyxml2::XMLDocument doc;
const auto err = doc.Parse(data.data());
@@ -27,7 +27,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
// First get all the positions of bricks
for (const auto& brick : lxfml["Bricks"]) {
const auto* part = brick.FirstChildElement("Part");
if (part) {
while (part) {
const auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
@@ -36,6 +36,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
if (refID) transformations[refID] = transformation;
}
}
part = part->NextSiblingElement("Part");
}
}
@@ -43,29 +44,42 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
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;
NiPoint3 delta = NiPoint3Constant::ZERO;
if (curPosition == NiPoint3Constant::ZERO) {
// 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 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;
delta = (highest - lowest) / 2.0f;
} else {
lowest = curPosition;
highest = curPosition;
delta = NiPoint3Constant::ZERO;
}
auto delta = (highest - lowest) / 2.0f;
auto newRootPos = lowest + delta;
// Need to snap this chosen position to the nearest valid spot
// on the LEGO grid
newRootPos.x = GeneralUtils::RountToNearestEven(newRootPos.x, 0.8f);
newRootPos.z = GeneralUtils::RountToNearestEven(newRootPos.z, 0.8f);
// Clamp the Y to the lowest point on the model
newRootPos.y = lowest.y;
@@ -77,9 +91,9 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
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;
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x + curPosition.x;
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y + curPosition.y;
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z + curPosition.z;
std::stringstream stream;
for (int i = 0; i < 9; i++) {
stream << split[i];
@@ -92,7 +106,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
// Finally write the new transformation back into the lxfml
for (auto& brick : lxfml["Bricks"]) {
auto* part = brick.FirstChildElement("Part");
if (part) {
while (part) {
auto* bone = part->FirstChildElement("Bone");
if (bone) {
auto* transformation = bone->Attribute("transformation");
@@ -103,6 +117,7 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data) {
}
}
}
part = part->NextSiblingElement("Part");
}
}

View File

@@ -17,7 +17,11 @@ namespace Lxfml {
// 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);
[[nodiscard]] Result NormalizePosition(const std::string_view data, const NiPoint3& curPosition = NiPoint3Constant::ZERO);
// 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
View 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;
}

View File

@@ -6,10 +6,14 @@
\brief Defines a point in space in XYZ coordinates
*/
class NiPoint3;
class NiQuaternion;
typedef NiPoint3 Vector3; //!< The Vector3 class is technically the NiPoint3 class, but typedef'd for clarity in some cases
#include <glm/ext/vector_float3.hpp>
#include "NiQuaternion.h"
//! A custom class the defines a point in space
class NiPoint3 {
public:
@@ -21,6 +25,12 @@ public:
//! Initializer
constexpr NiPoint3() = default;
constexpr NiPoint3(const glm::vec3& vec) noexcept
: x{ vec.x }
, y{ vec.y }
, z{ vec.z } {
}
//! Initializer
/*!
\param x The x coordinate

View File

@@ -4,6 +4,7 @@
#endif
#include "NiQuaternion.h"
#include <glm/ext/quaternion_float.hpp>
// MARK: Getters / Setters

View File

@@ -3,37 +3,18 @@
// C++
#include <cmath>
#include <glm/gtx/quaternion.hpp>
// MARK: Member Functions
Vector3 NiQuaternion::GetEulerAngles() const {
Vector3 angles;
// roll (x-axis rotation)
const float sinr_cosp = 2 * (w * x + y * z);
const float cosr_cosp = 1 - 2 * (x * x + y * y);
angles.x = std::atan2(sinr_cosp, cosr_cosp);
// pitch (y-axis rotation)
const float sinp = 2 * (w * y - z * x);
if (std::abs(sinp) >= 1) {
angles.y = std::copysign(3.14 / 2, sinp); // use 90 degrees if out of range
} else {
angles.y = std::asin(sinp);
}
// yaw (z-axis rotation)
const float siny_cosp = 2 * (w * z + x * y);
const float cosy_cosp = 1 - 2 * (y * y + z * z);
angles.z = std::atan2(siny_cosp, cosy_cosp);
return angles;
Vector3 QuatUtils::Euler(const NiQuaternion& quat) {
return glm::eulerAngles(quat);
}
// MARK: Helper Functions
//! Look from a specific point in space to another point in space (Y-locked)
NiQuaternion NiQuaternion::LookAt(const NiPoint3& sourcePoint, const NiPoint3& destPoint) {
NiQuaternion QuatUtils::LookAt(const NiPoint3& sourcePoint, const NiPoint3& destPoint) {
//To make sure we don't orient around the X/Z axis:
NiPoint3 source = sourcePoint;
NiPoint3 dest = destPoint;
@@ -51,11 +32,11 @@ NiQuaternion NiQuaternion::LookAt(const NiPoint3& sourcePoint, const NiPoint3& d
NiPoint3 vecB = vecA.CrossProduct(posZ);
if (vecB.DotProduct(forwardVector) < 0) rotAngle = -rotAngle;
return NiQuaternion::CreateFromAxisAngle(vecA, rotAngle);
return glm::angleAxis(rotAngle, glm::vec3{vecA.x, vecA.y, vecA.z});
}
//! Look from a specific point in space to another point in space
NiQuaternion NiQuaternion::LookAtUnlocked(const NiPoint3& sourcePoint, const NiPoint3& destPoint) {
NiQuaternion QuatUtils::LookAtUnlocked(const NiPoint3& sourcePoint, const NiPoint3& destPoint) {
NiPoint3 forwardVector = NiPoint3(destPoint - sourcePoint).Unitize();
NiPoint3 posZ = NiPoint3Constant::UNIT_Z;
@@ -67,37 +48,26 @@ NiQuaternion NiQuaternion::LookAtUnlocked(const NiPoint3& sourcePoint, const NiP
NiPoint3 vecB = vecA.CrossProduct(posZ);
if (vecB.DotProduct(forwardVector) < 0) rotAngle = -rotAngle;
return NiQuaternion::CreateFromAxisAngle(vecA, rotAngle);
return glm::angleAxis(rotAngle, glm::vec3{vecA.x, vecA.y, vecA.z});
}
//! Creates a Quaternion from a specific axis and angle relative to that axis
NiQuaternion NiQuaternion::CreateFromAxisAngle(const Vector3& axis, float angle) {
float halfAngle = angle * 0.5f;
float s = static_cast<float>(sin(halfAngle));
NiQuaternion q;
q.x = axis.GetX() * s;
q.y = axis.GetY() * s;
q.z = axis.GetZ() * s;
q.w = static_cast<float>(cos(halfAngle));
return q;
NiQuaternion QuatUtils::AxisAngle(const Vector3& axis, float angle) {
return glm::angleAxis(angle, glm::vec3(axis.x, axis.y, axis.z));
}
NiQuaternion NiQuaternion::FromEulerAngles(const NiPoint3& eulerAngles) {
// Abbreviations for the various angular functions
float cy = cos(eulerAngles.z * 0.5);
float sy = sin(eulerAngles.z * 0.5);
float cp = cos(eulerAngles.y * 0.5);
float sp = sin(eulerAngles.y * 0.5);
float cr = cos(eulerAngles.x * 0.5);
float sr = sin(eulerAngles.x * 0.5);
NiQuaternion q;
q.w = cr * cp * cy + sr * sp * sy;
q.x = sr * cp * cy - cr * sp * sy;
q.y = cr * sp * cy + sr * cp * sy;
q.z = cr * cp * sy - sr * sp * cy;
return q;
NiQuaternion QuatUtils::FromEuler(const NiPoint3& eulerAngles) {
return glm::quat(glm::vec3(eulerAngles.x, eulerAngles.y, eulerAngles.z));
}
Vector3 QuatUtils::Forward(const NiQuaternion& quat) {
return quat * glm::vec3(0, 0, 1);
}
Vector3 QuatUtils::Up(const NiQuaternion& quat) {
return quat * glm::vec3(0, 1, 0);
}
Vector3 QuatUtils::Right(const NiQuaternion& quat) {
return quat * glm::vec3(1, 0, 0);
}

View File

@@ -1,158 +1,27 @@
#ifndef __NIQUATERNION_H__
#define __NIQUATERNION_H__
#ifndef NIQUATERNION_H
#define NIQUATERNION_H
// Custom Classes
#include "NiPoint3.h"
/*!
\file NiQuaternion.hpp
\brief Defines a quaternion in space in WXYZ coordinates
*/
#define GLM_FORCE_QUAT_DATA_WXYZ
class NiQuaternion;
typedef NiQuaternion Quaternion; //!< A typedef for a shorthand version of NiQuaternion
#include <glm/ext/quaternion_float.hpp>
//! A class that defines a rotation in space
class NiQuaternion {
public:
float w{ 1 }; //!< The w coordinate
float x{ 0 }; //!< The x coordinate
float y{ 0 }; //!< The y coordinate
float z{ 0 }; //!< The z coordinate
using Quaternion = glm::quat;
using NiQuaternion = Quaternion;
//! The initializer
constexpr NiQuaternion() = default;
//! The initializer
/*!
\param w The w coordinate
\param x The x coordinate
\param y The y coordinate
\param z The z coordinate
*/
constexpr NiQuaternion(const float w, const float x, const float y, const float z) noexcept
: w{ w }
, x{ x }
, y{ y }
, z{ z } {
}
// MARK: Setters / Getters
//! Gets the W coordinate
/*!
\return The w coordinate
*/
[[nodiscard]] constexpr float GetW() const noexcept;
//! Sets the W coordinate
/*!
\param w The w coordinate
*/
constexpr void SetW(const float w) noexcept;
//! Gets the X coordinate
/*!
\return The x coordinate
*/
[[nodiscard]] constexpr float GetX() const noexcept;
//! Sets the X coordinate
/*!
\param x The x coordinate
*/
constexpr void SetX(const float x) noexcept;
//! Gets the Y coordinate
/*!
\return The y coordinate
*/
[[nodiscard]] constexpr float GetY() const noexcept;
//! Sets the Y coordinate
/*!
\param y The y coordinate
*/
constexpr void SetY(const float y) noexcept;
//! Gets the Z coordinate
/*!
\return The z coordinate
*/
[[nodiscard]] constexpr float GetZ() const noexcept;
//! Sets the Z coordinate
/*!
\param z The z coordinate
*/
constexpr void SetZ(const float z) noexcept;
// MARK: Member Functions
//! Returns the forward vector from the quaternion
/*!
\return The forward vector of the quaternion
*/
[[nodiscard]] constexpr Vector3 GetForwardVector() const noexcept;
//! Returns the up vector from the quaternion
/*!
\return The up vector fo the quaternion
*/
[[nodiscard]] constexpr Vector3 GetUpVector() const noexcept;
//! Returns the right vector from the quaternion
/*!
\return The right vector of the quaternion
*/
[[nodiscard]] constexpr Vector3 GetRightVector() const noexcept;
[[nodiscard]] Vector3 GetEulerAngles() const;
// MARK: Operators
//! Operator to check for equality
constexpr bool operator==(const NiQuaternion& rot) const noexcept;
//! Operator to check for inequality
constexpr bool operator!=(const NiQuaternion& rot) const noexcept;
// MARK: Helper Functions
//! Look from a specific point in space to another point in space (Y-locked)
/*!
\param sourcePoint The source location
\param destPoint The destination location
\return The Quaternion with the rotation towards the destination
*/
[[nodiscard]] static NiQuaternion LookAt(const NiPoint3& sourcePoint, const NiPoint3& destPoint);
//! Look from a specific point in space to another point in space
/*!
\param sourcePoint The source location
\param destPoint The destination location
\return The Quaternion with the rotation towards the destination
*/
[[nodiscard]] static NiQuaternion LookAtUnlocked(const NiPoint3& sourcePoint, const NiPoint3& destPoint);
//! Creates a Quaternion from a specific axis and angle relative to that axis
/*!
\param axis The axis that is used
\param angle The angle relative to this axis
\return A quaternion created from the axis and angle
*/
[[nodiscard]] static NiQuaternion CreateFromAxisAngle(const Vector3& axis, float angle);
[[nodiscard]] static NiQuaternion FromEulerAngles(const NiPoint3& eulerAngles);
namespace QuatUtils {
constexpr NiQuaternion IDENTITY = glm::identity<NiQuaternion>();
Vector3 Forward(const NiQuaternion& quat);
Vector3 Up(const NiQuaternion& quat);
Vector3 Right(const NiQuaternion& quat);
NiQuaternion LookAt(const NiPoint3& from, const NiPoint3& to);
NiQuaternion LookAtUnlocked(const NiPoint3& from, const NiPoint3& to);
Vector3 Euler(const NiQuaternion& quat);
NiQuaternion AxisAngle(const Vector3& axis, float angle);
NiQuaternion FromEuler(const NiPoint3& eulerAngles);
constexpr float PI_OVER_180 = glm::pi<float>() / 180.0f;
};
// Static Variables
namespace NiQuaternionConstant {
constexpr NiQuaternion IDENTITY(1, 0, 0, 0);
}
// Include constexpr and inline function definitions in a seperate file for readability
#include "NiQuaternion.inl"
#endif // !__NIQUATERNION_H__
#endif // !NIQUATERNION_H

View File

@@ -1,75 +0,0 @@
#pragma once
#ifndef __NIQUATERNION_H__
#error "This should only be included inline in NiQuaternion.h: Do not include directly!"
#endif
// MARK: Setters / Getters
//! Gets the W coordinate
constexpr float NiQuaternion::GetW() const noexcept {
return this->w;
}
//! Sets the W coordinate
constexpr void NiQuaternion::SetW(const float w) noexcept {
this->w = w;
}
//! Gets the X coordinate
constexpr float NiQuaternion::GetX() const noexcept {
return this->x;
}
//! Sets the X coordinate
constexpr void NiQuaternion::SetX(const float x) noexcept {
this->x = x;
}
//! Gets the Y coordinate
constexpr float NiQuaternion::GetY() const noexcept {
return this->y;
}
//! Sets the Y coordinate
constexpr void NiQuaternion::SetY(const float y) noexcept {
this->y = y;
}
//! Gets the Z coordinate
constexpr float NiQuaternion::GetZ() const noexcept {
return this->z;
}
//! Sets the Z coordinate
constexpr void NiQuaternion::SetZ(const float z) noexcept {
this->z = z;
}
// MARK: Member Functions
//! Returns the forward vector from the quaternion
constexpr Vector3 NiQuaternion::GetForwardVector() const noexcept {
return Vector3(2 * (x * z + w * y), 2 * (y * z - w * x), 1 - 2 * (x * x + y * y));
}
//! Returns the up vector from the quaternion
constexpr Vector3 NiQuaternion::GetUpVector() const noexcept {
return Vector3(2 * (x * y - w * z), 1 - 2 * (x * x + z * z), 2 * (y * z + w * x));
}
//! Returns the right vector from the quaternion
constexpr Vector3 NiQuaternion::GetRightVector() const noexcept {
return Vector3(1 - 2 * (y * y + z * z), 2 * (x * y + w * z), 2 * (x * z - w * y));
}
// MARK: Operators
//! Operator to check for equality
constexpr bool NiQuaternion::operator==(const NiQuaternion& rot) const noexcept {
return rot.x == this->x && rot.y == this->y && rot.z == this->z && rot.w == this->w;
}
//! Operator to check for inequality
constexpr bool NiQuaternion::operator!=(const NiQuaternion& rot) const noexcept {
return !(*this == rot);
}

View File

@@ -24,7 +24,7 @@ struct LocalSpaceInfo {
struct PositionUpdate {
NiPoint3 position = NiPoint3Constant::ZERO;
NiQuaternion rotation = NiQuaternionConstant::IDENTITY;
NiQuaternion rotation = QuatUtils::IDENTITY;
bool onGround = false;
bool onRail = false;
NiPoint3 velocity = NiPoint3Constant::ZERO;

View File

@@ -0,0 +1,14 @@
#ifndef __SERVICETYPE__H__
#define __SERVICETYPE__H__
enum class ServiceType : uint16_t {
COMMON = 0,
AUTH,
CHAT,
WORLD = 4,
CLIENT,
MASTER,
UNKNOWN
};
#endif //!__SERVICETYPE__H__

View File

@@ -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 "ServiceType.h"
#pragma warning (disable:4251) //Disables SQL warnings
@@ -33,7 +34,7 @@ constexpr uint32_t lowFrameDelta = FRAMES_TO_MS(lowFramerate);
#define CBITSTREAM RakNet::BitStream bitStream;
#define CINSTREAM RakNet::BitStream inStream(packet->data, packet->length, false);
#define CINSTREAM_SKIP_HEADER CINSTREAM if (inStream.GetNumberOfUnreadBits() >= BYTES_TO_BITS(HEADER_SIZE)) inStream.IgnoreBytes(HEADER_SIZE); else inStream.IgnoreBits(inStream.GetNumberOfUnreadBits());
#define CMSGHEADER BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::GAME_MSG);
#define CMSGHEADER BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::GAME_MSG);
#define SEND_PACKET Game::server->Send(bitStream, sysAddr, false);
#define SEND_PACKET_BROADCAST Game::server->Send(bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);
@@ -99,6 +100,7 @@ public:
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...

View File

@@ -1,14 +0,0 @@
#ifndef __ECONNECTIONTYPE__H__
#define __ECONNECTIONTYPE__H__
enum class eConnectionType : uint16_t {
SERVER = 0,
AUTH,
CHAT,
WORLD = 4,
CLIENT,
MASTER,
UNKNOWN
};
#endif //!__ECONNECTIONTYPE__H__

View File

@@ -1,6 +1,8 @@
#ifndef __EHTTPMETHODS__H__
#define __EHTTPMETHODS__H__
#include "dPlatforms.h"
#ifdef DARKFLAME_PLATFORM_WIN32
#pragma push_macro("DELETE")
#undef DELETE

View File

@@ -15,7 +15,7 @@ target_include_directories(dDatabaseCDClient PUBLIC "."
"${PROJECT_SOURCE_DIR}/dCommon"
"${PROJECT_SOURCE_DIR}/dCommon/dEnums"
)
target_link_libraries(dDatabaseCDClient PRIVATE sqlite3)
target_link_libraries(dDatabaseCDClient PRIVATE sqlite3 glm::glm)
if (${CDCLIENT_CACHE_ALL})
add_compile_definitions(dDatabaseCDClient PRIVATE CDCLIENT_CACHE_ALL=${CDCLIENT_CACHE_ALL})

View File

@@ -10,4 +10,5 @@ add_dependencies(dDatabase conncpp_dylib)
target_include_directories(dDatabase PUBLIC ".")
target_link_libraries(dDatabase
PUBLIC dDatabaseCDClient dDatabaseGame)
PUBLIC dDatabaseCDClient dDatabaseGame
PRIVATE glm::glm)

View File

@@ -29,7 +29,7 @@ target_include_directories(dDatabaseGame PUBLIC "."
target_link_libraries(dDatabaseGame
INTERFACE dCommon
PRIVATE sqlite3 MariaDB::ConnCpp)
PRIVATE sqlite3 MariaDB::ConnCpp glm::glm)
# Glob together all headers that need to be precompiled
file(

View File

@@ -14,6 +14,7 @@ public:
std::string bcryptPassword;
uint32_t id{};
uint32_t playKeyId{};
uint64_t muteExpire{};
bool banned{};
bool locked{};
eGameMasterLevel maxGmLevel{};

View File

@@ -8,6 +8,7 @@
enum class eActivityType : uint32_t {
PlayerLoggedIn,
PlayerLoggedOut,
PlayerChangedZone
};
class IActivityLog {

View File

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

View File

@@ -13,11 +13,11 @@ public:
}
NiPoint3 position;
NiQuaternion rotation;
NiQuaternion rotation = QuatUtils::IDENTITY;
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.
@@ -34,9 +34,9 @@ public:
virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0;
// Update the model position and rotation for the given property id.
virtual void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) = 0;
virtual void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<int32_t, 5> behaviorIDs) {
std::array<std::pair<int32_t, std::string>, 5> behaviors;
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);
}

View File

@@ -75,7 +75,7 @@ public:
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void UpdateModel(const LWOOBJID& 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;
@@ -110,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;

View File

@@ -3,7 +3,7 @@
#include "eGameMasterLevel.h"
std::optional<IAccounts::Info> MySQLDatabase::GetAccountInfo(const std::string_view username) {
auto result = ExecuteSelect("SELECT id, password, banned, locked, play_key_id, gm_level FROM accounts WHERE name = ? LIMIT 1;", username);
auto result = ExecuteSelect("SELECT id, password, banned, locked, play_key_id, gm_level, mute_expire FROM accounts WHERE name = ? LIMIT 1;", username);
if (!result->next()) {
return std::nullopt;
@@ -16,6 +16,7 @@ std::optional<IAccounts::Info> MySQLDatabase::GetAccountInfo(const std::string_v
toReturn.banned = result->getBoolean("banned");
toReturn.locked = result->getBoolean("locked");
toReturn.playKeyId = result->getUInt("play_key_id");
toReturn.muteExpire = result->getUInt64("mute_expire");
return toReturn;
}

View File

@@ -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() : "";
}

View File

@@ -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,7 +52,7 @@ void MySQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IPr
}
}
void MySQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
void MySQLDatabase::UpdateModel(const LWOOBJID& 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 = ?;",
@@ -79,11 +79,11 @@ IPropertyContents::Model MySQLDatabase::GetModel(const LWOOBJID modelID) {
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");
}
return model;

View File

@@ -73,7 +73,7 @@ public:
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void UpdateModel(const LWOOBJID& 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;
@@ -108,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;

View File

@@ -17,6 +17,7 @@ std::optional<IAccounts::Info> SQLiteDatabase::GetAccountInfo(const std::string_
toReturn.banned = result.getIntField("banned");
toReturn.locked = result.getIntField("locked");
toReturn.playKeyId = result.getIntField("play_key_id");
toReturn.muteExpire = static_cast<uint64_t>(result.getInt64Field("mute_expire"));
return toReturn;
}

View File

@@ -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") : "";
}

View File

@@ -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,7 +52,7 @@ void SQLiteDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const IP
}
}
void SQLiteDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
void SQLiteDatabase::UpdateModel(const LWOOBJID& 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 = ?;",
@@ -80,11 +80,11 @@ IPropertyContents::Model SQLiteDatabase::GetModel(const LWOOBJID modelID) {
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");
} while (result.nextRow());
}

View File

@@ -168,7 +168,7 @@ void TestSQLDatabase::InsertNewPropertyModel(const LWOOBJID& propertyId, const I
}
void TestSQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) {
void TestSQLDatabase::UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<LWOOBJID, std::string>, 5>& behaviors) {
}
@@ -292,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) {
}

View File

@@ -52,7 +52,7 @@ class TestSQLDatabase : public GameDatabase {
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModel(const LWOOBJID& modelID, const NiPoint3& position, const NiQuaternion& rotation, const std::array<std::pair<int32_t, std::string>, 5>& behaviors) override;
void UpdateModel(const LWOOBJID& 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;
@@ -87,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 {}; };

View File

@@ -47,6 +47,8 @@ void MigrationRunner::RunMigrations() {
std::string finalSQL = "";
bool runSd0Migrations = false;
bool runNormalizeMigrations = false;
bool runNormalizeAfterFirstPartMigrations = false;
bool runBrickBuildsNotOnGrid = false;
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/" / migrationFolder).string())) {
auto migration = LoadMigration("dlu/" + migrationFolder + "/", entry);
@@ -61,6 +63,10 @@ void MigrationRunner::RunMigrations() {
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 if (migration.name.ends_with("_brickbuilds_not_on_grid.sql")) {
runBrickBuildsNotOnGrid = true;
} else {
finalSQL.append(migration.data.c_str());
}
@@ -68,7 +74,7 @@ void MigrationRunner::RunMigrations() {
Database::Get()->InsertMigration(migration.name);
}
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations) {
if (finalSQL.empty() && !runSd0Migrations && !runNormalizeMigrations && !runNormalizeAfterFirstPartMigrations && !runBrickBuildsNotOnGrid) {
LOG("Server database is up to date.");
return;
}
@@ -96,6 +102,14 @@ void MigrationRunner::RunMigrations() {
if (runNormalizeMigrations) {
ModelNormalizeMigration::Run();
}
if (runNormalizeAfterFirstPartMigrations) {
ModelNormalizeMigration::RunAfterFirstPart();
}
if (runBrickBuildsNotOnGrid) {
ModelNormalizeMigration::RunBrickBuildGrid();
}
}
void MigrationRunner::RunSQLiteMigrations() {

View File

@@ -14,7 +14,7 @@ void ModelNormalizeMigration::Run() {
Sd0 sd0(lxfmlData);
const auto asStr = sd0.GetAsStringUncompressed();
const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr);
const auto [newLxfml, newCenter] = Lxfml::NormalizePositionOnlyFirstPart(asStr);
if (newCenter == NiPoint3Constant::ZERO) {
LOG("Failed to update model %llu due to failure reading xml.");
continue;
@@ -28,3 +28,44 @@ void ModelNormalizeMigration::Run() {
}
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);
}
void ModelNormalizeMigration::RunBrickBuildGrid() {
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::NormalizePosition(asStr, model.position);
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
LOG("Updated model %llu to have a center of %f %f %f", modelID, newCenter.x, newCenter.y, newCenter.z);
auto asStream = sd0.GetAsStream();
Database::Get()->UpdateModel(model.id, newCenter, model.rotation, model.behaviors);
Database::Get()->UpdateUgcModelData(id, asStream);
}
Database::Get()->SetAutoCommit(oldCommit);
}

View File

@@ -6,6 +6,8 @@
namespace ModelNormalizeMigration {
void Run();
void RunAfterFirstPart();
void RunBrickBuildGrid();
};
#endif //!MODELNORMALIZEMIGRATION_H

View File

@@ -496,7 +496,7 @@ void Character::OnZoneLoad() {
// Remove all GM items
for (const auto lot : Inventory::GetAllGMItems()) {
inventoryComponent->RemoveItem(lot, inventoryComponent->GetLotCount(lot));
inventoryComponent->RemoveItem(lot, inventoryComponent->GetLotCount(lot), eInventoryType::ALL);
}
}

View File

@@ -654,7 +654,7 @@ private:
/**
* The spawn rotation of this character when loading in
*/
NiQuaternion m_OriginalRotation;
NiQuaternion m_OriginalRotation = QuatUtils::IDENTITY;
/**
* The respawn points of this character, per world

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
#include <map>
#include <functional>
#include <tuple>
#include <typeinfo>
#include <type_traits>
#include <unordered_map>
@@ -34,7 +35,6 @@ namespace tinyxml2 {
};
class Player;
class EntityInfo;
class User;
class Spawner;
class ScriptComponent;
@@ -45,6 +45,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 +61,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 +114,7 @@ public:
float GetDefaultScale() const;
const NiPoint3& GetPosition() const;
NiPoint3 GetPosition() const;
const NiQuaternion& GetRotation() const;
@@ -124,6 +125,8 @@ public:
// then return the collision group from that.
int32_t GetCollisionGroup() const;
const NiPoint3& GetVelocity() const;
/**
* Setters
*/
@@ -144,9 +147,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
@@ -157,6 +162,12 @@ public:
template<typename T>
T* GetComponent() const;
template<typename... T>
auto GetComponents() const;
template<typename... T>
auto GetComponentsMut() const;
template<typename T>
bool TryGetComponent(eReplicaComponentType componentId, T*& component) const;
@@ -164,8 +175,10 @@ public:
void AddComponent(eReplicaComponentType componentId, Component* component);
bool MsgRequestServerObjectInfo(GameMessages::GameMsg& msg);
// 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 +191,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 +204,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 +251,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 +275,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;
@@ -325,12 +338,17 @@ public:
bool HandleMsg(GameMessages::GameMsg& msg) const;
void RegisterMsg(const MessageType::Game msgId, auto* self, const auto handler) {
RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1));
}
/**
* @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;
@@ -339,7 +357,7 @@ protected:
std::vector<LDFBaseData*> m_NetworkSettings;
NiPoint3 m_DefaultPosition;
NiQuaternion m_DefaultRotation;
NiQuaternion m_DefaultRotation = QuatUtils::IDENTITY;
float m_Scale;
Spawner* m_Spawner;
@@ -353,7 +371,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 +396,8 @@ protected:
bool m_IsParentChildDirty = true;
bool m_IsSleeping = false;
/*
* Collision
*/
@@ -387,7 +406,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 +454,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);
}
@@ -573,3 +592,13 @@ inline ComponentType* Entity::AddComponent(VaArgs... args) {
// To allow a static cast here instead of a dynamic one.
return dynamic_cast<ComponentType*>(componentToReturn);
}
template<typename... T>
auto Entity::GetComponents() const {
return GetComponentsMut<const T...>();
}
template<typename... T>
auto Entity::GetComponentsMut() const {
return std::tuple{GetComponent<T>()...};
}

View File

@@ -129,6 +129,8 @@ Entity* EntityManager::CreateEntity(EntityInfo info, User* user, Entity* parentE
// Set the zone control entity if the entity is a zone control object, this should only happen once
if (controller) {
m_ZoneControlEntity = entity;
// Proooooobably shouldn't ghost zoneControl
m_ZoneControlEntity->SetIsGhostingCandidate(false);
}
// Check if this entity is a respawn point, if so add it to the registry
@@ -279,6 +281,8 @@ std::vector<Entity*> EntityManager::GetEntitiesByComponent(const eReplicaCompone
withComp.push_back(entity);
}
} else {
for (auto* const entity : m_Entities | std::views::values) withComp.push_back(entity);
}
return withComp;
}
@@ -320,7 +324,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;
@@ -363,16 +367,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 {
@@ -419,7 +421,7 @@ void EntityManager::DestructEntity(Entity* entity, const SystemAddress& sysAddr)
void EntityManager::SerializeEntity(Entity* entity) {
if (!entity) return;
EntityManager::SerializeEntity(*entity);
}
@@ -513,9 +515,9 @@ void EntityManager::UpdateGhosting(Entity* player) {
ghostComponent->ObserveEntity(id);
ConstructEntity(entity, player->GetSystemAddress());
entity->SetObservers(entity->GetObservers() + 1);
ConstructEntity(entity, player->GetSystemAddress());
}
}
}
@@ -604,3 +606,14 @@ void EntityManager::FireEventServerSide(Entity* origin, std::string args) {
bool EntityManager::IsExcludedFromGhosting(LOT lot) {
return std::find(m_GhostingExcludedLOTs.begin(), m_GhostingExcludedLOTs.end(), lot) != m_GhostingExcludedLOTs.end();
}
bool EntityManager::SendMessage(GameMessages::GameMsg& msg) const {
bool handled = false;
const auto entityItr = m_Entities.find(msg.target);
if (entityItr != m_Entities.end()) {
auto* const entity = entityItr->second;
if (entity) handled = entity->HandleMsg(msg);
}
return handled;
}

View File

@@ -9,11 +9,15 @@
#include "dCommonVars.h"
class Entity;
class EntityInfo;
struct EntityInfo;
class Player;
class User;
enum class eReplicaComponentType : uint32_t;
namespace GameMessages {
struct GameMsg;
}
struct SystemAddress;
class EntityManager {
@@ -42,7 +46,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);
@@ -72,6 +76,9 @@ public:
const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; };
const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; };
// Messaging
bool SendMessage(GameMessages::GameMsg& msg) const;
private:
void SerializeEntities();
void KillEntities();

View File

@@ -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);
}

View File

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

View File

@@ -7,6 +7,10 @@
#include "dZoneManager.h"
#include "eServerDisconnectIdentifiers.h"
#include "eGameMasterLevel.h"
#include "BitStreamUtils.h"
#include "MessageType/Chat.h"
#include <chrono>
#include <ctime>
User::User(const SystemAddress& sysAddr, const std::string& username, const std::string& sessionKey) {
m_AccountID = 0;
@@ -28,7 +32,7 @@ User::User(const SystemAddress& sysAddr, const std::string& username, const std:
if (userInfo) {
m_AccountID = userInfo->id;
m_MaxGMLevel = userInfo->maxGmLevel;
m_MuteExpire = 0; //res->getUInt64(3);
m_MuteExpire = userInfo->muteExpire;
}
//If we're loading a zone, we'll load the last used (aka current) character:
@@ -91,8 +95,28 @@ Character* User::GetLastUsedChar() {
}
}
bool User::GetIsMuted() const {
return m_MuteExpire == 1 || m_MuteExpire > time(NULL);
bool User::GetIsMuted() {
using namespace std::chrono;
constexpr auto refreshInterval = seconds{ 60 };
const auto now = steady_clock::now();
if (now - m_LastMuteCheck >= refreshInterval) {
m_LastMuteCheck = now;
if (const auto info = Database::Get()->GetAccountInfo(m_Username)) {
const auto expire = static_cast<time_t>(info->muteExpire);
if (expire != m_MuteExpire) {
m_MuteExpire = expire;
if (Game::chatServer && m_LoggedInCharID != 0) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::GM_MUTE);
bitStream.Write(m_LoggedInCharID);
bitStream.Write(m_MuteExpire);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
}
}
}
return m_MuteExpire == 1 || m_MuteExpire > std::time(nullptr);
}
time_t User::GetMuteExpire() const {

View File

@@ -3,6 +3,7 @@
#include <string>
#include <vector>
#include <chrono>
#include "RakNetTypes.h"
#include "dCommonVars.h"
@@ -46,7 +47,7 @@ public:
const std::unordered_map<std::string, bool>& GetIsBestFriendMap() { return m_IsBestFriendMap; }
void UpdateBestFriendValue(const std::string_view playerName, const bool newValue);
bool GetIsMuted() const;
bool GetIsMuted();
time_t GetMuteExpire() const;
void SetMuteExpire(time_t value);
@@ -72,7 +73,8 @@ private:
bool m_LastChatMessageApproved = false;
int m_AmountOfTimesOutOfSync = 0;
const int m_MaxDesyncAllowed = 12;
time_t m_MuteExpire;
uint64_t m_MuteExpire;
std::chrono::steady_clock::time_point m_LastMuteCheck{};
};
#endif // USER_H

View File

@@ -25,10 +25,11 @@
#include "eGameMasterLevel.h"
#include "eCharacterCreationResponse.h"
#include "eRenameResponse.h"
#include "eConnectionType.h"
#include "ServiceType.h"
#include "MessageType/Chat.h"
#include "BitStreamUtils.h"
#include "CheatDetection.h"
#include "CharacterComponent.h"
UserManager* UserManager::m_Address = nullptr;
@@ -216,7 +217,7 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) {
}
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::CHARACTER_LIST_RESPONSE);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::CHARACTER_LIST_RESPONSE);
std::vector<Character*> characters = u->GetCharacters();
bitStream.Write<uint8_t>(characters.size());
@@ -340,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\"/>";
@@ -423,7 +427,7 @@ void UserManager::DeleteCharacter(const SystemAddress& sysAddr, Packet* packet)
Database::Get()->DeleteCharacter(charID);
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::UNEXPECTED_DISCONNECT);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::UNEXPECTED_DISCONNECT);
bitStream.Write(objectID);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
@@ -522,6 +526,13 @@ void UserManager::LoginCharacter(const SystemAddress& sysAddr, uint32_t playerID
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);

View File

@@ -16,7 +16,7 @@
#include "QuickBuildComponent.h"
#include "eReplicaComponentType.h"
#include "TeamManager.h"
#include "eConnectionType.h"
#include "ServiceType.h"
BehaviorSyncEntry::BehaviorSyncEntry() {
}
@@ -212,7 +212,7 @@ void BehaviorContext::UpdatePlayerSyncs(float deltaTime) {
echo.sBitStream.assign(reinterpret_cast<char*>(bitStream.GetData()), bitStream.GetNumberOfBytesUsed());
RakNet::BitStream message;
BitStreamUtils::WriteHeader(message, eConnectionType::CLIENT, MessageType::Client::GAME_MSG);
BitStreamUtils::WriteHeader(message, ServiceType::CLIENT, MessageType::Client::GAME_MSG);
message.Write(this->originator);
echo.Serialize(message);
@@ -285,7 +285,7 @@ bool BehaviorContext::CalculateUpdate(const float deltaTime) {
// Write message
RakNet::BitStream message;
BitStreamUtils::WriteHeader(message, eConnectionType::CLIENT, MessageType::Client::GAME_MSG);
BitStreamUtils::WriteHeader(message, ServiceType::CLIENT, MessageType::Client::GAME_MSG);
message.Write(this->originator);
echo.Serialize(message);

View File

@@ -3,6 +3,8 @@
#include "BehaviorContext.h"
#include "EntityManager.h"
#include <glm/gtc/quaternion.hpp>
void ChangeOrientationBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitStream, BehaviorBranchContext branch) {
Entity* sourceEntity;
if (this->m_orientCaster) sourceEntity = Game::entityManager->GetEntity(context->originator);
@@ -16,12 +18,12 @@ void ChangeOrientationBehavior::Calculate(BehaviorContext* context, RakNet::BitS
if (!destinationEntity) return;
sourceEntity->SetRotation(
NiQuaternion::LookAt(sourceEntity->GetPosition(), destinationEntity->GetPosition())
QuatUtils::LookAt(sourceEntity->GetPosition(), destinationEntity->GetPosition())
);
} else if (this->m_toAngle){
auto baseAngle = NiPoint3(0, 0, this->m_angle);
if (this->m_relative) baseAngle += sourceEntity->GetRotation().GetForwardVector();
sourceEntity->SetRotation(NiQuaternion::FromEulerAngles(baseAngle));
if (this->m_relative) baseAngle += QuatUtils::Forward(sourceEntity->GetRotation());
sourceEntity->SetRotation(glm::quat(glm::vec3(baseAngle.x, baseAngle.y, baseAngle.z)));
} else return;
Game::entityManager->SerializeEntity(sourceEntity);
return;

View File

@@ -12,7 +12,7 @@ void ConsumeItemBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bi
auto inventoryComponent = caster->GetComponent<InventoryComponent>();
if (!inventoryComponent) return;
if (inventoryComponent->RemoveItem(this->m_ConsumeLOT, this->m_NumToConsume, eInventoryType::INVALID, false, true)){
if (inventoryComponent->RemoveItem(this->m_ConsumeLOT, this->m_NumToConsume, eInventoryType::ALL, false, true)){
action_to_cast = m_ActionConsumed;
}
}

View File

@@ -48,7 +48,7 @@ void ForceMovementBehavior::Calculate(BehaviorContext* context, RakNet::BitStrea
if (controllablePhysicsComponent != nullptr) {
if (m_Forward == 1) {
controllablePhysicsComponent->SetVelocity(controllablePhysicsComponent->GetRotation().GetForwardVector() * 25);
controllablePhysicsComponent->SetVelocity(QuatUtils::Forward(controllablePhysicsComponent->GetRotation()) * 25);
}
Game::entityManager->SerializeEntity(casterEntity);

View File

@@ -92,7 +92,7 @@ void ProjectileAttackBehavior::Calculate(BehaviorContext* context, RakNet::BitSt
const auto time = distance / this->m_projectileSpeed;
const auto rotation = NiQuaternion::LookAtUnlocked(position, other->GetPosition());
const auto rotation = QuatUtils::LookAtUnlocked(position, other->GetPosition());
const auto targetPosition = other->GetPosition();
@@ -112,13 +112,13 @@ void ProjectileAttackBehavior::Calculate(BehaviorContext* context, RakNet::BitSt
bitStream.Write(id);
auto eulerAngles = rotation.GetEulerAngles();
auto eulerAngles = QuatUtils::Euler(rotation);
eulerAngles.y += angle * (3.14 / 180);
eulerAngles.y += angle * (glm::pi<float>() / 180.0f);
const auto angledRotation = NiQuaternion::FromEulerAngles(eulerAngles);
const auto angledRotation = QuatUtils::FromEuler(eulerAngles);
const auto direction = angledRotation.GetForwardVector();
const auto direction = QuatUtils::Forward(angledRotation);
const auto destination = position + direction * distance;

View File

@@ -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();

View File

@@ -36,7 +36,7 @@ void SpawnBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bitStrea
info.spawner = nullptr;
info.spawnerID = context->originator;
info.spawnerNodeID = 0;
info.pos = info.pos + (info.rot.GetForwardVector() * m_Distance);
info.pos = info.pos + (QuatUtils::Forward(info.rot) * m_Distance);
auto* entity = Game::entityManager->CreateEntity(
info,

View File

@@ -125,7 +125,7 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitS
if (targetPos.y > reference.y && heightDifference > this->m_upperBound || targetPos.y < reference.y && heightDifference > this->m_lowerBound)
continue;
const auto forward = self->GetRotation().GetForwardVector();
const auto forward = QuatUtils::Forward(self->GetRotation());
// forward is a normalized vector of where the caster is facing.
// targetPos is the position of the target.

View File

@@ -64,12 +64,11 @@ void AchievementVendorComponent::Buy(Entity* buyer, LOT lot, uint32_t count) {
}
const uint32_t altCurrencyCost = itemComp.commendationCost * count;
if (inventoryComponent->GetLotCount(costLOT) < altCurrencyCost) {
if (inventoryComponent->GetLotCount(costLOT) < altCurrencyCost || !inventoryComponent->RemoveItem(costLOT, altCurrencyCost, eInventoryType::ALL)) {
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_FAIL);
return;
}
inventoryComponent->RemoveItem(costLOT, altCurrencyCost);
inventoryComponent->AddItem(lot, count, eLootSourceType::VENDOR);
GameMessages::SendVendorTransactionResult(buyer, buyer->GetSystemAddress(), eVendorTransactionResult::PURCHASE_SUCCESS);

View File

@@ -20,15 +20,19 @@
#include "Loot.h"
#include "eMissionTaskType.h"
#include "eMatchUpdate.h"
#include "eConnectionType.h"
#include "ServiceType.h"
#include "MessageType/Chat.h"
#include "CDCurrencyTableTable.h"
#include "CDActivityRewardsTable.h"
#include "CDActivitiesTable.h"
#include "LeaderboardManager.h"
#include "CharacterComponent.h"
#include "Amf3.h"
ActivityComponent::ActivityComponent(Entity* parent, int32_t activityID) : Component(parent) {
using namespace GameMessages;
RegisterMsg<GetObjectReportInfo>(this, &ActivityComponent::OnGetObjectReportInfo);
/*
* This is precisely what the client does functionally
* Use the component id as the default activity id and load its data from the database
@@ -333,7 +337,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 +348,15 @@ 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>();
return CheckCost(player) && inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount, eInventoryType::ALL);
}
void ActivityComponent::PlayerReady(Entity* player, bool bReady) {
for (Lobby* lobby : m_Queue) {
for (LobbyPlayer* lobbyPlayer : lobby->players) {
@@ -381,7 +389,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;
}
@@ -501,7 +509,7 @@ void ActivityInstance::StartZone() {
// only make a team if we have more than one participant
if (participants.size() > 1) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, MessageType::Chat::CREATE_TEAM);
BitStreamUtils::WriteHeader(bitStream, ServiceType::CHAT, MessageType::Chat::CREATE_TEAM);
bitStream.Write(leader->GetObjectID());
bitStream.Write(m_Participants.size());
@@ -526,6 +534,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);
@@ -561,7 +574,7 @@ void ActivityInstance::RewardParticipant(Entity* participant) {
maxCoins = currencyTable[0].maxvalue;
}
Loot::DropLoot(participant, m_Parent, activityRewards[0].LootMatrixIndex, minCoins, maxCoins);
Loot::DropLoot(participant, m_Parent->GetObjectID(), activityRewards[0].LootMatrixIndex, minCoins, maxCoins);
}
}
@@ -604,3 +617,91 @@ void ActivityInstance::SetScore(uint32_t score) {
Entity* LobbyPlayer::GetEntity() const {
return Game::entityManager->GetEntity(entityID);
}
bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& activityInfo = reportInfo.info->PushDebug("Activity");
auto& instances = activityInfo.PushDebug("Instances: " + std::to_string(m_Instances.size()));
size_t i = 0;
for (const auto& activityInstance : m_Instances) {
if (!activityInstance) continue;
auto& instance = instances.PushDebug("Instance " + std::to_string(i++));
instance.PushDebug<AMFIntValue>("Score") = activityInstance->GetScore();
instance.PushDebug<AMFIntValue>("Next Zone Clone ID") = activityInstance->GetNextZoneCloneID();
{
auto& activityInfo = instance.PushDebug("Activity Info");
const auto& instanceActInfo = activityInstance->GetActivityInfo();
activityInfo.PushDebug<AMFIntValue>("ActivityID") = instanceActInfo.ActivityID;
activityInfo.PushDebug<AMFIntValue>("locStatus") = instanceActInfo.locStatus;
activityInfo.PushDebug<AMFIntValue>("instanceMapID") = instanceActInfo.instanceMapID;
activityInfo.PushDebug<AMFIntValue>("minTeams") = instanceActInfo.minTeams;
activityInfo.PushDebug<AMFIntValue>("maxTeams") = instanceActInfo.maxTeams;
activityInfo.PushDebug<AMFIntValue>("minTeamSize") = instanceActInfo.minTeamSize;
activityInfo.PushDebug<AMFIntValue>("maxTeamSize") = instanceActInfo.maxTeamSize;
activityInfo.PushDebug<AMFIntValue>("waitTime") = instanceActInfo.waitTime;
activityInfo.PushDebug<AMFIntValue>("startDelay") = instanceActInfo.startDelay;
activityInfo.PushDebug<AMFBoolValue>("requiresUniqueData") = instanceActInfo.requiresUniqueData;
activityInfo.PushDebug<AMFIntValue>("leaderboardType") = instanceActInfo.leaderboardType;
activityInfo.PushDebug<AMFBoolValue>("localize") = instanceActInfo.localize;
activityInfo.PushDebug<AMFIntValue>("optionalCostLOT") = instanceActInfo.optionalCostLOT;
activityInfo.PushDebug<AMFIntValue>("optionalCostCount") = instanceActInfo.optionalCostCount;
activityInfo.PushDebug<AMFBoolValue>("showUIRewards") = instanceActInfo.showUIRewards;
activityInfo.PushDebug<AMFIntValue>("CommunityActivityFlagID") = instanceActInfo.CommunityActivityFlagID;
activityInfo.PushDebug<AMFStringValue>("gate_version") = instanceActInfo.gate_version;
activityInfo.PushDebug<AMFBoolValue>("noTeamLootOnDeath") = instanceActInfo.noTeamLootOnDeath;
activityInfo.PushDebug<AMFDoubleValue>("optionalPercentage") = instanceActInfo.optionalPercentage;
}
auto& participants = instance.PushDebug("Participants");
for (const auto* participant : activityInstance->GetParticipants()) {
if (!participant) continue;
auto* character = participant->GetCharacter();
if (!character) continue;
participants.PushDebug<AMFStringValue>(std::to_string(participant->GetObjectID()) + ": " + character->GetName()) = "";
}
}
auto& queue = activityInfo.PushDebug("Queue");
i = 0;
for (const auto& lobbyQueue : m_Queue) {
auto& lobby = queue.PushDebug("Lobby " + std::to_string(i++));
lobby.PushDebug<AMFDoubleValue>("Timer") = lobbyQueue->timer;
auto& players = lobby.PushDebug("Players");
for (const auto* player : lobbyQueue->players) {
if (!player) continue;
auto* playerEntity = player->GetEntity();
if (!playerEntity) continue;
auto* character = playerEntity->GetCharacter();
if (!character) continue;
players.PushDebug<AMFStringValue>(std::to_string(playerEntity->GetObjectID()) + ": " + character->GetName()) = player->ready ? "Ready" : "Not Ready";
}
}
auto& activityPlayers = activityInfo.PushDebug("Activity Players");
for (const auto* activityPlayer : m_ActivityPlayers) {
if (!activityPlayer) continue;
auto* const activityPlayerEntity = Game::entityManager->GetEntity(activityPlayer->playerID);
if (!activityPlayerEntity) continue;
auto* character = activityPlayerEntity->GetCharacter();
if (!character) continue;
auto& playerData = activityPlayers.PushDebug(std::to_string(activityPlayer->playerID) + " " + character->GetName());
auto& scores = playerData.PushDebug("Scores");
for (size_t i = 0; i < 10; ++i) {
scores.PushDebug<AMFDoubleValue>(std::to_string(i)) = activityPlayer->values[i];
}
}
auto& lootMatrices = activityInfo.PushDebug("Loot Matrices");
for (const auto& [activityRating, lootMatrixID] : m_ActivityLootMatrices) {
lootMatrices.PushDebug<AMFIntValue>("Loot Matrix " + std::to_string(activityRating)) = lootMatrixID;
}
activityInfo.PushDebug<AMFIntValue>("ActivityID") = m_ActivityID;
return true;
}

View File

@@ -9,6 +9,10 @@
#include "CDActivitiesTable.h"
namespace GameMessages {
class GameMsg;
};
/**
* Represents an instance of an activity, having participants and score
*/
@@ -60,6 +64,10 @@ public:
* Currently unused
*/
void SetScore(uint32_t score);
[[nodiscard]] uint32_t GetNextZoneCloneID() const noexcept { return m_NextZoneCloneID; }
const CDActivities& GetActivityInfo() const noexcept { return m_ActivityInfo; }
private:
/**
@@ -75,12 +83,12 @@ private:
/**
* The database information for this activity
*/
CDActivities m_ActivityInfo;
CDActivities m_ActivityInfo{};
/**
* The entity that owns this activity (the entity that has the ScriptedActivityComponent)
*/
Entity* m_Parent;
Entity* m_Parent{};
/**
* All the participants of this activity
@@ -234,10 +242,17 @@ public:
*/
bool IsPlayedBy(LWOOBJID playerID) const;
/**
* Checks if the entity has enough cost to play this activity
* @param player the entity to check
* @return true if the entity has enough cost to play this activity, false otherwise
*/
bool CheckCost(Entity* player) const;
/**
* Removes the cost of the activity (e.g. green imaginate) for the entity that plays this activity
* @param player the entity to take cost for
* @return true if the cost was successfully deducted, false otherwise
* @return true if the cost was taken, false otherwise
*/
bool TakeCost(Entity* player) const;
@@ -334,6 +349,7 @@ public:
uint32_t GetLootMatrixForTeamSize(uint32_t teamSize) { return m_ActivityLootMatrices[teamSize]; }
private:
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
/**
* The database information for this activity
*/

View File

@@ -767,7 +767,7 @@ void BaseCombatAIComponent::LookAt(const NiPoint3& point) {
return;
}
m_Parent->SetRotation(NiQuaternion::LookAt(m_Parent->GetPosition(), point));
m_Parent->SetRotation(QuatUtils::LookAt(m_Parent->GetPosition(), point));
}
void BaseCombatAIComponent::SetDisabled(bool value) {

View File

@@ -77,4 +77,4 @@ target_include_directories(dComponents PUBLIC "."
)
target_precompile_headers(dComponents REUSE_FROM dGameBase)
target_link_libraries(dComponents INTERFACE dBehaviors)
target_link_libraries(dComponents INTERFACE dBehaviors PRIVATE glm::glm)

View File

@@ -49,19 +49,13 @@ CharacterComponent::CharacterComponent(Entity* parent, Character* character, con
m_LastUpdateTimestamp = std::time(nullptr);
m_SystemAddress = systemAddress;
RegisterMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, this, &CharacterComponent::OnRequestServerObjectInfo);
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &CharacterComponent::OnGetObjectReportInfo);
}
bool CharacterComponent::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) {
auto& request = static_cast<GameMessages::RequestServerObjectInfo&>(msg);
AMFArrayValue response;
bool CharacterComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
response.Insert("visible", true);
response.Insert("objectID", std::to_string(request.targetForReport));
response.Insert("serverInfo", true);
auto& data = *response.InsertArray("data");
auto& cmptType = data.PushDebug("Character");
auto& cmptType = reportInfo.info->PushDebug("Character");
cmptType.PushDebug<AMFIntValue>("Component ID") = GeneralUtils::ToUnderlying(ComponentType);
cmptType.PushDebug<AMFIntValue>("Character's account ID") = m_Character->GetParentUser()->GetAccountID();
@@ -72,6 +66,13 @@ bool CharacterComponent::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) {
cmptType.PushDebug<AMFStringValue>("Total currency") = std::to_string(m_Character->GetCoins());
cmptType.PushDebug<AMFStringValue>("Currency able to be picked up") = std::to_string(m_DroppedCoins);
cmptType.PushDebug<AMFStringValue>("Tooltip flags value") = "0";
auto& vl = cmptType.PushDebug("Visited Levels");
for (const auto zoneID : m_VisitedLevels) {
std::stringstream sstream;
sstream << "MapID: " << zoneID.GetMapID() << " CloneID: " << zoneID.GetCloneID();
vl.PushDebug<AMFStringValue>(sstream.str()) = "";
}
// visited locations
cmptType.PushDebug<AMFBoolValue>("is a GM") = m_GMLevel > eGameMasterLevel::CIVILIAN;
cmptType.PushDebug<AMFBoolValue>("Has PVP flag turned on") = m_PvpEnabled;
@@ -83,9 +84,6 @@ bool CharacterComponent::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) {
cmptType.PushDebug<AMFIntValue>("Current Activity Type") = GeneralUtils::ToUnderlying(m_CurrentActivity);
cmptType.PushDebug<AMFDoubleValue>("Property Clone ID") = m_Character->GetPropertyCloneID();
GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, m_Parent->GetSystemAddress());
LOG("Handled!");
return true;
}
@@ -245,6 +243,8 @@ void CharacterComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
SetReputation(0);
}
auto* vl = character->FirstChildElement("vl");
if (vl) LoadVisitedLevelsXml(*vl);
character->QueryUnsigned64Attribute("co", &m_ClaimCodes[0]);
character->QueryUnsigned64Attribute("co1", &m_ClaimCodes[1]);
character->QueryUnsigned64Attribute("co2", &m_ClaimCodes[2]);
@@ -374,6 +374,10 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
return;
}
auto* vl = character->FirstChildElement("vl");
if (!vl) vl = character->InsertNewChildElement("vl");
UpdateVisitedLevelsXml(*vl);
if (m_ClaimCodes[0] != 0) character->SetAttribute("co", m_ClaimCodes[0]);
if (m_ClaimCodes[1] != 0) character->SetAttribute("co1", m_ClaimCodes[1]);
if (m_ClaimCodes[2] != 0) character->SetAttribute("co2", m_ClaimCodes[2]);
@@ -855,8 +859,9 @@ void CharacterComponent::SendToZone(LWOMAPID zoneId, LWOCLONEID cloneId) const {
character->SetZoneID(zoneID);
character->SetZoneInstance(zoneInstance);
character->SetZoneClone(zoneClone);
characterComponent->SetLastRocketConfig(u"");
characterComponent->AddVisitedLevel(LWOZONEID(zoneID, LWOINSTANCEID_INVALID, zoneClone));
character->SaveXMLToDatabase();
}
@@ -883,3 +888,30 @@ void CharacterComponent::SetRespawnPos(const NiPoint3& position) {
void CharacterComponent::SetRespawnRot(const NiQuaternion& rotation) {
m_respawnRot = rotation;
}
void CharacterComponent::AddVisitedLevel(const LWOZONEID zoneID) {
LWOZONEID toInsert(zoneID.GetMapID(), LWOINSTANCEID_INVALID, zoneID.GetCloneID());
m_VisitedLevels.insert(toInsert);
}
void CharacterComponent::UpdateVisitedLevelsXml(tinyxml2::XMLElement& vl) {
vl.DeleteChildren();
// <vl>
for (const auto zoneID : m_VisitedLevels) {
// <l id=\"1100\" cid=\"0\"/>
auto* l = vl.InsertNewChildElement("l");
l->SetAttribute("id", zoneID.GetMapID());
l->SetAttribute("cid", zoneID.GetCloneID());
}
// </vl>
}
void CharacterComponent::LoadVisitedLevelsXml(const tinyxml2::XMLElement& vl) {
// <vl>
for (const auto* l = vl.FirstChildElement("l"); l != nullptr; l = l->NextSiblingElement("l")) {
// <l id=\"1100\" cid=\"0\"/>
LWOZONEID toInsert(l->IntAttribute("id"), LWOINSTANCEID_INVALID, l->IntAttribute("cid"));
m_VisitedLevels.insert(toInsert);
}
// </vl>
}

View File

@@ -10,6 +10,7 @@
#include "tinyxml2.h"
#include "eReplicaComponentType.h"
#include <array>
#include <set>
#include "Loot.h"
enum class eGameActivity : uint32_t;
@@ -321,9 +322,16 @@ public:
* Character info regarding this character, including clothing styles, etc.
*/
Character* m_Character;
/* Saves the provided zoneID as a visited level. Ignores InstanceID */
void AddVisitedLevel(const LWOZONEID zoneID);
/* Updates the VisitedLevels (vl) node of the charxml */
void UpdateVisitedLevelsXml(tinyxml2::XMLElement& doc);
/* Reads the VisitedLevels (vl) node of the charxml */
void LoadVisitedLevelsXml(const tinyxml2::XMLElement& doc);
private:
bool OnRequestServerObjectInfo(GameMessages::GameMsg& msg);
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
/**
* The map of active venture vision effects
@@ -614,11 +622,13 @@ private:
NiPoint3 m_respawnPos;
NiQuaternion m_respawnRot;
NiQuaternion m_respawnRot = QuatUtils::IDENTITY;
std::map<LWOOBJID, Loot::Info> m_DroppedLoot;
uint64_t m_DroppedCoins = 0;
std::set<LWOZONEID> m_VisitedLevels;
};
#endif // CHARACTERCOMPONENT_H

View File

@@ -56,10 +56,16 @@ public:
protected:
void RegisterMsg(const MessageType::Game msgId, auto* self, const auto handler) {
inline void RegisterMsg(const MessageType::Game msgId, auto* self, const auto handler) {
m_Parent->RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1));
}
template<typename T>
inline void RegisterMsg(auto* self, const auto handler) {
T msg;
RegisterMsg(msg.msgId, self, handler);
}
/**
* The entity that owns this component
*/

View File

@@ -14,8 +14,12 @@
#include "dZoneManager.h"
#include "LevelProgressionComponent.h"
#include "eStateChangeType.h"
#include "StringifiedEnum.h"
#include "Amf3.h"
ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity, int32_t componentId) : PhysicsComponent(entity, componentId) {
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &ControllablePhysicsComponent::OnGetObjectReportInfo);
m_Velocity = {};
m_AngularVelocity = {};
m_InJetpackMode = false;
@@ -354,3 +358,58 @@ void ControllablePhysicsComponent::SetStunImmunity(
bImmuneToStunUseItem
);
}
bool ControllablePhysicsComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
PhysicsComponent::OnGetObjectReportInfo(msg);
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& info = reportInfo.subCategory->PushDebug("Controllable Info");
auto& vel = info.PushDebug("Velocity");
vel.PushDebug<AMFDoubleValue>("x") = m_Velocity.x;
vel.PushDebug<AMFDoubleValue>("y") = m_Velocity.y;
vel.PushDebug<AMFDoubleValue>("z") = m_Velocity.z;
auto& angularVelocity = info.PushDebug("Angular Velocity");
angularVelocity.PushDebug<AMFDoubleValue>("x") = m_AngularVelocity.x;
angularVelocity.PushDebug<AMFDoubleValue>("y") = m_AngularVelocity.y;
angularVelocity.PushDebug<AMFDoubleValue>("z") = m_AngularVelocity.z;
info.PushDebug<AMFBoolValue>("Is On Ground") = m_IsOnGround;
info.PushDebug<AMFBoolValue>("Is On Rail") = m_IsOnRail;
info.PushDebug<AMFBoolValue>("Is In Jetpack Mode") = m_InJetpackMode;
info.PushDebug<AMFBoolValue>("Is Jetpack Flying") = m_JetpackFlying;
info.PushDebug<AMFBoolValue>("Is Bypassing Jetpack Checks") = m_JetpackBypassChecks;
info.PushDebug<AMFIntValue>("Jetpack Effect ID") = m_JetpackEffectID;
info.PushDebug<AMFDoubleValue>("Speed Multiplier") = m_SpeedMultiplier;
info.PushDebug<AMFDoubleValue>("Gravity Scale") = m_GravityScale;
info.PushDebug<AMFBoolValue>("Is Static") = m_Static;
auto& pickupRadii = info.PushDebug("Active Pickup Radius Scales");
size_t i = 0;
for (const auto& scale : m_ActivePickupRadiusScales) {
pickupRadii.PushDebug<AMFStringValue>(std::to_string(i++) + " " + std::to_string(scale)) = "";
}
info.PushDebug<AMFDoubleValue>("Largest Pickup Radius") = m_PickupRadius;
info.PushDebug<AMFBoolValue>("Is Teleporting") = m_IsTeleporting;
auto& activeSpeedBoosts = info.PushDebug("Active Speed Boosts");
i = 0;
for (const auto& boost : m_ActiveSpeedBoosts) {
activeSpeedBoosts.PushDebug<AMFStringValue>(std::to_string(i++) + " " + std::to_string(boost)) = "";
}
info.PushDebug<AMFDoubleValue>("Speed Boost") = m_SpeedBoost;
info.PushDebug<AMFBoolValue>("Is In Bubble") = m_IsInBubble;
info.PushDebug<AMFStringValue>("Bubble Type") = StringifiedEnum::ToString(m_BubbleType).data();
info.PushDebug<AMFBoolValue>("Special Anims") = m_SpecialAnims;
info.PushDebug<AMFIntValue>("Immune To Stun Attack Count") = m_ImmuneToStunAttackCount;
info.PushDebug<AMFIntValue>("Immune To Stun Equip Count") = m_ImmuneToStunEquipCount;
info.PushDebug<AMFIntValue>("Immune To Stun Interact Count") = m_ImmuneToStunInteractCount;
info.PushDebug<AMFIntValue>("Immune To Stun Jump Count") = m_ImmuneToStunJumpCount;
info.PushDebug<AMFIntValue>("Immune To Stun Move Count") = m_ImmuneToStunMoveCount;
info.PushDebug<AMFIntValue>("Immune To Stun Turn Count") = m_ImmuneToStunTurnCount;
info.PushDebug<AMFIntValue>("Immune To Stun UseItem Count") = m_ImmuneToStunUseItemCount;
return true;
}

View File

@@ -1,16 +1,18 @@
#ifndef CONTROLLABLEPHYSICSCOMPONENT_H
#define CONTROLLABLEPHYSICSCOMPONENT_H
#include "PhysicsComponent.h"
#include "eReplicaComponentType.h"
#include "dCommonVars.h"
#include "RakNetTypes.h"
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include "tinyxml2.h"
#include "PhysicsComponent.h"
#include "dpCollisionChecks.h"
#include "PhantomPhysicsComponent.h"
#include "eBubbleType.h"
#include "eReplicaComponentType.h"
namespace tinyxml2 {
class XMLDocument;
}
class Entity;
class dpEntity;
@@ -281,6 +283,8 @@ public:
const bool GetImmuneToStunUseItem() { return m_ImmuneToStunUseItemCount > 0;};
private:
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
/**
* The entity that owns this component
*/
@@ -374,7 +378,7 @@ private:
/**
* The active speed boost for this entity
*/
float m_SpeedBoost;
float m_SpeedBoost = 500.0f;
/*
* If Bubble info is dirty

View File

@@ -21,6 +21,7 @@
#include "BuffComponent.h"
#include "SkillComponent.h"
#include "Item.h"
#include "Amf3.h"
#include <sstream>
#include <algorithm>
@@ -29,6 +30,7 @@
#include "CharacterComponent.h"
#include "PossessableComponent.h"
#include "PossessorComponent.h"
#include "ModelComponent.h"
#include "InventoryComponent.h"
#include "dZoneManager.h"
#include "WorldConfig.h"
@@ -42,6 +44,7 @@ Implementation<bool, const Entity*> DestroyableComponent::IsEnemyImplentation;
Implementation<bool, const Entity*> DestroyableComponent::IsFriendImplentation;
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
using namespace GameMessages;
m_iArmor = 0;
m_fMaxArmor = 0.0f;
m_iImagination = 0;
@@ -78,6 +81,9 @@ DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
m_DeathBehavior = -1;
m_DamageCooldownTimer = 0.0f;
RegisterMsg<GetObjectReportInfo>(this, &DestroyableComponent::OnGetObjectReportInfo);
RegisterMsg<GameMessages::SetFaction>(this, &DestroyableComponent::OnSetFaction);
}
DestroyableComponent::~DestroyableComponent() {
@@ -575,6 +581,14 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
return;
}
// Client does the same check, so we're doing it too
auto* const modelComponent = m_Parent->GetComponent<ModelComponent>();
if (modelComponent) {
modelComponent->OnHit();
// Don't actually deal the damage so the model doesn't die
return;
}
// If this entity has damage reduction, reduce the damage to a minimum of 1
if (m_DamageReduction > 0 && damage > 0) {
if (damage > m_DamageReduction) {
@@ -755,18 +769,18 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
auto* member = Game::entityManager->GetEntity(specificOwner);
if (member) Loot::DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins());
if (member) Loot::DropLoot(member, m_Parent->GetObjectID(), lootMatrixId, GetMinCoins(), GetMaxCoins());
} else {
for (const auto memberId : team->members) { // Free for all
auto* member = Game::entityManager->GetEntity(memberId);
if (member == nullptr) continue;
Loot::DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins());
Loot::DropLoot(member, m_Parent->GetObjectID(), lootMatrixId, GetMinCoins(), GetMaxCoins());
}
}
} else { // drop loot for non team user
Loot::DropLoot(owner, m_Parent, GetLootMatrixID(), GetMinCoins(), GetMaxCoins());
Loot::DropLoot(owner, m_Parent->GetObjectID(), GetLootMatrixID(), GetMinCoins(), GetMaxCoins());
}
}
} else {
@@ -784,7 +798,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
coinsTotal -= coinsToLose;
Loot::DropLoot(m_Parent, m_Parent, -1, coinsToLose, coinsToLose);
Loot::DropLoot(m_Parent, m_Parent->GetObjectID(), -1, coinsToLose, coinsToLose);
character->SetCoins(coinsTotal, eLootSourceType::PICKUP);
}
}
@@ -1031,3 +1045,65 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
}
}
}
bool DestroyableComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
auto& destroyableInfo = reportInfo.info->PushDebug("Destroyable");
destroyableInfo.PushDebug<AMFIntValue>("Health") = m_iHealth;
destroyableInfo.PushDebug<AMFDoubleValue>("Max Health") = m_fMaxHealth;
destroyableInfo.PushDebug<AMFIntValue>("Armor") = m_iArmor;
destroyableInfo.PushDebug<AMFDoubleValue>("Max Armor") = m_fMaxArmor;
destroyableInfo.PushDebug<AMFIntValue>("Imagination") = m_iImagination;
destroyableInfo.PushDebug<AMFDoubleValue>("Max Imagination") = m_fMaxImagination;
destroyableInfo.PushDebug<AMFIntValue>("Damage To Absorb") = m_DamageToAbsorb;
destroyableInfo.PushDebug<AMFBoolValue>("Is GM Immune") = m_IsGMImmune;
destroyableInfo.PushDebug<AMFBoolValue>("Is Shielded") = m_IsShielded;
destroyableInfo.PushDebug<AMFIntValue>("Attacks To Block") = m_AttacksToBlock;
destroyableInfo.PushDebug<AMFIntValue>("Damage Reduction") = m_DamageReduction;
auto& factions = destroyableInfo.PushDebug("Factions");
size_t i = 0;
for (const auto factionID : m_FactionIDs) {
factions.PushDebug<AMFStringValue>(std::to_string(i++) + " " + std::to_string(factionID)) = "";
}
auto& enemyFactions = destroyableInfo.PushDebug("Enemy Factions");
i = 0;
for (const auto enemyFactionID : m_EnemyFactionIDs) {
enemyFactions.PushDebug<AMFStringValue>(std::to_string(i++) + " " + std::to_string(enemyFactionID)) = "";
}
destroyableInfo.PushDebug<AMFBoolValue>("Is Smashable") = m_IsSmashable;
destroyableInfo.PushDebug<AMFBoolValue>("Is Dead") = m_IsDead;
destroyableInfo.PushDebug<AMFBoolValue>("Is Smashed") = m_IsSmashed;
destroyableInfo.PushDebug<AMFBoolValue>("Is Module Assembly") = m_IsModuleAssembly;
destroyableInfo.PushDebug<AMFDoubleValue>("Explode Factor") = m_ExplodeFactor;
destroyableInfo.PushDebug<AMFBoolValue>("Has Threats") = m_HasThreats;
destroyableInfo.PushDebug<AMFIntValue>("Loot Matrix ID") = m_LootMatrixID;
destroyableInfo.PushDebug<AMFIntValue>("Min Coins") = m_MinCoins;
destroyableInfo.PushDebug<AMFIntValue>("Max Coins") = m_MaxCoins;
destroyableInfo.PushDebug<AMFStringValue>("Killer ID") = std::to_string(m_KillerID);
// "Scripts"; idk what to do about scripts yet
auto& immuneCounts = destroyableInfo.PushDebug("Immune Counts");
immuneCounts.PushDebug<AMFIntValue>("Basic Attack") = m_ImmuneToBasicAttackCount;
immuneCounts.PushDebug<AMFIntValue>("Damage Over Time") = m_ImmuneToDamageOverTimeCount;
immuneCounts.PushDebug<AMFIntValue>("Knockback") = m_ImmuneToKnockbackCount;
immuneCounts.PushDebug<AMFIntValue>("Interrupt") = m_ImmuneToInterruptCount;
immuneCounts.PushDebug<AMFIntValue>("Speed") = m_ImmuneToSpeedCount;
immuneCounts.PushDebug<AMFIntValue>("Imagination Gain") = m_ImmuneToImaginationGainCount;
immuneCounts.PushDebug<AMFIntValue>("Imagination Loss") = m_ImmuneToImaginationLossCount;
immuneCounts.PushDebug<AMFIntValue>("Quickbuild Interrupt") = m_ImmuneToQuickbuildInterruptCount;
immuneCounts.PushDebug<AMFIntValue>("Pull To Point") = m_ImmuneToPullToPointCount;
destroyableInfo.PushDebug<AMFIntValue>("Death Behavior") = m_DeathBehavior;
destroyableInfo.PushDebug<AMFDoubleValue>("Damage Cooldown Timer") = m_DamageCooldownTimer;
return true;
}
bool DestroyableComponent::OnSetFaction(GameMessages::GameMsg& msg) {
auto& modifyFaction = static_cast<GameMessages::SetFaction&>(msg);
m_DirtyHealth = true;
Game::entityManager->SerializeEntity(m_Parent);
SetFaction(modifyFaction.factionID, modifyFaction.bIgnoreChecks);
return true;
}

View File

@@ -9,6 +9,10 @@
#include "eReplicaComponentType.h"
#include "Implementation.h"
namespace GameMessages {
struct GetObjectReportInfo;
};
namespace CppScripts {
class Script;
}; //! namespace CppScripts
@@ -464,6 +468,9 @@ public:
// handle hardcode mode drops
void DoHardcoreModeDrops(const LWOOBJID source);
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
bool OnSetFaction(GameMessages::GameMsg& msg);
static Implementation<bool, const Entity*> IsEnemyImplentation;
static Implementation<bool, const Entity*> IsFriendImplentation;
@@ -591,7 +598,7 @@ private:
/**
* The ID of the entity that smashed this entity, if any
*/
LWOOBJID m_KillerID;
LWOOBJID m_KillerID{};
/**
* The list of callbacks that will be called when this entity gets hit

View File

@@ -1,7 +1,10 @@
#include "HavokVehiclePhysicsComponent.h"
#include "EntityManager.h"
#include "Amf3.h"
HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) {
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &HavokVehiclePhysicsComponent::OnGetObjectReportInfo);
m_Velocity = NiPoint3Constant::ZERO;
m_AngularVelocity = NiPoint3Constant::ZERO;
m_IsOnGround = true;
@@ -98,3 +101,34 @@ void HavokVehiclePhysicsComponent::Serialize(RakNet::BitStream& outBitStream, bo
outBitStream.Write0();
}
bool HavokVehiclePhysicsComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
PhysicsComponent::OnGetObjectReportInfo(msg);
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
if (!reportInfo.subCategory) {
return false;
}
auto& info = reportInfo.subCategory->PushDebug("Havok Vehicle Physics Info");
auto& velocity = info.PushDebug("Velocity");
velocity.PushDebug<AMFDoubleValue>("x") = m_Velocity.x;
velocity.PushDebug<AMFDoubleValue>("y") = m_Velocity.y;
velocity.PushDebug<AMFDoubleValue>("z") = m_Velocity.z;
auto& angularVelocity = info.PushDebug("Angular Velocity");
angularVelocity.PushDebug<AMFDoubleValue>("x") = m_AngularVelocity.x;
angularVelocity.PushDebug<AMFDoubleValue>("y") = m_AngularVelocity.y;
angularVelocity.PushDebug<AMFDoubleValue>("z") = m_AngularVelocity.z;
info.PushDebug<AMFBoolValue>("Is On Ground") = m_IsOnGround;
info.PushDebug<AMFBoolValue>("Is On Rail") = m_IsOnRail;
info.PushDebug<AMFIntValue>("End Behavior") = m_EndBehavior;
auto& remoteInputInfo = info.PushDebug("Remote Input Info");
remoteInputInfo.PushDebug<AMFDoubleValue>("Remote Input X") = m_RemoteInputInfo.m_RemoteInputX;
remoteInputInfo.PushDebug<AMFDoubleValue>("Remote Input Y") = m_RemoteInputInfo.m_RemoteInputY;
remoteInputInfo.PushDebug<AMFBoolValue>("Is Powersliding") = m_RemoteInputInfo.m_IsPowersliding;
remoteInputInfo.PushDebug<AMFBoolValue>("Is Modified") = m_RemoteInputInfo.m_IsModified;
return true;
}

View File

@@ -68,6 +68,8 @@ public:
void SetRemoteInputInfo(const RemoteInputInfo&);
private:
bool OnGetObjectReportInfo(GameMessages::GameMsg& msg);
NiPoint3 m_Velocity;
NiPoint3 m_AngularVelocity;

View File

@@ -1279,7 +1279,7 @@ void InventoryComponent::SpawnPet(Item* item) {
EntityInfo info{};
info.lot = item->GetLot();
info.pos = m_Parent->GetPosition();
info.rot = NiQuaternionConstant::IDENTITY;
info.rot = QuatUtils::IDENTITY;
info.spawnerID = m_Parent->GetObjectID();
auto* pet = Game::entityManager->CreateEntity(info);

View File

@@ -7,20 +7,23 @@
#include "BehaviorStates.h"
#include "ControlBehaviorMsgs.h"
#include "tinyxml2.h"
#include "InventoryComponent.h"
#include "SimplePhysicsComponent.h"
#include "eObjectBits.h"
#include "Database.h"
#include "DluAssert.h"
ModelComponent::ModelComponent(Entity* parent) : Component(parent) {
using namespace GameMessages;
m_OriginalPosition = m_Parent->GetDefaultPosition();
m_OriginalRotation = m_Parent->GetDefaultRotation();
m_IsPaused = false;
m_NumListeningInteract = 0;
m_userModelID = m_Parent->GetVarAs<LWOOBJID>(u"userModelID");
RegisterMsg(MessageType::Game::REQUEST_USE, this, &ModelComponent::OnRequestUse);
RegisterMsg(MessageType::Game::RESET_MODEL_TO_DEFAULTS, this, &ModelComponent::OnResetModelToDefaults);
RegisterMsg<RequestUse>(this, &ModelComponent::OnRequestUse);
RegisterMsg<ResetModelToDefaults>(this, &ModelComponent::OnResetModelToDefaults);
}
bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) {
@@ -30,10 +33,25 @@ bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) {
unsmash.target = GetParent()->GetObjectID();
unsmash.duration = 0.0f;
unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS);
m_Parent->SetPosition(m_OriginalPosition);
m_Parent->SetRotation(m_OriginalRotation);
m_Parent->SetVelocity(NiPoint3Constant::ZERO);
m_Speed = 3.0f;
m_NumListeningInteract = 0;
m_NumActiveUnSmash = 0;
m_NumActiveAttack = 0;
GameMessages::SetFaction set{};
set.target = m_Parent->GetObjectID();
set.factionID = -1; // Default faction for smashables
set.bIgnoreChecks = true; // Remove the attack faction
set.Send();
m_Dirty = true;
Game::entityManager->SerializeEntity(GetParent());
return true;
}
@@ -54,6 +72,12 @@ void ModelComponent::Update(float deltaTime) {
for (auto& behavior : m_Behaviors) {
behavior.Update(deltaTime, *this);
}
if (!m_RestartAtEndOfFrame) return;
GameMessages::ResetModelToDefaults reset{};
OnResetModelToDefaults(reset);
m_RestartAtEndOfFrame = false;
}
void ModelComponent::LoadBehaviors() {
@@ -61,25 +85,30 @@ void ModelComponent::LoadBehaviors() {
for (const auto& behavior : behaviors) {
if (behavior.empty()) continue;
const auto behaviorId = GeneralUtils::TryParse<int32_t>(behavior);
const auto behaviorId = GeneralUtils::TryParse<LWOOBJID>(behavior);
if (!behaviorId.has_value() || behaviorId.value() == 0) continue;
LOG_DEBUG("Loading behavior %d", behaviorId.value());
auto& inserted = m_Behaviors.emplace_back();
inserted.SetBehaviorId(*behaviorId);
// add behavior at the back
LoadBehavior(behaviorId.value(), m_Behaviors.size(), false);
}
}
const auto behaviorStr = Database::Get()->GetBehavior(behaviorId.value());
void ModelComponent::LoadBehavior(const LWOOBJID behaviorID, const size_t index, const bool isIndexed) {
LOG_DEBUG("Loading behavior %d", behaviorID);
auto& inserted = *m_Behaviors.emplace(m_Behaviors.begin() + index, PropertyBehavior(isIndexed));
inserted.SetBehaviorId(behaviorID);
tinyxml2::XMLDocument behaviorXml;
auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size());
LOG_DEBUG("Behavior %i %d: %s", res, behaviorId.value(), behaviorStr.c_str());
const auto behaviorStr = Database::Get()->GetBehavior(behaviorID);
const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior");
if (!behaviorRoot) {
LOG("Failed to load behavior %d due to missing behavior root", behaviorId.value());
continue;
}
tinyxml2::XMLDocument behaviorXml;
auto res = behaviorXml.Parse(behaviorStr.c_str(), behaviorStr.size());
LOG_DEBUG("Behavior %i %llu: %s", res, behaviorID, behaviorStr.c_str());
const auto* const behaviorRoot = behaviorXml.FirstChildElement("Behavior");
if (behaviorRoot) {
inserted.Deserialize(*behaviorRoot);
} else {
LOG("Failed to load behavior %d due to missing behavior root", behaviorID);
}
}
@@ -110,8 +139,12 @@ void ModelComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialU
if (bIsInitialUpdate) outBitStream.Write0(); // We are not writing model editing info
}
void ModelComponent::UpdatePendingBehaviorId(const int32_t newId) {
for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == -1) behavior.SetBehaviorId(newId);
void ModelComponent::UpdatePendingBehaviorId(const LWOOBJID newId, const LWOOBJID oldId) {
for (auto& behavior : m_Behaviors) {
if (behavior.GetBehaviorId() != oldId) continue;
behavior.SetBehaviorId(newId);
behavior.SetIsLoot(false);
}
}
void ModelComponent::SendBehaviorListToClient(AMFArrayValue& args) const {
@@ -128,7 +161,7 @@ void ModelComponent::VerifyBehaviors() {
for (auto& behavior : m_Behaviors) behavior.VerifyLastEditedState();
}
void ModelComponent::SendBehaviorBlocksToClient(int32_t behaviorToSend, AMFArrayValue& args) const {
void ModelComponent::SendBehaviorBlocksToClient(const LWOOBJID behaviorToSend, AMFArrayValue& args) const {
args.Insert("BehaviorID", std::to_string(behaviorToSend));
args.Insert("objectID", std::to_string(m_Parent->GetObjectID()));
for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == behaviorToSend) behavior.SendBehaviorBlocksToClient(args);
@@ -137,8 +170,21 @@ void ModelComponent::SendBehaviorBlocksToClient(int32_t behaviorToSend, AMFArray
void ModelComponent::AddBehavior(AddMessage& msg) {
// Can only have 1 of the loot behaviors
for (auto& behavior : m_Behaviors) if (behavior.GetBehaviorId() == msg.GetBehaviorId()) return;
m_Behaviors.insert(m_Behaviors.begin() + msg.GetBehaviorIndex(), PropertyBehavior());
m_Behaviors.at(msg.GetBehaviorIndex()).HandleMsg(msg);
// If we're loading a behavior from an ADD, it is from the database.
// Mark it as not modified by default to prevent wasting persistentIDs.
LoadBehavior(msg.GetBehaviorId(), msg.GetBehaviorIndex(), true);
auto& insertedBehavior = m_Behaviors[msg.GetBehaviorIndex()];
auto* const playerEntity = Game::entityManager->GetEntity(msg.GetOwningPlayerID());
if (playerEntity) {
auto* inventoryComponent = playerEntity->GetComponent<InventoryComponent>();
if (inventoryComponent) {
// Check if this behavior is able to be found via lot (if so, its a loot behavior).
insertedBehavior.SetIsLoot(inventoryComponent->FindItemByLot(msg.GetBehaviorId(), eInventoryType::BEHAVIORS));
}
}
auto* const simplePhysComponent = m_Parent->GetComponent<SimplePhysicsComponent>();
if (simplePhysComponent) {
simplePhysComponent->SetPhysicsMotionState(1);
@@ -146,8 +192,41 @@ void ModelComponent::AddBehavior(AddMessage& msg) {
}
}
void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) {
std::string ModelComponent::SaveBehavior(const PropertyBehavior& behavior) const {
tinyxml2::XMLDocument doc;
auto* root = doc.NewElement("Behavior");
behavior.Serialize(*root);
doc.InsertFirstChild(root);
tinyxml2::XMLPrinter printer(0, true, 0);
doc.Print(&printer);
return printer.CStr();
}
void ModelComponent::RemoveBehavior(MoveToInventoryMessage& msg, const bool keepItem) {
if (msg.GetBehaviorIndex() >= m_Behaviors.size() || m_Behaviors.at(msg.GetBehaviorIndex()).GetBehaviorId() != msg.GetBehaviorId()) return;
const auto behavior = m_Behaviors[msg.GetBehaviorIndex()];
if (keepItem) {
auto* const playerEntity = Game::entityManager->GetEntity(msg.GetOwningPlayerID());
if (playerEntity) {
auto* const inventoryComponent = playerEntity->GetComponent<InventoryComponent>();
if (inventoryComponent && !behavior.GetIsLoot()) {
// config is owned by the item
std::vector<LDFBaseData*> config;
config.push_back(new LDFData<std::string>(u"userModelName", behavior.GetName()));
inventoryComponent->AddItem(7965, 1, eLootSourceType::PROPERTY, eInventoryType::BEHAVIORS, config, LWOOBJID_EMPTY, true, false, msg.GetBehaviorId());
}
}
}
// save the behavior before deleting it so players can re-add them
IBehaviors::Info info{};
info.behaviorId = msg.GetBehaviorId();
info.behaviorInfo = SaveBehavior(behavior);
info.characterId = msg.GetOwningPlayerID();
Database::Get()->AddBehavior(info);
m_Behaviors.erase(m_Behaviors.begin() + msg.GetBehaviorIndex());
// TODO move to the inventory
if (m_Behaviors.empty()) {
@@ -159,22 +238,14 @@ void ModelComponent::MoveToInventory(MoveToInventoryMessage& msg) {
}
}
std::array<std::pair<int32_t, std::string>, 5> ModelComponent::GetBehaviorsForSave() const {
std::array<std::pair<int32_t, std::string>, 5> toReturn{};
std::array<std::pair<LWOOBJID, std::string>, 5> ModelComponent::GetBehaviorsForSave() const {
std::array<std::pair<LWOOBJID, std::string>, 5> toReturn{};
for (auto i = 0; i < m_Behaviors.size(); i++) {
const auto& behavior = m_Behaviors.at(i);
if (behavior.GetBehaviorId() == -1) continue;
auto& [id, behaviorData] = toReturn[i];
id = behavior.GetBehaviorId();
tinyxml2::XMLDocument doc;
auto* root = doc.NewElement("Behavior");
behavior.Serialize(*root);
doc.InsertFirstChild(root);
tinyxml2::XMLPrinter printer(0, true, 0);
doc.Print(&printer);
behaviorData = printer.CStr();
behaviorData = SaveBehavior(behavior);
}
return toReturn;
}
@@ -203,3 +274,67 @@ void ModelComponent::RemoveUnSmash() {
LOG_DEBUG("Removing UnSmash %i", m_NumActiveUnSmash);
m_NumActiveUnSmash--;
}
bool ModelComponent::TrySetVelocity(const NiPoint3& velocity) const {
auto currentVelocity = m_Parent->GetVelocity();
// If we're currently moving on an axis, prevent the move so only 1 behavior can have control over an axis
if (velocity != NiPoint3Constant::ZERO) {
const auto [x, y, z] = velocity * m_Speed;
if (x != 0.0f) {
if (currentVelocity.x != 0.0f) return false;
currentVelocity.x = x;
} else if (y != 0.0f) {
if (currentVelocity.y != 0.0f) return false;
currentVelocity.y = y;
} else if (z != 0.0f) {
if (currentVelocity.z != 0.0f) return false;
currentVelocity.z = z;
}
} else {
currentVelocity = velocity;
}
m_Parent->SetVelocity(currentVelocity);
return true;
}
void ModelComponent::SetVelocity(const NiPoint3& velocity) const {
m_Parent->SetVelocity(velocity);
}
void ModelComponent::OnChatMessageReceived(const std::string& sMessage) {
for (auto& behavior : m_Behaviors) behavior.OnChatMessageReceived(sMessage);
}
void ModelComponent::OnHit() {
for (auto& behavior : m_Behaviors) {
behavior.OnHit();
}
}
void ModelComponent::AddAttack() {
LOG_DEBUG("Adding attack %i", m_NumActiveAttack);
m_Dirty = true;
if (m_NumActiveAttack == 0) {
GameMessages::SetFaction set{};
set.target = m_Parent->GetObjectID();
set.factionID = 6; // Default faction for smashables
set.Send();
}
m_NumActiveAttack++;
}
void ModelComponent::RemoveAttack() {
LOG_DEBUG("Removing attack %i", m_NumActiveAttack);
DluAssert(m_NumActiveAttack > 0);
m_Dirty = true;
m_NumActiveAttack--;
if (m_NumActiveAttack == 0) {
GameMessages::SetFaction set{};
set.target = m_Parent->GetObjectID();
set.factionID = -1; // Default faction for smashables
set.bIgnoreChecks = true; // Remove the attack faction
set.Send();
}
}

View File

@@ -41,7 +41,7 @@ public:
* Returns the original position of the model
* @return the original position of the model
*/
const NiPoint3& GetPosition() { return m_OriginalPosition; }
const NiPoint3& GetOriginalPosition() { return m_OriginalPosition; }
/**
* Sets the original position of the model
@@ -53,7 +53,7 @@ public:
* Returns the original rotation of the model
* @return the original rotation of the model
*/
const NiQuaternion& GetRotation() { return m_OriginalRotation; }
const NiQuaternion& GetOriginalRotation() { return m_OriginalRotation; }
/**
* Sets the original rotation of the model
@@ -66,15 +66,18 @@ public:
*
* @tparam Msg The message type to pass
* @param args the arguments of the message to be deserialized
*
* @return returns true if a new behaviorID is needed.
*/
template<typename Msg>
void HandleControlBehaviorsMsg(const AMFArrayValue& args) {
bool HandleControlBehaviorsMsg(const AMFArrayValue& args) {
static_assert(std::is_base_of_v<BehaviorMessageBase, Msg>, "Msg must be a BehaviorMessageBase");
Msg msg{ args };
for (auto&& behavior : m_Behaviors) {
if (behavior.GetBehaviorId() == msg.GetBehaviorId()) {
behavior.CheckModifyState(msg);
behavior.HandleMsg(msg);
return;
return msg.GetNeedsNewBehaviorID();
}
}
@@ -82,22 +85,24 @@ public:
if (m_Behaviors.size() > 5) m_Behaviors.resize(5);
// Do not allow more than 5 to be added. The client UI will break if you do!
if (m_Behaviors.size() == 5) return;
if (m_Behaviors.size() == 5) return false;
auto newBehavior = m_Behaviors.insert(m_Behaviors.begin(), PropertyBehavior());
// Generally if we are inserting a new behavior, it is because the client is creating a new behavior.
// However if we are testing behaviors the behavior will not exist on the initial pass, so we set the ID here to that of the msg.
// This will either set the ID to -1 (no change in the current default) or set the ID to the ID of the behavior we are testing.
newBehavior->SetBehaviorId(msg.GetBehaviorId());
newBehavior->CheckModifyState(msg);
newBehavior->HandleMsg(msg);
return msg.GetNeedsNewBehaviorID();
};
void AddBehavior(AddMessage& msg);
void MoveToInventory(MoveToInventoryMessage& msg);
void RemoveBehavior(MoveToInventoryMessage& msg, const bool keepItem);
// Updates the pending behavior ID to the new ID.
void UpdatePendingBehaviorId(const int32_t newId);
void UpdatePendingBehaviorId(const LWOOBJID newId, const LWOOBJID oldId);
// Sends the behavior list to the client.
@@ -112,11 +117,11 @@ public:
*/
void SendBehaviorListToClient(AMFArrayValue& args) const;
void SendBehaviorBlocksToClient(int32_t behaviorToSend, AMFArrayValue& args) const;
void SendBehaviorBlocksToClient(const LWOOBJID behaviorToSend, AMFArrayValue& args) const;
void VerifyBehaviors();
std::array<std::pair<int32_t, std::string>, 5> GetBehaviorsForSave() const;
std::array<std::pair<LWOOBJID, std::string>, 5> GetBehaviorsForSave() const;
const std::vector<PropertyBehavior>& GetBehaviors() const { return m_Behaviors; };
@@ -130,7 +135,40 @@ public:
bool IsUnSmashing() const { return m_NumActiveUnSmash != 0; }
void Resume();
// Attempts to set the velocity of an axis for movement.
// If the axis currently has a velocity of zero, returns true.
// If the axis is currently controlled by a behavior, returns false.
bool TrySetVelocity(const NiPoint3& velocity) const;
// Force sets the velocity to a value.
void SetVelocity(const NiPoint3& velocity) const;
void OnChatMessageReceived(const std::string& sMessage);
void OnHit();
// Sets the speed of the model
void SetSpeed(const float newSpeed) { m_Speed = newSpeed; }
// Whether or not to restart at the end of the frame
void RestartAtEndOfFrame() { m_RestartAtEndOfFrame = true; }
// Increments the number of strips listening for an attack.
// If this is the first strip adding an attack, it will set the factions to the correct values.
void AddAttack();
// Decrements the number of strips listening for an attack.
// If this is the last strip removing an attack, it will reset the factions to the default of -1.
void RemoveAttack();
private:
// Loads a behavior from the database.
void LoadBehavior(const LWOOBJID behaviorID, const size_t index, const bool isIndexed);
// Writes a behavior to a string so it can be saved.
std::string SaveBehavior(const PropertyBehavior& behavior) const;
// Number of Actions that are awaiting an UnSmash to finish.
uint32_t m_NumActiveUnSmash{};
@@ -140,6 +178,9 @@ private:
// The number of strips listening for a RequestUse GM to come in.
uint32_t m_NumListeningInteract{};
// The number of strips listening for an attack.
uint32_t m_NumActiveAttack{};
// Whether or not the model is paused and should reject all interactions regarding behaviors.
bool m_IsPaused{};
/**
@@ -157,10 +198,16 @@ private:
/**
* The rotation original of the model
*/
NiQuaternion m_OriginalRotation;
NiQuaternion m_OriginalRotation = QuatUtils::IDENTITY;
/**
* The ID of the user that made the model
*/
LWOOBJID m_userModelID;
// The speed at which this model moves
float m_Speed{ 3.0f };
// Whether or not to restart at the end of the frame.
bool m_RestartAtEndOfFrame{ false };
};

View File

@@ -14,6 +14,7 @@
#include "dZoneManager.h"
#include "CDComponentsRegistryTable.h"
#include "QuickBuildComponent.h"
#include "CDPhysicsComponentTable.h"
#include "dNavMesh.h"
@@ -54,6 +55,7 @@ MovementAIComponent::MovementAIComponent(Entity* parent, MovementAIInfo info) :
m_SourcePosition = m_Parent->GetPosition();
m_Paused = false;
m_SavedVelocity = NiPoint3Constant::ZERO;
m_IsBounced = false;
if (!m_Parent->GetComponent<BaseCombatAIComponent>()) SetPath(m_Parent->GetVarAsString(u"attached_path"));
}
@@ -81,13 +83,16 @@ void MovementAIComponent::Resume() {
m_Paused = false;
SetVelocity(m_SavedVelocity);
m_SavedVelocity = NiPoint3Constant::ZERO;
SetRotation(NiQuaternion::LookAt(m_Parent->GetPosition(), m_NextWaypoint));
SetRotation(QuatUtils::LookAt(m_Parent->GetPosition(), m_NextWaypoint));
Game::entityManager->SerializeEntity(m_Parent);
}
void MovementAIComponent::Update(const float deltaTime) {
if (m_Paused) return;
auto* const quickBuildComponent = m_Parent->GetComponent<QuickBuildComponent>();
if (quickBuildComponent && quickBuildComponent->GetState() != eQuickBuildState::COMPLETED) return;
if (m_PullingToPoint) {
const auto source = GetCurrentWaypoint();
@@ -149,30 +154,36 @@ void MovementAIComponent::Update(const float deltaTime) {
m_TimeTravelled = 0.0f;
m_TimeToTravel = length / speed;
SetRotation(NiQuaternion::LookAt(source, m_NextWaypoint));
SetRotation(QuatUtils::LookAt(source, m_NextWaypoint));
}
} else {
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1;
if (m_CurrentPath.empty()) {
if (m_Path) {
if (m_Path->pathBehavior == PathBehavior::Loop) {
SetPath(m_Path->pathWaypoints);
} else if (m_Path->pathBehavior == PathBehavior::Bounce) {
m_IsBounced = !m_IsBounced;
std::vector<PathWaypoint> waypoints = m_Path->pathWaypoints;
std::reverse(waypoints.begin(), waypoints.end());
if (m_IsBounced) std::ranges::reverse(waypoints);
SetPath(waypoints);
} else if (m_Path->pathBehavior == PathBehavior::Once) {
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
Stop();
return;
}
} else {
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
Stop();
return;
}
}
SetDestination(m_CurrentPath.top().position);
} else {
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
SetDestination(m_CurrentPath.top().position);
m_CurrentPath.pop();
m_CurrentPath.pop();
}
}
Game::entityManager->SerializeEntity(m_Parent);
@@ -248,6 +259,7 @@ void MovementAIComponent::Stop() {
m_InterpolatedWaypoints.clear();
while (!m_CurrentPath.empty()) m_CurrentPath.pop();
m_CurrentPathWaypointCount = 0;
m_PathIndex = 0;
@@ -270,6 +282,7 @@ void MovementAIComponent::SetPath(std::vector<PathWaypoint> path) {
this->m_CurrentPath.push(point);
});
m_CurrentPathWaypointCount = path.size();
SetDestination(path.front().position);
}

View File

@@ -209,6 +209,8 @@ public:
*/
static float GetBaseSpeed(LOT lot);
bool IsPaused() const { return m_Paused; }
private:
/**
@@ -321,6 +323,11 @@ private:
bool m_Paused;
NiPoint3 m_SavedVelocity;
bool m_IsBounced{};
// The number of waypoints that were on the path in the call to SetPath
uint32_t m_CurrentPathWaypointCount{ 0 };
};
#endif // MOVEMENTAICOMPONENT_H

View File

@@ -42,35 +42,6 @@ std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::activePets{};
* Maps all the pet lots to a flag indicating that the player has caught it. All basic pets have been guessed by ObjID
* while the faction ones could be checked using their respective missions.
*/
const std::map<LOT, int32_t> PetComponent::petFlags{
{ 3050, 801 }, // Elephant
{ 3054, 803 }, // Cat
{ 3195, 806 }, // Triceratops
{ 3254, 807 }, // Terrier
{ 3261, 811 }, // Skunk
{ 3672, 813 }, // Bunny
{ 3994, 814 }, // Crocodile
{ 5635, 815 }, // Doberman
{ 5636, 816 }, // Buffalo
{ 5637, 818 }, // Robot Dog
{ 5639, 819 }, // Red Dragon
{ 5640, 820 }, // Tortoise
{ 5641, 821 }, // Green Dragon
{ 5643, 822 }, // Panda, see mission 786
{ 5642, 823 }, // Mantis
{ 6720, 824 }, // Warthog
{ 3520, 825 }, // Lion, see mission 1318
{ 7638, 826 }, // Goat
{ 7694, 827 }, // Crab
{ 12294, 829 }, // Reindeer
{ 12431, 830 }, // Stegosaurus, see mission 1386
{ 12432, 831 }, // Saber cat, see mission 1389
{ 12433, 832 }, // Gryphon, see mission 1392
{ 12434, 833 }, // Alien, see mission 1188
// 834: unknown?, see mission 506, 688
{ 16210, 836 }, // Ninjago Earth Dragon, see mission 1836
{ 13067, 838 }, // Skeleton dragon
};
PetComponent::PetComponent(Entity* parentEntity, uint32_t componentId) : Component{ parentEntity } {
m_PetInfo = CDClientManager::GetTable<CDPetComponentTable>()->GetByID(componentId); // TODO: Make reference when safe
@@ -197,7 +168,7 @@ void PetComponent::OnUse(Entity* originator) {
const auto originatorPosition = originator->GetPosition();
m_Parent->SetRotation(NiQuaternion::LookAt(petPosition, originatorPosition));
m_Parent->SetRotation(QuatUtils::LookAt(petPosition, originatorPosition));
float interactionDistance = m_Parent->GetVar<float>(u"interaction_distance");
if (interactionDistance <= 0) {
@@ -206,7 +177,7 @@ void PetComponent::OnUse(Entity* originator) {
auto position = originatorPosition;
NiPoint3 forward = NiQuaternion::LookAt(m_Parent->GetPosition(), originator->GetPosition()).GetForwardVector();
NiPoint3 forward = QuatUtils::Forward(QuatUtils::LookAt(m_Parent->GetPosition(), originator->GetPosition()));
forward.y = 0;
if (dpWorld::IsLoaded()) {
@@ -215,7 +186,7 @@ void PetComponent::OnUse(Entity* originator) {
NiPoint3 nearestPoint = dpWorld::GetNavMesh()->NearestPoint(attempt);
while (std::abs(nearestPoint.y - petPosition.y) > 4 && interactionDistance > 10) {
const NiPoint3 forward = m_Parent->GetRotation().GetForwardVector();
const NiPoint3 forward = QuatUtils::Forward(m_Parent->GetRotation());
attempt = originatorPosition + forward * interactionDistance;
@@ -229,7 +200,7 @@ void PetComponent::OnUse(Entity* originator) {
position = petPosition + forward * interactionDistance;
}
auto rotation = NiQuaternion::LookAt(position, petPosition);
auto rotation = QuatUtils::LookAt(position, petPosition);
GameMessages::SendNotifyPetTamingMinigame(
originator->GetObjectID(),
@@ -489,7 +460,7 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
EntityInfo info{};
info.lot = entry->puzzleModelLot;
info.pos = position;
info.rot = NiQuaternionConstant::IDENTITY;
info.rot = QuatUtils::IDENTITY;
info.spawnerID = tamer->GetObjectID();
auto* modelEntity = Game::entityManager->CreateEntity(info);
@@ -551,14 +522,13 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
ePetTamingNotifyType::NAMINGPET,
NiPoint3Constant::ZERO,
NiPoint3Constant::ZERO,
NiQuaternionConstant::IDENTITY,
QuatUtils::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);
// Triggers the catch a pet missions
if (petFlags.find(m_Parent->GetLOT()) != petFlags.end()) {
tamer->GetCharacter()->SetPlayerFlag(petFlags.at(m_Parent->GetLOT()), true);
}
constexpr auto PET_FLAG_BASE = 800;
tamer->GetCharacter()->SetPlayerFlag(PET_FLAG_BASE + m_ComponentId, true);
auto* missionComponent = tamer->GetComponent<MissionComponent>();
@@ -631,7 +601,7 @@ void PetComponent::RequestSetPetName(std::u16string name) {
ePetTamingNotifyType::SUCCESS,
NiPoint3Constant::ZERO,
NiPoint3Constant::ZERO,
NiQuaternionConstant::IDENTITY,
QuatUtils::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);
@@ -675,7 +645,7 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) {
ePetTamingNotifyType::QUIT,
NiPoint3Constant::ZERO,
NiPoint3Constant::ZERO,
NiQuaternionConstant::IDENTITY,
QuatUtils::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);
@@ -726,7 +696,7 @@ void PetComponent::ClientFailTamingMinigame() {
ePetTamingNotifyType::FAILED,
NiPoint3Constant::ZERO,
NiPoint3Constant::ZERO,
NiQuaternionConstant::IDENTITY,
QuatUtils::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);
@@ -942,6 +912,11 @@ void PetComponent::Command(const NiPoint3& position, const LWOOBJID source, cons
if (commandType == 1) {
// Emotes
GameMessages::SendPlayEmote(m_Parent->GetObjectID(), typeId, owner->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::EmotePlayed msg;
msg.target = owner->GetObjectID();
msg.emoteID = typeId;
msg.targetID = 0; // Or set to the intended target entity's ID, or 0 if no target
msg.Send(UNASSIGNED_SYSTEM_ADDRESS);
} else if (commandType == 3) {
// Follow me, ???
} else if (commandType == 6) {

View File

@@ -250,11 +250,6 @@ private:
*/
static std::unordered_map<LWOOBJID, LWOOBJID> currentActivities;
/**
* Flags that indicate that a player has tamed a pet, indexed by the LOT of the pet
*/
static const std::map<LOT, int32_t> petFlags;
/**
* The ID of the component in the pet component table
*/

View File

@@ -21,6 +21,7 @@
#include "CDPhysicsComponentTable.h"
#include "dServer.h"
#include "EntityInfo.h"
#include "Amf3.h"
#include "dpWorld.h"
#include "dpEntity.h"
@@ -28,6 +29,8 @@
#include "dpShapeSphere.h"
PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, int32_t componentId) : PhysicsComponent(parent, componentId) {
RegisterMsg(MessageType::Game::GET_OBJECT_REPORT_INFO, this, &PhantomPhysicsComponent::OnGetObjectReportInfo);
m_Position = m_Parent->GetDefaultPosition();
m_Rotation = m_Parent->GetDefaultRotation();
m_Scale = m_Parent->GetDefaultScale();
@@ -238,3 +241,43 @@ void PhantomPhysicsComponent::SetRotation(const NiQuaternion& rot) {
PhysicsComponent::SetRotation(rot);
if (m_dpEntity) m_dpEntity->SetRotation(rot);
}
bool PhantomPhysicsComponent::OnGetObjectReportInfo(GameMessages::GameMsg& msg) {
PhysicsComponent::OnGetObjectReportInfo(msg);
auto& reportInfo = static_cast<GameMessages::GetObjectReportInfo&>(msg);
if (!reportInfo.subCategory) {
return false;
}
auto& info = reportInfo.subCategory->PushDebug("Phantom Physics Info");
info.PushDebug<AMFDoubleValue>("Scale") = m_Scale;
info.PushDebug<AMFBoolValue>("Is Physics Effect Active") = m_IsPhysicsEffectActive;
info.PushDebug<AMFIntValue>("Effect Type") = static_cast<int>(m_EffectType);
info.PushDebug<AMFDoubleValue>("Directional Multiplier") = m_DirectionalMultiplier;
info.PushDebug<AMFBoolValue>("Is Directional") = m_IsDirectional;
auto& direction = info.PushDebug("Direction");
direction.PushDebug<AMFDoubleValue>("x") = m_Direction.x;
direction.PushDebug<AMFDoubleValue>("y") = m_Direction.y;
direction.PushDebug<AMFDoubleValue>("z") = m_Direction.z;
if (m_MinMax) {
auto& minMaxInfo = info.PushDebug("Min Max Info");
minMaxInfo.PushDebug<AMFIntValue>("Min") = m_Min;
minMaxInfo.PushDebug<AMFIntValue>("Max") = m_Max;
}
if (m_IsRespawnVolume) {
auto& respawnInfo = info.PushDebug("Respawn Info");
respawnInfo.PushDebug<AMFBoolValue>("Is Respawn Volume") = m_IsRespawnVolume;
auto& respawnPos = respawnInfo.PushDebug("Respawn Position");
respawnPos.PushDebug<AMFDoubleValue>("x") = m_RespawnPos.x;
respawnPos.PushDebug<AMFDoubleValue>("y") = m_RespawnPos.y;
respawnPos.PushDebug<AMFDoubleValue>("z") = m_RespawnPos.z;
auto& respawnRot = respawnInfo.PushDebug("Respawn Rotation");
respawnRot.PushDebug<AMFDoubleValue>("w") = m_RespawnRot.w;
respawnRot.PushDebug<AMFDoubleValue>("x") = m_RespawnRot.x;
respawnRot.PushDebug<AMFDoubleValue>("y") = m_RespawnRot.y;
respawnRot.PushDebug<AMFDoubleValue>("z") = m_RespawnRot.z;
}
return true;
}

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