Compare commits

..

5 Commits

Author SHA1 Message Date
0e7be44ab6 simplify and address feedback 2023-05-11 10:04:05 -05:00
37f60d9177 Make it more robust like speedboost
add check for default
Fix error in GetActiveSpeedboosts
2023-05-10 20:03:02 -05:00
7d3f538456 Fixup 2023-05-10 19:41:29 -05:00
565b23227e Merge branch 'main' into FallSpeedBehavior 2023-05-10 11:45:57 -05:00
4e5b38d602 Hacky FallSpeedBehavior 2022-07-08 19:04:57 -05:00
361 changed files with 9912 additions and 9019 deletions

View File

@@ -10,7 +10,7 @@ trim_trailing_whitespace=true
end_of_line=lf
insert_final_newline=true
[*.{c++,cc,cpp,cxx,h,h++,hh,hpp,hxx,inl,ipp,tlh,tli,tcc,tpp}]
[*.{c++,cc,cpp,cxx,h,h++,hh,hpp,hxx,inl,ipp,tlh,tli}]
vc_generate_documentation_comments=doxygen_slash_star

View File

@@ -36,16 +36,22 @@ jobs:
testPreset: "ci-${{matrix.os}}"
- name: artifacts
uses: actions/upload-artifact@v3
if: ${{ github.ref == 'ref/head/main' }}
with:
name: build-${{matrix.os}}
path: |
build/*Server*
build/*.ini
build/*.so
build/*.dll
build/vanity/
build/navmeshes/
build/migrations/
build/*.dcf
!build/*.pdb
!build/d*/
build
!build/tests
!build/Testing
!build/CMakeFiles
!build/DartConfiguration.tcl
!build/CTestTestfile.cmake
!build/CMakeCache.txt
!build/build.ninja
!build/_deps
!build/cmake_install.cmake
!build/*.a
!build/*.lib
!build/*.dir
!build/*.vcxproj
!build/*.vcxproj.filters

View File

@@ -305,7 +305,7 @@ file(
file(
GLOB HEADERS_DGAME
LIST_DIRECTORIES false
${PROJECT_SOURCE_DIR}/dGame/dEntity/Entity.h
${PROJECT_SOURCE_DIR}/dGame/Entity.h
${PROJECT_SOURCE_DIR}/dGame/dGameMessages/GameMessages.h
${PROJECT_SOURCE_DIR}/dGame/EntityManager.h
${PROJECT_SOURCE_DIR}/dScripts/CppScripts.h

View File

@@ -17,7 +17,7 @@ __dynamic=1
# Set __compile_backtrace__ to 1 to compile the backtrace library instead of using system libraries.
# __compile_backtrace__=1
# Set to the number of jobs (make -j equivalent) to compile the mariadbconn files with.
__maria_db_connector_compile_jobs__=
__maria_db_connector_compile_jobs__=1
# When set to 1 and uncommented, compiling and linking testing folders and libraries will be done.
__enable_testing__=1
# The path to OpenSSL. Change this if your OpenSSL install path is different than the default.

View File

@@ -338,7 +338,7 @@ This is a Work in Progress, but below are some quick links to documentaion for s
## Former Contributors
* TheMachine
* Matthew
* [Raine](https://github.com/uwainium)
* [Raine](https://github.com/Rainebannister)
* Bricknave
## Special Thanks

View File

@@ -1,79 +1,77 @@
#include "AMFDeserialize.h"
#include <stdexcept>
#include "Amf3.h"
#include "AMFFormat.h"
/**
* AMF3 Reference document https://rtmp.veriskope.com/pdf/amf3-file-format-spec.pdf
* AMF3 Deserializer written by EmosewaMC
*/
AMFBaseValue* AMFDeserialize::Read(RakNet::BitStream* inStream) {
AMFValue* AMFDeserialize::Read(RakNet::BitStream* inStream) {
if (!inStream) return nullptr;
AMFBaseValue* returnValue = nullptr;
AMFValue* returnValue = nullptr;
// Read in the value type from the bitStream
eAmf marker;
int8_t marker;
inStream->Read(marker);
// Based on the typing, create the value associated with that and return the base value class
switch (marker) {
case eAmf::Undefined: {
returnValue = new AMFBaseValue();
case AMFValueType::AMFUndefined: {
returnValue = new AMFUndefinedValue();
break;
}
case eAmf::Null: {
case AMFValueType::AMFNull: {
returnValue = new AMFNullValue();
break;
}
case eAmf::False: {
returnValue = new AMFBoolValue(false);
case AMFValueType::AMFFalse: {
returnValue = new AMFFalseValue();
break;
}
case eAmf::True: {
returnValue = new AMFBoolValue(true);
case AMFValueType::AMFTrue: {
returnValue = new AMFTrueValue();
break;
}
case eAmf::Integer: {
case AMFValueType::AMFInteger: {
returnValue = ReadAmfInteger(inStream);
break;
}
case eAmf::Double: {
case AMFValueType::AMFDouble: {
returnValue = ReadAmfDouble(inStream);
break;
}
case eAmf::String: {
case AMFValueType::AMFString: {
returnValue = ReadAmfString(inStream);
break;
}
case eAmf::Array: {
case AMFValueType::AMFArray: {
returnValue = ReadAmfArray(inStream);
break;
}
// These values are unimplemented in the live client and will remain unimplemented
// unless someone modifies the client to allow serializing of these values.
case eAmf::XMLDoc:
case eAmf::Date:
case eAmf::Object:
case eAmf::XML:
case eAmf::ByteArray:
case eAmf::VectorInt:
case eAmf::VectorUInt:
case eAmf::VectorDouble:
case eAmf::VectorObject:
case eAmf::Dictionary: {
throw marker;
// TODO We do not need these values, but if someone wants to implement them
// then please do so and add the corresponding unit tests.
case AMFValueType::AMFXMLDoc:
case AMFValueType::AMFDate:
case AMFValueType::AMFObject:
case AMFValueType::AMFXML:
case AMFValueType::AMFByteArray:
case AMFValueType::AMFVectorInt:
case AMFValueType::AMFVectorUInt:
case AMFValueType::AMFVectorDouble:
case AMFValueType::AMFVectorObject:
case AMFValueType::AMFDictionary: {
throw static_cast<AMFValueType>(marker);
break;
}
default:
throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker)));
throw static_cast<AMFValueType>(marker);
break;
}
return returnValue;
@@ -101,7 +99,7 @@ uint32_t AMFDeserialize::ReadU29(RakNet::BitStream* inStream) {
return actualNumber;
}
const std::string AMFDeserialize::ReadString(RakNet::BitStream* inStream) {
std::string AMFDeserialize::ReadString(RakNet::BitStream* inStream) {
auto length = ReadU29(inStream);
// Check if this is a reference
bool isReference = length % 2 == 1;
@@ -115,39 +113,48 @@ const std::string AMFDeserialize::ReadString(RakNet::BitStream* inStream) {
return value;
} else {
// Length is a reference to a previous index - use that as the read in value
return accessedElements.at(length);
return accessedElements[length];
}
}
AMFBaseValue* AMFDeserialize::ReadAmfDouble(RakNet::BitStream* inStream) {
AMFValue* AMFDeserialize::ReadAmfDouble(RakNet::BitStream* inStream) {
auto doubleValue = new AMFDoubleValue();
double value;
inStream->Read<double>(value);
return new AMFDoubleValue(value);
doubleValue->SetDoubleValue(value);
return doubleValue;
}
AMFBaseValue* AMFDeserialize::ReadAmfArray(RakNet::BitStream* inStream) {
AMFValue* AMFDeserialize::ReadAmfArray(RakNet::BitStream* inStream) {
auto arrayValue = new AMFArrayValue();
// Read size of dense array
auto sizeOfDenseArray = (ReadU29(inStream) >> 1);
// Then read associative portion
// Then read Key'd portion
while (true) {
auto key = ReadString(inStream);
// No more associative values when we encounter an empty string key
// No more values when we encounter an empty string
if (key.size() == 0) break;
arrayValue->Insert(key, Read(inStream));
arrayValue->InsertValue(key, Read(inStream));
}
// Finally read dense portion
for (uint32_t i = 0; i < sizeOfDenseArray; i++) {
arrayValue->Insert(i, Read(inStream));
arrayValue->PushBackValue(Read(inStream));
}
return arrayValue;
}
AMFBaseValue* AMFDeserialize::ReadAmfString(RakNet::BitStream* inStream) {
return new AMFStringValue(ReadString(inStream));
AMFValue* AMFDeserialize::ReadAmfString(RakNet::BitStream* inStream) {
auto stringValue = new AMFStringValue();
stringValue->SetStringValue(ReadString(inStream));
return stringValue;
}
AMFBaseValue* AMFDeserialize::ReadAmfInteger(RakNet::BitStream* inStream) {
return new AMFIntValue(ReadU29(inStream));
AMFValue* AMFDeserialize::ReadAmfInteger(RakNet::BitStream* inStream) {
auto integerValue = new AMFIntegerValue();
integerValue->SetIntegerValue(ReadU29(inStream));
return integerValue;
}

View File

@@ -5,8 +5,7 @@
#include <vector>
#include <string>
class AMFBaseValue;
class AMFValue;
class AMFDeserialize {
public:
/**
@@ -15,7 +14,7 @@ public:
* @param inStream inStream to read value from.
* @return Returns an AMFValue with all the information from the bitStream in it.
*/
AMFBaseValue* Read(RakNet::BitStream* inStream);
AMFValue* Read(RakNet::BitStream* inStream);
private:
/**
* @brief Private method to read a U29 integer from a bitstream
@@ -31,7 +30,7 @@ private:
* @param inStream bitStream to read data from
* @return The read string
*/
const std::string ReadString(RakNet::BitStream* inStream);
std::string ReadString(RakNet::BitStream* inStream);
/**
* @brief Read an AMFDouble value from a bitStream
@@ -39,7 +38,7 @@ private:
* @param inStream bitStream to read data from
* @return Double value represented as an AMFValue
*/
AMFBaseValue* ReadAmfDouble(RakNet::BitStream* inStream);
AMFValue* ReadAmfDouble(RakNet::BitStream* inStream);
/**
* @brief Read an AMFArray from a bitStream
@@ -47,7 +46,7 @@ private:
* @param inStream bitStream to read data from
* @return Array value represented as an AMFValue
*/
AMFBaseValue* ReadAmfArray(RakNet::BitStream* inStream);
AMFValue* ReadAmfArray(RakNet::BitStream* inStream);
/**
* @brief Read an AMFString from a bitStream
@@ -55,7 +54,7 @@ private:
* @param inStream bitStream to read data from
* @return String value represented as an AMFValue
*/
AMFBaseValue* ReadAmfString(RakNet::BitStream* inStream);
AMFValue* ReadAmfString(RakNet::BitStream* inStream);
/**
* @brief Read an AMFInteger from a bitStream
@@ -63,7 +62,7 @@ private:
* @param inStream bitStream to read data from
* @return Integer value represented as an AMFValue
*/
AMFBaseValue* ReadAmfInteger(RakNet::BitStream* inStream);
AMFValue* ReadAmfInteger(RakNet::BitStream* inStream);
/**
* List of strings read so far saved to be read by reference.

156
dCommon/AMFFormat.cpp Normal file
View File

@@ -0,0 +1,156 @@
#include "AMFFormat.h"
// AMFInteger
void AMFIntegerValue::SetIntegerValue(uint32_t value) {
this->value = value;
}
uint32_t AMFIntegerValue::GetIntegerValue() {
return this->value;
}
// AMFDouble
void AMFDoubleValue::SetDoubleValue(double value) {
this->value = value;
}
double AMFDoubleValue::GetDoubleValue() {
return this->value;
}
// AMFString
void AMFStringValue::SetStringValue(const std::string& value) {
this->value = value;
}
std::string AMFStringValue::GetStringValue() {
return this->value;
}
// AMFXMLDoc
void AMFXMLDocValue::SetXMLDocValue(const std::string& value) {
this->xmlData = value;
}
std::string AMFXMLDocValue::GetXMLDocValue() {
return this->xmlData;
}
// AMFDate
void AMFDateValue::SetDateValue(uint64_t value) {
this->millisecondTimestamp = value;
}
uint64_t AMFDateValue::GetDateValue() {
return this->millisecondTimestamp;
}
// AMFArray Insert Value
void AMFArrayValue::InsertValue(const std::string& key, AMFValue* value) {
this->associative.insert(std::make_pair(key, value));
}
// AMFArray Remove Value
void AMFArrayValue::RemoveValue(const std::string& key) {
_AMFArrayMap_::iterator it = this->associative.find(key);
if (it != this->associative.end()) {
this->associative.erase(it);
}
}
// AMFArray Get Associative Iterator Begin
_AMFArrayMap_::iterator AMFArrayValue::GetAssociativeIteratorValueBegin() {
return this->associative.begin();
}
// AMFArray Get Associative Iterator End
_AMFArrayMap_::iterator AMFArrayValue::GetAssociativeIteratorValueEnd() {
return this->associative.end();
}
// AMFArray Push Back Value
void AMFArrayValue::PushBackValue(AMFValue* value) {
this->dense.push_back(value);
}
// AMFArray Pop Back Value
void AMFArrayValue::PopBackValue() {
this->dense.pop_back();
}
// AMFArray Get Dense List Size
uint32_t AMFArrayValue::GetDenseValueSize() {
return (uint32_t)this->dense.size();
}
// AMFArray Get Dense Iterator Begin
_AMFArrayList_::iterator AMFArrayValue::GetDenseIteratorBegin() {
return this->dense.begin();
}
// AMFArray Get Dense Iterator End
_AMFArrayList_::iterator AMFArrayValue::GetDenseIteratorEnd() {
return this->dense.end();
}
AMFArrayValue::~AMFArrayValue() {
for (auto valueToDelete : GetDenseArray()) {
if (valueToDelete) delete valueToDelete;
}
for (auto valueToDelete : GetAssociativeMap()) {
if (valueToDelete.second) delete valueToDelete.second;
}
}
// AMFObject Constructor
AMFObjectValue::AMFObjectValue(std::vector<std::pair<std::string, AMFValueType>> traits) {
this->traits.reserve(traits.size());
std::vector<std::pair<std::string, AMFValueType>>::iterator it = traits.begin();
while (it != traits.end()) {
this->traits.insert(std::make_pair(it->first, std::make_pair(it->second, new AMFNullValue())));
it++;
}
}
// AMFObject Set Value
void AMFObjectValue::SetTraitValue(const std::string& trait, AMFValue* value) {
if (value) {
_AMFObjectTraits_::iterator it = this->traits.find(trait);
if (it != this->traits.end()) {
if (it->second.first == value->GetValueType()) {
it->second.second = value;
}
}
}
}
// AMFObject Get Value
AMFValue* AMFObjectValue::GetTraitValue(const std::string& trait) {
_AMFObjectTraits_::iterator it = this->traits.find(trait);
if (it != this->traits.end()) {
return it->second.second;
}
return nullptr;
}
// AMFObject Get Trait Iterator Begin
_AMFObjectTraits_::iterator AMFObjectValue::GetTraitsIteratorBegin() {
return this->traits.begin();
}
// AMFObject Get Trait Iterator End
_AMFObjectTraits_::iterator AMFObjectValue::GetTraitsIteratorEnd() {
return this->traits.end();
}
// AMFObject Get Trait Size
uint32_t AMFObjectValue::GetTraitArrayCount() {
return (uint32_t)this->traits.size();
}
AMFObjectValue::~AMFObjectValue() {
for (auto valueToDelete = GetTraitsIteratorBegin(); valueToDelete != GetTraitsIteratorEnd(); valueToDelete++) {
if (valueToDelete->second.second) delete valueToDelete->second.second;
}
}

413
dCommon/AMFFormat.h Normal file
View File

@@ -0,0 +1,413 @@
#pragma once
// Custom Classes
#include "dCommonVars.h"
// C++
#include <unordered_map>
#include <vector>
/*!
\file AMFFormat.hpp
\brief A class for managing AMF values
*/
class AMFValue; // Forward declaration
// Definitions
#define _AMFArrayMap_ std::unordered_map<std::string, AMFValue*>
#define _AMFArrayList_ std::vector<AMFValue*>
#define _AMFObjectTraits_ std::unordered_map<std::string, std::pair<AMFValueType, AMFValue*>>
#define _AMFObjectDynamicTraits_ std::unordered_map<std::string, AMFValue*>
//! An enum for each AMF value type
enum AMFValueType : unsigned char {
AMFUndefined = 0x00, //!< An undefined AMF Value
AMFNull = 0x01, //!< A null AMF value
AMFFalse = 0x02, //!< A false AMF value
AMFTrue = 0x03, //!< A true AMF value
AMFInteger = 0x04, //!< An integer AMF value
AMFDouble = 0x05, //!< A double AMF value
AMFString = 0x06, //!< A string AMF value
AMFXMLDoc = 0x07, //!< An XML Doc AMF value
AMFDate = 0x08, //!< A date AMF value
AMFArray = 0x09, //!< An array AMF value
AMFObject = 0x0A, //!< An object AMF value
AMFXML = 0x0B, //!< An XML AMF value
AMFByteArray = 0x0C, //!< A byte array AMF value
AMFVectorInt = 0x0D, //!< An integer vector AMF value
AMFVectorUInt = 0x0E, //!< An unsigned integer AMF value
AMFVectorDouble = 0x0F, //!< A double vector AMF value
AMFVectorObject = 0x10, //!< An object vector AMF value
AMFDictionary = 0x11 //!< A dictionary AMF value
};
//! An enum for the object value types
enum AMFObjectValueType : unsigned char {
AMFObjectAnonymous = 0x01,
AMFObjectTyped = 0x02,
AMFObjectDynamic = 0x03,
AMFObjectExternalizable = 0x04
};
//! The base AMF value class
class AMFValue {
public:
//! Returns the AMF value type
/*!
\return The AMF value type
*/
virtual AMFValueType GetValueType() = 0;
virtual ~AMFValue() {};
};
//! A typedef for a pointer to an AMF value
typedef AMFValue* NDGFxValue;
// The various AMF value types
//! The undefined value AMF type
class AMFUndefinedValue : public AMFValue {
private:
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFUndefined;
};
//! The null value AMF type
class AMFNullValue : public AMFValue {
private:
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFNull;
};
//! The false value AMF type
class AMFFalseValue : public AMFValue {
private:
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFFalse;
};
//! The true value AMF type
class AMFTrueValue : public AMFValue {
private:
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFTrue;
};
//! The integer value AMF type
class AMFIntegerValue : public AMFValue {
private:
uint32_t value; //!< The value of the AMF type
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFInteger;
//! Sets the integer value
/*!
\param value The value to set
*/
void SetIntegerValue(uint32_t value);
//! Gets the integer value
/*!
\return The integer value
*/
uint32_t GetIntegerValue();
};
//! The double value AMF type
class AMFDoubleValue : public AMFValue {
private:
double value; //!< The value of the AMF type
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFDouble;
//! Sets the double value
/*!
\param value The value to set to
*/
void SetDoubleValue(double value);
//! Gets the double value
/*!
\return The double value
*/
double GetDoubleValue();
};
//! The string value AMF type
class AMFStringValue : public AMFValue {
private:
std::string value; //!< The value of the AMF type
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFString;
//! Sets the string value
/*!
\param value The string value to set to
*/
void SetStringValue(const std::string& value);
//! Gets the string value
/*!
\return The string value
*/
std::string GetStringValue();
};
//! The XML doc value AMF type
class AMFXMLDocValue : public AMFValue {
private:
std::string xmlData; //!< The value of the AMF type
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFXMLDoc;
//! Sets the XML Doc value
/*!
\param value The value to set to
*/
void SetXMLDocValue(const std::string& value);
//! Gets the XML Doc value
/*!
\return The XML Doc value
*/
std::string GetXMLDocValue();
};
//! The date value AMF type
class AMFDateValue : public AMFValue {
private:
uint64_t millisecondTimestamp; //!< The time in milliseconds since the ephoch
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFDate;
//! Sets the date time
/*!
\param value The value to set to
*/
void SetDateValue(uint64_t value);
//! Gets the date value
/*!
\return The date value in milliseconds since the epoch
*/
uint64_t GetDateValue();
};
//! The array value AMF type
// This object will manage it's own memory map and list. Do not delete its values.
class AMFArrayValue : public AMFValue {
private:
_AMFArrayMap_ associative; //!< The array map (associative part)
_AMFArrayList_ dense; //!< The array list (dense part)
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() override { return ValueType; }
public:
static const AMFValueType ValueType = AMFArray;
~AMFArrayValue() override;
//! Inserts an item into the array map for a specific key
/*!
\param key The key to set
\param value The value to add
*/
void InsertValue(const std::string& key, AMFValue* value);
//! Removes an item for a specific key
/*!
\param key The key to remove
*/
void RemoveValue(const std::string& key);
//! Finds an AMF value
/*!
\return The AMF value if found, nullptr otherwise
*/
template <typename T>
T* FindValue(const std::string& key) const {
_AMFArrayMap_::const_iterator it = this->associative.find(key);
if (it != this->associative.end() && T::ValueType == it->second->GetValueType()) {
return dynamic_cast<T*>(it->second);
}
return nullptr;
};
//! Returns where the associative iterator begins
/*!
\return Where the array map iterator begins
*/
_AMFArrayMap_::iterator GetAssociativeIteratorValueBegin();
//! Returns where the associative iterator ends
/*!
\return Where the array map iterator ends
*/
_AMFArrayMap_::iterator GetAssociativeIteratorValueEnd();
//! Pushes back a value into the array list
/*!
\param value The value to push back
*/
void PushBackValue(AMFValue* value);
//! Pops back the last value in the array list
void PopBackValue();
//! Gets the count of the dense list
/*!
\return The dense list size
*/
uint32_t GetDenseValueSize();
//! Gets a specific value from the list for the specified index
/*!
\param index The index to get
*/
template <typename T>
T* GetValueAt(uint32_t index) {
if (index >= this->dense.size()) return nullptr;
AMFValue* foundValue = this->dense.at(index);
return T::ValueType == foundValue->GetValueType() ? dynamic_cast<T*>(foundValue) : nullptr;
};
//! Returns where the dense iterator begins
/*!
\return Where the iterator begins
*/
_AMFArrayList_::iterator GetDenseIteratorBegin();
//! Returns where the dense iterator ends
/*!
\return Where the iterator ends
*/
_AMFArrayList_::iterator GetDenseIteratorEnd();
//! Returns the associative map
/*!
\return The associative map
*/
_AMFArrayMap_ GetAssociativeMap() { return this->associative; };
//! Returns the dense array
/*!
\return The dense array
*/
_AMFArrayList_ GetDenseArray() { return this->dense; };
};
//! The anonymous object value AMF type
class AMFObjectValue : public AMFValue {
private:
_AMFObjectTraits_ traits; //!< The object traits
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() override { return ValueType; }
~AMFObjectValue() override;
public:
static const AMFValueType ValueType = AMFObject;
//! Constructor
/*!
\param traits The traits to set
*/
AMFObjectValue(std::vector<std::pair<std::string, AMFValueType>> traits);
//! Gets the object value type
/*!
\return The object value type
*/
virtual AMFObjectValueType GetObjectValueType() { return AMFObjectAnonymous; }
//! Sets the value of a trait
/*!
\param trait The trait to set the value for
\param value The AMF value to set
*/
void SetTraitValue(const std::string& trait, AMFValue* value);
//! Gets a trait value
/*!
\param trait The trait to get the value for
\return The trait value
*/
AMFValue* GetTraitValue(const std::string& trait);
//! Gets the beginning of the object traits iterator
/*!
\return The AMF trait array iterator begin
*/
_AMFObjectTraits_::iterator GetTraitsIteratorBegin();
//! Gets the end of the object traits iterator
/*!
\return The AMF trait array iterator begin
*/
_AMFObjectTraits_::iterator GetTraitsIteratorEnd();
//! Gets the amount of traits
/*!
\return The amount of traits
*/
uint32_t GetTraitArrayCount();
};

View File

@@ -0,0 +1,259 @@
#include "AMFFormat_BitStream.h"
// Writes an AMFValue pointer to a RakNet::BitStream
template<>
void RakNet::BitStream::Write<AMFValue*>(AMFValue* value) {
if (value != nullptr) {
AMFValueType type = value->GetValueType();
switch (type) {
case AMFUndefined: {
AMFUndefinedValue* v = (AMFUndefinedValue*)value;
this->Write(*v);
break;
}
case AMFNull: {
AMFNullValue* v = (AMFNullValue*)value;
this->Write(*v);
break;
}
case AMFFalse: {
AMFFalseValue* v = (AMFFalseValue*)value;
this->Write(*v);
break;
}
case AMFTrue: {
AMFTrueValue* v = (AMFTrueValue*)value;
this->Write(*v);
break;
}
case AMFInteger: {
AMFIntegerValue* v = (AMFIntegerValue*)value;
this->Write(*v);
break;
}
case AMFDouble: {
AMFDoubleValue* v = (AMFDoubleValue*)value;
this->Write(*v);
break;
}
case AMFString: {
AMFStringValue* v = (AMFStringValue*)value;
this->Write(*v);
break;
}
case AMFXMLDoc: {
AMFXMLDocValue* v = (AMFXMLDocValue*)value;
this->Write(*v);
break;
}
case AMFDate: {
AMFDateValue* v = (AMFDateValue*)value;
this->Write(*v);
break;
}
case AMFArray: {
this->Write((AMFArrayValue*)value);
break;
}
case AMFObject:
case AMFXML:
case AMFByteArray:
case AMFVectorInt:
case AMFVectorUInt:
case AMFVectorDouble:
case AMFVectorObject:
case AMFDictionary:
break;
}
}
}
/**
* A private function to write an value to a RakNet::BitStream
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteUInt29(RakNet::BitStream* bs, uint32_t v) {
unsigned char b4 = (unsigned char)v;
if (v < 0x00200000) {
b4 = b4 & 0x7F;
if (v > 0x7F) {
unsigned char b3;
v = v >> 7;
b3 = ((unsigned char)(v)) | 0x80;
if (v > 0x7F) {
unsigned char b2;
v = v >> 7;
b2 = ((unsigned char)(v)) | 0x80;
bs->Write(b2);
}
bs->Write(b3);
}
} else {
unsigned char b1;
unsigned char b2;
unsigned char b3;
v = v >> 8;
b3 = ((unsigned char)(v)) | 0x80;
v = v >> 7;
b2 = ((unsigned char)(v)) | 0x80;
v = v >> 7;
b1 = ((unsigned char)(v)) | 0x80;
bs->Write(b1);
bs->Write(b2);
bs->Write(b3);
}
bs->Write(b4);
}
/**
* Writes a flag number to a RakNet::BitStream
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteFlagNumber(RakNet::BitStream* bs, uint32_t v) {
v = (v << 1) | 0x01;
WriteUInt29(bs, v);
}
/**
* Writes an AMFString to a RakNet::BitStream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFString(RakNet::BitStream* bs, const std::string& str) {
WriteFlagNumber(bs, (uint32_t)str.size());
bs->Write(str.c_str(), (uint32_t)str.size());
}
/**
* Writes an U16 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU16(RakNet::BitStream* bs, uint16_t value) {
bs->Write(value);
}
/**
* Writes an U32 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU32(RakNet::BitStream* bs, uint32_t value) {
bs->Write(value);
}
/**
* Writes an U64 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU64(RakNet::BitStream* bs, uint64_t value) {
bs->Write(value);
}
// Writes an AMFUndefinedValue to BitStream
template<>
void RakNet::BitStream::Write<AMFUndefinedValue>(AMFUndefinedValue value) {
this->Write(AMFUndefined);
}
// Writes an AMFNullValue to BitStream
template<>
void RakNet::BitStream::Write<AMFNullValue>(AMFNullValue value) {
this->Write(AMFNull);
}
// Writes an AMFFalseValue to BitStream
template<>
void RakNet::BitStream::Write<AMFFalseValue>(AMFFalseValue value) {
this->Write(AMFFalse);
}
// Writes an AMFTrueValue to BitStream
template<>
void RakNet::BitStream::Write<AMFTrueValue>(AMFTrueValue value) {
this->Write(AMFTrue);
}
// Writes an AMFIntegerValue to BitStream
template<>
void RakNet::BitStream::Write<AMFIntegerValue>(AMFIntegerValue value) {
this->Write(AMFInteger);
WriteUInt29(this, value.GetIntegerValue());
}
// Writes an AMFDoubleValue to BitStream
template<>
void RakNet::BitStream::Write<AMFDoubleValue>(AMFDoubleValue value) {
this->Write(AMFDouble);
double d = value.GetDoubleValue();
WriteAMFU64(this, *((unsigned long long*) & d));
}
// Writes an AMFStringValue to BitStream
template<>
void RakNet::BitStream::Write<AMFStringValue>(AMFStringValue value) {
this->Write(AMFString);
std::string v = value.GetStringValue();
WriteAMFString(this, v);
}
// Writes an AMFXMLDocValue to BitStream
template<>
void RakNet::BitStream::Write<AMFXMLDocValue>(AMFXMLDocValue value) {
this->Write(AMFXMLDoc);
std::string v = value.GetXMLDocValue();
WriteAMFString(this, v);
}
// Writes an AMFDateValue to BitStream
template<>
void RakNet::BitStream::Write<AMFDateValue>(AMFDateValue value) {
this->Write(AMFDate);
uint64_t date = value.GetDateValue();
WriteAMFU64(this, date);
}
// Writes an AMFArrayValue to BitStream
template<>
void RakNet::BitStream::Write<AMFArrayValue*>(AMFArrayValue* value) {
this->Write(AMFArray);
uint32_t denseSize = value->GetDenseValueSize();
WriteFlagNumber(this, denseSize);
_AMFArrayMap_::iterator it = value->GetAssociativeIteratorValueBegin();
_AMFArrayMap_::iterator end = value->GetAssociativeIteratorValueEnd();
while (it != end) {
WriteAMFString(this, it->first);
this->Write(it->second);
it++;
}
this->Write(AMFNull);
if (denseSize > 0) {
_AMFArrayList_::iterator it2 = value->GetDenseIteratorBegin();
_AMFArrayList_::iterator end2 = value->GetDenseIteratorEnd();
while (it2 != end2) {
this->Write(*it2);
it2++;
}
}
}

View File

@@ -0,0 +1,92 @@
#pragma once
// Custom Classes
#include "AMFFormat.h"
// RakNet
#include <BitStream.h>
/*!
\file AMFFormat_BitStream.h
\brief A class that implements native writing of AMF values to RakNet::BitStream
*/
// We are using the RakNet namespace
namespace RakNet {
//! Writes an AMFValue pointer to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFValue*>(AMFValue* value);
//! Writes an AMFUndefinedValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFUndefinedValue>(AMFUndefinedValue value);
//! Writes an AMFNullValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFNullValue>(AMFNullValue value);
//! Writes an AMFFalseValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFFalseValue>(AMFFalseValue value);
//! Writes an AMFTrueValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFTrueValue>(AMFTrueValue value);
//! Writes an AMFIntegerValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFIntegerValue>(AMFIntegerValue value);
//! Writes an AMFDoubleValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFDoubleValue>(AMFDoubleValue value);
//! Writes an AMFStringValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFStringValue>(AMFStringValue value);
//! Writes an AMFXMLDocValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFXMLDocValue>(AMFXMLDocValue value);
//! Writes an AMFDateValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFDateValue>(AMFDateValue value);
//! Writes an AMFArrayValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFArrayValue*>(AMFArrayValue* value);
} // namespace RakNet

View File

@@ -1,367 +0,0 @@
#ifndef __AMF3__H__
#define __AMF3__H__
#include "dCommonVars.h"
#include "dLogger.h"
#include "Game.h"
#include <unordered_map>
#include <vector>
enum class eAmf : uint8_t {
Undefined = 0x00, // An undefined AMF Value
Null = 0x01, // A null AMF value
False = 0x02, // A false AMF value
True = 0x03, // A true AMF value
Integer = 0x04, // An integer AMF value
Double = 0x05, // A double AMF value
String = 0x06, // A string AMF value
XMLDoc = 0x07, // Unused in the live client and cannot be serialized without modification. An XML Doc AMF value
Date = 0x08, // Unused in the live client and cannot be serialized without modification. A date AMF value
Array = 0x09, // An array AMF value
Object = 0x0A, // Unused in the live client and cannot be serialized without modification. An object AMF value
XML = 0x0B, // Unused in the live client and cannot be serialized without modification. An XML AMF value
ByteArray = 0x0C, // Unused in the live client and cannot be serialized without modification. A byte array AMF value
VectorInt = 0x0D, // Unused in the live client and cannot be serialized without modification. An integer vector AMF value
VectorUInt = 0x0E, // Unused in the live client and cannot be serialized without modification. An unsigned integer AMF value
VectorDouble = 0x0F, // Unused in the live client and cannot be serialized without modification. A double vector AMF value
VectorObject = 0x10, // Unused in the live client and cannot be serialized without modification. An object vector AMF value
Dictionary = 0x11 // Unused in the live client and cannot be serialized without modification. A dictionary AMF value
};
class AMFBaseValue {
public:
virtual eAmf GetValueType() { return eAmf::Undefined; };
AMFBaseValue() {};
virtual ~AMFBaseValue() {};
};
template<typename ValueType>
class AMFValue : public AMFBaseValue {
public:
AMFValue() {};
AMFValue(ValueType value) { SetValue(value); };
virtual ~AMFValue() override {};
eAmf GetValueType() override { return eAmf::Undefined; };
const ValueType& GetValue() { return data; };
void SetValue(ValueType value) { data = value; };
protected:
ValueType data;
};
// As a string this is much easier to write and read from a BitStream.
template<>
class AMFValue<const char*> : public AMFBaseValue {
public:
AMFValue() {};
AMFValue(const char* value) { SetValue(std::string(value)); };
virtual ~AMFValue() override {};
eAmf GetValueType() override { return eAmf::String; };
const std::string& GetValue() { return data; };
void SetValue(std::string value) { data = value; };
protected:
std::string data;
};
typedef AMFValue<std::nullptr_t> AMFNullValue;
typedef AMFValue<bool> AMFBoolValue;
typedef AMFValue<int32_t> AMFIntValue;
typedef AMFValue<std::string> AMFStringValue;
typedef AMFValue<double> AMFDoubleValue;
template<> inline eAmf AMFValue<std::nullptr_t>::GetValueType() { return eAmf::Null; };
template<> inline eAmf AMFValue<bool>::GetValueType() { return this->data ? eAmf::True : eAmf::False; };
template<> inline eAmf AMFValue<int32_t>::GetValueType() { return eAmf::Integer; };
template<> inline eAmf AMFValue<uint32_t>::GetValueType() { return eAmf::Integer; };
template<> inline eAmf AMFValue<std::string>::GetValueType() { return eAmf::String; };
template<> inline eAmf AMFValue<double>::GetValueType() { return eAmf::Double; };
/**
* The AMFArrayValue object holds 2 types of lists:
* An associative list where a key maps to a value
* A Dense list where elements are stored back to back
*
* Objects that are Registered are owned by this object
* and are not to be deleted by a caller.
*/
class AMFArrayValue : public AMFBaseValue {
typedef std::unordered_map<std::string, AMFBaseValue*> AMFAssociative;
typedef std::vector<AMFBaseValue*> AMFDense;
public:
eAmf GetValueType() override { return eAmf::Array; };
~AMFArrayValue() override {
for (auto valueToDelete : GetDense()) {
if (valueToDelete) {
delete valueToDelete;
valueToDelete = nullptr;
}
}
for (auto valueToDelete : GetAssociative()) {
if (valueToDelete.second) {
delete valueToDelete.second;
valueToDelete.second = nullptr;
}
}
};
/**
* Returns the Associative portion of the object
*/
inline AMFAssociative& GetAssociative() { return this->associative; };
/**
* Returns the dense portion of the object
*/
inline AMFDense& GetDense() { return this->dense; };
/**
* Inserts an AMFValue into the associative portion with the given key.
* If a duplicate is attempted to be inserted, it is ignored and the
* first value with that key is kept in the map.
*
* These objects are not to be deleted by the caller as they are owned by
* the AMFArray object which manages its own memory.
*
* @param key The key to associate with the value
* @param value The value to insert
*
* @return The inserted element if the type matched,
* or nullptr if a key existed and was not the same type
*/
template<typename ValueType>
std::pair<AMFValue<ValueType>*, bool> Insert(const std::string& key, ValueType value) {
auto element = associative.find(key);
AMFValue<ValueType>* val = nullptr;
bool found = true;
if (element == associative.end()) {
val = new AMFValue<ValueType>(value);
associative.insert(std::make_pair(key, val));
} else {
val = dynamic_cast<AMFValue<ValueType>*>(element->second);
found = false;
}
return std::make_pair(val, found);
};
// Associates an array with a string key
std::pair<AMFBaseValue*, bool> Insert(const std::string& key) {
auto element = associative.find(key);
AMFArrayValue* val = nullptr;
bool found = true;
if (element == associative.end()) {
val = new AMFArrayValue();
associative.insert(std::make_pair(key, val));
} else {
val = dynamic_cast<AMFArrayValue*>(element->second);
found = false;
}
return std::make_pair(val, found);
};
// Associates an array with an integer key
std::pair<AMFBaseValue*, bool> Insert(const uint32_t& index) {
AMFArrayValue* val = nullptr;
bool inserted = false;
if (index >= dense.size()) {
dense.resize(index + 1);
val = new AMFArrayValue();
dense.at(index) = val;
inserted = true;
}
return std::make_pair(dynamic_cast<AMFArrayValue*>(dense.at(index)), inserted);
};
/**
* @brief Inserts an AMFValue into the AMFArray key'd by index.
* Attempting to insert the same key to the same value twice overwrites
* the previous value with the new one.
*
* @param index The index to associate with the value
* @param value The value to insert
* @return The inserted element, or nullptr if the type did not match
* what was at the index.
*/
template<typename ValueType>
std::pair<AMFValue<ValueType>*, bool> Insert(const uint32_t& index, ValueType value) {
AMFValue<ValueType>* val = nullptr;
bool inserted = false;
if (index >= this->dense.size()) {
this->dense.resize(index + 1);
val = new AMFValue<ValueType>(value);
this->dense.at(index) = val;
inserted = true;
}
return std::make_pair(dynamic_cast<AMFValue<ValueType>*>(this->dense.at(index)), inserted);
};
/**
* Inserts an AMFValue into the associative portion with the given key.
* If a duplicate is attempted to be inserted, it replaces the original
*
* The inserted element is now owned by this object and is not to be deleted
*
* @param key The key to associate with the value
* @param value The value to insert
*/
void Insert(const std::string& key, AMFBaseValue* value) {
auto element = associative.find(key);
if (element != associative.end() && element->second) {
delete element->second;
element->second = value;
} else {
associative.insert(std::make_pair(key, value));
}
};
/**
* Inserts an AMFValue into the associative portion with the given index.
* If a duplicate is attempted to be inserted, it replaces the original
*
* The inserted element is now owned by this object and is not to be deleted
*
* @param key The key to associate with the value
* @param value The value to insert
*/
void Insert(const uint32_t index, AMFBaseValue* value) {
if (index < dense.size()) {
AMFDense::iterator itr = dense.begin() + index;
if (*itr) delete dense.at(index);
} else {
dense.resize(index + 1);
}
dense.at(index) = value;
};
/**
* Pushes an AMFValue into the back of the dense portion.
*
* These objects are not to be deleted by the caller as they are owned by
* the AMFArray object which manages its own memory.
*
* @param value The value to insert
*
* @return The inserted pointer, or nullptr should the key already be in use.
*/
template<typename ValueType>
inline AMFValue<ValueType>* Push(ValueType value) {
return Insert(this->dense.size(), value).first;
};
/**
* Removes the key from the associative portion
*
* The pointer removed is now no longer managed by this container
*
* @param key The key to remove from the associative portion
*/
void Remove(const std::string& key, bool deleteValue = true) {
AMFAssociative::iterator it = this->associative.find(key);
if (it != this->associative.end()) {
if (deleteValue) delete it->second;
this->associative.erase(it);
}
}
/**
* Pops the last element in the dense portion, deleting it in the process.
*/
void Remove(const uint32_t index) {
if (!this->dense.empty() && index < this->dense.size()) {
auto itr = this->dense.begin() + index;
if (*itr) delete (*itr);
this->dense.erase(itr);
}
}
void Pop() {
if (!this->dense.empty()) Remove(this->dense.size() - 1);
}
AMFArrayValue* GetArray(const std::string& key) {
AMFAssociative::const_iterator it = this->associative.find(key);
if (it != this->associative.end()) {
return dynamic_cast<AMFArrayValue*>(it->second);
}
return nullptr;
};
AMFArrayValue* GetArray(const uint32_t index) {
return index >= this->dense.size() ? nullptr : dynamic_cast<AMFArrayValue*>(this->dense.at(index));
};
inline AMFArrayValue* InsertArray(const std::string& key) {
return static_cast<AMFArrayValue*>(Insert(key).first);
};
inline AMFArrayValue* InsertArray(const uint32_t index) {
return static_cast<AMFArrayValue*>(Insert(index).first);
};
inline AMFArrayValue* PushArray() {
return static_cast<AMFArrayValue*>(Insert(this->dense.size()).first);
};
/**
* Gets an AMFValue by the key from the associative portion and converts it
* to the AmfValue template type. If the key did not exist, it is inserted.
*
* @tparam The target object type
* @param key The key to lookup
*
* @return The AMFValue
*/
template <typename AmfType>
AMFValue<AmfType>* Get(const std::string& key) const {
AMFAssociative::const_iterator it = this->associative.find(key);
return it != this->associative.end() ?
dynamic_cast<AMFValue<AmfType>*>(it->second) :
nullptr;
};
// Get from the array but dont cast it
AMFBaseValue* Get(const std::string& key) const {
AMFAssociative::const_iterator it = this->associative.find(key);
return it != this->associative.end() ? it->second : nullptr;
};
/**
* @brief Get an AMFValue object at a position in the dense portion.
* Gets an AMFValue by the index from the dense portion and converts it
* to the AmfValue template type. If the index did not exist, it is inserted.
*
* @tparam The target object type
* @param index The index to get
* @return The casted object, or nullptr.
*/
template <typename AmfType>
AMFValue<AmfType>* Get(uint32_t index) const {
return index < this->dense.size() ?
dynamic_cast<AMFValue<AmfType>*>(this->dense.at(index)) :
nullptr;
};
// Get from the dense but dont cast it
AMFBaseValue* Get(const uint32_t index) const {
return index < this->dense.size() ? this->dense.at(index) : nullptr;
};
private:
/**
* The associative portion. These values are key'd with strings to an AMFValue.
*/
AMFAssociative associative;
/**
* The dense portion. These AMFValue's are stored one after
* another with the most recent addition being at the back.
*/
AMFDense dense;
};
#endif //!__AMF3__H__

View File

@@ -1,184 +0,0 @@
#include "AmfSerialize.h"
#include "Game.h"
#include "dLogger.h"
// Writes an AMFValue pointer to a RakNet::BitStream
template<>
void RakNet::BitStream::Write<AMFBaseValue&>(AMFBaseValue& value) {
eAmf type = value.GetValueType();
this->Write(type);
switch (type) {
case eAmf::Integer: {
this->Write<AMFIntValue&>(*static_cast<AMFIntValue*>(&value));
break;
}
case eAmf::Double: {
this->Write<AMFDoubleValue&>(*static_cast<AMFDoubleValue*>(&value));
break;
}
case eAmf::String: {
this->Write<AMFStringValue&>(*static_cast<AMFStringValue*>(&value));
break;
}
case eAmf::Array: {
this->Write<AMFArrayValue&>(*static_cast<AMFArrayValue*>(&value));
break;
}
default: {
Game::logger->Log("AmfSerialize", "Encountered unwritable AMFType %i!", type);
}
case eAmf::Undefined:
case eAmf::Null:
case eAmf::False:
case eAmf::True:
case eAmf::Date:
case eAmf::Object:
case eAmf::XML:
case eAmf::XMLDoc:
case eAmf::ByteArray:
case eAmf::VectorInt:
case eAmf::VectorUInt:
case eAmf::VectorDouble:
case eAmf::VectorObject:
case eAmf::Dictionary:
break;
}
}
/**
* A private function to write an value to a RakNet::BitStream
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteUInt29(RakNet::BitStream* bs, uint32_t v) {
unsigned char b4 = (unsigned char)v;
if (v < 0x00200000) {
b4 = b4 & 0x7F;
if (v > 0x7F) {
unsigned char b3;
v = v >> 7;
b3 = ((unsigned char)(v)) | 0x80;
if (v > 0x7F) {
unsigned char b2;
v = v >> 7;
b2 = ((unsigned char)(v)) | 0x80;
bs->Write(b2);
}
bs->Write(b3);
}
} else {
unsigned char b1;
unsigned char b2;
unsigned char b3;
v = v >> 8;
b3 = ((unsigned char)(v)) | 0x80;
v = v >> 7;
b2 = ((unsigned char)(v)) | 0x80;
v = v >> 7;
b1 = ((unsigned char)(v)) | 0x80;
bs->Write(b1);
bs->Write(b2);
bs->Write(b3);
}
bs->Write(b4);
}
/**
* Writes a flag number to a RakNet::BitStream
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteFlagNumber(RakNet::BitStream* bs, uint32_t v) {
v = (v << 1) | 0x01;
WriteUInt29(bs, v);
}
/**
* Writes an AMFString to a RakNet::BitStream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFString(RakNet::BitStream* bs, const std::string& str) {
WriteFlagNumber(bs, (uint32_t)str.size());
bs->Write(str.c_str(), (uint32_t)str.size());
}
/**
* Writes an U16 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU16(RakNet::BitStream* bs, uint16_t value) {
bs->Write(value);
}
/**
* Writes an U32 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU32(RakNet::BitStream* bs, uint32_t value) {
bs->Write(value);
}
/**
* Writes an U64 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU64(RakNet::BitStream* bs, uint64_t value) {
bs->Write(value);
}
// Writes an AMFIntegerValue to BitStream
template<>
void RakNet::BitStream::Write<AMFIntValue&>(AMFIntValue& value) {
WriteUInt29(this, value.GetValue());
}
// Writes an AMFDoubleValue to BitStream
template<>
void RakNet::BitStream::Write<AMFDoubleValue&>(AMFDoubleValue& value) {
double d = value.GetValue();
WriteAMFU64(this, *reinterpret_cast<uint64_t*>(&d));
}
// Writes an AMFStringValue to BitStream
template<>
void RakNet::BitStream::Write<AMFStringValue&>(AMFStringValue& value) {
WriteAMFString(this, value.GetValue());
}
// Writes an AMFArrayValue to BitStream
template<>
void RakNet::BitStream::Write<AMFArrayValue&>(AMFArrayValue& value) {
uint32_t denseSize = value.GetDense().size();
WriteFlagNumber(this, denseSize);
auto it = value.GetAssociative().begin();
auto end = value.GetAssociative().end();
while (it != end) {
WriteAMFString(this, it->first);
this->Write<AMFBaseValue&>(*it->second);
it++;
}
this->Write(eAmf::Null);
if (denseSize > 0) {
auto it2 = value.GetDense().begin();
auto end2 = value.GetDense().end();
while (it2 != end2) {
this->Write<AMFBaseValue&>(**it2);
it2++;
}
}
}

View File

@@ -1,50 +0,0 @@
#pragma once
// Custom Classes
#include "Amf3.h"
// RakNet
#include <BitStream.h>
/*!
\file AmfSerialize.h
\brief A class that implements native writing of AMF values to RakNet::BitStream
*/
// We are using the RakNet namespace
namespace RakNet {
//! Writes an AMFValue pointer to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFBaseValue&>(AMFBaseValue& value);
//! Writes an AMFIntegerValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFIntValue&>(AMFIntValue& value);
//! Writes an AMFDoubleValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFDoubleValue&>(AMFDoubleValue& value);
//! Writes an AMFStringValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFStringValue&>(AMFStringValue& value);
//! Writes an AMFArrayValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFArrayValue&>(AMFArrayValue& value);
} // namespace RakNet

View File

@@ -1,6 +1,6 @@
set(DCOMMON_SOURCES
set(DCOMMON_SOURCES "AMFFormat.cpp"
"AMFDeserialize.cpp"
"AmfSerialize.cpp"
"AMFFormat_BitStream.cpp"
"BinaryIO.cpp"
"dConfig.cpp"
"Diagnostics.cpp"

View File

@@ -1,12 +0,0 @@
#ifndef __DLUASSERT__H__
#define __DLUASSERT__H__
#include <assert.h>
#ifdef _DEBUG
# define DluAssert(expression) assert(expression)
#else
# define DluAssert(expression)
#endif
#endif //!__DLUASSERT__H__

View File

@@ -33,7 +33,7 @@ public:
virtual void WriteToPacket(RakNet::BitStream* packet) = 0;
virtual const std::u16string& GetKey() const = 0;
virtual const std::u16string& GetKey() = 0;
virtual eLDFType GetValueType() = 0;
@@ -117,7 +117,7 @@ public:
/*!
\return The key
*/
const std::u16string& GetKey(void) const override { return this->key; }
const std::u16string& GetKey(void) override { return this->key; }
//! Gets the LDF Type
/*!

View File

@@ -56,13 +56,6 @@ const uint32_t LWOCLONEID_INVALID = -1; //!< Invalid LWOCLONEID
const uint16_t LWOINSTANCEID_INVALID = -1; //!< Invalid LWOINSTANCEID
const uint16_t LWOMAPID_INVALID = -1; //!< Invalid LWOMAPID
const uint64_t LWOZONEID_INVALID = 0; //!< Invalid LWOZONEID
const LOT LOT_ZONE_CONTROL = 2365;
const LOT LOT_3D_AMBIENT_SOUND = 6368;
const LOT LOT_MODEL_IN_WORLD = 14;
const LOT LOT_THINKING_CAP = 6086;
const LOT LOT_ROCKET = 6416;
const LOT LOT_LEGACY_RESPAWN_POINT = 4945;
const float PI = 3.14159f;

View File

@@ -1,14 +0,0 @@
#ifndef __EPHYSICSBEHAVIORTYPE__H__
#define __EPHYSICSBEHAVIORTYPE__H__
#include <cstdint>
enum class ePhysicsBehaviorType : int32_t {
INVALID = -1,
GROUND,
FLYING,
STANDARD,
DYNAMIC
};
#endif //!__EPHYSICSBEHAVIORTYPE__H__

View File

@@ -11,14 +11,14 @@ enum class eReplicaComponentType : uint32_t {
CHARACTER,
SCRIPT,
BOUNCER,
DESTROYABLE,
BUFF, // buff is really 98, this is DESTROYABLE
GHOST,
SKILL,
SPAWN,
SPAWNER,
ITEM,
MODULAR_BUILD,
BUILD_CONTROLLER,
BUILD_ACTIVATOR,
REBUILD,
REBUILD_START,
REBUILD_ACTIVATOR,
ICON_ONLY,
VENDOR,
INVENTORY,
@@ -33,8 +33,8 @@ enum class eReplicaComponentType : uint32_t {
PET,
PLATFORM_BOUNDARY,
MODULE,
JETPACKPAD,
HAVOK_VEHICLE_PHYSICS,
ARCADE,
VEHICLE_PHYSICS, // Havok demo based
MOVEMENT_AI,
EXHIBIT,
OVERHEAD_ICON,
@@ -46,23 +46,23 @@ enum class eReplicaComponentType : uint32_t {
SCRIPTED_ACTIVITY,
PHANTOM_PHYSICS,
SPRINGPAD,
MODEL_BEHAVIOR,
MODEL,
PROPERTY_ENTRANCE,
FX,
PROPERTY_MANAGEMENT,
VEHICLE_PHYSICS,
VEHICLE_PHYSICS_NEW, // internal physics based on havok
PHYSICS_SYSTEM,
QUICK_BUILD,
SWITCH,
MINIGAME_CONTROL,
CHANGLING_BUILD,
ZONE_CONTROL, // Minigame
CHANGLING,
CHOICE_BUILD,
PACKAGE,
SOUND_REPEATER,
SOUND_AMBIENT_2D,
SOUND_AMBIENT_3D,
PRECONDITION,
FLAG,
PLAYER_FLAG,
CUSTOM_BUILD_ASSEMBLY,
BASE_COMBAT_AI,
MODULE_ASSEMBLY,
@@ -71,8 +71,8 @@ enum class eReplicaComponentType : uint32_t {
GENERIC_ACTIVATOR,
PROPERTY_VENDOR,
HF_LIGHT_DIRECTION_GADGET,
ROCKET_LAUNCHPAD_CONTROL,
ROCKET_ANIMATION_CONTROL,
ROCKET_LAUNCH,
ROCKET_LANDING,
TRIGGER,
DROPPED_LOOT,
RACING_CONTROL,
@@ -84,7 +84,7 @@ enum class eReplicaComponentType : uint32_t {
SOUND_TRIGGER,
PROXIMITY_MONITOR,
RACING_SOUND_TRIGGER,
CHAT_BUBBLE,
CHAT,
FRIENDS_LIST,
GUILD,
LOCAL_SYSTEM,
@@ -101,27 +101,27 @@ enum class eReplicaComponentType : uint32_t {
TRADE,
USER_CONTROL,
IGNORE_LIST,
MULTI_ZONE_ENTRANCE,
BUFF,
ROCKET_LAUNCH_LUP,
BUFF_REAL, // the real buff component, should just be name BUFF
INTERACTION_MANAGER,
DONATION_VENDOR,
COMBAT_MEDIATOR,
ACHIEVEMENT_VENDOR,
GATE_RUSH_CONTROL,
COMMENDATION_VENDOR,
UNKNOWN_103,
RAIL_ACTIVATOR,
ROLLER,
PLAYER_FORCED_MOVEMENT,
CRAFTING,
POSSESSABLE,
LEVEL_PROGRESSION,
POSSESSION,
POSSESSOR,
MOUNT_CONTROL,
UNKNOWN_112,
PROPERTY_PLAQUE,
BUILD_BORDER,
UNKNOWN_115,
UNKOWN_115,
CULLING_PLANE,
NUMBER_OF_COMPONENTS
DESTROYABLE = 1000 // Actually 7
};
#endif //!__EREPLICACOMPONENTTYPE__H__

View File

@@ -1,13 +0,0 @@
#ifndef __EUGCMODERATIONSTATUS__H__
#define __EUGCMODERATIONSTATUS__H__
#include <cstdint>
enum class eUgcModerationStatus : uint32_t {
NoStatus,
Approved,
Rejected,
};
#endif //!__EUGCMODERATIONSTATUS__H__

View File

@@ -16,6 +16,9 @@
// Enable this to cache all entries in each table for fast access, comes with more memory cost
//#define CDCLIENT_CACHE_ALL
// Enable this to skip some unused columns in some tables
#define UNUSED(v)
/*!
\file CDClientDatabase.hpp
\brief An interface between the CDClient.sqlite file and the server

View File

@@ -40,7 +40,7 @@
CDClientManager::CDClientManager() {
CDActivityRewardsTable::Instance();
CDAnimationsTable::Instance();
UNUSED(CDAnimationsTable::Instance());
CDBehaviorParameterTable::Instance();
CDBehaviorTemplateTable::Instance();
CDComponentsRegistryTable::Instance();

View File

@@ -4,8 +4,6 @@
#include "Singleton.h"
#define UNUSED_TABLE(v)
/**
* Initialize the CDClient tables so they are all loaded into memory.
*/

View File

@@ -1,83 +1,56 @@
#include "CDAnimationsTable.h"
#include "GeneralUtils.h"
#include "Game.h"
bool CDAnimationsTable::CacheData(CppSQLite3Statement& queryToCache) {
auto tableData = queryToCache.execQuery();
// If we received a bad lookup, cache it anyways so we do not run the query again.
if (tableData.eof()) return false;
CDAnimationsTable::CDAnimationsTable(void) {
do {
std::string animation_type = tableData.getStringField("animation_type", "");
DluAssert(!animation_type.empty());
AnimationGroupID animationGroupID = tableData.getIntField("animationGroupID", -1);
DluAssert(animationGroupID != -1);
// First, get the size of the table
unsigned int size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM Animations");
while (!tableSize.eof()) {
size = tableSize.getIntField(0, 0);
CDAnimation entry;
tableSize.nextRow();
}
tableSize.finalize();
// Reserve the size
this->entries.reserve(size);
// Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM Animations");
while (!tableData.eof()) {
CDAnimations entry;
entry.animationGroupID = tableData.getIntField("animationGroupID", -1);
entry.animation_type = tableData.getStringField("animation_type", "");
entry.animation_name = tableData.getStringField("animation_name", "");
entry.chance_to_play = tableData.getFloatField("chance_to_play", 1.0f);
UNUSED_COLUMN(entry.min_loops = tableData.getIntField("min_loops", 0);)
UNUSED_COLUMN(entry.max_loops = tableData.getIntField("max_loops", 0);)
entry.animation_length = tableData.getFloatField("animation_length", 0.0f);
UNUSED_COLUMN(entry.hideEquip = tableData.getIntField("hideEquip", 0) == 1;)
UNUSED_COLUMN(entry.ignoreUpperBody = tableData.getIntField("ignoreUpperBody", 0) == 1;)
UNUSED_COLUMN(entry.restartable = tableData.getIntField("restartable", 0) == 1;)
UNUSED_COLUMN(entry.face_animation_name = tableData.getStringField("face_animation_name", "");)
UNUSED_COLUMN(entry.priority = tableData.getFloatField("priority", 0.0f);)
UNUSED_COLUMN(entry.blendTime = tableData.getFloatField("blendTime", 0.0f);)
entry.chance_to_play = tableData.getFloatField("chance_to_play", -1.0f);
entry.min_loops = tableData.getIntField("min_loops", -1);
entry.max_loops = tableData.getIntField("max_loops", -1);
entry.animation_length = tableData.getFloatField("animation_length", -1.0f);
entry.hideEquip = tableData.getIntField("hideEquip", -1) == 1 ? true : false;
entry.ignoreUpperBody = tableData.getIntField("ignoreUpperBody", -1) == 1 ? true : false;
entry.restartable = tableData.getIntField("restartable", -1) == 1 ? true : false;
entry.face_animation_name = tableData.getStringField("face_animation_name", "");
entry.priority = tableData.getFloatField("priority", -1.0f);
entry.blendTime = tableData.getFloatField("blendTime", -1.0f);
this->animations[CDAnimationKey(animation_type, animationGroupID)].push_back(entry);
this->entries.push_back(entry);
tableData.nextRow();
} while (!tableData.eof());
}
tableData.finalize();
return true;
}
void CDAnimationsTable::CacheAnimations(const CDAnimationKey animationKey) {
auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM Animations WHERE animationGroupID = ? and animation_type = ?");
query.bind(1, static_cast<int32_t>(animationKey.second));
query.bind(2, animationKey.first.c_str());
// If we received a bad lookup, cache it anyways so we do not run the query again.
if (!CacheData(query)) {
this->animations[animationKey];
}
std::vector<CDAnimations> CDAnimationsTable::Query(std::function<bool(CDAnimations)> predicate) {
std::vector<CDAnimations> data = cpplinq::from(this->entries)
>> cpplinq::where(predicate)
>> cpplinq::to_vector();
return data;
}
void CDAnimationsTable::CacheAnimationGroup(AnimationGroupID animationGroupID) {
auto animationEntryCached = this->animations.find(CDAnimationKey("", animationGroupID));
if (animationEntryCached != this->animations.end()) {
return;
}
auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM Animations WHERE animationGroupID = ?");
query.bind(1, static_cast<int32_t>(animationGroupID));
// Cache the query so we don't run the query again.
CacheData(query);
this->animations[CDAnimationKey("", animationGroupID)];
std::vector<CDAnimations> CDAnimationsTable::GetEntries(void) const {
return this->entries;
}
CDAnimationLookupResult CDAnimationsTable::GetAnimation(const AnimationID& animationType, const std::string& previousAnimationName, const AnimationGroupID animationGroupID) {
CDAnimationKey animationKey(animationType, animationGroupID);
auto animationEntryCached = this->animations.find(animationKey);
if (animationEntryCached == this->animations.end()) {
this->CacheAnimations(animationKey);
}
auto animationEntry = this->animations.find(animationKey);
// If we have only one animation, return it regardless of the chance to play.
if (animationEntry->second.size() == 1) {
return CDAnimationLookupResult(animationEntry->second.front());
}
auto randomAnimation = GeneralUtils::GenerateRandomNumber<float>(0, 1);
for (auto& animationEntry : animationEntry->second) {
randomAnimation -= animationEntry.chance_to_play;
// This is how the client gets the random animation.
if (animationEntry.animation_name != previousAnimationName && randomAnimation <= 0.0f) return CDAnimationLookupResult(animationEntry);
}
return CDAnimationLookupResult();
}

View File

@@ -1,66 +1,33 @@
#pragma once
// Custom Classes
#include "CDTable.h"
#include <list>
struct CDAnimation {
// unsigned int animationGroupID;
// std::string animation_type;
// The above two are a pair to represent a primary key in the map.
struct CDAnimations {
unsigned int animationGroupID; //!< The animation group ID
std::string animation_type; //!< The animation type
std::string animation_name; //!< The animation name
float chance_to_play; //!< The chance to play the animation
UNUSED_COLUMN(unsigned int min_loops;) //!< The minimum number of loops
UNUSED_COLUMN(unsigned int max_loops;) //!< The maximum number of loops
unsigned int min_loops; //!< The minimum number of loops
unsigned int max_loops; //!< The maximum number of loops
float animation_length; //!< The animation length
UNUSED_COLUMN(bool hideEquip;) //!< Whether or not to hide the equip
UNUSED_COLUMN(bool ignoreUpperBody;) //!< Whether or not to ignore the upper body
UNUSED_COLUMN(bool restartable;) //!< Whether or not the animation is restartable
UNUSED_COLUMN(std::string face_animation_name;) //!< The face animation name
UNUSED_COLUMN(float priority;) //!< The priority
UNUSED_COLUMN(float blendTime;) //!< The blend time
bool hideEquip; //!< Whether or not to hide the equip
bool ignoreUpperBody; //!< Whether or not to ignore the upper body
bool restartable; //!< Whether or not the animation is restartable
std::string face_animation_name; //!< The face animation name
float priority; //!< The priority
float blendTime; //!< The blend time
};
typedef LookupResult<CDAnimation> CDAnimationLookupResult;
class CDAnimationsTable : public CDTable<CDAnimationsTable> {
typedef int32_t AnimationGroupID;
typedef std::string AnimationID;
typedef std::pair<std::string, AnimationGroupID> CDAnimationKey;
public:
/**
* Given an animationType and the previousAnimationName played, return the next animationType to play.
* If there are more than 1 animationTypes that can be played, one is selected at random but also does not allow
* the previousAnimationName to be played twice.
*
* @param animationType The animationID to lookup
* @param previousAnimationName The previously played animation
* @param animationGroupID The animationGroupID to lookup
* @return CDAnimationLookupResult
*/
[[nodiscard]] CDAnimationLookupResult GetAnimation(const AnimationID& animationType, const std::string& previousAnimationName, const AnimationGroupID animationGroupID);
/**
* Cache a full AnimationGroup by its ID.
*/
void CacheAnimationGroup(AnimationGroupID animationGroupID);
private:
std::vector<CDAnimations> entries;
/**
* Cache all animations given a premade key
*/
void CacheAnimations(const CDAnimationKey animationKey);
public:
CDAnimationsTable();
// Queries the table with a custom "where" clause
std::vector<CDAnimations> Query(std::function<bool(CDAnimations)> predicate);
/**
* Run the query responsible for caching the data.
* @param queryToCache
* @return true
* @return false
*/
bool CacheData(CppSQLite3Statement& queryToCache);
/**
* Each animation is key'd by its animationName and its animationGroupID. Each
* animation has a possible list of animations. This is because there can be animations have a percent chance to play so one is selected at random.
*/
std::map<CDAnimationKey, std::list<CDAnimation>> animations;
std::vector<CDAnimations> GetEntries(void) const;
};

View File

@@ -90,11 +90,4 @@ int32_t CDComponentsRegistryTable::GetByIDAndType(uint32_t id, eReplicaComponent
return defaultValue;
#endif
}
std::vector<std::pair<eReplicaComponentType, uint32_t>> CDComponentsRegistryTable::GetTemplateComponents(LOT templateId) {
std::vector<std::pair<eReplicaComponentType, uint32_t>> components;
for (int8_t i = 0; static_cast<eReplicaComponentType>(i) < eReplicaComponentType::NUMBER_OF_COMPONENTS; i++) {
auto compId = GetByIDAndType(templateId, static_cast<eReplicaComponentType>(i), -1);
if (compId != -1) components.push_back(std::make_pair(static_cast<eReplicaComponentType>(i), compId));
}
return components;
}

View File

@@ -2,7 +2,6 @@
// Custom Classes
#include "CDTable.h"
#include "dCommonVars.h"
enum class eReplicaComponentType : uint32_t;
struct CDComponentsRegistry {
@@ -19,5 +18,4 @@ private:
public:
CDComponentsRegistryTable();
int32_t GetByIDAndType(uint32_t id, eReplicaComponentType componentType, int32_t defaultValue = 0);
std::vector<std::pair<eReplicaComponentType, uint32_t>> GetTemplateComponents(LOT templateId);
};

View File

@@ -1,8 +1,6 @@
#include "CDItemComponentTable.h"
#include "GeneralUtils.h"
#include "eItemType.h"
CDItemComponent CDItemComponentTable::Default = {};
//! Constructor
@@ -76,8 +74,8 @@ CDItemComponentTable::CDItemComponentTable(void) {
#endif
}
const CDItemComponent& CDItemComponentTable::GetItemComponentByID(unsigned int id) {
const auto& it = this->entries.find(id);
const CDItemComponent& CDItemComponentTable::GetItemComponentByID(unsigned int skillID) {
const auto& it = this->entries.find(skillID);
if (it != this->entries.end()) {
return it->second;
}
@@ -85,11 +83,11 @@ const CDItemComponent& CDItemComponentTable::GetItemComponentByID(unsigned int i
#ifndef CDCLIENT_CACHE_ALL
std::stringstream query;
query << "SELECT * FROM ItemComponent WHERE id = " << std::to_string(id);
query << "SELECT * FROM ItemComponent WHERE id = " << std::to_string(skillID);
auto tableData = CDClientDatabase::ExecuteQuery(query.str());
if (tableData.eof()) {
entries.insert(std::make_pair(id, Default));
entries.insert(std::make_pair(skillID, Default));
return Default;
}
@@ -100,7 +98,7 @@ const CDItemComponent& CDItemComponentTable::GetItemComponentByID(unsigned int i
entry.baseValue = tableData.getIntField("baseValue", -1);
entry.isKitPiece = tableData.getIntField("isKitPiece", -1) == 1 ? true : false;
entry.rarity = tableData.getIntField("rarity", 0);
entry.itemType = static_cast<eItemType>(tableData.getIntField("itemType", -1));
entry.itemType = tableData.getIntField("itemType", -1);
entry.itemInfo = tableData.getInt64Field("itemInfo", -1);
entry.inLootTable = tableData.getIntField("inLootTable", -1) == 1 ? true : false;
entry.inVendor = tableData.getIntField("inVendor", -1) == 1 ? true : false;
@@ -142,7 +140,7 @@ const CDItemComponent& CDItemComponentTable::GetItemComponentByID(unsigned int i
tableData.nextRow();
}
const auto& it2 = this->entries.find(id);
const auto& it2 = this->entries.find(skillID);
if (it2 != this->entries.end()) {
return it2->second;
}

View File

@@ -4,15 +4,13 @@
#include "CDTable.h"
#include "dCommonVars.h"
enum class eItemType : int32_t;
struct CDItemComponent {
unsigned int id; //!< The Component ID
std::string equipLocation; //!< The equip location
unsigned int baseValue; //!< The monetary base value of the item
bool isKitPiece; //!< Whether or not the item belongs to a kit
unsigned int rarity; //!< The rarity of the item
eItemType itemType; //!< The item type
unsigned int itemType; //!< The item type
int64_t itemInfo; //!< The item info
bool inLootTable; //!< Whether or not the item is in a loot table
bool inVendor; //!< Whether or not the item is in a vendor inventory
@@ -60,7 +58,7 @@ public:
static std::map<LOT, uint32_t> ParseCraftingCurrencies(const CDItemComponent& itemComponent);
// Gets an entry by ID
const CDItemComponent& GetItemComponentByID(unsigned int id);
const CDItemComponent& GetItemComponentByID(unsigned int skillID);
static CDItemComponent Default;
};

View File

@@ -37,7 +37,10 @@ CDPhysicsComponentTable::~CDPhysicsComponentTable() {
}
CDPhysicsComponent* CDPhysicsComponentTable::GetByID(unsigned int componentID) {
auto itr = m_entries.find(componentID);
return itr != m_entries.end() ? itr->second : nullptr;
for (auto e : m_entries) {
if (e.first == componentID) return e.second;
}
return nullptr;
}

View File

@@ -2,7 +2,6 @@
#include "CDClientDatabase.h"
#include "Singleton.h"
#include "DluAssert.h"
#include <functional>
#include <string>
@@ -16,12 +15,6 @@
#endif
#include "cpplinq.hpp"
// Used for legacy
#define UNUSED(x)
// Enable this to skip some unused columns in some tables
#define UNUSED_COLUMN(v)
#pragma warning (disable : 4244) //Disable double to float conversion warnings
#pragma warning (disable : 4715) //Disable "not all control paths return a value"
@@ -30,15 +23,3 @@ class CDTable : public Singleton<Table> {
protected:
virtual ~CDTable() = default;
};
template<class T>
class LookupResult {
typedef std::pair<T, bool> DataType;
public:
LookupResult() { m_data.first = T(); m_data.second = false; };
LookupResult(T& data) { m_data.first = data; m_data.second = true; };
inline const T& Data() { return m_data.first; };
inline const bool& FoundData() { return m_data.second; };
private:
DataType m_data;
};

View File

@@ -1,4 +1,5 @@
set(DGAME_SOURCES "Character.cpp"
"Entity.cpp"
"EntityManager.cpp"
"LeaderboardManager.cpp"
"Player.cpp"

View File

@@ -418,7 +418,7 @@ void Character::WriteToDatabase() {
delete printer;
}
void Character::SetPlayerFlag(const uint32_t flagId, const bool value) {
void Character::SetPlayerFlag(const int32_t flagId, const bool value) {
// If the flag is already set, we don't have to recalculate it
if (GetPlayerFlag(flagId) == value) return;
@@ -465,7 +465,7 @@ void Character::SetPlayerFlag(const uint32_t flagId, const bool value) {
GameMessages::SendNotifyClientFlagChange(m_ObjectID, flagId, value, m_ParentUser->GetSystemAddress());
}
bool Character::GetPlayerFlag(const uint32_t flagId) const {
bool Character::GetPlayerFlag(const int32_t flagId) const {
// Calculate the index first
const auto flagIndex = uint32_t(std::floor(flagId / 64));

View File

@@ -415,14 +415,14 @@ public:
* @param flagId the ID of the flag to set
* @param value the value to set for the flag
*/
void SetPlayerFlag(uint32_t flagId, bool value);
void SetPlayerFlag(int32_t flagId, bool value);
/**
* Gets the value for a certain character flag
* @param flagId the ID of the flag to get a value for
* @return the value of the flag given the ID (the default is false, obviously)
*/
bool GetPlayerFlag(uint32_t flagId) const;
bool GetPlayerFlag(int32_t flagId) const;
/**
* Notifies the character that they're now muted

2044
dGame/Entity.cpp Normal file

File diff suppressed because it is too large Load Diff

502
dGame/Entity.h Normal file
View File

@@ -0,0 +1,502 @@
#pragma once
#include <map>
#include <functional>
#include <typeinfo>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include "LDFFormat.h"
#include "eKillType.h"
namespace Loot {
class Info;
};
namespace tinyxml2 {
class XMLDocument;
};
class Player;
class EntityInfo;
class User;
class Spawner;
class ScriptComponent;
class dpEntity;
class EntityTimer;
class Component;
class Item;
class Character;
class EntityCallbackTimer;
enum class eTriggerEventType;
enum class eGameMasterLevel : uint8_t;
enum class eReplicaComponentType : uint32_t;
enum class eReplicaPacketType : uint8_t;
enum class eCinematicEvent : uint32_t;
namespace CppScripts {
class Script;
};
/**
* An entity in the world. Has multiple components.
*/
class Entity {
public:
explicit Entity(const LWOOBJID& objectID, EntityInfo info, Entity* parentEntity = nullptr);
virtual ~Entity();
virtual void Initialize();
bool operator==(const Entity& other) const;
bool operator!=(const Entity& other) const;
/**
* Getters
*/
const LWOOBJID& GetObjectID() const { return m_ObjectID; }
const LOT GetLOT() const { return m_TemplateID; }
Character* GetCharacter() const { return m_Character; }
eGameMasterLevel GetGMLevel() const { return m_GMLevel; }
uint8_t GetCollectibleID() const { return uint8_t(m_CollectibleID); }
Entity* GetParentEntity() const { return m_ParentEntity; }
std::vector<std::string>& GetGroups() { return m_Groups; };
Spawner* GetSpawner() const { return m_Spawner; }
LWOOBJID GetSpawnerID() const { return m_SpawnerID; }
const std::vector<LDFBaseData*>& GetSettings() const { return m_Settings; }
const std::vector<LDFBaseData*>& GetNetworkSettings() const { return m_NetworkSettings; }
bool GetIsDead() const;
bool GetPlayerReadyForUpdates() const { return m_PlayerIsReadyForUpdates; }
bool GetIsGhostingCandidate() const;
int8_t GetObservers() const;
uint16_t GetNetworkId() const;
Entity* GetOwner() const;
const NiPoint3& GetDefaultPosition() const;
const NiQuaternion& GetDefaultRotation() const;
float GetDefaultScale() const;
const NiPoint3& GetPosition() const;
const NiQuaternion& GetRotation() const;
virtual User* GetParentUser() const;
virtual SystemAddress GetSystemAddress() const { return UNASSIGNED_SYSTEM_ADDRESS; };
/**
* Setters
*/
void SetCharacter(Character* value) { m_Character = value; }
void SetGMLevel(eGameMasterLevel value);
void SetOwnerOverride(LWOOBJID value);
void SetPlayerReadyForUpdates() { m_PlayerIsReadyForUpdates = true; }
void SetObservers(int8_t value);
void SetNetworkId(uint16_t id);
void SetPosition(NiPoint3 position);
void SetRotation(NiQuaternion rotation);
virtual void SetRespawnPos(NiPoint3 position) {}
virtual void SetRespawnRot(NiQuaternion rotation) {}
virtual void SetSystemAddress(const SystemAddress& value) {};
/**
* Component management
*/
Component* GetComponent(eReplicaComponentType componentID) const;
template<typename T>
T* GetComponent() const;
template<typename T>
bool TryGetComponent(eReplicaComponentType componentId, T*& component) const;
bool HasComponent(eReplicaComponentType componentId) const;
void AddComponent(eReplicaComponentType componentId, Component* component);
std::vector<ScriptComponent*> GetScriptComponents();
void Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, const std::string& notificationName);
void Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationName);
void SetProximityRadius(float proxRadius, std::string name);
void SetProximityRadius(dpEntity* entity, std::string name);
void AddChild(Entity* child);
void RemoveChild(Entity* child);
void RemoveParent();
void AddTimer(std::string name, float time);
void AddCallbackTimer(float time, std::function<void()> callback);
bool HasTimer(const std::string& name);
void CancelCallbackTimers();
void CancelAllTimers();
void CancelTimer(const std::string& name);
void AddToGroup(const std::string& group);
bool IsPlayer() const;
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 ResetFlags();
void UpdateXMLDoc(tinyxml2::XMLDocument* doc);
void Update(float deltaTime);
// Events
void OnCollisionProximity(LWOOBJID otherEntity, const std::string& proxName, const std::string& status);
void OnCollisionPhantom(LWOOBJID otherEntity);
void OnCollisionLeavePhantom(LWOOBJID otherEntity);
void OnFireEventServerSide(Entity* sender, std::string args, int32_t param1 = -1, int32_t param2 = -1, int32_t param3 = -1);
void OnActivityStateChangeRequest(const LWOOBJID senderID, const int32_t value1, const int32_t value2,
const std::u16string& stringValue);
void OnCinematicUpdate(Entity* self, Entity* sender, eCinematicEvent event, const std::u16string& pathName,
float_t pathTime, float_t totalTime, int32_t waypoint);
void NotifyObject(Entity* sender, const std::string& name, int32_t param1 = 0, int32_t param2 = 0);
void OnEmoteReceived(int32_t emote, Entity* target);
void OnUse(Entity* originator);
void OnHitOrHealResult(Entity* attacker, int32_t damage);
void OnHit(Entity* attacker);
void OnZonePropertyEditBegin();
void OnZonePropertyEditEnd();
void OnZonePropertyModelEquipped();
void OnZonePropertyModelPlaced(Entity* player);
void OnZonePropertyModelPickedUp(Entity* player);
void OnZonePropertyModelRemoved(Entity* player);
void OnZonePropertyModelRemovedWhileEquipped(Entity* player);
void OnZonePropertyModelRotated(Entity* player);
void OnMessageBoxResponse(Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData);
void OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier);
void Smash(const LWOOBJID source = LWOOBJID_EMPTY, const eKillType killType = eKillType::VIOLENT, const std::u16string& deathType = u"");
void Kill(Entity* murderer = nullptr);
void AddRebuildCompleteCallback(const std::function<void(Entity* user)>& callback) const;
void AddCollisionPhantomCallback(const std::function<void(Entity* target)>& callback);
void AddDieCallback(const std::function<void()>& callback);
void Resurrect();
void AddLootItem(const Loot::Info& info);
void PickupItem(const LWOOBJID& objectID);
bool CanPickupCoins(uint64_t count);
void RegisterCoinDrop(uint64_t count);
void ScheduleKillAfterUpdate(Entity* murderer = nullptr);
void TriggerEvent(eTriggerEventType event, Entity* optionalTarget = nullptr);
void ScheduleDestructionAfterUpdate() { m_ShouldDestroyAfterUpdate = true; }
virtual NiPoint3 GetRespawnPosition() const { return NiPoint3::ZERO; }
virtual NiQuaternion GetRespawnRotation() const { return NiQuaternion::IDENTITY; }
void Sleep();
void Wake();
bool IsSleeping() const;
/*
* Utility
*/
/**
* Retroactively corrects the model vault size due to incorrect initialization in a previous patch.
*
*/
void RetroactiveVaultSize();
bool GetBoolean(const std::u16string& name) const;
int32_t GetI32(const std::u16string& name) const;
int64_t GetI64(const std::u16string& name) const;
void SetBoolean(const std::u16string& name, bool value);
void SetI32(const std::u16string& name, int32_t value);
void SetI64(const std::u16string& name, int64_t value);
bool HasVar(const std::u16string& name) const;
template<typename T>
const T& GetVar(const std::u16string& name) const;
template<typename T>
void SetVar(const std::u16string& name, T value);
void SendNetworkVar(const std::string& data, const SystemAddress& sysAddr);
template<typename T>
void SetNetworkVar(const std::u16string& name, T value, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
template<typename T>
void SetNetworkVar(const std::u16string& name, std::vector<T> value, const SystemAddress& sysAddr = UNASSIGNED_SYSTEM_ADDRESS);
template<typename T>
T GetNetworkVar(const std::u16string& name);
/**
* Get the LDF value and cast it as T.
*/
template<typename T>
T GetVarAs(const std::u16string& name) const;
/**
* Get the LDF data.
*/
LDFBaseData* GetVarData(const std::u16string& name) const;
/**
* Get the LDF value and convert it to a string.
*/
std::string GetVarAsString(const std::u16string& name) const;
/*
* Collision
*/
std::vector<LWOOBJID>& GetTargetsInPhantom();
Entity* GetScheduledKiller() { return m_ScheduleKiller; }
protected:
LWOOBJID m_ObjectID;
LOT m_TemplateID;
std::vector<LDFBaseData*> m_Settings;
std::vector<LDFBaseData*> m_NetworkSettings;
NiPoint3 m_DefaultPosition;
NiQuaternion m_DefaultRotation;
float m_Scale;
Spawner* m_Spawner;
LWOOBJID m_SpawnerID;
bool m_HasSpawnerNodeID;
uint32_t m_SpawnerNodeID;
Character* m_Character;
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;
std::vector<std::function<void(Entity* target)>> m_PhantomCollisionCallbacks;
std::unordered_map<eReplicaComponentType, Component*> m_Components;
std::vector<EntityTimer*> m_Timers;
std::vector<EntityTimer*> m_PendingTimers;
std::vector<EntityCallbackTimer*> m_CallbackTimers;
bool m_ShouldDestroyAfterUpdate = false;
LWOOBJID m_OwnerOverride;
Entity* m_ScheduleKiller;
bool m_PlayerIsReadyForUpdates = false;
bool m_IsGhostingCandidate = false;
int8_t m_Observers = 0;
bool m_IsParentChildDirty = true;
/*
* Collision
*/
std::vector<LWOOBJID> m_TargetsInPhantom;
};
/**
* Template definitions.
*/
template<typename T>
bool Entity::TryGetComponent(const eReplicaComponentType componentId, T*& component) const {
const auto& index = m_Components.find(componentId);
if (index == m_Components.end()) {
component = nullptr;
return false;
}
component = dynamic_cast<T*>(index->second);
return true;
}
template <typename T>
T* Entity::GetComponent() const {
return dynamic_cast<T*>(GetComponent(T::ComponentType));
}
template<typename T>
const T& Entity::GetVar(const std::u16string& name) const {
auto* data = GetVarData(name);
if (data == nullptr) {
return LDFData<T>::Default;
}
auto* typed = dynamic_cast<LDFData<T>*>(data);
if (typed == nullptr) {
return LDFData<T>::Default;
}
return typed->GetValue();
}
template<typename T>
T Entity::GetVarAs(const std::u16string& name) const {
const auto data = GetVarAsString(name);
T value;
if (!GeneralUtils::TryParse(data, value)) {
return LDFData<T>::Default;
}
return value;
}
template<typename T>
void Entity::SetVar(const std::u16string& name, T value) {
auto* data = GetVarData(name);
if (data == nullptr) {
auto* data = new LDFData<T>(name, value);
m_Settings.push_back(data);
return;
}
auto* typed = dynamic_cast<LDFData<T>*>(data);
if (typed == nullptr) {
return;
}
typed->SetValue(value);
}
template<typename T>
void Entity::SetNetworkVar(const std::u16string& name, T value, const SystemAddress& sysAddr) {
LDFData<T>* newData = nullptr;
for (auto* data : m_NetworkSettings) {
if (data->GetKey() != name)
continue;
newData = dynamic_cast<LDFData<T>*>(data);
if (newData != nullptr) {
newData->SetValue(value);
} else { // If we're changing types
m_NetworkSettings.erase(
std::remove(m_NetworkSettings.begin(), m_NetworkSettings.end(), data), m_NetworkSettings.end()
);
delete data;
}
break;
}
if (newData == nullptr) {
newData = new LDFData<T>(name, value);
}
m_NetworkSettings.push_back(newData);
SendNetworkVar(newData->GetString(true), sysAddr);
}
template<typename T>
void Entity::SetNetworkVar(const std::u16string& name, std::vector<T> values, const SystemAddress& sysAddr) {
std::stringstream updates;
auto index = 1;
for (const auto& value : values) {
LDFData<T>* newData = nullptr;
const auto& indexedName = name + u"." + GeneralUtils::to_u16string(index);
for (auto* data : m_NetworkSettings) {
if (data->GetKey() != indexedName)
continue;
newData = dynamic_cast<LDFData<T>*>(data);
newData->SetValue(value);
break;
}
if (newData == nullptr) {
newData = new LDFData<T>(indexedName, value);
}
m_NetworkSettings.push_back(newData);
if (index == values.size()) {
updates << newData->GetString(true);
} else {
updates << newData->GetString(true) << "\n";
}
index++;
}
SendNetworkVar(updates.str(), sysAddr);
}
template<typename T>
T Entity::GetNetworkVar(const std::u16string& name) {
for (auto* data : m_NetworkSettings) {
if (data == nullptr || data->GetKey() != name)
continue;
auto* typed = dynamic_cast<LDFData<T>*>(data);
if (typed == nullptr)
continue;
return typed->GetValue();
}
return LDFData<T>::Default;
}

View File

@@ -24,7 +24,6 @@
#include "eGameMasterLevel.h"
#include "eReplicaComponentType.h"
#include "eReplicaPacketType.h"
#include "CollectibleComponent.h"
EntityManager* EntityManager::m_Address = nullptr;
@@ -519,9 +518,7 @@ void EntityManager::UpdateGhosting(Player* player) {
entity->SetObservers(entity->GetObservers() - 1);
} else if (!observed && ghostingDistanceMin > distance) {
// Check collectables, don't construct if it has been collected
auto* collectibleComponent = entity->GetComponent<CollectibleComponent>();
if (collectibleComponent) {
uint32_t collectionId = collectibleComponent->GetCollectibleId();
uint32_t collectionId = entity->GetCollectibleID();
if (collectionId != 0) {
collectionId = static_cast<uint32_t>(collectionId) + static_cast<uint32_t>(Game::server->GetZoneID() << 8);
@@ -530,7 +527,7 @@ void EntityManager::UpdateGhosting(Player* player) {
continue;
}
}
}
player->ObserveEntity(id);
ConstructEntity(entity, player->GetSystemAddress());
@@ -602,7 +599,7 @@ void EntityManager::ScheduleForKill(Entity* entity) {
if (!entity)
return;
auto* switchComp = entity->GetComponent<SwitchComponent>();
SwitchComponent* switchComp = entity->GetComponent<SwitchComponent>();
if (switchComp) {
entity->TriggerEvent(eTriggerEventType::DEACTIVATED, entity);
}

View File

@@ -51,17 +51,21 @@ User* Player::GetParentUser() const {
return m_ParentUser;
}
SystemAddress Player::GetSystemAddress() const {
return m_SystemAddress;
}
void Player::SetSystemAddress(const SystemAddress& value) {
m_SystemAddress = value;
}
void Player::SetRespawnPosition(const NiPoint3& position) {
void Player::SetRespawnPos(const NiPoint3 position) {
m_respawnPos = position;
m_Character->SetRespawnPoint(dZoneManager::Instance()->GetZone()->GetWorldID(), position);
}
void Player::SetRespawnRotation(const NiQuaternion& rotation) {
void Player::SetRespawnRot(const NiQuaternion rotation) {
m_respawnRot = rotation;
}
@@ -247,7 +251,7 @@ const std::vector<Player*>& Player::GetAllPlayers() {
return m_Players;
}
uint64_t Player::GetDroppedCoins() const {
uint64_t Player::GetDroppedCoins() {
return m_DroppedCoins;
}
@@ -282,12 +286,16 @@ Player::~Player() {
if (IsPlayer()) {
Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity();
zoneControl->GetScript()->OnPlayerExit(zoneControl, this);
for (CppScripts::Script* script : CppScripts::GetEntityScripts(zoneControl)) {
script->OnPlayerExit(zoneControl, this);
}
std::vector<Entity*> scriptedActs = EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY);
for (Entity* scriptEntity : scriptedActs) {
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
scriptEntity->GetScript()->OnPlayerExit(scriptEntity, this);
for (CppScripts::Script* script : CppScripts::GetEntityScripts(scriptEntity)) {
script->OnPlayerExit(scriptEntity, this);
}
}
}
}

View File

@@ -20,7 +20,7 @@ public:
User* GetParentUser() const override;
const SystemAddress GetSystemAddress() const override { return m_SystemAddress; }
SystemAddress GetSystemAddress() const override;
NiPoint3 GetRespawnPosition() const override;
@@ -36,7 +36,7 @@ public:
std::map<LWOOBJID, Loot::Info>& GetDroppedLoot();
uint64_t GetDroppedCoins() const;
uint64_t GetDroppedCoins();
/**
* Setters
@@ -44,9 +44,9 @@ public:
void SetSystemAddress(const SystemAddress& value) override;
void SetRespawnPosition(const NiPoint3& position) override;
void SetRespawnPos(NiPoint3 position) override;
void SetRespawnRotation(const NiQuaternion& rotation) override;
void SetRespawnRot(NiQuaternion rotation) override;
void SetGhostReferencePoint(const NiPoint3& value);

View File

@@ -219,7 +219,7 @@ void Trade::SendUpdateToOther(LWOOBJID participant) {
if (inventoryComponent == nullptr) return;
for (const auto tradeItem : itemIds) {
auto item = inventoryComponent->FindItemById(tradeItem.itemId);
auto* item = inventoryComponent->FindItemById(tradeItem.itemId);
if (item == nullptr) return;

View File

@@ -4,6 +4,7 @@
#include "BehaviorBranchContext.h"
#include "BuffComponent.h"
void ApplyBuffBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) {
auto* entity = EntityManager::Instance()->GetEntity(branch.target == LWOOBJID_EMPTY ? context->originator : branch.target);

View File

@@ -7,7 +7,7 @@
#include "dLogger.h"
#include "BehaviorBranchContext.h"
#include "BehaviorContext.h"
#include "QuickBuildComponent.h"
#include "RebuildComponent.h"
#include "DestroyableComponent.h"
#include "Game.h"
#include "dLogger.h"

View File

@@ -162,7 +162,7 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
}
auto* destroyableComponent = targetEntity->GetComponent<DestroyableComponent>();
if (!destroyableComponent || !destroyableComponent->GetParentEntity()) {
if (!destroyableComponent || !destroyableComponent->GetParent()) {
Game::logger->Log("BasicAttackBehavior", "No destroyable component on %llu", branch.target);
return;
}
@@ -213,7 +213,7 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
bitStream->Write(armorDamageDealt);
bitStream->Write(healthDamageDealt);
bitStream->Write(targetEntity->IsDead());
bitStream->Write(targetEntity->GetIsDead());
}
bitStream->Write(successState);

View File

@@ -13,7 +13,7 @@
#include "DestroyableComponent.h"
#include "EchoSyncSkill.h"
#include "PhantomPhysicsComponent.h"
#include "QuickBuildComponent.h"
#include "RebuildComponent.h"
#include "eReplicaComponentType.h"
#include "eConnectionType.h"
@@ -331,8 +331,8 @@ std::vector<LWOOBJID> BehaviorContext::GetValidTargets(int32_t ignoreFaction, in
}
if (ignoreFaction || includeFaction || (!entity->HasComponent(eReplicaComponentType::PHANTOM_PHYSICS) && targets.empty())) {
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (!destroyableComponent) {
DestroyableComponent* destroyableComponent;
if (!entity->TryGetComponent(eReplicaComponentType::DESTROYABLE, destroyableComponent)) {
return targets;
}

View File

@@ -16,7 +16,7 @@ void HealBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bit_strea
return;
}
auto* destroyable = entity->GetComponent<DestroyableComponent>();
auto* destroyable = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE));
if (destroyable == nullptr) {
Game::logger->Log("HealBehavior", "Failed to find destroyable component for %(llu)!", branch.target);

View File

@@ -4,7 +4,7 @@ void LootBuffBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitSt
auto target = EntityManager::Instance()->GetEntity(context->caster);
if (!target) return;
auto* controllablePhysicsComponent = target->GetComponent<ControllablePhysicsComponent>();
auto controllablePhysicsComponent = target->GetComponent<ControllablePhysicsComponent>();
if (!controllablePhysicsComponent) return;
controllablePhysicsComponent->AddPickupRadiusScale(m_Scale);
@@ -22,7 +22,7 @@ void LootBuffBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext br
auto target = EntityManager::Instance()->GetEntity(context->caster);
if (!target) return;
auto* controllablePhysicsComponent = target->GetComponent<ControllablePhysicsComponent>();
auto controllablePhysicsComponent = target->GetComponent<ControllablePhysicsComponent>();
if (!controllablePhysicsComponent) return;
controllablePhysicsComponent->RemovePickupRadiusScale(m_Scale);

View File

@@ -16,7 +16,7 @@ void RepairBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bit_str
return;
}
auto* destroyable = entity->GetComponent<DestroyableComponent>();
auto* destroyable = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE));
if (destroyable == nullptr) {
Game::logger->Log("RepairBehavior", "Failed to find destroyable component for %(llu)!", branch.target);

View File

@@ -9,7 +9,9 @@ void SkillEventBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bit
auto* caster = EntityManager::Instance()->GetEntity(context->originator);
if (caster != nullptr && target != nullptr && this->m_effectHandle != nullptr && !this->m_effectHandle->empty()) {
target->GetScript()->OnSkillEventFired(target, caster, *this->m_effectHandle);
for (CppScripts::Script* script : CppScripts::GetEntityScripts(target)) {
script->OnSkillEventFired(target, caster, *this->m_effectHandle);
}
}
}
@@ -19,6 +21,8 @@ SkillEventBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitSt
auto* caster = EntityManager::Instance()->GetEntity(context->originator);
if (caster != nullptr && target != nullptr && this->m_effectHandle != nullptr && !this->m_effectHandle->empty()) {
target->GetScript()->OnSkillEventFired(target, caster, *this->m_effectHandle);
for (CppScripts::Script* script : CppScripts::GetEntityScripts(target)) {
script->OnSkillEventFired(target, caster, *this->m_effectHandle);
}
}
}

View File

@@ -6,7 +6,7 @@
#include "Game.h"
#include "dLogger.h"
#include "DestroyableComponent.h"
#include "QuickBuildComponent.h"
#include "RebuildComponent.h"
#include "Entity.h"
#include "EntityInfo.h"
#include "eReplicaComponentType.h"
@@ -53,10 +53,10 @@ void SpawnBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStrea
entity->SetOwnerOverride(context->originator);
// Unset the flag to reposition the player, this makes it harder to glitch out of the map
auto* quickBuildComponent = entity->GetComponent<QuickBuildComponent>();
auto* rebuildComponent = entity->GetComponent<RebuildComponent>();
if (quickBuildComponent != nullptr) {
quickBuildComponent->SetRepositionPlayer(false);
if (rebuildComponent != nullptr) {
rebuildComponent->SetRepositionPlayer(false);
}
EntityManager::Instance()->ConstructEntity(entity);
@@ -87,9 +87,9 @@ void SpawnBehavior::Timer(BehaviorContext* context, const BehaviorBranchContext
return;
}
auto* destroyable = entity->GetComponent<DestroyableComponent>();
auto* destroyable = static_cast<DestroyableComponent*>(entity->GetComponent(eReplicaComponentType::DESTROYABLE));
if (!destroyable) {
if (destroyable == nullptr) {
entity->Smash(context->originator);
return;

View File

@@ -33,7 +33,7 @@ void StunBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStream
* If our target is an enemy we can go ahead and stun it.
*/
auto* combatAiComponent = target->GetComponent<BaseCombatAIComponent>();
auto* combatAiComponent = static_cast<BaseCombatAIComponent*>(target->GetComponent(eReplicaComponentType::BASE_COMBAT_AI));
if (combatAiComponent == nullptr) {
return;
@@ -56,7 +56,7 @@ void StunBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStr
* See if we can stun ourselves
*/
auto* combatAiComponent = self->GetComponent<BaseCombatAIComponent>();
auto* combatAiComponent = static_cast<BaseCombatAIComponent*>(self->GetComponent(eReplicaComponentType::BASE_COMBAT_AI));
if (combatAiComponent == nullptr) {
return;
@@ -91,7 +91,7 @@ void StunBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitStr
* If our target is an enemy we can go ahead and stun it.
*/
auto* combatAiComponent = target->GetComponent<BaseCombatAIComponent>();
auto* combatAiComponent = static_cast<BaseCombatAIComponent*>(target->GetComponent(eReplicaComponentType::BASE_COMBAT_AI));
if (combatAiComponent == nullptr) {
return;

View File

@@ -30,7 +30,7 @@ void SwitchBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bitStre
Game::logger->LogDebug("SwitchBehavior", "[%i] State: (%d), imagination: (%i) / (%f)", entity->GetLOT(), state, destroyableComponent->GetImagination(), destroyableComponent->GetMaxImagination());
if (state) {
if (state || (entity->GetLOT() == 8092 && destroyableComponent->GetImagination() >= m_imagination)) {
this->m_actionTrue->Handle(context, bitStream, branch);
} else {
this->m_actionFalse->Handle(context, bitStream, branch);

View File

@@ -6,7 +6,7 @@
#include "BehaviorContext.h"
#include "BaseCombatAIComponent.h"
#include "EntityManager.h"
#include "QuickBuildComponent.h"
#include "RebuildComponent.h"
#include "DestroyableComponent.h"
#include <vector>
@@ -148,7 +148,7 @@ void TacArcBehavior::Calculate(BehaviorContext* context, RakNet::BitStream* bitS
continue;
}
if (entity->IsDead()) continue;
if (entity->GetIsDead()) continue;
const auto otherPosition = entity->GetPosition();

View File

@@ -8,7 +8,7 @@ void VentureVisionBehavior::Handle(BehaviorContext* context, RakNet::BitStream*
const auto targetEntity = EntityManager::Instance()->GetEntity(branch.target);
if (targetEntity) {
auto* characterComponent = targetEntity->GetComponent<CharacterComponent>();
auto characterComponent = targetEntity->GetComponent<CharacterComponent>();
if (characterComponent) {
if (m_show_collectibles) characterComponent->AddVentureVisionEffect(m_ShowCollectibles);
@@ -24,7 +24,7 @@ void VentureVisionBehavior::UnCast(BehaviorContext* context, BehaviorBranchConte
const auto targetEntity = EntityManager::Instance()->GetEntity(branch.target);
if (targetEntity) {
auto* characterComponent = targetEntity->GetComponent<CharacterComponent>();
auto characterComponent = targetEntity->GetComponent<CharacterComponent>();
if (characterComponent) {
if (m_show_collectibles) characterComponent->RemoveVentureVisionEffect(m_ShowCollectibles);

View File

@@ -1,9 +1,8 @@
#include "eMissionTaskType.h"
#ifndef __ACHIEVEMENTCACHEKEY__H__
#define __ACHIEVEMENTCACHEKEY__H__
#include "eMissionTaskType.h"
#include "GeneralUtils.h"
class AchievementCacheKey {
public:
AchievementCacheKey() {

View File

@@ -1,5 +0,0 @@
#include "AchievementVendorComponent.h"
AchievementVendorComponent::AchievementVendorComponent(Entity* parent) : VendorComponent(parent) {
}

View File

@@ -1,16 +0,0 @@
#ifndef __ACHIEVEMENTVENDORCOMPONENT__H__
#define __ACHIEVEMENTVENDORCOMPONENT__H__
#include "VendorComponent.h"
#include "eReplicaComponentType.h"
class Entity;
class AchievementVendorComponent final : public VendorComponent {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::ACHIEVEMENT_VENDOR;
AchievementVendorComponent(Entity* parent);
};
#endif //!__ACHIEVEMENTVENDORCOMPONENT__H__

View File

@@ -1,621 +0,0 @@
#include "ActivityComponent.h"
#include "GameMessages.h"
#include "CDClientManager.h"
#include "MissionComponent.h"
#include "Character.h"
#include "dZoneManager.h"
#include "ZoneInstanceManager.h"
#include "Game.h"
#include "dLogger.h"
#include <WorldPackets.h>
#include "EntityManager.h"
#include "ChatPackets.h"
#include "Player.h"
#include "PacketUtils.h"
#include "dServer.h"
#include "GeneralUtils.h"
#include "dZoneManager.h"
#include "dConfig.h"
#include "InventoryComponent.h"
#include "DestroyableComponent.h"
#include "Loot.h"
#include "eMissionTaskType.h"
#include "eMatchUpdate.h"
#include "eConnectionType.h"
#include "eChatInternalMessageType.h"
#include "CDCurrencyTableTable.h"
#include "CDActivityRewardsTable.h"
#include "CDActivitiesTable.h"
#include "LeaderboardManager.h"
ActivityComponent::ActivityComponent(Entity* parent, int32_t componentId) : Component(parent) {
m_ActivityID = componentId;
CDActivitiesTable* activitiesTable = CDClientManager::Instance().GetTable<CDActivitiesTable>();
std::vector<CDActivities> activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); });
for (CDActivities activity : activities) {
m_ActivityInfo = activity;
if (static_cast<LeaderboardType>(activity.leaderboardType) == LeaderboardType::Racing && Game::config->GetValue("solo_racing") == "1") {
m_ActivityInfo.minTeamSize = 1;
m_ActivityInfo.minTeams = 1;
}
const auto& transferOverride = parent->GetVar<std::u16string>(u"transferZoneID");
if (!transferOverride.empty()) {
m_ActivityInfo.instanceMapID = std::stoi(GeneralUtils::UTF16ToWTF8(transferOverride));
// TODO: LU devs made me do it (for some reason cannon cove instancer is marked to go to GF survival)
// NOTE: 1301 is GF survival
if (m_ActivityInfo.instanceMapID == 1301) {
m_ActivityInfo.instanceMapID = 1302;
}
}
}
auto* destroyableComponent = m_ParentEntity->GetComponent<DestroyableComponent>();
if (destroyableComponent) {
// check for LMIs and set the loot LMIs
CDActivityRewardsTable* activityRewardsTable = CDClientManager::Instance().GetTable<CDActivityRewardsTable>();
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([=](CDActivityRewards entry) {return (entry.LootMatrixIndex == destroyableComponent->GetLootMatrixID()); });
uint32_t startingLMI = 0;
if (activityRewards.size() > 0) {
startingLMI = activityRewards[0].LootMatrixIndex;
}
if (startingLMI > 0) {
// now time for bodge :)
std::vector<CDActivityRewards> objectTemplateActivities = activityRewardsTable->Query([=](CDActivityRewards entry) {return (activityRewards[0].objectTemplate == entry.objectTemplate); });
for (const auto& item : objectTemplateActivities) {
if (item.activityRating > 0 && item.activityRating < 5) {
m_ActivityLootMatrices.insert({ item.activityRating, item.LootMatrixIndex });
}
}
}
}
}
ActivityComponent::~ActivityComponent()
= default;
void ActivityComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) const {
outBitStream->Write(true);
outBitStream->Write<uint32_t>(m_ActivityPlayers.size());
if (!m_ActivityPlayers.empty()) {
for (const auto& activityPlayer : m_ActivityPlayers) {
outBitStream->Write<LWOOBJID>(activityPlayer->playerID);
for (const auto& activityValue : activityPlayer->values) {
outBitStream->Write<float_t>(activityValue);
}
}
}
}
void ActivityComponent::ReloadConfig() {
CDActivitiesTable* activitiesTable = CDClientManager::Instance().GetTable<CDActivitiesTable>();
std::vector<CDActivities> activities = activitiesTable->Query([=](CDActivities entry) {return (entry.ActivityID == m_ActivityID); });
for (auto activity : activities) {
auto mapID = m_ActivityInfo.instanceMapID;
if ((mapID == 1203 || mapID == 1261 || mapID == 1303 || mapID == 1403) && Game::config->GetValue("solo_racing") == "1") {
m_ActivityInfo.minTeamSize = 1;
m_ActivityInfo.minTeams = 1;
} else {
m_ActivityInfo.minTeamSize = activity.minTeamSize;
m_ActivityInfo.minTeams = activity.minTeams;
}
}
}
void ActivityComponent::HandleMessageBoxResponse(Entity* player, const std::string& id) {
if (m_ActivityInfo.ActivityID == 103) {
return;
}
if (id == "LobbyExit") {
PlayerLeave(player->GetObjectID());
} else if (id == "PlayButton") {
PlayerJoin(player);
}
}
void ActivityComponent::PlayerJoin(Entity* player) {
if (m_ActivityInfo.ActivityID == 103 || PlayerIsInQueue(player) || !IsValidActivity(player)) {
return;
}
// If we have a lobby, queue the player and allow others to join, otherwise spin up an instance on the spot
if (HasLobby()) {
PlayerJoinLobby(player);
} else if (!IsPlayedBy(player)) {
auto* instance = NewInstance();
instance->AddParticipant(player);
}
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
}
void ActivityComponent::PlayerJoinLobby(Entity* player) {
if (!m_ParentEntity->HasComponent(eReplicaComponentType::QUICK_BUILD))
GameMessages::SendMatchResponse(player, player->GetSystemAddress(), 0); // tell the client they joined a lobby
LobbyPlayer* newLobbyPlayer = new LobbyPlayer();
newLobbyPlayer->entityID = player->GetObjectID();
Lobby* playerLobby = nullptr;
auto* character = player->GetCharacter();
if (character != nullptr)
character->SetLastNonInstanceZoneID(dZoneManager::Instance()->GetZone()->GetWorldID());
for (Lobby* lobby : m_Queue) {
if (lobby->players.size() < m_ActivityInfo.maxTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() < m_ActivityInfo.maxTeams) {
// If an empty slot in an existing lobby is found
lobby->players.push_back(newLobbyPlayer);
playerLobby = lobby;
// Update the joining player on players already in the lobby, and update players already in the lobby on the joining player
std::string matchUpdateJoined = "player=9:" + std::to_string(player->GetObjectID()) + "\nplayerName=0:" + player->GetCharacter()->GetName();
for (LobbyPlayer* joinedPlayer : lobby->players) {
auto* entity = joinedPlayer->GetEntity();
if (entity == nullptr) {
continue;
}
std::string matchUpdate = "player=9:" + std::to_string(entity->GetObjectID()) + "\nplayerName=0:" + entity->GetCharacter()->GetName();
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchUpdate, eMatchUpdate::PLAYER_ADDED);
PlayerReady(entity, joinedPlayer->ready);
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateJoined, eMatchUpdate::PLAYER_ADDED);
}
}
}
if (!playerLobby) {
// If all lobbies are full
playerLobby = new Lobby();
playerLobby->players.push_back(newLobbyPlayer);
playerLobby->timer = m_ActivityInfo.waitTime / 1000;
m_Queue.push_back(playerLobby);
}
if (m_ActivityInfo.maxTeamSize != 1 && playerLobby->players.size() >= m_ActivityInfo.minTeamSize || m_ActivityInfo.maxTeamSize == 1 && playerLobby->players.size() >= m_ActivityInfo.minTeams) {
// Update the joining player on the match timer
std::string matchTimerUpdate = "time=3:" + std::to_string(playerLobby->timer);
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY);
}
}
void ActivityComponent::PlayerLeave(LWOOBJID playerID) {
// Removes the player from a lobby and notifies the others, not applicable for non-lobby instances
for (Lobby* lobby : m_Queue) {
for (int i = 0; i < lobby->players.size(); ++i) {
if (lobby->players[i]->entityID == playerID) {
std::string matchUpdateLeft = "player=9:" + std::to_string(playerID);
for (LobbyPlayer* lobbyPlayer : lobby->players) {
auto* entity = lobbyPlayer->GetEntity();
if (entity == nullptr)
continue;
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateLeft, eMatchUpdate::PLAYER_REMOVED);
}
delete lobby->players[i];
lobby->players[i] = nullptr;
lobby->players.erase(lobby->players.begin() + i);
return;
}
}
}
}
void ActivityComponent::Update(float deltaTime) {
std::vector<Lobby*> lobbiesToRemove{};
// Ticks all the lobbies, not applicable for non-instance activities
for (Lobby* lobby : m_Queue) {
for (LobbyPlayer* player : lobby->players) {
auto* entity = player->GetEntity();
if (entity == nullptr) {
PlayerLeave(player->entityID);
return;
}
}
if (lobby->players.empty()) {
lobbiesToRemove.push_back(lobby);
continue;
}
// Update the match time for all players
if (m_ActivityInfo.maxTeamSize != 1 && lobby->players.size() >= m_ActivityInfo.minTeamSize
|| m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() >= m_ActivityInfo.minTeams) {
if (lobby->timer == m_ActivityInfo.waitTime / 1000) {
for (LobbyPlayer* joinedPlayer : lobby->players) {
auto* entity = joinedPlayer->GetEntity();
if (entity == nullptr)
continue;
std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer);
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY);
}
}
lobby->timer -= deltaTime;
}
bool lobbyReady = true;
for (LobbyPlayer* player : lobby->players) {
if (player->ready) continue;
lobbyReady = false;
}
// If everyone's ready, jump the timer
if (lobbyReady && lobby->timer > m_ActivityInfo.startDelay / 1000) {
lobby->timer = m_ActivityInfo.startDelay / 1000;
// Update players in lobby on switch to start delay
std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer);
for (LobbyPlayer* player : lobby->players) {
auto* entity = player->GetEntity();
if (entity == nullptr)
continue;
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_START);
}
}
// The timer has elapsed, start the instance
if (lobby->timer <= 0.0f) {
Game::logger->Log("ActivityComponent", "Setting up instance.");
ActivityInstance* instance = NewInstance();
LoadPlayersIntoInstance(instance, lobby->players);
instance->StartZone();
lobbiesToRemove.push_back(lobby);
}
}
while (!lobbiesToRemove.empty()) {
RemoveLobby(lobbiesToRemove.front());
lobbiesToRemove.erase(lobbiesToRemove.begin());
}
}
void ActivityComponent::RemoveLobby(Lobby* lobby) {
for (int i = 0; i < m_Queue.size(); ++i) {
if (m_Queue[i] == lobby) {
m_Queue.erase(m_Queue.begin() + i);
return;
}
}
}
bool ActivityComponent::HasLobby() const {
// If the player is not in the world he has to be, create a lobby for the transfer
return m_ActivityInfo.instanceMapID != UINT_MAX && m_ActivityInfo.instanceMapID != Game::server->GetZoneID();
}
bool ActivityComponent::IsValidActivity(Entity* player) {
// Makes it so that scripted activities with an unimplemented map cannot be joined
/*if (player->GetGMLevel() < eGameMasterLevel::DEVELOPER && (m_ActivityInfo.instanceMapID == 1302 || m_ActivityInfo.instanceMapID == 1301)) {
if (m_ParentEntity->GetLOT() == 4860) {
auto* missionComponent = player->GetComponent<MissionComponent>();
missionComponent->CompleteMission(229);
}
ChatPackets::SendSystemMessage(player->GetSystemAddress(), u"Sorry, this activity is not ready.");
static_cast<Player*>(player)->SendToZone(dZoneManager::Instance()->GetZone()->GetWorldID()); // Gets them out of this stuck state
return false;
}*/
return true;
}
bool ActivityComponent::PlayerIsInQueue(Entity* player) {
for (Lobby* lobby : m_Queue) {
for (LobbyPlayer* lobbyPlayer : lobby->players) {
if (player->GetObjectID() == lobbyPlayer->entityID) return true;
}
}
return false;
}
bool ActivityComponent::IsPlayedBy(Entity* player) const {
for (const auto* instance : this->m_Instances) {
for (const auto* instancePlayer : instance->GetParticipants()) {
if (instancePlayer != nullptr && instancePlayer->GetObjectID() == player->GetObjectID())
return true;
}
}
return false;
}
bool ActivityComponent::IsPlayedBy(LWOOBJID playerID) const {
for (const auto* instance : this->m_Instances) {
for (const auto* instancePlayer : instance->GetParticipants()) {
if (instancePlayer != nullptr && instancePlayer->GetObjectID() == playerID)
return true;
}
}
return false;
}
bool ActivityComponent::TakeCost(Entity* player) const {
if (m_ActivityInfo.optionalCostLOT <= 0 || m_ActivityInfo.optionalCostCount <= 0)
return true;
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr)
return false;
if (inventoryComponent->GetLotCount(m_ActivityInfo.optionalCostLOT) < m_ActivityInfo.optionalCostCount)
return false;
inventoryComponent->RemoveItem(m_ActivityInfo.optionalCostLOT, m_ActivityInfo.optionalCostCount);
return true;
}
void ActivityComponent::PlayerReady(Entity* player, bool bReady) {
for (Lobby* lobby : m_Queue) {
for (LobbyPlayer* lobbyPlayer : lobby->players) {
if (lobbyPlayer->entityID == player->GetObjectID()) {
lobbyPlayer->ready = bReady;
// Update players in lobby on player being ready
std::string matchReadyUpdate = "player=9:" + std::to_string(player->GetObjectID());
eMatchUpdate readyStatus = eMatchUpdate::PLAYER_READY;
if (!bReady) readyStatus = eMatchUpdate::PLAYER_NOT_READY;
for (LobbyPlayer* otherPlayer : lobby->players) {
auto* entity = otherPlayer->GetEntity();
if (entity == nullptr)
continue;
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchReadyUpdate, readyStatus);
}
}
}
}
}
ActivityInstance* ActivityComponent::NewInstance() {
auto* instance = new ActivityInstance(m_ParentEntity, m_ActivityInfo);
m_Instances.push_back(instance);
return instance;
}
void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const {
for (LobbyPlayer* player : lobby) {
auto* entity = player->GetEntity();
if (entity == nullptr || !TakeCost(entity)) {
continue;
}
instance->AddParticipant(entity);
}
}
const std::vector<ActivityInstance*>& ActivityComponent::GetInstances() const {
return m_Instances;
}
ActivityInstance* ActivityComponent::GetInstance(const LWOOBJID playerID) {
for (const auto* instance : GetInstances()) {
for (const auto* participant : instance->GetParticipants()) {
if (participant->GetObjectID() == playerID)
return const_cast<ActivityInstance*>(instance);
}
}
return nullptr;
}
void ActivityComponent::ClearInstances() {
for (ActivityInstance* instance : m_Instances) {
delete instance;
}
m_Instances.clear();
}
ActivityPlayer* ActivityComponent::GetActivityPlayerData(LWOOBJID playerID) {
for (auto* activityData : m_ActivityPlayers) {
if (activityData->playerID == playerID) {
return activityData;
}
}
return nullptr;
}
void ActivityComponent::RemoveActivityPlayerData(LWOOBJID playerID) {
for (size_t i = 0; i < m_ActivityPlayers.size(); i++) {
if (m_ActivityPlayers[i]->playerID == playerID) {
delete m_ActivityPlayers[i];
m_ActivityPlayers[i] = nullptr;
m_ActivityPlayers.erase(m_ActivityPlayers.begin() + i);
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
return;
}
}
}
ActivityPlayer* ActivityComponent::AddActivityPlayerData(LWOOBJID playerID) {
auto* data = GetActivityPlayerData(playerID);
if (data != nullptr)
return data;
m_ActivityPlayers.push_back(new ActivityPlayer{ playerID, {} });
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
return GetActivityPlayerData(playerID);
}
float_t ActivityComponent::GetActivityValue(LWOOBJID playerID, uint32_t index) {
auto value = -1.0f;
auto* data = GetActivityPlayerData(playerID);
if (data != nullptr) {
value = data->values[std::min(index, (uint32_t)9)];
}
return value;
}
void ActivityComponent::SetActivityValue(LWOOBJID playerID, uint32_t index, float_t value) {
auto* data = AddActivityPlayerData(playerID);
if (data != nullptr) {
data->values[std::min(index, (uint32_t)9)] = value;
}
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
}
void ActivityComponent::PlayerRemove(LWOOBJID playerID) {
for (auto* instance : GetInstances()) {
auto participants = instance->GetParticipants();
for (const auto* participant : participants) {
if (participant != nullptr && participant->GetObjectID() == playerID) {
instance->RemoveParticipant(participant);
RemoveActivityPlayerData(playerID);
// If the instance is empty after the delete of the participant, delete the instance too
if (instance->GetParticipants().empty()) {
m_Instances.erase(std::find(m_Instances.begin(), m_Instances.end(), instance));
delete instance;
}
return;
}
}
}
}
void ActivityInstance::StartZone() {
if (m_Participants.empty())
return;
const auto& participants = GetParticipants();
if (participants.empty())
return;
auto* leader = participants[0];
LWOZONEID zoneId = LWOZONEID(m_ActivityInfo.instanceMapID, 0, leader->GetCharacter()->GetPropertyCloneID());
// only make a team if we have more than one participant
if (participants.size() > 1) {
CBITSTREAM;
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::CREATE_TEAM);
bitStream.Write(leader->GetObjectID());
bitStream.Write(m_Participants.size());
for (const auto& participant : m_Participants) {
bitStream.Write(participant);
}
bitStream.Write(zoneId);
Game::chatServer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
}
const auto cloneId = GeneralUtils::GenerateRandomNumber<uint32_t>(1, UINT32_MAX);
for (Entity* player : participants) {
const auto objid = player->GetObjectID();
ZoneInstanceManager::Instance()->RequestZoneTransfer(Game::server, m_ActivityInfo.instanceMapID, cloneId, false, [objid](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
auto* player = EntityManager::Instance()->GetEntity(objid);
if (player == nullptr)
return;
Game::logger->Log("UserManager", "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()) {
player->GetCharacter()->SetZoneID(zoneID);
player->GetCharacter()->SetZoneInstance(zoneInstance);
player->GetCharacter()->SetZoneClone(zoneClone);
}
WorldPackets::SendTransferToWorld(player->GetSystemAddress(), serverIP, serverPort, mythranShift);
return;
});
}
m_NextZoneCloneID++;
}
void ActivityInstance::RewardParticipant(Entity* participant) {
auto* missionComponent = participant->GetComponent<MissionComponent>();
if (missionComponent) {
missionComponent->Progress(eMissionTaskType::ACTIVITY, m_ActivityInfo.ActivityID);
}
// First, get the activity data
auto* activityRewardsTable = CDClientManager::Instance().GetTable<CDActivityRewardsTable>();
std::vector<CDActivityRewards> activityRewards = activityRewardsTable->Query([=](CDActivityRewards entry) { return (entry.objectTemplate == m_ActivityInfo.ActivityID); });
if (!activityRewards.empty()) {
uint32_t minCoins = 0;
uint32_t maxCoins = 0;
auto* currencyTableTable = CDClientManager::Instance().GetTable<CDCurrencyTableTable>();
std::vector<CDCurrencyTable> currencyTable = currencyTableTable->Query([=](CDCurrencyTable entry) { return (entry.currencyIndex == activityRewards[0].CurrencyIndex && entry.npcminlevel == 1); });
if (!currencyTable.empty()) {
minCoins = currencyTable[0].minvalue;
maxCoins = currencyTable[0].maxvalue;
}
LootGenerator::Instance().DropLoot(participant, m_ParentEntity, activityRewards[0].LootMatrixIndex, minCoins, maxCoins);
}
}
std::vector<Entity*> ActivityInstance::GetParticipants() const {
std::vector<Entity*> entities;
entities.reserve(m_Participants.size());
for (const auto& id : m_Participants) {
auto* entity = EntityManager::Instance()->GetEntity(id);
if (entity != nullptr)
entities.push_back(entity);
}
return entities;
}
void ActivityInstance::AddParticipant(Entity* participant) {
const auto id = participant->GetObjectID();
if (std::count(m_Participants.begin(), m_Participants.end(), id))
return;
m_Participants.push_back(id);
}
void ActivityInstance::RemoveParticipant(const Entity* participant) {
const auto loadedParticipant = std::find(m_Participants.begin(), m_Participants.end(), participant->GetObjectID());
if (loadedParticipant != m_Participants.end()) {
m_Participants.erase(loadedParticipant);
}
}
uint32_t ActivityInstance::GetScore() const {
return score;
}
void ActivityInstance::SetScore(uint32_t score) {
this->score = score;
}
Entity* LobbyPlayer::GetEntity() const {
return EntityManager::Instance()->GetEntity(entityID);
}

View File

@@ -1,379 +0,0 @@
/*
* Darkflame Universe
* Copyright 2018
*/
#include "CDClientManager.h"
#ifndef ACTIVITYCOMPONENT_H
#define ACTIVITYCOMPONENT_H
#include "BitStream.h"
#include "Entity.h"
#include "Component.h"
#include "eReplicaComponentType.h"
#include "CDActivitiesTable.h"
/**
* Represents an instance of an activity, having participants and score
*/
class ActivityInstance {
public:
ActivityInstance(Entity* parent, CDActivities activityInfo) { m_ParentEntity = parent; m_ActivityInfo = activityInfo; };
//~ActivityInstance();
/**
* Adds an entity to this activity
* @param participant the entity to add
*/
void AddParticipant(Entity* participant);
/**
* Removes all the participants from this activity
*/
void ClearParticipants() { m_Participants.clear(); };
/**
* Starts the instance world for this activity and sends all participants there
*/
void StartZone();
/**
* Gives the rewards for completing this activity to some participant
* @param participant the participant to give rewards to
*/
void RewardParticipant(Entity* participant);
/**
* Removes a participant from this activity
* @param participant the participant to remove
*/
void RemoveParticipant(const Entity* participant);
/**
* Returns all the participants of this activity
* @return all the participants of this activity
*/
std::vector<Entity*> GetParticipants() const;
/**
* Currently unused
*/
uint32_t GetScore() const;
/**
* Currently unused
*/
void SetScore(uint32_t score);
private:
/**
* Currently unused
*/
uint32_t score = 0;
/**
* The instance ID of this activity
*/
uint32_t m_NextZoneCloneID = 0;
/**
* The database information for this activity
*/
CDActivities m_ActivityInfo;
/**
* The entity that owns this activity (the entity that has the ActivityComponent)
*/
Entity* m_ParentEntity;
/**
* All the participants of this activity
*/
std::vector<LWOOBJID> m_Participants;
};
/**
* Represents an entity in a lobby
*/
struct LobbyPlayer {
/**
* The ID of the entity that is in the lobby
*/
LWOOBJID entityID;
/**
* Whether or not the entity is ready
*/
bool ready = false;
/**
* Returns the entity that is in the lobby
* @return the entity that is in the lobby
*/
Entity* GetEntity() const;
};
/**
* Represents a lobby of players with a timer until it should start the activity
*/
struct Lobby {
/**
* The lobby of players
*/
std::vector<LobbyPlayer*> players;
/**
* The timer that determines when the activity should start
*/
float timer;
};
/**
* Represents the score for the player in an activity, one index might represent score, another one time, etc.
*/
struct ActivityPlayer {
/**
* The entity that the score is tracked for
*/
LWOOBJID playerID;
/**
* The list of score for this entity
*/
float values[10];
};
/**
* Welcome to the absolute behemoth that is the activity component. I have now clue how this was managed in
* live but I figure somewhat similarly and it's terrible. In a nutshell, this components handles any activity that
* can be done in the game from quick builds to boss fights to races. On top of that, this component handles instancing
* and lobbying.
*/
class ActivityComponent : public Component {
public:
ActivityComponent(Entity* parent, int32_t componentId);
~ActivityComponent() override;
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) const;
/**
* Makes some entity join the minigame, if it's a lobbied one, the entity will be placed in the lobby
* @param player the entity to join the game
*/
void PlayerJoin(Entity* player);
/**
* Makes an entity join the lobby for this minigame, if it exists
* @param player the entity to join
*/
void PlayerJoinLobby(Entity* player);
/**
* Makes the player leave the lobby
* @param playerID the entity to leave the lobby
*/
void PlayerLeave(LWOOBJID playerID);
/**
* Removes the entity from the minigame (and its score)
* @param playerID the entity to remove from the minigame
*/
void PlayerRemove(LWOOBJID playerID);
/**
* Adds all the players to an instance of some activity
* @param instance the instance to load the players into
* @param lobby the players to load into the instance
*/
void LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const;
/**
* Removes a lobby from the activity manager
* @param lobby the lobby to remove
*/
void RemoveLobby(Lobby* lobby);
/**
* Marks a player as (un)ready in a lobby
* @param player the entity to mark
* @param bReady true if the entity is ready, false otherwise
*/
void PlayerReady(Entity* player, bool bReady);
/**
* Returns the ID of this activity
* @return the ID of this activity
*/
int GetActivityID() { return m_ActivityInfo.ActivityID; }
/**
* Returns if this activity has a lobby, e.g. if it needs to instance players to some other map
* @return true if this activity has a lobby, false otherwise
*/
bool HasLobby() const;
/**
* Checks if a player is currently waiting in a lobby
* @param player the entity to check for
* @return true if the entity is waiting in a lobby, false otherwise
*/
bool PlayerIsInQueue(Entity* player);
/**
* Checks if an entity is currently playing this activity
* @param player the entity to check
* @return true if the entity is playing this lobby, false otherwise
*/
bool IsPlayedBy(Entity* player) const;
/**
* Checks if an entity is currently playing this activity
* @param playerID the entity to check
* @return true if the entity is playing this lobby, false otherwise
*/
bool IsPlayedBy(LWOOBJID playerID) const;
/**
* Legacy: used to check for unimplemented maps, gladly, this now just returns true :)
*/
bool IsValidActivity(Entity* player);
/**
* 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
*/
bool TakeCost(Entity* player) const;
/**
* Handles any response from a player clicking on a lobby / instance menu
* @param player the entity that clicked
* @param id the message that was passed
*/
void HandleMessageBoxResponse(Entity* player, const std::string& id);
/**
* Creates a new instance for this activity
* @return a new instance for this activity
*/
ActivityInstance* NewInstance();
/**
* Returns all the currently active instances of this activity
* @return all the currently active instances of this activity
*/
const std::vector<ActivityInstance*>& GetInstances() const;
/**
* Returns the instance that some entity is currently playing in
* @param playerID the entity to check for
* @return if any, the instance that the entity is currently in
*/
ActivityInstance* GetInstance(const LWOOBJID playerID);
/**
* @brief Reloads the config settings for this component
*
*/
void ReloadConfig();
/**
* Removes all the instances
*/
void ClearInstances();
/**
* Returns all the score for the players that are currently playing this activity
* @return
*/
std::vector<ActivityPlayer*> GetActivityPlayers() { return m_ActivityPlayers; };
/**
* Returns activity data for a specific entity (e.g. score and such).
* @param playerID the entity to get data for
* @return the activity data (score) for the passed player in this activity, if it exists
*/
ActivityPlayer* GetActivityPlayerData(LWOOBJID playerID);
/**
* Sets some score value for an entity
* @param playerID the entity to set score for
* @param index the score index to set
* @param value the value to set in for that index
*/
void SetActivityValue(LWOOBJID playerID, uint32_t index, float_t value);
/**
* Returns activity score for the passed parameters
* @param playerID the entity to get score for
* @param index the index to get score for
* @return activity score for the passed parameters
*/
float_t GetActivityValue(LWOOBJID playerID, uint32_t index);
/**
* Removes activity score tracking for some entity
* @param playerID the entity to remove score for
*/
void RemoveActivityPlayerData(LWOOBJID playerID);
/**
* Adds activity score tracking for some entity
* @param playerID the entity to add the activity score for
* @return the created entry
*/
ActivityPlayer* AddActivityPlayerData(LWOOBJID playerID);
/**
* Sets the mapID that this activity points to
* @param mapID the map ID to set
*/
void SetInstanceMapID(uint32_t mapID) { m_ActivityInfo.instanceMapID = mapID; };
/**
* Returns the LMI that this activity points to for a team size
* @param teamSize the team size to get the LMI for
* @return the LMI that this activity points to for a team size
*/
uint32_t GetLootMatrixForTeamSize(uint32_t teamSize) { return m_ActivityLootMatrices[teamSize]; }
private:
/**
* The database information for this activity
*/
CDActivities m_ActivityInfo;
/**
* All the active instances of this activity
*/
std::vector<ActivityInstance*> m_Instances;
/**
* The current lobbies for this activity
*/
std::vector<Lobby*> m_Queue;
/**
* All the activity score for the players in this activity
*/
std::vector<ActivityPlayer*> m_ActivityPlayers;
/**
* LMIs for team sizes
*/
std::unordered_map<uint32_t, uint32_t> m_ActivityLootMatrices;
/**
* The activity id
*
*/
int32_t m_ActivityID;
};
#endif // ACTIVITYCOMPONENT_H

View File

@@ -1,10 +1,6 @@
#include "BaseCombatAIComponent.h"
#include <BitStream.h>
#include <algorithm>
#include <sstream>
#include <vector>
#include "BitStream.h"
#include "Entity.h"
#include "EntityManager.h"
#include "ControllablePhysicsComponent.h"
@@ -19,32 +15,32 @@
#include "CDClientManager.h"
#include "DestroyableComponent.h"
#include <algorithm>
#include <sstream>
#include <vector>
#include "SkillComponent.h"
#include "QuickBuildComponent.h"
#include "RebuildComponent.h"
#include "DestroyableComponent.h"
#include "Metrics.hpp"
#include "CDComponentsRegistryTable.h"
#include "CDPhysicsComponentTable.h"
BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t componentId) : Component(parent) {
BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const uint32_t id): Component(parent) {
m_Target = LWOOBJID_EMPTY;
m_ComponentId = componentId;
SetAiState(AiState::Spawn);
SetAiState(AiState::spawn);
m_Timer = 1.0f;
m_StartPosition = parent->GetPosition();
m_MovementAI = nullptr;
m_Disabled = false;
m_SkillEntries = {};
m_MovementAI = nullptr;
m_SoftTimer = 5.0f;
m_dpEntity = nullptr;
m_dpEntityEnemy = nullptr;
}
void BaseCombatAIComponent::LoadTemplateData() {
//Grab the aggro information from BaseCombatAI:
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;");
componentQuery.bind(1, m_ComponentId);
componentQuery.bind(1, (int)id);
auto componentResult = componentQuery.execQuery();
@@ -67,12 +63,21 @@ void BaseCombatAIComponent::LoadTemplateData() {
componentResult.finalize();
// Get aggro and tether radius from settings and use this if it is present. Only overwrite the
// radii if it is greater than the one in the database.
if (m_Parent) {
auto aggroRadius = m_Parent->GetVar<float>(u"aggroRadius");
m_AggroRadius = aggroRadius != 0 ? aggroRadius : m_AggroRadius;
auto tetherRadius = m_Parent->GetVar<float>(u"tetherRadius");
m_HardTetherRadius = tetherRadius != 0 ? tetherRadius : m_HardTetherRadius;
}
/*
* Find skills
*/
auto skillQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);");
skillQuery.bind(1, m_ParentEntity->GetLOT());
skillQuery.bind(1, (int)parent->GetLOT());
auto result = skillQuery.execQuery();
@@ -85,51 +90,44 @@ void BaseCombatAIComponent::LoadTemplateData() {
auto* behavior = Behavior::CreateBehavior(behaviorId);
m_SkillEntries.push_back(AiSkillEntry(skillId, 0, abilityCooldown, behavior));
std::stringstream behaviorQuery;
AiSkillEntry entry = { skillId, 0, abilityCooldown, behavior };
m_SkillEntries.push_back(entry);
result.nextRow();
}
}
void BaseCombatAIComponent::LoadConfigData() {
// Get aggro and tether radius from settings and use this if it is present. Only overwrite the
// radii if it is greater than the one in the database.
if (m_ParentEntity) {
auto aggroRadius = m_ParentEntity->GetVar<float>(u"aggroRadius");
m_AggroRadius = aggroRadius != 0 ? aggroRadius : m_AggroRadius;
auto tetherRadius = m_ParentEntity->GetVar<float>(u"tetherRadius");
m_HardTetherRadius = tetherRadius != 0 ? tetherRadius : m_HardTetherRadius;
}
}
void BaseCombatAIComponent::Startup() {
Stun(1.0f);
// Add physics
/*
* Add physics
*/
int32_t collisionGroup = (COLLISION_GROUP_DYNAMIC | COLLISION_GROUP_ENEMY);
auto* componentRegistryTable = CDClientManager::Instance().GetTable<CDComponentsRegistryTable>();
if (!componentRegistryTable) return;
CDComponentsRegistryTable* componentRegistryTable = CDClientManager::Instance().GetTable<CDComponentsRegistryTable>();
auto componentID = componentRegistryTable->GetByIDAndType(parent->GetLOT(), eReplicaComponentType::CONTROLLABLE_PHYSICS);
auto componentID = componentRegistryTable->GetByIDAndType(m_ParentEntity->GetLOT(), eReplicaComponentType::CONTROLLABLE_PHYSICS);
auto* physicsComponentTable = CDClientManager::Instance().GetTable<CDPhysicsComponentTable>();
if (!physicsComponentTable) return;
CDPhysicsComponentTable* physicsComponentTable = CDClientManager::Instance().GetTable<CDPhysicsComponentTable>();
if (physicsComponentTable != nullptr) {
auto* info = physicsComponentTable->GetByID(componentID);
if (info) collisionGroup = info->bStatic ? COLLISION_GROUP_NEUTRAL : info->collisionGroup;
if (info != nullptr) {
collisionGroup = info->bStatic ? COLLISION_GROUP_NEUTRAL : info->collisionGroup;
}
}
// Why are these new'd here and then deleted by the dpworld??
//Create a phantom physics volume so we can detect when we're aggro'd.
m_dpEntity = new dpEntity(m_ParentEntity->GetObjectID(), m_AggroRadius);
m_dpEntityEnemy = new dpEntity(m_ParentEntity->GetObjectID(), m_AggroRadius, false);
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), m_AggroRadius);
m_dpEntityEnemy = new dpEntity(m_Parent->GetObjectID(), m_AggroRadius, false);
m_dpEntity->SetCollisionGroup(collisionGroup);
m_dpEntityEnemy->SetCollisionGroup(collisionGroup);
m_dpEntity->SetPosition(m_ParentEntity->GetPosition());
m_dpEntityEnemy->SetPosition(m_ParentEntity->GetPosition());
m_dpEntity->SetPosition(m_Parent->GetPosition());
m_dpEntityEnemy->SetPosition(m_Parent->GetPosition());
dpWorld::Instance().AddEntity(m_dpEntity);
dpWorld::Instance().AddEntity(m_dpEntityEnemy);
@@ -137,29 +135,28 @@ void BaseCombatAIComponent::Startup() {
}
BaseCombatAIComponent::~BaseCombatAIComponent() {
if (m_dpEntity) dpWorld::Instance().RemoveEntity(m_dpEntity);
if (m_dpEntity)
dpWorld::Instance().RemoveEntity(m_dpEntity);
if (m_dpEntityEnemy) dpWorld::Instance().RemoveEntity(m_dpEntityEnemy);
m_MovementAI = nullptr;
m_dpEntity = nullptr;
m_dpEntityEnemy = nullptr;
if (m_dpEntityEnemy)
dpWorld::Instance().RemoveEntity(m_dpEntityEnemy);
}
void BaseCombatAIComponent::Update(const float deltaTime) {
//First, we need to process physics:
if (!m_dpEntity) return;
m_dpEntity->SetPosition(m_ParentEntity->GetPosition()); //make sure our position is synced with our dpEntity
m_dpEntityEnemy->SetPosition(m_ParentEntity->GetPosition());
m_dpEntity->SetPosition(m_Parent->GetPosition()); //make sure our position is synced with our dpEntity
m_dpEntityEnemy->SetPosition(m_Parent->GetPosition());
//Process enter events
for (auto en : m_dpEntity->GetNewObjects()) {
m_ParentEntity->OnCollisionPhantom(en->GetObjectID());
m_Parent->OnCollisionPhantom(en->GetObjectID());
}
//Process exit events
for (auto en : m_dpEntity->GetRemovedObjects()) {
m_ParentEntity->OnCollisionLeavePhantom(en->GetObjectID());
m_Parent->OnCollisionLeavePhantom(en->GetObjectID());
}
// Check if we should stop the tether effect
@@ -168,35 +165,39 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
const auto& info = m_MovementAI->GetInfo();
if (m_Target != LWOOBJID_EMPTY || (NiPoint3::DistanceSquared(
m_StartPosition,
m_ParentEntity->GetPosition()) < 20 * 20 && m_TetherTime <= 0)
m_Parent->GetPosition()) < 20 * 20 && m_TetherTime <= 0)
) {
GameMessages::SendStopFXEffect(m_ParentEntity, true, "tether");
GameMessages::SendStopFXEffect(m_Parent, true, "tether");
m_TetherEffectActive = false;
}
}
if (m_SoftTimer <= 0.0f) {
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
m_SoftTimer = 5.0f;
} else {
m_SoftTimer -= deltaTime;
}
if (m_Disabled || m_ParentEntity->IsDead()) return;
if (m_Disabled || m_Parent->GetIsDead())
return;
bool stunnedThisFrame = m_Stunned;
CalculateCombat(deltaTime); // Putting this here for now
if (m_StartPosition == NiPoint3::ZERO) {
m_StartPosition = m_ParentEntity->GetPosition();
m_StartPosition = m_Parent->GetPosition();
}
if (!m_MovementAI) {
m_MovementAI = m_ParentEntity->GetComponent<MovementAIComponent>();
if (!m_MovementAI) return;
m_MovementAI = m_Parent->GetComponent<MovementAIComponent>();
if (m_MovementAI == nullptr) {
return;
}
if (stunnedThisFrame) {
m_MovementAI->Stop();
return;
}
@@ -206,25 +207,24 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
}
switch (m_State) {
case AiState::Spawn:
case AiState::spawn:
Stun(2.0f);
SetAiState(AiState::Idle);
SetAiState(AiState::idle);
break;
case AiState::Idle:
case AiState::idle:
Wander();
break;
case AiState::Aggro:
case AiState::aggro:
OnAggro();
break;
case AiState::Tether:
case AiState::tether:
OnTether();
break;
default:
Game::logger->Log("BaseCombatAIComponent", "Entity %i is in an invalid state %i", m_ParentEntity->GetLOT(), m_State);
break;
}
}
@@ -243,7 +243,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
bool hadRemainingDowntime = m_SkillTime > 0.0f;
if (m_SkillTime > 0.0f) m_SkillTime -= deltaTime;
auto* rebuild = m_ParentEntity->GetComponent<QuickBuildComponent>();
auto* rebuild = m_Parent->GetComponent<RebuildComponent>();
if (rebuild != nullptr) {
const auto state = rebuild->GetState();
@@ -253,9 +253,11 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
}
}
auto* skillComponent = m_ParentEntity->GetComponent<SkillComponent>();
auto* skillComponent = m_Parent->GetComponent<SkillComponent>();
if (!skillComponent) return;
if (skillComponent == nullptr) {
return;
}
skillComponent->CalculateUpdate(deltaTime);
@@ -285,7 +287,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
}
if (!m_TetherEffectActive && m_OutOfCombat && (m_OutOfCombatTime -= deltaTime) <= 0) {
auto* destroyableComponent = m_ParentEntity->GetComponent<DestroyableComponent>();
auto* destroyableComponent = m_Parent->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr && destroyableComponent->HasFaction(4)) {
auto serilizationRequired = false;
@@ -303,10 +305,10 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
}
if (serilizationRequired) {
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
GameMessages::SendPlayFXEffect(m_ParentEntity->GetObjectID(), 6270, u"tether", "tether");
GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), 6270, u"tether", "tether");
m_TetherEffectActive = true;
@@ -327,19 +329,19 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
SetTarget(newTarget);
if (m_Target != LWOOBJID_EMPTY) {
if (m_State == AiState::Idle) {
if (m_State == AiState::idle) {
m_Timer = 0;
}
SetAiState(AiState::Aggro);
SetAiState(AiState::aggro);
} else {
SetAiState(AiState::Idle);
SetAiState(AiState::idle);
}
if (!hasSkillToCast) return;
if (m_Target == LWOOBJID_EMPTY) {
SetAiState(AiState::Idle);
SetAiState(AiState::idle);
return;
}
@@ -364,7 +366,7 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
m_MovementAI->Stop();
}
SetAiState(AiState::Aggro);
SetAiState(AiState::aggro);
m_Timer = 0;
@@ -380,6 +382,8 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
}
LWOOBJID BaseCombatAIComponent::FindTarget() {
//const auto reference = m_MovementAI == nullptr ? m_StartPosition : m_MovementAI->ApproximateLocation();
NiPoint3 reference = m_StartPosition;
if (m_MovementAI) reference = m_MovementAI->ApproximateLocation();
@@ -492,10 +496,10 @@ LWOOBJID BaseCombatAIComponent::FindTarget() {
std::vector<LWOOBJID> BaseCombatAIComponent::GetTargetWithinAggroRange() const {
std::vector<LWOOBJID> targets;
for (auto id : m_ParentEntity->GetTargetsInPhantom()) {
for (auto id : m_Parent->GetTargetsInPhantom()) {
auto* other = EntityManager::Instance()->GetEntity(id);
const auto distance = Vector3::DistanceSquared(m_ParentEntity->GetPosition(), other->GetPosition());
const auto distance = Vector3::DistanceSquared(m_Parent->GetPosition(), other->GetPosition());
if (distance > m_AggroRadius * m_AggroRadius) continue;
@@ -505,12 +509,25 @@ std::vector<LWOOBJID> BaseCombatAIComponent::GetTargetWithinAggroRange() const {
return targets;
}
bool BaseCombatAIComponent::IsMech() {
switch (m_Parent->GetLOT()) {
case 6253:
return true;
default:
return false;
}
return false;
}
void BaseCombatAIComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write(m_DirtyStateOrTarget);
if (bIsInitialUpdate || m_DirtyStateOrTarget) {
outBitStream->Write(m_State);
outBitStream->Write(m_DirtyStateOrTarget || bIsInitialUpdate);
if (m_DirtyStateOrTarget || bIsInitialUpdate) {
outBitStream->Write(uint32_t(m_State));
outBitStream->Write(m_Target);
if (!bIsInitialUpdate) m_DirtyStateOrTarget = false;
m_DirtyStateOrTarget = false;
}
}
@@ -518,13 +535,13 @@ void BaseCombatAIComponent::SetAiState(AiState newState) {
if (newState == this->m_State) return;
this->m_State = newState;
m_DirtyStateOrTarget = true;
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const {
auto* entity = EntityManager::Instance()->GetEntity(target);
if (!entity) {
if (entity == nullptr) {
Game::logger->Log("BaseCombatAIComponent", "Invalid entity for checking validity (%llu)!", target);
return false;
@@ -532,19 +549,21 @@ bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const {
auto* destroyable = entity->GetComponent<DestroyableComponent>();
if (!destroyable) return false;
if (destroyable == nullptr) {
return false;
}
auto* referenceDestroyable = m_ParentEntity->GetComponent<DestroyableComponent>();
auto* referenceDestroyable = m_Parent->GetComponent<DestroyableComponent>();
if (!referenceDestroyable) {
Game::logger->Log("BaseCombatAIComponent", "Invalid reference destroyable component on (%llu)!", m_ParentEntity->GetObjectID());
if (referenceDestroyable == nullptr) {
Game::logger->Log("BaseCombatAIComponent", "Invalid reference destroyable component on (%llu)!", m_Parent->GetObjectID());
return false;
}
auto* quickbuild = entity->GetComponent<QuickBuildComponent>();
auto* quickbuild = entity->GetComponent<RebuildComponent>();
if (quickbuild) {
if (quickbuild != nullptr) {
const auto state = quickbuild->GetState();
if (state != eRebuildState::COMPLETED) {
@@ -556,7 +575,7 @@ bool BaseCombatAIComponent::IsEnemy(LWOOBJID target) const {
auto candidateList = destroyable->GetFactionIDs();
for (const auto value : candidateList) {
for (auto value : candidateList) {
if (std::find(enemyList.begin(), enemyList.end(), value) != enemyList.end()) {
return true;
}
@@ -569,7 +588,7 @@ void BaseCombatAIComponent::SetTarget(const LWOOBJID target) {
if (this->m_Target == target) return;
m_Target = target;
m_DirtyStateOrTarget = true;
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
Entity* BaseCombatAIComponent::GetTargetEntity() const {
@@ -578,7 +597,8 @@ Entity* BaseCombatAIComponent::GetTargetEntity() const {
void BaseCombatAIComponent::Taunt(LWOOBJID offender, float threat) {
// Can't taunt self
if (offender == m_ParentEntity->GetObjectID()) return;
if (offender == m_Parent->GetObjectID())
return;
m_ThreatEntries[offender] += threat;
m_DirtyThreat = true;
@@ -613,7 +633,9 @@ void BaseCombatAIComponent::ClearThreat() {
}
void BaseCombatAIComponent::Wander() {
if (!m_MovementAI->AtFinalWaypoint()) return;
if (!m_MovementAI->AtFinalWaypoint()) {
return;
}
m_MovementAI->SetHaltDistance(0);
@@ -656,7 +678,9 @@ void BaseCombatAIComponent::OnAggro() {
auto* target = GetTargetEntity();
if (!target) return;
if (target == nullptr) {
return;
}
m_MovementAI->SetHaltDistance(m_AttackRadius);
@@ -679,7 +703,7 @@ void BaseCombatAIComponent::OnAggro() {
m_MovementAI->SetDestination(targetPos);
SetAiState(AiState::Tether);
SetAiState(AiState::tether);
}
m_Timer += 0.5f;
@@ -705,7 +729,7 @@ void BaseCombatAIComponent::OnTether() {
m_MovementAI->SetDestination(m_StartPosition);
SetAiState(AiState::Aggro);
SetAiState(AiState::aggro);
} else {
if (IsMech() && Vector3::DistanceSquared(targetPos, currentPos) > m_AttackRadius * m_AttackRadius * 3 * 3) return;
@@ -717,18 +741,62 @@ void BaseCombatAIComponent::OnTether() {
m_Timer += 0.5f;
}
bool BaseCombatAIComponent::GetStunned() const {
return m_Stunned;
}
void BaseCombatAIComponent::SetStunned(const bool value) {
m_Stunned = value;
}
bool BaseCombatAIComponent::GetStunImmune() const {
return m_StunImmune;
}
void BaseCombatAIComponent::SetStunImmune(bool value) {
m_StunImmune = value;
}
float BaseCombatAIComponent::GetTetherSpeed() const {
return m_TetherSpeed;
}
void BaseCombatAIComponent::SetTetherSpeed(float value) {
m_TetherSpeed = value;
}
void BaseCombatAIComponent::Stun(const float time) {
if (m_StunImmune || m_StunTime > time) return;
if (m_StunImmune || m_StunTime > time) {
return;
}
m_StunTime = time;
m_Stunned = true;
}
void BaseCombatAIComponent::LookAt(const NiPoint3& point) {
if (m_Stunned) return;
float BaseCombatAIComponent::GetAggroRadius() const {
return m_AggroRadius;
}
m_ParentEntity->SetRotation(NiQuaternion::LookAt(m_ParentEntity->GetPosition(), point));
void BaseCombatAIComponent::SetAggroRadius(const float value) {
m_AggroRadius = value;
}
void BaseCombatAIComponent::LookAt(const NiPoint3& point) {
if (m_Stunned) {
return;
}
m_Parent->SetRotation(NiQuaternion::LookAt(m_Parent->GetPosition(), point));
}
void BaseCombatAIComponent::SetDisabled(bool value) {
m_Disabled = value;
}
bool BaseCombatAIComponent::GetDistabled() const {
return m_Disabled;
}
void BaseCombatAIComponent::Sleep() {

View File

@@ -10,7 +10,6 @@
#include "Component.h"
#include "eReplicaComponentType.h"
#include <memory>
#include <vector>
#include <map>
@@ -20,25 +19,20 @@ class Entity;
/**
* The current state of the AI
*/
enum class AiState : int32_t {
Idle = 0, // Doing nothing
Aggro, // Waiting for an enemy to cross / running back to spawn
Tether, // Chasing an enemy
Spawn, // Spawning into the world
Dead // Killed
enum class AiState : int {
idle = 0, // Doing nothing
aggro, // Waiting for an enemy to cross / running back to spawn
tether, // Chasing an enemy
spawn, // Spawning into the world
dead // Killed
};
/**
* Represents a skill that can be cast by this enemy, including its cooldowns, which determines how often the skill
* may be cast.
*/
struct AiSkillEntry {
AiSkillEntry(uint32_t skillId, float cooldown, float abilityCooldown, Behavior* behavior) {
this->skillId = skillId;
this->cooldown = cooldown;
this->abilityCooldown = abilityCooldown;
this->behavior = behavior;
}
struct AiSkillEntry
{
uint32_t skillId;
float cooldown;
@@ -51,16 +45,13 @@ struct AiSkillEntry {
/**
* Handles the AI of entities, making them wander, tether and attack their enemies
*/
class BaseCombatAIComponent final : public Component {
class BaseCombatAIComponent : public Component {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::BASE_COMBAT_AI;
static const eReplicaComponentType ComponentType = eReplicaComponentType::BASE_COMBAT_AI;
BaseCombatAIComponent(Entity* parentEntity, uint32_t id);
~BaseCombatAIComponent() override;
void LoadTemplateData() override;
void LoadConfigData() override;
void Startup() override;
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
@@ -155,37 +146,37 @@ public:
* Gets whether or not the entity is currently stunned
* @return whether the entity is currently stunned
*/
bool GetStunned() const { return m_Stunned; }
bool GetStunned() const;
/**
* (un)stuns the entity, determining whether it'll be able to attack other entities
* @param value whether the enemy is stunned
*/
void SetStunned(bool value) { m_Stunned = value; }
void SetStunned(bool value);
/**
* Gets if this entity may be stunned
* @return if this entity may be stunned
*/
bool GetStunImmune() const { return m_StunImmune; }
bool GetStunImmune() const;
/**
* Set the stun immune value, determining if the entity may be stunned
* @param value
*/
void SetStunImmune(bool value) { m_StunImmune = value; }
void SetStunImmune(bool value);
/**
* Gets the current speed at which an entity runs when tethering
* @return the current speed at which an entity runs when tethering
*/
float GetTetherSpeed() const { return m_TetherSpeed; }
float GetTetherSpeed() const;
/**
* Sets the speed at which an entity will tether
* @param value the new tether speed
*/
void SetTetherSpeed(float value) { m_TetherSpeed = value; }
void SetTetherSpeed(float value);
/**
* Stuns the entity for a certain amount of time, will not work if the entity is stun immune
@@ -197,13 +188,13 @@ public:
* Gets the radius that will cause this entity to get aggro'd, causing a target chase
* @return the aggro radius of the entity
*/
float GetAggroRadius() const { return m_AggroRadius; }
float GetAggroRadius() const;
/**
* Sets the aggro radius, causing the entity to start chasing enemies in this range
* @param value the aggro radius to set
*/
void SetAggroRadius(float value) { m_AggroRadius = value; }
void SetAggroRadius(float value);
/**
* Makes the entity look at a certain point in space
@@ -215,13 +206,13 @@ public:
* (dis)ables the AI, causing it to stop/start attacking enemies
* @param value
*/
void SetDisabled(bool value) { m_Disabled = value; }
void SetDisabled(bool value);
/**
* Gets the current state of the AI, whether or not it's looking for enemies to attack
* @return
*/
bool GetDistabled() const { return m_Disabled; }
bool GetDistabled() const;
/**
* Turns the entity asleep, stopping updates to its physics volumes
@@ -395,9 +386,7 @@ private:
* Whether the current entity is a mech enemy, needed as mechs tether radius works differently
* @return whether this entity is a mech
*/
bool IsMech() const { return m_ParentEntity->GetLOT() == 6253; };
int32_t m_ComponentId;
bool IsMech();
};
#endif // BASECOMBATAICOMPONENT_H

View File

@@ -6,50 +6,66 @@
#include "Game.h"
#include "dLogger.h"
#include "GameMessages.h"
#include "BitStream.h"
#include <BitStream.h>
#include "eTriggerEventType.h"
BouncerComponent::BouncerComponent(Entity* parent) : Component(parent) {
m_BounceOnCollision = false;
m_DirtyBounceInfo = false;
m_PetEnabled = false;
m_PetBouncerEnabled = false;
m_PetSwitchLoaded = false;
if (parent->GetLOT() == 7625) {
LookupPetSwitch();
}
}
void BouncerComponent::Startup() {
if (m_ParentEntity->GetLOT() == 7625) LookupPetSwitch();
BouncerComponent::~BouncerComponent() {
}
void BouncerComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write(bIsInitialUpdate || m_DirtyBounceInfo);
if (bIsInitialUpdate || m_DirtyBounceInfo) {
outBitStream->Write(m_BounceOnCollision);
if (!bIsInitialUpdate) m_DirtyBounceInfo = false;
outBitStream->Write(m_PetEnabled);
if (m_PetEnabled) {
outBitStream->Write(m_PetBouncerEnabled);
}
}
void BouncerComponent::SetBounceOnCollision(bool value) {
if (m_BounceOnCollision == value) return;
m_BounceOnCollision = value;
m_DirtyBounceInfo = true;
Entity* BouncerComponent::GetParentEntity() const {
return m_Parent;
}
void BouncerComponent::SetBouncerEnabled(bool value) {
m_BounceOnCollision = value;
void BouncerComponent::SetPetEnabled(bool value) {
m_PetEnabled = value;
GameMessages::SendBouncerActiveStatus(m_ParentEntity->GetObjectID(), value, UNASSIGNED_SYSTEM_ADDRESS);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
void BouncerComponent::SetPetBouncerEnabled(bool value) {
m_PetBouncerEnabled = value;
GameMessages::SendBouncerActiveStatus(m_Parent->GetObjectID(), value, UNASSIGNED_SYSTEM_ADDRESS);
EntityManager::Instance()->SerializeEntity(m_Parent);
if (value) {
m_ParentEntity->TriggerEvent(eTriggerEventType::PET_ON_SWITCH, m_ParentEntity);
GameMessages::SendPlayFXEffect(m_ParentEntity->GetObjectID(), 1513, u"create", "PetOnSwitch");
m_Parent->TriggerEvent(eTriggerEventType::PET_ON_SWITCH, m_Parent);
GameMessages::SendPlayFXEffect(m_Parent->GetObjectID(), 1513, u"create", "PetOnSwitch", LWOOBJID_EMPTY, 1, 1, true);
} else {
m_ParentEntity->TriggerEvent(eTriggerEventType::PET_OFF_SWITCH, m_ParentEntity);
GameMessages::SendStopFXEffect(m_ParentEntity, true, "PetOnSwitch");
m_Parent->TriggerEvent(eTriggerEventType::PET_OFF_SWITCH, m_Parent);
GameMessages::SendStopFXEffect(m_Parent, true, "PetOnSwitch");
}
}
bool BouncerComponent::GetPetEnabled() const {
return m_PetEnabled;
}
bool BouncerComponent::GetPetBouncerEnabled() const {
return m_PetBouncerEnabled;
}
void BouncerComponent::LookupPetSwitch() {
const auto& groups = m_ParentEntity->GetGroups();
const auto& groups = m_Parent->GetGroups();
for (const auto& group : groups) {
const auto& entities = EntityManager::Instance()->GetEntitiesInGroup(group);
@@ -57,22 +73,24 @@ void BouncerComponent::LookupPetSwitch() {
for (auto* entity : entities) {
auto* switchComponent = entity->GetComponent<SwitchComponent>();
if (!switchComponent) continue;
if (switchComponent != nullptr) {
switchComponent->SetPetBouncer(this);
m_DirtyBounceInfo = true;
m_PetSwitchLoaded = true;
m_PetEnabled = true;
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
Game::logger->Log("BouncerComponent", "Loaded bouncer %i:%llu", m_ParentEntity->GetLOT(), m_ParentEntity->GetObjectID());
return;
Game::logger->Log("BouncerComponent", "Loaded pet bouncer");
}
}
}
float retryTime = 0.5f;
Game::logger->Log("BouncerComponent", "Failed to load pet bouncer for %i:%llu, trying again in %f seconds", m_ParentEntity->GetLOT(), m_ParentEntity->GetObjectID(), retryTime);
if (!m_PetSwitchLoaded) {
Game::logger->Log("BouncerComponent", "Failed to load pet bouncer");
m_ParentEntity->AddCallbackTimer(retryTime, [this]() {
m_Parent->AddCallbackTimer(0.5f, [this]() {
LookupPetSwitch();
});
}
}

View File

@@ -10,27 +10,41 @@
/**
* Attached to bouncer entities, allowing other entities to bounce off of it
*/
class BouncerComponent final : public Component {
class BouncerComponent : public Component {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::BOUNCER;
static const eReplicaComponentType ComponentType = eReplicaComponentType::BOUNCER;
BouncerComponent(Entity* parentEntity);
~BouncerComponent() override;
void Startup() override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
Entity* GetParentEntity() const;
/**
* Sets whether or not this bouncer needs to be activated by a pet
* @param value whether or not this bouncer needs to be activated by a pet
*/
void SetBounceOnCollision(bool value);
void SetPetEnabled(bool value);
/**
* Sets whether or not this bouncer is currently being activated by a pet, allowing entities to bounce off of it,
* also displays FX accordingly.
* @param value whether or not this bouncer is activated by a pet
*/
void SetBouncerEnabled(bool value);
void SetPetBouncerEnabled(bool value);
/**
* Gets whether this bouncer should be enabled using pets
* @return whether this bouncer should be enabled using pets
*/
bool GetPetEnabled() const;
/**
* Gets whether this bouncer is currently activated by a pet
* @return whether this bouncer is currently activated by a pet
*/
bool GetPetBouncerEnabled() const;
/**
* Finds the switch used to activate this bouncer if its pet-enabled and stores this components' state there
@@ -38,12 +52,20 @@ public:
void LookupPetSwitch();
private:
/**
* Whether this bouncer needs to be activated by a pet
*/
bool m_PetEnabled;
/**
* Whether this bouncer is currently being activated by a pet
*/
bool m_BounceOnCollision;
bool m_PetBouncerEnabled;
bool m_DirtyBounceInfo;
/**
* Whether the pet switch for this bouncer has been located
*/
bool m_PetSwitchLoaded;
};
#endif // BOUNCERCOMPONENT_H

View File

@@ -14,12 +14,20 @@
std::unordered_map<int32_t, std::vector<BuffParameter>> BuffComponent::m_Cache{};
BuffComponent::BuffComponent(Entity* parent) : Component(parent) {
}
BuffComponent::~BuffComponent() {
}
void BuffComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
if (!bIsInitialUpdate) return;
outBitStream->Write(!m_Buffs.empty());
if (!m_Buffs.empty()) {
if (m_Buffs.empty()) {
outBitStream->Write0();
} else {
outBitStream->Write1();
outBitStream->Write<uint32_t>(m_Buffs.size());
for (const auto& buff : m_Buffs) {
outBitStream->Write<uint32_t>(buff.first);
outBitStream->Write0();
@@ -39,7 +47,7 @@ void BuffComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUp
}
}
outBitStream->Write0(); // immunities
outBitStream->Write0();
}
void BuffComponent::Update(float deltaTime) {
@@ -47,28 +55,30 @@ void BuffComponent::Update(float deltaTime) {
* Loop through all buffs and apply deltaTime to ther time.
* If they have expired, remove the buff and break.
*/
for (auto& [buffId, buffInfo] : m_Buffs) {
for (auto& buff : m_Buffs) {
// For damage buffs
if (buffInfo.tick != 0.0f && buffInfo.stacks > 0) {
buffInfo.tickTime -= deltaTime;
if (buff.second.tick != 0.0f && buff.second.stacks > 0) {
buff.second.tickTime -= deltaTime;
if (buffInfo.tickTime <= 0.0f) {
buffInfo.tickTime = buffInfo.tick;
buffInfo.stacks--;
if (buff.second.tickTime <= 0.0f) {
buff.second.tickTime = buff.second.tick;
buff.second.stacks--;
SkillComponent::HandleUnmanaged(buffInfo.behaviorID, m_ParentEntity->GetObjectID(), buffInfo.source);
SkillComponent::HandleUnmanaged(buff.second.behaviorID, m_Parent->GetObjectID(), buff.second.source);
}
}
// These are indefinate buffs, don't update them.
if (buffInfo.time == 0.0f) continue;
if (buff.second.time == 0.0f) {
continue;
}
buffInfo.time -= deltaTime;
buff.second.time -= deltaTime;
if (buffInfo.time <= 0.0f) {
RemoveBuff(buffId);
if (buff.second.time <= 0.0f) {
RemoveBuff(buff.first);
break; // Break because we modified or may modify the map.
break;
}
}
}
@@ -77,9 +87,11 @@ void BuffComponent::ApplyBuff(const int32_t id, const float duration, const LWOO
bool cancelOnDamaged, bool cancelOnDeath, bool cancelOnLogout, bool cancelOnRemoveBuff,
bool cancelOnUi, bool cancelOnUnequip, bool cancelOnZone) {
// Prevent buffs from stacking.
if (HasBuff(id)) return;
if (HasBuff(id)) {
return;
}
GameMessages::SendAddBuff(m_ParentEntity->GetObjectID(), source, (uint32_t)id,
GameMessages::SendAddBuff(const_cast<LWOOBJID&>(m_Parent->GetObjectID()), source, (uint32_t)id,
(uint32_t)duration * 1000, addImmunity, cancelOnDamaged, cancelOnDeath,
cancelOnLogout, cancelOnRemoveBuff, cancelOnUi, cancelOnUnequip, cancelOnZone);
@@ -88,14 +100,14 @@ void BuffComponent::ApplyBuff(const int32_t id, const float duration, const LWOO
int32_t behaviorID = 0;
const auto& parameters = GetBuffParameters(id);
auto* skillBehaviorTable = CDClientManager::Instance().GetTable<CDSkillBehaviorTable>();
for (const auto& parameter : parameters) {
if (parameter.name == "overtime") {
auto* behaviorTemplateTable = CDClientManager::Instance().GetTable<CDSkillBehaviorTable>();
behaviorID = skillBehaviorTable->GetSkillByID(parameter.values.skillId).behaviorID;
stacks = static_cast<int32_t>(parameter.values.stacks);
tick = parameter.values.tick;
const auto unknown2 = parameter.values.unknown2; // Always 0
behaviorID = behaviorTemplateTable->GetSkillByID(parameter.values[0]).behaviorID;
stacks = static_cast<int32_t>(parameter.values[1]);
tick = parameter.values[2];
const auto unknown2 = parameter.values[3]; // Always 0
}
}
@@ -110,28 +122,34 @@ void BuffComponent::ApplyBuff(const int32_t id, const float duration, const LWOO
buff.source = source;
buff.behaviorID = behaviorID;
m_Buffs.insert_or_assign(id, buff);
m_Buffs.emplace(id, buff);
}
void BuffComponent::RemoveBuff(int32_t id, bool fromUnEquip, bool removeImmunity) {
const auto& iter = m_Buffs.find(id);
if (iter == m_Buffs.end()) return;
if (iter == m_Buffs.end()) {
return;
}
GameMessages::SendRemoveBuff(m_ParentEntity, fromUnEquip, removeImmunity, id);
GameMessages::SendRemoveBuff(m_Parent, fromUnEquip, removeImmunity, id);
m_Buffs.erase(iter);
RemoveBuffEffect(id);
}
bool BuffComponent::HasBuff(int32_t id) {
return m_Buffs.find(id) != m_Buffs.end();
}
void BuffComponent::ApplyBuffEffect(int32_t id) {
const auto& parameters = GetBuffParameters(id);
for (const auto& parameter : parameters) {
if (parameter.name == "max_health") {
const auto maxHealth = parameter.value;
auto* destroyable = this->GetParentEntity()->GetComponent<DestroyableComponent>();
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
if (destroyable == nullptr) return;
@@ -139,7 +157,7 @@ void BuffComponent::ApplyBuffEffect(int32_t id) {
} else if (parameter.name == "max_armor") {
const auto maxArmor = parameter.value;
auto* destroyable = this->GetParentEntity()->GetComponent<DestroyableComponent>();
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
if (destroyable == nullptr) return;
@@ -147,13 +165,13 @@ void BuffComponent::ApplyBuffEffect(int32_t id) {
} else if (parameter.name == "max_imagination") {
const auto maxImagination = parameter.value;
auto* destroyable = this->GetParentEntity()->GetComponent<DestroyableComponent>();
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
if (destroyable == nullptr) return;
destroyable->SetMaxImagination(destroyable->GetMaxImagination() + maxImagination);
} else if (parameter.name == "speed") {
auto* controllablePhysicsComponent = this->GetParentEntity()->GetComponent<ControllablePhysicsComponent>();
auto* controllablePhysicsComponent = this->GetParent()->GetComponent<ControllablePhysicsComponent>();
if (!controllablePhysicsComponent) return;
const auto speed = parameter.value;
controllablePhysicsComponent->AddSpeedboost(speed);
@@ -167,29 +185,29 @@ void BuffComponent::RemoveBuffEffect(int32_t id) {
if (parameter.name == "max_health") {
const auto maxHealth = parameter.value;
auto* destroyable = this->GetParentEntity()->GetComponent<DestroyableComponent>();
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
if (!destroyable) return;
if (destroyable == nullptr) return;
destroyable->SetMaxHealth(destroyable->GetMaxHealth() - maxHealth);
} else if (parameter.name == "max_armor") {
const auto maxArmor = parameter.value;
auto* destroyable = this->GetParentEntity()->GetComponent<DestroyableComponent>();
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
if (!destroyable) return;
if (destroyable == nullptr) return;
destroyable->SetMaxArmor(destroyable->GetMaxArmor() - maxArmor);
} else if (parameter.name == "max_imagination") {
const auto maxImagination = parameter.value;
auto* destroyable = this->GetParentEntity()->GetComponent<DestroyableComponent>();
auto* destroyable = this->GetParent()->GetComponent<DestroyableComponent>();
if (!destroyable) return;
if (destroyable == nullptr) return;
destroyable->SetMaxImagination(destroyable->GetMaxImagination() - maxImagination);
} else if (parameter.name == "speed") {
auto* controllablePhysicsComponent = this->GetParentEntity()->GetComponent<ControllablePhysicsComponent>();
auto* controllablePhysicsComponent = this->GetParent()->GetComponent<ControllablePhysicsComponent>();
if (!controllablePhysicsComponent) return;
const auto speed = parameter.value;
controllablePhysicsComponent->RemoveSpeedboost(speed);
@@ -198,19 +216,27 @@ void BuffComponent::RemoveBuffEffect(int32_t id) {
}
void BuffComponent::RemoveAllBuffs() {
for (const auto& [buffId, buffInfo] : m_Buffs) {
RemoveBuffEffect(buffId);
for (const auto& buff : m_Buffs) {
RemoveBuffEffect(buff.first);
}
m_Buffs.clear();
}
void BuffComponent::Reset() {
RemoveAllBuffs();
}
void BuffComponent::ReApplyBuffs() {
for (const auto& [buffId, buffInfo] : m_Buffs) {
ApplyBuffEffect(buffId);
for (const auto& buff : m_Buffs) {
ApplyBuffEffect(buff.first);
}
}
Entity* BuffComponent::GetParent() const {
return m_Parent;
}
void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
// Load buffs
auto* dest = doc->FirstChildElement("obj")->FirstChildElement("dest");
@@ -219,7 +245,9 @@ void BuffComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
auto* buffElement = dest->FirstChildElement("buff");
// Old character, no buffs to load
if (buffElement) return;
if (buffElement == nullptr) {
return;
}
auto* buffEntry = buffElement->FirstChildElement("b");
@@ -277,9 +305,12 @@ void BuffComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
const std::vector<BuffParameter>& BuffComponent::GetBuffParameters(int32_t buffId) {
const auto& pair = m_Cache.find(buffId);
if (pair != m_Cache.end()) return pair->second;
if (pair != m_Cache.end()) {
return pair->second;
}
auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM BuffParameters WHERE BuffID = ?;");
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT * FROM BuffParameters WHERE BuffID = ?;");
query.bind(1, (int)buffId);
auto result = query.execQuery();
@@ -294,15 +325,17 @@ const std::vector<BuffParameter>& BuffComponent::GetBuffParameters(int32_t buffI
param.value = result.getFloatField(2);
if (!result.fieldIsNull(3)) {
const auto parameterInfo = result.getStringField(3);
const auto values = GeneralUtils::SplitString(parameterInfo, ',');
if (values.size() >= 4) {
GeneralUtils::TryParse(values.at(0), param.values.skillId);
GeneralUtils::TryParse(values.at(1), param.values.stacks);
GeneralUtils::TryParse(values.at(2), param.values.tick);
GeneralUtils::TryParse(values.at(3), param.values.unknown2);
} else {
Game::logger->Log("BuffComponent", "Failed to parse %s into parameter struct. Too few parameters to split on.", parameterInfo);
std::istringstream stream(result.getStringField(3));
std::string token;
while (std::getline(stream, token, ',')) {
try {
const auto value = std::stof(token);
param.values.push_back(value);
} catch (std::invalid_argument& exception) {
Game::logger->Log("BuffComponent", "Failed to parse value (%s): (%s)!", token.c_str(), exception.what());
}
}
}

View File

@@ -14,24 +14,20 @@ class Entity;
/**
* Extra information on effects to apply after applying a buff, for example whether to buff armor, imag or health and by how much
*/
struct BuffParameter {
struct ParameterValues {
int32_t skillId = 0;
int32_t stacks = 0;
float tick = 0.0f;
int32_t unknown2 = 0;
};
int32_t buffId = 0;
struct BuffParameter
{
int32_t buffId;
std::string name;
float value = 0.0f;
ParameterValues values;
int32_t effectId = 0;
float value;
std::vector<float> values;
int32_t effectId;
};
/**
* Meta information about a buff that can be applied, e.g. how long it's applied, who applied it, etc.
*/
struct Buff {
struct Buff
{
int32_t id = 0;
float time = 0;
float tick = 0;
@@ -44,11 +40,15 @@ struct Buff {
/**
* Allows for the application of buffs to the parent entity, altering health, armor and imagination.
*/
class BuffComponent final : public Component {
class BuffComponent : public Component {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::BUFF;
static const eReplicaComponentType ComponentType = eReplicaComponentType::BUFF;
explicit BuffComponent(Entity* parent) : Component(parent) {};
explicit BuffComponent(Entity* parent);
~BuffComponent();
Entity* GetParent() const;
void LoadFromXml(tinyxml2::XMLDocument* doc) override;
@@ -88,7 +88,7 @@ public:
* @param id the id of the buff to find
* @return whether or not the entity has a buff with the specified id active
*/
bool HasBuff(int32_t id) { return m_Buffs.find(id) != m_Buffs.end(); };
bool HasBuff(int32_t id);
/**
* Applies the effects of the buffs on the entity, e.g.: changing armor, health, imag, etc.
@@ -110,7 +110,7 @@ public:
/**
* Removes all buffs for the entity and reverses all of their effects
*/
void Reset() { RemoveAllBuffs(); };
void Reset();
/**
* Applies all effects for all buffs, active or not, again

View File

@@ -9,26 +9,41 @@
#include "Item.h"
#include "PropertyManagementComponent.h"
void BuildBorderComponent::OnUse(Entity* originator) {
if (!originator->GetCharacter()) return;
BuildBorderComponent::BuildBorderComponent(Entity* parent) : Component(parent) {
}
BuildBorderComponent::~BuildBorderComponent() {
}
void BuildBorderComponent::OnUse(Entity* originator) {
if (originator->GetCharacter()) {
const auto& entities = EntityManager::Instance()->GetEntitiesInGroup("PropertyPlaque");
auto buildArea = entities.empty() ? m_ParentEntity->GetObjectID() : entities.front()->GetObjectID();
auto buildArea = m_Parent->GetObjectID();
if (!entities.empty()) {
buildArea = entities[0]->GetObjectID();
Game::logger->Log("BuildBorderComponent", "Using PropertyPlaque");
}
auto* inventoryComponent = originator->GetComponent<InventoryComponent>();
if (!inventoryComponent) return;
if (inventoryComponent == nullptr) {
return;
}
auto* thinkingHat = inventoryComponent->FindItemByLot(LOT_THINKING_CAP);
auto* thinkingHat = inventoryComponent->FindItemByLot(6086);
if (!thinkingHat) return;
Game::logger->Log("BuildBorderComponent", "Using BuildArea %llu for player %llu", buildArea, originator->GetObjectID());
if (thinkingHat == nullptr) {
return;
}
inventoryComponent->PushEquippedItems();
if (PropertyManagementComponent::Instance()) {
Game::logger->Log("BuildBorderComponent", "Starting with %llu", buildArea);
if (PropertyManagementComponent::Instance() != nullptr) {
GameMessages::SendStartArrangingWithItem(
originator,
originator->GetSystemAddress(),
@@ -40,9 +55,16 @@ void BuildBorderComponent::OnUse(Entity* originator) {
thinkingHat->GetLot(),
4,
0,
-1
-1,
NiPoint3::ZERO,
0
);
} else {
GameMessages::SendStartArrangingWithItem(originator, originator->GetSystemAddress(), true, buildArea, originator->GetPosition());
}
InventoryComponent* inv = m_Parent->GetComponent<InventoryComponent>();
if (!inv) return;
inv->PushEquippedItems(); // technically this is supposed to happen automatically... but it doesnt? so just keep this here
}
}

View File

@@ -6,23 +6,27 @@
#ifndef BUILDBORDERCOMPONENT_H
#define BUILDBORDERCOMPONENT_H
#include "BitStream.h"
#include "Entity.h"
#include "Component.h"
#include "eReplicaComponentType.h"
/**
* Component for the build border, allowing the user to start building when interacting with it
*/
class BuildBorderComponent final : public Component {
class BuildBorderComponent : public Component {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::BUILD_BORDER;
static const eReplicaComponentType ComponentType = eReplicaComponentType::BUILD_BORDER;
BuildBorderComponent(Entity* parent) : Component(parent) { };
BuildBorderComponent(Entity* parent);
~BuildBorderComponent() override;
/**
* Causes the originator to start build with this entity as a reference point
* @param originator the entity (probably a player) that triggered the event
*/
void OnUse(Entity* originator) override;
private:
};
#endif // BUILDBORDERCOMPONENT_H

View File

@@ -1,49 +1,37 @@
set(DGAME_DCOMPONENTS_SOURCES "AchievementVendorComponent.cpp"
"ActivityComponent.cpp"
"BaseCombatAIComponent.cpp"
set(DGAME_DCOMPONENTS_SOURCES "BaseCombatAIComponent.cpp"
"BouncerComponent.cpp"
"BuffComponent.cpp"
"BuildBorderComponent.cpp"
"CharacterComponent.cpp"
"CollectibleComponent.cpp"
"Component.cpp"
"ControllablePhysicsComponent.cpp"
"DestroyableComponent.cpp"
"DonationVendorComponent.cpp"
"GateRushComponent.cpp"
"InventoryComponent.cpp"
"ItemComponent.cpp"
"LevelProgressionComponent.cpp"
"LUPExhibitComponent.cpp"
"MinigameControlComponent.cpp"
"MissionComponent.cpp"
"MissionOfferComponent.cpp"
"ModelBehaviorComponent.cpp"
"ModelComponent.cpp"
"ModuleAssemblyComponent.cpp"
"MovementAIComponent.cpp"
"MovingPlatformComponent.cpp"
"MutableModelBehaviorComponent.cpp"
"PetComponent.cpp"
"PhantomPhysicsComponent.cpp"
"PlayerForcedMovementComponent.cpp"
"PossessableComponent.cpp"
"PossessionComponent.cpp"
"PossessorComponent.cpp"
"PropertyComponent.cpp"
"PropertyEntranceComponent.cpp"
"PropertyManagementComponent.cpp"
"PropertyVendorComponent.cpp"
"ProximityMonitorComponent.cpp"
"RacingComponent.cpp"
"RacingControlComponent.cpp"
"RacingSoundTriggerComponent.cpp"
"RacingStatsComponent.cpp"
"RailActivatorComponent.cpp"
"QuickBuildComponent.cpp"
"RebuildComponent.cpp"
"RenderComponent.cpp"
"RigidbodyPhantomPhysicsComponent.cpp"
"MultiZoneEntranceComponent.cpp"
"RocketLaunchLupComponent.cpp"
"RocketLaunchpadControlComponent.cpp"
"ScriptComponent.cpp"
"ScriptedActivityComponent.cpp"
"ShootingGalleryComponent.cpp"
"SimplePhysicsComponent.cpp"
@@ -51,5 +39,5 @@ set(DGAME_DCOMPONENTS_SOURCES "AchievementVendorComponent.cpp"
"SoundTriggerComponent.cpp"
"SwitchComponent.cpp"
"TriggerComponent.cpp"
"HavokVehiclePhysicsComponent.cpp"
"VehiclePhysicsComponent.cpp"
"VendorComponent.cpp" PARENT_SCOPE)

View File

@@ -10,9 +10,10 @@
#include "InventoryComponent.h"
#include "ControllablePhysicsComponent.h"
#include "EntityManager.h"
#include "VehiclePhysicsComponent.h"
#include "GameMessages.h"
#include "Item.h"
#include "Amf3.h"
#include "AMFFormat.h"
#include "eGameMasterLevel.h"
#include "eGameActivity.h"
@@ -66,12 +67,16 @@ bool CharacterComponent::LandingAnimDisabled(int zoneID) {
return false;
}
CharacterComponent::~CharacterComponent() {
}
void CharacterComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
if (bIsInitialUpdate) {
outBitStream->Write0(); // Claim codes. Dont't belive these have an effect.
outBitStream->Write0(); // Claim codes. Dont't belive these have an effect.
outBitStream->Write0(); // Claim codes. Dont't belive these have an effect.
outBitStream->Write0(); // Claim codes. Dont't belive these have an effect.
outBitStream->Write0();
outBitStream->Write0();
outBitStream->Write0();
outBitStream->Write0();
outBitStream->Write(m_Character->GetHairColor());
outBitStream->Write(m_Character->GetHairStyle());
@@ -118,8 +123,6 @@ void CharacterComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInit
outBitStream->Write(m_RacesFinished);
outBitStream->Write(m_FirstPlaceRaceFinishes);
// This is a really weird bit of serialization. This is serialized as a two bit integer, but the first bit written is always zero.
// If the 2 bit integer is exactly 1, we write the rocket configuration.
outBitStream->Write0();
outBitStream->Write(m_IsLanding);
if (m_IsLanding) {
@@ -130,24 +133,20 @@ void CharacterComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInit
}
}
outBitStream->Write(bIsInitialUpdate || m_DirtyGMInfo);
if (bIsInitialUpdate || m_DirtyGMInfo) {
outBitStream->Write(m_DirtyGMInfo);
if (m_DirtyGMInfo) {
outBitStream->Write(m_PvpEnabled);
outBitStream->Write(m_IsGM);
outBitStream->Write(m_GMLevel);
outBitStream->Write(m_EditorEnabled);
outBitStream->Write(m_EditorLevel);
if (!bIsInitialUpdate) m_DirtyGMInfo = false;
}
outBitStream->Write(bIsInitialUpdate || m_DirtyCurrentActivity);
if (bIsInitialUpdate || m_DirtyCurrentActivity) {
outBitStream->Write(m_CurrentActivity);
if (!bIsInitialUpdate) m_DirtyCurrentActivity = false;
}
outBitStream->Write(m_DirtyCurrentActivity);
if (m_DirtyCurrentActivity) outBitStream->Write(m_CurrentActivity);
outBitStream->Write(bIsInitialUpdate || m_DirtySocialInfo);
if (bIsInitialUpdate || m_DirtySocialInfo) {
outBitStream->Write(m_DirtySocialInfo);
if (m_DirtySocialInfo) {
outBitStream->Write(m_GuildID);
outBitStream->Write<unsigned char>(static_cast<unsigned char>(m_GuildName.size()));
if (!m_GuildName.empty())
@@ -155,7 +154,6 @@ void CharacterComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInit
outBitStream->Write(m_IsLEGOClubMember);
outBitStream->Write(m_CountryCode);
if (!bIsInitialUpdate) m_DirtySocialInfo = false;
}
}
@@ -164,21 +162,21 @@ bool CharacterComponent::GetPvpEnabled() const {
}
void CharacterComponent::SetPvpEnabled(const bool value) {
if (m_PvpEnabled == value) return;
m_DirtyGMInfo = true;
m_PvpEnabled = value;
}
void CharacterComponent::SetGMLevel(eGameMasterLevel gmlevel) {
if (m_GMLevel == gmlevel) return;
m_DirtyGMInfo = true;
m_IsGM = gmlevel > eGameMasterLevel::CIVILIAN;
if (gmlevel > eGameMasterLevel::CIVILIAN) m_IsGM = true;
else m_IsGM = false;
m_GMLevel = gmlevel;
}
void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
auto* character = doc->FirstChildElement("obj")->FirstChildElement("char");
tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char");
if (!character) {
Game::logger->Log("CharacterComponent", "Failed to find char tag while loading XML!");
return;
@@ -198,7 +196,7 @@ void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
}
// Load the zone statistics
m_ZoneStatistics.clear();
m_ZoneStatistics = {};
auto zoneStatistics = character->FirstChildElement("zs");
if (zoneStatistics) {
@@ -221,16 +219,20 @@ void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
}
}
const auto* rocketConfig = character->FindAttribute("lcbp");
const tinyxml2::XMLAttribute* rocketConfig = character->FindAttribute("lcbp");
m_LastRocketConfig = rocketConfig ? GeneralUtils::ASCIIToUTF16(rocketConfig->Value()) : u"";
if (rocketConfig) {
m_LastRocketConfig = GeneralUtils::ASCIIToUTF16(rocketConfig->Value());
} else {
m_LastRocketConfig = u"";
}
//
// Begin custom attributes
//
// Load the last rocket item ID
const auto* lastRocketItemID = character->FindAttribute("lrid");
const tinyxml2::XMLAttribute* lastRocketItemID = character->FindAttribute("lrid");
if (lastRocketItemID) {
m_LastRocketItemID = lastRocketItemID->Int64Value();
}
@@ -243,11 +245,11 @@ void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
m_IsGM = true;
m_DirtyGMInfo = true;
m_EditorLevel = m_GMLevel;
m_EditorEnabled = false; // We're not currently in HF if we're loading in
m_EditorEnabled = false; //We're not currently in HF if we're loading in
}
//Annoying guild bs:
const auto* guildName = character->FindAttribute("gn");
const tinyxml2::XMLAttribute* guildName = character->FindAttribute("gn");
if (guildName) {
const char* gn = guildName->Value();
int64_t gid = 0;
@@ -268,8 +270,10 @@ void CharacterComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
if (!m_Character) return;
// If the loaded zoneID is different from the current zoneID, we are landing
m_IsLanding = m_Character->GetZoneID() != Game::server->GetZoneID();
//Check to see if we're landing:
if (m_Character->GetZoneID() != Game::server->GetZoneID()) {
m_IsLanding = true;
}
if (LandingAnimDisabled(m_Character->GetZoneID()) || LandingAnimDisabled(Game::server->GetZoneID()) || m_LastRocketConfig.empty()) {
m_IsLanding = false; //Don't make us land on VE/minigames lol
@@ -297,7 +301,7 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
// done with minifig
auto* character = doc->FirstChildElement("obj")->FirstChildElement("char");
tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char");
if (!character) {
Game::logger->Log("CharacterComponent", "Failed to find char tag while updating XML!");
return;
@@ -313,15 +317,15 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
if (!zoneStatistics) zoneStatistics = doc->NewElement("zs");
zoneStatistics->DeleteChildren();
for (const auto&[mapId, zoneStatisticToSave] : m_ZoneStatistics) {
for (auto pair : m_ZoneStatistics) {
auto zoneStatistic = doc->NewElement("s");
zoneStatistic->SetAttribute("map", mapId);
zoneStatistic->SetAttribute("ac", zoneStatisticToSave.m_AchievementsCollected);
zoneStatistic->SetAttribute("bc", zoneStatisticToSave.m_BricksCollected);
zoneStatistic->SetAttribute("cc", zoneStatisticToSave.m_CoinsCollected);
zoneStatistic->SetAttribute("es", zoneStatisticToSave.m_EnemiesSmashed);
zoneStatistic->SetAttribute("qbc", zoneStatisticToSave.m_QuickBuildsCompleted);
zoneStatistic->SetAttribute("map", pair.first);
zoneStatistic->SetAttribute("ac", pair.second.m_AchievementsCollected);
zoneStatistic->SetAttribute("bc", pair.second.m_BricksCollected);
zoneStatistic->SetAttribute("cc", pair.second.m_CoinsCollected);
zoneStatistic->SetAttribute("es", pair.second.m_EnemiesSmashed);
zoneStatistic->SetAttribute("qbc", pair.second.m_QuickBuildsCompleted);
zoneStatistics->LinkEndChild(zoneStatistic);
}
@@ -347,7 +351,7 @@ void CharacterComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
//
auto newUpdateTimestamp = std::time(nullptr);
Game::logger->Log("TotalTimePlayed", "Time since %i last saved: %d", m_Character->GetID(), newUpdateTimestamp - m_LastUpdateTimestamp);
Game::logger->Log("TotalTimePlayed", "Time since last save: %d", newUpdateTimestamp - m_LastUpdateTimestamp);
m_TotalTimePlayed += newUpdateTimestamp - m_LastUpdateTimestamp;
character->SetAttribute("time", m_TotalTimePlayed);
@@ -388,7 +392,7 @@ Item* CharacterComponent::RocketEquip(Entity* player) {
if (!rocket) return rocket;
// build and define the rocket config
for (auto* data : rocket->GetConfig()) {
for (LDFBaseData* data : rocket->GetConfig()) {
if (data->GetKey() == u"assemblyPartLOTs") {
std::string newRocketStr = data->GetValueAsString() + ";";
GeneralUtils::ReplaceInString(newRocketStr, "+", ";");
@@ -468,7 +472,9 @@ void CharacterComponent::TrackImaginationDelta(int32_t imagination) {
}
void CharacterComponent::TrackArmorDelta(int32_t armor) {
if (armor > 0) UpdatePlayerStatistic(TotalArmorRepaired, armor);
if (armor > 0) {
UpdatePlayerStatistic(TotalArmorRepaired, armor);
}
}
void CharacterComponent::TrackRebuildComplete() {
@@ -480,16 +486,17 @@ void CharacterComponent::TrackRebuildComplete() {
void CharacterComponent::TrackRaceCompleted(bool won) {
m_RacesFinished++;
if (won) m_FirstPlaceRaceFinishes++;
if (won)
m_FirstPlaceRaceFinishes++;
}
void CharacterComponent::TrackPositionUpdate(const NiPoint3& newPosition) {
const auto distance = NiPoint3::Distance(newPosition, m_ParentEntity->GetPosition());
const auto distance = NiPoint3::Distance(newPosition, m_Parent->GetPosition());
if (m_IsRacing) {
UpdatePlayerStatistic(DistanceDriven, static_cast<uint64_t>(distance));
UpdatePlayerStatistic(DistanceDriven, (uint64_t)distance);
} else {
UpdatePlayerStatistic(MetersTraveled, static_cast<uint64_t>(distance));
UpdatePlayerStatistic(MetersTraveled, (uint64_t)distance);
}
}
@@ -660,45 +667,45 @@ void CharacterComponent::InitializeEmptyStatistics() {
std::string CharacterComponent::StatisticsToString() const {
std::stringstream result;
result
<< m_CurrencyCollected << ';'
<< m_BricksCollected << ';'
<< m_SmashablesSmashed << ';'
<< m_QuickBuildsCompleted << ';'
<< m_EnemiesSmashed << ';'
<< m_RocketsUsed << ';'
<< m_MissionsCompleted << ';'
<< m_PetsTamed << ';'
<< m_ImaginationPowerUpsCollected << ';'
<< m_LifePowerUpsCollected << ';'
<< m_ArmorPowerUpsCollected << ';'
<< m_MetersTraveled << ';'
<< m_TimesSmashed << ';'
<< m_TotalDamageTaken << ';'
<< m_TotalDamageHealed << ';'
<< m_TotalArmorRepaired << ';'
<< m_TotalImaginationRestored << ';'
<< m_TotalImaginationUsed << ';'
<< m_DistanceDriven << ';'
<< m_TimeAirborneInCar << ';'
<< m_RacingImaginationPowerUpsCollected << ';'
<< m_RacingImaginationCratesSmashed << ';'
<< m_RacingCarBoostsActivated << ';'
<< m_RacingTimesWrecked << ';'
<< m_RacingSmashablesSmashed << ';'
<< m_RacesFinished << ';'
<< m_FirstPlaceRaceFinishes << ';';
result << std::to_string(m_CurrencyCollected) << ';'
<< std::to_string(m_BricksCollected) << ';'
<< std::to_string(m_SmashablesSmashed) << ';'
<< std::to_string(m_QuickBuildsCompleted) << ';'
<< std::to_string(m_EnemiesSmashed) << ';'
<< std::to_string(m_RocketsUsed) << ';'
<< std::to_string(m_MissionsCompleted) << ';'
<< std::to_string(m_PetsTamed) << ';'
<< std::to_string(m_ImaginationPowerUpsCollected) << ';'
<< std::to_string(m_LifePowerUpsCollected) << ';'
<< std::to_string(m_ArmorPowerUpsCollected) << ';'
<< std::to_string(m_MetersTraveled) << ';'
<< std::to_string(m_TimesSmashed) << ';'
<< std::to_string(m_TotalDamageTaken) << ';'
<< std::to_string(m_TotalDamageHealed) << ';'
<< std::to_string(m_TotalArmorRepaired) << ';'
<< std::to_string(m_TotalImaginationRestored) << ';'
<< std::to_string(m_TotalImaginationUsed) << ';'
<< std::to_string(m_DistanceDriven) << ';'
<< std::to_string(m_TimeAirborneInCar) << ';'
<< std::to_string(m_RacingImaginationPowerUpsCollected) << ';'
<< std::to_string(m_RacingImaginationCratesSmashed) << ';'
<< std::to_string(m_RacingCarBoostsActivated) << ';'
<< std::to_string(m_RacingTimesWrecked) << ';'
<< std::to_string(m_RacingSmashablesSmashed) << ';'
<< std::to_string(m_RacesFinished) << ';'
<< std::to_string(m_FirstPlaceRaceFinishes) << ';';
return result.str();
}
uint64_t CharacterComponent::GetStatisticFromSplit(std::vector<std::string> split, uint32_t index) {
return index < split.size() ? std::stoul(split.at(index)) : 0;
return split.size() > index ? std::stoul(split.at(index)) : 0;
}
ZoneStatistics& CharacterComponent::GetZoneStatisticsForMap(LWOMAPID mapID) {
auto stats = m_ZoneStatistics.find(mapID);
if (stats == m_ZoneStatistics.end()) m_ZoneStatistics.insert({ mapID, {0, 0, 0, 0, 0 } });
if (stats == m_ZoneStatistics.end())
m_ZoneStatistics.insert({ mapID, {0, 0, 0, 0, 0 } });
return m_ZoneStatistics.at(mapID);
}
@@ -725,8 +732,8 @@ void CharacterComponent::RemoveVentureVisionEffect(std::string ventureVisionType
}
void CharacterComponent::UpdateClientMinimap(bool showFaction, std::string ventureVisionType) const {
if (!m_ParentEntity) return;
if (!m_Parent) return;
AMFArrayValue arrayToSend;
arrayToSend.Insert(ventureVisionType, showFaction);
GameMessages::SendUIMessageServerToSingleClient(m_ParentEntity, m_ParentEntity ? m_ParentEntity->GetSystemAddress() : UNASSIGNED_SYSTEM_ADDRESS, "SetFactionVisibility", arrayToSend);
arrayToSend.InsertValue(ventureVisionType, showFaction ? static_cast<AMFValue*>(new AMFTrueValue()) : static_cast<AMFValue*>(new AMFFalseValue()));
GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent ? m_Parent->GetSystemAddress() : UNASSIGNED_SYSTEM_ADDRESS, "SetFactionVisibility", &arrayToSend);
}

View File

@@ -60,11 +60,12 @@ enum StatisticID {
/**
* Represents a character, including their rockets and stats
*/
class CharacterComponent final : public Component {
class CharacterComponent : public Component {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::CHARACTER;
static const eReplicaComponentType ComponentType = eReplicaComponentType::CHARACTER;
CharacterComponent(Entity* parent, Character* character);
~CharacterComponent() override;
void LoadFromXml(tinyxml2::XMLDocument* doc) override;
void UpdateXml(tinyxml2::XMLDocument* doc) override;

View File

@@ -1,7 +0,0 @@
#include "CollectibleComponent.h"
#include "Entity.h"
void CollectibleComponent::Startup() {
m_CollectibleId = GetParentEntity()->GetVarAs<int32_t>(u"collectible_id");
}

View File

@@ -1,22 +0,0 @@
#ifndef __COLLECTIBLECOMPONENT__H__
#define __COLLECTIBLECOMPONENT__H__
#include "Component.h"
#include "eReplicaComponentType.h"
#include <cstdint>
class CollectibleComponent : public Component {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::COLLECTIBLE;
CollectibleComponent(Entity* parent) : Component(parent) { };
void Startup() override;
uint32_t GetCollectibleId() const { return m_CollectibleId; }
private:
uint32_t m_CollectibleId;
};
#endif //!__COLLECTIBLECOMPONENT__H__

View File

@@ -1,7 +1,30 @@
#include "Component.h"
#include "DluAssert.h"
Component::Component(Entity* owningEntity) {
DluAssert(owningEntity != nullptr);
m_ParentEntity = owningEntity;
Component::Component(Entity* parent) {
m_Parent = parent;
}
Component::~Component() {
}
Entity* Component::GetParent() const {
return m_Parent;
}
void Component::Update(float deltaTime) {
}
void Component::OnUse(Entity* originator) {
}
void Component::UpdateXml(tinyxml2::XMLDocument* doc) {
}
void Component::LoadFromXml(tinyxml2::XMLDocument* doc) {
}

View File

@@ -1,74 +1,52 @@
#pragma once
#include "tinyxml2.h"
#include "../thirdparty/tinyxml2/tinyxml2.h"
class Entity;
namespace RakNet {
class BitStream;
}
/**
* Component base class, provides methods for game loop updates, usage events and loading and saving to XML.
*/
class Component {
class Component
{
public:
Component(Entity* owningEntity);
virtual ~Component() {};
Component(Entity* parent);
virtual ~Component();
/**
* Gets the owner of this component
* @return the owner of this component
*/
Entity* GetParentEntity() const { return m_ParentEntity; };
/**
* Event called when this component is being used, e.g. when some entity interacted with it
* @param originator
*/
virtual void OnUse(Entity* originator) {};
/**
* Save data from this componennt to character XML
* @param doc the document to write data to
*/
virtual void UpdateXml(tinyxml2::XMLDocument* doc) {};
/**
* Load base data for this component from character XML
* @param doc the document to read data from
*/
virtual void LoadFromXml(tinyxml2::XMLDocument* doc) {};
/**
* Call after you have newed the component to initialize it
*/
virtual void Startup() {};
Entity* GetParent() const;
/**
* Updates the component in the game loop
* @param deltaTime time passed since last update
*/
virtual void Update(float deltaTime) {};
virtual void Update(float deltaTime);
/**
* Loads the data of this component from the luz/lvl configuration
* Event called when this component is being used, e.g. when some entity interacted with it
* @param originator
*/
virtual void LoadConfigData() {};
virtual void OnUse(Entity* originator);
/**
* Loads the data of this component from the cdclient database
* Save data from this componennt to character XML
* @param doc the document to write data to
*/
virtual void LoadTemplateData() {};
virtual void UpdateXml(tinyxml2::XMLDocument* doc);
/**
* Serializes the component for delivery to the client(s)
* Load base data for this component from character XML
* @param doc the document to read data from
*/
virtual void Serialize(RakNet::BitStream* bitStream, bool isConstruction = false) {};
virtual void LoadFromXml(tinyxml2::XMLDocument* doc);
protected:
/**
* The entity that owns this component
*/
Entity* m_ParentEntity;
Entity* m_Parent;
};

View File

@@ -1,153 +0,0 @@
Legend
```
├── Explicit inheritance
├~~ Loaded with Parent
├-> SubComponent
├-? idk lol, but related
originalName -> newName
```
```tree
LWOActivityComponent
├── LWOQuickBuildComponent
├── LWOMiniGameControlComponent
├── LWOShootingGalleryComponent
├── LWOScriptedActivityComponent
| └── LWOBaseRacingControlComponent -> RacingControlComponent
| ├── LWORacingControlComponent -> RacingComponent
| └── LWOGateRushControlComponent -> GateRushComponent
LWOBaseCombatAIComponent
├~~ LWOPathfindingControlComponent
├~~ LWOProximityMonitorComponent
LWOProjectilePhysComponent
LWOBasePhysComponent
├── LWORigidBodyPhantomComponent
├── LWOPhantomPhysComponent
├── LWOVehiclePhysicsComponent
├── LWOSimplePhysComponent
| ├~~ LWOPhantomPhysicsComponent
├── LWOPhysicsSystemComponent
├── LWOHavokVehiclePhysicsComponent
├── LWOControllablePhysComponent
LWOBaseRenderComponent
├── LWOSkinnedRenderComponent
├~~ LWOSkinnedRenderComponent
├~~ LWOFXComponent
LWOBaseVendorComponent
├── LWOVendorComponent
| ├~~ LWOProximityMonitorComponent
├── LWODonationVendorComponent
| ├~~ LWOProximityMonitorComponent
├── LWOAchievementVendorComponent
| ├~~ LWOProximityMonitorComponent
LWOBBBComponent_Common
├── LWOBBBComponent_Client
LWOBlueprintComponent
LWOBouncerComponent
LWOBuildControllerComponentCommon
├── LWOBuildControllerComponent
LWOChangelingBuildComponent
LWOCharacterComponent
├── LWOMinifigComponent
├~~ LWOPossessionControlComponent
├~~ LWOMountControlComponent
├~~ LWOPetCreatorComponent
├~~ LWOLevelProgressionComponent
├~~ LWOPlayerForcedMovementComponent
| ├~? LWOPathfindingControlComponent
LWOChestComponent
LWOChoiceBuildComponent
LWOCollectibleComponent
LWOCustomBuildAssemblyComponent
LWODestroyableComponent
├~~ LWOBuffComponent
├~~ LWOStatusEffectComponent
LWODropEffectComponent
LWODroppedLootComponent
LWOExhibitComponent
LWOGenericActivatorComponent
LWOGhostComponent
LWOHFLightDirectionGadgetComponent
LWOInventoryComponent_Common
├── LWOInventoryComponent_Client
| └── LWOInventoryComponent_EquippedItem
LWOItemComponent
LWOLUPExhibitComponent
LWOMissionOfferComponent
LWOModelBehaviorComponent
├~~ LWOSimplePhysComponent
├~~ LWOControllablePhysComponent
├~~ LWOPathfindingControlComponent
├~~ LWOMutableModelBehaviorComponent
LWOModelBuilderComponent
LWOModularBuildComponentCommon
├── LWOModularBuildComponent
LWOModuleAssemblyComponent
├── LWOModuleAssemblyComponentCommon
LWOModuleComponentCommon
├── LWOModuleComponent
LWOMovementAIComponent
LWOMultiZoneEntranceComponent
LWOOverheadIconComponent
LWOPetComponent
├~~ LWOPathfindingControlComponent
├~? LWOItemComponent
├~? LWOModelBehaviorComponent
| ├~~ ...
LWOPlatformBoundaryComponent
LWOPlatformComponent
├-> LWOMoverPlatformSubComponent
├-> LWOSimpleMoverPlatformSubComponent
├-> LWORotaterPlatformSubComponent
LWOPropertyComponent
LWOPropertyEntranceComponent
LWOPropertyManagementComponent
LWOPropertyVendorComponent
LWOProximityMonitorComponent
LWOSoundTriggerComponent
├── LWORacingSoundTriggerComponent
LWORacingStatsComponentCommon
├── LWORacingStatsComponent
LWORocketAnimationControlComponentCommon
├── LWORocketAnimationControlComponent
LWORocketLaunchpadControlComponentCommon
├── LWORocketLaunchpadControlComponent
LWOScriptComponent
├~~ LWOPathfindingControlComponent
├~~ LWOProximityMonitorComponent
LWOShowcaseModelHandlerComponent
LWOSkillComponent
LWOSoundAmbient2DComponent
LWOSoundAmbient3DComponent
LWOSoundRepeaterComponent
LWOSpawnComponent
LWOSpringpadComponent
LWOSwitchComponent
LWOTriggerComponent
LocalPlayer - This is a function call in the client which, if the generated Entity is a player, the below components are added. This **must** be done for all players.
├~~ LWOInteractionManagerComponent
├~~ LWOUserControlComponent
├~~ LWOFriendsListComponent
├~~ LWOIgnoreListComponent
├~~ LWOTextEffectComponent
├~~ LWOChatBubbleComponent
├~~ LWOGuildComponent
├~~ LWOPlayerPetTamingComponent
├~~ LWOLocalSystemsComponent
├~~ LWOSlashCommandComponent
├~~ LWOMissionComponent
├~~ LWOPropertyEditorComponent
├~~ LWOComponent115
├~~ LWOTeamsComponent
├~~ LWOChatComponent
├~~ LWOPetControlComponent
├~~ LWOTradeComponent
├~~ LWOPreconditionComponent
├~~ LWOFlagComponent
├~~ LWOFactionTriggerComponent
```

View File

@@ -3,7 +3,6 @@
#include "BitStream.h"
#include "dLogger.h"
#include "Game.h"
#include "dServer.h"
#include "dpWorld.h"
#include "dpEntity.h"
@@ -52,30 +51,34 @@ ControllablePhysicsComponent::ControllablePhysicsComponent(Entity* entity) : Com
m_ImmuneToStunTurnCount = 0;
m_ImmuneToStunUseItemCount = 0;
// Other physics entities we care about will be added by BaseCombatAI
if (entity->GetLOT() != 1) return;
if (entity->GetLOT() != 1) // Other physics entities we care about will be added by BaseCombatAI
return;
if (entity->GetLOT() == 1) {
Game::logger->Log("ControllablePhysicsComponent", "Using patch to load minifig physics");
float radius = 1.5f;
m_dpEntity = new dpEntity(m_ParentEntity->GetObjectID(), radius, false);
m_dpEntity = new dpEntity(m_Parent->GetObjectID(), radius, false);
m_dpEntity->SetCollisionGroup(COLLISION_GROUP_DYNAMIC | COLLISION_GROUP_FRIENDLY);
dpWorld::Instance().AddEntity(m_dpEntity);
}
}
ControllablePhysicsComponent::~ControllablePhysicsComponent() {
if (!m_dpEntity) return;
if (m_dpEntity) {
dpWorld::Instance().RemoveEntity(m_dpEntity);
}
}
void ControllablePhysicsComponent::Startup() {
NiPoint3 pos = m_ParentEntity->GetDefaultPosition();
NiQuaternion rot = m_ParentEntity->GetDefaultRotation();
SetPosition(pos);
SetRotation(rot);
void ControllablePhysicsComponent::Update(float deltaTime) {
}
void ControllablePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
//If this is a creation, then we assume the position is dirty, even when it isn't.
//This is because new clients will still need to receive the position.
//if (bIsInitialUpdate) m_DirtyPosition = true;
if (bIsInitialUpdate) {
outBitStream->Write(m_InJetpackMode);
if (m_InJetpackMode) {
@@ -96,32 +99,33 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bo
if (m_IgnoreMultipliers) m_DirtyCheats = false;
outBitStream->Write(bIsInitialUpdate || m_DirtyCheats);
if (bIsInitialUpdate || m_DirtyCheats) {
outBitStream->Write(m_DirtyCheats);
if (m_DirtyCheats) {
outBitStream->Write(m_GravityScale);
outBitStream->Write(m_SpeedMultiplier);
if (!bIsInitialUpdate) m_DirtyCheats = false;
m_DirtyCheats = false;
}
outBitStream->Write(bIsInitialUpdate || m_DirtyEquippedItemInfo);
if (bIsInitialUpdate || m_DirtyEquippedItemInfo) {
outBitStream->Write(m_DirtyEquippedItemInfo);
if (m_DirtyEquippedItemInfo) {
outBitStream->Write(m_PickupRadius);
outBitStream->Write(m_InJetpackMode);
if (!bIsInitialUpdate) m_DirtyEquippedItemInfo = false;
m_DirtyEquippedItemInfo = false;
}
outBitStream->Write(bIsInitialUpdate || m_DirtyBubble);
if (bIsInitialUpdate || m_DirtyBubble) {
outBitStream->Write(m_DirtyBubble);
if (m_DirtyBubble) {
outBitStream->Write(m_IsInBubble);
if (m_IsInBubble) {
outBitStream->Write(m_BubbleType);
outBitStream->Write(m_SpecialAnims);
}
if (!bIsInitialUpdate) m_DirtyBubble = false;
m_DirtyBubble = false;
}
outBitStream->Write(bIsInitialUpdate || m_DirtyPosition);
if (bIsInitialUpdate || m_DirtyPosition) {
outBitStream->Write(m_DirtyPosition || bIsInitialUpdate);
if (m_DirtyPosition || bIsInitialUpdate) {
outBitStream->Write(m_Position.x);
outBitStream->Write(m_Position.y);
outBitStream->Write(m_Position.z);
@@ -134,22 +138,20 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bo
outBitStream->Write(m_IsOnGround);
outBitStream->Write(m_IsOnRail);
outBitStream->Write(bIsInitialUpdate || m_DirtyVelocity);
if (bIsInitialUpdate || m_DirtyVelocity) {
outBitStream->Write(m_DirtyVelocity);
if (m_DirtyVelocity) {
outBitStream->Write(m_Velocity.x);
outBitStream->Write(m_Velocity.y);
outBitStream->Write(m_Velocity.z);
if (!bIsInitialUpdate) m_DirtyVelocity = false;
}
outBitStream->Write(bIsInitialUpdate || m_DirtyAngularVelocity);
if (bIsInitialUpdate || m_DirtyAngularVelocity) {
outBitStream->Write(m_DirtyAngularVelocity);
if (m_DirtyAngularVelocity) {
outBitStream->Write(m_AngularVelocity.x);
outBitStream->Write(m_AngularVelocity.y);
outBitStream->Write(m_AngularVelocity.z);
if (!bIsInitialUpdate) m_DirtyAngularVelocity = false;
}
if (!bIsInitialUpdate) m_DirtyPosition = false;
outBitStream->Write0();
}
@@ -160,45 +162,22 @@ void ControllablePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bo
}
void ControllablePhysicsComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
tinyxml2::XMLElement* characterElem = doc->FirstChildElement("obj")->FirstChildElement("char");
if (!characterElem) {
tinyxml2::XMLElement* character = doc->FirstChildElement("obj")->FirstChildElement("char");
if (!character) {
Game::logger->Log("ControllablePhysicsComponent", "Failed to find char tag!");
return;
}
m_ParentEntity->GetCharacter()->LoadXmlRespawnCheckpoints();
m_Parent->GetCharacter()->LoadXmlRespawnCheckpoints();
characterElem->QueryAttribute("lzx", &m_Position.x);
characterElem->QueryAttribute("lzy", &m_Position.y);
characterElem->QueryAttribute("lzz", &m_Position.z);
characterElem->QueryAttribute("lzrx", &m_Rotation.x);
characterElem->QueryAttribute("lzry", &m_Rotation.y);
characterElem->QueryAttribute("lzrz", &m_Rotation.z);
characterElem->QueryAttribute("lzrw", &m_Rotation.w);
character->QueryAttribute("lzx", &m_Position.x);
character->QueryAttribute("lzy", &m_Position.y);
character->QueryAttribute("lzz", &m_Position.z);
character->QueryAttribute("lzrx", &m_Rotation.x);
character->QueryAttribute("lzry", &m_Rotation.y);
character->QueryAttribute("lzrz", &m_Rotation.z);
character->QueryAttribute("lzrw", &m_Rotation.w);
auto* character = GetParentEntity()->GetCharacter();
const auto mapID = Game::server->GetZoneID();
//If we came from another zone, put us in the starting loc
if (character->GetZoneID() != Game::server->GetZoneID() || mapID == 1603) { // Exception for Moon Base as you tend to spawn on the roof.
const auto& targetSceneName = character->GetTargetScene();
auto* targetScene = EntityManager::Instance()->GetSpawnPointEntity(targetSceneName);
NiPoint3 pos;
NiQuaternion rot;
if (character->HasBeenToWorld(mapID) && targetSceneName.empty()) {
pos = character->GetRespawnPoint(mapID);
rot = dZoneManager::Instance()->GetZone()->GetSpawnRot();
} else if (targetScene) {
pos = targetScene->GetPosition();
rot = targetScene->GetRotation();
} else {
pos = dZoneManager::Instance()->GetZone()->GetSpawnPos();
rot = dZoneManager::Instance()->GetZone()->GetSpawnRot();
}
SetPosition(pos);
SetRotation(rot);
}
m_DirtyPosition = true;
}
@@ -229,7 +208,9 @@ void ControllablePhysicsComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
}
void ControllablePhysicsComponent::SetPosition(const NiPoint3& pos) {
if (m_Static || pos == m_Position) return;
if (m_Static) {
return;
}
m_Position.x = pos.x;
m_Position.y = pos.y;
@@ -240,7 +221,9 @@ void ControllablePhysicsComponent::SetPosition(const NiPoint3& pos) {
}
void ControllablePhysicsComponent::SetRotation(const NiQuaternion& rot) {
if (m_Static || rot == m_Rotation) return;
if (m_Static) {
return;
}
m_Rotation = rot;
m_DirtyPosition = true;
@@ -249,7 +232,9 @@ void ControllablePhysicsComponent::SetRotation(const NiQuaternion& rot) {
}
void ControllablePhysicsComponent::SetVelocity(const NiPoint3& vel) {
if (m_Static || m_Velocity == vel) return;
if (m_Static) {
return;
}
m_Velocity = vel;
m_DirtyPosition = true;
@@ -259,7 +244,9 @@ void ControllablePhysicsComponent::SetVelocity(const NiPoint3& vel) {
}
void ControllablePhysicsComponent::SetAngularVelocity(const NiPoint3& vel) {
if (m_Static || vel == m_AngularVelocity) return;
if (m_Static) {
return;
}
m_AngularVelocity = vel;
m_DirtyPosition = true;
@@ -267,15 +254,25 @@ void ControllablePhysicsComponent::SetAngularVelocity(const NiPoint3& vel) {
}
void ControllablePhysicsComponent::SetIsOnGround(bool val) {
if (m_IsOnGround == val) return;
m_IsOnGround = val;
m_DirtyPosition = true;
m_IsOnGround = val;
}
void ControllablePhysicsComponent::SetIsOnRail(bool val) {
if (m_IsOnRail == val) return;
m_IsOnRail = val;
m_DirtyPosition = true;
m_IsOnRail = val;
}
void ControllablePhysicsComponent::SetDirtyPosition(bool val) {
m_DirtyPosition = val;
}
void ControllablePhysicsComponent::SetDirtyVelocity(bool val) {
m_DirtyVelocity = val;
}
void ControllablePhysicsComponent::SetDirtyAngularVelocity(bool val) {
m_DirtyAngularVelocity = val;
}
void ControllablePhysicsComponent::AddPickupRadiusScale(float value) {
@@ -300,10 +297,10 @@ void ControllablePhysicsComponent::RemovePickupRadiusScale(float value) {
m_PickupRadius = 0.0f;
m_DirtyEquippedItemInfo = true;
for (uint32_t i = 0; i < m_ActivePickupRadiusScales.size(); i++) {
auto candidateRadius = m_ActivePickupRadiusScales.at(i);
auto candidateRadius = m_ActivePickupRadiusScales[i];
if (m_PickupRadius < candidateRadius) m_PickupRadius = candidateRadius;
}
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
void ControllablePhysicsComponent::AddSpeedboost(float value) {
@@ -322,33 +319,33 @@ void ControllablePhysicsComponent::RemoveSpeedboost(float value) {
}
// Recalculate speedboost since we removed one
m_SpeedBoost = 500.0f;
m_SpeedBoost = 0.0f;
if (m_ActiveSpeedBoosts.empty()) { // no active speed boosts left, so return to base speed
auto* levelProgressionComponent = m_ParentEntity->GetComponent<LevelProgressionComponent>();
auto* levelProgressionComponent = m_Parent->GetComponent<LevelProgressionComponent>();
if (levelProgressionComponent) m_SpeedBoost = levelProgressionComponent->GetSpeedBase();
} else { // Used the last applied speedboost
m_SpeedBoost = m_ActiveSpeedBoosts.back();
}
SetSpeedMultiplier(m_SpeedBoost / 500.0f); // 500 being the base speed
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
void ControllablePhysicsComponent::ActivateBubbleBuff(eBubbleType bubbleType, bool specialAnims) {
void ControllablePhysicsComponent::ActivateBubbleBuff(eBubbleType bubbleType, bool specialAnims){
if (m_IsInBubble) {
Game::logger->Log("ControllablePhysicsComponent", "%llu is already in bubble", m_ParentEntity->GetObjectID());
Game::logger->Log("ControllablePhysicsComponent", "Already in bubble");
return;
}
m_BubbleType = bubbleType;
m_IsInBubble = true;
m_DirtyBubble = true;
m_SpecialAnims = specialAnims;
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
void ControllablePhysicsComponent::DeactivateBubbleBuff() {
void ControllablePhysicsComponent::DeactivateBubbleBuff(){
m_DirtyBubble = true;
m_IsInBubble = false;
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
};
void ControllablePhysicsComponent::SetStunImmunity(
@@ -360,9 +357,9 @@ void ControllablePhysicsComponent::SetStunImmunity(
const bool bImmuneToStunJump,
const bool bImmuneToStunMove,
const bool bImmuneToStunTurn,
const bool bImmuneToStunUseItem) {
const bool bImmuneToStunUseItem){
if (state == eStateChangeType::POP) {
if (state == eStateChangeType::POP){
if (bImmuneToStunAttack && m_ImmuneToStunAttackCount > 0) m_ImmuneToStunAttackCount -= 1;
if (bImmuneToStunEquip && m_ImmuneToStunEquipCount > 0) m_ImmuneToStunEquipCount -= 1;
if (bImmuneToStunInteract && m_ImmuneToStunInteractCount > 0) m_ImmuneToStunInteractCount -= 1;
@@ -381,7 +378,7 @@ void ControllablePhysicsComponent::SetStunImmunity(
}
GameMessages::SendSetStunImmunity(
m_ParentEntity->GetObjectID(), state, m_ParentEntity->GetSystemAddress(), originator,
m_Parent->GetObjectID(), state, m_Parent->GetSystemAddress(), originator,
bImmuneToStunAttack,
bImmuneToStunEquip,
bImmuneToStunInteract,

View File

@@ -19,18 +19,18 @@ enum class eStateChangeType : uint32_t;
/**
* Handles the movement of controllable Entities, e.g. enemies and players
*/
class ControllablePhysicsComponent final : public Component {
class ControllablePhysicsComponent : public Component {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::CONTROLLABLE_PHYSICS;
static const eReplicaComponentType ComponentType = eReplicaComponentType::CONTROLLABLE_PHYSICS;
ControllablePhysicsComponent(Entity* entity);
~ControllablePhysicsComponent() override;
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
void LoadFromXml(tinyxml2::XMLDocument* doc) override;
void ResetFlags();
void UpdateXml(tinyxml2::XMLDocument* doc) override;
void Startup() override;
/**
* Sets the position of this entity, also ensures this update is serialized next tick.
@@ -115,19 +115,19 @@ public:
* Mark the position as dirty, forcing a serialization update next tick
* @param val whether or not the position is dirty
*/
void SetDirtyPosition(bool val) { m_DirtyPosition = val; }
void SetDirtyPosition(bool val);
/**
* Mark the velocity as dirty, forcing a serializtion update next tick
* @param val whether or not the velocity is dirty
*/
void SetDirtyVelocity(bool val) { m_DirtyVelocity = val; }
void SetDirtyVelocity(bool val);
/**
* Mark the angular velocity as dirty, forcing a serialization update next tick
* @param val whether or not the angular velocity is dirty
*/
void SetDirtyAngularVelocity(bool val) { m_DirtyAngularVelocity = val; }
void SetDirtyAngularVelocity(bool val);
/**
* Sets whether or not the entity is currently wearing a jetpack

View File

@@ -4,14 +4,14 @@
#include "Game.h"
#include "dConfig.h"
#include "Amf3.h"
#include "AmfSerialize.h"
#include "AMFFormat.h"
#include "AMFFormat_BitStream.h"
#include "GameMessages.h"
#include "User.h"
#include "CDClientManager.h"
#include "CDDestructibleComponentTable.h"
#include "EntityManager.h"
#include "QuickBuildComponent.h"
#include "RebuildComponent.h"
#include "CppScripts.h"
#include "Loot.h"
#include "Character.h"
@@ -28,7 +28,7 @@
#include "MissionComponent.h"
#include "CharacterComponent.h"
#include "PossessableComponent.h"
#include "PossessionComponent.h"
#include "PossessorComponent.h"
#include "InventoryComponent.h"
#include "dZoneManager.h"
#include "WorldConfig.h"
@@ -37,9 +37,8 @@
#include "eGameActivity.h"
#include "CDComponentsRegistryTable.h"
#include "CDCurrencyTableTable.h"
DestroyableComponent::DestroyableComponent(Entity* parent, int32_t componentId) : Component(parent) {
DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
m_iArmor = 0;
m_fMaxArmor = 0.0f;
m_iImagination = 0;
@@ -52,7 +51,7 @@ DestroyableComponent::DestroyableComponent(Entity* parent, int32_t componentId)
m_IsGMImmune = false;
m_IsShielded = false;
m_DamageToAbsorb = 0;
m_IsModuleAssembly = m_ParentEntity->HasComponent(eReplicaComponentType::MODULE_ASSEMBLY);
m_HasBricks = false;
m_DirtyThreatList = false;
m_HasThreats = false;
m_ExplodeFactor = 1.0f;
@@ -63,7 +62,6 @@ DestroyableComponent::DestroyableComponent(Entity* parent, int32_t componentId)
m_MinCoins = 0;
m_MaxCoins = 0;
m_DamageReduction = 0;
m_ComponentId = componentId;
m_ImmuneToBasicAttackCount = 0;
m_ImmuneToDamageOverTimeCount = 0;
@@ -75,71 +73,49 @@ DestroyableComponent::DestroyableComponent(Entity* parent, int32_t componentId)
m_ImmuneToQuickbuildInterruptCount = 0;
m_ImmuneToPullToPointCount = 0;
}
void DestroyableComponent::Startup() {
DestroyableComponent::~DestroyableComponent() {
}
void DestroyableComponent::LoadConfigData() {
SetIsSmashable(GetIsSmashable() | (m_ParentEntity->GetVarAs<int32_t>(u"is_smashable") != 0));
}
void DestroyableComponent::LoadTemplateData() {
if (m_ParentEntity->IsPlayer()) return;
auto* destroyableComponentTable = CDClientManager::Instance().GetTable<CDDestructibleComponentTable>();
auto destroyableDataLookup = destroyableComponentTable->Query([this](CDDestructibleComponent entry) { return (entry.id == this->m_ComponentId); });
if (m_ComponentId == -1 || destroyableDataLookup.empty()) {
void DestroyableComponent::Reinitialize(LOT templateID) {
CDComponentsRegistryTable* compRegistryTable = CDClientManager::Instance().GetTable<CDComponentsRegistryTable>();
int32_t buffComponentID = compRegistryTable->GetByIDAndType(templateID, eReplicaComponentType::BUFF);
int32_t collectibleComponentID = compRegistryTable->GetByIDAndType(templateID, eReplicaComponentType::COLLECTIBLE);
int32_t rebuildComponentID = compRegistryTable->GetByIDAndType(templateID, eReplicaComponentType::QUICK_BUILD);
int32_t componentID = 0;
if (collectibleComponentID > 0) componentID = collectibleComponentID;
if (rebuildComponentID > 0) componentID = rebuildComponentID;
if (buffComponentID > 0) componentID = buffComponentID;
CDDestructibleComponentTable* destCompTable = CDClientManager::Instance().GetTable<CDDestructibleComponentTable>();
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
if (componentID > 0) {
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
if (destCompData.size() > 0) {
SetHealth(destCompData[0].life);
SetImagination(destCompData[0].imagination);
SetArmor(destCompData[0].armor);
SetMaxHealth(destCompData[0].life);
SetMaxImagination(destCompData[0].imagination);
SetMaxArmor(destCompData[0].armor);
SetIsSmashable(destCompData[0].isSmashable);
}
} else {
SetHealth(1);
SetImagination(0);
SetArmor(0);
SetMaxHealth(1);
SetMaxImagination(0);
SetMaxArmor(0);
SetIsSmashable(true);
AddFaction(-1);
AddFaction(6); //Smashables
// A race car has 60 imagination, other entities defaults to 0.
SetImagination(m_ParentEntity->HasComponent(eReplicaComponentType::RACING_STATS) ? 60 : 0);
SetMaxImagination(m_ParentEntity->HasComponent(eReplicaComponentType::RACING_STATS) ? 60 : 0);
return;
}
auto destroyableData = destroyableDataLookup.at(0);
if (m_ParentEntity->HasComponent(eReplicaComponentType::RACING_STATS)) {
destroyableData.imagination = 60;
}
SetHealth(destroyableData.life);
SetImagination(destroyableData.imagination);
SetArmor(destroyableData.armor);
SetMaxHealth(destroyableData.life);
SetMaxImagination(destroyableData.imagination);
SetMaxArmor(destroyableData.armor);
SetIsSmashable(destroyableData.isSmashable);
SetLootMatrixID(destroyableData.LootMatrixIndex);
// Now get currency information
uint32_t npcMinLevel = destroyableData.level;
uint32_t currencyIndex = destroyableData.CurrencyIndex;
auto* currencyTable = CDClientManager::Instance().GetTable<CDCurrencyTableTable>();
auto currencyValues = currencyTable->Query([=](CDCurrencyTable entry) { return (entry.currencyIndex == currencyIndex && entry.npcminlevel == npcMinLevel); });
if (currencyValues.size() > 0) {
// Set the coins
SetMinCoins(currencyValues.at(0).minvalue);
SetMaxCoins(currencyValues.at(0).maxvalue);
}
AddFaction(destroyableData.faction);
std::stringstream ss(destroyableData.factionList);
std::string tokenStr;
while (std::getline(ss, tokenStr, ',')) {
int32_t factionToAdd = -2;
if (!GeneralUtils::TryParse(tokenStr, factionToAdd) || factionToAdd == -2 || factionToAdd == destroyableData.faction) continue;
AddFaction(factionToAdd);
}
}
@@ -187,7 +163,7 @@ void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsIn
outBitStream->Write(m_IsSmashed);
if (m_IsSmashable) {
outBitStream->Write(m_IsModuleAssembly);
outBitStream->Write(m_HasBricks);
outBitStream->Write(m_ExplodeFactor != 1.0f);
if (m_ExplodeFactor != 1.0f) outBitStream->Write(m_ExplodeFactor);
}
@@ -209,7 +185,7 @@ void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
return;
}
auto* buffComponent = m_ParentEntity->GetComponent<BuffComponent>();
auto* buffComponent = m_Parent->GetComponent<BuffComponent>();
if (buffComponent != nullptr) {
buffComponent->LoadFromXml(doc);
@@ -231,7 +207,7 @@ void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
return;
}
auto* buffComponent = m_ParentEntity->GetComponent<BuffComponent>();
auto* buffComponent = m_Parent->GetComponent<BuffComponent>();
if (buffComponent != nullptr) {
buffComponent->UpdateXml(doc);
@@ -248,7 +224,7 @@ void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
void DestroyableComponent::SetHealth(int32_t value) {
m_DirtyHealth = true;
auto* characterComponent = m_ParentEntity->GetComponent<CharacterComponent>();
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->TrackHealthDelta(value - m_iHealth);
}
@@ -268,16 +244,20 @@ void DestroyableComponent::SetMaxHealth(float value, bool playAnim) {
if (playAnim) {
// Now update the player bar
if (!m_ParentEntity->GetParentUser()) return;
if (!m_Parent->GetParentUser()) return;
AMFStringValue* amount = new AMFStringValue();
amount->SetStringValue(std::to_string(difference));
AMFStringValue* type = new AMFStringValue();
type->SetStringValue("health");
AMFArrayValue args;
args.Insert("amount", std::to_string(difference));
args.Insert("type", "health");
args.InsertValue("amount", amount);
args.InsertValue("type", type);
GameMessages::SendUIMessageServerToSingleClient(m_ParentEntity, m_ParentEntity->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", args);
GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args);
}
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
void DestroyableComponent::SetArmor(int32_t value) {
@@ -286,14 +266,14 @@ void DestroyableComponent::SetArmor(int32_t value) {
// If Destroyable Component already has zero armor do not trigger the passive ability again.
bool hadArmor = m_iArmor > 0;
auto* characterComponent = m_ParentEntity->GetComponent<CharacterComponent>();
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->TrackArmorDelta(value - m_iArmor);
}
m_iArmor = value;
auto* inventroyComponent = m_ParentEntity->GetComponent<InventoryComponent>();
auto* inventroyComponent = m_Parent->GetComponent<InventoryComponent>();
if (m_iArmor == 0 && inventroyComponent != nullptr && hadArmor) {
inventroyComponent->TriggerPassiveAbility(PassiveAbilityTrigger::SentinelArmor);
}
@@ -309,29 +289,33 @@ void DestroyableComponent::SetMaxArmor(float value, bool playAnim) {
if (playAnim) {
// Now update the player bar
if (!m_ParentEntity->GetParentUser()) return;
if (!m_Parent->GetParentUser()) return;
AMFStringValue* amount = new AMFStringValue();
amount->SetStringValue(std::to_string(value));
AMFStringValue* type = new AMFStringValue();
type->SetStringValue("armor");
AMFArrayValue args;
args.Insert("amount", std::to_string(value));
args.Insert("type", "armor");
args.InsertValue("amount", amount);
args.InsertValue("type", type);
GameMessages::SendUIMessageServerToSingleClient(m_ParentEntity, m_ParentEntity->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", args);
GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args);
}
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
void DestroyableComponent::SetImagination(int32_t value) {
m_DirtyHealth = true;
auto* characterComponent = m_ParentEntity->GetComponent<CharacterComponent>();
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
if (characterComponent != nullptr) {
characterComponent->TrackImaginationDelta(value - m_iImagination);
}
m_iImagination = value;
auto* inventroyComponent = m_ParentEntity->GetComponent<InventoryComponent>();
auto* inventroyComponent = m_Parent->GetComponent<InventoryComponent>();
if (m_iImagination == 0 && inventroyComponent != nullptr) {
inventroyComponent->TriggerPassiveAbility(PassiveAbilityTrigger::AssemblyImagination);
}
@@ -349,15 +333,19 @@ void DestroyableComponent::SetMaxImagination(float value, bool playAnim) {
if (playAnim) {
// Now update the player bar
if (!m_ParentEntity->GetParentUser()) return;
if (!m_Parent->GetParentUser()) return;
AMFStringValue* amount = new AMFStringValue();
amount->SetStringValue(std::to_string(difference));
AMFStringValue* type = new AMFStringValue();
type->SetStringValue("imagination");
AMFArrayValue args;
args.Insert("amount", std::to_string(difference));
args.Insert("type", "imagination");
args.InsertValue("amount", amount);
args.InsertValue("type", type);
GameMessages::SendUIMessageServerToSingleClient(m_ParentEntity, m_ParentEntity->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", args);
GameMessages::SendUIMessageServerToSingleClient(m_Parent, m_Parent->GetParentUser()->GetSystemAddress(), "MaxPlayerBarUpdate", &args);
}
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
void DestroyableComponent::SetDamageToAbsorb(int32_t value) {
@@ -479,8 +467,8 @@ bool DestroyableComponent::IsImmune() const {
}
bool DestroyableComponent::IsKnockbackImmune() const {
auto* characterComponent = m_ParentEntity->GetComponent<CharacterComponent>();
auto* inventoryComponent = m_ParentEntity->GetComponent<InventoryComponent>();
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
auto* inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
if (characterComponent != nullptr && inventoryComponent != nullptr && characterComponent->GetCurrentActivity() == eGameActivity::QUICKBUILDING) {
const auto hasPassive = inventoryComponent->HasAnyPassive({
@@ -523,7 +511,7 @@ bool DestroyableComponent::CheckValidity(const LWOOBJID target, const bool ignor
return false;
}
auto* targetQuickbuild = targetEntity->GetComponent<QuickBuildComponent>();
auto* targetQuickbuild = targetEntity->GetComponent<RebuildComponent>();
if (targetQuickbuild != nullptr) {
const auto state = targetQuickbuild->GetState();
@@ -556,7 +544,7 @@ void DestroyableComponent::Heal(const uint32_t health) {
SetHealth(current);
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
@@ -574,7 +562,7 @@ void DestroyableComponent::Imagine(const int32_t deltaImagination) {
SetImagination(current);
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
@@ -588,7 +576,7 @@ void DestroyableComponent::Repair(const uint32_t armor) {
SetArmor(current);
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
@@ -640,13 +628,13 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
SetIsShielded(absorb > 0);
// Dismount on the possessable hit
auto* possessable = m_ParentEntity->GetComponent<PossessableComponent>();
auto possessable = m_Parent->GetComponent<PossessableComponent>();
if (possessable && possessable->GetDepossessOnHit()) {
possessable->Dismount();
}
// Dismount on the possessor hit
auto* possessor = m_ParentEntity->GetComponent<PossessionComponent>();
auto possessor = m_Parent->GetComponent<PossessorComponent>();
if (possessor) {
auto possessableId = possessor->GetPossessable();
if (possessableId != LWOOBJID_EMPTY) {
@@ -657,17 +645,17 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
}
}
if (m_ParentEntity->GetLOT() != 1) {
if (m_Parent->GetLOT() != 1) {
echo = true;
}
if (echo) {
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
auto* attacker = EntityManager::Instance()->GetEntity(source);
m_ParentEntity->OnHit(attacker);
m_ParentEntity->OnHitOrHealResult(attacker, sourceDamage);
m_Parent->OnHit(attacker);
m_Parent->OnHitOrHealResult(attacker, sourceDamage);
NotifySubscribers(attacker, sourceDamage);
for (const auto& cb : m_OnHitCallbacks) {
@@ -675,7 +663,7 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
}
if (health != 0) {
auto* combatComponent = m_ParentEntity->GetComponent<BaseCombatAIComponent>();
auto* combatComponent = m_Parent->GetComponent<BaseCombatAIComponent>();
if (combatComponent != nullptr) {
combatComponent->Taunt(source, sourceDamage * 10); // * 10 is arbatrary
@@ -692,32 +680,26 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
Smash(source, eKillType::VIOLENT, u"", skillID);
}
void DestroyableComponent::Subscribe(CppScripts::Script* scriptToAdd) {
auto foundScript = std::find(m_SubscribedScripts.begin(), m_SubscribedScripts.end(), scriptToAdd);
if (foundScript != m_SubscribedScripts.end()) {
Game::logger->LogDebug("DestroyableComponent", "WARNING: Tried to add a script for Entity %llu but the script was already subscribed", m_ParentEntity->GetObjectID());
return;
}
m_SubscribedScripts.push_back(scriptToAdd);
Game::logger->LogDebug("DestroyableComponent", "A script has subscribed to entity %llu", m_ParentEntity->GetObjectID());
void DestroyableComponent::Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd) {
m_SubscribedScripts.insert(std::make_pair(scriptObjId, scriptToAdd));
Game::logger->LogDebug("DestroyableComponent", "Added script %llu to entity %llu", scriptObjId, m_Parent->GetObjectID());
Game::logger->LogDebug("DestroyableComponent", "Number of subscribed scripts %i", m_SubscribedScripts.size());
}
void DestroyableComponent::Unsubscribe(CppScripts::Script* scriptToRemove) {
auto foundScript = std::find(m_SubscribedScripts.begin(), m_SubscribedScripts.end(), scriptToRemove);
void DestroyableComponent::Unsubscribe(LWOOBJID scriptObjId) {
auto foundScript = m_SubscribedScripts.find(scriptObjId);
if (foundScript != m_SubscribedScripts.end()) {
m_SubscribedScripts.erase(foundScript);
Game::logger->LogDebug("DestroyableComponent", "Unsubscribed a script from entity %llu", m_ParentEntity->GetObjectID());
Game::logger->LogDebug("DestroyableComponent", "Number of subscribed scripts %i", m_SubscribedScripts.size());
return;
Game::logger->LogDebug("DestroyableComponent", "Removed script %llu from entity %llu", scriptObjId, m_Parent->GetObjectID());
} else {
Game::logger->LogDebug("DestroyableComponent", "Tried to remove a script for Entity %llu but script %llu didnt exist", m_Parent->GetObjectID(), scriptObjId);
}
Game::logger->LogDebug("DestroyableComponent", "WARNING: Tried to remove a script for Entity %llu but the script was not subscribed", m_ParentEntity->GetObjectID());
Game::logger->LogDebug("DestroyableComponent", "Number of subscribed scripts %i", m_SubscribedScripts.size());
}
void DestroyableComponent::NotifySubscribers(Entity* attacker, uint32_t damage) {
for (auto* script : m_SubscribedScripts) {
DluAssert(script != nullptr);
script->NotifyHitOrHealResult(m_ParentEntity, attacker, damage);
for (auto script : m_SubscribedScripts) {
script.second->NotifyHitOrHealResult(m_Parent, attacker, damage);
}
}
@@ -726,7 +708,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
SetArmor(0);
SetHealth(0);
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
m_KillerID = source;
@@ -738,12 +720,12 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
auto* team = TeamManager::Instance()->GetTeam(owner->GetObjectID());
const auto isEnemy = m_ParentEntity->GetComponent<BaseCombatAIComponent>() != nullptr;
const auto isEnemy = m_Parent->GetComponent<BaseCombatAIComponent>() != nullptr;
auto* inventoryComponent = owner->GetComponent<InventoryComponent>();
if (inventoryComponent != nullptr && isEnemy) {
inventoryComponent->TriggerPassiveAbility(PassiveAbilityTrigger::EnemySmashed, m_ParentEntity);
inventoryComponent->TriggerPassiveAbility(PassiveAbilityTrigger::EnemySmashed, m_Parent);
}
auto* missions = owner->GetComponent<MissionComponent>();
@@ -759,28 +741,28 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
if (memberMissions == nullptr) continue;
memberMissions->Progress(eMissionTaskType::SMASH, m_ParentEntity->GetLOT());
memberMissions->Progress(eMissionTaskType::USE_SKILL, m_ParentEntity->GetLOT(), skillID);
memberMissions->Progress(eMissionTaskType::SMASH, m_Parent->GetLOT());
memberMissions->Progress(eMissionTaskType::USE_SKILL, m_Parent->GetLOT(), skillID);
}
} else {
missions->Progress(eMissionTaskType::SMASH, m_ParentEntity->GetLOT());
missions->Progress(eMissionTaskType::USE_SKILL, m_ParentEntity->GetLOT(), skillID);
missions->Progress(eMissionTaskType::SMASH, m_Parent->GetLOT());
missions->Progress(eMissionTaskType::USE_SKILL, m_Parent->GetLOT(), skillID);
}
}
}
const auto isPlayer = m_ParentEntity->IsPlayer();
const auto isPlayer = m_Parent->IsPlayer();
GameMessages::SendDie(m_ParentEntity, source, source, true, killType, deathType, 0, 0, 0, isPlayer, false, 1);
GameMessages::SendDie(m_Parent, source, source, true, killType, deathType, 0, 0, 0, isPlayer, false, 1);
//NANI?!
if (!isPlayer) {
if (owner != nullptr) {
auto* team = TeamManager::Instance()->GetTeam(owner->GetObjectID());
if (team != nullptr && m_ParentEntity->GetComponent<BaseCombatAIComponent>() != nullptr) {
if (team != nullptr && m_Parent->GetComponent<BaseCombatAIComponent>() != nullptr) {
LWOOBJID specificOwner = LWOOBJID_EMPTY;
auto* scriptedActivityComponent = m_ParentEntity->GetComponent<ScriptedActivityComponent>();
auto* scriptedActivityComponent = m_Parent->GetComponent<ScriptedActivityComponent>();
uint32_t teamSize = team->members.size();
uint32_t lootMatrixId = GetLootMatrixID();
@@ -793,24 +775,24 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
auto* member = EntityManager::Instance()->GetEntity(specificOwner);
if (member) LootGenerator::Instance().DropLoot(member, m_ParentEntity, lootMatrixId, GetMinCoins(), GetMaxCoins());
if (member) LootGenerator::Instance().DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins());
} else {
for (const auto memberId : team->members) { // Free for all
auto* member = EntityManager::Instance()->GetEntity(memberId);
if (member == nullptr) continue;
LootGenerator::Instance().DropLoot(member, m_ParentEntity, lootMatrixId, GetMinCoins(), GetMaxCoins());
LootGenerator::Instance().DropLoot(member, m_Parent, lootMatrixId, GetMinCoins(), GetMaxCoins());
}
}
} else { // drop loot for non team user
LootGenerator::Instance().DropLoot(owner, m_ParentEntity, GetLootMatrixID(), GetMinCoins(), GetMaxCoins());
LootGenerator::Instance().DropLoot(owner, m_Parent, GetLootMatrixID(), GetMinCoins(), GetMaxCoins());
}
}
} else {
//Check if this zone allows coin drops
if (dZoneManager::Instance()->GetPlayerLoseCoinOnDeath()) {
auto* character = m_ParentEntity->GetCharacter();
auto* character = m_Parent->GetCharacter();
uint64_t coinsTotal = character->GetCoins();
const uint64_t minCoinsToLose = dZoneManager::Instance()->GetWorldConfig()->coinsLostOnDeathMin;
if (coinsTotal >= minCoinsToLose) {
@@ -822,23 +804,27 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
coinsTotal -= coinsToLose;
LootGenerator::Instance().DropLoot(m_ParentEntity, m_ParentEntity, -1, coinsToLose, coinsToLose);
LootGenerator::Instance().DropLoot(m_Parent, m_Parent, -1, coinsToLose, coinsToLose);
character->SetCoins(coinsTotal, eLootSourceType::PICKUP);
}
}
Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity();
zoneControl->GetScript()->OnPlayerDied(zoneControl, m_ParentEntity);
for (CppScripts::Script* script : CppScripts::GetEntityScripts(zoneControl)) {
script->OnPlayerDied(zoneControl, m_Parent);
}
std::vector<Entity*> scriptedActs = EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY);
for (Entity* scriptEntity : scriptedActs) {
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
scriptEntity->GetScript()->OnPlayerDied(scriptEntity, m_ParentEntity);
for (CppScripts::Script* script : CppScripts::GetEntityScripts(scriptEntity)) {
script->OnPlayerDied(scriptEntity, m_Parent);
}
}
}
}
m_ParentEntity->Kill(owner);
m_Parent->Kill(owner);
}
void DestroyableComponent::SetFaction(int32_t factionID, bool ignoreChecks) {
@@ -871,7 +857,7 @@ void DestroyableComponent::SetStatusImmunity(
if (bImmuneToQuickbuildInterrupt && m_ImmuneToQuickbuildInterruptCount > 0) m_ImmuneToQuickbuildInterruptCount -= 1;
if (bImmuneToPullToPoint && m_ImmuneToPullToPointCount > 0) m_ImmuneToPullToPointCount -= 1;
} else if (state == eStateChangeType::PUSH) {
} else if (state == eStateChangeType::PUSH){
if (bImmuneToBasicAttack) m_ImmuneToBasicAttackCount += 1;
if (bImmuneToDamageOverTime) m_ImmuneToDamageOverTimeCount += 1;
if (bImmuneToKnockback) m_ImmuneToKnockbackCount += 1;
@@ -884,7 +870,7 @@ void DestroyableComponent::SetStatusImmunity(
}
GameMessages::SendSetStatusImmunity(
m_ParentEntity->GetObjectID(), state, m_ParentEntity->GetSystemAddress(),
m_Parent->GetObjectID(), state, m_Parent->GetSystemAddress(),
bImmuneToBasicAttack,
bImmuneToDamageOverTime,
bImmuneToKnockback,
@@ -898,7 +884,7 @@ void DestroyableComponent::SetStatusImmunity(
}
void DestroyableComponent::FixStats() {
auto* entity = GetParentEntity();
auto* entity = GetParent();
if (entity == nullptr) return;
@@ -998,43 +984,43 @@ void DestroyableComponent::AddOnHitCallback(const std::function<void(Entity*)>&
m_OnHitCallbacks.push_back(callback);
}
void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source){
//check if this is a player:
if (m_ParentEntity->IsPlayer()) {
if (m_Parent->IsPlayer()) {
//remove hardcore_lose_uscore_on_death_percent from the player's uscore:
auto* character = m_ParentEntity->GetComponent<CharacterComponent>();
auto* character = m_Parent->GetComponent<CharacterComponent>();
auto uscore = character->GetUScore();
auto uscoreToLose = uscore * (EntityManager::Instance()->GetHardcoreLoseUscoreOnDeathPercent() / 100);
character->SetUScore(uscore - uscoreToLose);
GameMessages::SendModifyLEGOScore(m_ParentEntity, m_ParentEntity->GetSystemAddress(), -uscoreToLose, eLootSourceType::MISSION);
GameMessages::SendModifyLEGOScore(m_Parent, m_Parent->GetSystemAddress(), -uscoreToLose, eLootSourceType::MISSION);
if (EntityManager::Instance()->GetHardcoreDropinventoryOnDeath()) {
//drop all items from inventory:
auto* inventory = m_ParentEntity->GetComponent<InventoryComponent>();
auto* inventory = m_Parent->GetComponent<InventoryComponent>();
if (inventory) {
//get the items inventory:
auto items = inventory->GetInventory(eInventoryType::ITEMS);
if (items) {
if (items){
auto itemMap = items->GetItems();
if (!itemMap.empty()) {
if (!itemMap.empty()){
for (const auto& item : itemMap) {
//drop the item:
if (!item.second) continue;
// don't drop the thinkng cap
if (item.second->GetLot() == 6086) continue;
GameMessages::SendDropClientLoot(m_ParentEntity, source, item.second->GetLot(), 0, m_ParentEntity->GetPosition(), item.second->GetCount());
GameMessages::SendDropClientLoot(m_Parent, source, item.second->GetLot(), 0, m_Parent->GetPosition(), item.second->GetCount());
item.second->SetCount(0, false, false);
}
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
}
}
}
//get character:
auto* chars = m_ParentEntity->GetCharacter();
auto* chars = m_Parent->GetCharacter();
if (chars) {
auto coins = chars->GetCoins();
@@ -1042,13 +1028,13 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
chars->SetCoins(0, eLootSourceType::NONE);
//drop all coins:
GameMessages::SendDropClientLoot(m_ParentEntity, source, LOT_NULL, coins, m_ParentEntity->GetPosition());
GameMessages::SendDropClientLoot(m_Parent, source, LOT_NULL, coins, m_Parent->GetPosition());
}
// Reload the player since we can't normally reduce uscore from the server and we want the UI to update
// do this last so we don't get killed.... again
EntityManager::Instance()->DestructEntity(m_ParentEntity);
EntityManager::Instance()->ConstructEntity(m_ParentEntity);
EntityManager::Instance()->DestructEntity(m_Parent);
EntityManager::Instance()->ConstructEntity(m_Parent);
return;
}
@@ -1065,7 +1051,7 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
playerStats->SetUScore(playerStats->GetUScore() + uscore);
GameMessages::SendModifyLEGOScore(player, player->GetSystemAddress(), uscore, eLootSourceType::MISSION);
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
EntityManager::Instance()->SerializeEntity(m_Parent);
}
}
}

View File

@@ -17,19 +17,23 @@ enum class eStateChangeType : uint32_t;
* Represents the stats of an entity, for example its health, imagination and armor. Also handles factions, which
* indicate which enemies this entity has.
*/
class DestroyableComponent final : public Component {
class DestroyableComponent : public Component {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::DESTROYABLE;
static const eReplicaComponentType ComponentType = eReplicaComponentType::DESTROYABLE;
DestroyableComponent(Entity* parentEntity, int32_t componentId = -1);
DestroyableComponent(Entity* parentEntity);
~DestroyableComponent() override;
void Startup() override;
void LoadConfigData() override;
void LoadTemplateData() override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, uint32_t& flags);
void LoadFromXml(tinyxml2::XMLDocument* doc) override;
void UpdateXml(tinyxml2::XMLDocument* doc) override;
/**
* Initializes the component using a different LOT
* @param templateID the ID to use for initialization
*/
void Reinitialize(LOT templateID);
/**
* Sets the health of this entity. Makes sure this is serialized on the next tick and if this is a character its
* stats will also update.
@@ -235,7 +239,7 @@ public:
* Returns whether or not this entity has bricks flying out when smashed
* @return whether or not this entity has bricks flying out when smashed
*/
bool GetHasBricks() const { return m_IsModuleAssembly; }
bool GetHasBricks() const { return m_HasBricks; }
/**
* Sets the multiplier for the explosion that's visible when the bricks fly out when this entity is smashed
@@ -447,15 +451,13 @@ public:
*/
void NotifySubscribers(Entity* attacker, uint32_t damage);
void Subscribe(CppScripts::Script* scriptToAdd);
void Unsubscribe(CppScripts::Script* scriptToRemove);
void Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd);
void Unsubscribe(LWOOBJID scriptObjId);
// handle hardcode mode drops
void DoHardcoreModeDrops(const LWOOBJID source);
private:
// The ID of this component
int32_t m_ComponentId;
/**
* Whether or not the health should be serialized
*/
@@ -544,7 +546,7 @@ private:
/**
* Whether this entity has bricks flying out when smashed (causes the client to look up the files)
*/
bool m_IsModuleAssembly;
bool m_HasBricks;
/**
* The rate at which bricks fly out when smashed
@@ -587,9 +589,9 @@ private:
std::vector<std::function<void(Entity*)>> m_OnHitCallbacks;
/**
* Scripts that are subscribed to this component
* The list of scripts subscribed to this components actions
*/
std::vector<CppScripts::Script*> m_SubscribedScripts;
std::map<LWOOBJID, CppScripts::Script*> m_SubscribedScripts;
/**
* status immunity counters

View File

@@ -1,23 +0,0 @@
#include "DonationVendorComponent.h"
DonationVendorComponent::DonationVendorComponent(Entity* parent) : VendorComponent(parent) {
m_PercentComplete = 0.0;
m_TotalDonated = 0;
m_TotalRemaining = 0;
}
void DonationVendorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
VendorComponent::Serialize(outBitStream, bIsInitialUpdate, flags);
outBitStream->Write(bIsInitialUpdate || m_DirtyDonationVendor);
if (bIsInitialUpdate || m_DirtyDonationVendor) {
outBitStream->Write(m_PercentComplete);
outBitStream->Write(m_TotalDonated);
outBitStream->Write(m_TotalRemaining);
if (!bIsInitialUpdate) m_DirtyDonationVendor = false;
}
}
void DonationVendorComponent::LoadConfigData() {
m_ActivityId = m_ParentEntity->GetVar<uint32_t>(u"activityID");
VendorComponent::LoadConfigData();
}

View File

@@ -1,44 +0,0 @@
#ifndef __DONATIONVENDORCOMPONENT__H__
#define __DONATIONVENDORCOMPONENT__H__
#include "VendorComponent.h"
#include "eReplicaComponentType.h"
class Entity;
class DonationVendorComponent final : public VendorComponent {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::DONATION_VENDOR;
DonationVendorComponent(Entity* parent);
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
void LoadConfigData() override;
void SetPercentComplete(float percentComplete){
if (m_PercentComplete == percentComplete) return;
m_PercentComplete = percentComplete;
m_DirtyDonationVendor = true;
}
void SetTotalDonated(float totalDonated){
if (m_TotalDonated == totalDonated) return;
m_TotalDonated = totalDonated;
m_DirtyDonationVendor = true;
}
void SetTotalRemaining(float totalRemaining){
if (m_TotalRemaining == totalRemaining) return;
m_TotalRemaining = totalRemaining;
m_DirtyDonationVendor = true;
}
private:
bool m_DirtyDonationVendor = false;
float m_PercentComplete = 0.0;
int32_t m_TotalDonated = 0;
int32_t m_TotalRemaining = 0;
uint32_t m_ActivityId = 0;
};
#endif //!__DONATIONVENDORCOMPONENT__H__

View File

@@ -1,5 +0,0 @@
#include "GateRushComponent.h"
GateRushComponent::GateRushComponent(Entity* parent, int32_t componentId) : RacingControlComponent(parent, componentId) {
}

View File

@@ -1,15 +0,0 @@
#ifndef __GATERUSHCONTROLCOMPONENT__H__
#define __GATERUSHCONTROLCOMPONENT__H__
#include "RacingControlComponent.h"
#include "eReplicaComponentType.h"
class Entity;
class GateRushComponent final : public RacingControlComponent {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::GATE_RUSH_CONTROL;
GateRushComponent(Entity* parent, int32_t componentId);
};
#endif //!__GATERUSHCONTROLCOMPONENT__H__

View File

@@ -1,118 +0,0 @@
#include "HavokVehiclePhysicsComponent.h"
#include "EntityManager.h"
HavokVehiclePhysicsComponent::HavokVehiclePhysicsComponent(Entity* parent) : Component(parent) {
m_Position = NiPoint3::ZERO;
m_Rotation = NiQuaternion::IDENTITY;
m_Velocity = NiPoint3::ZERO;
m_AngularVelocity = NiPoint3::ZERO;
m_IsOnGround = true;
m_IsOnRail = false;
m_DirtyPosition = true;
m_DirtyVelocity = true;
m_DirtyAngularVelocity = true;
m_EndBehavior = GeneralUtils::GenerateRandomNumber<uint32_t>(0, 7);
}
void HavokVehiclePhysicsComponent::SetPosition(const NiPoint3& pos) {
if (pos == m_Position) return;
m_DirtyPosition = true;
m_Position = pos;
}
void HavokVehiclePhysicsComponent::SetRotation(const NiQuaternion& rot) {
if (rot == m_Rotation) return;
m_DirtyPosition = true;
m_Rotation = rot;
}
void HavokVehiclePhysicsComponent::SetVelocity(const NiPoint3& vel) {
if (vel == m_Velocity) return;
m_DirtyPosition = true;
m_Velocity = vel;
}
void HavokVehiclePhysicsComponent::SetAngularVelocity(const NiPoint3& vel) {
if (vel == m_AngularVelocity) return;
m_DirtyPosition = true;
m_AngularVelocity = vel;
}
void HavokVehiclePhysicsComponent::SetIsOnGround(bool val) {
if (val == m_IsOnGround) return;
m_DirtyPosition = true;
m_IsOnGround = val;
}
void HavokVehiclePhysicsComponent::SetIsOnRail(bool val) {
if (val == m_IsOnRail) return;
m_DirtyPosition = true;
m_IsOnRail = val;
}
void HavokVehiclePhysicsComponent::SetRemoteInputInfo(const RemoteInputInfo& remoteInputInfo) {
if (m_RemoteInputInfo == remoteInputInfo) return;
this->m_RemoteInputInfo = remoteInputInfo;
m_DirtyRemoteInput = true;
}
void HavokVehiclePhysicsComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write(bIsInitialUpdate || m_DirtyPosition);
if (bIsInitialUpdate || m_DirtyPosition) {
if (!bIsInitialUpdate) m_DirtyPosition = false;
outBitStream->Write(m_Position.x);
outBitStream->Write(m_Position.y);
outBitStream->Write(m_Position.z);
outBitStream->Write(m_Rotation.x);
outBitStream->Write(m_Rotation.y);
outBitStream->Write(m_Rotation.z);
outBitStream->Write(m_Rotation.w);
outBitStream->Write(m_IsOnGround);
outBitStream->Write(m_IsOnRail);
outBitStream->Write(bIsInitialUpdate || m_DirtyVelocity);
if (bIsInitialUpdate || m_DirtyVelocity) {
outBitStream->Write(m_Velocity.x);
outBitStream->Write(m_Velocity.y);
outBitStream->Write(m_Velocity.z);
if (!bIsInitialUpdate) m_DirtyVelocity = false;
}
outBitStream->Write(bIsInitialUpdate || m_DirtyAngularVelocity);
if (bIsInitialUpdate || m_DirtyAngularVelocity) {
outBitStream->Write(m_AngularVelocity.x);
outBitStream->Write(m_AngularVelocity.y);
outBitStream->Write(m_AngularVelocity.z);
if (!bIsInitialUpdate) m_DirtyAngularVelocity = false;
}
outBitStream->Write0(); // local_space_info. TODO: Implement this
outBitStream->Write(m_DirtyRemoteInput || bIsInitialUpdate); // remote_input_info
if (m_DirtyRemoteInput || bIsInitialUpdate) {
outBitStream->Write(m_RemoteInputInfo.m_RemoteInputX);
outBitStream->Write(m_RemoteInputInfo.m_RemoteInputY);
outBitStream->Write(m_RemoteInputInfo.m_IsPowersliding);
outBitStream->Write(m_RemoteInputInfo.m_IsModified);
if (!bIsInitialUpdate) m_DirtyRemoteInput = false;
}
outBitStream->Write(125.0f); // remote_input_ping TODO: Figure out how this should be calculated as it seems to be constant through the whole race.
if (!bIsInitialUpdate) {
outBitStream->Write0();
}
}
if (bIsInitialUpdate) {
outBitStream->Write<uint8_t>(m_EndBehavior);
outBitStream->Write1(); // is input locked?
}
outBitStream->Write0();
}

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@
#include "Component.h"
#include "ItemSetPassiveAbility.h"
#include "eItemSetPassiveAbilityID.h"
#include "PossessionComponent.h"
#include "PossessorComponent.h"
#include "eInventoryType.h"
#include "eReplicaComponentType.h"
#include "eLootSourceType.h"
@@ -35,17 +35,17 @@ enum class eItemType : int32_t;
* of different types, each type representing a different group of items, see `eInventoryType` for a list of
* inventories.
*/
class InventoryComponent final : public Component
class InventoryComponent : public Component
{
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY;
static const eReplicaComponentType ComponentType = eReplicaComponentType::INVENTORY;
explicit InventoryComponent(Entity* parent, tinyxml2::XMLDocument* document = nullptr);
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
void LoadXml(tinyxml2::XMLDocument* document);
void UpdateXml(tinyxml2::XMLDocument* document) override;
void ResetFlags() { m_Dirty = false; };
void ResetFlags();
/**
* Returns an inventory of the specified type, if it exists
@@ -58,12 +58,10 @@ public:
* Returns all the inventories this entity has, indexed by type
* @return all the inventories this entity has, indexed by type
*/
const std::map<eInventoryType, Inventory*>& GetInventories() const { return m_Inventories; }
const std::map<eInventoryType, Inventory*>& GetInventories() const;
/**
* Returns the amount of items this entity possesses of a certain LOT
* This method counts the lot count for all inventories, including inventories the player may not be able to see.
* If you need the count in a specific inventory, call the inventory equivalent.
* @param lot the lot to search for
* @return the amount of items this entity possesses the specified LOT
*/
@@ -81,7 +79,7 @@ public:
* Returns the items that are currently equipped by this entity
* @return the items that are currently equipped by this entity
*/
const EquipmentMap& GetEquippedItems() const { return m_Equipped; }
const EquipmentMap& GetEquippedItems() const;
/**
* Adds an item to the inventory of the entity
@@ -208,7 +206,7 @@ public:
* @param item the Item to unequip
* @return if we were successful
*/
void HandlePossession(Item* item) const;
void HandlePossession(Item* item);
/**
* Adds a buff related to equipping a lot to the entity
@@ -249,13 +247,13 @@ public:
* Sets the current consumable lot
* @param lot the lot to set as consumable
*/
void SetConsumable(LOT lot) { m_Consumable = lot; };
void SetConsumable(LOT lot);
/**
* Returns the current consumable lot
* @return the current consumable lot
*/
LOT GetConsumable() const { return m_Consumable; }
LOT GetConsumable() const;
/**
* Finds all the buffs related to a lot
@@ -287,7 +285,7 @@ public:
* Triggers one of the passive abilities from the equipped item set
* @param trigger the trigger to fire
*/
void TriggerPassiveAbility(PassiveAbilityTrigger trigger, Entity* target = nullptr) const;
void TriggerPassiveAbility(PassiveAbilityTrigger trigger, Entity* target = nullptr);
/**
* Returns if the entity has any of the passed passive abilities equipped
@@ -327,13 +325,13 @@ public:
* @param id the id of the object to check for
* @return if the provided object ID is in this inventory and is a pet
*/
bool IsPet(const LWOOBJID& id) const { return m_Pets.find(id) != m_Pets.end(); }
bool IsPet(LWOOBJID id) const;
/**
* Removes pet database information from the item with the specified object id
* @param id the object id to remove pet info for
*/
void RemoveDatabasePet(const LWOOBJID& id) { m_Pets.erase(id); }
void RemoveDatabasePet(LWOOBJID id);
/**
* Returns the current behavior slot active for the passed item type
@@ -361,14 +359,14 @@ public:
*
* @param equippedItem The item script to lookup and call equip on
*/
void EquipScripts(Item* equippedItem) const;
void EquipScripts(Item* equippedItem);
/**
* Call this when you unequip an item. This calls OnFactionTriggerItemUnequipped for any scripts found on the items.
*
* @param unequippedItem The item script to lookup and call unequip on
*/
void UnequipScripts(Item* unequippedItem) const;
void UnequipScripts(Item* unequippedItem);
~InventoryComponent() override;

View File

@@ -1,32 +0,0 @@
#include "ItemComponent.h"
#include "Entity.h"
#include "eUgcModerationStatus.h"
ItemComponent::ItemComponent(Entity* parent) : Component(parent) {
m_ParentEntity = parent;
m_DirtyItemInfo = false;
m_UgId = m_ParentEntity->GetVarAs<LWOOBJID>(u"userModelID");
if (m_UgId == LWOOBJID_EMPTY) m_UgId = m_ParentEntity->GetObjectID();
m_UgModerationStatus = eUgcModerationStatus::NoStatus;
m_UgDescription = u"";
}
void ItemComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write(m_DirtyItemInfo || bIsInitialUpdate);
if (m_DirtyItemInfo || bIsInitialUpdate) {
outBitStream->Write(m_UgId);
outBitStream->Write(m_UgModerationStatus);
outBitStream->Write(!m_UgDescription.empty());
if (!m_UgDescription.empty()){
outBitStream->Write<uint32_t>(m_UgDescription.length());
outBitStream->Write(reinterpret_cast<const char*>(m_UgDescription.c_str()), m_UgDescription.length() * sizeof(uint16_t));
}
m_DirtyItemInfo = false;
}
}

View File

@@ -1,55 +0,0 @@
#ifndef __ITEMCOMPONENT__H__
#define __ITEMCOMPONENT__H__
#pragma once
#include "dCommonVars.h"
#include "RakNetTypes.h"
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include "Component.h"
#include "eReplicaComponentType.h"
class Entity;
enum class eUgcModerationStatus : uint32_t;
class ItemComponent final : public Component {
public:
static const eReplicaComponentType ComponentType = eReplicaComponentType::ITEM;
ItemComponent(Entity* parent);
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags);
void SetUgId(LWOOBJID id) { m_UgId = id; m_DirtyItemInfo = true; };
LWOOBJID GetUgId() { return m_UgId; };
void SetUgModerationStatus(eUgcModerationStatus status) { m_UgModerationStatus = status; m_DirtyItemInfo = true; };
eUgcModerationStatus GetUgModerationStatus() { return m_UgModerationStatus; };
void SetUgDescription(std::u16string description) { m_UgDescription = description; m_DirtyItemInfo = true; };
std::u16string GetUgDescription() { return m_UgDescription;};
private:
/**
* If we have change the item info
*/
bool m_DirtyItemInfo;
/**
* The ID of the user that made the model
*/
LWOOBJID m_UgId;
/**
* Whether or not the description of this item is approved.
*/
eUgcModerationStatus m_UgModerationStatus;
/**
* The user generated description
*/
std::u16string m_UgDescription;
};
#endif //!__ITEMCOMPONENT__H__

View File

@@ -3,32 +3,42 @@
#include "EntityManager.h"
LUPExhibitComponent::LUPExhibitComponent(Entity* parent) : Component(parent) {
m_Exhibits = { 11121, 11295, 11423, 11979 };
m_ExhibitIndex = 0;
m_UpdateTimer = 0.0f;
m_Exhibit = m_Exhibits.front();
m_DirtyExhibitInfo = true;
m_Exhibit = m_Exhibits[m_ExhibitIndex];
}
LUPExhibitComponent::~LUPExhibitComponent() {
}
void LUPExhibitComponent::Update(float deltaTime) {
m_UpdateTimer += deltaTime;
if (m_UpdateTimer < 20.0f) return;
if (m_UpdateTimer > 20.0f) {
NextExhibit();
m_UpdateTimer = 0.0f;
}
}
void LUPExhibitComponent::NextExhibit() {
m_ExhibitIndex++;
// After 1361 years, this will skip exhibit 4 one time. I think modulo is ok here.
m_Exhibit = m_Exhibits.at(m_ExhibitIndex % m_Exhibits.size());
EntityManager::Instance()->SerializeEntity(m_ParentEntity);
if (m_ExhibitIndex >= m_Exhibits.size()) {
m_ExhibitIndex = 0;
}
m_Exhibit = m_Exhibits[m_ExhibitIndex];
EntityManager::Instance()->SerializeEntity(m_Parent);
}
void LUPExhibitComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, uint32_t& flags) {
outBitStream->Write(bIsInitialUpdate || m_DirtyExhibitInfo);
if (bIsInitialUpdate || m_DirtyExhibitInfo) {
outBitStream->Write1(); // Dirty flag?
outBitStream->Write(m_Exhibit);
if (!bIsInitialUpdate) m_DirtyExhibitInfo = false;
}
}

View File

@@ -8,12 +8,13 @@
* Component that handles the LOT that is shown in the LUP exhibit in the LUP world. Works by setting a timer and
* switching the LOTs around that we'd like to display.
*/
class LUPExhibitComponent final : public Component
class LUPExhibitComponent : public Component
{
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::EXHIBIT;
static const eReplicaComponentType ComponentType = eReplicaComponentType::EXHIBIT;
LUPExhibitComponent(Entity* parent);
~LUPExhibitComponent();
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, uint32_t& flags);
@@ -35,13 +36,10 @@ private:
/**
* The list of possible exhibits to show
*/
const std::vector<LOT> m_Exhibits = { 11121, 11295, 11423, 11979 };
std::vector<LOT> m_Exhibits;
/**
* The current index in the exhibit list
*/
size_t m_ExhibitIndex;
// Whether or not to notify clients of a change in the visible exhibit
bool m_DirtyExhibitInfo;
};

View File

@@ -7,7 +7,7 @@
#include "CDRewardsTable.h"
LevelProgressionComponent::LevelProgressionComponent(Entity* parent) : Component(parent) {
m_ParentEntity = parent;
m_Parent = parent;
m_Level = 1;
m_SpeedBase = 500.0f;
m_CharacterVersion = eCharacterVersion::LIVE;
@@ -35,60 +35,55 @@ void LevelProgressionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
uint32_t characterVersion;
level->QueryAttribute("cv", &characterVersion);
m_CharacterVersion = static_cast<eCharacterVersion>(characterVersion);
auto* controllablePhysicsComponent = m_ParentEntity->GetComponent<ControllablePhysicsComponent>();
if (!controllablePhysicsComponent) return;
controllablePhysicsComponent->SetSpeedMultiplier(GetSpeedBase() / 500.0f);
}
void LevelProgressionComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write(bIsInitialUpdate || m_DirtyLevelInfo);
if (bIsInitialUpdate || m_DirtyLevelInfo) {
outBitStream->Write(m_Level);
if (!bIsInitialUpdate) m_DirtyLevelInfo = false;
}
if (bIsInitialUpdate || m_DirtyLevelInfo) outBitStream->Write(m_Level);
m_DirtyLevelInfo = false;
}
void LevelProgressionComponent::HandleLevelUp() {
auto* rewardsTable = CDClientManager::Instance().GetTable<CDRewardsTable>();
const auto& rewards = rewardsTable->GetByLevelID(m_Level);
if (rewards.empty()) return;
bool rewardingItem = rewards.size() > 0;
auto* inventoryComponent = m_ParentEntity->GetComponent<InventoryComponent>();
auto* controllablePhysicsComponent = m_ParentEntity->GetComponent<ControllablePhysicsComponent>();
auto* inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
auto* controllablePhysicsComponent = m_Parent->GetComponent<ControllablePhysicsComponent>();
if (!inventoryComponent || !controllablePhysicsComponent) return;
// Tell the client we beginning to send level rewards.
GameMessages::NotifyLevelRewards(m_ParentEntity->GetObjectID(), m_ParentEntity->GetSystemAddress(), m_Level, true);
if (rewardingItem) GameMessages::NotifyLevelRewards(m_Parent->GetObjectID(), m_Parent->GetSystemAddress(), m_Level, rewardingItem);
for (auto* reward : rewards) {
switch (reward->rewardType) {
case 0:
inventoryComponent->AddItem(reward->value, reward->count, eLootSourceType::LEVEL_REWARD);
break;
case 4: {
case 4:
{
auto* items = inventoryComponent->GetInventory(eInventoryType::ITEMS);
if (!items) continue;
items->SetSize(items->GetSize() + reward->value);
break;
}
break;
case 9:
SetSpeedBase(static_cast<float>(reward->value));
SetSpeedBase(static_cast<float>(reward->value) );
controllablePhysicsComponent->SetSpeedMultiplier(GetSpeedBase() / 500.0f);
break;
case 11:
case 12:
break;
default:
break;
}
}
// Tell the client we have finished sending level rewards.
GameMessages::NotifyLevelRewards(m_ParentEntity->GetObjectID(), m_ParentEntity->GetSystemAddress(), m_Level, false);
if (rewardingItem) GameMessages::NotifyLevelRewards(m_Parent->GetObjectID(), m_Parent->GetSystemAddress(), m_Level, !rewardingItem);
}
void LevelProgressionComponent::SetRetroactiveBaseSpeed() {
void LevelProgressionComponent::SetRetroactiveBaseSpeed(){
if (m_Level >= 20) m_SpeedBase = 525.0f;
auto* controllablePhysicsComponent = m_ParentEntity->GetComponent<ControllablePhysicsComponent>();
auto* controllablePhysicsComponent = m_Parent->GetComponent<ControllablePhysicsComponent>();
if (controllablePhysicsComponent) controllablePhysicsComponent->SetSpeedMultiplier(m_SpeedBase / 500.0f);
}

View File

@@ -11,9 +11,9 @@
*
*/
class LevelProgressionComponent final : public Component {
class LevelProgressionComponent : public Component {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::LEVEL_PROGRESSION;
static const eReplicaComponentType ComponentType = eReplicaComponentType::LEVEL_PROGRESSION;
/**
* Constructor for this component
@@ -45,11 +45,7 @@ public:
* Sets the level of the entity
* @param level the level to set
*/
void SetLevel(uint32_t level) {
if (m_Level == level) return;
m_Level = level;
m_DirtyLevelInfo = true;
}
void SetLevel(uint32_t level) { m_Level = level; m_DirtyLevelInfo = true; }
/**
* Gets the current Speed Base of the entity
@@ -102,7 +98,7 @@ private:
float m_SpeedBase;
/**
* The Character format version. Certain bug fixes increment this version number.
* The Character format version
*/
eCharacterVersion m_CharacterVersion;

View File

@@ -1,7 +0,0 @@
#include "Entity.h"
#include "MinigameControlComponent.h"
MinigameControlComponent::MinigameControlComponent(Entity* parent, int32_t componentId) : ActivityComponent(parent, componentId) {
}

View File

@@ -1,15 +0,0 @@
#ifndef __MINIGAMECONTROLCOMPONENT__H__
#define __MINIGAMECONTROLCOMPONENT__H__
#include "ActivityComponent.h"
#include "eReplicaComponentType.h"
class Entity;
class MinigameControlComponent : public ActivityComponent {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::MINIGAME_CONTROL;
MinigameControlComponent(Entity* parent, int32_t componentId);
};
#endif //!__MINIGAMECONTROLCOMPONENT__H__

View File

@@ -1,8 +1,9 @@
/*
* Darkflame Universe
* Copyright 2023
* Copyright 2019
*/
#include <sstream>
#include <string>
#include "MissionComponent.h"
@@ -12,46 +13,68 @@
#include "InventoryComponent.h"
#include "GameMessages.h"
#include "Game.h"
#include "Amf3.h"
#include "AMFFormat.h"
#include "dZoneManager.h"
#include "Mail.h"
#include "MissionPrerequisites.h"
#include "AchievementCacheKey.h"
#include "eMissionState.h"
#include "GeneralUtils.h"
// MARK: Mission Component
std::unordered_map<AchievementCacheKey, std::vector<uint32_t>> MissionComponent::m_AchievementCache = {};
//! Initializer
MissionComponent::MissionComponent(Entity* parent) : Component(parent) {
m_LastUsedMissionOrderUID = dZoneManager::Instance()->GetUniqueMissionIdStartingValue();
}
//! Destructor
MissionComponent::~MissionComponent() {
for (const auto& [missionId, mission] : m_Missions) {
delete mission;
for (const auto& mission : m_Missions) {
delete mission.second;
}
this->m_Missions.clear();
}
Mission* MissionComponent::GetMission(const uint32_t missionId) const {
if (m_Missions.count(missionId) == 0) return nullptr;
if (m_Missions.count(missionId) == 0) {
return nullptr;
}
const auto& index = m_Missions.find(missionId);
return index == m_Missions.end() ? nullptr : index->second;
if (index == m_Missions.end()) {
return nullptr;
}
return index->second;
}
eMissionState MissionComponent::GetMissionState(const uint32_t missionId) const {
auto* mission = GetMission(missionId);
if (!mission) return CanAccept(missionId) ? eMissionState::AVAILABLE : eMissionState::UNKNOWN;
if (mission == nullptr) {
return CanAccept(missionId) ? eMissionState::AVAILABLE : eMissionState::UNKNOWN;
}
return mission->GetMissionState();
}
const std::unordered_map<uint32_t, Mission*>& MissionComponent::GetMissions() const {
return m_Missions;
}
bool MissionComponent::CanAccept(const uint32_t missionId) const {
return MissionPrerequisites::CanAccept(missionId, m_Missions);
}
void MissionComponent::AcceptMission(const uint32_t missionId, const bool skipChecks) {
if (!skipChecks && !CanAccept(missionId)) {
return;
@@ -60,7 +83,7 @@ void MissionComponent::AcceptMission(const uint32_t missionId, const bool skipCh
// If this is a daily mission, it may already be "accepted"
auto* mission = this->GetMission(missionId);
if (mission) {
if (mission != nullptr) {
if (mission->GetClientInfo().repeatable) {
mission->Accept();
if (mission->IsMission()) mission->SetUniqueMissionOrderID(++m_LastUsedMissionOrderUID);
@@ -77,41 +100,54 @@ void MissionComponent::AcceptMission(const uint32_t missionId, const bool skipCh
this->m_Missions.insert_or_assign(missionId, mission);
if (missionId == 1728) {
//Needs to send a mail
if (missionId == 1728) Mail::HandleNotificationRequest(m_ParentEntity->GetSystemAddress(), m_ParentEntity->GetObjectID());
auto address = m_Parent->GetSystemAddress();
Mail::HandleNotificationRequest(address, m_Parent->GetObjectID());
}
}
void MissionComponent::CompleteMission(const uint32_t missionId, const bool skipChecks, const bool yieldRewards) {
// Get the mission first
auto* mission = this->GetMission(missionId);
if (!mission) {
if (mission == nullptr) {
AcceptMission(missionId, skipChecks);
mission = this->GetMission(missionId);
if (!mission) return;
if (mission == nullptr) {
return;
}
}
//If this mission is not repeatable, and already completed, we stop here.
if (mission->IsComplete() && !mission->IsRepeatable()) return;
if (mission->IsComplete() && !mission->IsRepeatable()) {
return;
}
mission->Complete(yieldRewards);
}
void MissionComponent::RemoveMission(const uint32_t missionId) {
auto missionItr = m_Missions.find(missionId);
void MissionComponent::RemoveMission(uint32_t missionId) {
auto* mission = this->GetMission(missionId);
if (missionItr == m_Missions.end()) return;
if (mission == nullptr) {
return;
}
delete missionItr->second;
delete mission;
m_Missions.erase(missionItr);
m_Missions.erase(missionId);
}
void MissionComponent::Progress(const eMissionTaskType type, const int32_t value, const LWOOBJID& associate, const std::string& targets, const int32_t count, const bool ignoreAchievements) {
for (const auto& [missionId, mission] : m_Missions) {
if (!mission) continue;
void MissionComponent::Progress(eMissionTaskType type, int32_t value, LWOOBJID associate, const std::string& targets, int32_t count, bool ignoreAchievements) {
for (const auto& pair : m_Missions) {
auto* mission = pair.second;
if (mission->IsAchievement() && ignoreAchievements) continue;
if (mission->IsComplete()) continue;
@@ -127,57 +163,73 @@ void MissionComponent::Progress(const eMissionTaskType type, const int32_t value
void MissionComponent::ForceProgress(const uint32_t missionId, const uint32_t taskId, const int32_t value, const bool acceptMission) {
auto* mission = GetMission(missionId);
if (!mission) {
if (!acceptMission) return;
if (mission == nullptr) {
if (!acceptMission) {
return;
}
AcceptMission(missionId);
mission = GetMission(missionId);
if (!mission) return;
if (mission == nullptr) {
return;
}
}
std::for_each(mission->GetTasks().begin(), mission->GetTasks().end(), [value, taskId](MissionTask* element) {
if (element->GetClientInfo().uid != taskId) return;
for (auto* element : mission->GetTasks()) {
if (element->GetClientInfo().uid != taskId) continue;
element->AddProgress(value);
});
}
if (!mission->IsComplete()) mission->CheckCompletion();
if (!mission->IsComplete()) {
mission->CheckCompletion();
}
}
void MissionComponent::ForceProgressTaskType(const uint32_t missionId, const uint32_t taskType, const int32_t value, const bool acceptMission) {
auto* mission = GetMission(missionId);
if (!mission) {
if (!acceptMission) return;
if (mission == nullptr) {
if (!acceptMission) {
return;
}
CDMissions missionInfo;
if (!GetMissionInfo(missionId, missionInfo)) return;
if (!GetMissionInfo(missionId, missionInfo)) {
return;
}
if (missionInfo.isMission) return;
if (missionInfo.isMission) {
return;
}
AcceptMission(missionId);
mission = GetMission(missionId);
if (!mission) return;
if (mission == nullptr) {
return;
}
}
std::for_each(mission->GetTasks().begin(), mission->GetTasks().end(), [value, taskType](MissionTask* element) {
if (element->GetType() != static_cast<eMissionTaskType>(taskType)) return;
for (auto* element : mission->GetTasks()) {
if (element->GetType() != static_cast<eMissionTaskType>(taskType)) continue;
element->AddProgress(value);
});
}
if (!mission->IsComplete()) mission->CheckCompletion();
if (!mission->IsComplete()) {
mission->CheckCompletion();
}
}
void MissionComponent::ForceProgressValue(const uint32_t missionId, const uint32_t taskType, const int32_t value, const bool acceptMission) {
void MissionComponent::ForceProgressValue(uint32_t missionId, uint32_t taskType, int32_t value, bool acceptMission) {
auto* mission = GetMission(missionId);
if (!mission) {
if (mission == nullptr) {
if (!acceptMission) {
return;
}
@@ -212,11 +264,11 @@ void MissionComponent::ForceProgressValue(const uint32_t missionId, const uint32
}
}
bool MissionComponent::GetMissionInfo(const uint32_t missionId, CDMissions& result) const {
bool MissionComponent::GetMissionInfo(uint32_t missionId, CDMissions& result) {
auto* missionsTable = CDClientManager::Instance().GetTable<CDMissionsTable>();
const auto missions = missionsTable->Query([=](const CDMissions& entry) {
return entry.id == static_cast<uint32_t>(missionId);
return entry.id == static_cast<int>(missionId);
});
if (missions.empty()) {
@@ -228,7 +280,10 @@ bool MissionComponent::GetMissionInfo(const uint32_t missionId, CDMissions& resu
return true;
}
bool MissionComponent::LookForAchievements(const eMissionTaskType type, const int32_t value, const bool progress, const LWOOBJID& associate, const std::string& targets, const int32_t count) {
#define MISSION_NEW_METHOD
bool MissionComponent::LookForAchievements(eMissionTaskType type, int32_t value, bool progress, LWOOBJID associate, const std::string& targets, int32_t count) {
#ifdef MISSION_NEW_METHOD
// Query for achievments, using the cache
const auto& result = QueryAchievements(type, value, targets);
@@ -236,7 +291,9 @@ bool MissionComponent::LookForAchievements(const eMissionTaskType type, const in
for (const uint32_t missionID : result) {
// Check if we already have this achievement
if (GetMission(missionID)) continue;
if (GetMission(missionID) != nullptr) {
continue;
}
// Check if we can accept this achievement
if (!MissionPrerequisites::CanAccept(missionID, m_Missions)) {
@@ -254,15 +311,87 @@ bool MissionComponent::LookForAchievements(const eMissionTaskType type, const in
any = true;
if (!progress) continue;
if (progress) {
// Progress mission to bring it up to speed
instance->Progress(type, value, associate, targets, count);
}
}
return any;
#else
auto* missionTasksTable = CDClientManager::Instance().GetTable<CDMissionTasksTable>();
auto* missionsTable = CDClientManager::Instance().GetTable<CDMissionsTable>();
auto tasks = missionTasksTable->Query([=](const CDMissionTasks& entry) {
return entry.taskType == static_cast<unsigned>(type);
});
auto any = false;
for (const auto& task : tasks) {
if (GetMission(task.id) != nullptr) {
continue;
}
const auto missionEntries = missionsTable->Query([=](const CDMissions& entry) {
return entry.id == static_cast<int>(task.id) && !entry.isMission;
});
if (missionEntries.empty()) {
continue;
}
const auto mission = missionEntries[0];
if (mission.isMission || !MissionPrerequisites::CanAccept(mission.id, m_Missions)) {
continue;
}
if (task.target != value && task.targetGroup != targets) {
auto stream = std::istringstream(task.targetGroup);
std::string token;
auto found = false;
while (std::getline(stream, token, ',')) {
try {
const auto target = std::stoul(token);
found = target == value;
if (found) {
break;
}
} catch (std::invalid_argument& exception) {
Game::logger->Log("MissionComponent", "Failed to parse target (%s): (%s)!", token.c_str(), exception.what());
}
}
if (!found) {
continue;
}
}
auto* instance = new Mission(this, mission.id);
m_Missions.insert_or_assign(mission.id, instance);
if (instance->IsMission()) instance->SetUniqueMissionOrderID(++m_LastUsedMissionOrderUID);
instance->Accept();
any = true;
if (progress) {
instance->Progress(type, value, associate, targets, count);
}
}
return any;
#endif
}
const std::vector<uint32_t>& MissionComponent::QueryAchievements(const eMissionTaskType type, const int32_t value, const std::string& targets) {
const std::vector<uint32_t>& MissionComponent::QueryAchievements(eMissionTaskType type, int32_t value, const std::string targets) {
// Create a hash which represent this query for achievements
AchievementCacheKey toFind;
toFind.SetType(type);
@@ -291,68 +420,95 @@ const std::vector<uint32_t>& MissionComponent::QueryAchievements(const eMissionT
// Seek the assosicated mission
auto foundMission = false;
const auto& cdMission = missionsTable->GetByMissionID(task.id, foundMission);
const auto& mission = missionsTable->GetByMissionID(task.id, foundMission);
if (!foundMission || cdMission.isMission) continue;
if (!foundMission || mission.isMission) {
continue;
}
// Compare the easy values
if (task.target == value || task.targetGroup == targets) {
result.push_back(cdMission.id);
result.push_back(mission.id);
continue;
}
// Compare the target group, array separated by ','
for (const auto& possibleMissionStr : GeneralUtils::SplitString(task.targetGroup, ',')) {
uint32_t possibleMission;
if (GeneralUtils::TryParse(possibleMissionStr, possibleMission) && possibleMission == value) {
result.push_back(cdMission.id);
auto stream = std::istringstream(task.targetGroup);
std::string token;
break;
while (std::getline(stream, token, ',')) {
try {
if (std::stoi(token) == value) {
result.push_back(mission.id);
continue;
}
} catch (std::invalid_argument& exception) {
// Ignored
}
}
}
// Insert into cache and return the inserted value.
return m_AchievementCache.insert_or_assign(toFind, result).first->second;
// Insert into cache
m_AchievementCache.insert_or_assign(toFind, result);
return m_AchievementCache.find(toFind)->second;
}
bool MissionComponent::RequiresItem(const LOT lot) {
auto query = CDClientDatabase::CreatePreppedStmt("SELECT type FROM Objects WHERE id = ?;");
query.bind(1, static_cast<int>(lot));
auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT type FROM Objects WHERE id = ?;");
query.bind(1, (int)lot);
auto result = query.execQuery();
if (result.eof()) return false;
if (result.eof()) {
return false;
}
if (!result.fieldIsNull(0)) {
const auto type = std::string(result.getStringField(0));
if (type == "Powerup") return true;
result.finalize();
if (type == "Powerup") {
return true;
}
}
for (const auto& [missionId, mission] : m_Missions) {
if (mission->IsComplete()) continue;
result.finalize();
for (const auto& pair : m_Missions) {
auto* mission = pair.second;
if (mission->IsComplete()) {
continue;
}
for (auto* task : mission->GetTasks()) {
if (task->IsComplete() || task->GetType() != eMissionTaskType::GATHER) {
continue;
}
if (!task->InAllTargets(lot)) continue;
if (!task->InAllTargets(lot)) {
continue;
}
return true;
}
}
return LookForAchievements(eMissionTaskType::GATHER, lot, false);
const auto required = LookForAchievements(eMissionTaskType::GATHER, lot, false);
return required;
}
void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
if (!doc) return;
if (doc == nullptr) return;
auto* mis = doc->FirstChildElement("obj")->FirstChildElement("mis");
if (!mis) return;
if (mis == nullptr) return;
auto* cur = mis->FirstChildElement("cur");
auto* done = mis->FirstChildElement("done");
@@ -360,7 +516,7 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
auto* doneM = done->FirstChildElement();
while (doneM) {
uint32_t missionId;
int missionId;
doneM->QueryAttribute("id", &missionId);
@@ -377,7 +533,7 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
uint32_t missionOrder{};
while (currentM) {
uint32_t missionId;
int missionId;
currentM->QueryAttribute("id", &missionId);
@@ -387,7 +543,7 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) {
mission->SetUniqueMissionOrderID(missionOrder);
m_LastUsedMissionOrderUID = std::max(missionOrder, m_LastUsedMissionOrderUID);
if (missionOrder > m_LastUsedMissionOrderUID) m_LastUsedMissionOrderUID = missionOrder;
}
currentM = currentM->NextSiblingElement();
@@ -398,7 +554,7 @@ void MissionComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
if (!doc) return;
if (doc == nullptr) return;
auto shouldInsertMis = false;
@@ -406,7 +562,7 @@ void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
auto* mis = obj->FirstChildElement("mis");
if (!mis) {
if (mis == nullptr) {
mis = doc->NewElement("mis");
shouldInsertMis = true;
@@ -417,33 +573,50 @@ void MissionComponent::UpdateXml(tinyxml2::XMLDocument* doc) {
auto* done = doc->NewElement("done");
auto* cur = doc->NewElement("cur");
for (const auto& [missionId, mission] : m_Missions) {
if (!mission) continue;
for (const auto& pair : m_Missions) {
auto* mission = pair.second;
if (mission) {
const auto complete = mission->IsComplete();
auto* missionElement = doc->NewElement("m");
auto* m = doc->NewElement("m");
if (!complete && mission->IsMission()) missionElement->SetAttribute("o", mission->GetUniqueMissionOrderID());
if (complete) {
mission->UpdateXml(m);
mission->UpdateXml(missionElement);
done->LinkEndChild(m);
cur->LinkEndChild(missionElement);
continue;
}
if (mission->IsMission()) m->SetAttribute("o", mission->GetUniqueMissionOrderID());
mission->UpdateXml(m);
cur->LinkEndChild(m);
}
}
mis->InsertFirstChild(done);
mis->InsertEndChild(cur);
if (shouldInsertMis) obj->LinkEndChild(mis);
if (shouldInsertMis) {
obj->LinkEndChild(mis);
}
}
void MissionComponent::AddCollectible(const int32_t collectibleID) {
if (!HasCollectible(collectibleID)) m_Collectibles.push_back(collectibleID);
void MissionComponent::AddCollectible(int32_t collectibleID) {
// Check if this collectible is already in the list
if (HasCollectible(collectibleID)) {
return;
}
m_Collectibles.push_back(collectibleID);
}
bool MissionComponent::HasCollectible(const int32_t collectibleID) const {
bool MissionComponent::HasCollectible(int32_t collectibleID) {
return std::find(m_Collectibles.begin(), m_Collectibles.end(), collectibleID) != m_Collectibles.end();
}
bool MissionComponent::HasMission(const uint32_t missionId) const {
bool MissionComponent::HasMission(uint32_t missionId) {
return GetMission(missionId) != nullptr;
}

View File

@@ -24,10 +24,10 @@ class AchievementCacheKey;
* The mission inventory of an entity. Tracks mission state for each mission that can be accepted and allows for
* progression of each of the mission task types (see eMissionTaskType).
*/
class MissionComponent final : public Component
class MissionComponent : public Component
{
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::MISSION;
static const eReplicaComponentType ComponentType = eReplicaComponentType::MISSION;
explicit MissionComponent(Entity* parent);
~MissionComponent() override;
@@ -39,35 +39,35 @@ public:
* Returns all the missions for this entity, mapped by mission ID
* @return the missions for this entity, mapped by mission ID
*/
const std::unordered_map<uint32_t, Mission*>& GetMissions() const { return m_Missions; };
const std::unordered_map<uint32_t, Mission*>& GetMissions() const;
/**
* Returns the mission for the given mission ID, if it exists
* @param missionId the id of the mission to get
* @return the mission for the given mission ID
*/
Mission* GetMission(const uint32_t missionId) const;
Mission* GetMission(uint32_t missionId) const;
/**
* Returns the current state of the entities progression for the mission of the specified ID
* @param missionId the ID of the mission to get the mission state for
* @return the mission state of the mission specified by the ID
*/
eMissionState GetMissionState(const uint32_t missionId) const;
eMissionState GetMissionState(uint32_t missionId) const;
/**
* Checks if the entity has all the requirements for accepting the mission specified by the ID.
* @param missionId the mission ID to check for if the character may accept it
* @return whether this entity can accept the mission represented by the given mission ID
*/
bool CanAccept(const uint32_t missionId) const;
bool CanAccept(uint32_t missionId) const;
/**
* Accepts the mission specified by the ID, if the entity may accept it. Also stores it in the mission inventory.
* @param missionId the ID of the mission to accept
* @param skipChecks skips the checks for the mission prerequisites
*/
void AcceptMission(const uint32_t missionId, const bool skipChecks = false);
void AcceptMission(uint32_t missionId, bool skipChecks = false);
/**
* Completes the mission specified by the given ID, if the entity has fulfilled all progress requirements.
@@ -75,13 +75,13 @@ public:
* @param skipChecks skips the checks for having completed all of the mission tasks
* @param yieldRewards whether to yield mission rewards, currently unused
*/
void CompleteMission(const uint32_t missionId, const bool skipChecks = false, const bool yieldRewards = true);
void CompleteMission(uint32_t missionId, bool skipChecks = false, bool yieldRewards = true);
/**
* Removes the mission from the entities' mission chain. Not used for normal gameplay but useful for debugging.
* @param missionId the ID of the mission to remove
*/
void RemoveMission(const uint32_t missionId);
void RemoveMission(uint32_t missionId);
/**
* Attempts to progress mission tasks for a given type using parameters to progress. Note that this function is
@@ -94,7 +94,7 @@ public:
* @param count the number to progress by, for example the number of items
* @param ignoreAchievements do not progress achievements
*/
void Progress(const eMissionTaskType type, const int32_t value, const LWOOBJID& associate = 0, const std::string& targets = "", const int32_t count = 1, const bool ignoreAchievements = false);
void Progress(eMissionTaskType type, int32_t value, LWOOBJID associate = 0, const std::string& targets = "", int32_t count = 1, bool ignoreAchievements = false);
/**
* Forces progression for a mission and task, ignoring checks
@@ -103,7 +103,7 @@ public:
* @param value the value to progress with
* @param acceptMission accept the mission if it was not already accepted
*/
void ForceProgress(const uint32_t missionId, const uint32_t taskId, const int32_t value, const bool acceptMission = true);
void ForceProgress(uint32_t missionId, uint32_t taskId, int32_t value, bool acceptMission = true);
/**
* Forces progress for all tasks of a certain type that belong to the same mission
@@ -112,7 +112,7 @@ public:
* @param value the value to progress with
* @param acceptMission accept the mission if it wasn't already
*/
void ForceProgressTaskType(const uint32_t missionId, const uint32_t taskType, const int32_t value, const bool acceptMission = true);
void ForceProgressTaskType(uint32_t missionId, uint32_t taskType, int32_t value, bool acceptMission = true);
/**
* Force progresses by checking the value and progressing by 1
@@ -121,7 +121,7 @@ public:
* @param value the value to check the mission values before progressing
* @param acceptMission accept the mission if it wasn't already
*/
void ForceProgressValue(const uint32_t missionId, const uint32_t taskType, const int32_t value, const bool acceptMission = true);
void ForceProgressValue(uint32_t missionId, uint32_t taskType, int32_t value, bool acceptMission = true);
/**
* Returns client database mission information for a mission
@@ -129,7 +129,7 @@ public:
* @param result the result to store the information in
* @return true if the information was succesfully retrieved, false otherwise
*/
bool GetMissionInfo(const uint32_t missionId, CDMissions& result) const;
bool GetMissionInfo(uint32_t missionId, CDMissions& result);
/**
* Checks if there's any achievements we might be able to accept for the given parameters
@@ -141,34 +141,34 @@ public:
* @param count the number of values to progress by (differs by task type)
* @return true if a achievement was accepted, false otherwise
*/
bool LookForAchievements(const eMissionTaskType type, const int32_t value, const bool progress = true, const LWOOBJID& associate = LWOOBJID_EMPTY, const std::string& targets = "", const int32_t count = 1);
bool LookForAchievements(eMissionTaskType type, int32_t value, bool progress = true, LWOOBJID associate = LWOOBJID_EMPTY, const std::string& targets = "", int32_t count = 1);
/**
* Checks if there's a mission active that requires the collection of the specified LOT
* @param lot the LOT to check for
* @return if there's a mission active that requires the collection of the specified LOT
*/
bool RequiresItem(const LOT lot);
bool RequiresItem(LOT lot);
/**
* Collects a collectable for the entity, unrendering it for the entity
* @param collectibleID the ID of the collectable to add
*/
void AddCollectible(const int32_t collectibleID);
void AddCollectible(int32_t collectibleID);
/**
* Checks if the entity already has a collectible of the specified ID
* @param collectibleID the ID of the collectible to check
* @return if the entity already has a collectible of the specified ID
*/
bool HasCollectible(const int32_t collectibleID) const;
bool HasCollectible(int32_t collectibleID);
/**
* Checks if the entity has a certain mission in its inventory
* @param missionId the ID of the mission to check
* @return if the entity has a certain mission in its inventory
*/
bool HasMission(const uint32_t missionId) const;
bool HasMission(uint32_t missionId);
private:
/**
@@ -189,7 +189,7 @@ private:
* @param targets optional targets to progress with
* @return list of mission IDs (achievements) that can be progressed for the given parameters
*/
static const std::vector<uint32_t>& QueryAchievements(const eMissionTaskType type, const int32_t value, const std::string& targets);
static const std::vector<uint32_t>& QueryAchievements(eMissionTaskType type, int32_t value, const std::string targets);
/**
* As achievements can be hard to query, we here store a list of all the mission IDs that can be unlocked for a

View File

@@ -1,6 +1,6 @@
/*
* Darkflame Universe
* Copyright 2023
* Copyright 2019
*/
#include <sstream>
@@ -18,24 +18,59 @@
#include "CDComponentsRegistryTable.h"
MissionOfferComponent::MissionOfferComponent(Entity* parent, const int32_t componentId) : Component(parent) {
m_ComponentId = componentId;
OfferedMission::OfferedMission(const uint32_t missionId, const bool offersMission, const bool acceptsMission) {
this->missionId = missionId;
this->offersMission = offersMission;
this->acceptsMission = acceptsMission;
}
void MissionOfferComponent::LoadTemplateData() {
if (m_ComponentId == -1) return;
uint32_t OfferedMission::GetMissionId() const {
return this->missionId;
}
bool OfferedMission::GetOfferMission() const {
return this->offersMission;
}
bool OfferedMission::GetAcceptMission() const {
return this->acceptsMission;
}
//------------------------ MissionOfferComponent below ------------------------
MissionOfferComponent::MissionOfferComponent(Entity* parent, const LOT parentLot) : Component(parent) {
auto* compRegistryTable = CDClientManager::Instance().GetTable<CDComponentsRegistryTable>();
auto value = compRegistryTable->GetByIDAndType(parentLot, eReplicaComponentType::MISSION_OFFER, -1);
if (value != -1) {
const uint32_t componentId = value;
// Now lookup the missions in the MissionNPCComponent table
auto* missionNpcComponentTable = CDClientManager::Instance().GetTable<CDMissionNPCComponentTable>();
auto missions = missionNpcComponentTable->Query([=](const CDMissionNPCComponent& entry) {
return entry.id == static_cast<int32_t>(m_ComponentId);
return entry.id == static_cast<unsigned>(componentId);
});
for (const auto& mission : missions) {
this->offeredMissions.emplace_back(
std::make_unique<OfferedMission>(mission.missionID, mission.offersMission, mission.acceptsMission)
);
for (auto& mission : missions) {
auto* offeredMission = new OfferedMission(mission.missionID, mission.offersMission, mission.acceptsMission);
this->offeredMissions.push_back(offeredMission);
}
}
}
MissionOfferComponent::~MissionOfferComponent() {
for (auto* mission : this->offeredMissions) {
if (mission) {
delete mission;
mission = nullptr;
}
}
offeredMissions.clear();
}
void MissionOfferComponent::OnUse(Entity* originator) {
@@ -44,7 +79,7 @@ void MissionOfferComponent::OnUse(Entity* originator) {
void MissionOfferComponent::OfferMissions(Entity* entity, const uint32_t specifiedMissionId) {
// First, get the entity's MissionComponent. If there is not one, then we cannot offer missions to this entity.
auto* missionComponent = entity->GetComponent<MissionComponent>();
auto* missionComponent = static_cast<MissionComponent*>(entity->GetComponent(eReplicaComponentType::MISSION));
if (!missionComponent) {
Game::logger->Log("MissionOfferComponent", "Unable to get mission component for Entity %llu", entity->GetObjectID());
@@ -53,36 +88,35 @@ void MissionOfferComponent::OfferMissions(Entity* entity, const uint32_t specifi
std::vector<uint32_t> offered{};
CDMissions missionInfo{};
CDMissions info{};
if (specifiedMissionId > 0 && !Mission::IsValidMission(specifiedMissionId, missionInfo)) {
if (specifiedMissionId > 0 && !Mission::IsValidMission(specifiedMissionId, info)) {
return;
}
for (const auto& offeredMission : offeredMissions) {
if (specifiedMissionId > 0 && (offeredMission->GetMissionId() != specifiedMissionId && !missionInfo.isRandom)) {
for (auto* offeredMission : this->offeredMissions) {
if (specifiedMissionId > 0) {
if (offeredMission->GetMissionId() != specifiedMissionId && !info.isRandom) {
continue;
}
}
// First, check if we already have the mission
const auto missionId = offeredMission->GetMissionId();
auto* mission = missionComponent->GetMission(missionId);
if (mission) {
if (mission != nullptr) {
if (specifiedMissionId <= 0) {
// Handles the odd case where the offer object should not display the mission again
if (!mission->IsComplete() &&
mission->GetClientInfo().offer_objectID == m_ParentEntity->GetLOT() &&
mission->GetClientInfo().target_objectID != m_ParentEntity->GetLOT() &&
mission->IsFetchMission()) {
if (!mission->IsComplete() && mission->GetClientInfo().offer_objectID == m_Parent->GetLOT() && mission->GetClientInfo().target_objectID != m_Parent->GetLOT() && mission->IsFetchMission()) {
continue;
}
}
// We have the mission, if it is not complete, offer it
if (mission->IsActive() || mission->IsReadyToComplete()) {
GameMessages::SendOfferMission(entity->GetObjectID(), entity->GetSystemAddress(), missionId, m_ParentEntity->GetObjectID());
GameMessages::SendOfferMission(entity->GetObjectID(), entity->GetSystemAddress(), missionId, m_Parent->GetObjectID());
offered.push_back(missionId);
@@ -93,30 +127,50 @@ void MissionOfferComponent::OfferMissions(Entity* entity, const uint32_t specifi
const auto canAccept = MissionPrerequisites::CanAccept(missionId, missionComponent->GetMissions());
// Mission has not yet been accepted - check the prereqs
if (!canAccept || !Mission::IsValidMission(missionId, missionInfo)) continue;
if (!canAccept)
continue;
// This means the mission is part of a random pool of missions.
if (missionInfo.isRandom && missionInfo.randomPool.empty()) continue;
if (!Mission::IsValidMission(missionId, info)) {
continue;
}
if (missionInfo.isRandom && !missionInfo.randomPool.empty()) {
auto randomMissionPoolStr = GeneralUtils::SplitString(missionInfo.randomPool, ',');
const auto& randomPool = info.randomPool;
const auto isRandom = info.isRandom;
std::vector<uint32_t> randomMissions;
for (const auto& randomMissionStr : randomMissionPoolStr) {
uint32_t randomMission;
if (GeneralUtils::TryParse(randomMissionStr, randomMission)) randomMissions.push_back(randomMission);
if (isRandom && randomPool.empty()) // This means the mission is part of a random pool of missions.
{
continue;
}
if (isRandom && !randomPool.empty()) {
std::istringstream stream(randomPool);
std::string token;
std::vector<uint32_t> randomMissionPool;
while (std::getline(stream, token, ',')) {
try {
const auto value = std::stoul(token);
randomMissionPool.push_back(value);
} catch (std::invalid_argument& exception) {
Game::logger->Log("MissionOfferComponent", "Failed to parse value (%s): (%s)!", token.c_str(), exception.what());
}
}
if (specifiedMissionId > 0) {
if (std::find(randomMissions.begin(), randomMissions.end(), specifiedMissionId) != randomMissions.end() &&
MissionPrerequisites::CanAccept(specifiedMissionId, missionComponent->GetMissions())) {
GameMessages::SendOfferMission(entity->GetObjectID(), entity->GetSystemAddress(), specifiedMissionId, m_ParentEntity->GetObjectID());
const auto& iter = std::find(randomMissionPool.begin(), randomMissionPool.end(), specifiedMissionId);
if (iter != randomMissionPool.end() && MissionPrerequisites::CanAccept(specifiedMissionId, missionComponent->GetMissions())) {
GameMessages::SendOfferMission(entity->GetObjectID(), entity->GetSystemAddress(), specifiedMissionId, m_Parent->GetObjectID());
return;
}
}
std::vector<uint32_t> canAcceptPool;
for (const auto& sample : randomMissions) {
for (const auto sample : randomMissionPool) {
const auto state = missionComponent->GetMissionState(sample);
if (state == eMissionState::ACTIVE ||
@@ -126,29 +180,31 @@ void MissionOfferComponent::OfferMissions(Entity* entity, const uint32_t specifi
sample == specifiedMissionId) {
mission = missionComponent->GetMission(sample);
if (!mission || mission->IsAchievement()) continue;
if (mission == nullptr || mission->IsAchievement()) {
continue;
}
GameMessages::SendOfferMission(entity->GetObjectID(), entity->GetSystemAddress(), sample, m_ParentEntity->GetObjectID());
GameMessages::SendOfferMission(entity->GetObjectID(), entity->GetSystemAddress(), sample, m_Parent->GetObjectID());
canAcceptPool.clear();
break;
}
if (std::find(offered.begin(), offered.end(), sample) == offered.end() &&
MissionPrerequisites::CanAccept(sample, missionComponent->GetMissions())) {
if (std::find(offered.begin(), offered.end(), sample) == offered.end() && MissionPrerequisites::CanAccept(sample, missionComponent->GetMissions())) {
canAcceptPool.push_back(sample);
}
}
// If the mission is already active or we already completed one of them today
if (canAcceptPool.empty()) continue;
if (canAcceptPool.empty())
continue;
const auto selected = canAcceptPool[GeneralUtils::GenerateRandomNumber<int32_t>(0, canAcceptPool.size() - 1)];
const auto selected = canAcceptPool[GeneralUtils::GenerateRandomNumber<int>(0, canAcceptPool.size() - 1)];
GameMessages::SendOfferMission(entity->GetObjectID(), entity->GetSystemAddress(), selected, m_ParentEntity->GetObjectID());
GameMessages::SendOfferMission(entity->GetObjectID(), entity->GetSystemAddress(), selected, m_Parent->GetObjectID());
} else if (std::find(offered.begin(), offered.end(), missionId) == offered.end() && offeredMission->GetOfferMission()) {
GameMessages::SendOfferMission(entity->GetObjectID(), entity->GetSystemAddress(), missionId, m_ParentEntity->GetObjectID());
GameMessages::SendOfferMission(entity->GetObjectID(), entity->GetSystemAddress(), missionId, m_Parent->GetObjectID());
}
}
}

View File

@@ -1,18 +1,15 @@
/*
* Darkflame Universe
* Copyright 2023
* Copyright 2019
*/
#ifndef __MISSIONOFFERCOMPONENT_H__
#define __MISSIONOFFERCOMPONENT_H__
#pragma once
#include <memory>
#include <vector>
#include <stdint.h>
#ifndef MISSIONOFFERCOMPONENT_H
#define MISSIONOFFERCOMPONENT_H
#include "dCommonVars.h"
#include "Component.h"
#include <vector>
#include <stdint.h>
#include "eReplicaComponentType.h"
class Entity;
@@ -21,29 +18,25 @@ class Entity;
* Light wrapper around missions that may be offered by an entity
*/
struct OfferedMission {
OfferedMission(const uint32_t missionId, const bool offersMission, const bool acceptsMission) {
this->missionId = missionId;
this->offersMission = offersMission;
this->acceptsMission = acceptsMission;
};
OfferedMission(uint32_t missionId, bool offersMission, bool acceptsMission);
/**
* Returns the ID of the mission
* @return the ID of the mission
*/
uint32_t GetMissionId() const { return missionId; };
uint32_t GetMissionId() const;
/**
* Returns if this mission is offered by the entity
* @return true if this mission is offered by the entity, false otherwise
*/
bool GetOfferMission() const { return offersMission; };
bool GetOfferMission() const;
/**
* Returns if this mission may be accepted by the entity (currently unused)
* @return true if this mission may be accepted by the entity, false otherwise
*/
bool GetAcceptMission() const { return acceptsMission; };
bool GetAcceptMission() const;
private:
@@ -66,13 +59,12 @@ private:
/**
* Allows entities to offer missions to other entities, depending on their mission inventory progression.
*/
class MissionOfferComponent final : public Component {
class MissionOfferComponent : public Component {
public:
inline static const eReplicaComponentType ComponentType = eReplicaComponentType::MISSION_OFFER;
static const eReplicaComponentType ComponentType = eReplicaComponentType::MISSION_OFFER;
MissionOfferComponent(Entity* parent, const int32_t componentId = -1);
void LoadTemplateData() override;
MissionOfferComponent(Entity* parent, LOT parentLot);
~MissionOfferComponent() override;
/**
* Handles the OnUse event triggered by some entity, determines which missions to show based on what they may
@@ -93,9 +85,7 @@ private:
/**
* The missions this entity has to offer
*/
std::vector<std::unique_ptr<OfferedMission>> offeredMissions;
int32_t m_ComponentId;
std::vector<OfferedMission*> offeredMissions;
};
#endif // __MISSIONOFFERCOMPONENT_H__
#endif // MISSIONOFFERCOMPONENT_H

View File

@@ -1,23 +0,0 @@
#include "ModelBehaviorComponent.h"
#include "Entity.h"
#include "ePhysicsBehaviorType.h"
ModelBehaviorComponent::ModelBehaviorComponent(Entity* parent) : Component(parent) {
m_DirtyModelInfo = true;
m_IsPickable = false;
m_PhysicsType = ePhysicsBehaviorType::STANDARD;
m_OriginalPosition = m_ParentEntity->GetDefaultPosition();
m_OriginalRotation = m_ParentEntity->GetDefaultRotation();
}
void ModelBehaviorComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
outBitStream->Write(m_DirtyModelInfo || bIsInitialUpdate);
if (m_DirtyModelInfo || bIsInitialUpdate) {
outBitStream->Write(m_IsPickable);
outBitStream->Write(m_PhysicsType);
outBitStream->Write(m_OriginalPosition);
outBitStream->Write(m_OriginalRotation);
if (!bIsInitialUpdate) m_DirtyModelInfo = false;
}
}

View File

@@ -0,0 +1,31 @@
#include "ModelComponent.h"
#include "Entity.h"
ModelComponent::ModelComponent(Entity* parent) : Component(parent) {
m_OriginalPosition = m_Parent->GetDefaultPosition();
m_OriginalRotation = m_Parent->GetDefaultRotation();
m_userModelID = m_Parent->GetVarAs<LWOOBJID>(u"userModelID");
}
void ModelComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags) {
// ItemComponent Serialization. Pets do not get this serialization.
if (!m_Parent->HasComponent(eReplicaComponentType::PET)) {
outBitStream->Write1();
outBitStream->Write<LWOOBJID>(m_userModelID != LWOOBJID_EMPTY ? m_userModelID : m_Parent->GetObjectID());
outBitStream->Write<int>(0);
outBitStream->Write0();
}
//actual model component:
outBitStream->Write1(); // Yes we are writing model info
outBitStream->Write0(); // Is pickable
outBitStream->Write<uint32_t>(2); // Physics type
outBitStream->Write(m_OriginalPosition); // Original position
outBitStream->Write(m_OriginalRotation); // Original rotation
outBitStream->Write1(); // We are writing behavior info
outBitStream->Write<uint32_t>(0); // Number of behaviors
outBitStream->Write1(); // Is this model paused
if (bIsInitialUpdate) outBitStream->Write0(); // We are not writing model editing info
}

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