This commit is contained in:
Aaron Kimbrell
2025-09-03 05:03:53 -05:00
parent 8198ad70f6
commit b6a799a3e9
16 changed files with 2380 additions and 0 deletions

View File

@@ -322,6 +322,7 @@ add_subdirectory(dWorldServer)
add_subdirectory(dAuthServer)
add_subdirectory(dChatServer)
add_subdirectory(dMasterServer) # Add MasterServer last so it can rely on the other binaries
add_subdirectory(dSimClient) # Simulation client for testing and load testing
target_precompile_headers(
dZoneManager PRIVATE

56
build_simclient.bat Normal file
View File

@@ -0,0 +1,56 @@
@echo off
REM Simple build and test script for SimClient (Windows)
REM This script demonstrates how to build and run the SimClient
echo DarkflameServer SimClient Build and Test Script
echo ===============================================
REM Check if we're in the right directory
if not exist CMakeLists.txt (
echo Error: Please run this script from the DarkflameServer root directory
pause
exit /b 1
)
REM Create build directory if it doesn't exist
if not exist build (
echo Creating build directory...
mkdir build
)
cd build
REM Generate build files
echo Generating build files...
cmake .. -DCMAKE_BUILD_TYPE=Release
if errorlevel 1 (
echo Error: CMake configuration failed
pause
exit /b 1
)
REM Build SimClient
echo Building SimClient...
cmake --build . --target SimClient --config Release
if errorlevel 1 (
echo Error: Build failed
pause
exit /b 1
)
echo Build completed successfully!
echo.
echo SimClient executable location:
dir /s /b SimClient.exe 2>nul
echo.
echo To run SimClient:
echo SimClient.exe --help # Show help
echo SimClient.exe -t basic -v # Basic test with verbose logging
echo SimClient.exe -n 5 -t load # Load test with 5 clients
echo SimClient.exe -a 127.0.0.1:1001 -n 3 # Connect to specific auth server
echo.
echo Make sure your DarkflameServer is running before testing!
pause

53
build_simclient.sh Normal file
View File

@@ -0,0 +1,53 @@
#!/bin/bash
# Simple build and test script for SimClient
# This script demonstrates how to build and run the SimClient
echo "DarkflameServer SimClient Build and Test Script"
echo "==============================================="
# Check if we're in the right directory
if [ ! -f "CMakeLists.txt" ]; then
echo "Error: Please run this script from the DarkflameServer root directory"
exit 1
fi
# Create build directory if it doesn't exist
if [ ! -d "build" ]; then
echo "Creating build directory..."
mkdir build
fi
cd build
# Generate build files
echo "Generating build files..."
cmake .. -DCMAKE_BUILD_TYPE=Release
# Build SimClient
echo "Building SimClient..."
if command -v make >/dev/null 2>&1; then
make SimClient -j$(nproc)
else
# For systems without make, try cmake --build
cmake --build . --target SimClient --config Release
fi
if [ $? -ne 0 ]; then
echo "Error: Build failed"
exit 1
fi
echo "Build completed successfully!"
echo ""
echo "SimClient executable location:"
find . -name "SimClient*" -type f -executable 2>/dev/null | head -1
echo ""
echo "To run SimClient:"
echo " ./SimClient --help # Show help"
echo " ./SimClient -t basic -v # Basic test with verbose logging"
echo " ./SimClient -n 5 -t load # Load test with 5 clients"
echo " ./SimClient -a 127.0.0.1:1001 -n 3 # Connect to specific auth server"
echo ""
echo "Make sure your DarkflameServer is running before testing!"

29
dSimClient/CMakeLists.txt Normal file
View File

@@ -0,0 +1,29 @@
set(DSIMCLIENT_SOURCES
"SimClient.cpp"
"SimUser.cpp"
"SimUser.h"
"SimWorld.cpp"
"SimWorld.h"
"SimBehavior.cpp"
"SimBehavior.h"
"SimConfig.cpp"
"SimConfig.h"
)
add_executable(SimClient ${DSIMCLIENT_SOURCES})
target_link_libraries(SimClient ${COMMON_LIBRARIES})
target_include_directories(SimClient PRIVATE
"${PROJECT_SOURCE_DIR}/dCommon"
"${PROJECT_SOURCE_DIR}/dDatabase"
"${PROJECT_SOURCE_DIR}/dNet"
"${PROJECT_SOURCE_DIR}/thirdparty/raknet/Source"
"${PROJECT_SOURCE_DIR}/dServer"
)
# Copy the configuration file to build directory
configure_file(
"${PROJECT_SOURCE_DIR}/resources/simclient.ini"
"${CMAKE_CURRENT_BINARY_DIR}/simclient.ini"
COPYONLY
)

266
dSimClient/README.md Normal file
View File

@@ -0,0 +1,266 @@
# DarkflameServer SimClient
A simulation client for testing and load testing the DarkflameServer.
## Overview
The SimClient is a headless client that can simulate multiple player connections to test server performance, stability, and functionality. It's designed for:
- **Load Testing**: Simulate many concurrent users
- **Performance Testing**: Measure server response under load
- **Regression Testing**: Automated testing of server functionality
- **Development Testing**: Quick connectivity and feature testing
## Features
- **Multiple Client Simulation**: Spawn many simulated clients
- **Configurable Behaviors**: Movement, chat, random actions
- **Network Statistics**: Track packets, bytes, connection stats
- **Flexible Configuration**: File-based and command-line configuration
- **Different Test Modes**: Basic, load, stress, movement, chat tests
- **Real-time Monitoring**: Live statistics and status updates
## Building
The SimClient is built as part of the normal DarkflameServer build process:
```bash
mkdir build && cd build
cmake ..
make SimClient # or build SimClient target in Visual Studio
```
## Usage
### Basic Usage
```bash
# Run with default settings (1 client)
./SimClient
# Run load test with 10 clients
./SimClient -n 10 -t load
# Run with verbose logging
./SimClient -v -n 5
# Connect to remote server
./SimClient -a 192.168.1.100:1001 -w 192.168.1.100:2000
```
### Command Line Options
- `-h, --help` - Show help message
- `-c, --config <file>` - Configuration file (default: simclient.ini)
- `-n, --clients <count>` - Number of clients to simulate
- `-a, --auth <ip:port>` - Auth server address
- `-w, --world <ip:port>` - World server address
- `-v, --verbose` - Enable verbose logging
- `-t, --test <type>` - Test type (basic, load, stress, movement, chat)
### Test Types
- **basic** - Single client connectivity test
- **load** - Multiple client load test
- **stress** - High-load stress test (2x client count)
- **movement** - Movement simulation test
- **chat** - Chat functionality test
### Configuration File
Create `simclient.ini` to configure default settings:
```ini
# Server Settings
auth_server_ip=127.0.0.1
auth_server_port=1001
world_server_ip=127.0.0.1
world_server_port=2000
# Simulation Settings
client_count=5
spawn_delay_ms=2000
tick_rate_ms=16
# Behavior Settings
enable_movement=1
enable_chat=0
enable_random_actions=1
action_interval_ms=5000
# Test Accounts
test_account_1=testuser1:testpass1
test_account_2=testuser2:testpass2
```
## Simulated Behaviors
### Movement Simulation
- Random movement within zones
- Configurable movement patterns (random, circular, linear)
- Position updates sent to server
- Collision-free pathfinding
### Chat Simulation
- Predefined message sets (normal, spam, roleplay)
- Configurable chat intervals
- Multiple chat channels support
### Connection Management
- Automatic reconnection on failures
- Graceful disconnection handling
- Connection state tracking
- Error reporting and recovery
### Statistics Tracking
- Packets sent/received per client
- Bytes sent/received per client
- Connection success/failure rates
- Real-time performance metrics
## Use Cases
### Load Testing
Test server performance under various loads:
```bash
# Test with 50 concurrent users
./SimClient -t load -n 50
# Stress test with 100 users
./SimClient -t stress -n 50 # Will actually use 100 (2x)
```
### Development Testing
Quick testing during development:
```bash
# Basic connectivity test
./SimClient -t basic
# Test movement system
./SimClient -t movement -n 3
```
### Automated Testing
Integrate into CI/CD pipelines:
```bash
#!/bin/bash
# Start server
./MasterServer &
./AuthServer &
./WorldServer &
sleep 5
# Run automated tests
./SimClient -t basic || exit 1
./SimClient -t load -n 10 || exit 1
# Cleanup
killall MasterServer AuthServer WorldServer
```
### Performance Monitoring
Monitor server performance over time:
```bash
# Long-running load test with statistics
./SimClient -t load -n 25 -v > load_test_$(date +%Y%m%d_%H%M%S).log
```
## Architecture
### Components
- **SimClient** - Main executable and command-line interface
- **SimConfig** - Configuration management
- **SimWorld** - Simulation coordinator and statistics
- **SimUser** - Individual client simulation
- **SimBehavior** - Behavior patterns and actions
### Network Protocol
The SimClient implements a simplified version of the DarkflameServer client protocol:
1. **Authentication Flow**
- Connect to AuthServer
- Send login credentials
- Receive session token
- Get world server info
2. **World Connection**
- Connect to WorldServer
- Send session validation
- Load character/world data
- Enter simulation mode
3. **Simulation Loop**
- Send position updates
- Process server messages
- Execute configured behaviors
- Maintain connection health
### Error Handling
- Automatic retry on connection failures
- Graceful degradation on partial failures
- Detailed error logging and reporting
- Recovery strategies for different error types
## Troubleshooting
### Common Issues
**Connection Refused**
- Ensure servers are running and accessible
- Check firewall settings
- Verify IP addresses and ports
**Authentication Failures**
- Verify test account credentials
- Check database connectivity
- Review auth server logs
**High Memory Usage**
- Reduce client count for available RAM
- Monitor for memory leaks
- Use 64-bit build for large tests
**Performance Issues**
- Adjust tick rate and spawn delays
- Monitor server resource usage
- Check network bandwidth limits
### Debugging
Enable verbose logging to see detailed operations:
```bash
./SimClient -v -n 1 -t basic
```
Check log files in the `logs/` directory for detailed information.
## Contributing
When adding new features to SimClient:
1. Follow existing code patterns
2. Add appropriate error handling
3. Update configuration options
4. Add command-line parameters as needed
5. Update this documentation
6. Test with various scenarios
## Future Enhancements
Potential improvements for SimClient:
- **Protocol Coverage**: Support more game messages and features
- **Behavior Scripting**: Lua or Python scripting for custom behaviors
- **Performance Profiling**: Built-in server performance monitoring
- **Web Interface**: Real-time web dashboard for monitoring
- **Distributed Testing**: Coordinate multiple SimClient instances
- **Recording/Playback**: Record real client sessions and replay them

162
dSimClient/SimBehavior.cpp Normal file
View File

@@ -0,0 +1,162 @@
#include "SimBehavior.h"
#include "SimUser.h"
#include <random>
#include <cmath>
SimBehavior::MovementPattern SimBehavior::GetRandomMovementPattern() {
MovementPattern pattern;
pattern.minDistance = 5.0f;
pattern.maxDistance = 15.0f;
pattern.moveSpeed = 5.0f;
pattern.pauseTime = 2.0f;
return pattern;
}
SimBehavior::MovementPattern SimBehavior::GetCircularMovementPattern(float radius) {
MovementPattern pattern;
pattern.minDistance = radius * 0.8f;
pattern.maxDistance = radius * 1.2f;
pattern.moveSpeed = 3.0f;
pattern.pauseTime = 1.0f;
return pattern;
}
SimBehavior::MovementPattern SimBehavior::GetLinearMovementPattern(float distance) {
MovementPattern pattern;
pattern.minDistance = distance;
pattern.maxDistance = distance;
pattern.moveSpeed = 4.0f;
pattern.pauseTime = 0.5f;
return pattern;
}
SimBehavior::ChatPattern SimBehavior::GetRandomChatPattern() {
ChatPattern pattern;
pattern.messages = {
"Hello everyone!",
"How's everyone doing?",
"Nice day for building!",
"Anyone want to race?",
"This place looks cool!",
"Great work on that build!",
"Thanks for the help!",
"See you around!",
"What's your favorite zone?",
"This game is awesome!"
};
pattern.intervalMin = 30.0f; // 30 seconds minimum
pattern.intervalMax = 120.0f; // 2 minutes maximum
return pattern;
}
SimBehavior::ChatPattern SimBehavior::GetSpamChatPattern() {
ChatPattern pattern;
pattern.messages = {
"Test message 1",
"Test message 2",
"Test message 3",
"Spam test",
"Load test message"
};
pattern.intervalMin = 1.0f; // 1 second minimum
pattern.intervalMax = 3.0f; // 3 seconds maximum
return pattern;
}
SimBehavior::ChatPattern SimBehavior::GetRoleplayChatPattern() {
ChatPattern pattern;
pattern.messages = {
"*waves to everyone*",
"*looks around curiously*",
"*examines the nearby structures*",
"*checks inventory*",
"*stretches after a long journey*",
"*admires the scenery*",
"*practices building techniques*",
"*takes a short break*",
"*prepares for the next adventure*",
"*organizes backpack*"
};
pattern.intervalMin = 45.0f; // 45 seconds minimum
pattern.intervalMax = 180.0f; // 3 minutes maximum
return pattern;
}
void SimBehavior::ExecuteRandomMovement(SimUser* user, const MovementPattern& pattern) {
if (!user) return;
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_real_distribution<float> angleDist(0.0f, 2.0f * 3.14159f);
static std::uniform_real_distribution<float> distDist(pattern.minDistance, pattern.maxDistance);
// Generate random direction and distance
float angle = angleDist(gen);
float distance = distDist(gen);
// Calculate target position
NiPoint3 currentPos = user->GetPosition();
NiPoint3 targetPos;
targetPos.x = currentPos.x + distance * std::cos(angle);
targetPos.y = currentPos.y;
targetPos.z = currentPos.z + distance * std::sin(angle);
// Simulate movement (this would be implemented in SimUser)
// user->MoveTo(targetPos, pattern.moveSpeed);
}
void SimBehavior::ExecuteChat(SimUser* user, const ChatPattern& pattern) {
if (!user || pattern.messages.empty()) return;
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_int_distribution<size_t> msgDist(0, pattern.messages.size() - 1);
// Select random message
size_t messageIndex = msgDist(gen);
const std::string& message = pattern.messages[messageIndex];
user->SendChatMessage(message);
}
void SimBehavior::ExecuteIdleBehavior(SimUser* user) {
if (!user) return;
// Simple idle behavior - just send occasional heartbeats
// The user's Update method handles most of this
}
void SimBehavior::ExecuteStressTest(SimUser* user) {
if (!user) return;
// Stress test behavior - rapid actions
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_int_distribution<int> actionDist(1, 4);
int action = actionDist(gen);
switch (action) {
case 1:
// Rapid movement
ExecuteRandomMovement(user, GetRandomMovementPattern());
break;
case 2:
// Rapid chat
ExecuteChat(user, GetSpamChatPattern());
break;
case 3:
// Multiple position updates
for (int i = 0; i < 5; i++) {
user->SimulateMovement();
}
break;
case 4:
// Just maintain connection
ExecuteIdleBehavior(user);
break;
}
}

51
dSimClient/SimBehavior.h Normal file
View File

@@ -0,0 +1,51 @@
#pragma once
#include <vector>
#include <string>
/**
* @brief Defines and manages different test behaviors for simulated clients
*/
class SimBehavior {
public:
enum class BehaviorType {
IDLE,
RANDOM_MOVEMENT,
CHAT_SPAM,
RAPID_RECONNECT,
INVENTORY_ACTIONS,
SKILL_USAGE,
BUILDING_ACTIONS,
RACING_ACTIONS
};
struct MovementPattern {
float minDistance;
float maxDistance;
float moveSpeed;
float pauseTime;
};
struct ChatPattern {
std::vector<std::string> messages;
float intervalMin;
float intervalMax;
};
static MovementPattern GetRandomMovementPattern();
static MovementPattern GetCircularMovementPattern(float radius);
static MovementPattern GetLinearMovementPattern(float distance);
static ChatPattern GetRandomChatPattern();
static ChatPattern GetSpamChatPattern();
static ChatPattern GetRoleplayChatPattern();
// Behavior executors
static void ExecuteRandomMovement(class SimUser* user, const MovementPattern& pattern);
static void ExecuteChat(class SimUser* user, const ChatPattern& pattern);
static void ExecuteIdleBehavior(class SimUser* user);
static void ExecuteStressTest(class SimUser* user);
private:
SimBehavior() = delete;
};

230
dSimClient/SimClient.cpp Normal file
View File

@@ -0,0 +1,230 @@
#include <iostream>
#include <memory>
#include <string>
#include <csignal>
#include <thread>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
// DLU Includes
#include "Logger.h"
#include "dConfig.h"
#include "Game.h"
#include "Server.h"
#include "BinaryPathFinder.h"
// SimClient includes
#include "SimConfig.h"
#include "SimWorld.h"
#include "SimUser.h"
#include "SimBehavior.h"
namespace Game {
Logger* logger = nullptr;
dConfig* config = nullptr;
Game::signal_t lastSignal = 0;
}
void PrintUsage(const char* programName) {
std::cout << "Usage: " << programName << " [options]" << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " -h, --help Show this help message" << std::endl;
std::cout << " -c, --config <file> Configuration file (default: simclient.ini)" << std::endl;
std::cout << " -n, --clients <count> Number of clients to simulate (default: 1)" << std::endl;
std::cout << " -a, --auth <ip:port> Auth server address (default: 127.0.0.1:1001)" << std::endl;
std::cout << " -w, --world <ip:port> World server address (default: 127.0.0.1:2000)" << std::endl;
std::cout << " -v, --verbose Enable verbose logging" << std::endl;
std::cout << " -t, --test <type> Run specific test type:" << std::endl;
std::cout << " basic - Basic connectivity test (1 client)" << std::endl;
std::cout << " load - Load test (multiple clients)" << std::endl;
std::cout << " stress - Stress test (high load)" << std::endl;
std::cout << " movement - Movement simulation test" << std::endl;
std::cout << " chat - Chat functionality test" << std::endl;
std::cout << std::endl;
std::cout << "Examples:" << std::endl;
std::cout << " " << programName << " -n 10 -v # 10 clients with verbose logging" << std::endl;
std::cout << " " << programName << " -t load -n 50 # Load test with 50 clients" << std::endl;
std::cout << " " << programName << " -a 192.168.1.100:1001 # Connect to remote server" << std::endl;
}
void SignalHandler(int signal) {
std::cout << std::endl << "Received signal " << signal << ", shutting down..." << std::endl;
Game::lastSignal = signal;
}
bool ParseAddress(const std::string& addr, std::string& ip, uint16_t& port) {
size_t colonPos = addr.find(':');
if (colonPos == std::string::npos) {
return false;
}
ip = addr.substr(0, colonPos);
try {
port = static_cast<uint16_t>(std::stoul(addr.substr(colonPos + 1)));
return true;
} catch (const std::exception&) {
return false;
}
}
int main(int argc, char* argv[]) {
std::cout << "DarkflameServer Simulation Client" << std::endl;
std::cout << "Version: " << PROJECT_VERSION << std::endl;
std::cout << "Compiled on: " << __TIMESTAMP__ << std::endl;
std::cout << std::endl;
// Set up signal handling
std::signal(SIGINT, SignalHandler);
std::signal(SIGTERM, SignalHandler);
// Parse command line arguments
std::string configFile = "simclient.ini";
std::string testType = "load";
bool showHelp = false;
auto config = std::make_shared<SimConfig>();
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "-h" || arg == "--help") {
showHelp = true;
} else if (arg == "-c" || arg == "--config") {
if (i + 1 < argc) {
configFile = argv[++i];
} else {
std::cerr << "Error: " << arg << " requires a filename" << std::endl;
return 1;
}
} else if (arg == "-n" || arg == "--clients") {
if (i + 1 < argc) {
try {
uint32_t clientCount = std::stoul(argv[++i]);
config->SetClientCount(clientCount);
} catch (const std::exception&) {
std::cerr << "Error: Invalid client count" << std::endl;
return 1;
}
} else {
std::cerr << "Error: " << arg << " requires a number" << std::endl;
return 1;
}
} else if (arg == "-a" || arg == "--auth") {
if (i + 1 < argc) {
std::string ip;
uint16_t port;
if (ParseAddress(argv[++i], ip, port)) {
config->SetAuthServer(ip, port);
} else {
std::cerr << "Error: Invalid auth server address format (use ip:port)" << std::endl;
return 1;
}
} else {
std::cerr << "Error: " << arg << " requires an address" << std::endl;
return 1;
}
} else if (arg == "-w" || arg == "--world") {
if (i + 1 < argc) {
std::string ip;
uint16_t port;
if (ParseAddress(argv[++i], ip, port)) {
config->SetWorldServer(ip, port);
} else {
std::cerr << "Error: Invalid world server address format (use ip:port)" << std::endl;
return 1;
}
} else {
std::cerr << "Error: " << arg << " requires an address" << std::endl;
return 1;
}
} else if (arg == "-v" || arg == "--verbose") {
config->SetVerboseLogging(true);
} else if (arg == "-t" || arg == "--test") {
if (i + 1 < argc) {
testType = argv[++i];
} else {
std::cerr << "Error: " << arg << " requires a test type" << std::endl;
return 1;
}
} else {
std::cerr << "Error: Unknown argument: " << arg << std::endl;
return 1;
}
}
if (showHelp) {
PrintUsage(argv[0]);
return 0;
}
// Setup logging
const auto logsDir = BinaryPathFinder::GetBinaryDir() / "logs";
if (!std::filesystem::exists(logsDir)) {
std::filesystem::create_directories(logsDir);
}
std::string logPath = (logsDir / "SimClient").string() + "_" + std::to_string(time(nullptr)) + ".log";
Game::logger = new Logger(logPath, true, config->GetVerboseLogging());
if (Game::logger) {
#ifdef _WIN32
LOG("SimClient started with PID %lu", GetCurrentProcessId());
#else
LOG("SimClient started with PID %d", getpid());
#endif
}
// Load configuration file if it exists
if (!config->LoadFromFile(configFile)) {
std::cout << "Using default configuration (config file not found or invalid)" << std::endl;
}
// Create and initialize simulation world
SimWorld simWorld;
if (!simWorld.Initialize(config)) {
std::cerr << "Failed to initialize simulation world" << std::endl;
return 1;
}
std::cout << "Starting simulation..." << std::endl;
std::cout << "Press Ctrl+C to stop" << std::endl;
std::cout << std::endl;
// Run the appropriate test
try {
if (testType == "basic") {
simWorld.RunBasicConnectivityTest();
} else if (testType == "load") {
simWorld.RunLoadTest();
} else if (testType == "stress") {
simWorld.RunStressTest();
} else if (testType == "movement") {
simWorld.RunMovementTest();
} else if (testType == "chat") {
simWorld.RunChatTest();
} else {
std::cerr << "Unknown test type: " << testType << std::endl;
std::cerr << "Valid types: basic, load, stress, movement, chat" << std::endl;
return 1;
}
} catch (const std::exception& e) {
std::cerr << "Exception during simulation: " << e.what() << std::endl;
return 1;
}
// Clean shutdown
simWorld.Shutdown();
if (Game::logger) {
LOG("SimClient shutting down");
delete Game::logger;
Game::logger = nullptr;
}
std::cout << "Simulation complete!" << std::endl;
return 0;
}

113
dSimClient/SimConfig.cpp Normal file
View File

@@ -0,0 +1,113 @@
#include "SimConfig.h"
#include <fstream>
#include <sstream>
#include <iostream>
SimConfig::SimConfig()
: m_AuthServerIP("127.0.0.1")
, m_AuthServerPort(1001)
, m_WorldServerIP("127.0.0.1")
, m_WorldServerPort(2000)
, m_ClientCount(1)
, m_SpawnDelayMs(1000)
, m_TickRateMs(16)
, m_EnableMovement(true)
, m_EnableChat(false)
, m_EnableRandomActions(true)
, m_ActionIntervalMs(5000)
, m_VerboseLogging(false)
, m_LogFile("simclient.log")
{
// Add some default test accounts
m_TestAccounts.push_back(std::make_pair("testuser1", "testpass1"));
m_TestAccounts.push_back(std::make_pair("testuser2", "testpass2"));
m_TestAccounts.push_back(std::make_pair("testuser3", "testpass3"));
m_TestAccounts.push_back(std::make_pair("testuser4", "testpass4"));
m_TestAccounts.push_back(std::make_pair("testuser5", "testpass5"));
}
SimConfig::~SimConfig() {
}
bool SimConfig::LoadFromFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
std::cout << "Warning: Could not open config file '" << filename << "', using defaults" << std::endl;
return false;
}
std::string line;
while (std::getline(file, line)) {
// Skip comments and empty lines
if (line.empty() || line[0] == '#') {
continue;
}
size_t equalsPos = line.find('=');
if (equalsPos == std::string::npos) {
continue;
}
std::string key = line.substr(0, equalsPos);
std::string value = line.substr(equalsPos + 1);
// Trim whitespace
key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t") + 1);
// Parse configuration values
if (key == "auth_server_ip") {
m_AuthServerIP = value;
} else if (key == "auth_server_port") {
m_AuthServerPort = static_cast<uint16_t>(std::stoul(value));
} else if (key == "world_server_ip") {
m_WorldServerIP = value;
} else if (key == "world_server_port") {
m_WorldServerPort = static_cast<uint16_t>(std::stoul(value));
} else if (key == "client_count") {
m_ClientCount = static_cast<uint32_t>(std::stoul(value));
} else if (key == "spawn_delay_ms") {
m_SpawnDelayMs = static_cast<uint32_t>(std::stoul(value));
} else if (key == "tick_rate_ms") {
m_TickRateMs = static_cast<uint32_t>(std::stoul(value));
} else if (key == "enable_movement") {
m_EnableMovement = (value == "1" || value == "true");
} else if (key == "enable_chat") {
m_EnableChat = (value == "1" || value == "true");
} else if (key == "enable_random_actions") {
m_EnableRandomActions = (value == "1" || value == "true");
} else if (key == "action_interval_ms") {
m_ActionIntervalMs = static_cast<uint32_t>(std::stoul(value));
} else if (key == "verbose_logging") {
m_VerboseLogging = (value == "1" || value == "true");
} else if (key == "log_file") {
m_LogFile = value;
} else if (key.substr(0, 12) == "test_account") {
// Parse test_account_1=username:password format
size_t colonPos = value.find(':');
if (colonPos != std::string::npos) {
std::string username = value.substr(0, colonPos);
std::string password = value.substr(colonPos + 1);
// Check if this account already exists
bool found = false;
for (auto& account : m_TestAccounts) {
if (account.first == username) {
account.second = password;
found = true;
break;
}
}
if (!found) {
m_TestAccounts.push_back(std::make_pair(username, password));
}
}
}
}
file.close();
return true;
}

72
dSimClient/SimConfig.h Normal file
View File

@@ -0,0 +1,72 @@
#pragma once
#include <string>
#include <vector>
#include <memory>
/**
* @brief Configuration class for simulation client
*/
class SimConfig {
public:
SimConfig();
~SimConfig();
// Load configuration from file
bool LoadFromFile(const std::string& filename);
// Server connection settings
std::string GetAuthServerIP() const { return m_AuthServerIP; }
uint16_t GetAuthServerPort() const { return m_AuthServerPort; }
std::string GetWorldServerIP() const { return m_WorldServerIP; }
uint16_t GetWorldServerPort() const { return m_WorldServerPort; }
// Client simulation settings
uint32_t GetClientCount() const { return m_ClientCount; }
uint32_t GetSpawnDelayMs() const { return m_SpawnDelayMs; }
uint32_t GetTickRateMs() const { return m_TickRateMs; }
// Test behavior settings
bool GetEnableMovement() const { return m_EnableMovement; }
bool GetEnableChat() const { return m_EnableChat; }
bool GetEnableRandomActions() const { return m_EnableRandomActions; }
uint32_t GetActionIntervalMs() const { return m_ActionIntervalMs; }
// Authentication settings
std::vector<std::pair<std::string, std::string>> GetTestAccounts() const { return m_TestAccounts; }
// Logging settings
bool GetVerboseLogging() const { return m_VerboseLogging; }
std::string GetLogFile() const { return m_LogFile; }
// Setters for programmatic configuration
void SetAuthServer(const std::string& ip, uint16_t port) { m_AuthServerIP = ip; m_AuthServerPort = port; }
void SetWorldServer(const std::string& ip, uint16_t port) { m_WorldServerIP = ip; m_WorldServerPort = port; }
void SetClientCount(uint32_t count) { m_ClientCount = count; }
void SetVerboseLogging(bool enabled) { m_VerboseLogging = enabled; }
private:
// Server connection
std::string m_AuthServerIP;
uint16_t m_AuthServerPort;
std::string m_WorldServerIP;
uint16_t m_WorldServerPort;
// Client simulation
uint32_t m_ClientCount;
uint32_t m_SpawnDelayMs;
uint32_t m_TickRateMs;
// Test behavior
bool m_EnableMovement;
bool m_EnableChat;
bool m_EnableRandomActions;
uint32_t m_ActionIntervalMs;
// Authentication
std::vector<std::pair<std::string, std::string>> m_TestAccounts;
// Logging
bool m_VerboseLogging;
std::string m_LogFile;
};

413
dSimClient/SimUser.cpp Normal file
View File

@@ -0,0 +1,413 @@
#include "SimUser.h"
#include "RakPeerInterface.h"
#include "RakNetworkFactory.h"
#include "BitStream.h"
#include "MessageIdentifiers.h"
#include "Logger.h"
#include "dNetCommon.h"
#include "Game.h"
#include <iostream>
#include <algorithm>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
SimUser::SimUser(uint32_t id, const std::string& username, const std::string& password)
: m_ID(id)
, m_Username(username)
, m_Password(password)
, m_State(SimUserState::DISCONNECTED)
, m_AuthPeer(nullptr)
, m_WorldPeer(nullptr)
, m_CharacterID(LWOOBJID_EMPTY)
, m_WorldInstanceID(0)
, m_ZoneID(1000) // Default to Venture Explorer
, m_Position(NiPoint3::ZERO)
, m_Rotation(NiQuaternion::IDENTITY)
, m_Velocity(NiPoint3::ZERO)
, m_TargetPosition(NiPoint3::ZERO)
, m_IsMoving(false)
, m_PacketsSent(0)
, m_PacketsReceived(0)
, m_BytesSent(0)
, m_BytesReceived(0)
, m_HasError(false)
, m_RandomEngine(std::random_device{}())
, m_RandomFloat(0.0f, 1.0f)
{
m_LastUpdate = std::chrono::steady_clock::now();
m_LastHeartbeat = std::chrono::steady_clock::now();
m_LastRandomAction = std::chrono::steady_clock::now();
}
SimUser::~SimUser() {
Disconnect();
}
bool SimUser::ConnectToAuth(const std::string& authIP, uint16_t authPort) {
if (m_State != SimUserState::DISCONNECTED) {
SetError("Cannot connect to auth server: already connected or in error state");
return false;
}
m_AuthPeer = RakNetworkFactory::GetRakPeerInterface();
if (!m_AuthPeer) {
SetError("Failed to create RakPeer interface for auth connection");
return false;
}
SocketDescriptor socketDescriptor(0, nullptr);
if (m_AuthPeer->Startup(1, 30, &socketDescriptor, 1) != RAKNET_STARTED) {
SetError("Failed to startup auth peer");
RakNetworkFactory::DestroyRakPeerInterface(m_AuthPeer);
m_AuthPeer = nullptr;
return false;
}
if (m_AuthPeer->Connect(authIP.c_str(), authPort, nullptr, 0) != CONNECTION_ATTEMPT_STARTED) {
SetError("Failed to start connection attempt to auth server");
m_AuthPeer->Shutdown(0);
RakNetworkFactory::DestroyRakPeerInterface(m_AuthPeer);
m_AuthPeer = nullptr;
return false;
}
m_AuthServerAddr = SystemAddress(authIP.c_str(), authPort);
SetState(SimUserState::CONNECTING_TO_AUTH);
LogMessage("Connecting to auth server at " + authIP + ":" + std::to_string(authPort));
return true;
}
bool SimUser::ConnectToWorld(const std::string& worldIP, uint16_t worldPort) {
if (m_State != SimUserState::AUTHENTICATING && m_State != SimUserState::CONNECTING_TO_WORLD) {
SetError("Cannot connect to world server: not in correct state");
return false;
}
m_WorldPeer = RakNetworkFactory::GetRakPeerInterface();
if (!m_WorldPeer) {
SetError("Failed to create RakPeer interface for world connection");
return false;
}
SocketDescriptor socketDescriptor(0, nullptr);
if (m_WorldPeer->Startup(1, 30, &socketDescriptor, 1) != RAKNET_STARTED) {
SetError("Failed to startup world peer");
RakNetworkFactory::DestroyRakPeerInterface(m_WorldPeer);
m_WorldPeer = nullptr;
return false;
}
if (m_WorldPeer->Connect(worldIP.c_str(), worldPort, nullptr, 0) != CONNECTION_ATTEMPT_STARTED) {
SetError("Failed to start connection attempt to world server");
m_WorldPeer->Shutdown(0);
RakNetworkFactory::DestroyRakPeerInterface(m_WorldPeer);
m_WorldPeer = nullptr;
return false;
}
m_WorldServerAddr = SystemAddress(worldIP.c_str(), worldPort);
SetState(SimUserState::CONNECTING_TO_WORLD);
LogMessage("Connecting to world server at " + worldIP + ":" + std::to_string(worldPort));
return true;
}
void SimUser::Disconnect() {
if (m_AuthPeer) {
m_AuthPeer->Shutdown(100);
RakNetworkFactory::DestroyRakPeerInterface(m_AuthPeer);
m_AuthPeer = nullptr;
}
if (m_WorldPeer) {
m_WorldPeer->Shutdown(100);
RakNetworkFactory::DestroyRakPeerInterface(m_WorldPeer);
m_WorldPeer = nullptr;
}
SetState(SimUserState::DISCONNECTED);
}
void SimUser::ProcessIncomingPackets() {
// Process auth server packets
if (m_AuthPeer) {
Packet* packet = nullptr;
while ((packet = m_AuthPeer->Receive()) != nullptr) {
m_PacketsReceived++;
m_BytesReceived += packet->length;
HandleAuthPacket(packet);
m_AuthPeer->DeallocatePacket(packet);
}
}
// Process world server packets
if (m_WorldPeer) {
Packet* packet = nullptr;
while ((packet = m_WorldPeer->Receive()) != nullptr) {
m_PacketsReceived++;
m_BytesReceived += packet->length;
HandleWorldPacket(packet);
m_WorldPeer->DeallocatePacket(packet);
}
}
}
void SimUser::SendHeartbeat() {
auto now = std::chrono::steady_clock::now();
auto timeSinceLastHeartbeat = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_LastHeartbeat);
if (timeSinceLastHeartbeat.count() >= 5000) { // Send heartbeat every 5 seconds
// Send to appropriate server based on current state
RakNet::BitStream heartbeat;
heartbeat.Write(static_cast<MessageID>(ID_USER_PACKET_ENUM + 1)); // Simple heartbeat
if (m_State == SimUserState::IN_WORLD && m_WorldPeer) {
m_WorldPeer->Send(&heartbeat, HIGH_PRIORITY, RELIABLE, 0, m_WorldServerAddr, false);
m_PacketsSent++;
m_BytesSent += heartbeat.GetNumberOfBytesUsed();
}
m_LastHeartbeat = now;
}
}
void SimUser::Update(float deltaTime) {
ProcessIncomingPackets();
SendHeartbeat();
auto now = std::chrono::steady_clock::now();
auto timeSinceLastAction = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_LastRandomAction);
// Perform random actions based on current state
if (m_State == SimUserState::IN_WORLD && timeSinceLastAction.count() >= 5000) {
SimulateRandomAction();
m_LastRandomAction = now;
}
// Update movement simulation
if (m_IsMoving) {
SimulateMovement();
}
}
void SimUser::SimulateMovement() {
// Simple movement simulation - move towards target position
const float moveSpeed = 5.0f; // units per second
const float deltaTime = 0.016f; // Assuming 60 FPS
NiPoint3 direction = m_TargetPosition - m_Position;
float distance = direction.Length();
if (distance > 0.1f) {
direction = direction / distance; // Normalize
NiPoint3 movement = direction * moveSpeed * deltaTime;
if (movement.Length() >= distance) {
m_Position = m_TargetPosition;
m_IsMoving = false;
} else {
m_Position = m_Position + movement;
}
SendPositionUpdate();
} else {
m_IsMoving = false;
}
}
void SimUser::SimulateRandomAction() {
if (m_State != SimUserState::IN_WORLD) return;
float action = m_RandomFloat(m_RandomEngine);
if (action < 0.6f) {
// Generate random movement
GenerateRandomMovement();
} else if (action < 0.8f) {
// Send a chat message (if enabled)
SendChatMessage("Hello from simulated client " + std::to_string(m_ID));
} else {
// Just idle
LogMessage("Idling...");
}
}
void SimUser::SendChatMessage(const std::string& message) {
if (m_State != SimUserState::IN_WORLD || !m_WorldPeer) return;
// Create a simple chat message
RakNet::BitStream chatBitStream;
chatBitStream.Write(static_cast<MessageID>(ID_USER_PACKET_ENUM + 2));
chatBitStream.Write(static_cast<uint8_t>(0)); // General chat channel
chatBitStream.Write(static_cast<uint16_t>(0)); // Unknown
// Convert message to UTF-16
std::u16string u16message;
for (char c : message) {
u16message.push_back(static_cast<char16_t>(c));
}
chatBitStream.Write(static_cast<uint32_t>(u16message.length()));
for (char16_t c : u16message) {
chatBitStream.Write(c);
}
m_WorldPeer->Send(&chatBitStream, HIGH_PRIORITY, RELIABLE_ORDERED, 0, m_WorldServerAddr, false);
m_PacketsSent++;
m_BytesSent += chatBitStream.GetNumberOfBytesUsed();
LogMessage("Sent chat message: " + message);
}
void SimUser::GenerateRandomMovement() {
// Generate a random target position within a reasonable area
float randomX = (m_RandomFloat(m_RandomEngine) - 0.5f) * 20.0f; // -10 to 10
float randomZ = (m_RandomFloat(m_RandomEngine) - 0.5f) * 20.0f; // -10 to 10
m_TargetPosition = m_Position + NiPoint3(randomX, 0, randomZ);
m_IsMoving = true;
LogMessage("Started movement to (" + std::to_string(m_TargetPosition.x) + ", " +
std::to_string(m_TargetPosition.y) + ", " + std::to_string(m_TargetPosition.z) + ")");
}
void SimUser::SendPositionUpdate() {
if (m_State != SimUserState::IN_WORLD || !m_WorldPeer) return;
// Create position update packet
RakNet::BitStream positionBitStream;
positionBitStream.Write(static_cast<MessageID>(ID_USER_PACKET_ENUM + 3)); // Position update
positionBitStream.Write(m_Position.x);
positionBitStream.Write(m_Position.y);
positionBitStream.Write(m_Position.z);
positionBitStream.Write(m_Rotation.x);
positionBitStream.Write(m_Rotation.y);
positionBitStream.Write(m_Rotation.z);
positionBitStream.Write(m_Rotation.w);
m_WorldPeer->Send(&positionBitStream, HIGH_PRIORITY, UNRELIABLE_SEQUENCED, 0, m_WorldServerAddr, false);
m_PacketsSent++;
m_BytesSent += positionBitStream.GetNumberOfBytesUsed();
}
void SimUser::HandleAuthPacket(Packet* packet) {
switch (packet->data[0]) {
case ID_CONNECTION_REQUEST_ACCEPTED:
LogMessage("Connected to auth server");
SendLoginRequest();
break;
case ID_DISCONNECTION_NOTIFICATION:
case ID_CONNECTION_LOST:
LogMessage("Disconnected from auth server");
SetError("Lost connection to auth server");
break;
default:
// Handle auth-specific packets
if (packet->data[0] >= ID_USER_PACKET_ENUM) {
HandleLoginResponse(packet);
}
break;
}
}
void SimUser::HandleWorldPacket(Packet* packet) {
switch (packet->data[0]) {
case ID_CONNECTION_REQUEST_ACCEPTED:
LogMessage("Connected to world server");
SendWorldLoginRequest();
break;
case ID_DISCONNECTION_NOTIFICATION:
case ID_CONNECTION_LOST:
LogMessage("Disconnected from world server");
SetError("Lost connection to world server");
break;
default:
// Handle world-specific packets
if (packet->data[0] >= ID_USER_PACKET_ENUM) {
// For simplicity, just log that we received a world packet
LogMessage("Received world packet of type " + std::to_string(packet->data[0]));
}
break;
}
}
void SimUser::SendLoginRequest() {
if (!m_AuthPeer) return;
SetState(SimUserState::AUTHENTICATING);
// Create login request packet
RakNet::BitStream loginBitStream;
loginBitStream.Write(static_cast<MessageID>(ID_USER_PACKET_ENUM));
loginBitStream.Write(static_cast<uint32_t>(m_Username.length()));
loginBitStream.Write(m_Username.c_str(), m_Username.length());
loginBitStream.Write(static_cast<uint32_t>(m_Password.length()));
loginBitStream.Write(m_Password.c_str(), m_Password.length());
m_AuthPeer->Send(&loginBitStream, HIGH_PRIORITY, RELIABLE, 0, m_AuthServerAddr, false);
m_PacketsSent++;
m_BytesSent += loginBitStream.GetNumberOfBytesUsed();
LogMessage("Sent login request for user: " + m_Username);
}
void SimUser::HandleLoginResponse(Packet* packet) {
// Simple login response handling
LogMessage("Received login response");
// For simulation purposes, assume successful login and move to world
m_SessionKey = "sim_session_" + std::to_string(m_ID);
// Simulate connecting to world server (hardcoded for now)
ConnectToWorld("127.0.0.1", 2000);
}
void SimUser::SendWorldLoginRequest() {
if (!m_WorldPeer) return;
SetState(SimUserState::LOADING_WORLD);
// Create world login request
RakNet::BitStream worldLoginBitStream;
worldLoginBitStream.Write(static_cast<MessageID>(ID_USER_PACKET_ENUM + 10));
worldLoginBitStream.Write(static_cast<uint32_t>(m_SessionKey.length()));
worldLoginBitStream.Write(m_SessionKey.c_str(), m_SessionKey.length());
m_WorldPeer->Send(&worldLoginBitStream, HIGH_PRIORITY, RELIABLE, 0, m_WorldServerAddr, false);
m_PacketsSent++;
m_BytesSent += worldLoginBitStream.GetNumberOfBytesUsed();
LogMessage("Sent world login request");
// For simulation, immediately consider ourselves in world
SetState(SimUserState::IN_WORLD);
m_Position = NiPoint3(0, 0, 0); // Start at origin
LogMessage("Entered world simulation mode");
}
void SimUser::SetError(const std::string& error) {
m_HasError = true;
m_LastError = error;
m_State = SimUserState::ERROR_STATE;
LogMessage("ERROR: " + error);
}
void SimUser::LogMessage(const std::string& message) {
if (Game::logger) {
LOG("[SimUser %u (%s)] %s", m_ID, m_Username.c_str(), message.c_str());
} else {
std::cout << "[SimUser " << m_ID << " (" << m_Username << ")] " << message << std::endl;
}
}

139
dSimClient/SimUser.h Normal file
View File

@@ -0,0 +1,139 @@
#pragma once
#include "dCommonVars.h"
#include "RakNetTypes.h"
#include "RakPeerInterface.h"
#include "NiPoint3.h"
#include "NiQuaternion.h"
#include <string>
#include <chrono>
#include <random>
enum class SimUserState {
DISCONNECTED,
CONNECTING_TO_AUTH,
AUTHENTICATING,
CONNECTING_TO_WORLD,
LOADING_WORLD,
IN_WORLD,
DISCONNECTING,
ERROR_STATE
};
/**
* @brief Represents a simulated user/client connection
*/
class SimUser {
public:
SimUser(uint32_t id, const std::string& username, const std::string& password);
~SimUser();
// Connection management
bool ConnectToAuth(const std::string& authIP, uint16_t authPort);
bool ConnectToWorld(const std::string& worldIP, uint16_t worldPort);
void Disconnect();
// State management
SimUserState GetState() const { return m_State; }
void SetState(SimUserState state) { m_State = state; }
// User info
uint32_t GetID() const { return m_ID; }
const std::string& GetUsername() const { return m_Username; }
const std::string& GetPassword() const { return m_Password; }
// Network handling
void ProcessIncomingPackets();
void SendHeartbeat();
void Update(float deltaTime);
// Simulation behaviors
void SimulateMovement();
void SimulateRandomAction();
void SendChatMessage(const std::string& message);
// Position and movement
void SetPosition(const NiPoint3& position) { m_Position = position; }
void SetRotation(const NiQuaternion& rotation) { m_Rotation = rotation; }
const NiPoint3& GetPosition() const { return m_Position; }
const NiQuaternion& GetRotation() const { return m_Rotation; }
// Statistics
uint64_t GetPacketsSent() const { return m_PacketsSent; }
uint64_t GetPacketsReceived() const { return m_PacketsReceived; }
uint64_t GetBytesReceived() const { return m_BytesReceived; }
uint64_t GetBytesSent() const { return m_BytesSent; }
// Error handling
bool HasError() const { return m_HasError; }
const std::string& GetLastError() const { return m_LastError; }
private:
// Helper methods
void HandleAuthPacket(Packet* packet);
void HandleWorldPacket(Packet* packet);
void SetError(const std::string& error);
void LogMessage(const std::string& message);
// Authentication flow
void SendLoginRequest();
void HandleLoginResponse(Packet* packet);
void HandleWorldServerInfo(Packet* packet);
// World flow
void SendWorldLoginRequest();
void HandleWorldLoginResponse(Packet* packet);
void SendCharacterListRequest();
void HandleCharacterListResponse(Packet* packet);
void SendCharacterSelection();
void HandleMinifigureListResponse(Packet* packet);
void SendWorldReady();
// Movement simulation
void GenerateRandomMovement();
void SendPositionUpdate();
// Member variables
uint32_t m_ID;
std::string m_Username;
std::string m_Password;
SimUserState m_State;
// Network
RakPeerInterface* m_AuthPeer;
RakPeerInterface* m_WorldPeer;
SystemAddress m_AuthServerAddr;
SystemAddress m_WorldServerAddr;
// Session data
std::string m_SessionKey;
LWOOBJID m_CharacterID;
uint32_t m_WorldInstanceID;
uint32_t m_ZoneID;
// Position and movement
NiPoint3 m_Position;
NiQuaternion m_Rotation;
NiPoint3 m_Velocity;
NiPoint3 m_TargetPosition;
bool m_IsMoving;
// Timing
std::chrono::steady_clock::time_point m_LastUpdate;
std::chrono::steady_clock::time_point m_LastHeartbeat;
std::chrono::steady_clock::time_point m_LastRandomAction;
// Statistics
uint64_t m_PacketsSent;
uint64_t m_PacketsReceived;
uint64_t m_BytesSent;
uint64_t m_BytesReceived;
// Error handling
bool m_HasError;
std::string m_LastError;
// Random number generation
mutable std::mt19937 m_RandomEngine;
mutable std::uniform_real_distribution<float> m_RandomFloat;
};

337
dSimClient/SimWorld.cpp Normal file
View File

@@ -0,0 +1,337 @@
#include "SimWorld.h"
#include "SimUser.h"
#include "SimConfig.h"
#include "Logger.h"
#include "Game.h"
#include <iostream>
#include <algorithm>
#include <thread>
#include <random>
#include <iomanip>
SimWorld::SimWorld()
: m_NextUserID(1)
, m_Running(false)
, m_Initialized(false)
, m_TotalUsersSpawned(0)
, m_TotalUsersDisconnected(0)
, m_TotalErrors(0)
{
m_StartTime = std::chrono::steady_clock::now();
m_LastSpawn = std::chrono::steady_clock::now();
m_LastStatsPrint = std::chrono::steady_clock::now();
}
SimWorld::~SimWorld() {
Shutdown();
}
bool SimWorld::Initialize(std::shared_ptr<SimConfig> config) {
if (m_Initialized) {
std::cout << "SimWorld already initialized" << std::endl;
return false;
}
m_Config = config;
m_Initialized = true;
std::cout << "SimWorld initialized with configuration:" << std::endl;
std::cout << " Auth Server: " << m_Config->GetAuthServerIP() << ":" << m_Config->GetAuthServerPort() << std::endl;
std::cout << " World Server: " << m_Config->GetWorldServerIP() << ":" << m_Config->GetWorldServerPort() << std::endl;
std::cout << " Client Count: " << m_Config->GetClientCount() << std::endl;
std::cout << " Spawn Delay: " << m_Config->GetSpawnDelayMs() << "ms" << std::endl;
std::cout << " Tick Rate: " << m_Config->GetTickRateMs() << "ms" << std::endl;
return true;
}
void SimWorld::Shutdown() {
if (!m_Initialized) return;
m_Running = false;
// Disconnect all users
std::cout << "Shutting down simulation, disconnecting " << m_Users.size() << " users..." << std::endl;
for (auto& user : m_Users) {
user->Disconnect();
}
m_Users.clear();
m_Initialized = false;
std::cout << "SimWorld shutdown complete" << std::endl;
}
void SimWorld::Run() {
if (!m_Initialized) {
std::cout << "SimWorld not initialized, cannot run" << std::endl;
return;
}
m_Running = true;
std::cout << "Starting simulation..." << std::endl;
auto lastUpdate = std::chrono::steady_clock::now();
const auto tickRate = std::chrono::milliseconds(m_Config->GetTickRateMs());
while (m_Running) {
auto now = std::chrono::steady_clock::now();
auto deltaTime = std::chrono::duration<float>(now - lastUpdate).count();
lastUpdate = now;
Update(deltaTime);
// Print statistics every 10 seconds
auto timeSinceLastStats = std::chrono::duration_cast<std::chrono::seconds>(now - m_LastStatsPrint);
if (timeSinceLastStats.count() >= 10) {
PrintStatistics();
m_LastStatsPrint = now;
}
// Sleep to maintain tick rate
std::this_thread::sleep_for(tickRate);
}
std::cout << "Simulation stopped" << std::endl;
}
void SimWorld::Update(float deltaTime) {
SpawnUsersGradually();
CheckUserStates();
HandleUserErrors();
// Update all users
for (auto& user : m_Users) {
user->Update(deltaTime);
}
}
void SimWorld::SpawnUsersGradually() {
if (m_Users.size() >= m_Config->GetClientCount()) {
return; // Already have enough users
}
auto now = std::chrono::steady_clock::now();
auto timeSinceLastSpawn = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_LastSpawn);
if (timeSinceLastSpawn.count() >= m_Config->GetSpawnDelayMs()) {
// Get credentials from config or generate random ones
std::string username, password;
auto testAccounts = m_Config->GetTestAccounts();
if (m_TotalUsersSpawned < testAccounts.size()) {
// Use predefined test account
username = testAccounts[m_TotalUsersSpawned].first;
password = testAccounts[m_TotalUsersSpawned].second;
} else {
// Generate random credentials
username = GenerateRandomUsername();
password = GenerateRandomPassword();
}
if (SpawnUser(username, password)) {
m_LastSpawn = now;
m_TotalUsersSpawned++;
}
}
}
bool SimWorld::SpawnUser(const std::string& username, const std::string& password) {
auto user = std::make_unique<SimUser>(m_NextUserID++, username, password);
if (!user->ConnectToAuth(m_Config->GetAuthServerIP(), m_Config->GetAuthServerPort())) {
std::cout << "Failed to connect user " << username << " to auth server" << std::endl;
return false;
}
m_Users.push_back(std::move(user));
if (m_Config->GetVerboseLogging()) {
std::cout << "Spawned user: " << username << " (Total: " << m_Users.size() << ")" << std::endl;
}
return true;
}
void SimWorld::RemoveUser(uint32_t userID) {
auto it = std::remove_if(m_Users.begin(), m_Users.end(),
[userID](const std::unique_ptr<SimUser>& user) {
return user->GetID() == userID;
});
if (it != m_Users.end()) {
(*it)->Disconnect();
m_Users.erase(it, m_Users.end());
m_TotalUsersDisconnected++;
}
}
void SimWorld::CheckUserStates() {
// Remove users that have been in error state for too long
auto it = std::remove_if(m_Users.begin(), m_Users.end(),
[this](const std::unique_ptr<SimUser>& user) {
if (user->GetState() == SimUserState::ERROR_STATE) {
if (m_Config->GetVerboseLogging()) {
std::cout << "Removing user " << user->GetUsername()
<< " due to error: " << user->GetLastError() << std::endl;
}
m_TotalErrors++;
return true;
}
return false;
});
if (it != m_Users.end()) {
m_Users.erase(it, m_Users.end());
}
}
void SimWorld::HandleUserErrors() {
uint32_t errorCount = GetErrorUserCount();
if (errorCount > 0 && m_Config->GetVerboseLogging()) {
std::cout << "Warning: " << errorCount << " users in error state" << std::endl;
}
}
void SimWorld::PrintStatistics() {
auto now = std::chrono::steady_clock::now();
auto uptime = std::chrono::duration_cast<std::chrono::seconds>(now - m_StartTime);
std::cout << std::endl;
std::cout << "=== Simulation Statistics ===" << std::endl;
std::cout << "Uptime: " << uptime.count() << " seconds" << std::endl;
std::cout << "Active Users: " << GetActiveUserCount() << std::endl;
std::cout << "Connected Users: " << GetConnectedUserCount() << std::endl;
std::cout << "Total Spawned: " << m_TotalUsersSpawned << std::endl;
std::cout << "Total Disconnected: " << m_TotalUsersDisconnected << std::endl;
std::cout << "Total Errors: " << m_TotalErrors << std::endl;
std::cout << "Total Packets Sent: " << GetTotalPacketsSent() << std::endl;
std::cout << "Total Packets Received: " << GetTotalPacketsReceived() << std::endl;
std::cout << "Total Bytes Sent: " << GetTotalBytesSent() << std::endl;
std::cout << "Total Bytes Received: " << GetTotalBytesReceived() << std::endl;
if (uptime.count() > 0) {
std::cout << "Avg Packets/sec Sent: " << (GetTotalPacketsSent() / uptime.count()) << std::endl;
std::cout << "Avg Packets/sec Received: " << (GetTotalPacketsReceived() / uptime.count()) << std::endl;
std::cout << "Avg Bytes/sec Sent: " << (GetTotalBytesSent() / uptime.count()) << std::endl;
std::cout << "Avg Bytes/sec Received: " << (GetTotalBytesReceived() / uptime.count()) << std::endl;
}
std::cout << "=============================" << std::endl;
std::cout << std::endl;
}
uint64_t SimWorld::GetTotalPacketsSent() const {
uint64_t total = 0;
for (const auto& user : m_Users) {
total += user->GetPacketsSent();
}
return total;
}
uint64_t SimWorld::GetTotalPacketsReceived() const {
uint64_t total = 0;
for (const auto& user : m_Users) {
total += user->GetPacketsReceived();
}
return total;
}
uint64_t SimWorld::GetTotalBytesSent() const {
uint64_t total = 0;
for (const auto& user : m_Users) {
total += user->GetBytesSent();
}
return total;
}
uint64_t SimWorld::GetTotalBytesReceived() const {
uint64_t total = 0;
for (const auto& user : m_Users) {
total += user->GetBytesReceived();
}
return total;
}
uint32_t SimWorld::GetConnectedUserCount() const {
uint32_t count = 0;
for (const auto& user : m_Users) {
if (user->GetState() == SimUserState::IN_WORLD ||
user->GetState() == SimUserState::LOADING_WORLD ||
user->GetState() == SimUserState::CONNECTING_TO_WORLD ||
user->GetState() == SimUserState::AUTHENTICATING) {
count++;
}
}
return count;
}
uint32_t SimWorld::GetErrorUserCount() const {
uint32_t count = 0;
for (const auto& user : m_Users) {
if (user->GetState() == SimUserState::ERROR_STATE) {
count++;
}
}
return count;
}
std::string SimWorld::GenerateRandomUsername() {
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_int_distribution<> dis(1000, 9999);
return "simuser" + std::to_string(dis(gen));
}
std::string SimWorld::GenerateRandomPassword() {
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_int_distribution<> dis(10000, 99999);
return "simpass" + std::to_string(dis(gen));
}
void SimWorld::RunLoadTest() {
std::cout << "Running load test with " << m_Config->GetClientCount() << " clients..." << std::endl;
Run();
}
void SimWorld::RunStressTest() {
std::cout << "Running stress test..." << std::endl;
// Temporarily increase client count for stress testing
uint32_t originalCount = m_Config->GetClientCount();
m_Config->SetClientCount(originalCount * 2);
Run();
// Restore original count
m_Config->SetClientCount(originalCount);
}
void SimWorld::RunBasicConnectivityTest() {
std::cout << "Running basic connectivity test..." << std::endl;
// Set to 1 client for basic test
uint32_t originalCount = m_Config->GetClientCount();
m_Config->SetClientCount(1);
Run();
// Restore original count
m_Config->SetClientCount(originalCount);
}
void SimWorld::RunMovementTest() {
std::cout << "Running movement test..." << std::endl;
// Movement is handled automatically in the user simulation
Run();
}
void SimWorld::RunChatTest() {
std::cout << "Running chat test..." << std::endl;
// Chat is handled automatically in the user simulation
Run();
}

76
dSimClient/SimWorld.h Normal file
View File

@@ -0,0 +1,76 @@
#pragma once
#include <memory>
#include <vector>
#include <string>
#include <chrono>
class SimUser;
class SimConfig;
/**
* @brief Manages the simulation of multiple users and test behaviors
*/
class SimWorld {
public:
SimWorld();
~SimWorld();
// Initialization
bool Initialize(std::shared_ptr<SimConfig> config);
void Shutdown();
// Main simulation loop
void Run();
void Update(float deltaTime);
// User management
bool SpawnUser(const std::string& username, const std::string& password);
void RemoveUser(uint32_t userID);
size_t GetActiveUserCount() const { return m_Users.size(); }
// Test scenarios
void RunLoadTest();
void RunStressTest();
void RunBasicConnectivityTest();
void RunMovementTest();
void RunChatTest();
// Statistics
void PrintStatistics();
uint64_t GetTotalPacketsSent() const;
uint64_t GetTotalPacketsReceived() const;
uint64_t GetTotalBytesSent() const;
uint64_t GetTotalBytesReceived() const;
uint32_t GetConnectedUserCount() const;
uint32_t GetErrorUserCount() const;
// Control
void StopSimulation() { m_Running = false; }
bool IsRunning() const { return m_Running; }
private:
// Helper methods
void SpawnUsersGradually();
void CheckUserStates();
void HandleUserErrors();
std::string GenerateRandomUsername();
std::string GenerateRandomPassword();
// Member variables
std::shared_ptr<SimConfig> m_Config;
std::vector<std::unique_ptr<SimUser>> m_Users;
uint32_t m_NextUserID;
bool m_Running;
bool m_Initialized;
// Timing
std::chrono::steady_clock::time_point m_LastSpawn;
std::chrono::steady_clock::time_point m_StartTime;
std::chrono::steady_clock::time_point m_LastStatsPrint;
// Statistics
uint32_t m_TotalUsersSpawned;
uint32_t m_TotalUsersDisconnected;
uint32_t m_TotalErrors;
};

View File

@@ -0,0 +1,344 @@
# SimClient Integration Guide
This guide explains how the SimClient has been integrated into the DarkflameServer project and how to use it effectively.
## Integration Overview
The SimClient has been added as a new executable target to the existing DarkflameServer build system. It's designed to work alongside the existing server components for testing and load testing purposes.
### Files Added
#### Core SimClient Files
- `dSimClient/` - New directory containing all SimClient source code
- `dSimClient/CMakeLists.txt` - Build configuration for SimClient
- `dSimClient/SimClient.cpp` - Main executable entry point
- `dSimClient/SimUser.h/cpp` - Individual simulated user implementation
- `dSimClient/SimWorld.h/cpp` - Simulation coordinator and manager
- `dSimClient/SimBehavior.h/cpp` - Behavior patterns and test scenarios
- `dSimClient/SimConfig.h/cpp` - Configuration management
- `dSimClient/README.md` - Detailed SimClient documentation
#### Configuration and Build Files
- `resources/simclient.ini` - Default configuration file
- `build_simclient.sh` - Linux/macOS build script
- `build_simclient.bat` - Windows build script
#### Modified Files
- `CMakeLists.txt` - Added SimClient subdirectory to main build
## Build Integration
The SimClient is built as part of the normal DarkflameServer build process:
```bash
# Standard build process includes SimClient
mkdir build && cd build
cmake ..
make # Builds all targets including SimClient
# Or build just SimClient
make SimClient
```
### Dependencies
SimClient reuses existing DarkflameServer libraries:
- `dCommon` - Common utilities and data types
- `dDatabase` - Database connectivity (minimal usage)
- `dNet` - Network communication and RakNet integration
- `dServer` - Server utilities and logging
- `raknet` - RakNet networking library
This ensures consistency with the main server codebase and reduces maintenance overhead.
## Usage Scenarios
### Development Testing
During development, developers can use SimClient to quickly test server functionality:
```bash
# Quick connectivity test
./SimClient -t basic -v
# Test new features with multiple clients
./SimClient -n 3 -t movement
```
### Continuous Integration
Integrate SimClient into CI/CD pipelines:
```yaml
# Example GitHub Actions step
- name: Test Server with SimClient
run: |
# Start servers in background
./MasterServer &
./AuthServer &
./WorldServer &
# Wait for startup
sleep 10
# Run tests
./SimClient -t basic || exit 1
./SimClient -t load -n 5 || exit 1
# Cleanup
killall MasterServer AuthServer WorldServer
```
### Load Testing
Simulate realistic server loads:
```bash
# Test with 50 concurrent users
./SimClient -t load -n 50
# Stress test with high load
./SimClient -t stress -n 25 # Actually uses 50 clients (2x)
# Custom configuration for specific tests
./SimClient -c load_test.ini -t load
```
### Performance Monitoring
Monitor server performance over time:
```bash
# Long-running test with detailed logging
./SimClient -t load -n 20 -v > performance_test_$(date +%Y%m%d).log &
# Let it run for hours or days, then analyze logs
```
## Configuration Management
### File-based Configuration
The SimClient uses an INI-style configuration file that can be customized for different test scenarios:
```ini
# Basic connectivity test config
client_count=1
enable_movement=0
enable_chat=0
verbose_logging=1
# Load test config
client_count=25
spawn_delay_ms=1000
enable_movement=1
enable_random_actions=1
# Stress test config
client_count=100
spawn_delay_ms=500
tick_rate_ms=8
```
### Command-line Overrides
Command-line parameters override configuration file settings:
```bash
# Override client count from config file
./SimClient -c load_test.ini -n 10
# Override server addresses
./SimClient -a 192.168.1.100:1001 -w 192.168.1.100:2000
```
## Testing Integration
### Unit Testing
The SimClient components can be unit tested independently:
```cpp
// Example unit test
TEST(SimConfigTest, LoadValidConfig) {
SimConfig config;
ASSERT_TRUE(config.LoadFromFile("test_config.ini"));
ASSERT_EQ(config.GetClientCount(), 5);
}
```
### Integration Testing
SimClient can be used for automated integration testing:
```bash
#!/bin/bash
# Integration test script
# Test basic server startup and connectivity
./SimClient -t basic || { echo "Basic connectivity failed"; exit 1; }
# Test multiple client handling
./SimClient -t load -n 5 || { echo "Load test failed"; exit 1; }
# Test movement system
./SimClient -t movement -n 3 || { echo "Movement test failed"; exit 1; }
echo "All integration tests passed!"
```
## Error Handling and Debugging
### Logging Integration
SimClient integrates with the DarkflameServer logging system:
- Uses same Logger class as server components
- Writes to `logs/SimClient_<timestamp>.log`
- Supports verbose and debug logging levels
- Correlates with server logs for debugging
### Error Recovery
SimClient implements robust error handling:
- Automatic reconnection on network failures
- Graceful degradation when servers are overloaded
- Detailed error reporting for debugging
- State recovery after temporary issues
### Debugging Features
Enable detailed debugging with:
```bash
# Maximum verbosity
./SimClient -v -n 1 -t basic
# Debug specific client behaviors
./SimClient -t movement -n 1 -v | grep "movement"
```
## Performance Considerations
### Resource Usage
SimClient is designed to be lightweight:
- Minimal memory footprint per simulated client
- Efficient network communication
- Configurable tick rates to balance load
- Automatic cleanup of failed connections
### Scalability
For large-scale testing:
```bash
# Multiple SimClient instances for extreme load
./SimClient -n 100 &
./SimClient -n 100 &
./SimClient -n 100 &
wait
# Distributed testing across multiple machines
ssh server1 "./SimClient -n 50 -a $SERVER_IP" &
ssh server2 "./SimClient -n 50 -a $SERVER_IP" &
```
### Monitoring
Track performance metrics:
- Packets per second sent/received
- Connection success/failure rates
- Response times and latency
- Memory and CPU usage
## Future Enhancements
### Planned Features
1. **Protocol Coverage**: Support more game messages and interactions
2. **Behavior Scripting**: Lua/Python scripting for complex behaviors
3. **Web Dashboard**: Real-time monitoring interface
4. **Distributed Coordination**: Multiple SimClient instance coordination
5. **Performance Profiling**: Built-in server performance analysis
### Extension Points
The SimClient architecture supports easy extension:
```cpp
// Add new behavior patterns
class CustomBehavior : public SimBehavior {
void ExecuteCustomAction(SimUser* user) override {
// Implement custom test behavior
}
};
// Add new test scenarios
void SimWorld::RunCustomTest() {
// Implement custom test logic
}
```
## Best Practices
### Development Workflow
1. **Start Small**: Begin with basic connectivity tests
2. **Incremental Load**: Gradually increase client counts
3. **Monitor Resources**: Watch server CPU/memory during tests
4. **Log Analysis**: Correlate SimClient and server logs
5. **Automate Testing**: Integrate into development workflow
### Production Testing
1. **Staging Environment**: Test on production-like infrastructure
2. **Gradual Rollout**: Start with small loads, increase gradually
3. **Baseline Metrics**: Establish performance baselines
4. **Failure Scenarios**: Test server behavior under failures
5. **Recovery Testing**: Verify graceful degradation and recovery
## Troubleshooting
### Common Issues
**Build Failures**
- Ensure all DarkflameServer dependencies are met
- Check CMake version and compiler compatibility
- Verify RakNet and other third-party libraries
**Connection Issues**
- Verify server components are running
- Check firewall and network connectivity
- Validate configuration file settings
- Review server logs for authentication errors
**Performance Issues**
- Reduce client count for available resources
- Adjust tick rate and spawn delays
- Monitor network bandwidth usage
- Check for memory leaks in long-running tests
### Getting Help
- Check SimClient logs in `logs/` directory
- Review server logs for corresponding errors
- Enable verbose logging for detailed diagnostics
- Correlate timestamps between SimClient and server logs
## Contributing
When contributing to SimClient:
1. Follow existing code style and patterns
2. Add comprehensive error handling
3. Update configuration options as needed
4. Add command-line parameters for new features
5. Update documentation
6. Test with various scenarios and loads
7. Consider backwards compatibility
The SimClient is designed to grow with the DarkflameServer project and can be extended to support new testing scenarios as the server evolves.

38
resources/simclient.ini Normal file
View File

@@ -0,0 +1,38 @@
# DarkflameServer Simulation Client Configuration
# Lines starting with # are comments
# Authentication Server Settings
auth_server_ip=127.0.0.1
auth_server_port=1001
# World Server Settings
world_server_ip=127.0.0.1
world_server_port=2000
# Client Simulation Settings
client_count=5
spawn_delay_ms=2000
tick_rate_ms=16
# Test Behavior Settings
enable_movement=1
enable_chat=0
enable_random_actions=1
action_interval_ms=5000
# Logging Settings
verbose_logging=0
log_file=simclient.log
# Test Accounts (username:password format)
# If not enough accounts are provided, random ones will be generated
test_account_1=testuser1:testpass1
test_account_2=testuser2:testpass2
test_account_3=testuser3:testpass3
test_account_4=testuser4:testpass4
test_account_5=testuser5:testpass5
test_account_6=loadtest1:loadtest1
test_account_7=loadtest2:loadtest2
test_account_8=loadtest3:loadtest3
test_account_9=loadtest4:loadtest4
test_account_10=loadtest5:loadtest5