Compare commits

..

6 Commits

Author SHA1 Message Date
David Markowitz
b8ba79bb7b Fix FetchContent_Declare speed 2025-09-11 22:47:15 -07: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
88 changed files with 429 additions and 582 deletions

View File

@@ -1,154 +0,0 @@
# Darkflame Universe Server Development Instructions
Darkflame Universe (DLU) is a LEGO Universe server emulator written in C++ with a multi-server architecture (AuthServer, ChatServer, MasterServer, WorldServer). Always reference these instructions first and fallback to search or bash commands only when you encounter unexpected information that does not match the info here.
## Working Effectively
### Bootstrap, Build, and Test - REQUIRED STEPS
Execute these commands in order for ANY development work. NEVER CANCEL builds - they take time but work reliably:
```bash
# 1. Install system dependencies (Ubuntu/Debian)
sudo apt update && sudo apt install -y build-essential gcc zlib1g-dev libssl-dev openssl mariadb-server cmake
# 2. Initialize git submodules (CRITICAL - project won't build without this)
git submodule update --init --recursive
# 3. Build using the provided script
./build.sh -j2
```
- **Build time: ~6 minutes. NEVER CANCEL. Set timeout to 720+ seconds (12+ minutes).**
- **Uses CMake 3.25-3.31 (confirmed working with 3.31.6)**
- **Requires g++11+ (confirmed working with g++ 13.3.0)**
### Alternative Build Methods
```bash
# Using CMake presets (CI-style)
cmake --workflow --preset ci-ubuntu-22.04
# Manual CMake (for custom configurations)
mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE="Release" ..
cmake --build . --config Release -j2
```
- **Same timing: ~6 minutes. NEVER CANCEL. Set timeout to 720+ seconds.**
### Run Tests
```bash
cd build
ctest --output-on-failure
```
- **Test time: <4 seconds. Set timeout to 30+ seconds.**
- **91 tests run, all should pass**
- **Tests are built automatically when ENABLE_TESTING=1 in CMakeVariables.txt**
### Database Setup (for runtime testing)
```bash
# Start MariaDB
sudo systemctl start mysql
# Create test database and user
sudo mysql -e "CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'testpass'; GRANT ALL ON *.* TO 'testuser'@'localhost' WITH GRANT OPTION; FLUSH PRIVILEGES; CREATE DATABASE testdarkflame;"
```
## Validation
### Build Validation
- **ALWAYS run the bootstrapping steps first** before making any code changes
- **ALWAYS build and test your changes** before considering them complete
- Build output should include all server binaries: AuthServer, ChatServer, MasterServer, WorldServer
- Build directory contains required files: `*.ini` configs, `navmeshes/`, `migrations/`, `vanity/`, `blocklist.dcf`, `libmariadbcpp.so`
### Runtime Validation
The servers can be started for basic validation:
```bash
cd build
./MasterServer
```
- **Server will start but complain about missing client files (this is expected)**
- **Database connections work with proper configuration in sharedconfig.ini**
- **For full server testing, LEGO Universe client files are required (not available in this repository)**
### Code Validation
**ALWAYS validate your changes by**:
1. Building successfully with no new compilation errors
2. Running the test suite and confirming all tests pass
3. Starting MasterServer to verify basic functionality
4. **Use .editorconfig** - code style uses tabs (width=4), Unix line endings, trailing whitespace removal
## Common Tasks
### Project Structure
```
/home/runner/work/DarkflameServer/DarkflameServer/
├── dAuthServer/ # Authentication server code
├── dChatServer/ # Chat server code
├── dMasterServer/ # Master server (main coordinator)
├── dWorldServer/ # World/game server code
├── dCommon/ # Shared common utilities
├── dDatabase/ # Database abstraction layer
├── dGame/ # Core game logic, components, behaviors
├── dScripts/ # Game scripts (NPCs, quests, etc.)
├── dNet/ # Network utilities
├── dPhysics/ # Physics integration
├── tests/ # Unit tests (GoogleTest)
├── migrations/ # Database schema migrations
├── thirdparty/ # External dependencies
├── build.sh # Main build script
├── CMakeVariables.txt # Build configuration variables
└── CMakePresets.json # CMake preset configurations
```
### Key Files to Know
- **CMakeVariables.txt**: Build configuration (testing enabled, MariaDB jobs, etc.)
- **build/sharedconfig.ini**: Database connection, client location, server settings
- **build/masterconfig.ini**: Master server port and startup configuration
- **CONTRIBUTING.md**: Code style guidelines and commit message format
- **docs/Commands.md**: Complete list of in-game server commands
### Build Configuration
Located in `CMakeVariables.txt`:
- `ENABLE_TESTING=1` - Unit tests enabled (keep enabled)
- `MARIADB_CONNECTOR_COMPILE_JOBS=1` - Parallel compilation jobs for MariaDB connector
- `CDCLIENT_CACHE_ALL=0` - Database caching strategy
### Common Commands Reference
```bash
# Build from clean state
rm -rf build && ./build.sh -j2
# Run specific test
cd build && ctest -R "TestName" --output-on-failure
# Check which servers were built
cd build && ls -la *Server
# View build configuration
cat CMakeVariables.txt
# Check git submodules status
git submodule status
```
### Important Notes
- **Client files are NOT included** - this is only the server emulator
- **Database can use SQLite or MariaDB** - SQLite recommended for development since it's lighter and doesn't require an external service
- **Multi-server architecture** requires all 4 servers to run a complete setup
- **Network ports**: Auth (1001), Chat (2005), Master (2000), World (3000+)
- **Development uses Debug builds**, production uses Release builds
- **GM level 0** = normal player, **GM level 8-9** = admin privileges
## Troubleshooting
- **"Asset bundle not found"**: Expected without LEGO Universe client files
- **"Submodule errors"**: Run `git submodule update --init --recursive`
- **"CMake version errors"**: Requires CMake 3.25-3.31
- **"MariaDB connection errors"**: Check database setup and sharedconfig.ini
- **"Permission denied on port"**: Run `sudo setcap 'cap_net_bind_service=+ep' AuthServer` for ports <1024
### CI Information
- **GitHub Actions** runs builds on Windows, Ubuntu, and macOS
- **Build matrix** tests multiple configurations via CMake presets
- **All tests must pass** for CI to succeed
- **Build artifacts** are automatically generated and uploaded
**Remember: This is a complex game server requiring LEGO Universe client files for full functionality, but the server has the capability to mock everything that's needed to test without the client since cdclient can be mocked and the database can be mocked as well.**

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)
@@ -306,7 +307,7 @@ 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

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

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

@@ -14,6 +14,6 @@ add_compile_definitions(ChatServer PRIVATE PROJECT_VERSION="\"${PROJECT_VERSION}
add_library(dChatServer ${DCHATSERVER_SOURCES})
target_include_directories(dChatServer PRIVATE "${PROJECT_SOURCE_DIR}/dServer" "${PROJECT_SOURCE_DIR}/dChatFilter")
target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter)
target_link_libraries(dChatServer ${COMMON_LIBRARIES} dChatFilter glm::glm)
target_link_libraries(ChatServer ${COMMON_LIBRARIES} dChatFilter dChatServer dServer mongoose dWeb)

View File

@@ -54,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.
@@ -74,5 +76,6 @@ else ()
endif ()
target_link_libraries(dCommon
PUBLIC glm::glm
PRIVATE ZLIB::ZLIB bcrypt tinyxml2
INTERFACE dDatabase)

View File

@@ -20,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;
@@ -244,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;
@@ -252,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;
}
/**
@@ -261,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>

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

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

@@ -13,7 +13,7 @@ public:
}
NiPoint3 position;
NiQuaternion rotation;
NiQuaternion rotation = QuatUtils::IDENTITY;
LWOOBJID id{};
LOT lot{};
uint32_t ugcId{};

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

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

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

View File

@@ -304,7 +304,7 @@ void Entity::Initialize() {
//If we came from another zone, put us in the starting loc
if (m_Character->GetZoneID() != Game::server->GetZoneID() || mapID == 1603) { // Exception for Moon Base as you tend to spawn on the roof.
NiPoint3 pos;
NiQuaternion rot;
NiQuaternion rot = QuatUtils::IDENTITY;
const auto& targetSceneName = m_Character->GetTargetScene();
auto* targetScene = Game::entityManager->GetSpawnPointEntity(targetSceneName);
@@ -1882,7 +1882,7 @@ const NiQuaternion& Entity::GetRotation() const {
return rigidBodyPhantomPhysicsComponent->GetRotation();
}
return NiQuaternionConstant::IDENTITY;
return QuatUtils::IDENTITY;
}
void Entity::SetPosition(const NiPoint3& position) {
@@ -2178,7 +2178,7 @@ const NiPoint3& Entity::GetRespawnPosition() const {
const NiQuaternion& Entity::GetRespawnRotation() const {
auto* characterComponent = GetComponent<CharacterComponent>();
return characterComponent ? characterComponent->GetRespawnRotation() : NiQuaternionConstant::IDENTITY;
return characterComponent ? characterComponent->GetRespawnRotation() : QuatUtils::IDENTITY;
}
void Entity::SetRespawnPos(const NiPoint3& position) const {

View File

@@ -357,7 +357,7 @@ private:
std::vector<LDFBaseData*> m_NetworkSettings;
NiPoint3 m_DefaultPosition;
NiQuaternion m_DefaultRotation;
NiQuaternion m_DefaultRotation = QuatUtils::IDENTITY;
float m_Scale;
Spawner* m_Spawner;

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

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

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

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

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

@@ -622,7 +622,7 @@ private:
NiPoint3 m_respawnPos;
NiQuaternion m_respawnRot;
NiQuaternion m_respawnRot = QuatUtils::IDENTITY;
std::map<LWOOBJID, Loot::Info> m_DroppedLoot;

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

@@ -198,7 +198,7 @@ private:
/**
* The rotation original of the model
*/
NiQuaternion m_OriginalRotation;
NiQuaternion m_OriginalRotation = QuatUtils::IDENTITY;
/**
* The ID of the user that made the model

View File

@@ -83,7 +83,7 @@ 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);
}
@@ -154,7 +154,7 @@ 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

View File

@@ -168,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) {
@@ -177,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()) {
@@ -186,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;
@@ -200,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(),
@@ -460,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);
@@ -522,7 +522,7 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
ePetTamingNotifyType::NAMINGPET,
NiPoint3Constant::ZERO,
NiPoint3Constant::ZERO,
NiQuaternionConstant::IDENTITY,
QuatUtils::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);
@@ -601,7 +601,7 @@ void PetComponent::RequestSetPetName(std::u16string name) {
ePetTamingNotifyType::SUCCESS,
NiPoint3Constant::ZERO,
NiPoint3Constant::ZERO,
NiQuaternionConstant::IDENTITY,
QuatUtils::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);
@@ -645,7 +645,7 @@ void PetComponent::ClientExitTamingMinigame(bool voluntaryExit) {
ePetTamingNotifyType::QUIT,
NiPoint3Constant::ZERO,
NiPoint3Constant::ZERO,
NiQuaternionConstant::IDENTITY,
QuatUtils::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);
@@ -696,7 +696,7 @@ void PetComponent::ClientFailTamingMinigame() {
ePetTamingNotifyType::FAILED,
NiPoint3Constant::ZERO,
NiPoint3Constant::ZERO,
NiQuaternionConstant::IDENTITY,
QuatUtils::IDENTITY,
UNASSIGNED_SYSTEM_ADDRESS
);

View File

@@ -175,5 +175,5 @@ private:
/**
* If this is a respawn volume, the exact rotation an entity will respawn
*/
NiQuaternion m_RespawnRot;
NiQuaternion m_RespawnRot = QuatUtils::IDENTITY;
};

View File

@@ -17,7 +17,7 @@
PhysicsComponent::PhysicsComponent(Entity* parent, int32_t componentId) : Component(parent) {
m_Position = NiPoint3Constant::ZERO;
m_Rotation = NiQuaternionConstant::IDENTITY;
m_Rotation = QuatUtils::IDENTITY;
m_DirtyPosition = false;
CDPhysicsComponentTable* physicsComponentTable = CDClientManager::GetTable<CDPhysicsComponentTable>();
@@ -81,10 +81,10 @@ dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type) {
toReturn = new dpEntity(m_Parent->GetObjectID(), 1.0f, 12.5f, 20.0f); // Not sure what the real size is
} else if (info->physicsAsset == "env\\NG_NinjaGo\\env_ng_gen_gate_chamber_puzzle_ceiling_tile_falling_phantom.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 18.0f, 5.0f, 15.0f);
m_Position += m_Rotation.GetForwardVector() * 7.5f;
m_Position += QuatUtils::Forward(m_Rotation) * 7.5f;
} else if (info->physicsAsset == "env\\NG_NinjaGo\\ng_flamejet_brick_phantom.HKX") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 1.0f, 1.0f, 12.0f);
m_Position += m_Rotation.GetForwardVector() * 6.0f;
m_Position += QuatUtils::Forward(m_Rotation) * 6.0f;
} else if (info->physicsAsset == "env\\Ring_Trigger.hkx") {
toReturn = new dpEntity(m_Parent->GetObjectID(), 6.0f, 6.0f, 6.0f);
} else if (info->physicsAsset == "env\\vfx_propertyImaginationBall.hkx") {

View File

@@ -45,7 +45,7 @@ protected:
NiPoint3 m_Position;
NiQuaternion m_Rotation;
NiQuaternion m_Rotation = QuatUtils::IDENTITY;
bool m_DirtyPosition;

View File

@@ -333,7 +333,7 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N
const auto modelLOT = item->GetLot();
if (rotation != NiQuaternionConstant::IDENTITY) {
if (rotation != QuatUtils::IDENTITY) {
rotation = { rotation.w, rotation.z, rotation.y, rotation.x };
}
@@ -516,7 +516,7 @@ void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int delet
GameMessages::SendGetModelsOnProperty(entity->GetObjectID(), GetModels(), UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), NiPoint3Constant::ZERO, LWOOBJID_EMPTY, 16, NiQuaternionConstant::IDENTITY);
GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), NiPoint3Constant::ZERO, LWOOBJID_EMPTY, 16, QuatUtils::IDENTITY);
if (spawner != nullptr) {
Game::zoneManager->RemoveSpawner(spawner->m_Info.spawnerID);
@@ -569,7 +569,7 @@ void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int delet
GameMessages::SendGetModelsOnProperty(entity->GetObjectID(), GetModels(), UNASSIGNED_SYSTEM_ADDRESS);
GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), NiPoint3Constant::ZERO, LWOOBJID_EMPTY, 16, NiQuaternionConstant::IDENTITY);
GameMessages::SendPlaceModelResponse(entity->GetObjectID(), entity->GetSystemAddress(), NiPoint3Constant::ZERO, LWOOBJID_EMPTY, 16, QuatUtils::IDENTITY);
if (spawner != nullptr) {
Game::zoneManager->RemoveSpawner(spawner->m_Info.spawnerID);

View File

@@ -123,7 +123,7 @@ void RacingControlComponent::LoadPlayerVehicle(Entity* player,
auto spawnPointEntities = Game::entityManager->GetEntitiesByLOT(4843);
auto startPosition = NiPoint3Constant::ZERO;
auto startRotation = NiQuaternionConstant::IDENTITY;
auto startRotation = QuatUtils::IDENTITY;
const std::string placementAsString = std::to_string(positionNumber);
for (auto entity : spawnPointEntities) {
if (!entity) continue;

View File

@@ -48,7 +48,7 @@ struct RacingPlayerInfo {
/**
* Rotation that the player will respawn at if they smash their car
*/
NiQuaternion respawnRotation;
NiQuaternion respawnRotation = QuatUtils::IDENTITY;
/**
* The index in the respawn point the player is now at

View File

@@ -316,7 +316,7 @@ SkillExecutionResult SkillComponent::CalculateBehavior(
start.originatorRot = originator->GetRotation();
}
if (rotationOverride != NiQuaternionConstant::IDENTITY) {
if (rotationOverride != QuatUtils::IDENTITY) {
start.originatorRot = rotationOverride;
}
//start.optionalTargetID = target;

View File

@@ -129,7 +129,7 @@ public:
* @param optionalOriginatorID change the originator of the skill
* @return if the case succeeded
*/
bool CastSkill(const uint32_t skillId, LWOOBJID target = LWOOBJID_EMPTY, const LWOOBJID optionalOriginatorID = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = NiQuaternionConstant::IDENTITY);
bool CastSkill(const uint32_t skillId, LWOOBJID target = LWOOBJID_EMPTY, const LWOOBJID optionalOriginatorID = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = QuatUtils::IDENTITY);
/**
* Initializes a server-side skill calculation.
@@ -141,7 +141,7 @@ public:
* @param originatorOverride an override for the originator of the skill calculation
* @return the result of the skill calculation
*/
SkillExecutionResult CalculateBehavior(uint32_t skillId, uint32_t behaviorId, LWOOBJID target, bool ignoreTarget = false, bool clientInitalized = false, LWOOBJID originatorOverride = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = NiQuaternionConstant::IDENTITY);
SkillExecutionResult CalculateBehavior(uint32_t skillId, uint32_t behaviorId, LWOOBJID target, bool ignoreTarget = false, bool clientInitalized = false, LWOOBJID originatorOverride = LWOOBJID_EMPTY, const int32_t castType = 0, const NiQuaternion rotationOverride = QuatUtils::IDENTITY);
/**
* Register a server-side projectile.

View File

@@ -17,6 +17,8 @@
#include "EntityManager.h"
#include "MovementAIComponent.h"
#include <glm/gtc/quaternion.hpp>
TriggerComponent::TriggerComponent(Entity* parent, const std::string triggerInfo) : Component(parent) {
m_Parent = parent;
m_Trigger = nullptr;
@@ -240,10 +242,9 @@ void TriggerComponent::HandleMoveObject(Entity* targetEntity, std::vector<std::s
void TriggerComponent::HandleRotateObject(Entity* targetEntity, std::vector<std::string> argArray) {
if (argArray.size() <= 2) return;
const NiPoint3 vector = GeneralUtils::TryParse<NiPoint3>(argArray).value_or(NiPoint3Constant::ZERO);
const auto vector = GeneralUtils::TryParse<glm::vec3>(argArray).value_or(glm::zero<glm::vec3>());
NiQuaternion rotation = NiQuaternion::FromEulerAngles(vector);
targetEntity->SetRotation(rotation);
targetEntity->SetRotation(glm::quat(vector));
}
void TriggerComponent::HandlePushObject(Entity* targetEntity, std::vector<std::string> argArray) {

View File

@@ -33,7 +33,7 @@ struct EntityInfo {
LWOOBJID id;
LOT lot;
NiPoint3 pos;
NiQuaternion rot;
NiQuaternion rot = QuatUtils::IDENTITY;
std::vector<LDFBaseData*> settings;
std::vector<LDFBaseData*> networkSettings;
float scale;

View File

@@ -15,11 +15,11 @@ public:
iCastType = 0;
lastClickedPosit = NiPoint3Constant::ZERO;
optionalTargetID = LWOOBJID_EMPTY;
originatorRot = NiQuaternionConstant::IDENTITY;
originatorRot = QuatUtils::IDENTITY;
uiSkillHandle = 0;
}
EchoStartSkill(LWOOBJID _optionalOriginatorID, std::string _sBitStream, TSkillID _skillID, bool _bUsedMouse = false, float _fCasterLatency = 0.0f, int32_t _iCastType = 0, NiPoint3 _lastClickedPosit = NiPoint3Constant::ZERO, LWOOBJID _optionalTargetID = LWOOBJID_EMPTY, NiQuaternion _originatorRot = NiQuaternionConstant::IDENTITY, uint32_t _uiSkillHandle = 0) {
EchoStartSkill(LWOOBJID _optionalOriginatorID, std::string _sBitStream, TSkillID _skillID, bool _bUsedMouse = false, float _fCasterLatency = 0.0f, int32_t _iCastType = 0, NiPoint3 _lastClickedPosit = NiPoint3Constant::ZERO, LWOOBJID _optionalTargetID = LWOOBJID_EMPTY, NiQuaternion _originatorRot = QuatUtils::IDENTITY, uint32_t _uiSkillHandle = 0) {
bUsedMouse = _bUsedMouse;
fCasterLatency = _fCasterLatency;
iCastType = _iCastType;
@@ -58,8 +58,8 @@ public:
stream.Write(optionalTargetID != LWOOBJID_EMPTY);
if (optionalTargetID != LWOOBJID_EMPTY) stream.Write(optionalTargetID);
stream.Write(originatorRot != NiQuaternionConstant::IDENTITY);
if (originatorRot != NiQuaternionConstant::IDENTITY) stream.Write(originatorRot);
stream.Write(originatorRot != QuatUtils::IDENTITY);
if (originatorRot != QuatUtils::IDENTITY) stream.Write(originatorRot);
uint32_t sBitStreamLength = sBitStream.length();
stream.Write(sBitStreamLength);
@@ -121,7 +121,7 @@ public:
NiPoint3 lastClickedPosit;
LWOOBJID optionalOriginatorID;
LWOOBJID optionalTargetID;
NiQuaternion originatorRot;
NiQuaternion originatorRot = QuatUtils::IDENTITY;
std::string sBitStream;
TSkillID skillID;
uint32_t uiSkillHandle;

View File

@@ -389,7 +389,7 @@ void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAd
float fMoveTimeElapsed = 0.0f;
float fPercentBetweenPoints = 0.0f;
NiPoint3 ptUnexpectedLocation = NiPoint3Constant::ZERO;
NiQuaternion qUnexpectedRotation = NiQuaternionConstant::IDENTITY;
NiQuaternion qUnexpectedRotation = QuatUtils::IDENTITY;
bitStream.Write(bReverse);
bitStream.Write(bStopAtDesiredWaypoint);
@@ -406,8 +406,8 @@ void GameMessages::SendPlatformResync(Entity* entity, const SystemAddress& sysAd
bitStream.Write(ptUnexpectedLocation.y);
bitStream.Write(ptUnexpectedLocation.z);
bitStream.Write(qUnexpectedRotation != NiQuaternionConstant::IDENTITY);
if (qUnexpectedRotation != NiQuaternionConstant::IDENTITY) {
bitStream.Write(qUnexpectedRotation != QuatUtils::IDENTITY);
if (qUnexpectedRotation != QuatUtils::IDENTITY) {
bitStream.Write(qUnexpectedRotation.x);
bitStream.Write(qUnexpectedRotation.y);
bitStream.Write(qUnexpectedRotation.z);
@@ -1181,7 +1181,7 @@ void GameMessages::SendPlayerReachedRespawnCheckpoint(Entity* entity, const NiPo
bitStream.Write(position.y);
bitStream.Write(position.z);
const bool bIsNotIdentity = rotation != NiQuaternionConstant::IDENTITY;
const bool bIsNotIdentity = rotation != QuatUtils::IDENTITY;
bitStream.Write(bIsNotIdentity);
if (bIsNotIdentity) {
@@ -2129,8 +2129,8 @@ void GameMessages::SendPlaceModelResponse(LWOOBJID objectId, const SystemAddress
bitStream.Write(response);
}
bitStream.Write(rotation != NiQuaternionConstant::IDENTITY);
if (rotation != NiQuaternionConstant::IDENTITY) {
bitStream.Write(rotation != QuatUtils::IDENTITY);
if (rotation != QuatUtils::IDENTITY) {
bitStream.Write(response);
}
@@ -2395,13 +2395,13 @@ void GameMessages::HandlePlacePropertyModel(RakNet::BitStream& inStream, Entity*
inStream.Read(model);
PropertyManagementComponent::Instance()->UpdateModelPosition(model, NiPoint3Constant::ZERO, NiQuaternionConstant::IDENTITY);
PropertyManagementComponent::Instance()->UpdateModelPosition(model, NiPoint3Constant::ZERO, QuatUtils::IDENTITY);
}
void GameMessages::HandleUpdatePropertyModel(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
LWOOBJID model;
NiPoint3 position;
NiQuaternion rotation = NiQuaternionConstant::IDENTITY;
NiQuaternion rotation = QuatUtils::IDENTITY;
inStream.Read(model);
inStream.Read(position);
@@ -3400,7 +3400,7 @@ void GameMessages::SendNotifyPetTamingMinigame(LWOOBJID objectId, LWOOBJID petId
bitStream.Write(petsDestPos);
bitStream.Write(telePos);
const bool hasDefault = teleRot != NiQuaternionConstant::IDENTITY;
const bool hasDefault = teleRot != QuatUtils::IDENTITY;
bitStream.Write(hasDefault);
if (hasDefault) bitStream.Write(teleRot);

View File

@@ -18,7 +18,6 @@ class AMFBaseValue;
class AMFArrayValue;
class Entity;
class Item;
class NiQuaternion;
class User;
class Leaderboard;
class PropertySelectQueryProperty;
@@ -765,7 +764,7 @@ namespace GameMessages {
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
NiPoint3 target{};
NiQuaternion rotation{};
NiQuaternion rotation = QuatUtils::IDENTITY;
};
struct ChildLoaded : public GameMsg {

View File

@@ -18,11 +18,11 @@ public:
iCastType = 0;
lastClickedPosit = NiPoint3Constant::ZERO;
optionalTargetID = LWOOBJID_EMPTY;
originatorRot = NiQuaternionConstant::IDENTITY;
originatorRot = QuatUtils::IDENTITY;
uiSkillHandle = 0;
}
StartSkill(LWOOBJID _optionalOriginatorID, std::string _sBitStream, TSkillID _skillID, bool _bUsedMouse = false, LWOOBJID _consumableItemID = LWOOBJID_EMPTY, float _fCasterLatency = 0.0f, int32_t _iCastType = 0, NiPoint3 _lastClickedPosit = NiPoint3Constant::ZERO, LWOOBJID _optionalTargetID = LWOOBJID_EMPTY, NiQuaternion _originatorRot = NiQuaternionConstant::IDENTITY, uint32_t _uiSkillHandle = 0) {
StartSkill(LWOOBJID _optionalOriginatorID, std::string _sBitStream, TSkillID _skillID, bool _bUsedMouse = false, LWOOBJID _consumableItemID = LWOOBJID_EMPTY, float _fCasterLatency = 0.0f, int32_t _iCastType = 0, NiPoint3 _lastClickedPosit = NiPoint3Constant::ZERO, LWOOBJID _optionalTargetID = LWOOBJID_EMPTY, NiQuaternion _originatorRot = QuatUtils::IDENTITY, uint32_t _uiSkillHandle = 0) {
bUsedMouse = _bUsedMouse;
consumableItemID = _consumableItemID;
fCasterLatency = _fCasterLatency;
@@ -65,8 +65,8 @@ public:
stream.Write(optionalTargetID != LWOOBJID_EMPTY);
if (optionalTargetID != LWOOBJID_EMPTY) stream.Write(optionalTargetID);
stream.Write(originatorRot != NiQuaternionConstant::IDENTITY);
if (originatorRot != NiQuaternionConstant::IDENTITY) stream.Write(originatorRot);
stream.Write(originatorRot != QuatUtils::IDENTITY);
if (originatorRot != QuatUtils::IDENTITY) stream.Write(originatorRot);
uint32_t sBitStreamLength = sBitStream.length();
stream.Write(sBitStreamLength);
@@ -133,7 +133,7 @@ public:
NiPoint3 lastClickedPosit{};
LWOOBJID optionalOriginatorID{};
LWOOBJID optionalTargetID{};
NiQuaternion originatorRot{};
NiQuaternion originatorRot = QuatUtils::IDENTITY;
std::string sBitStream = "";
TSkillID skillID = 0;
uint32_t uiSkillHandle = 0;

View File

@@ -138,7 +138,7 @@ void Strip::Spawn(LOT lot, Entity& entity) {
EntityInfo info{};
info.lot = lot;
info.pos = entity.GetPosition();
info.rot = NiQuaternionConstant::IDENTITY;
info.rot = QuatUtils::IDENTITY;
info.spawnerID = entity.GetObjectID();
auto* const spawnedEntity = Game::entityManager->CreateEntity(info, nullptr, &entity);
spawnedEntity->AddToGroup("SpawnedPropertyEnemies");

View File

@@ -808,6 +808,15 @@ void SlashCommandHandler::Startup() {
};
RegisterCommand(DeleteInvenCommand);
Command ExecuteCommand{
.help = "Execute commands with modified context (Minecraft-style)",
.info = "Execute commands as different entities or from different positions. Usage: /execute <subcommand> ... run <command>. Subcommands: as <entity>, at <entity>, positioned <x> <y> <z>",
.aliases = { "execute", "exec" },
.handle = DEVGMCommands::Execute,
.requiredLevel = eGameMasterLevel::DEVELOPER
};
RegisterCommand(ExecuteCommand);
// Register Greater Than Zero Commands
Command KickCommand{

View File

@@ -559,23 +559,25 @@ namespace DEVGMCommands {
}
}
std::optional<float> ParseRelativeAxis(const float sourcePos, const std::string& toParse) {
if (toParse.empty()) return std::nullopt;
// relative offset from current position
if (toParse[0] == '~') {
if (toParse.size() == 1) return sourcePos;
if (toParse.size() < 3 || !(toParse[1] != '+' || toParse[1] != '-')) return std::nullopt;
const auto offset = GeneralUtils::TryParse<float>(toParse.substr(2));
if (!offset.has_value()) return std::nullopt;
bool isNegative = toParse[1] == '-';
return isNegative ? sourcePos - offset.value() : sourcePos + offset.value();
// Parse coordinates with support for relative positioning (~)
std::optional<float> ParseRelativeAxis(const float currentValue, const std::string& rawCoord) {
if (rawCoord.empty()) return std::nullopt;
std::string coord = rawCoord;
// Remove any '+' characters to simplify parsing, since they don't affect the value
coord.erase(std::remove(coord.begin(), coord.end(), '+'), coord.end());
if (coord[0] == '~') {
if (coord.length() == 1) {
return currentValue;
} else {
auto offsetOpt = GeneralUtils::TryParse<float>(coord.substr(1));
if (!offsetOpt) return std::nullopt;
return currentValue + offsetOpt.value();
}
} else {
auto absoluteOpt = GeneralUtils::TryParse<float>(coord);
if (!absoluteOpt) return std::nullopt;
return absoluteOpt.value();
}
return GeneralUtils::TryParse<float>(toParse);
}
void Teleport(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
@@ -649,7 +651,7 @@ namespace DEVGMCommands {
if (havokVehiclePhysicsComponent) {
havokVehiclePhysicsComponent->SetPosition(pos);
Game::entityManager->SerializeEntity(possassableEntity);
} else GameMessages::SendTeleport(possassableEntity->GetObjectID(), pos, NiQuaternion(), sysAddr);
} else GameMessages::SendTeleport(possassableEntity->GetObjectID(), pos, QuatUtils::IDENTITY, sysAddr);
}
}
}
@@ -660,7 +662,7 @@ namespace DEVGMCommands {
const auto characters = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::CHARACTER);
for (auto* character : characters) {
GameMessages::SendTeleport(character->GetObjectID(), pos, NiQuaternion(), character->GetSystemAddress());
GameMessages::SendTeleport(character->GetObjectID(), pos, QuatUtils::IDENTITY, character->GetSystemAddress());
}
}
@@ -815,7 +817,7 @@ namespace DEVGMCommands {
// Set the position to the generated random position plus the player position. This will
// spawn the entity in a circle around the player. As you get further from the player, the angle chosen will get less accurate.
info.pos = playerPosition + NiPoint3(cos(randomAngle) * randomRadius, 0.0f, sin(randomAngle) * randomRadius);
info.rot = NiQuaternion();
info.rot = QuatUtils::IDENTITY;
auto newEntity = Game::entityManager->CreateEntity(info);
if (newEntity == nullptr) {
@@ -1664,4 +1666,163 @@ namespace DEVGMCommands {
LOG("Despawned entity (%llu)", target->GetObjectID());
ChatPackets::SendSystemMessage(sysAddr, u"Despawned entity: " + GeneralUtils::to_u16string(target->GetObjectID()));
}
void Execute(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (args.empty()) {
ChatPackets::SendSystemMessage(sysAddr,
u"Usage: /execute <subcommand> ... run <command>\n"
u"Subcommands:\n"
u" as <playername> - Execute as different player\n"
u" at <playername> - Execute from player's position\n"
u" positioned <x> <y> <z> - Execute from coordinates (absolute or relative with ~)\n"
u"Examples:\n"
u" /execute as Player1 run pos\n"
u" /execute at Player2 positioned 100 200 300 run spawn 1234\n"
u" /execute positioned ~5 ~10 ~ run spawn 1234"
);
return;
}
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
// Prevent execute command recursion by checking if this is already an execute command
for (const auto& arg : splitArgs) {
if (arg == "execute" || arg == "exec") {
ChatPackets::SendSystemMessage(sysAddr, u"Error: Recursive execute commands are not allowed");
return;
}
}
// Context variables for execution
Entity* execEntity = entity; // Entity to execute as
NiPoint3 execPosition = entity->GetPosition(); // Position to execute from
bool positionOverridden = false;
std::string finalCommand;
// Parse subcommands
size_t i = 0;
while (i < splitArgs.size()) {
const std::string& subcommand = splitArgs[i];
if (subcommand == "as") {
if (i + 1 >= splitArgs.size()) {
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'as' requires a player name");
return;
}
const std::string& targetName = splitArgs[i + 1];
auto* targetPlayer = PlayerManager::GetPlayer(targetName);
if (!targetPlayer) {
ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found");
return;
}
execEntity = targetPlayer;
i += 2;
} else if (subcommand == "at") {
if (i + 1 >= splitArgs.size()) {
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'at' requires a player name");
return;
}
const std::string& targetName = splitArgs[i + 1];
auto* targetPlayer = PlayerManager::GetPlayer(targetName);
if (!targetPlayer) {
ChatPackets::SendSystemMessage(sysAddr, u"Error: Player '" + GeneralUtils::ASCIIToUTF16(targetName) + u"' not found");
return;
}
execPosition = targetPlayer->GetPosition();
positionOverridden = true;
i += 2;
} else if (subcommand == "positioned") {
if (i + 3 >= splitArgs.size()) {
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'positioned' requires x, y, z coordinates");
return;
}
auto xOpt = ParseRelativeAxis(execPosition.x, splitArgs[i + 1]);
auto yOpt = ParseRelativeAxis(execPosition.y, splitArgs[i + 2]);
auto zOpt = ParseRelativeAxis(execPosition.z, splitArgs[i + 3]);
if (!xOpt || !yOpt || !zOpt) {
ChatPackets::SendSystemMessage(sysAddr, u"Error: Invalid coordinates for 'positioned'. Use numeric values or relative coordinates with ~.");
return;
}
execPosition = NiPoint3(xOpt.value(), yOpt.value(), zOpt.value());
positionOverridden = true;
i += 4;
} else if (subcommand == "run") {
// Everything after "run" is the command to execute
if (i + 1 >= splitArgs.size()) {
ChatPackets::SendSystemMessage(sysAddr, u"Error: 'run' requires a command");
return;
}
// Reconstruct the command from remaining args
for (size_t j = i + 1; j < splitArgs.size(); ++j) {
if (!finalCommand.empty()) finalCommand += " ";
finalCommand += splitArgs[j];
}
break;
} else {
ChatPackets::SendSystemMessage(sysAddr, u"Error: Unknown subcommand '" + GeneralUtils::ASCIIToUTF16(subcommand) + u"'");
ChatPackets::SendSystemMessage(sysAddr, u"Valid subcommands: as, at, positioned, run");
return;
}
}
if (finalCommand.empty()) {
ChatPackets::SendSystemMessage(sysAddr, u"Error: No command specified to run. Use 'run <command>' at the end.");
return;
}
// Validate that the command starts with a valid character
if (finalCommand.empty() || finalCommand[0] == '/') {
ChatPackets::SendSystemMessage(sysAddr, u"Error: Command should not start with '/'. Just specify the command name.");
return;
}
// Store original position if we need to restore it
NiPoint3 originalPosition;
bool needToRestore = false;
if (positionOverridden && execEntity == entity) {
// If we're executing as ourselves but from a different position,
// temporarily move the entity
originalPosition = entity->GetPosition();
needToRestore = true;
// Set the position temporarily for the command execution
auto* controllable = entity->GetComponent<ControllablePhysicsComponent>();
if (controllable) {
controllable->SetPosition(execPosition);
}
}
// Provide feedback about what we're executing
std::string execAsName = execEntity->GetCharacter() ? execEntity->GetCharacter()->GetName() : "Unknown";
ChatPackets::SendSystemMessage(sysAddr, u"[Execute] Running as '" + GeneralUtils::ASCIIToUTF16(execAsName) +
u"' from <" + GeneralUtils::to_u16string(execPosition.x) + u", " +
GeneralUtils::to_u16string(execPosition.y) + u", " +
GeneralUtils::to_u16string(execPosition.z) + u">: /" +
GeneralUtils::ASCIIToUTF16(finalCommand));
// Execute the command through the slash command handler
SlashCommandHandler::HandleChatCommand(GeneralUtils::ASCIIToUTF16("/" + finalCommand), execEntity, sysAddr);
// Restore original position if needed
if (needToRestore) {
auto* controllable = entity->GetComponent<ControllablePhysicsComponent>();
if (controllable) {
controllable->SetPosition(originalPosition);
}
}
}
};

View File

@@ -76,6 +76,7 @@ namespace DEVGMCommands {
void Shutdown(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Barfight(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Despawn(Entity* entity, const SystemAddress& sysAddr, const std::string args);
void Execute(Entity* entity, const SystemAddress& sysAddr, const std::string args);
}
#endif //!DEVGMCOMMANDS_H

View File

@@ -8,7 +8,7 @@
struct VanityObjectLocation {
float m_Chance = 1.0f;
NiPoint3 m_Position;
NiQuaternion m_Rotation;
NiQuaternion m_Rotation = QuatUtils::IDENTITY;
float m_Scale = 1.0f;
};

View File

@@ -159,6 +159,7 @@ int main(int argc, char** argv) {
}
MigrationRunner::RunMigrations();
Database::Get()->Commit();
const auto resServerPath = BinaryPathFinder::GetBinaryDir() / "resServer";
std::filesystem::create_directories(resServerPath);
const bool cdServerExists = std::filesystem::exists(resServerPath / "CDServer.sqlite");

View File

@@ -10,7 +10,7 @@ set(DNET_SOURCES "AuthPackets.cpp"
"ZoneInstanceManager.cpp")
add_library(dNet STATIC ${DNET_SOURCES})
target_link_libraries(dNet PRIVATE bcrypt MD5)
target_link_libraries(dNet PRIVATE bcrypt MD5 glm::glm)
target_include_directories(dNet PRIVATE
"${PROJECT_SOURCE_DIR}/dCommon"
"${PROJECT_SOURCE_DIR}/dCommon/dEnums"

View File

@@ -12,5 +12,5 @@ target_include_directories(dPhysics PUBLIC "."
"${PROJECT_SOURCE_DIR}/dCommon/dEnums"
)
target_link_libraries(dPhysics
PUBLIC Recast Detour
PUBLIC Recast Detour glm::glm
INTERFACE dNavigation dCommon)

View File

@@ -68,7 +68,7 @@ private:
bool m_IsStatic;
NiPoint3 m_Position;
NiQuaternion m_Rotation;
NiQuaternion m_Rotation = QuatUtils::IDENTITY;
float m_Scale;
NiPoint3 m_Velocity;

View File

@@ -88,7 +88,7 @@ void BossSpiderQueenEnemyServer::WithdrawSpider(Entity* self, const bool withdra
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"SetColGroup", 10, 0, 0, "", UNASSIGNED_SYSTEM_ADDRESS);
//First rotate for anim
NiQuaternion rot = NiQuaternionConstant::IDENTITY;
NiQuaternion rot = QuatUtils::IDENTITY;
controllable->SetStatic(false);
@@ -405,7 +405,7 @@ void BossSpiderQueenEnemyServer::OnTimerDone(Entity* self, const std::string tim
const auto withdrawn = self->GetBoolean(u"isWithdrawn");
if (!withdrawn) return;
NiQuaternion rot = NiQuaternionConstant::IDENTITY;
NiQuaternion rot = QuatUtils::IDENTITY;
//First rotate for anim
controllable->SetStatic(false);
@@ -600,12 +600,12 @@ void BossSpiderQueenEnemyServer::OnUpdate(Entity* self) {
if (!isWithdrawn) return;
if (controllable->GetRotation() == NiQuaternionConstant::IDENTITY) {
if (controllable->GetRotation() == QuatUtils::IDENTITY) {
return;
}
controllable->SetStatic(false);
controllable->SetRotation(NiQuaternionConstant::IDENTITY);
controllable->SetRotation(QuatUtils::IDENTITY);
controllable->SetStatic(true);
Game::entityManager->SerializeEntity(self);

View File

@@ -52,7 +52,7 @@ private:
ControllablePhysicsComponent* controllable = nullptr;
BaseCombatAIComponent* combat = nullptr;
NiQuaternion originRotation;
NiQuaternion originRotation = QuatUtils::IDENTITY;
int m_CurrentBossStage = 0;
int m_DeathCounter = 0;

View File

@@ -76,7 +76,7 @@ void AmDarklingDragon::OnHitOrHealResult(Entity* self, Entity* attacker, int32_t
self->AddTimer("timeToStunLoop", 1.0f);
auto position = self->GetPosition();
auto forward = self->GetRotation().GetForwardVector();
auto forward = QuatUtils::Forward(self->GetRotation());
auto backwards = forward * -1;
forward.x *= 10;

View File

@@ -92,7 +92,7 @@ void FvMaelstromDragon::OnHitOrHealResult(Entity* self, Entity* attacker, int32_
self->AddTimer("timeToStunLoop", 1.0f);
auto position = self->GetPosition();
auto forward = self->GetRotation().GetForwardVector();
auto forward = QuatUtils::Forward(self->GetRotation());
auto backwards = forward * -1;
forward.x *= 10;

View File

@@ -66,7 +66,7 @@ void BaseEnemyApe::OnTimerDone(Entity* self, std::string timerName) {
const auto position = self->GetPosition();
const auto rotation = self->GetRotation();
const auto backwardVector = rotation.GetForwardVector() * -1;
const auto backwardVector = QuatUtils::Forward(rotation) * -1;
const auto objectPosition = NiPoint3(
position.GetX() - (backwardVector.GetX() * 8),
position.GetY(),

View File

@@ -93,7 +93,7 @@ void AmDrawBridge::MoveBridgeDown(Entity* self, Entity* bridge, bool down) {
return;
}
auto forwardVect = simplePhysicsComponent->GetRotation().GetForwardVector();
auto forwardVect = QuatUtils::Forward(simplePhysicsComponent->GetRotation());
auto degrees = down ? 90.0f : -90.0f;

View File

@@ -129,7 +129,7 @@ void AmShieldGenerator::EnemyEnteredShield(Entity* self, Entity* intruder) {
return;
}
auto dir = intruder->GetRotation().GetForwardVector() * -1;
auto dir = QuatUtils::Forward(intruder->GetRotation()) * -1;
dir.y += 15;
dir.x *= 50;
dir.z *= 50;

View File

@@ -187,7 +187,7 @@ void AmShieldGeneratorQuickbuild::EnemyEnteredShield(Entity* self, Entity* intru
return;
}
auto dir = intruder->GetRotation().GetForwardVector() * -1;
auto dir = QuatUtils::Forward(intruder->GetRotation()) * -1;
dir.y += 15;
dir.x *= 50;
dir.z *= 50;

View File

@@ -46,23 +46,23 @@ void AmSkullkinTower::SpawnLegs(Entity* self, const std::string& loc) {
info.rot = newRot;
if (loc == "Right") {
const auto dir = rot.GetForwardVector();
const auto dir = QuatUtils::Forward(rot);
pos.x += dir.x * offset;
pos.z += dir.z * offset;
info.pos = pos;
} else if (loc == "Rear") {
const auto dir = rot.GetRightVector();
const auto dir = QuatUtils::Right(rot);
pos.x += dir.x * offset;
pos.z += dir.z * offset;
info.pos = pos;
} else if (loc == "Left") {
const auto dir = rot.GetForwardVector() * -1;
const auto dir = QuatUtils::Forward(rot) * -1;
pos.x += dir.x * offset;
pos.z += dir.z * offset;
info.pos = pos;
}
info.rot = NiQuaternion::LookAt(info.pos, self->GetPosition());
info.rot = QuatUtils::LookAt(info.pos, self->GetPosition());
auto* entity = Game::entityManager->CreateEntity(info, nullptr, self);

View File

@@ -21,7 +21,7 @@ void GfCaptainsCannon::OnUse(Entity* self, Entity* user) {
);
auto position = self->GetPosition();
auto forward = self->GetRotation().GetForwardVector();
auto forward = QuatUtils::Forward(self->GetRotation());
position.x += forward.x * -3;
position.z += forward.z * -3;

View File

@@ -65,7 +65,7 @@ void MastTeleport::OnTimerDone(Entity* self, std::string timerName) {
} else if (timerName == "PlayerAnimDone") {
GameMessages::SendStopFXEffect(player, true, "hook");
auto forward = self->GetRotation().GetForwardVector();
auto forward = QuatUtils::Forward(self->GetRotation());
const auto degrees = -25.0f;
@@ -81,7 +81,7 @@ void MastTeleport::OnTimerDone(Entity* self, std::string timerName) {
GameMessages::SendOrientToAngle(playerId, true, rads, player->GetSystemAddress());
GameMessages::SendTeleport(playerId, position, NiQuaternionConstant::IDENTITY, player->GetSystemAddress());
GameMessages::SendTeleport(playerId, position, QuatUtils::IDENTITY, player->GetSystemAddress());
GameMessages::SendSetStunned(playerId, eStateChangeType::POP, player->GetSystemAddress(),
LWOOBJID_EMPTY, true, true, true, true, true, true, true

View File

@@ -45,13 +45,13 @@ void QbSpawner::OnTimerDone(Entity* self, std::string timerName) {
if (!gate) return;
auto oPos = gate->GetPosition();
auto oDir = gate->GetRotation().GetForwardVector();
auto oDir = QuatUtils::Forward(gate->GetRotation());
NiPoint3 newPos(
oPos.x + (oDir.x * spawnDist),
oPos.y,
oPos.z + (oDir.z * spawnDist)
);
auto newRot = NiQuaternion::LookAt(newPos, oPos);
auto newRot = QuatUtils::LookAt(newPos, oPos);
for (int i = 0; i < mobTable.size(); i++) {
int posOffset = -10;

View File

@@ -108,7 +108,7 @@ void NtCombatChallengeServer::OnChildLoaded(Entity& self, GameMessages::ChildLoa
auto* const child = Game::entityManager->GetEntity(childLoaded.childID);
if (child) {
child->SetRotation(NiQuaternion::FromEulerAngles(child->GetRotation().GetEulerAngles() += NiPoint3(0, PI, 0))); // rotate 180 degrees
child->SetRotation(QuatUtils::FromEuler(QuatUtils::Euler(child->GetRotation()) += NiPoint3(0, PI, 0))); // rotate 180 degrees
}
self.SetVar(u"currentTargetID", child->GetObjectID());

View File

@@ -48,7 +48,7 @@ void NtParadoxPanelServer::OnUse(Entity* self, Entity* user) {
RenderComponent::PlayAnimation(user, shockAnim);
const auto dir = self->GetRotation().GetRightVector();
const auto dir = QuatUtils::Right(self->GetRotation());
GameMessages::SendKnockback(user->GetObjectID(), self->GetObjectID(), self->GetObjectID(), 0, { dir.x * 15, 5, dir.z * 15 });

View File

@@ -18,7 +18,7 @@ void NtSentinelWalkwayServer::OnStartup(Entity* self) {
force = 115;
}
const auto forward = self->GetRotation().GetRightVector() * -1;
const auto forward = QuatUtils::Right(self->GetRotation()) * -1;
phantomPhysicsComponent->SetEffectType(ePhysicsEffectType::PUSH);
phantomPhysicsComponent->SetDirectionalMultiplier(force);

View File

@@ -27,7 +27,7 @@ void FlameJetServer::OnCollisionPhantom(Entity* self, Entity* target) {
skillComponent->CalculateBehavior(726, 11723, target->GetObjectID(), true);
auto dir = target->GetRotation().GetForwardVector();
auto dir = QuatUtils::Forward(target->GetRotation());
dir.y = 25;
dir.x = -dir.x * 15;

View File

@@ -36,7 +36,7 @@ void AgSpiderBossMessage::OnCollisionPhantom(Entity* self, Entity* target) {
auto box = GetBox(self);
// knockback the target
auto forward = self->GetRotation().GetForwardVector();
auto forward = QuatUtils::Forward(self->GetRotation());
box.boxTarget = target->GetObjectID();
GameMessages::SendPlayFXEffect(target->GetObjectID(), 1378, u"create", "pushBack");
RenderComponent::PlayAnimation(target, "knockback-recovery");

View File

@@ -10,8 +10,8 @@ void GfBanana::SpawnBanana(Entity* self) {
const auto rotation = self->GetRotation();
position.y += 12;
position.x -= rotation.GetRightVector().x * 5;
position.z -= rotation.GetRightVector().z * 5;
position.x -= QuatUtils::Right(rotation).x * 5;
position.z -= QuatUtils::Right(rotation).z * 5;
EntityInfo info{};

View File

@@ -22,7 +22,7 @@ struct SGEnemy {
struct SGConstants {
Vector3 playerStartPosition;
Quaternion playerStartRotation;
Quaternion playerStartRotation = QuatUtils::IDENTITY;
LOT cannonLot;
uint32_t impactSkillID;
LOT projectileLot;

View File

@@ -273,7 +273,7 @@ void NsConcertInstrument::RepositionPlayer(Entity* self, Entity* player) {
case Keyboard:
position.SetX(position.GetX() - 0.45f);
position.SetZ(position.GetZ() + 0.75f);
rotation = NiQuaternion::CreateFromAxisAngle(position, -0.8f); // Slight rotation to make the animation sensible
rotation = QuatUtils::AxisAngle(position, -0.8f); // Slight rotation to make the animation sensible
break;
case Drum:
position.SetZ(position.GetZ() - 0.5f);

View File

@@ -10,7 +10,7 @@ void WblRobotCitizen::OnStartup(Entity* self) {
void WblRobotCitizen::OnUse(Entity* self, Entity* user) {
auto movementAIComponent = self->GetComponent<MovementAIComponent>();
if (movementAIComponent) movementAIComponent->Pause();
auto face = NiQuaternion::LookAt(self->GetPosition(), user->GetPosition());
auto face = QuatUtils::LookAt(self->GetPosition(), user->GetPosition());
self->SetRotation(face);
auto timer = RenderComponent::PlayAnimation(self, "wave", 0.4f);
self->AddTimer("animation time", timer);

View File

@@ -1048,7 +1048,7 @@ void HandlePacket(Packet* packet) {
Game::entityManager->ConstructEntity(player, UNASSIGNED_SYSTEM_ADDRESS);
if (respawnPoint != NiPoint3Constant::ZERO) {
GameMessages::SendPlayerReachedRespawnCheckpoint(player, respawnPoint, NiQuaternionConstant::IDENTITY);
GameMessages::SendPlayerReachedRespawnCheckpoint(player, respawnPoint, QuatUtils::IDENTITY);
}
Game::entityManager->ConstructAllEntities(packet->systemAddress);

View File

@@ -13,7 +13,7 @@
struct SpawnerNode {
NiPoint3 position = NiPoint3Constant::ZERO;
NiQuaternion rotation = NiQuaternionConstant::IDENTITY;
NiQuaternion rotation = QuatUtils::IDENTITY;
uint32_t nodeID = 0;
uint32_t nodeMax = 1;
std::vector<LWOOBJID> entities;

View File

@@ -71,7 +71,7 @@ struct RacingPathWaypoint {
struct PathWaypoint {
NiPoint3 position;
NiQuaternion rotation; // not included in all, but it's more convenient here
NiQuaternion rotation = QuatUtils::IDENTITY; // not included in all, but it's more convenient here
MovingPlatformPathWaypoint movingPlatform;
CameraPathWaypoint camera;
RacingPathWaypoint racing;
@@ -237,7 +237,7 @@ private:
uint32_t m_CheckSum;
uint32_t m_WorldID; //should be equal to the MapID
NiPoint3 m_Spawnpoint;
NiQuaternion m_SpawnpointRotation;
NiQuaternion m_SpawnpointRotation = QuatUtils::IDENTITY;
uint32_t m_SceneCount;
std::string m_ZonePath; //Path to the .luz's folder

View File

@@ -11,7 +11,7 @@ struct SceneObject {
uint32_t nodeType;
uint32_t glomId;
NiPoint3 position;
NiQuaternion rotation;
NiQuaternion rotation = QuatUtils::IDENTITY;
float scale = 1.0f;
//std::string settings;
uint32_t value3;

View File

@@ -116,6 +116,7 @@ These commands are primarily for development and testing. The usage of many of t
|setrewardcode|`/setrewardcode <code>`|Sets the rewardcode for the account you are logged into if it's a valid rewardcode, See cdclient table `RewardCodes`|8|
|barfight|`/barfight start`|Starts a barfight (turns everyones pvp on)|8|
|despawn|`/despawn <objectID>`|Despawns the entity objectID IF it was spawned in through a slash command.|8|
|execute|`/execute <subcommand> ... run <command>`|Execute commands with modified context (Minecraft-style). Subcommands: `as <playername>` (execute as different player), `at <playername>` (execute from player's position), `positioned <x> <y> <z>` (execute from coordinates - supports absolute coordinates like `100 200 300` or relative coordinates like `~5 ~10 ~` where `~` means current position). Example: `/execute as Player1 run pos`, `/execute positioned ~5 ~ ~-3 run spawn 1234`|8|
|crash|`/crash`|Crashes the server.|9|
|rollloot|`/rollloot <loot matrix index> <item id> <amount>`|Given a `loot matrix index`, look for `item id` in that matrix `amount` times and print to the chat box statistics of rolling that loot matrix.|9|
|castskill|`/castskill <skill id>`|Casts the skill as the player|9|

View File

@@ -30,7 +30,7 @@ class GameDependenciesTest : public ::testing::Test {
protected:
void SetUpDependencies() {
info.pos = NiPoint3Constant::ZERO;
info.rot = NiQuaternionConstant::IDENTITY;
info.rot = QuatUtils::IDENTITY;
info.scale = 1.0f;
info.spawner = nullptr;
info.lot = 999;

View File

@@ -40,13 +40,16 @@ add_subdirectory(magic_enum)
# Create our third party library objects
add_subdirectory(raknet)
include(FetchContent)
# Download Backtrace if configured
if(UNIX AND NOT APPLE)
include(FetchContent)
if (${INCLUDE_BACKTRACE} AND ${COMPILE_BACKTRACE})
FetchContent_Declare(
backtrace
GIT_REPOSITORY https://github.com/ianlancetaylor/libbacktrace.git
GIT_PROGRESS TRUE
GIT_SHALLOW 1
)
FetchContent_MakeAvailable(backtrace)
@@ -64,6 +67,16 @@ if(UNIX AND NOT APPLE)
endif()
endif()
FetchContent_Declare(
glm
GIT_REPOSITORY https://github.com/g-truc/glm.git
GIT_TAG bf71a834948186f4097caa076cd2663c69a10e1e #refs/tags/1.0.1
GIT_PROGRESS TRUE
GIT_SHALLOW 1
)
FetchContent_MakeAvailable(glm)
add_subdirectory(MD5)
add_subdirectory(mongoose)