mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-12-17 12:04:27 -06:00
Compare commits
15 Commits
Fix-explod
...
issue-1208
| Author | SHA1 | Date | |
|---|---|---|---|
| dbc02b12b1 | |||
|
|
68e5737b74 | ||
|
|
411dce7457 | ||
|
|
2c9a98313a | ||
|
|
d1dc9f5403 | ||
|
|
52b5994b98 | ||
|
|
4c10faa852 | ||
|
|
070d4a1fa8 | ||
|
|
6795dd189c | ||
|
|
f40fce7711 | ||
|
|
797abb176a | ||
|
|
0f9e951162 | ||
|
|
cea0b98edf | ||
|
|
1e2d5605eb | ||
|
|
32cf111810 |
39
README.md
39
README.md
@@ -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.
|
||||
|
||||
### 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
|
||||
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)
|
||||
|
||||
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 clients identify themselves using a higher version number than the regular live clients out there.
|
||||
|
||||
@@ -144,7 +144,7 @@ std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::
|
||||
listOfBadSegments.emplace_back(position, originalSegment.length());
|
||||
}
|
||||
|
||||
position += segment.length() + 1;
|
||||
position += originalSegment.length() + 1;
|
||||
}
|
||||
|
||||
return listOfBadSegments;
|
||||
|
||||
@@ -15,7 +15,9 @@ enum class eCharacterVersion : uint32_t {
|
||||
// Fixes vault size value
|
||||
VAULT_SIZE,
|
||||
// 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__
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "Game.h"
|
||||
#include "Logger.h"
|
||||
#include "EntityManager.h"
|
||||
#include "dZoneManager.h"
|
||||
#include "WorldConfig.h"
|
||||
#include "DestroyableComponent.h"
|
||||
#include "BehaviorContext.h"
|
||||
#include "eBasicAttackSuccessTypes.h"
|
||||
@@ -13,8 +15,15 @@ void BasicAttackBehavior::Handle(BehaviorContext* context, RakNet::BitStream* bi
|
||||
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
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);
|
||||
|
||||
//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);
|
||||
@@ -72,6 +81,7 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
|
||||
}
|
||||
|
||||
if (isImmune) {
|
||||
LOG_DEBUG("Target targetEntity %llu is immune!", branch.target);
|
||||
this->m_OnFailImmune->Handle(context, bitStream, branch);
|
||||
return;
|
||||
}
|
||||
@@ -178,11 +188,15 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
|
||||
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);
|
||||
|
||||
if (isImmune) {
|
||||
LOG_DEBUG("Target targetEntity %llu is immune!", branch.target);
|
||||
this->m_OnFailImmune->Calculate(context, bitStream, branch);
|
||||
return;
|
||||
}
|
||||
@@ -203,6 +217,12 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
|
||||
|
||||
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;
|
||||
if (isSuccess) {
|
||||
if (healthDamageDealt >= 1) {
|
||||
@@ -236,6 +256,8 @@ void BasicAttackBehavior::DoBehaviorCalculation(BehaviorContext* context, RakNet
|
||||
}
|
||||
|
||||
void BasicAttackBehavior::Load() {
|
||||
this->m_DontApplyImmune = GetBoolean("dont_apply_immune");
|
||||
|
||||
this->m_MinDamage = GetInt("min damage");
|
||||
if (this->m_MinDamage == 0) this->m_MinDamage = 1;
|
||||
|
||||
|
||||
@@ -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
|
||||
* is then offset to after the allocated bits for this stream.
|
||||
*
|
||||
*
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
* 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.
|
||||
@@ -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
|
||||
* of bits used is then written to where the 16bit short initially was.
|
||||
*
|
||||
*
|
||||
*/
|
||||
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
|
||||
*
|
||||
*
|
||||
* @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 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:
|
||||
* 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.
|
||||
*
|
||||
*
|
||||
*/
|
||||
void Load() override;
|
||||
private:
|
||||
bool m_DontApplyImmune;
|
||||
|
||||
uint32_t m_MinDamage;
|
||||
|
||||
uint32_t m_MaxDamage;
|
||||
|
||||
@@ -73,6 +73,8 @@ DestroyableComponent::DestroyableComponent(Entity* parent) : Component(parent) {
|
||||
m_ImmuneToQuickbuildInterruptCount = 0;
|
||||
m_ImmuneToPullToPointCount = 0;
|
||||
m_DeathBehavior = -1;
|
||||
|
||||
m_DamageCooldownTimer = 0.0f;
|
||||
}
|
||||
|
||||
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) {
|
||||
tinyxml2::XMLElement* dest = doc->FirstChildElement("obj")->FirstChildElement("dest");
|
||||
if (!dest) {
|
||||
@@ -409,7 +415,7 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
|
||||
}
|
||||
|
||||
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>();
|
||||
if (!thisCharacterComponent) return false;
|
||||
auto* otherCharacterComponent = other->GetComponent<CharacterComponent>();
|
||||
@@ -464,6 +470,10 @@ bool DestroyableComponent::IsImmune() const {
|
||||
return m_IsGMImmune || m_ImmuneToBasicAttackCount > 0;
|
||||
}
|
||||
|
||||
bool DestroyableComponent::IsCooldownImmune() const {
|
||||
return m_DamageCooldownTimer > 0.0f;
|
||||
}
|
||||
|
||||
bool DestroyableComponent::IsKnockbackImmune() const {
|
||||
auto* characterComponent = m_Parent->GetComponent<CharacterComponent>();
|
||||
auto* inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
|
||||
@@ -546,7 +556,8 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsImmune()) {
|
||||
if (IsImmune() || IsCooldownImmune()) {
|
||||
LOG_DEBUG("Target targetEntity %llu is immune!", m_Parent->GetObjectID()); //Immune is succesfully proc'd
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -634,9 +645,9 @@ void DestroyableComponent::Damage(uint32_t damage, const LWOOBJID source, uint32
|
||||
}
|
||||
|
||||
//check if hardcore mode is enabled
|
||||
if (Game::entityManager->GetHardcoreMode()) {
|
||||
if (Game::entityManager->GetHardcoreMode()) {
|
||||
DoHardcoreModeDrops(source);
|
||||
}
|
||||
}
|
||||
|
||||
Smash(source, eKillType::VIOLENT, u"", skillID);
|
||||
}
|
||||
@@ -796,16 +807,16 @@ void DestroyableComponent::SetFaction(int32_t factionID, bool ignoreChecks) {
|
||||
}
|
||||
|
||||
void DestroyableComponent::SetStatusImmunity(
|
||||
const eStateChangeType state,
|
||||
const bool bImmuneToBasicAttack,
|
||||
const bool bImmuneToDamageOverTime,
|
||||
const bool bImmuneToKnockback,
|
||||
const bool bImmuneToInterrupt,
|
||||
const bool bImmuneToSpeed,
|
||||
const bool bImmuneToImaginationGain,
|
||||
const bool bImmuneToImaginationLoss,
|
||||
const bool bImmuneToQuickbuildInterrupt,
|
||||
const bool bImmuneToPullToPoint) {
|
||||
const eStateChangeType state,
|
||||
const bool bImmuneToBasicAttack,
|
||||
const bool bImmuneToDamageOverTime,
|
||||
const bool bImmuneToKnockback,
|
||||
const bool bImmuneToInterrupt,
|
||||
const bool bImmuneToSpeed,
|
||||
const bool bImmuneToImaginationGain,
|
||||
const bool bImmuneToImaginationLoss,
|
||||
const bool bImmuneToQuickbuildInterrupt,
|
||||
const bool bImmuneToPullToPoint) {
|
||||
|
||||
if (state == eStateChangeType::POP) {
|
||||
if (bImmuneToBasicAttack && m_ImmuneToBasicAttackCount > 0) m_ImmuneToBasicAttackCount -= 1;
|
||||
@@ -818,7 +829,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;
|
||||
@@ -945,7 +956,7 @@ 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_Parent->IsPlayer()) {
|
||||
//remove hardcore_lose_uscore_on_death_percent from the player's uscore:
|
||||
@@ -963,9 +974,9 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source){
|
||||
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;
|
||||
|
||||
@@ -24,6 +24,7 @@ public:
|
||||
DestroyableComponent(Entity* parentEntity);
|
||||
~DestroyableComponent() override;
|
||||
|
||||
void Update(float deltaTime) override;
|
||||
void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate) override;
|
||||
void LoadFromXml(tinyxml2::XMLDocument* doc) override;
|
||||
void UpdateXml(tinyxml2::XMLDocument* doc) override;
|
||||
@@ -166,6 +167,11 @@ public:
|
||||
*/
|
||||
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
|
||||
* @param value the GM immunity of this entity
|
||||
@@ -406,18 +412,23 @@ public:
|
||||
);
|
||||
|
||||
// Getters for status immunities
|
||||
const bool GetImmuneToBasicAttack() {return m_ImmuneToBasicAttackCount > 0;};
|
||||
const bool GetImmuneToDamageOverTime() {return m_ImmuneToDamageOverTimeCount > 0;};
|
||||
const bool GetImmuneToKnockback() {return m_ImmuneToKnockbackCount > 0;};
|
||||
const bool GetImmuneToInterrupt() {return m_ImmuneToInterruptCount > 0;};
|
||||
const bool GetImmuneToSpeed() {return m_ImmuneToSpeedCount > 0;};
|
||||
const bool GetImmuneToImaginationGain() {return m_ImmuneToImaginationGainCount > 0;};
|
||||
const bool GetImmuneToImaginationLoss() {return m_ImmuneToImaginationLossCount > 0;};
|
||||
const bool GetImmuneToQuickbuildInterrupt() {return m_ImmuneToQuickbuildInterruptCount > 0;};
|
||||
const bool GetImmuneToPullToPoint() {return m_ImmuneToPullToPointCount > 0;};
|
||||
const bool GetImmuneToBasicAttack() { return m_ImmuneToBasicAttackCount > 0; };
|
||||
const bool GetImmuneToDamageOverTime() { return m_ImmuneToDamageOverTimeCount > 0; };
|
||||
const bool GetImmuneToKnockback() { return m_ImmuneToKnockbackCount > 0; };
|
||||
const bool GetImmuneToInterrupt() { return m_ImmuneToInterruptCount > 0; };
|
||||
const bool GetImmuneToSpeed() { return m_ImmuneToSpeedCount > 0; };
|
||||
const bool GetImmuneToImaginationGain() { return m_ImmuneToImaginationGainCount > 0; };
|
||||
const bool GetImmuneToImaginationLoss() { return m_ImmuneToImaginationLossCount > 0; };
|
||||
const bool GetImmuneToQuickbuildInterrupt() { return m_ImmuneToQuickbuildInterruptCount > 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; }
|
||||
int32_t GetDeathBehavior() const { return m_DeathBehavior; }
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
|
||||
@@ -620,3 +620,7 @@ bool MissionComponent::HasCollectible(int32_t collectibleID) {
|
||||
bool MissionComponent::HasMission(uint32_t missionId) {
|
||||
return GetMission(missionId) != nullptr;
|
||||
}
|
||||
|
||||
void FixExplorerMissions() {
|
||||
|
||||
}
|
||||
|
||||
@@ -170,6 +170,9 @@ public:
|
||||
*/
|
||||
bool HasMission(uint32_t missionId);
|
||||
|
||||
|
||||
void FixExplorerMissions();
|
||||
|
||||
private:
|
||||
/**
|
||||
* All the missions owned by this entity, mapped by mission ID
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "RenderComponent.h"
|
||||
#include "eObjectBits.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "eMissionState.h"
|
||||
|
||||
std::unordered_map<LOT, PetComponent::PetPuzzleData> PetComponent::buildCache{};
|
||||
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);
|
||||
|
||||
if (closestTresure != nullptr) {
|
||||
if (closestTresure != nullptr && digUnlocked) {
|
||||
// Skeleton Dragon Pat special case for bone digging
|
||||
if (closestTresure->GetLOT() == 12192 && m_Parent->GetLOT() != 13067) {
|
||||
goto skipTresure;
|
||||
|
||||
@@ -2523,15 +2523,9 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent
|
||||
inStream->Read(sd0Size);
|
||||
std::shared_ptr<char[]> sd0Data(new char[sd0Size]);
|
||||
|
||||
if (sd0Data == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (sd0Data == nullptr) return;
|
||||
|
||||
for (uint32_t i = 0; i < sd0Size; ++i) {
|
||||
uint8_t c;
|
||||
inStream->Read(c);
|
||||
sd0Data[i] = c;
|
||||
}
|
||||
inStream->ReadAlignedBytes(reinterpret_cast<unsigned char*>(sd0Data.get()), sd0Size);
|
||||
|
||||
uint32_t 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.
|
||||
//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:
|
||||
ObjectIDManager::Instance()->RequestPersistentID([=](uint32_t newID) {
|
||||
LWOOBJID newIDL = newID;
|
||||
GeneralUtils::SetBit(newIDL, eObjectBits::CHARACTER);
|
||||
GeneralUtils::SetBit(newIDL, eObjectBits::PERSISTENT);
|
||||
//We need to get a new ID for our model first:
|
||||
ObjectIDManager::Instance()->RequestPersistentID([=](uint32_t newID) {
|
||||
LWOOBJID newIDL = newID;
|
||||
GeneralUtils::SetBit(newIDL, eObjectBits::CHARACTER);
|
||||
GeneralUtils::SetBit(newIDL, eObjectBits::PERSISTENT);
|
||||
|
||||
ObjectIDManager::Instance()->RequestPersistentID([=](uint32_t blueprintIDSmall) {
|
||||
blueprintIDSmall = ObjectIDManager::Instance()->GenerateRandomObjectID();
|
||||
LWOOBJID blueprintID = blueprintIDSmall;
|
||||
GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER);
|
||||
GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT);
|
||||
uint32_t blueprintIDSmall = ObjectIDManager::Instance()->GenerateRandomObjectID();
|
||||
LWOOBJID blueprintID = blueprintIDSmall;
|
||||
GeneralUtils::SetBit(blueprintID, eObjectBits::CHARACTER);
|
||||
GeneralUtils::SetBit(blueprintID, eObjectBits::PERSISTENT);
|
||||
|
||||
//We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
|
||||
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
|
||||
//We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
|
||||
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
|
||||
|
||||
const auto zoneId = worldId.GetMapID();
|
||||
const auto cloneId = worldId.GetCloneID();
|
||||
const auto zoneId = worldId.GetMapID();
|
||||
const auto cloneId = worldId.GetCloneID();
|
||||
|
||||
auto query = CDClientDatabase::CreatePreppedStmt(
|
||||
"SELECT id FROM PropertyTemplate WHERE mapID = ?;");
|
||||
query.bind(1, (int)zoneId);
|
||||
auto query = CDClientDatabase::CreatePreppedStmt(
|
||||
"SELECT id FROM PropertyTemplate WHERE mapID = ?;");
|
||||
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);
|
||||
propertyLookup->setInt64(2, cloneId);
|
||||
auto* propertyEntry = propertyLookup->executeQuery();
|
||||
uint64_t propertyId = 0;
|
||||
|
||||
auto* propertyEntry = propertyLookup->executeQuery();
|
||||
uint64_t propertyId = 0;
|
||||
if (propertyEntry->next()) {
|
||||
propertyId = propertyEntry->getUInt64(1);
|
||||
}
|
||||
|
||||
if (propertyEntry->next()) {
|
||||
propertyId = propertyEntry->getUInt64(1);
|
||||
}
|
||||
delete propertyEntry;
|
||||
delete propertyLookup;
|
||||
|
||||
delete propertyEntry;
|
||||
delete propertyLookup;
|
||||
//Insert into ugc:
|
||||
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:
|
||||
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);
|
||||
//whacky stream biz
|
||||
std::string s(sd0Data.get(), sd0Size);
|
||||
std::istringstream iss(s);
|
||||
|
||||
//whacky stream biz
|
||||
std::string s(sd0Data.get(), sd0Size);
|
||||
std::istringstream iss(s);
|
||||
std::istream& stream = iss;
|
||||
ugcs->setBlob(5, &iss);
|
||||
ugcs->setBoolean(6, false);
|
||||
ugcs->setString(7, "weedeater.lxfml");
|
||||
ugcs->execute();
|
||||
delete ugcs;
|
||||
|
||||
ugcs->setBlob(5, &iss);
|
||||
ugcs->setBoolean(6, false);
|
||||
ugcs->setString(7, "weedeater.lxfml");
|
||||
ugcs->execute();
|
||||
delete ugcs;
|
||||
//Insert into the db as a BBB model:
|
||||
auto* stmt = Database::CreatePreppedStmt("INSERT INTO `properties_contents` VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
|
||||
stmt->setUInt64(1, newIDL);
|
||||
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;
|
||||
|
||||
//Insert into the db as a BBB model:
|
||||
auto* stmt = Database::CreatePreppedStmt("INSERT INTO `properties_contents` VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
|
||||
stmt->setUInt64(1, newIDL);
|
||||
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;
|
||||
/*
|
||||
Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream.
|
||||
(or you uncomment the lxfml decomp stuff above)
|
||||
*/
|
||||
|
||||
/*
|
||||
Commented out until UGC server would be updated to use a sd0 file instead of lxfml stream.
|
||||
(or you uncomment the lxfml decomp stuff above)
|
||||
*/
|
||||
////Send off to UGC for processing, if enabled:
|
||||
//if (Game::config->GetValue("ugc_remote") == "1") {
|
||||
// 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:
|
||||
//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 ^
|
||||
|
||||
// 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:
|
||||
// std::string request = "/3dservices/UGCC150/150" + std::to_string(blueprintID) + ".lxfml";
|
||||
// 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 &
|
||||
// //the nif, hkx and checksum files are ready to be downloaded from cache.
|
||||
//}
|
||||
|
||||
// //When the "put" above returns, it means that the UGC HTTP server is done processing our model &
|
||||
// //the nif, hkx and checksum files are ready to be downloaded from cache.
|
||||
//}
|
||||
//Tell the client their model is saved: (this causes us to actually pop out of our current state):
|
||||
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):
|
||||
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.Write<uint32_t>(sd0Size);
|
||||
bitStream.WriteAlignedBytes(reinterpret_cast<unsigned char*>(sd0Data.get()), sd0Size);
|
||||
|
||||
for (size_t i = 0; i < sd0Size; ++i) {
|
||||
bitStream.Write(sd0Data[i]);
|
||||
}
|
||||
SEND_PACKET;
|
||||
|
||||
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;
|
||||
info.lot = 14;
|
||||
info.pos = {};
|
||||
info.rot = {};
|
||||
info.spawner = nullptr;
|
||||
info.spawnerID = entity->GetObjectID();
|
||||
info.spawnerNodeID = 0;
|
||||
LDFBaseData* ldfBlueprintID = new LDFData<LWOOBJID>(u"blueprintid", blueprintID);
|
||||
LDFBaseData* componentWhitelist = new LDFData<int>(u"componentWhitelist", 1);
|
||||
LDFBaseData* modelType = new LDFData<int>(u"modelType", 2);
|
||||
LDFBaseData* propertyObjectID = new LDFData<bool>(u"propertyObjectID", true);
|
||||
LDFBaseData* userModelID = new LDFData<LWOOBJID>(u"userModelID", newIDL);
|
||||
|
||||
LDFBaseData* ldfBlueprintID = new LDFData<LWOOBJID>(u"blueprintid", blueprintID);
|
||||
LDFBaseData* componentWhitelist = new LDFData<int>(u"componentWhitelist", 1);
|
||||
LDFBaseData* modelType = new LDFData<int>(u"modelType", 2);
|
||||
LDFBaseData* propertyObjectID = new LDFData<bool>(u"propertyObjectID", true);
|
||||
LDFBaseData* userModelID = new LDFData<LWOOBJID>(u"userModelID", newIDL);
|
||||
info.settings.push_back(ldfBlueprintID);
|
||||
info.settings.push_back(componentWhitelist);
|
||||
info.settings.push_back(modelType);
|
||||
info.settings.push_back(propertyObjectID);
|
||||
info.settings.push_back(userModelID);
|
||||
|
||||
info.settings.push_back(ldfBlueprintID);
|
||||
info.settings.push_back(componentWhitelist);
|
||||
info.settings.push_back(modelType);
|
||||
info.settings.push_back(propertyObjectID);
|
||||
info.settings.push_back(userModelID);
|
||||
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
||||
if (newEntity) {
|
||||
Game::entityManager->ConstructEntity(newEntity);
|
||||
|
||||
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
||||
if (newEntity) {
|
||||
Game::entityManager->ConstructEntity(newEntity);
|
||||
//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);
|
||||
}
|
||||
|
||||
//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) {
|
||||
@@ -5625,10 +5610,18 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream* inStream, Entity*
|
||||
std::vector<LDFBaseData*> config;
|
||||
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) {
|
||||
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) {
|
||||
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>();
|
||||
|
||||
@@ -29,7 +29,7 @@ void ReportCheat(User* user, const SystemAddress& sysAddr, const char* messageIf
|
||||
std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt(
|
||||
"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.");
|
||||
|
||||
constexpr int32_t bufSize = 4096;
|
||||
|
||||
@@ -247,7 +247,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -1490,6 +1490,24 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
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) {
|
||||
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) {
|
||||
auto* skillComponent = entity->GetComponent<SkillComponent>();
|
||||
if (skillComponent){
|
||||
if (skillComponent) {
|
||||
uint32_t skillId;
|
||||
|
||||
if (!GeneralUtils::TryParse(args[0], skillId)) {
|
||||
@@ -1860,7 +1878,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
uint32_t skillId;
|
||||
int slot;
|
||||
auto* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
if (inventoryComponent){
|
||||
if (inventoryComponent) {
|
||||
if (!GeneralUtils::TryParse(args[0], slot)) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Error getting slot.");
|
||||
return;
|
||||
@@ -1869,7 +1887,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Error getting skill.");
|
||||
return;
|
||||
} 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");
|
||||
}
|
||||
}
|
||||
@@ -1878,7 +1896,7 @@ void SlashCommandHandler::HandleChatCommand(const std::u16string& command, Entit
|
||||
|
||||
if (chatCommand == "setfaction" && entity->GetGMLevel() >= eGameMasterLevel::DEVELOPER && args.size() >= 1) {
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
if (destroyableComponent){
|
||||
if (destroyableComponent) {
|
||||
int32_t 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) {
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
if (destroyableComponent){
|
||||
if (destroyableComponent) {
|
||||
int32_t 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) {
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
if (destroyableComponent){
|
||||
if (destroyableComponent) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, u"Friendly factions:");
|
||||
for (const auto entry : destroyableComponent->GetFactionIDs()) {
|
||||
ChatPackets::SendSystemMessage(sysAddr, (GeneralUtils::to_u16string(entry)));
|
||||
|
||||
@@ -154,7 +154,7 @@ int main(int argc, char** argv) {
|
||||
Game::assetManager = new AssetManager(clientPath);
|
||||
} catch (std::runtime_error& ex) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "NtCombatChallengeExplodingDummy.h"
|
||||
#include "EntityManager.h"
|
||||
#include "SkillComponent.h"
|
||||
#include "DestroyableComponent.h"
|
||||
|
||||
void NtCombatChallengeExplodingDummy::OnDie(Entity* self, Entity* killer) {
|
||||
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) {
|
||||
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");
|
||||
|
||||
auto* challengeObject = Game::entityManager->GetEntity(challengeObjectID);
|
||||
@@ -28,5 +40,6 @@ void NtCombatChallengeExplodingDummy::OnHitOrHealResult(Entity* self, Entity* at
|
||||
if (skillComponent != nullptr) {
|
||||
skillComponent->CalculateBehavior(1338, 30875, attacker->GetObjectID());
|
||||
}
|
||||
self->Kill(attacker);
|
||||
GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(self, u"camshake", self->GetObjectID(), 16.0f);
|
||||
self->Smash(attacker->GetObjectID());
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
#include "ZCompression.h"
|
||||
#include "EntityManager.h"
|
||||
#include "CheatDetection.h"
|
||||
#include "MissionComponent.h"
|
||||
|
||||
namespace Game {
|
||||
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!
|
||||
if (Game::server->GetZoneID() != 0) {
|
||||
auto user = UserManager::Instance()->GetUser(packet->systemAddress);
|
||||
if (!user) return;
|
||||
if (!user || !user->GetLastUsedChar()) return;
|
||||
Game::entityManager->DestroyEntity(user->GetLastUsedChar()->GetEntity());
|
||||
}
|
||||
|
||||
@@ -1057,6 +1058,9 @@ void HandlePacket(Packet* packet) {
|
||||
auto* levelComponent = player->GetComponent<LevelProgressionComponent>();
|
||||
if (!levelComponent) return;
|
||||
|
||||
auto* missionComponent = player->GetComponent<MissionComponent>();
|
||||
if (!missionComponent) return;
|
||||
|
||||
auto version = levelComponent->GetCharacterVersion();
|
||||
switch(version) {
|
||||
case eCharacterVersion::RELEASE:
|
||||
@@ -1070,9 +1074,14 @@ void HandlePacket(Packet* packet) {
|
||||
player->RetroactiveVaultSize();
|
||||
levelComponent->SetCharacterVersion(eCharacterVersion::VAULT_SIZE);
|
||||
case eCharacterVersion::VAULT_SIZE:
|
||||
LOG("Updaing Speedbase");
|
||||
LOG("Updating Speedbase");
|
||||
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:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|ban|`/ban <username>`|Bans a user from the server.|4|
|
||||
|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|
|
||||
|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|
|
||||
|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|
|
||||
|
||||
Binary file not shown.
19
systemd.example
Normal file
19
systemd.example
Normal 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
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user