Compare commits

..

15 Commits

Author SHA1 Message Date
dbc02b12b1 WIP 2023-11-13 22:39:19 -06:00
jadebenn
68e5737b74 Fix: Pets can no longer dig treasure without completing Bella Pepper's "Lost Tags" mission (#1287)
* Added mission check to pet digs

* Little formatting change

* More formatting clean-up
2023-11-13 02:41:27 -08:00
jadebenn
411dce7457 Adding damage cooldown/"invincibility frames" as in Live (#1276)
* Added cooldown handling

* Made most of the logs hidden outside of debug mode

* removed weird submodule

* kill this phantom submodule

* updated to reflect reviewed feedback

* Added IsCooldownImmune() method to DestroyableComponent

* friggin typo

* Implemented non-pending changes and added cooldown immunity functions to DestroyableComponentTests

* add trailing linebreak

* another typo :(

* flipped cooldown test order (not leaving immune)

* Clean up comment and add DestroyableComponent test
2023-11-12 05:53:03 -06:00
Nathan Ogden
2c9a98313a chore: Notes for running as system service (#1252)
* Note for running as system service
Note for running as system service

* Additional detailing of linux service.

* Added darkflame.service file

changed readme to reference new file
2023-11-08 19:15:46 -08:00
David Markowitz
d1dc9f5403 fix: add Onedrive log message (#1269)
* fix: Onedrive log message

* Update README.md
2023-11-08 17:56:59 -08:00
David Markowitz
52b5994b98 Ugc: Add subkey for rockets and cars (#1266)
Tested that, if a user has followed the guide and turned UGCUSE3DSERVICES from 1 to 0, the do not get booted to login for having a rocket with a subkey in their inventory.

Add bouncer logic

Ugc: Use random Id
2023-11-08 12:18:02 -06:00
Gie "Max" Vanommeslaeghe
4c10faa852 Merge pull request #1265 from DarkflameUniverse/ugc_patch
fix: Ugc Remove async and second id usage
2023-11-07 19:09:37 +01:00
Gie "Max" Vanommeslaeghe
070d4a1fa8 Merge pull request #1267 from DarkflameUniverse/dummyfix
fix: Nexus Tower Combat Challenge exploding dummy
2023-11-07 19:03:12 +01:00
David Markowitz
6795dd189c CombatChallenge: Fix exploding dummy 2023-11-06 14:49:53 -08:00
David Markowitz
f40fce7711 Ugc: Remove async and second id usage 2023-11-06 01:41:28 -08:00
David Markowitz
797abb176a ChatFilter: Fix incorrect segment highlighting (#1255) 2023-11-05 17:19:26 -06:00
David Markowitz
0f9e951162 Database: Use null for accounts (#1253) 2023-11-05 01:00:31 -08:00
David Markowitz
cea0b98edf WorldServer: Fix crash from deleting last char (#1254)
Revert "WorldServer: Fix crash from deleting last char"

This reverts commit d9adafa1fef0ac88ed2b3b8ca6b97c2421a603e2.

Update WorldServer.cpp
2023-11-05 01:00:19 -08:00
David Markowitz
1e2d5605eb fix: Navmesh updates to Frostburgh and Forbidden Valley (#1251)
* Navmesh: Add Frostburgh mesh

* Navmesh: Add Frostburgh mesh

Also edit Forbidden Valley mesh
2023-11-04 10:42:39 -05:00
David Markowitz
32cf111810 Script: Fix incorrect kill method (#1248) 2023-11-04 10:42:28 -05:00
20 changed files with 366 additions and 188 deletions

View File

@@ -224,6 +224,44 @@ sudo setcap 'cap_net_bind_service=+ep' AuthServer
``` ```
and then go to `build/masterconfig.ini` and change `use_sudo_auth` to 0. and then go to `build/masterconfig.ini` and change `use_sudo_auth` to 0.
### Linux Service
If you are running this on a linux based system, it will use your terminal to run the program interactively, preventing you using it for other tasks and requiring it to be open to run the server.
_Note: You could use screen or tmux instead for virtual terminals_
To run the server non-interactively, we can use a systemctl service by copying the following file:
```shell
cp ./systemd.example /etc/systemd/system/darkflame.service
```
Make sure to edit the file in `/etc/systemd/system/darkflame.service` and change the:
- `User` and `Group` to the user that runs the darkflame server.
- `ExecPath` to the full file path of the server executable.
To register, enable and start the service use the following commands:
- Reload the systemd manager configuration to make it aware of the new service file:
```shell
systemctl daemon-reload
```
- Start the service:
```shell
systemctl start darkflame.service
```
- Enable OR disable the service to start on boot using:
```shell
systemctl enable darkflame.service
systemctl disable darkflame.service
```
- Verify that the service is running without errors:
```shell
systemctl status darkflame.service
```
- You can also restart, stop, or check the logs of the service using journalctl
```shell
systemctl restart darkflame.service
systemctl stop darkflame.service
journalctl -xeu darkflame.service
```
### First admin user ### First admin user
Run `MasterServer -a` to get prompted to create an admin account. This method is only intended for the system administrator as a means to get started, do NOT use this method to create accounts for other users! Run `MasterServer -a` to get prompted to create an admin account. This method is only intended for the system administrator as a means to get started, do NOT use this method to create accounts for other users!
@@ -285,6 +323,7 @@ Below are known good SHA256 checksums of the client:
* `0d862f71eedcadc4494c4358261669721b40b2131101cbd6ef476c5a6ec6775b` (unpacked client, includes extra locales, rar compressed) * `0d862f71eedcadc4494c4358261669721b40b2131101cbd6ef476c5a6ec6775b` (unpacked client, includes extra locales, rar compressed)
If the returned hash matches one of the lines above then you can continue with setting up the server. If you are using a fully downloaded and complete client from live, then it will work, but the hash above may not match. Otherwise you must obtain a full install of LEGO® Universe 1.10.64. If the returned hash matches one of the lines above then you can continue with setting up the server. If you are using a fully downloaded and complete client from live, then it will work, but the hash above may not match. Otherwise you must obtain a full install of LEGO® Universe 1.10.64.
You must also make absolutely sure your LEGO Universe client is not in a Windows OneDrive. DLU is not and will not support a client being stored in a OneDrive, so ensure you have moved the client outside of that location.
### Darkflame Universe Client ### Darkflame Universe Client
Darkflame Universe clients identify themselves using a higher version number than the regular live clients out there. Darkflame Universe clients identify themselves using a higher version number than the regular live clients out there.

View File

@@ -144,7 +144,7 @@ std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::
listOfBadSegments.emplace_back(position, originalSegment.length()); listOfBadSegments.emplace_back(position, originalSegment.length());
} }
position += segment.length() + 1; position += originalSegment.length() + 1;
} }
return listOfBadSegments; return listOfBadSegments;

View File

@@ -15,7 +15,9 @@ enum class eCharacterVersion : uint32_t {
// Fixes vault size value // Fixes vault size value
VAULT_SIZE, VAULT_SIZE,
// Fixes speed base value in level component // Fixes speed base value in level component
UP_TO_DATE, // will become SPEED_BASE SPEED_BASE,
// Fixes explore missions that were corruped
UP_TO_DATE // Will become EXPLORE
}; };
#endif //!__ECHARACTERVERSION__H__ #endif //!__ECHARACTERVERSION__H__

View File

@@ -3,6 +3,8 @@
#include "Game.h" #include "Game.h"
#include "Logger.h" #include "Logger.h"
#include "EntityManager.h" #include "EntityManager.h"
#include "dZoneManager.h"
#include "WorldConfig.h"
#include "DestroyableComponent.h" #include "DestroyableComponent.h"
#include "BehaviorContext.h" #include "BehaviorContext.h"
#include "eBasicAttackSuccessTypes.h" #include "eBasicAttackSuccessTypes.h"
@@ -13,8 +15,15 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent != nullptr) { if (destroyableComponent != nullptr) {
PlayFx(u"onhit", entity->GetObjectID()); PlayFx(u"onhit", entity->GetObjectID()); //This damage animation doesn't seem to play consistently
destroyableComponent->Damage(this->m_MaxDamage, context->originator, context->skillID); destroyableComponent->Damage(this->m_MaxDamage, context->originator, context->skillID);
//Handle player damage cooldown
if (entity->IsPlayer() && !this->m_DontApplyImmune) {
const float immunityTime = Game::zoneManager->GetWorldConfig()->globalImmunityTime;
destroyableComponent->SetDamageCooldownTimer(immunityTime);
LOG_DEBUG("Target targetEntity %llu took damage, setting damage cooldown timer to %f s", branch.target, immunityTime);
}
} }
this->m_OnSuccess->Handle(context, bitStream, branch); this->m_OnSuccess->Handle(context, bitStream, branch);
@@ -72,6 +81,7 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
} }
if (isImmune) { if (isImmune) {
LOG_DEBUG("Target targetEntity %llu is immune!", branch.target);
this->m_OnFailImmune->Handle(context, bitStream, branch); this->m_OnFailImmune->Handle(context, bitStream, branch);
return; return;
} }
@@ -178,11 +188,15 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
return; return;
} }
const bool isImmune = destroyableComponent->IsImmune(); const float immunityTime = Game::zoneManager->GetWorldConfig()->globalImmunityTime;
LOG_DEBUG("Damage cooldown timer currently %f s", destroyableComponent->GetDamageCooldownTimer());
const bool isImmune = (destroyableComponent->IsImmune()) || (destroyableComponent->IsCooldownImmune());
bitStream->Write(isImmune); bitStream->Write(isImmune);
if (isImmune) { if (isImmune) {
LOG_DEBUG("Target targetEntity %llu is immune!", branch.target);
this->m_OnFailImmune->Calculate(context, bitStream, branch); this->m_OnFailImmune->Calculate(context, bitStream, branch);
return; return;
} }
@@ -203,6 +217,12 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
bitStream->Write(isSuccess); bitStream->Write(isSuccess);
//Handle player damage cooldown
if (isSuccess && targetEntity->IsPlayer() && !this->m_DontApplyImmune) {
destroyableComponent->SetDamageCooldownTimer(immunityTime);
LOG_DEBUG("Target targetEntity %llu took damage, setting damage cooldown timer to %f s", branch.target, immunityTime);
}
eBasicAttackSuccessTypes successState = eBasicAttackSuccessTypes::FAILIMMUNE; eBasicAttackSuccessTypes successState = eBasicAttackSuccessTypes::FAILIMMUNE;
if (isSuccess) { if (isSuccess) {
if (healthDamageDealt >= 1) { if (healthDamageDealt >= 1) {
@@ -236,6 +256,8 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
} }
void BasicAttackBehavior::Load() { void BasicAttackBehavior::Load() {
this->m_DontApplyImmune = GetBoolean("dont_apply_immune");
this->m_MinDamage = GetInt("min damage"); this->m_MinDamage = GetInt("min damage");
if (this->m_MinDamage == 0) this->m_MinDamage = 1; if (this->m_MinDamage == 0) this->m_MinDamage = 1;

View File

@@ -10,14 +10,14 @@ public:
/** /**
* @brief Reads a 16bit short from the bitStream and when the actual behavior handling finishes with all of its branches, the bitStream * @brief Reads a 16bit short from the bitStream and when the actual behavior handling finishes with all of its branches, the bitStream
* is then offset to after the allocated bits for this stream. * is then offset to after the allocated bits for this stream.
* *
*/ */
void DoHandleBehavior(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch); void DoHandleBehavior(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch);
/** /**
* @brief Handles a client initialized Basic Attack Behavior cast to be deserialized and verified on the server. * @brief Handles a client initialized Basic Attack Behavior cast to be deserialized and verified on the server.
* *
* @param context The Skill's Behavior context. All behaviors in the same tree share the same context * @param context The Skill's Behavior context. All behaviors in the same tree share the same context
* @param bitStream The bitStream to deserialize. BitStreams will always check their bounds before reading in a behavior * @param bitStream The bitStream to deserialize. BitStreams will always check their bounds before reading in a behavior
* and will fail gracefully if an overread is detected. * and will fail gracefully if an overread is detected.
* @param branch The context of this specific branch of the Skill Behavior. Changes based on which branch you are going down. * @param branch The context of this specific branch of the Skill Behavior. Changes based on which branch you are going down.
@@ -27,13 +27,13 @@ public:
/** /**
* @brief Writes a 16bit short to the bitStream and when the actual behavior calculation finishes with all of its branches, the number * @brief Writes a 16bit short to the bitStream and when the actual behavior calculation finishes with all of its branches, the number
* of bits used is then written to where the 16bit short initially was. * of bits used is then written to where the 16bit short initially was.
* *
*/ */
void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override; void Calculate(BehaviorContext* context, RakNet::BitStream* bitStream, BehaviorBranchContext branch) override;
/** /**
* @brief Calculates a server initialized Basic Attack Behavior cast to be serialized to the client * @brief Calculates a server initialized Basic Attack Behavior cast to be serialized to the client
* *
* @param context The Skill's Behavior context. All behaviors in the same tree share the same context * @param context The Skill's Behavior context. All behaviors in the same tree share the same context
* @param bitStream The bitStream to serialize to. * @param bitStream The bitStream to serialize to.
* @param branch The context of this specific branch of the Skill Behavior. Changes based on which branch you are going down. * @param branch The context of this specific branch of the Skill Behavior. Changes based on which branch you are going down.
@@ -44,10 +44,12 @@ public:
* @brief Loads this Behaviors parameters from the database. For this behavior specifically: * @brief Loads this Behaviors parameters from the database. For this behavior specifically:
* max and min damage will always be the same. If min is less than max, they are both set to max. * max and min damage will always be the same. If min is less than max, they are both set to max.
* If an action is not in the database, then no action is taken for that result. * If an action is not in the database, then no action is taken for that result.
* *
*/ */
void Load() override; void Load() override;
private: private:
bool m_DontApplyImmune;
uint32_t m_MinDamage; uint32_t m_MinDamage;
uint32_t m_MaxDamage; uint32_t m_MaxDamage;

View File

@@ -73,6 +73,8 @@ DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
m_ImmuneToQuickbuildInterruptCount = 0; m_ImmuneToQuickbuildInterruptCount = 0;
m_ImmuneToPullToPointCount = 0; m_ImmuneToPullToPointCount = 0;
m_DeathBehavior = -1; m_DeathBehavior = -1;
m_DamageCooldownTimer = 0.0f;
} }
DestroyableComponent::~DestroyableComponent() { DestroyableComponent::~DestroyableComponent() {
@@ -179,6 +181,10 @@ void DestroyableComponent::Serialize(RakNet::BitStream* outBitStream, bool bIsIn
} }
} }
void DestroyableComponent::Update(float deltaTime) {
m_DamageCooldownTimer -= deltaTime;
}
void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) { void DestroyableComponent::LoadFromXml(tinyxml2::XMLDocument* doc) {
tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest"); tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest");
if (!dest) { if (!dest) {
@@ -409,7 +415,7 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
} }
bool DestroyableComponent::IsEnemy(const Entity* other) const { bool DestroyableComponent::IsEnemy(const Entity* other) const {
if (m_Parent->IsPlayer() && other->IsPlayer()){ if (m_Parent->IsPlayer() && other->IsPlayer()) {
auto* thisCharacterComponent = m_Parent->GetComponent<CharacterComponent>(); auto* thisCharacterComponent = m_Parent->GetComponent<CharacterComponent>();
if (!thisCharacterComponent) return false; if (!thisCharacterComponent) return false;
auto* otherCharacterComponent = other->GetComponent<CharacterComponent>(); auto* otherCharacterComponent = other->GetComponent<CharacterComponent>();
@@ -464,6 +470,10 @@ bool DestroyableComponent::IsImmune() const {
return m_IsGMImmune || m_ImmuneToBasicAttackCount > 0; return m_IsGMImmune || m_ImmuneToBasicAttackCount > 0;
} }
bool DestroyableComponent::IsCooldownImmune() const {
return m_DamageCooldownTimer > 0.0f;
}
bool DestroyableComponent::IsKnockbackImmune() const { bool DestroyableComponent::IsKnockbackImmune() const {
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>(); auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
auto* inventoryComponent = m_Parent->GetComponent<InventoryComponent>(); auto* inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
@@ -546,7 +556,8 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
return; return;
} }
if (IsImmune()) { if (IsImmune() || IsCooldownImmune()) {
LOG_DEBUG("Target targetEntity %llu is immune!", m_Parent->GetObjectID()); //Immune is succesfully proc'd
return; return;
} }
@@ -634,9 +645,9 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
} }
//check if hardcore mode is enabled //check if hardcore mode is enabled
if (Game::entityManager->GetHardcoreMode()) { if (Game::entityManager->GetHardcoreMode()) {
DoHardcoreModeDrops(source); DoHardcoreModeDrops(source);
} }
Smash(source, eKillType::VIOLENT, u"", skillID); Smash(source, eKillType::VIOLENT, u"", skillID);
} }
@@ -796,16 +807,16 @@ void DestroyableComponent::SetFaction(int32_t factionID, bool ignoreChecks) {
} }
void DestroyableComponent::SetStatusImmunity( void DestroyableComponent::SetStatusImmunity(
const eStateChangeType state, const eStateChangeType state,
const bool bImmuneToBasicAttack, const bool bImmuneToBasicAttack,
const bool bImmuneToDamageOverTime, const bool bImmuneToDamageOverTime,
const bool bImmuneToKnockback, const bool bImmuneToKnockback,
const bool bImmuneToInterrupt, const bool bImmuneToInterrupt,
const bool bImmuneToSpeed, const bool bImmuneToSpeed,
const bool bImmuneToImaginationGain, const bool bImmuneToImaginationGain,
const bool bImmuneToImaginationLoss, const bool bImmuneToImaginationLoss,
const bool bImmuneToQuickbuildInterrupt, const bool bImmuneToQuickbuildInterrupt,
const bool bImmuneToPullToPoint) { const bool bImmuneToPullToPoint) {
if (state == eStateChangeType::POP) { if (state == eStateChangeType::POP) {
if (bImmuneToBasicAttack && m_ImmuneToBasicAttackCount > 0) m_ImmuneToBasicAttackCount -= 1; if (bImmuneToBasicAttack && m_ImmuneToBasicAttackCount > 0) m_ImmuneToBasicAttackCount -= 1;
@@ -818,7 +829,7 @@ void DestroyableComponent::SetStatusImmunity(
if (bImmuneToQuickbuildInterrupt && m_ImmuneToQuickbuildInterruptCount > 0) m_ImmuneToQuickbuildInterruptCount -= 1; if (bImmuneToQuickbuildInterrupt && m_ImmuneToQuickbuildInterruptCount > 0) m_ImmuneToQuickbuildInterruptCount -= 1;
if (bImmuneToPullToPoint && m_ImmuneToPullToPointCount > 0) m_ImmuneToPullToPointCount -= 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 (bImmuneToBasicAttack) m_ImmuneToBasicAttackCount += 1;
if (bImmuneToDamageOverTime) m_ImmuneToDamageOverTimeCount += 1; if (bImmuneToDamageOverTime) m_ImmuneToDamageOverTimeCount += 1;
if (bImmuneToKnockback) m_ImmuneToKnockbackCount += 1; if (bImmuneToKnockback) m_ImmuneToKnockbackCount += 1;
@@ -945,7 +956,7 @@ void DestroyableComponent::AddOnHitCallback(const std::function<void(Entity*)>&
m_OnHitCallbacks.push_back(callback); m_OnHitCallbacks.push_back(callback);
} }
void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source){ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
//check if this is a player: //check if this is a player:
if (m_Parent->IsPlayer()) { if (m_Parent->IsPlayer()) {
//remove hardcore_lose_uscore_on_death_percent from the player's uscore: //remove hardcore_lose_uscore_on_death_percent from the player's uscore:
@@ -963,9 +974,9 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source){
if (inventory) { if (inventory) {
//get the items inventory: //get the items inventory:
auto items = inventory->GetInventory(eInventoryType::ITEMS); auto items = inventory->GetInventory(eInventoryType::ITEMS);
if (items){ if (items) {
auto itemMap = items->GetItems(); auto itemMap = items->GetItems();
if (!itemMap.empty()){ if (!itemMap.empty()) {
for (const auto& item : itemMap) { for (const auto& item : itemMap) {
//drop the item: //drop the item:
if (!item.second) continue; if (!item.second) continue;

View File

@@ -24,6 +24,7 @@ public:
DestroyableComponent(Entity* parentEntity); DestroyableComponent(Entity* parentEntity);
~DestroyableComponent() override; ~DestroyableComponent() override;
void Update(float deltaTime) override;
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override; void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override;
void LoadFromXml(tinyxml2::XMLDocument* doc) override; void LoadFromXml(tinyxml2::XMLDocument* doc) override;
void UpdateXml(tinyxml2::XMLDocument* doc) override; void UpdateXml(tinyxml2::XMLDocument* doc) override;
@@ -166,6 +167,11 @@ public:
*/ */
bool IsImmune() const; bool IsImmune() const;
/**
* @return whether this entity is currently immune to attacks due to a damage cooldown period
*/
bool IsCooldownImmune() const;
/** /**
* Sets if this entity has GM immunity, making it not killable * Sets if this entity has GM immunity, making it not killable
* @param value the GM immunity of this entity * @param value the GM immunity of this entity
@@ -406,18 +412,23 @@ public:
); );
// Getters for status immunities // Getters for status immunities
const bool GetImmuneToBasicAttack() {return m_ImmuneToBasicAttackCount > 0;}; const bool GetImmuneToBasicAttack() { return m_ImmuneToBasicAttackCount > 0; };
const bool GetImmuneToDamageOverTime() {return m_ImmuneToDamageOverTimeCount > 0;}; const bool GetImmuneToDamageOverTime() { return m_ImmuneToDamageOverTimeCount > 0; };
const bool GetImmuneToKnockback() {return m_ImmuneToKnockbackCount > 0;}; const bool GetImmuneToKnockback() { return m_ImmuneToKnockbackCount > 0; };
const bool GetImmuneToInterrupt() {return m_ImmuneToInterruptCount > 0;}; const bool GetImmuneToInterrupt() { return m_ImmuneToInterruptCount > 0; };
const bool GetImmuneToSpeed() {return m_ImmuneToSpeedCount > 0;}; const bool GetImmuneToSpeed() { return m_ImmuneToSpeedCount > 0; };
const bool GetImmuneToImaginationGain() {return m_ImmuneToImaginationGainCount > 0;}; const bool GetImmuneToImaginationGain() { return m_ImmuneToImaginationGainCount > 0; };
const bool GetImmuneToImaginationLoss() {return m_ImmuneToImaginationLossCount > 0;}; const bool GetImmuneToImaginationLoss() { return m_ImmuneToImaginationLossCount > 0; };
const bool GetImmuneToQuickbuildInterrupt() {return m_ImmuneToQuickbuildInterruptCount > 0;}; const bool GetImmuneToQuickbuildInterrupt() { return m_ImmuneToQuickbuildInterruptCount > 0; };
const bool GetImmuneToPullToPoint() {return m_ImmuneToPullToPointCount > 0;}; const bool GetImmuneToPullToPoint() { return m_ImmuneToPullToPointCount > 0; };
int32_t GetDeathBehavior() const { return m_DeathBehavior; } // Damage cooldown setters/getters
void SetDamageCooldownTimer(float value) { m_DamageCooldownTimer = value; }
float GetDamageCooldownTimer() { return m_DamageCooldownTimer; }
// Death behavior setters/getters
void SetDeathBehavior(int32_t value) { m_DeathBehavior = value; } void SetDeathBehavior(int32_t value) { m_DeathBehavior = value; }
int32_t GetDeathBehavior() const { return m_DeathBehavior; }
/** /**
* Utility to reset all stats to the default stats based on items and completed missions * Utility to reset all stats to the default stats based on items and completed missions
@@ -605,6 +616,11 @@ private:
* Death behavior type. If 0, the client plays a death animation as opposed to a smash animation. * Death behavior type. If 0, the client plays a death animation as opposed to a smash animation.
*/ */
int32_t m_DeathBehavior; int32_t m_DeathBehavior;
/**
* Damage immunity cooldown timer. Set to a value that then counts down to create a damage cooldown for players
*/
float m_DamageCooldownTimer;
}; };
#endif // DESTROYABLECOMPONENT_H #endif // DESTROYABLECOMPONENT_H

View File

@@ -620,3 +620,7 @@ bool MissionComponent::HasCollectible(int32_t collectibleID) {
bool MissionComponent::HasMission(uint32_t missionId) { bool MissionComponent::HasMission(uint32_t missionId) {
return GetMission(missionId) != nullptr; return GetMission(missionId) != nullptr;
} }
void FixExplorerMissions() {
}

View File

@@ -170,6 +170,9 @@ public:
*/ */
bool HasMission(uint32_t missionId); bool HasMission(uint32_t missionId);
void FixExplorerMissions();
private: private:
/** /**
* All the missions owned by this entity, mapped by mission ID * All the missions owned by this entity, mapped by mission ID

View File

@@ -29,6 +29,7 @@
#include "RenderComponent.h" #include "RenderComponent.h"
#include "eObjectBits.h" #include "eObjectBits.h"
#include "eGameMasterLevel.h" #include "eGameMasterLevel.h"
#include "eMissionState.h"
std::unordered_map<LOT, PetComponent::PetPuzzleData> PetComponent::buildCache{}; std::unordered_map<LOT, PetComponent::PetPuzzleData> PetComponent::buildCache{};
std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::currentActivities{}; std::unordered_map<LWOOBJID, LWOOBJID> PetComponent::currentActivities{};
@@ -439,9 +440,15 @@ void PetComponent::Update(float deltaTime) {
} }
} }
auto* missionComponent = owner->GetComponent<MissionComponent>();
if (!missionComponent) return;
// Determine if the "Lost Tags" mission has been completed and digging has been unlocked
const bool digUnlocked = missionComponent->GetMissionState(842) == eMissionState::COMPLETE;
Entity* closestTresure = PetDigServer::GetClosestTresure(position); Entity* closestTresure = PetDigServer::GetClosestTresure(position);
if (closestTresure != nullptr) { if (closestTresure != nullptr && digUnlocked) {
// Skeleton Dragon Pat special case for bone digging // Skeleton Dragon Pat special case for bone digging
if (closestTresure->GetLOT() == 12192 && m_Parent->GetLOT() != 13067) { if (closestTresure->GetLOT() == 12192 && m_Parent->GetLOT() != 13067) {
goto skipTresure; goto skipTresure;

View File

@@ -2523,15 +2523,9 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent
inStream->Read(sd0Size); inStream->Read(sd0Size);
std::shared_ptr<char[]> sd0Data(new char[sd0Size]); std::shared_ptr<char[]> sd0Data(new char[sd0Size]);
if (sd0Data == nullptr) { if (sd0Data == nullptr) return;
return;
}
for (uint32_t i = 0; i < sd0Size; ++i) { inStream->ReadAlignedBytes(reinterpret_cast<unsigned char*>(sd0Data.get()), sd0Size);
uint8_t c;
inStream->Read(c);
sd0Data[i] = c;
}
uint32_t timeTaken; uint32_t timeTaken;
inStream->Read(timeTaken); inStream->Read(timeTaken);
@@ -2565,165 +2559,156 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent
//We runs this in async because the http library here is blocking, meaning it'll halt the thread. //We runs this in async because the http library here is blocking, meaning it'll halt the thread.
//But we don't want the server to go unresponsive, because then the client would disconnect. //But we don't want the server to go unresponsive, because then the client would disconnect.
auto returnVal = std::async(std::launch::async, [&]() {
//We need to get a new ID for our model first: //We need to get a new ID for our model first:
ObjectIDManager::Instance()->RequestPersistentID([=](uint32_t newID) { ObjectIDManager::Instance()->RequestPersistentID([=](uint32_t newID) {
LWOOBJID newIDL = newID; LWOOBJID newIDL = newID;
GeneralUtils::SetBit(newIDL, eObjectBits::CHARACTER); GeneralUtils::SetBit(newIDL, eObjectBits::CHARACTER);
GeneralUtils::SetBit(newIDL, eObjectBits::PERSISTENT); GeneralUtils::SetBit(newIDL, eObjectBits::PERSISTENT);
ObjectIDManager::Instance()->RequestPersistentID([=](uint32_t blueprintIDSmall) { uint32_t blueprintIDSmall = ObjectIDManager::Instance()->GenerateRandomObjectID();
blueprintIDSmall = ObjectIDManager::Instance()->GenerateRandomObjectID(); LWOOBJID blueprintID = blueprintIDSmall;
LWOOBJID blueprintID = blueprintIDSmall; GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER);
GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER); GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT);
GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT);
//We need to get the propertyID: (stolen from Wincent's propertyManagementComp) //We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID(); const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
const auto zoneId = worldId.GetMapID(); const auto zoneId = worldId.GetMapID();
const auto cloneId = worldId.GetCloneID(); const auto cloneId = worldId.GetCloneID();
auto query = CDClientDatabase::CreatePreppedStmt( auto query = CDClientDatabase::CreatePreppedStmt(
"SELECT id FROM PropertyTemplate WHERE mapID = ?;"); "SELECT id FROM PropertyTemplate WHERE mapID = ?;");
query.bind(1, (int)zoneId); query.bind(1, (int)zoneId);
auto result = query.execQuery(); auto result = query.execQuery();
if (result.eof() || result.fieldIsNull(0)) return; if (result.eof() || result.fieldIsNull(0)) return;
int templateId = result.getIntField(0); int templateId = result.getIntField(0);
result.finalize(); auto* propertyLookup = Database::CreatePreppedStmt("SELECT * FROM properties WHERE template_id = ? AND clone_id = ?;");
auto* propertyLookup = Database::CreatePreppedStmt("SELECT * FROM properties WHERE template_id = ? AND clone_id = ?;"); propertyLookup->setInt(1, templateId);
propertyLookup->setInt64(2, cloneId);
propertyLookup->setInt(1, templateId); auto* propertyEntry = propertyLookup->executeQuery();
propertyLookup->setInt64(2, cloneId); uint64_t propertyId = 0;
auto* propertyEntry = propertyLookup->executeQuery(); if (propertyEntry->next()) {
uint64_t propertyId = 0; propertyId = propertyEntry->getUInt64(1);
}
if (propertyEntry->next()) { delete propertyEntry;
propertyId = propertyEntry->getUInt64(1); delete propertyLookup;
}
delete propertyEntry; //Insert into ugc:
delete propertyLookup; auto ugcs = Database::CreatePreppedStmt("INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)");
ugcs->setUInt(1, blueprintIDSmall);
ugcs->setInt(2, entity->GetParentUser()->GetAccountID());
ugcs->setInt(3, entity->GetCharacter()->GetID());
ugcs->setInt(4, 0);
//Insert into ugc: //whacky stream biz
auto ugcs = Database::CreatePreppedStmt("INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`) VALUES (?,?,?,?,?,?,?)"); std::string s(sd0Data.get(), sd0Size);
ugcs->setUInt(1, blueprintIDSmall); std::istringstream iss(s);
ugcs->setInt(2, entity->GetParentUser()->GetAccountID());
ugcs->setInt(3, entity->GetCharacter()->GetID());
ugcs->setInt(4, 0);
//whacky stream biz ugcs->setBlob(5, &iss);
std::string s(sd0Data.get(), sd0Size); ugcs->setBoolean(6, false);
std::istringstream iss(s); ugcs->setString(7, "weedeater.lxfml");
std::istream& stream = iss; ugcs->execute();
delete ugcs;
ugcs->setBlob(5, &iss); //Insert into the db as a BBB model:
ugcs->setBoolean(6, false); auto* stmt = Database::CreatePreppedStmt("INSERT INTO `properties_contents` VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
ugcs->setString(7, "weedeater.lxfml"); stmt->setUInt64(1, newIDL);
ugcs->execute(); stmt->setUInt64(2, propertyId);
delete ugcs; stmt->setUInt(3, blueprintIDSmall);
stmt->setUInt(4, 14); // 14 is the lot the BBB models use
stmt->setDouble(5, 0.0f); // x
stmt->setDouble(6, 0.0f); // y
stmt->setDouble(7, 0.0f); // z
stmt->setDouble(8, 0.0f); // rx
stmt->setDouble(9, 0.0f); // ry
stmt->setDouble(10, 0.0f); // rz
stmt->setDouble(11, 0.0f); // rw
stmt->setString(12, "Objects_14_name"); // Model name. TODO make this customizable
stmt->setString(13, ""); // Model description. TODO implement this.
stmt->setDouble(14, 0); // behavior 1. TODO implement this.
stmt->setDouble(15, 0); // behavior 2. TODO implement this.
stmt->setDouble(16, 0); // behavior 3. TODO implement this.
stmt->setDouble(17, 0); // behavior 4. TODO implement this.
stmt->setDouble(18, 0); // behavior 5. TODO implement this.
stmt->execute();
delete stmt;
//Insert into the db as a BBB model: /*
auto* stmt = Database::CreatePreppedStmt("INSERT INTO `properties_contents` VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream.
stmt->setUInt64(1, newIDL); (or you uncomment the lxfml decomp stuff above)
stmt->setUInt64(2, propertyId); */
stmt->setUInt(3, blueprintIDSmall);
stmt->setUInt(4, 14); // 14 is the lot the BBB models use
stmt->setDouble(5, 0.0f); // x
stmt->setDouble(6, 0.0f); // y
stmt->setDouble(7, 0.0f); // z
stmt->setDouble(8, 0.0f); // rx
stmt->setDouble(9, 0.0f); // ry
stmt->setDouble(10, 0.0f); // rz
stmt->setDouble(11, 0.0f); // rw
stmt->setString(12, "Objects_14_name"); // Model name. TODO make this customizable
stmt->setString(13, ""); // Model description. TODO implement this.
stmt->setDouble(14, 0); // behavior 1. TODO implement this.
stmt->setDouble(15, 0); // behavior 2. TODO implement this.
stmt->setDouble(16, 0); // behavior 3. TODO implement this.
stmt->setDouble(17, 0); // behavior 4. TODO implement this.
stmt->setDouble(18, 0); // behavior 5. TODO implement this.
stmt->execute();
delete stmt;
/* ////Send off to UGC for processing, if enabled:
Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream. //if (Game::config->GetValue("ugc_remote") == "1") {
(or you uncomment the lxfml decomp stuff above) // std::string ugcIP = Game::config->GetValue("ugc_ip");
*/ // int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));
////Send off to UGC for processing, if enabled: // httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^
//if (Game::config->GetValue("ugc_remote") == "1") {
// std::string ugcIP = Game::config->GetValue("ugc_ip");
// int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));
// httplib::Client cli(ugcIP, ugcPort); //connect to UGC HTTP server using our config above ^ // //Send out a request:
// std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml";
// cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml");
// //Send out a request: // //When the "put" above returns, it means that the UGC HTTP server is done processing our model &
// std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml"; // //the nif, hkx and checksum files are ready to be downloaded from cache.
// cli.Put(request.c_str(), lxfml.c_str(), "text/lxfml"); //}
// //When the "put" above returns, it means that the UGC HTTP server is done processing our model & //Tell the client their model is saved: (this causes us to actually pop out of our current state):
// //the nif, hkx and checksum files are ready to be downloaded from cache. CBITSTREAM;
//} BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::BLUEPRINT_SAVE_RESPONSE);
bitStream.Write(localId);
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
bitStream.Write<uint32_t>(1);
bitStream.Write(blueprintID);
//Tell the client their model is saved: (this causes us to actually pop out of our current state): bitStream.Write<uint32_t>(sd0Size);
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::BLUEPRINT_SAVE_RESPONSE);
bitStream.Write(localId);
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
bitStream.Write<uint32_t>(1);
bitStream.Write(blueprintID);
bitStream.Write<uint32_t>(sd0Size); bitStream.WriteAlignedBytes(reinterpret_cast<unsigned char*>(sd0Data.get()), sd0Size);
for (size_t i = 0; i < sd0Size; ++i) { SEND_PACKET;
bitStream.Write(sd0Data[i]);
}
SEND_PACKET; //Now we have to construct this object:
//Now we have to construct this object: EntityInfo info;
info.lot = 14;
info.pos = {};
info.rot = {};
info.spawner = nullptr;
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
EntityInfo info; LDFBaseData* ldfBlueprintID = new LDFData<LWOOBJID>(u"blueprintid", blueprintID);
info.lot = 14; LDFBaseData* componentWhitelist = new LDFData<int>(u"componentWhitelist", 1);
info.pos = {}; LDFBaseData* modelType = new LDFData<int>(u"modelType", 2);
info.rot = {}; LDFBaseData* propertyObjectID = new LDFData<bool>(u"propertyObjectID", true);
info.spawner = nullptr; LDFBaseData* userModelID = new LDFData<LWOOBJID>(u"userModelID", newIDL);
info.spawnerID = entity->GetObjectID();
info.spawnerNodeID = 0;
LDFBaseData* ldfBlueprintID = new LDFData<LWOOBJID>(u"blueprintid", blueprintID); info.settings.push_back(ldfBlueprintID);
LDFBaseData* componentWhitelist = new LDFData<int>(u"componentWhitelist", 1); info.settings.push_back(componentWhitelist);
LDFBaseData* modelType = new LDFData<int>(u"modelType", 2); info.settings.push_back(modelType);
LDFBaseData* propertyObjectID = new LDFData<bool>(u"propertyObjectID", true); info.settings.push_back(propertyObjectID);
LDFBaseData* userModelID = new LDFData<LWOOBJID>(u"userModelID", newIDL); info.settings.push_back(userModelID);
info.settings.push_back(ldfBlueprintID); Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
info.settings.push_back(componentWhitelist); if (newEntity) {
info.settings.push_back(modelType); Game::entityManager->ConstructEntity(newEntity);
info.settings.push_back(propertyObjectID);
info.settings.push_back(userModelID);
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr); //Make sure the propMgmt doesn't delete our model after the server dies
if (newEntity) { //Trying to do this after the entity is constructed. Shouldn't really change anything but
Game::entityManager->ConstructEntity(newEntity); //there was an issue with builds not appearing since it was placed above ConstructEntity.
PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL);
}
//Make sure the propMgmt doesn't delete our model after the server dies });
//Trying to do this after the entity is constructed. Shouldn't really change anything but
//there was an issue with builds not appearing since it was placed above ConstructEntity.
PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL);
}
});
});
});
} }
void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) {
@@ -5625,10 +5610,18 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream* inStream, Entity*
std::vector<LDFBaseData*> config; std::vector<LDFBaseData*> config;
config.push_back(moduleAssembly); config.push_back(moduleAssembly);
LWOOBJID newIdBig;
// Make sure a subkey isnt already in use. Persistent Ids do not make sense here since this only needs to be unique for
// this character. Because of that, we just generate a random id and check for a collision.
do {
newIdBig = ObjectIDManager::Instance()->GenerateRandomObjectID();
GeneralUtils::SetBit(newIdBig, eObjectBits::CHARACTER);
} while (inv->FindItemBySubKey(newIdBig));
if (count == 3) { if (count == 3) {
inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config); inv->AddItem(6416, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig);
} else if (count == 7) { } else if (count == 7) {
inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config); inv->AddItem(8092, 1, eLootSourceType::QUICKBUILD, eInventoryType::MODELS, config, LWOOBJID_EMPTY, true, false, newIdBig);
} }
auto* missionComponent = character->GetComponent<MissionComponent>(); auto* missionComponent = character->GetComponent<MissionComponent>();

View File

@@ -29,7 +29,7 @@ void ReportCheat(User* user, const SystemAddress& sysAddr, const char* messageIf
std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt( std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt(
"INSERT INTO player_cheat_detections (account_id, name, violation_msg, violation_system_address) VALUES (?, ?, ?, ?)") "INSERT INTO player_cheat_detections (account_id, name, violation_msg, violation_system_address) VALUES (?, ?, ?, ?)")
); );
stmt->setInt(1, user ? user->GetAccountID() : 0); user ? stmt->setInt(1, user->GetAccountID()) : stmt->setNull(1, sql::DataType::INTEGER);
stmt->setString(2, user ? user->GetUsername().c_str() : "User is null."); stmt->setString(2, user ? user->GetUsername().c_str() : "User is null.");
constexpr int32_t bufSize = 4096; constexpr int32_t bufSize = 4096;

View File

@@ -247,7 +247,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
} }
if (chatCommand == "credits" || chatCommand == "info") { if (chatCommand == "credits" || chatCommand == "info") {
const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string()); const auto& customText = chatCommand == "credits" ? VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/CREDITS.md").string()) : VanityUtilities::ParseMarkdown((BinaryPathFinder::GetBinaryDir() / "vanity/INFO.md").string());
{ {
AMFArrayValue args; AMFArrayValue args;
@@ -1490,6 +1490,24 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
return; return;
} }
//Testing basic attack immunity
if (chatCommand == "attackimmune" && args.size() >= 1 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
int32_t state = false;
if (!GeneralUtils::TryParse(args[0], state)) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid state.");
return;
}
if (destroyableComponent != nullptr) {
destroyableComponent->SetIsImmune(state);
}
return;
}
if (chatCommand == "buff" && args.size() >= 2 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { if (chatCommand == "buff" && args.size() >= 2 && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
auto* buffComponent = entity->GetComponent<BuffComponent>(); auto* buffComponent = entity->GetComponent<BuffComponent>();
@@ -1843,7 +1861,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
if (chatCommand == "castskill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { if (chatCommand == "castskill" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
auto* skillComponent = entity->GetComponent<SkillComponent>(); auto* skillComponent = entity->GetComponent<SkillComponent>();
if (skillComponent){ if (skillComponent) {
uint32_t skillId; uint32_t skillId;
if (!GeneralUtils::TryParse(args[0], skillId)) { if (!GeneralUtils::TryParse(args[0], skillId)) {
@@ -1860,7 +1878,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
uint32_t skillId; uint32_t skillId;
int slot; int slot;
auto* inventoryComponent = entity->GetComponent<InventoryComponent>(); auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
if (inventoryComponent){ if (inventoryComponent) {
if (!GeneralUtils::TryParse(args[0], slot)) { if (!GeneralUtils::TryParse(args[0], slot)) {
ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot."); ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot.");
return; return;
@@ -1869,7 +1887,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill."); ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill.");
return; return;
} else { } else {
if(inventoryComponent->SetSkill(slot, skillId)) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully"); if (inventoryComponent->SetSkill(slot, skillId)) ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot successfully");
else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed"); else ChatPackets::SendSystemMessage(sysAddr, u"Set skill to slot failed");
} }
} }
@@ -1878,7 +1896,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
if (chatCommand == "setfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { if (chatCommand == "setfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent){ if (destroyableComponent) {
int32_t faction; int32_t faction;
if (!GeneralUtils::TryParse(args[0], faction)) { if (!GeneralUtils::TryParse(args[0], faction)) {
@@ -1893,7 +1911,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
if (chatCommand == "addfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) { if (chatCommand == "addfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent){ if (destroyableComponent) {
int32_t faction; int32_t faction;
if (!GeneralUtils::TryParse(args[0], faction)) { if (!GeneralUtils::TryParse(args[0], faction)) {
@@ -1908,7 +1926,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
if (chatCommand == "getfactions" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) { if (chatCommand == "getfactions" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER) {
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
if (destroyableComponent){ if (destroyableComponent) {
ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:"); ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:");
for (const auto entry : destroyableComponent->GetFactionIDs()) { for (const auto entry : destroyableComponent->GetFactionIDs()) {
ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry))); ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry)));

View File

@@ -154,7 +154,7 @@ int main(int argc, char** argv) {
Game::assetManager = new AssetManager(clientPath); Game::assetManager = new AssetManager(clientPath);
} catch (std::runtime_error& ex) { } catch (std::runtime_error& ex) {
LOG("Got an error while setting up assets: %s", ex.what()); LOG("Got an error while setting up assets: %s", ex.what());
LOG("Is the provided client_location in Windows Onedrive? If so, remove it from Onedrive.");
return EXIT_FAILURE; return EXIT_FAILURE;
} }

View File

@@ -1,6 +1,7 @@
#include "NtCombatChallengeExplodingDummy.h" #include "NtCombatChallengeExplodingDummy.h"
#include "EntityManager.h" #include "EntityManager.h"
#include "SkillComponent.h" #include "SkillComponent.h"
#include "DestroyableComponent.h"
void NtCombatChallengeExplodingDummy::OnDie(Entity* self, Entity* killer) { void NtCombatChallengeExplodingDummy::OnDie(Entity* self, Entity* killer) {
const auto challengeObjectID = self->GetVar<LWOOBJID>(u"challengeObjectID"); const auto challengeObjectID = self->GetVar<LWOOBJID>(u"challengeObjectID");
@@ -15,6 +16,17 @@ void NtCombatChallengeExplodingDummy::OnDie(Entity* self, Entity* killer) {
} }
void NtCombatChallengeExplodingDummy::OnHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) { void NtCombatChallengeExplodingDummy::OnHitOrHealResult(Entity* self, Entity* attacker, int32_t damage) {
auto* destroyableComponent = self->GetComponent<DestroyableComponent>();
auto numTimesHit = self->GetVar<int32_t>(u"numTimesHit");
if (destroyableComponent && numTimesHit == 0) {
self->SetVar<int32_t>(u"numTimesHit", 1);
destroyableComponent->SetHealth(destroyableComponent->GetHealth() / 2);
return;
} else if (numTimesHit == 2) {
return;
}
self->SetVar<int32_t>(u"numTimesHit", 2);
const auto challengeObjectID = self->GetVar<LWOOBJID>(u"challengeObjectID"); const auto challengeObjectID = self->GetVar<LWOOBJID>(u"challengeObjectID");
auto* challengeObject = Game::entityManager->GetEntity(challengeObjectID); auto* challengeObject = Game::entityManager->GetEntity(challengeObjectID);
@@ -28,5 +40,6 @@ void NtCombatChallengeExplodingDummy::OnHitOrHealResult(Entity* self, Entity* at
if (skillComponent != nullptr) { if (skillComponent != nullptr) {
skillComponent->CalculateBehavior(1338, 30875, attacker->GetObjectID()); skillComponent->CalculateBehavior(1338, 30875, attacker->GetObjectID());
} }
self->Kill(attacker); GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(self, u"camshake", self->GetObjectID(), 16.0f);
self->Smash(attacker->GetObjectID());
} }

View File

@@ -74,6 +74,7 @@
#include "ZCompression.h" #include "ZCompression.h"
#include "EntityManager.h" #include "EntityManager.h"
#include "CheatDetection.h" #include "CheatDetection.h"
#include "MissionComponent.h"
namespace Game { namespace Game {
Logger* logger = nullptr; Logger* logger = nullptr;
@@ -925,7 +926,7 @@ void HandlePacket(Packet* packet) {
//We need to delete the entity first, otherwise the char list could delete it while it exists in the world! //We need to delete the entity first, otherwise the char list could delete it while it exists in the world!
if (Game::server->GetZoneID() != 0) { if (Game::server->GetZoneID() != 0) {
auto user = UserManager::Instance()->GetUser(packet->systemAddress); auto user = UserManager::Instance()->GetUser(packet->systemAddress);
if (!user) return; if (!user || !user->GetLastUsedChar()) return;
Game::entityManager->DestroyEntity(user->GetLastUsedChar()->GetEntity()); Game::entityManager->DestroyEntity(user->GetLastUsedChar()->GetEntity());
} }
@@ -1057,6 +1058,9 @@ void HandlePacket(Packet* packet) {
auto* levelComponent = player->GetComponent<LevelProgressionComponent>(); auto* levelComponent = player->GetComponent<LevelProgressionComponent>();
if (!levelComponent) return; if (!levelComponent) return;
auto* missionComponent = player->GetComponent<MissionComponent>();
if (!missionComponent) return;
auto version = levelComponent->GetCharacterVersion(); auto version = levelComponent->GetCharacterVersion();
switch(version) { switch(version) {
case eCharacterVersion::RELEASE: case eCharacterVersion::RELEASE:
@@ -1070,9 +1074,14 @@ void HandlePacket(Packet* packet) {
player->RetroactiveVaultSize(); player->RetroactiveVaultSize();
levelComponent->SetCharacterVersion(eCharacterVersion::VAULT_SIZE); levelComponent->SetCharacterVersion(eCharacterVersion::VAULT_SIZE);
case eCharacterVersion::VAULT_SIZE: case eCharacterVersion::VAULT_SIZE:
LOG("Updaing Speedbase"); LOG("Updating Speedbase");
levelComponent->SetRetroactiveBaseSpeed(); levelComponent->SetRetroactiveBaseSpeed();
levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE); levelComponent->SetCharacterVersion(eCharacterVersion::SPEED_BASE);
case eCharacterVersion::SPEED_BASE:
LOG("Fixing Explorer missions");
missionComponent->FixExplorerMissions();
// Commented out so it runs every time for testing
// levelComponent->SetCharacterVersion(eCharacterVersion::UP_TO_DATE);
case eCharacterVersion::UP_TO_DATE: case eCharacterVersion::UP_TO_DATE:
break; break;
} }

View File

@@ -25,6 +25,7 @@
|ban|`/ban <username>`|Bans a user from the server.|4| |ban|`/ban <username>`|Bans a user from the server.|4|
|approveproperty|`/approveproperty`|Approves the property the player is currently visiting.|5| |approveproperty|`/approveproperty`|Approves the property the player is currently visiting.|5|
|mute|`/mute <username> (days) (hours)`|Mute player for the given amount of time. If no time is given, the mute is indefinite.|6| |mute|`/mute <username> (days) (hours)`|Mute player for the given amount of time. If no time is given, the mute is indefinite.|6|
|attackimmune|`/attackimmune <value>`|Sets the character's immunity to basic attacks state, where value can be one of "1", to make yourself immune to basic attack damage, or "0" to undo.|8|
|gmimmune|`/gmimmunve <value>`|Sets the character's GMImmune state, where value can be one of "1", to make yourself immune to damage, or "0" to undo.|8| |gmimmune|`/gmimmunve <value>`|Sets the character's GMImmune state, where value can be one of "1", to make yourself immune to damage, or "0" to undo.|8|
|gminvis|`/gminvis`|Toggles invisibility for the character, though it's currently a bit buggy. Requires nonzero GM Level for the character, but the account must have a GM level of 8.|8| |gminvis|`/gminvis`|Toggles invisibility for the character, though it's currently a bit buggy. Requires nonzero GM Level for the character, but the account must have a GM level of 8.|8|
|setname|`/setname <name>`|Sets a temporary name for your player. The name resets when you log out.|8| |setname|`/setname <name>`|Sets a temporary name for your player. The name resets when you log out.|8|

Binary file not shown.

19
systemd.example Normal file
View File

@@ -0,0 +1,19 @@
[Unit]
# Description of the service.
Description=Darkflame LEGO Universe Server
# Wait for network to start first before starting this service.
After=network.target
[Service]
Type=simple
# Services should have their own, dedicated, user account.
# The specific user that our service will run as, you can find your current user by issuing `id -un`
User=darkflame
# The specific group that our service will run as, you can find your current primary group by issuing `id -gn`
Group=darkflame
# Full Path to the darkflame server process
ExecStart=/PATH/TO/DarkflameServer/build/MasterServer
[Install]
# Define the behavior if the service is enabled or disabled to automatically started at boot.
WantedBy=multi-user.target

View File

@@ -536,3 +536,22 @@ TEST_F(DestroyableTest, DestroyableComponentImmunityTest) {
} }
/**
* Test the Damage cooldown timer of DestroyableComponent
*/
TEST_F(DestroyableTest, DestroyableComponentDamageCooldownTest) {
// Test the damage immune timer state (anything above 0.0f)
destroyableComponent->SetDamageCooldownTimer(1.0f);
EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 1.0f);
ASSERT_TRUE(destroyableComponent->IsCooldownImmune());
// Test that the Update() function correctly decrements the damage cooldown timer
destroyableComponent->Update(0.5f);
EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 0.5f);
ASSERT_TRUE(destroyableComponent->IsCooldownImmune());
// Test the non damage immune timer state (anything below or equal to 0.0f)
destroyableComponent->SetDamageCooldownTimer(0.0f);
EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 0.0f);
ASSERT_FALSE(destroyableComponent->IsCooldownImmune());
}