diff --git a/README.md b/README.md index b51fa4b0..310e75eb 100644 --- a/README.md +++ b/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. diff --git a/dChatFilter/dChatFilter.cpp b/dChatFilter/dChatFilter.cpp index 6e3cd069..8d2015b0 100644 --- a/dChatFilter/dChatFilter.cpp +++ b/dChatFilter/dChatFilter.cpp @@ -144,7 +144,7 @@ std::vector> dChatFilter::IsSentenceOkay(const std:: listOfBadSegments.emplace_back(position, originalSegment.length()); } - position += segment.length() + 1; + position += originalSegment.length() + 1; } return listOfBadSegments; diff --git a/dDatabase/Tables/CDLootTableTable.cpp b/dDatabase/Tables/CDLootTableTable.cpp index c666babc..9ee875df 100644 --- a/dDatabase/Tables/CDLootTableTable.cpp +++ b/dDatabase/Tables/CDLootTableTable.cpp @@ -24,7 +24,6 @@ void SortTable(LootTableEntries& table) { lootToInsert = oldItrInner; } } - Game::logger->LogDebug("CDLootTableTable", "highest rarity %i item id %i", highestLootRarity, lootToInsert->itemid); std::swap(*oldItrOuter, *lootToInsert); } } diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 47910ede..9d120d1c 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -1037,7 +1037,7 @@ void GameMessages::SendSetNetworkScriptVar(Entity* entity, const SystemAddress& } void GameMessages::SendDropClientLoot(Entity* entity, const LWOOBJID& sourceID, LOT item, int currency, NiPoint3 spawnPos, int count) { - if (Game::config->GetValue("disable_drops") == "1") { + if (Game::config->GetValue("disable_drops") == "1" || !entity) { return; } @@ -2531,15 +2531,9 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent inStream->Read(sd0Size); std::shared_ptr 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(sd0Data.get()), sd0Size); uint32_t timeTaken; inStream->Read(timeTaken); @@ -2573,165 +2567,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(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(1); - bitStream.Write(blueprintID); + bitStream.Write(sd0Size); - bitStream.Write(sd0Size); + bitStream.WriteAlignedBytes(reinterpret_cast(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(u"blueprintid", blueprintID); + LDFBaseData* componentWhitelist = new LDFData(u"componentWhitelist", 1); + LDFBaseData* modelType = new LDFData(u"modelType", 2); + LDFBaseData* propertyObjectID = new LDFData(u"propertyObjectID", true); + LDFBaseData* userModelID = new LDFData(u"userModelID", newIDL); - LDFBaseData* ldfBlueprintID = new LDFData(u"blueprintid", blueprintID); - LDFBaseData* componentWhitelist = new LDFData(u"componentWhitelist", 1); - LDFBaseData* modelType = new LDFData(u"modelType", 2); - LDFBaseData* propertyObjectID = new LDFData(u"propertyObjectID", true); - LDFBaseData* userModelID = new LDFData(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) { @@ -5645,10 +5630,18 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream* inStream, Entity* std::vector 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(); diff --git a/dGame/dUtilities/CheatDetection.cpp b/dGame/dUtilities/CheatDetection.cpp index 9e1f597c..ce841710 100644 --- a/dGame/dUtilities/CheatDetection.cpp +++ b/dGame/dUtilities/CheatDetection.cpp @@ -29,7 +29,7 @@ void ReportCheat(User* user, const SystemAddress& sysAddr, const char* messageIf std::unique_ptr 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; diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 0380a89a..72eb4e59 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -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; } diff --git a/dScripts/02_server/Map/General/ExplodingAsset.cpp b/dScripts/02_server/Map/General/ExplodingAsset.cpp index ee8f8e68..ce96014b 100644 --- a/dScripts/02_server/Map/General/ExplodingAsset.cpp +++ b/dScripts/02_server/Map/General/ExplodingAsset.cpp @@ -4,6 +4,8 @@ #include "MissionComponent.h" #include "SkillComponent.h" #include "eMissionTaskType.h" +#include "CDClientManager.h" +#include "CDObjectSkillsTable.h" #include "RenderComponent.h" //TODO: this has to be updated so that you only get killed if you're in a certain radius. @@ -39,9 +41,11 @@ void ExplodingAsset::OnHit(Entity* self, Entity* attacker) { self->SetOwnerOverride(attacker->GetObjectID()); GameMessages::SendPlayEmbeddedEffectOnAllClientsNearObject(self, u"camshake", self->GetObjectID(), 16); + self->Smash(attacker->GetObjectID()); auto* skillComponent = self->GetComponent(); if (skillComponent != nullptr) { + // Technically supposed to get first skill in the skill component but only 1 object in the live game used this. skillComponent->CalculateBehavior(147, 4721, LWOOBJID_EMPTY, true); } @@ -65,8 +69,6 @@ void ExplodingAsset::OnHit(Entity* self, Entity* attacker) { } } } - - self->ScheduleKillAfterUpdate(); } void ExplodingAsset::OnProximityUpdate(Entity* self, Entity* entering, std::string name, std::string status) { diff --git a/dScripts/02_server/Map/NT/NtCombatChallengeExplodingDummy.cpp b/dScripts/02_server/Map/NT/NtCombatChallengeExplodingDummy.cpp index de27e106..4ae2b335 100644 --- a/dScripts/02_server/Map/NT/NtCombatChallengeExplodingDummy.cpp +++ b/dScripts/02_server/Map/NT/NtCombatChallengeExplodingDummy.cpp @@ -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(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(); + auto numTimesHit = self->GetVar(u"numTimesHit"); + if (destroyableComponent && numTimesHit == 0) { + self->SetVar(u"numTimesHit", 1); + destroyableComponent->SetHealth(destroyableComponent->GetHealth() / 2); + return; + } else if (numTimesHit == 2) { + return; + } + self->SetVar(u"numTimesHit", 2); + const auto challengeObjectID = self->GetVar(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()); } diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 6d383366..a840e618 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -938,7 +938,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()); } diff --git a/resources/navmeshes.zip b/resources/navmeshes.zip index 9cd3e03a..34835ed3 100644 Binary files a/resources/navmeshes.zip and b/resources/navmeshes.zip differ diff --git a/systemd.example b/systemd.example new file mode 100644 index 00000000..d82985df --- /dev/null +++ b/systemd.example @@ -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 \ No newline at end of file