mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 07:47:13 +00:00
revert(world): the edit mode world
This reverts commit f6914940. The client's level editor builds a zone's objects
itself from its own files, so a world server that constructs none of them has no use.
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
@@ -98,11 +98,6 @@ CppScripts::Script* const ScriptComponent::GetScript() {
|
||||
void ScriptComponent::SetScript(const std::string& scriptName) {
|
||||
// Scripts are managed by the CppScripts class and are effecitvely singletons
|
||||
// and they may also be used by other script components so DON'T delete them.
|
||||
// Edit mode (docs/EditMode.md): the zone control object stays, its zone script does not run
|
||||
if (Game::zoneManager && Game::zoneManager->GetEditMode() && m_Parent->GetObjectID() == dZoneManager::ZONE_CONTROL_OBJECT_ID) {
|
||||
m_Script = CppScripts::GetInvalidScript();
|
||||
return;
|
||||
}
|
||||
m_Script = CppScripts::GetScript(m_Parent, scriptName);
|
||||
}
|
||||
|
||||
|
||||
@@ -118,8 +118,7 @@ namespace GameMessages {
|
||||
std::vector<Entity*> racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL);
|
||||
for (Entity* racingController : racingControllers) {
|
||||
auto* racingComponent = racingController->GetComponent<RacingControlComponent>();
|
||||
// Edit mode: a race track's zone control doesn't take the GM into a race (or send them out for having no car)
|
||||
if (racingComponent != nullptr && !Game::zoneManager->GetEditMode()) {
|
||||
if (racingComponent != nullptr) {
|
||||
racingComponent->OnPlayerLoaded(entity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ set(DGAME_DUTILITIES_SOURCES "BrickDatabase.cpp"
|
||||
"CheatDetection.cpp"
|
||||
"DashboardActions.cpp"
|
||||
"EconomyLedger.cpp"
|
||||
"EditMode.cpp"
|
||||
"DashboardNotify.cpp"
|
||||
"GUID.cpp"
|
||||
"LiveEvents.cpp"
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#include "EditMode.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "CharacterComponent.h"
|
||||
#include "Game.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "dConfig.h"
|
||||
|
||||
eGameMasterLevel EditMode::RequiredLevel() {
|
||||
const auto level = GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("editor_gm_level")).value_or(CharacterComponent::DEFAULT_EDITOR_GM_LEVEL);
|
||||
return static_cast<eGameMasterLevel>(std::clamp<uint32_t>(level, 1, 9));
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "eGameMasterLevel.h"
|
||||
|
||||
/**
|
||||
* Edit mode (docs/EditMode.md): a private instance of a zone whose world server constructs none of the zone's level
|
||||
* objects, spawners or triggers, for editing the zone in the game client. Only the plumbing is here; nothing starts
|
||||
* such an instance yet.
|
||||
*/
|
||||
namespace EditMode {
|
||||
// Whether an account may be in an edit mode instance
|
||||
inline bool MayEnter(const eGameMasterLevel accountLevel, const eGameMasterLevel requiredLevel) {
|
||||
return accountLevel != eGameMasterLevel::CIVILIAN && accountLevel >= requiredLevel;
|
||||
}
|
||||
|
||||
// The account GM level an edit mode instance lets in: editor_gm_level (9 unless set)
|
||||
eGameMasterLevel RequiredLevel();
|
||||
}
|
||||
@@ -86,7 +86,6 @@
|
||||
#include "DestroyableComponent.h"
|
||||
#include "Game.h"
|
||||
#include "PropertyManagementComponent.h"
|
||||
#include "EditMode.h"
|
||||
#include "AssetManager.h"
|
||||
#include "ZoneOverrideStore.h"
|
||||
#include "LevelProgressionComponent.h"
|
||||
@@ -245,7 +244,6 @@ int main(int argc, char** argv) {
|
||||
uint32_t cloneID = 0;
|
||||
uint32_t maxClients = 8;
|
||||
uint32_t ourPort = 2007;
|
||||
bool editMode = false;
|
||||
|
||||
//Check our arguments:
|
||||
for (int32_t i = 0; (i + 1) < argc; i++) {
|
||||
@@ -260,7 +258,6 @@ int main(int argc, char** argv) {
|
||||
else if (argument == "-clone") cloneID = valOptional.value_or(0);
|
||||
else if (argument == "-maxclients") maxClients = valOptional.value_or(8);
|
||||
else if (argument == "-port") ourPort = valOptional.value_or(2007);
|
||||
else if (argument == "-editmode") editMode = valOptional.value_or(0) != 0;
|
||||
}
|
||||
|
||||
Game::config = new dConfig("worldconfig.ini");
|
||||
@@ -429,8 +426,6 @@ int main(int argc, char** argv) {
|
||||
Profiler::Scope navmesh("Navmesh and physics load", Profiler::Phase::PHYSICS);
|
||||
dpWorld::Initialize(zoneID);
|
||||
}
|
||||
// A GM's edit mode instance (docs/EditMode.md): master says so when it starts the world
|
||||
Game::zoneManager->SetEditMode(editMode);
|
||||
Game::zoneManager->Initialize(LWOZONEID(zoneID, g_InstanceID, cloneID));
|
||||
PacketCapture::SetClone(cloneID);
|
||||
g_CloneID = cloneID;
|
||||
@@ -586,8 +581,7 @@ int main(int argc, char** argv) {
|
||||
DashboardNotify::Flush();
|
||||
DashboardNotify::SendPlayerPositions(g_InstanceID);
|
||||
MessageInspector::Update();
|
||||
// (an edit mode instance has no server objects, so no live events either)
|
||||
if (!Game::zoneManager->GetEditMode()) LiveEvents::Update();
|
||||
LiveEvents::Update();
|
||||
UgcManifest::Update();
|
||||
|
||||
Metrics::StartMeasurement(MetricVariable::UpdateSpawners);
|
||||
@@ -898,17 +892,6 @@ namespace {
|
||||
} else {
|
||||
LOG("User %s authenticated with correct key.", username.GetAsString().c_str());
|
||||
|
||||
// An edit mode instance takes only accounts that may use /editmode, however they found its address
|
||||
if (Game::zoneManager->GetEditMode()) {
|
||||
const auto account = Database::Get()->GetAccountInfo(username.GetAsString());
|
||||
if (!EditMode::MayEnter(account ? account->maxGmLevel : eGameMasterLevel::CIVILIAN, EditMode::RequiredLevel())) {
|
||||
LOG("User %s may not enter an edit mode instance; disconnecting them", username.GetAsString().c_str());
|
||||
Game::server->Disconnect(it->second.sysAddr, eServerDisconnectIdentifiers::KICK);
|
||||
g_PendingUsers.erase(it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
UserManager::Instance()->DeleteUser(masterAddr);
|
||||
|
||||
//Create our user and send them in:
|
||||
|
||||
@@ -47,7 +47,7 @@ Level::Level(Zone* parentZone, const std::string& filepath, int32_t sceneID) {
|
||||
static_cast<LevelFile::ChunkHeader&>(header) = chunkHeader;
|
||||
m_ChunkHeaders.insert(std::make_pair(id, header));
|
||||
}
|
||||
LoadSceneObjects(levelFile.objects, m_SceneID);
|
||||
LoadSceneObjects(levelFile.objects);
|
||||
}
|
||||
|
||||
void Level::MakeSpawner(const SceneObject& obj, int32_t sceneID) {
|
||||
@@ -120,18 +120,7 @@ void Level::MakeSpawner(const SceneObject& obj, int32_t sceneID) {
|
||||
Game::zoneManager->MakeSpawner(spawnInfo);
|
||||
}
|
||||
|
||||
void Level::LoadSceneObjects(const std::vector<SceneObject>& objects, const int32_t sceneID) {
|
||||
// Edit mode: the GM's client has the level's objects from its own files, so none is made here. The oldest files
|
||||
// keep the spawn point in an object, which is still read.
|
||||
if (Game::zoneManager->GetEditMode()) {
|
||||
for (const auto& obj : objects) {
|
||||
if (obj.lot != LOT_MARKER_PLAYER_START) continue;
|
||||
Game::zoneManager->GetZoneMut()->SetSpawnPos(obj.position);
|
||||
Game::zoneManager->GetZoneMut()->SetSpawnRot(obj.rotation);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void Level::LoadSceneObjects(const std::vector<SceneObject>& objects) {
|
||||
CDFeatureGatingTable* featureGatingTable = CDClientManager::GetTable<CDFeatureGatingTable>();
|
||||
|
||||
CDFeatureGating gating;
|
||||
@@ -192,7 +181,7 @@ void Level::LoadSceneObjects(const std::vector<SceneObject>& objects, const int3
|
||||
}
|
||||
|
||||
if (obj.lot == 176) { //Spawner
|
||||
MakeSpawner(obj, sceneID);
|
||||
MakeSpawner(obj, m_SceneID);
|
||||
} else { //Regular object
|
||||
EntityInfo info;
|
||||
info.spawnerID = 0;
|
||||
@@ -202,7 +191,7 @@ void Level::LoadSceneObjects(const std::vector<SceneObject>& objects, const int3
|
||||
info.rot = obj.rotation;
|
||||
info.settings = obj.settings;
|
||||
info.scale = obj.scale;
|
||||
info.scene = sceneID;
|
||||
info.scene = m_SceneID;
|
||||
Game::entityManager->CreateEntity(info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,11 @@ public:
|
||||
|
||||
static void MakeSpawner(const SceneObject& obj, int32_t sceneID = -1);
|
||||
|
||||
// Makes the objects and spawners of a level file. In edit mode (dZoneManager::GetEditMode) none: only the player
|
||||
// start marker's place is taken
|
||||
static void LoadSceneObjects(const std::vector<SceneObject>& objects, int32_t sceneID = -1);
|
||||
|
||||
std::map<uint32_t, Header> m_ChunkHeaders;
|
||||
private:
|
||||
Zone* m_ParentZone;
|
||||
int32_t m_SceneID = -1;
|
||||
|
||||
//private functions:
|
||||
void LoadSceneObjects(const std::vector<SceneObject>& objects);
|
||||
};
|
||||
|
||||
@@ -138,8 +138,7 @@ void Zone::LoadZoneIntoMemory() {
|
||||
}
|
||||
}
|
||||
|
||||
// Edit mode: no spawner networks (the paths themselves are kept, they are only data)
|
||||
if (m_FileFormatVersion >= Zone::FileFormatVersion::EarlyAlpha && !Game::zoneManager->GetEditMode()) {
|
||||
if (m_FileFormatVersion >= Zone::FileFormatVersion::EarlyAlpha) {
|
||||
for (const Path& path : m_Paths) {
|
||||
if (path.pathType != PathType::Spawner) continue;
|
||||
SpawnerInfo info{};
|
||||
@@ -287,8 +286,7 @@ void Zone::LoadScene(ZoneScene&& zoneScene) {
|
||||
LWOSCENEID lwoSceneID(scene.id, scene.sceneType);
|
||||
|
||||
std::string luTriggersPath = scene.filename.substr(0, scene.filename.size() - 4) + ".lutriggers";
|
||||
// Edit mode: no triggers
|
||||
if (!Game::zoneManager->GetEditMode() && Game::assetManager->HasFile((m_ZonePath + luTriggersPath).c_str())) LoadLUTriggers(luTriggersPath, scene);
|
||||
if (Game::assetManager->HasFile((m_ZonePath + luTriggersPath).c_str())) LoadLUTriggers(luTriggersPath, scene);
|
||||
|
||||
m_Scenes[lwoSceneID] = std::move(scene);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,8 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) {
|
||||
EntityInfo info;
|
||||
info.lot = zoneControlTemplate;
|
||||
|
||||
info.id = ZONE_CONTROL_OBJECT_ID;
|
||||
/* Yep its hardcoded like this in the client too, this exact value. */
|
||||
info.id = 0x3FFF'FFFFFFFELL;
|
||||
Entity* zoneControl = Game::entityManager->CreateEntity(info, nullptr, nullptr, true);
|
||||
m_ZoneControlObject = zoneControl;
|
||||
|
||||
@@ -72,9 +73,7 @@ void dZoneManager::Initialize(const LWOZONEID& zoneID) {
|
||||
|
||||
LOG("Zone prepared in: %llu ms", (endTime - startTime));
|
||||
|
||||
// The vanity objects are server objects the client's editor would not know
|
||||
if (!m_EditMode) VanityUtilities::SpawnVanity();
|
||||
else LOG("Zone %i is in edit mode: no level objects, spawners, triggers or vanity objects were loaded", zoneID.GetMapID());
|
||||
VanityUtilities::SpawnVanity();
|
||||
}
|
||||
|
||||
dZoneManager::~dZoneManager() {
|
||||
|
||||
@@ -23,9 +23,6 @@ private:
|
||||
void LoadWorldConfig();
|
||||
|
||||
public:
|
||||
// The zone control object's ID, the same fixed value in the client (LevelManager::LoadZoneControlObject, 0x01082660)
|
||||
static constexpr LWOOBJID ZONE_CONTROL_OBJECT_ID = 0x3FFF'FFFFFFFELL;
|
||||
|
||||
void Initialize(const LWOZONEID& zoneID);
|
||||
~dZoneManager();
|
||||
|
||||
@@ -33,13 +30,6 @@ public:
|
||||
Zone* GetZoneMut() const;
|
||||
const Zone* GetZone() const { return GetZoneMut(); };
|
||||
bool HasZone() const { return m_pZone != nullptr; };
|
||||
|
||||
/**
|
||||
* A GM's edit mode (docs/EditMode.md): the zone's level objects, spawners, spawner networks and triggers are not
|
||||
* loaded, so the GM's client is the only one that has them. Set before Initialize.
|
||||
*/
|
||||
bool GetEditMode() const { return m_EditMode; }
|
||||
void SetEditMode(bool value) { m_EditMode = value; }
|
||||
void LoadZone(const LWOZONEID& zoneID); //Discard the current zone (if any) and loads a new zone.
|
||||
|
||||
/* Adds a spawner to the zone with the specified ID. */
|
||||
@@ -113,7 +103,6 @@ private:
|
||||
bool m_PlayerLoseCoinsOnDeath = false;
|
||||
bool m_DisableSaveLocation = false;
|
||||
bool m_MountsAllowed = true;
|
||||
bool m_EditMode = false;
|
||||
bool m_PetsAllowed = true;
|
||||
std::map<LWOOBJID, Spawner*> m_Spawners;
|
||||
std::optional<WorldConfig> m_WorldConfig = std::nullopt;
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# Edit mode (plumbing)
|
||||
|
||||
A private instance of a zone for editing the zone in the game client. The client loads the zone's objects from its own
|
||||
files; the server never reads the client's files, so in a normal instance its own copies of the same objects are there
|
||||
too. In an edit mode instance the server constructs none of them.
|
||||
|
||||
**Not exposed yet:** no command or packet from a client starts an edit mode instance. This is what exists for one to use.
|
||||
|
||||
## The flag
|
||||
|
||||
| Where | What |
|
||||
|---|---|
|
||||
| World -> master | `MasterPackets::CreatePrivateZone::editMode`, one more byte written only when set (`ZoneInstanceManager::CreatePrivateZone(..., editMode)`) |
|
||||
| Master | `Instance::GetIsEditMode`; `InstanceManager::CreatePrivateInstance(..., editMode)` |
|
||||
| Master -> world | The launch argument `-editmode 1` (`StartWorldServer`) |
|
||||
| World | `dZoneManager::GetEditMode`, set before the zone loads |
|
||||
|
||||
A reload (`/reloadworld`, a zone file change) or a live update replaces a private instance with one that has the same
|
||||
password and edit mode. A request to join a private instance that is still starting is answered once it is ready.
|
||||
|
||||
## What an edit mode instance loads
|
||||
|
||||
| Loaded | Not loaded |
|
||||
|---|---|
|
||||
| The zone file: spawn point, scenes, scene transitions, boundaries, paths (as data) | Level objects (`.lvl`), carver-only blockers |
|
||||
| The terrain's scene map, the navmesh and the physics world | Spawners (LOT 176) and spawner networks (spawner paths) |
|
||||
| The level files' revisions (the map checksum sent to the client) | Triggers (`.lutriggers`) |
|
||||
| The zone control object, with the zone's own template | The zone control object's server script |
|
||||
| The player, their items, pets and mounts; GM commands | Vanity objects, live event objects |
|
||||
|
||||
The zone control object stays: the client makes its own from the `ZoneTable` template under the same fixed ID
|
||||
(`LevelManager::LoadZoneControlObject`, 0x01082660), the server's construction must match it, and server code expects
|
||||
the object. Its script is replaced by one that does nothing. Its other components (an activity, a race) stay but are
|
||||
idle: a race track's zone control does not take the player into a race.
|
||||
|
||||
The client waits on nothing a level object sends: it reports the level loaded when its own resource queue is empty and
|
||||
finishes the login on its character and the server's "done loading" (`LWONetworkInterface::Run`, 0x00b32360).
|
||||
|
||||
## The map checksum
|
||||
|
||||
The client compares the checksum the server sends in `LOAD_STATIC_ZONE` with the one of its own files
|
||||
(`LWONetworkInterface::iSendMessage`, 0x00b35870), and logs out with "invalid map version" when they differ. The
|
||||
checksum is made of the zone and scene files' revisions. Edit mode does not change that: the server's copy of the zone
|
||||
must have the revisions the client's has.
|
||||
|
||||
## Who gets in
|
||||
|
||||
The world server of an edit mode instance disconnects accounts below `editor_gm_level` (worldconfig.ini, default 9).
|
||||
Master never sends anyone to a private instance from a normal transfer or matchmaking.
|
||||
|
||||
## The save's `edit` attribute
|
||||
|
||||
A character's save has `edit="1"` when its account's GM level is at least `editor_gm_level`, `0` for everyone else.
|
||||
The client takes anything but 0 as the editor being enabled.
|
||||
@@ -7,7 +7,6 @@ set(DGAMETEST_SOURCES
|
||||
"MailPacketTests.cpp"
|
||||
"MatchmakingTests.cpp"
|
||||
"EconomyLedgerTests.cpp"
|
||||
"EditModeTests.cpp"
|
||||
"PlayerReportsLimitTests.cpp"
|
||||
"SlashCommandPermissionTests.cpp"
|
||||
"StaleSaveGuardTests.cpp"
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
// Edit mode (docs/EditMode.md): who may enter, and a level file's objects not being made.
|
||||
#include "GameDependencies.h"
|
||||
|
||||
#include "EditMode.h"
|
||||
#include "EntityManager.h"
|
||||
#include "Level.h"
|
||||
#include "LevelFile.h"
|
||||
#include "Zone.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "dConfig.h"
|
||||
#include "eObjectBits.h"
|
||||
#include "dZoneManager.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
class EditModeTests : public GameDependenciesTest {
|
||||
protected:
|
||||
void SetUp() override { SetUpDependencies(); }
|
||||
void TearDown() override { TearDownDependencies(); }
|
||||
};
|
||||
|
||||
TEST_F(EditModeTests, OnlyEditorAccountsMayEnter) {
|
||||
EXPECT_TRUE(EditMode::MayEnter(eGameMasterLevel::OPERATOR, eGameMasterLevel::OPERATOR));
|
||||
EXPECT_FALSE(EditMode::MayEnter(eGameMasterLevel::DEVELOPER, eGameMasterLevel::OPERATOR));
|
||||
EXPECT_FALSE(EditMode::MayEnter(eGameMasterLevel::CIVILIAN, eGameMasterLevel::OPERATOR));
|
||||
EXPECT_FALSE(EditMode::MayEnter(eGameMasterLevel::CIVILIAN, eGameMasterLevel::CIVILIAN));
|
||||
EXPECT_TRUE(EditMode::MayEnter(eGameMasterLevel::DEVELOPER, eGameMasterLevel::JUNIOR_DEVELOPER));
|
||||
|
||||
// editor_gm_level, 9 unless set
|
||||
EXPECT_EQ(EditMode::RequiredLevel(), eGameMasterLevel::OPERATOR);
|
||||
Game::config->SetDatabaseValues({ { "editor_gm_level", "8" } }, {});
|
||||
EXPECT_EQ(EditMode::RequiredLevel(), eGameMasterLevel::DEVELOPER);
|
||||
Game::config->SetDatabaseValues({ { "editor_gm_level", "0" } }, {});
|
||||
EXPECT_EQ(EditMode::RequiredLevel(), eGameMasterLevel::FORUM_MODERATOR);
|
||||
Game::config->SetDatabaseValues({}, {});
|
||||
}
|
||||
|
||||
// In edit mode a level file's objects and spawners are not made; the player start marker still gives the spawn point
|
||||
TEST_F(EditModeTests, LevelObjectsAreNotMade) {
|
||||
Game::zoneManager->SetEditMode(true);
|
||||
ASSERT_TRUE(Game::zoneManager->GetEditMode());
|
||||
|
||||
std::vector<SceneObject> objects;
|
||||
SceneObject object;
|
||||
object.id = 1001;
|
||||
object.lot = 2365;
|
||||
objects.push_back(object);
|
||||
SceneObject spawner;
|
||||
spawner.id = 1002;
|
||||
spawner.lot = 176;
|
||||
objects.push_back(spawner);
|
||||
SceneObject start;
|
||||
start.id = 1003;
|
||||
start.lot = LOT_MARKER_PLAYER_START;
|
||||
start.position = NiPoint3(10.0f, 20.0f, 30.0f);
|
||||
objects.push_back(start);
|
||||
|
||||
Level::LoadSceneObjects(objects, 4);
|
||||
|
||||
// (a level object's ID gets the client bit when it is made)
|
||||
for (const LWOOBJID id : { 1001, 1002, 1003 }) {
|
||||
EXPECT_EQ(Game::entityManager->GetEntity(id), nullptr);
|
||||
LWOOBJID made = id;
|
||||
GeneralUtils::SetBit(made, eObjectBits::CLIENT);
|
||||
EXPECT_EQ(Game::entityManager->GetEntity(made), nullptr);
|
||||
}
|
||||
EXPECT_TRUE(Game::zoneManager->GetSpawners().empty());
|
||||
EXPECT_EQ(Game::zoneManager->GetZone()->GetSpawnPos(), NiPoint3(10.0f, 20.0f, 30.0f));
|
||||
}
|
||||
Reference in New Issue
Block a user