mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 07:47:13 +00:00
fix: the master server only starts instances of zones in the ZoneTable
A transfer or private zone request started a world server for whatever map ID it named, and for as many instances as were asked for. A new instance now needs a row in the ZoneTable for its zone, and no more than max_instances (masterconfig.ini, 256 by default, 0 for no limit) run at once. The character select has no row and is always started. A request that gets no instance is logged and dropped. Before, the handlers went on to use the missing instance. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
32
dMasterServer/InstanceLimits.h
Normal file
32
dMasterServer/InstanceLimits.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef __INSTANCELIMITS__H__
|
||||
#define __INSTANCELIMITS__H__
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace InstanceLimits {
|
||||
// The most world instances that run at once when the config does not say
|
||||
constexpr uint32_t DefaultMaxInstances = 256;
|
||||
|
||||
enum class eNewInstance : uint8_t {
|
||||
Allowed,
|
||||
UnknownZone,
|
||||
TooManyInstances
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether a new world instance may be started for a zone.
|
||||
* @param mapID the zone asked for, 0 is the character select and has no row in the ZoneTable
|
||||
* @param isInZoneTable whether the ZoneTable has a row for the zone
|
||||
* @param runningInstances the instances that run already
|
||||
* @param maxInstances the most instances that may run at once, 0 for no limit
|
||||
*/
|
||||
constexpr eNewInstance CheckNewInstance(const uint32_t mapID, const bool isInZoneTable, const uint64_t runningInstances, const uint32_t maxInstances) {
|
||||
// Players always have to be able to reach the character select
|
||||
if (mapID == 0) return eNewInstance::Allowed;
|
||||
if (!isInZoneTable) return eNewInstance::UnknownZone;
|
||||
if (maxInstances != 0 && runningInstances >= maxInstances) return eNewInstance::TooManyInstances;
|
||||
return eNewInstance::Allowed;
|
||||
}
|
||||
}
|
||||
|
||||
#endif //!__INSTANCELIMITS__H__
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "CDClientManager.h"
|
||||
#include "CDZoneTableTable.h"
|
||||
#include "Database.h"
|
||||
#include "InstanceLimits.h"
|
||||
#include "MasterPackets.h"
|
||||
#include "BitStreamUtils.h"
|
||||
#include "ServiceType.h"
|
||||
@@ -46,6 +47,23 @@ InstanceManager::InstanceManager(const std::string& externalIP) : mExternalIP{ e
|
||||
m_LastPort =
|
||||
GeneralUtils::TryParse<uint16_t>(Game::config->GetValue("world_port_start")).value_or(m_LastPort);
|
||||
m_LastInstanceID = LWOINSTANCEID_INVALID;
|
||||
m_MaxInstances =
|
||||
GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("max_instances")).value_or(InstanceLimits::DefaultMaxInstances);
|
||||
}
|
||||
|
||||
bool InstanceManager::CanCreateInstance(const LWOMAPID mapID, const LWOCLONEID cloneID) const {
|
||||
const bool isInZoneTable = mapID != 0 && CDZoneTableTable::Query(mapID) != nullptr;
|
||||
|
||||
switch (InstanceLimits::CheckNewInstance(mapID, isInZoneTable, m_Instances.size(), m_MaxInstances)) {
|
||||
case InstanceLimits::eNewInstance::UnknownZone:
|
||||
LOG("Tried to create an instance of map %i/%i, which is not in the ZoneTable.", mapID, cloneID);
|
||||
return false;
|
||||
case InstanceLimits::eNewInstance::TooManyInstances:
|
||||
LOG("Tried to create an instance of map %i/%i, but %zu instances run already and max_instances is %u.", mapID, cloneID, m_Instances.size(), m_MaxInstances);
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const InstancePtr& InstanceManager::GetInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneID) {
|
||||
@@ -65,6 +83,9 @@ const InstancePtr& InstanceManager::CreateInstance(LWOMAPID mapID, LWOCLONEID cl
|
||||
cloneID);
|
||||
return g_Empty;
|
||||
}
|
||||
|
||||
if (!CanCreateInstance(mapID, cloneID)) return g_Empty;
|
||||
|
||||
int softCap = 8;
|
||||
int maxPlayers = 12;
|
||||
|
||||
@@ -360,6 +381,8 @@ const InstancePtr& InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLO
|
||||
return g_Empty;
|
||||
}
|
||||
|
||||
if (!CanCreateInstance(mapID, cloneID)) return g_Empty;
|
||||
|
||||
int maxPlayers = 999;
|
||||
|
||||
uint32_t port = GetFreePort();
|
||||
|
||||
@@ -234,7 +234,17 @@ private:
|
||||
std::map<LWOMAPID, IServerOperations::ZoneLimit> m_ZoneLimits;
|
||||
std::map<LWOMAPID, SpareBackoff> m_SpareBackoff;
|
||||
|
||||
/**
|
||||
* The most instances that run at once, 0 for no limit
|
||||
*/
|
||||
uint32_t m_MaxInstances = 0;
|
||||
|
||||
//Private functions:
|
||||
|
||||
/**
|
||||
* Whether a new instance may be started for a zone, logs the reason when it may not
|
||||
*/
|
||||
bool CanCreateInstance(LWOMAPID mapID, LWOCLONEID cloneID) const;
|
||||
int GetSoftCap(LWOMAPID mapID);
|
||||
int GetHardCap(LWOMAPID mapID);
|
||||
const InstancePtr& CreateInstance(LWOMAPID mapID, LWOCLONEID cloneID);
|
||||
|
||||
@@ -745,6 +745,10 @@ namespace {
|
||||
return;
|
||||
}
|
||||
const auto& in = Game::im->GetInstance(zoneID, false, zoneClone);
|
||||
if (!in) {
|
||||
LOG("No instance for transfer request %llu to zone %i %i", requestID, zoneID, zoneClone);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& instance : Game::im->GetInstances()) {
|
||||
LOG("Instance: %i/%i/%i -> %i %s", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance == in, instance->GetSysAddr().ToString());
|
||||
@@ -887,6 +891,10 @@ namespace {
|
||||
void OnCreatePrivateZone(const CreatePrivateZone& request, const SystemAddress& sysAddr) {
|
||||
// Passwords were cut to 50 characters when read
|
||||
const auto& newInst = Game::im->CreatePrivateInstance(request.zoneID, request.cloneID, request.password.c_str());
|
||||
if (!newInst) {
|
||||
LOG("No private zone was created for map %i/%i", request.zoneID, request.cloneID);
|
||||
return;
|
||||
}
|
||||
LOG("Creating private zone %i/%i/%i", newInst->GetMapID(), newInst->GetCloneID(), newInst->GetInstanceID());
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@ master_server_port=2000
|
||||
# The port number to start world servers on. Will be incremented for each world
|
||||
world_port_start=3000
|
||||
|
||||
# The most world instances that run at once. The character select is always started.
|
||||
# A zone or property that needs a new instance past this is not started. 0 for no limit
|
||||
max_instances=256
|
||||
|
||||
# 0 or 1, should autostart auth, chat, and char servers
|
||||
prestart_servers=1
|
||||
|
||||
|
||||
@@ -62,6 +62,9 @@ if(APPLE)
|
||||
add_dependencies(dGameTests dGameTestsLink)
|
||||
endif()
|
||||
|
||||
# InstanceLimits.h
|
||||
target_include_directories(dGameTests PRIVATE "${PROJECT_SOURCE_DIR}/dMasterServer")
|
||||
|
||||
# Local capture fixtures (tests/fixtures-local, never committed) are found from the source folder
|
||||
target_compile_definitions(dGameTests PRIVATE DLU_SOURCE_DIR="${PROJECT_SOURCE_DIR}")
|
||||
target_link_libraries(dGameTests ${COMMON_LIBRARIES} sqlite3 GTest::gtest_main
|
||||
|
||||
@@ -20,6 +20,7 @@ SET(DGAMEMESSAGES_TESTS
|
||||
"ClientMessagesTests.cpp"
|
||||
"LoadSequenceTests.cpp"
|
||||
"ChatRoutingTests.cpp"
|
||||
"InstanceLimitsTests.cpp"
|
||||
"TradeOfferTests.cpp"
|
||||
"VendorPricingTests.cpp")
|
||||
|
||||
|
||||
34
tests/dGameTests/dGameMessagesTests/InstanceLimitsTests.cpp
Normal file
34
tests/dGameTests/dGameMessagesTests/InstanceLimitsTests.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "InstanceLimits.h"
|
||||
|
||||
using InstanceLimits::CheckNewInstance;
|
||||
using InstanceLimits::eNewInstance;
|
||||
|
||||
TEST(InstanceLimitsTest, AZoneOfTheZoneTableIsStarted) {
|
||||
EXPECT_EQ(CheckNewInstance(1000, true, 0, 256), eNewInstance::Allowed);
|
||||
EXPECT_EQ(CheckNewInstance(1150, true, 255, 256), eNewInstance::Allowed);
|
||||
}
|
||||
|
||||
TEST(InstanceLimitsTest, AZoneTheZoneTableDoesNotHaveIsRefused) {
|
||||
EXPECT_EQ(CheckNewInstance(31337, false, 0, 256), eNewInstance::UnknownZone);
|
||||
EXPECT_EQ(CheckNewInstance(31337, false, 0, 0), eNewInstance::UnknownZone);
|
||||
// The unknown zone is the reason given, also when the limit is reached too
|
||||
EXPECT_EQ(CheckNewInstance(31337, false, 256, 256), eNewInstance::UnknownZone);
|
||||
}
|
||||
|
||||
TEST(InstanceLimitsTest, NoInstanceIsStartedPastTheLimit) {
|
||||
EXPECT_EQ(CheckNewInstance(1000, true, 256, 256), eNewInstance::TooManyInstances);
|
||||
EXPECT_EQ(CheckNewInstance(1000, true, 1000, 256), eNewInstance::TooManyInstances);
|
||||
EXPECT_EQ(CheckNewInstance(1000, true, 1, 1), eNewInstance::TooManyInstances);
|
||||
}
|
||||
|
||||
TEST(InstanceLimitsTest, ALimitOfZeroIsNoLimit) {
|
||||
EXPECT_EQ(CheckNewInstance(1000, true, 100000, 0), eNewInstance::Allowed);
|
||||
}
|
||||
|
||||
TEST(InstanceLimitsTest, TheCharacterSelectIsAlwaysStarted) {
|
||||
// Zone 0 has no row in the ZoneTable
|
||||
EXPECT_EQ(CheckNewInstance(0, false, 0, 256), eNewInstance::Allowed);
|
||||
EXPECT_EQ(CheckNewInstance(0, false, 256, 256), eNewInstance::Allowed);
|
||||
}
|
||||
Reference in New Issue
Block a user