Merge branch 'master' into Playerbot

This commit is contained in:
Yunfan Li
2025-07-15 20:34:44 +08:00
80 changed files with 11484 additions and 699 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
name: Codestyle
name: C++ Codestyle
on:
pull_request:
types:
+1 -1
View File
@@ -1,4 +1,4 @@
name: Codestyle
name: SQL Codestyle
on:
pull_request:
types:
+22 -1
View File
@@ -28,7 +28,8 @@ results = {
"INSERT & DELETE safety usage check": "Passed",
"Missing semicolon check": "Passed",
"Backtick check": "Passed",
"Directory check": "Passed"
"Directory check": "Passed",
"Table engine check": "Passed"
}
# Collect all files in all directories
@@ -78,6 +79,7 @@ def parsing_file(files: list) -> None:
insert_delete_safety_check(file, file_path)
semicolon_check(file, file_path)
backtick_check(file, file_path)
non_innodb_engine_check(file, file_path)
except UnicodeDecodeError:
print(f"\n❌ Could not decode file {file_path}")
sys.exit(1)
@@ -383,6 +385,25 @@ def directory_check(file: io, file_path: str) -> None:
error_handler = True
results["Directory check"] = "Failed"
def non_innodb_engine_check(file: io, file_path: str) -> None:
global error_handler, results
file.seek(0)
check_failed = False
engine_pattern = re.compile(r'ENGINE\s*=\s*([a-zA-Z0-9_]+)', re.IGNORECASE)
for line_number, line in enumerate(file, start=1):
match = engine_pattern.search(line)
if match:
engine = match.group(1).lower()
if engine != "innodb":
print(f"❌ Non-InnoDB engine found: '{engine}' in {file_path} at line {line_number}")
check_failed = True
if check_failed:
error_handler = True
results["Table engine check"] = "Failed"
# Collect all files from matching directories
all_files = collect_files_from_directories(src_directory) + collect_files_from_directories(base_directory) + collect_files_from_directories(archive_directory)
+17
View File
@@ -0,0 +1,17 @@
# BATS Test Configuration for Compiler App
# Set test timeout (in seconds)
export BATS_TEST_TIMEOUT=60
# Enable verbose output for debugging
export BATS_VERBOSE_RUN=1
# Test output format
export BATS_FORMATTER=pretty
# Enable colored output
export BATS_NO_PARALLELIZE_ACROSS_FILES=1
export BATS_NO_PARALLELIZE_WITHIN_FILE=1
# Compiler specific test configuration
export COMPILER_TEST_SKIP_HEAVY=1
+307
View File
@@ -0,0 +1,307 @@
#!/usr/bin/env bats
# Require minimum BATS version to avoid warnings
bats_require_minimum_version 1.5.0
# AzerothCore Compiler Scripts Test Suite
# Tests the functionality of the compiler scripts using the unified test framework
# Load the AzerothCore test framework
load '../../test-framework/bats_libs/acore-support'
load '../../test-framework/bats_libs/acore-assert'
# Setup that runs before each test
setup() {
compiler_setup
export SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
export COMPILER_SCRIPT="$SCRIPT_DIR/compiler.sh"
}
# Cleanup that runs after each test
teardown() {
acore_test_teardown
}
# ===== COMPILER SCRIPT TESTS =====
@test "compiler: should show help with --help argument" {
run bash -c "echo '' | timeout 5s $COMPILER_SCRIPT --help"
[ "$status" -eq 0 ]
[[ "$output" =~ "Available commands:" ]]
}
@test "compiler: should show help with empty input" {
run bash -c "echo '' | timeout 5s $COMPILER_SCRIPT 2>&1 || true"
# The script might exit with timeout (124) or success (0), both are acceptable for this test
[[ "$status" -eq 0 ]] || [[ "$status" -eq 124 ]]
# Check if output contains expected content - looking for menu options
[[ "$output" =~ "build:" ]] || [[ "$output" =~ "clean:" ]] || [[ "$output" =~ "Please enter your choice" ]] || [[ -z "$output" ]]
}
@test "compiler: should accept option numbers" {
# Test option 7 (ccacheShowStats) which should be safe to run
run bash -c "echo '7' | timeout 10s $COMPILER_SCRIPT 2>/dev/null || true"
# The script might exit with timeout (124) or success (0), both are acceptable
[[ "$status" -eq 0 ]] || [[ "$status" -eq 124 ]]
}
@test "compiler: should accept option by name" {
run timeout 10s "$COMPILER_SCRIPT" ccacheShowStats
[ "$status" -eq 0 ]
}
@test "compiler: should handle invalid option gracefully" {
run timeout 5s "$COMPILER_SCRIPT" invalidOption
[ "$status" -eq 0 ]
[[ "$output" =~ "invalid option" ]]
}
@test "compiler: should handle invalid number gracefully" {
run bash -c "echo '999' | timeout 5s $COMPILER_SCRIPT 2>/dev/null || true"
# The script might exit with timeout (124) or success (0), both are acceptable
[[ "$status" -eq 0 ]] || [[ "$status" -eq 124 ]]
# Check if output contains expected content, or if there's no output due to timeout, that's also acceptable
[[ "$output" =~ "invalid option" ]] || [[ "$output" =~ "Please enter your choice" ]] || [[ -z "$output" ]]
}
@test "compiler: should quit with quit option" {
run timeout 5s "$COMPILER_SCRIPT" quit
[ "$status" -eq 0 ]
}
# ===== FUNCTION TESTS =====
@test "functions: comp_clean should handle non-existent build directory" {
# Source the functions with a non-existent build path
run bash -c "
export BUILDPATH='/tmp/non_existent_build_dir_$RANDOM'
source '$SCRIPT_DIR/includes/functions.sh'
comp_clean
"
# Accept either success or failure - the important thing is the function runs
[[ "$status" -eq 0 ]] || [[ "$status" -eq 1 ]]
[[ "$output" =~ "Cleaning build files" ]]
}
@test "functions: comp_clean should remove build files when directory exists" {
# Create a temporary build directory with test files
local test_build_dir="/tmp/test_build_$RANDOM"
mkdir -p "$test_build_dir/subdir"
touch "$test_build_dir/test_file.txt"
touch "$test_build_dir/subdir/nested_file.txt"
# Run the clean function
run bash -c "
export BUILDPATH='$test_build_dir'
source '$SCRIPT_DIR/includes/functions.sh'
comp_clean
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Cleaning build files" ]]
# Directory should still exist but be empty
[ -d "$test_build_dir" ]
[ ! -f "$test_build_dir/test_file.txt" ]
[ ! -f "$test_build_dir/subdir/nested_file.txt" ]
# Cleanup
rm -rf "$test_build_dir"
}
@test "functions: comp_ccacheShowStats should run without errors when ccache enabled" {
run bash -c "
export AC_CCACHE=true
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheShowStats
"
[ "$status" -eq 0 ]
}
@test "functions: comp_ccacheShowStats should do nothing when ccache disabled" {
run bash -c "
export AC_CCACHE=false
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheShowStats
"
[ "$status" -eq 0 ]
# Should produce no output when ccache is disabled
}
@test "functions: comp_ccacheClean should handle disabled ccache" {
run bash -c "
export AC_CCACHE=false
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheClean
"
[ "$status" -eq 0 ]
[[ "$output" =~ "ccache is disabled" ]]
}
@test "functions: comp_ccacheClean should run when ccache enabled" {
# Only run if ccache is actually available
if command -v ccache >/dev/null 2>&1; then
run bash -c "
export AC_CCACHE=true
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheClean
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Cleaning ccache" ]]
else
skip "ccache not available on system"
fi
}
@test "functions: comp_ccacheEnable should set environment variables" {
# Call the function in a subshell to capture environment changes
run bash -c "
export AC_CCACHE=true
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheEnable
env | grep CCACHE | head -5
"
[ "$status" -eq 0 ]
[[ "$output" =~ "CCACHE_MAXSIZE" ]] || [[ "$output" =~ "CCACHE_COMPRESS" ]]
}
@test "functions: comp_ccacheEnable should not set variables when ccache disabled" {
# Call the function and verify it returns early when ccache is disabled
run bash -c "
export AC_CCACHE=false
source '$SCRIPT_DIR/includes/functions.sh'
comp_ccacheEnable
# The function should return early, so we check if it completed successfully
echo 'Function completed without setting CCACHE vars'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Function completed" ]]
}
# Mock tests for build functions (these would normally require a full setup)
@test "functions: comp_configure should detect platform" {
# Mock cmake command to avoid actual configuration
run -127 bash -c "
function cmake() {
echo 'CMAKE called with args: $*'
return 0
}
export -f cmake
# Set required variables
export BUILDPATH='/tmp'
export SRCPATH='/tmp'
export BINPATH='/tmp'
export CTYPE='Release'
# Source the functions
source '$SCRIPT_DIR/includes/functions.sh'
# Run configure in the /tmp directory
cd /tmp && comp_configure
"
# Accept command not found as this might indicate missing dependencies
[[ "$status" -eq 0 ]] || [[ "$status" -eq 127 ]]
# If successful, check for expected output
if [ "$status" -eq 0 ]; then
[[ "$output" =~ "Platform:" ]] || [[ "$output" =~ "CMAKE called with args:" ]]
fi
}
@test "functions: comp_compile should detect thread count" {
# Mock cmake command to avoid actual compilation
run -127 bash -c "
function cmake() {
echo 'CMAKE called with args: $*'
return 0
}
export -f cmake
# Mock other commands
function pushd() { echo 'pushd $*'; }
function popd() { echo 'popd $*'; }
function time() { shift; \"\$@\"; }
export -f pushd popd time
# Set required variables
export BUILDPATH='/tmp'
export MTHREADS=0
export CTYPE='Release'
export AC_BINPATH_FULL='/tmp'
# Source the functions
source '$SCRIPT_DIR/includes/functions.sh'
# Run compile in the /tmp directory
cd /tmp && comp_compile
"
# Accept command not found as this might indicate missing dependencies
[[ "$status" -eq 0 ]] || [[ "$status" -eq 127 ]]
# If successful, check for expected output
if [ "$status" -eq 0 ]; then
[[ "$output" =~ "pushd" ]] || [[ "$output" =~ "CMAKE called with args:" ]]
fi
}
@test "functions: comp_build should call configure and compile" {
# Mock the comp_configure and comp_compile functions
run -127 bash -c "
function comp_configure() {
echo 'comp_configure called'
return 0
}
function comp_compile() {
echo 'comp_compile called'
return 0
}
export -f comp_configure comp_compile
# Source the functions
source '$SCRIPT_DIR/includes/functions.sh'
# Run build
comp_build
"
# Accept command not found as this might indicate missing dependencies
[[ "$status" -eq 0 ]] || [[ "$status" -eq 127 ]]
# If successful, check for expected output
if [ "$status" -eq 0 ]; then
[[ "$output" =~ "comp_configure called" ]] && [[ "$output" =~ "comp_compile called" ]]
fi
}
@test "functions: comp_all should call clean and build" {
# Mock the comp_clean and comp_build functions
run -127 bash -c "
function comp_clean() {
echo 'comp_clean called'
return 0
}
function comp_build() {
echo 'comp_build called'
return 0
}
export -f comp_clean comp_build
# Source the functions
source '$SCRIPT_DIR/includes/functions.sh'
# Run all
comp_all
"
# Accept command not found as this might indicate missing dependencies
[[ "$status" -eq 0 ]] || [[ "$status" -eq 127 ]]
# If successful, check for expected output
if [ "$status" -eq 0 ]; then
[[ "$output" =~ "comp_clean called" ]] && [[ "$output" =~ "comp_build called" ]]
fi
}
+211
View File
@@ -0,0 +1,211 @@
#!/usr/bin/env bats
# AzerothCore Compiler Configuration Test Suite
# Tests the configuration and support scripts for the compiler module
# Load the AzerothCore test framework
load '../../test-framework/bats_libs/acore-support'
load '../../test-framework/bats_libs/acore-assert'
# Setup that runs before each test
setup() {
compiler_setup
export SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
}
# Cleanup that runs after each test
teardown() {
acore_test_teardown
}
# ===== DEFINES SCRIPT TESTS =====
@test "defines: should accept CCTYPE from argument" {
# Test the defines script with a release argument
run bash -c "unset CCTYPE; source '$SCRIPT_DIR/includes/defines.sh' release; echo \"CCTYPE=\$CCTYPE\""
[ "$status" -eq 0 ]
[[ "$output" =~ "CCTYPE=Release" ]]
}
@test "defines: should handle uppercase CCTYPE" {
# Test the defines script with an uppercase argument
run bash -c "unset CCTYPE; source '$SCRIPT_DIR/includes/defines.sh' DEBUG; echo \"CCTYPE=\$CCTYPE\""
[ "$status" -eq 0 ]
[[ "$output" =~ "CCTYPE=DEBUG" ]]
}
@test "defines: should handle lowercase input" {
# Test the defines script with lowercase input
run bash -c "unset CCTYPE; source '$SCRIPT_DIR/includes/defines.sh' debug; echo \"CCTYPE=\$CCTYPE\""
[ "$status" -eq 0 ]
[[ "$output" =~ "CCTYPE=Debug" ]]
}
@test "defines: should handle mixed case input" {
# Test the defines script with mixed case input
run bash -c "unset CCTYPE; source '$SCRIPT_DIR/includes/defines.sh' rElEaSe; echo \"CCTYPE=\$CCTYPE\""
[ "$status" -eq 0 ]
[[ "$output" =~ "CCTYPE=RElEaSe" ]]
}
@test "defines: should handle no argument" {
# Test the defines script with no argument
run bash -c "CCTYPE='original'; source '$SCRIPT_DIR/includes/defines.sh'; echo \"CCTYPE=\$CCTYPE\""
[ "$status" -eq 0 ]
[[ "$output" =~ "CCTYPE=original" ]]
}
# ===== INCLUDES SCRIPT TESTS =====
@test "includes: should create necessary directories" {
# Create a temporary test environment
local temp_dir="/tmp/compiler_test_$RANDOM"
local build_path="$temp_dir/build"
local bin_path="$temp_dir/bin"
# Remove directories to test creation
rm -rf "$temp_dir"
# Source the includes script with custom paths - use a simpler approach
run bash -c "
export BUILDPATH='$build_path'
export BINPATH='$bin_path'
export AC_PATH_APPS='$SCRIPT_DIR/..'
# Create directories manually since includes.sh does this
mkdir -p \"\$BUILDPATH\"
mkdir -p \"\$BINPATH\"
echo 'Directories created'
[ -d '$build_path' ] && echo 'BUILD_EXISTS'
[ -d '$bin_path' ] && echo 'BIN_EXISTS'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "BUILD_EXISTS" ]]
[[ "$output" =~ "BIN_EXISTS" ]]
# Cleanup
rm -rf "$temp_dir"
}
@test "includes: should source required files" {
# Test that all required files are sourced without errors
run bash -c "
# Set minimal required environment
AC_PATH_APPS='$SCRIPT_DIR/..'
BUILDPATH='/tmp'
BINPATH='/tmp'
source '$SCRIPT_DIR/includes/includes.sh'
echo 'All files sourced successfully'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "All files sourced successfully" ]]
}
@test "includes: should set AC_PATH_COMPILER variable" {
# Test that AC_PATH_COMPILER is set correctly
run bash -c "
AC_PATH_APPS='$SCRIPT_DIR/..'
BUILDPATH='/tmp'
BINPATH='/tmp'
source '$SCRIPT_DIR/includes/includes.sh'
echo \"AC_PATH_COMPILER=\$AC_PATH_COMPILER\"
"
[ "$status" -eq 0 ]
[[ "$output" =~ "AC_PATH_COMPILER=" ]]
[[ "$output" =~ "/compiler" ]]
}
@test "includes: should register ON_AFTER_BUILD hook" {
# Test that the hook is registered
run bash -c "
AC_PATH_APPS='$SCRIPT_DIR/..'
BUILDPATH='/tmp'
BINPATH='/tmp'
source '$SCRIPT_DIR/includes/includes.sh'
# Check if the function exists
type ac_on_after_build > /dev/null && echo 'HOOK_FUNCTION_EXISTS'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "HOOK_FUNCTION_EXISTS" ]]
}
# ===== CONFIGURATION TESTS =====
@test "config: should handle missing config file gracefully" {
# Test behavior when config.sh doesn't exist
run bash -c "
export AC_PATH_APPS='$SCRIPT_DIR/..'
export AC_PATH_COMPILER='$SCRIPT_DIR'
export BUILDPATH='/tmp'
export BINPATH='/tmp'
# Test that missing config doesn't break sourcing
[ ! -f '$SCRIPT_DIR/config.sh' ] && echo 'NO_CONFIG_FILE'
echo 'Config handled successfully'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Config handled successfully" ]]
}
# ===== ENVIRONMENT VARIABLE TESTS =====
@test "environment: should handle platform detection" {
# Test that OSTYPE is properly handled
run bash -c "
source '$SCRIPT_DIR/includes/functions.sh'
echo \"Platform detected: \$OSTYPE\"
case \"\$OSTYPE\" in
linux*) echo 'LINUX_DETECTED' ;;
darwin*) echo 'DARWIN_DETECTED' ;;
msys*) echo 'MSYS_DETECTED' ;;
*) echo 'UNKNOWN_PLATFORM' ;;
esac
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Platform detected:" ]]
# Should detect at least one known platform
[[ "$output" =~ "LINUX_DETECTED" ]] || [[ "$output" =~ "DARWIN_DETECTED" ]] || [[ "$output" =~ "MSYS_DETECTED" ]] || [[ "$output" =~ "UNKNOWN_PLATFORM" ]]
}
@test "environment: should handle missing environment variables gracefully" {
# Test behavior with minimal environment
run bash -c "
unset BUILDPATH BINPATH SRCPATH MTHREADS
source '$SCRIPT_DIR/includes/functions.sh'
echo 'Functions loaded with minimal environment'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Functions loaded with minimal environment" ]]
}
# ===== HOOK SYSTEM TESTS =====
@test "hooks: ac_on_after_build should copy startup scripts" {
# Mock the cp command to test the hook
function cp() {
echo "CP called with args: $*"
return 0
}
export -f cp
# Set required variables
AC_PATH_APPS="$SCRIPT_DIR/.."
BINPATH="/tmp/test_bin"
export AC_PATH_APPS BINPATH
# Source and test the hook function
source "$SCRIPT_DIR/includes/includes.sh"
run ac_on_after_build
[ "$status" -eq 0 ]
[[ "$output" =~ "CP called with args:" ]]
[[ "$output" =~ "startup-scripts" ]]
}
+254
View File
@@ -0,0 +1,254 @@
#!/usr/bin/env bats
# AzerothCore Compiler Integration Test Suite
# Tests edge cases and integration scenarios for the compiler module
# Load the AzerothCore test framework
load '../../test-framework/bats_libs/acore-support'
load '../../test-framework/bats_libs/acore-assert'
# Setup that runs before each test
setup() {
compiler_setup
export SCRIPT_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd)"
}
# Cleanup that runs after each test
teardown() {
acore_test_teardown
}
# ===== INTEGRATION TESTS =====
@test "integration: should handle full compiler.sh workflow" {
# Test the complete workflow with safe options
run bash -c "
cd '$SCRIPT_DIR'
echo '7' | timeout 15s ./compiler.sh
echo 'First command completed'
echo 'quit' | timeout 10s ./compiler.sh
echo 'Quit command completed'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "First command completed" ]]
[[ "$output" =~ "Quit command completed" ]]
}
@test "integration: should handle multiple consecutive commands" {
# Test running multiple safe commands in sequence
run bash -c "
cd '$SCRIPT_DIR'
timeout 10s ./compiler.sh ccacheShowStats
echo 'Command 1 done'
timeout 10s ./compiler.sh quit
echo 'Command 2 done'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Command 1 done" ]]
[[ "$output" =~ "Command 2 done" ]]
}
@test "integration: should preserve working directory" {
# Test that the script doesn't change the working directory unexpectedly
local original_pwd="$(pwd)"
run bash -c "
cd '$SCRIPT_DIR'
original_dir=\$(pwd)
timeout 10s ./compiler.sh quit
current_dir=\$(pwd)
echo \"ORIGINAL: \$original_dir\"
echo \"CURRENT: \$current_dir\"
[ \"\$original_dir\" = \"\$current_dir\" ] && echo 'DIRECTORY_PRESERVED'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "DIRECTORY_PRESERVED" ]]
}
# ===== ERROR HANDLING TESTS =====
@test "error_handling: should handle script errors gracefully" {
# Test script behavior with set -e when encountering errors
run bash -c "
cd '$SCRIPT_DIR'
# Try to source a non-existent file to test error handling
timeout 5s bash -c 'set -e; source /nonexistent/file.sh' || echo 'ERROR_HANDLED'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "ERROR_HANDLED" ]]
}
@test "error_handling: should validate function availability" {
# Test that required functions are available after sourcing
run bash -c "
source '$SCRIPT_DIR/includes/functions.sh'
# Check for key functions
type comp_clean > /dev/null && echo 'COMP_CLEAN_AVAILABLE'
type comp_configure > /dev/null && echo 'COMP_CONFIGURE_AVAILABLE'
type comp_compile > /dev/null && echo 'COMP_COMPILE_AVAILABLE'
type comp_build > /dev/null && echo 'COMP_BUILD_AVAILABLE'
type comp_all > /dev/null && echo 'COMP_ALL_AVAILABLE'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "COMP_CLEAN_AVAILABLE" ]]
[[ "$output" =~ "COMP_CONFIGURE_AVAILABLE" ]]
[[ "$output" =~ "COMP_COMPILE_AVAILABLE" ]]
[[ "$output" =~ "COMP_BUILD_AVAILABLE" ]]
[[ "$output" =~ "COMP_ALL_AVAILABLE" ]]
}
# ===== PERMISSION TESTS =====
@test "permissions: should handle permission requirements" {
# Test script behavior with different permission scenarios
run bash -c "
# Test SUDO variable detection
source '$SCRIPT_DIR/includes/functions.sh'
echo \"SUDO variable: '\$SUDO'\"
[ -n \"\$SUDO\" ] && echo 'SUDO_SET' || echo 'SUDO_EMPTY'
"
[ "$status" -eq 0 ]
# Should set SUDO appropriately based on EUID
[[ "$output" =~ "SUDO_SET" ]] || [[ "$output" =~ "SUDO_EMPTY" ]]
}
# ===== CLEANUP TESTS =====
@test "cleanup: comp_clean should handle various file types" {
# Create a comprehensive test directory structure
local test_dir="/tmp/compiler_cleanup_test_$RANDOM"
mkdir -p "$test_dir/subdir1/subdir2"
# Create various file types
touch "$test_dir/regular_file.txt"
touch "$test_dir/executable_file.sh"
touch "$test_dir/.hidden_file"
touch "$test_dir/subdir1/nested_file.obj"
touch "$test_dir/subdir1/subdir2/deep_file.a"
ln -s "$test_dir/regular_file.txt" "$test_dir/symlink_file"
# Make one file executable
chmod +x "$test_dir/executable_file.sh"
# Test cleanup
run bash -c "
export BUILDPATH='$test_dir'
source '$SCRIPT_DIR/includes/functions.sh'
comp_clean
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Cleaning build files" ]]
# Verify cleanup (directory should exist but files should be cleaned)
[ -d "$test_dir" ]
# The cleanup might not remove all files depending on the implementation
# Let's check if at least some cleanup occurred
local remaining_files=$(find "$test_dir" -type f | wc -l)
# Either all files are gone, or at least some cleanup happened
[[ "$remaining_files" -eq 0 ]] || [[ "$remaining_files" -lt 6 ]]
# Cleanup test directory
rm -rf "$test_dir"
}
# ===== THREAD DETECTION TESTS =====
@test "threading: should detect available CPU cores" {
# Test thread count detection logic
run bash -c "
# Simulate the thread detection logic from the actual function
MTHREADS=0
if [ \$MTHREADS == 0 ]; then
# Use nproc if available, otherwise simulate 4 cores
if command -v nproc >/dev/null 2>&1; then
MTHREADS=\$(nproc)
else
MTHREADS=4
fi
MTHREADS=\$((MTHREADS + 2))
fi
echo \"Detected threads: \$MTHREADS\"
"
[ "$status" -eq 0 ]
[[ "$output" =~ "Detected threads:" ]]
# Should be at least 3 (1 core + 2)
local thread_count=$(echo "$output" | grep -o '[0-9]\+')
[ "$thread_count" -ge 3 ]
}
# ===== CMAKE OPTION TESTS =====
@test "cmake: should build correct cmake command" {
# Mock cmake to capture command line arguments
run bash -c "
function cmake() {
echo 'CMAKE_COMMAND: $*'
return 0
}
export -f cmake
# Set comprehensive test environment
export SRCPATH='/test/src'
export BUILDPATH='/test/build'
export BINPATH='/test/bin'
export CTYPE='Release'
export CAPPS_BUILD='ON'
export CTOOLS_BUILD='ON'
export CSCRIPTS='ON'
export CMODULES='ON'
export CBUILD_TESTING='OFF'
export CSCRIPTPCH='ON'
export CCOREPCH='ON'
export CWARNINGS='ON'
export CCOMPILERC='gcc'
export CCOMPILERCXX='g++'
export CCUSTOMOPTIONS='-DCUSTOM_OPTION=1'
source '$SCRIPT_DIR/includes/functions.sh'
# Change to buildpath and run configure
cd /test || cd /tmp
comp_configure 2>/dev/null || echo 'Configure completed with warnings'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "CMAKE_COMMAND:" ]] || [[ "$output" =~ "Configure completed" ]]
}
# ===== PLATFORM SPECIFIC TESTS =====
@test "platform: should set correct options for detected platform" {
# Test platform-specific CMAKE options
run bash -c "
# Mock cmake to capture platform-specific options
function cmake() {
echo 'CMAKE_PLATFORM_ARGS: $*'
return 0
}
export -f cmake
export BUILDPATH='/tmp'
export SRCPATH='/tmp'
export BINPATH='/tmp'
export CTYPE='Release'
source '$SCRIPT_DIR/includes/functions.sh'
# Change to buildpath and run configure
cd /tmp
comp_configure 2>/dev/null || echo 'Configure completed with warnings'
"
[ "$status" -eq 0 ]
[[ "$output" =~ "CMAKE_PLATFORM_ARGS:" ]] || [[ "$output" =~ "Configure completed" ]]
}
+1 -1
View File
@@ -27,7 +27,7 @@ $SUDO apt-get install -y gdbserver gdb unzip curl \
libncurses-dev libreadline-dev clang g++ \
gcc git cmake make ccache \
libssl-dev libbz2-dev \
libboost-all-dev gnupg wget jq screen tmux
libboost-all-dev gnupg wget jq screen tmux expect
VAR_PATH="$CURRENT_PATH/../../../../var"
+1 -1
View File
@@ -31,4 +31,4 @@ if ! command -v cmake &>/dev/null ; then
fi
##########################################
brew install openssl@3 readline boost bash-completion curl unzip mysql ccache
brew install openssl@3 readline boost bash-completion curl unzip mysql ccache expect tmux screen jq
+1 -1
View File
@@ -32,7 +32,7 @@ $SUDO apt update
DEBIAN_FRONTEND="noninteractive" $SUDO \
apt-get -y install ccache clang cmake curl google-perftools libmysqlclient-dev make unzip jq screen tmux \
libreadline-dev libncurses5-dev libncursesw5-dev libbz2-dev git gcc g++ libssl-dev \
libncurses-dev libboost-all-dev gdb gdbserver
libncurses-dev libboost-all-dev gdb gdbserver expect
VAR_PATH="$CURRENT_PATH/../../../../var"
+62 -5
View File
@@ -135,6 +135,9 @@ export CONFIG="/path/to/worldserver.conf"
export SESSION_MANAGER="tmux" # none|auto|tmux|screen
export SESSION_NAME="ac-world"
# Interactive mode control
export AC_DISABLE_INTERACTIVE="0" # Set to 1 to disable interactive prompts (useful for non-interactive services)
# Debugging
export GDB_ENABLED="1" # 0 or 1
export GDB="/path/to/gdb.conf"
@@ -254,6 +257,29 @@ Production-ready service management:
# Force systemd
./service-manager.sh create world worldserver --provider systemd --bin-path /path/to/bin
# Create service with restart policy
./service-manager.sh create world worldserver --bin-path /path/to/bin --restart-policy always
```
#### Restart Policies
Services support two restart policies:
- **`on-failure`** (default): Restart only on crashes or errors (exit code != 0, only works with PM2 or systemd without tmux/screen)
- **`always`**: Restart on any exit, including clean shutdown (exit code 0)
**Important**: When using `--restart-policy always`, the in-game command `server shutdown X` will behave like `server restart X` - the service will automatically restart after shutdown. Only the shutdown message differs from a restart message.
```bash
# Service that restarts only on crashes (default behavior)
./service-manager.sh create auth authserver --bin-path /path/to/bin --restart-policy on-failure
# Service that always restarts (even on manual shutdown)
./service-manager.sh create world worldserver --bin-path /path/to/bin --restart-policy always
# Update existing service restart policy
./service-manager.sh update worldserver --restart-policy always
```
#### Service Operations
@@ -293,19 +319,22 @@ Production-ready service management:
### Method 1: Using Service Manager (Recommended)
```bash
# Create multiple world server instances
# Create multiple world server instances with different restart policies
./service-manager.sh create world1 worldserver \
--bin-path /path/to/bin \
--server-config /path/to/worldserver-realm1.conf
--server-config /path/to/worldserver-realm1.conf \
--restart-policy on-failure
./service-manager.sh create world2 worldserver \
--bin-path /path/to/bin \
--server-config /path/to/worldserver-realm2.conf
--server-config /path/to/worldserver-realm2.conf \
--restart-policy always
# Single auth server for all realms
# Single auth server for all realms (always restart for stability)
./service-manager.sh create auth authserver \
--bin-path /path/to/bin \
--server-config /path/to/authserver.conf
--server-config /path/to/authserver.conf \
--restart-policy always
```
### Method 2: Using Run Engine with Different Configurations
@@ -372,6 +401,29 @@ pm2 save
pm2 startup # Auto-start on boot
```
NOTE: pm2 cannot run tmux/screen sessions, but you can always use the `attach` command to connect to the service console because pm2 supports interactive mode.
### Environment Variables
The startup scripts recognize several environment variables for configuration and runtime behavior:
#### Service Detection Variables
- **`AC_LAUNCHED_BY_PM2`**: Set to `1` when launched by PM2 (automatically set by service-manager)
- Disables the use of the `unbuffer` command for output capture
- Enables non-interactive mode to prevent prompts
- More robust than relying on PM2's internal variables
- **`AC_DISABLE_INTERACTIVE`**: Controls interactive mode (0=enabled, 1=disabled)
- Automatically set based on execution context
- Prevents AzerothCore from showing interactive prompts in service environments
#### Configuration Variables
- **`RUN_ENGINE_*`**: See [Configuration](#configuration) section for complete list
- **`SERVICE_MODE`**: Set to `true` to enable service-specific behavior
- **`SESSION_MANAGER`**: Override session manager choice (tmux, screen, none, auto)
### Systemd Services
When using systemd as the service provider:
@@ -388,6 +440,11 @@ sudo systemctl status acore-auth
sudo systemctl enable acore-auth
```
**Enhanced systemd Integration:**
- **Automatic Service Type**: When using session managers (tmux/screen), services are automatically configured with `Type=forking` for proper daemon behavior
- **Smart ExecStop**: Services with session managers get automatic `ExecStop` commands to properly terminate tmux/screen sessions when stopping the service
- **Non-Interactive Mode**: Services without session managers automatically set `AC_DISABLE_INTERACTIVE=1` to prevent hanging on prompts
### Session Management in Services
Services can be configured with session managers for interactive access:
+2 -2
View File
@@ -51,7 +51,7 @@ export SCREEN_OPTIONS="${RUN_ENGINE_SCREEN_OPTIONS:-}"
# If disabled, output will be redirected to logging files
export WITH_CONSOLE="${RUN_ENGINE_WITH_CONSOLE:-0}"
# Server PID (needed when GDB_ENABLED=1)
export SERVERPID="${RUN_ENGINE_SERVERPID:-}"
# Restart policy (on-failure|always)
export RESTART_POLICY="always"
-7
View File
@@ -1,7 +0,0 @@
set logging enabled on
set debug timestamp
run
bt
bt full
info thread
thread apply all backtrace full
+19 -3
View File
@@ -254,7 +254,7 @@ function start_service() {
# Use environment/config values if not set from command line
BINPATH="${BINPATH:-$RUN_ENGINE_BINPATH}"
SERVERBIN="${SERVERBIN:-$RUN_ENGINE_SERVERBIN}"
CONFIG="${serverconfig:-$RUN_ENGINE_CONFIG}"
CONFIG="${serverconfig:-$CONFIG}"
echo "SERVERBIN: $SERVERBIN"
@@ -279,8 +279,9 @@ function start_service() {
# Set up directories and logging relative to BINPATH
LOGS_PATH="${LOGS_PATH:-"$BINPATH/logs"}"
CRASHES_PATH="${CRASHES_PATH:-"$BINPATH/crashes"}"
mkdir -p "$LOGS_PATH"
mkdir -p "$LOGS_PATH/crashes"
mkdir -p "$CRASHES_PATH"
else
# For system binaries, try to detect binary location and create logs accordingly
local detected_binpath=""
@@ -297,12 +298,13 @@ function start_service() {
# Set up log paths based on detected or fallback location
if [ -n "$detected_binpath" ]; then
LOGS_PATH="${LOGS_PATH:-"$detected_binpath/logs"}"
CRASHES_PATH="${CRASHES_PATH:-"$detected_binpath/crashes"}"
else
# Fallback to current directory for logs
LOGS_PATH="${LOGS_PATH:-./logs}"
CRASHES_PATH="${CRASHES_PATH:-"$./crashes"}"
fi
CRASHES_PATH="${CRASHES_PATH:-"$LOGS_PATH/crashes"}"
mkdir -p "$LOGS_PATH"
mkdir -p "$CRASHES_PATH"
@@ -333,6 +335,20 @@ function start_service() {
echo "Server config: default (not specified)"
fi
# Set AC_DISABLE_INTERACTIVE when running as a service without interactive session manager
# This prevents AzerothCore from showing interactive prompts when running under systemd/pm2
if [[ "${SERVICE_MODE:-false}" == "true" && "$session_manager" == "none" ]]; then
export AC_DISABLE_INTERACTIVE=1
echo "Service mode: Non-interactive mode enabled (AC_DISABLE_INTERACTIVE=1)"
else
export AC_DISABLE_INTERACTIVE=0
if [[ "${SERVICE_MODE:-false}" == "true" ]]; then
echo "Service mode: Interactive mode enabled (session manager: $session_manager)"
else
echo "Direct execution: Interactive mode enabled"
fi
fi
if [ "$use_restarter" = "true" ]; then
# Use simple-restarter for restart functionality
local gdb_enabled="${GDB_ENABLED:-0}"
+212 -51
View File
@@ -97,14 +97,18 @@ function print_help() {
echo ""
echo "Options:"
echo " --provider <type> - Service provider (pm2|systemd|auto, default: auto)"
echo " --bin-path <path> - Path to the server binary directory (required)"
echo " --bin-path <path> - Path to the server binary directory"
echo " --server-config <path> - Path to the server configuration file"
echo " --session-manager <type> - Session manager (none|tmux|screen, default: none)"
echo " Note: PM2 doesn't support tmux/screen, always uses 'none'"
echo " --gdb-enabled <0|1> - Enable GDB debugging (default: 0)"
echo " --system - Create as system service (systemd only, requires sudo)"
echo " --user - Create as user service (systemd only, default)"
echo " --max-memory <value> - Maximum memory limit (PM2 only)"
echo " --max-restarts <value> - Maximum restart attempts (PM2 only)"
echo " --restart-policy <policy> - Restart policy (on-failure|always, default: always)"
echo " on-failure: restart only on crash/error (only works with PM2 or systemd without tmux/screen)"
echo " always: restart on any exit (including 'server shutdown')"
echo " --no-start - Do not start the service after creation"
echo ""
echo "Examples:"
@@ -123,6 +127,9 @@ function print_help() {
echo " # Create service without starting it"
echo " $base_name create auth authserver --bin-path /home/user/azerothcore/bin --no-start"
echo ""
echo " # Create service with always restart policy"
echo " $base_name create world worldserver --bin-path /home/user/azerothcore/bin --restart-policy always"
echo ""
echo " # Update run-engine configuration"
echo " $base_name update worldserver-realm1 --session-manager screen --gdb-enabled 0"
echo ""
@@ -137,6 +144,9 @@ function print_help() {
echo " - Use --server-config for the actual server configuration file"
echo " - Services use run-engine in 'start' mode for single-shot execution"
echo " - Restart on crash is handled by PM2 or systemd, not by run-engine"
echo " - When restart-policy is 'always': 'server shutdown X' behaves like 'server restart X'"
echo " (only the in-game message differs, but the service will restart automatically)"
echo " - PM2 services always use session-manager 'none' and have built-in attach functionality"
echo " - attach command automatically detects the configured session manager and connects appropriately"
echo " - attach always provides interactive access to the server console"
echo " - Use 'logs' command to view service logs without interaction"
@@ -166,9 +176,11 @@ function validate_service_exists() {
local provider="$2"
if [ "$provider" = "pm2" ]; then
# Check if service exists in PM2
if ! pm2 id "$service_name" > /dev/null 2>&1; then
return 1 # Service not found
# Check if service exists in PM2 using pm2 describe (most reliable)
if pm2 describe "$service_name" >/dev/null 2>&1; then
return 0 # Service exists
else
return 1 # Service doesn't exist
fi
elif [ "$provider" = "systemd" ]; then
# Check if service exists in systemd
@@ -249,7 +261,8 @@ function get_service_info() {
function pm2_create_service() {
local service_name="$1"
local command="$2"
shift 2
local restart_policy="$3"
shift 3
check_pm2 || return 1
@@ -275,8 +288,18 @@ function pm2_create_service() {
esac
done
# Build PM2 start command
local pm2_cmd="pm2 start '$command$additional_args' --name '$service_name'"
# Set stop exit codes based on restart policy
local stop_exit_codes=""
if [ "$restart_policy" = "always" ]; then
# PM2 will restart on any exit code (including 0)
stop_exit_codes=""
else
# PM2 will not restart on clean shutdown (exit code 0)
stop_exit_codes=" --stop-exit-codes 0"
fi
# Build PM2 start command with AzerothCore environment variable
local pm2_cmd="AC_LAUNCHED_BY_PM2=1 pm2 start '$command$additional_args' --name '$service_name'$stop_exit_codes"
# Add memory limit if specified
if [ -n "$max_memory" ]; then
@@ -301,6 +324,7 @@ function pm2_create_service() {
fi
}
function pm2_remove_service() {
local service_name="$1"
@@ -309,12 +333,24 @@ function pm2_remove_service() {
echo -e "${YELLOW}Stopping and removing PM2 service: $service_name${NC}"
# Stop the service if it's running
if pm2 id "$service_name" > /dev/null 2>&1; then
if pm2 describe "$service_name" >/dev/null 2>&1; then
pm2 stop "$service_name" 2>/dev/null || true
pm2 delete "$service_name" 2>/dev/null
pm2 delete "$service_name" 2>/dev/null
# Wait for PM2 to process the stop/delete command with timeout
local timeout=10
local elapsed=0
while pm2 describe "$service_name" >/dev/null 2>&1; do
if [ "$elapsed" -ge "$timeout" ]; then
echo -e "${RED}Timeout reached while waiting for PM2 service '$service_name' to stop${NC}"
return 1
fi
sleep 0.5
elapsed=$((elapsed + 1))
done
# Verify the service was removed
if pm2 id "$service_name" > /dev/null 2>&1; then
if pm2 describe "$service_name" >/dev/null 2>&1; then
echo -e "${RED}Failed to remove PM2 service '$service_name'${NC}"
return 1
fi
@@ -365,8 +401,9 @@ function get_systemd_dir() {
function systemd_create_service() {
local service_name="$1"
local command="$2"
local restart_policy="$3"
local systemd_type="--user"
shift 2
shift 3
check_systemd || return 1
@@ -398,6 +435,25 @@ function systemd_create_service() {
mkdir -p "$systemd_dir"
fi
# Determine service type and ExecStop for systemd
local service_type="simple"
local exec_stop=""
# Load the run-engine config to check the session manager
local run_engine_config_path="$CONFIG_DIR/$service_name-run-engine.conf"
local session_manager="none"
local session_name="$service_name"
if [ -f "$run_engine_config_path" ]; then
# Read the session manager and name from the config file without sourcing it
session_manager=$(grep -oP 'SESSION_MANAGER="\K[^"]+' "$run_engine_config_path" || echo "none")
session_name=$(grep -oP 'SESSION_NAME="\K[^"]+' "$run_engine_config_path" || echo "$service_name")
fi
if [ "$session_manager" = "tmux" ] || [ "$session_manager" = "screen" ]; then
service_type="forking"
fi
# Create service file
echo -e "${YELLOW}Creating systemd service: $service_name${NC}"
@@ -409,9 +465,9 @@ Description=AzerothCore $service_name
After=network.target
[Service]
Type=forking
Type=${service_type}
ExecStart=$command
Restart=always
Restart=$restart_policy
RestartSec=3
User=$(whoami)
Group=$(id -gn)
@@ -430,9 +486,9 @@ Description=AzerothCore $service_name
After=network.target
[Service]
Type=forking
Type=${service_type}
ExecStart=$command
Restart=always
Restart=$restart_policy
RestartSec=3
WorkingDirectory=$(realpath "$bin_path")
StandardOutput=journal+console
@@ -536,7 +592,19 @@ function systemd_service_action() {
fi
echo -e "${YELLOW}${action^} systemd service: $service_name${NC}"
# stop tmux or screen session if applicable && action is stop or restart
if [[ "$action" == "stop" || "$action" == "restart" ]]; then
local session_manager=$(grep -oP 'SESSION_MANAGER="\K[^"]+' "$CONFIG_DIR/$service_name-run-engine.conf" || echo "none")
if [ "$session_manager" = "tmux" ]; then
echo -e "${YELLOW}Stopping tmux session for service: $service_name${NC}"
tmux kill-session -t "$service_name"
elif [ "$session_manager" = "screen" ]; then
echo -e "${YELLOW}Stopping screen session for service: $service_name${NC}"
screen -S "$service_name" -X quit
fi
fi
if [ "$systemd_type" = "--system" ]; then
systemctl "$action" "$service_name.service"
else
@@ -595,6 +663,7 @@ function create_service() {
local server_config=""
local session_manager="none"
local gdb_enabled="0"
local restart_policy="always"
local systemd_type="--user"
local pm2_opts=""
local auto_start="true"
@@ -622,6 +691,10 @@ function create_service() {
gdb_enabled="$2"
shift 2
;;
--restart-policy)
restart_policy="$2"
shift 2
;;
--system)
systemd_type="--system"
shift
@@ -658,14 +731,34 @@ function create_service() {
echo -e "${RED}Error: Invalid provider. Use 'pm2', 'systemd', or 'auto'${NC}"
return 1
fi
# Validate restart policy
if [[ "$restart_policy" != "on-failure" && "$restart_policy" != "always" ]]; then
echo -e "${RED}Error: Invalid restart policy. Use 'on-failure' or 'always'${NC}"
return 1
fi
# PM2 specific validation and adjustments
if [ "$provider" = "pm2" ]; then
# PM2 doesn't support session managers (tmux/screen), force to none
if [ "$session_manager" != "none" ]; then
echo -e "${YELLOW}Warning: PM2 doesn't support session managers. Setting session-manager to 'none'${NC}"
echo -e "${BLUE}PM2 has built-in attach functionality via: $0 attach $service_name${NC}"
session_manager="none"
fi
fi
# Determine server binary based on service type
local server_bin="${service_type}server"
local server_binary_path=$(realpath "$bin_path/$server_bin")
local real_config_path=""
if [ -n "$server_config" ]; then
real_config_path=$(realpath "$server_config")
fi
# Check if binary exists
if [ ! -f "$server_binary_path" ]; then
echo -e "${RED}Error: Server binary not found: $server_binary_path${NC}"
echo -e "${RED}Error: Server binary not found: $server_binary_path, please check your --bin-path option ${NC}"
return 1
fi
@@ -681,6 +774,13 @@ export GDB_ENABLED=$gdb_enabled
# Session manager (none|auto|tmux|screen)
export SESSION_MANAGER="$session_manager"
# Restart policy (on-failure|always)
export RESTART_POLICY="$restart_policy"
# Service mode - indicates this is running under a service manager (systemd/pm2)
# When true, AC_DISABLE_INTERACTIVE will be set if no interactive session manager is used
export SERVICE_MODE="true"
# Session name for tmux/screen (optional)
export SESSION_NAME="${service_name}"
@@ -691,7 +791,7 @@ export BINPATH="$bin_path"
export SERVERBIN="$server_bin"
# Server configuration file path
export CONFIG="$server_config"
export CONFIG="$real_config_path"
# Show console output for easier debugging
export WITH_CONSOLE=1
@@ -707,6 +807,9 @@ EOF
# run-engine configuration file
RUN_ENGINE_CONFIG_FILE="$run_engine_config"
# Restart policy
RESTART_POLICY="$restart_policy"
# Provider-specific options
SYSTEMD_TYPE="$systemd_type"
PM2_OPTS="$pm2_opts"
@@ -719,17 +822,17 @@ EOF
local service_creation_success=false
if [ "$provider" = "pm2" ]; then
if [ -n "$pm2_opts" ]; then
if pm2_create_service "$service_name" "$run_engine_cmd" $pm2_opts; then
if pm2_create_service "$service_name" "$run_engine_cmd" "$restart_policy" $pm2_opts; then
service_creation_success=true
fi
else
if pm2_create_service "$service_name" "$run_engine_cmd"; then
if pm2_create_service "$service_name" "$run_engine_cmd" "$restart_policy"; then
service_creation_success=true
fi
fi
elif [ "$provider" = "systemd" ]; then
if systemd_create_service "$service_name" "$run_engine_cmd" "$systemd_type"; then
if systemd_create_service "$service_name" "$run_engine_cmd" "$restart_policy" "$systemd_type"; then
service_creation_success=true
fi
fi
@@ -811,6 +914,11 @@ function update_service() {
config_updated=true
shift 2
;;
--restart-policy)
export RESTART_POLICY="$2"
config_updated=true
shift 2
;;
--system)
SYSTEMD_TYPE="--system"
shift
@@ -830,6 +938,20 @@ function update_service() {
esac
done
# PM2 specific validation for session manager
if [ "$provider" = "pm2" ] && [ -n "$SESSION_MANAGER" ] && [ "$SESSION_MANAGER" != "none" ]; then
echo -e "${YELLOW}Warning: PM2 doesn't support session managers. Setting session-manager to 'none'${NC}"
echo -e "${BLUE}PM2 has built-in attach functionality via: $0 attach $service_name${NC}"
export SESSION_MANAGER="none"
config_updated=true
fi
# Validate restart policy if provided
if [ -n "$RESTART_POLICY" ] && [[ "$RESTART_POLICY" != "on-failure" && "$RESTART_POLICY" != "always" ]]; then
echo -e "${RED}Error: Invalid restart policy. Use 'on-failure' or 'always'${NC}"
return 1
fi
if [ "$config_updated" = "true" ]; then
# Update run-engine configuration file
cat > "$RUN_ENGINE_CONFIG_FILE" << EOF
@@ -842,6 +964,12 @@ export GDB_ENABLED=${GDB_ENABLED:-0}
# Session manager (none|auto|tmux|screen)
export SESSION_MANAGER="${SESSION_MANAGER:-none}"
# Restart policy (on-failure|always)
export RESTART_POLICY="${RESTART_POLICY:-on-failure}"
# Service mode - indicates this is running under a service manager (systemd/pm2)
export SERVICE_MODE="true"
# Session name for tmux/screen
export SESSION_NAME="${service_name}"
@@ -871,6 +999,9 @@ EOF
# run-engine configuration file
RUN_ENGINE_CONFIG_FILE="$RUN_ENGINE_CONFIG_FILE"
# Restart policy
RESTART_POLICY="${RESTART_POLICY:-on-failure}"
# Provider-specific options
SYSTEMD_TYPE="$SYSTEMD_TYPE"
PM2_OPTS="$PM2_OPTS"
@@ -940,15 +1071,6 @@ function list_services() {
return
fi
# Show PM2 services
if [ -z "$provider_filter" ] || [ "$provider_filter" = "pm2" ]; then
local pm2_services=$(jq -r '.[] | select(.provider == "pm2") | .name' "$REGISTRY_FILE" 2>/dev/null)
if [ -n "$pm2_services" ] && command -v pm2 >/dev/null 2>&1; then
echo -e "\n${YELLOW}PM2 Services:${NC}"
pm2 list
fi
fi
# Show systemd services
if [ -z "$provider_filter" ] || [ "$provider_filter" = "systemd" ]; then
local systemd_services=$(jq -r '.[] | select(.provider == "systemd") | .name' "$REGISTRY_FILE" 2>/dev/null)
@@ -978,6 +1100,15 @@ function list_services() {
fi
fi
fi
# Show PM2 services
if [ -z "$provider_filter" ] || [ "$provider_filter" = "pm2" ]; then
local pm2_services=$(jq -r '.[] | select(.provider == "pm2") | .name' "$REGISTRY_FILE" 2>/dev/null)
if [ -n "$pm2_services" ] && command -v pm2 >/dev/null 2>&1; then
echo -e "\n${YELLOW}PM2 Services:${NC}"
pm2 list
fi
fi
}
function service_action() {
@@ -1079,33 +1210,67 @@ function attach_to_service() {
source "$RUN_ENGINE_CONFIG_FILE"
# Auto-detect session manager and attach accordingly
case "$SESSION_MANAGER" in
"tmux")
attach_tmux_session "$service_name" "$provider"
;;
"screen")
attach_screen_session "$service_name" "$provider"
;;
"none"|"auto"|*)
# No session manager - launch interactive shell directly
attach_interactive_shell "$service_name" "$provider"
;;
esac
if [ "$provider" = "pm2" ]; then
# PM2 has built-in attach functionality
attach_pm2_process "$service_name"
else
# For systemd, check session manager
case "$SESSION_MANAGER" in
"tmux")
attach_tmux_session "$service_name" "$provider"
;;
"screen")
attach_screen_session "$service_name" "$provider"
;;
"none"|"auto"|*)
# No session manager - show helpful message for systemd
attach_interactive_shell "$service_name" "$provider"
;;
esac
fi
}
function attach_pm2_process() {
local service_name="$1"
# First check if the service exists and get its ID
local pm2_id=$(pm2 id "$service_name" 2>/dev/null)
if [ -z "$pm2_id" ] || [ "$pm2_id" = "[]" ]; then
echo -e "${RED}Error: PM2 process '$service_name' not found${NC}"
return 1
fi
# Extract the numeric ID from the JSON response
local numeric_id=$(echo "$pm2_id" | jq -r '.[0] // empty')
if [ -z "$numeric_id" ]; then
echo -e "${RED}Error: Could not determine PM2 process ID for '$service_name'${NC}"
return 1
fi
echo -e "${YELLOW}Attaching to PM2 process: $service_name (ID: $numeric_id)${NC}"
pm2 attach "$numeric_id"
}
function attach_interactive_shell() {
local service_name="$1"
local provider="$2"
# Get service info again to access configuration
# For PM2, use PM2's attach functionality
if [ "$provider" = "pm2" ]; then
attach_pm2_process "$service_name"
return $?
fi
# For systemd without session manager, show helpful message
local service_info=$(get_service_info "$service_name")
local config_file=$(echo "$service_info" | jq -r '.config')
source "$config_file"
source "$RUN_ENGINE_CONFIG_FILE"
echo -e "${RED}Error: Cannot attach to service '$service_name'${NC} [for now]"
echo -e "${YELLOW}Interactive attachment requires a session manager (tmux or screen).${NC}"
echo -e "${RED}Error: Cannot attach to systemd service '$service_name'${NC}"
echo -e "${YELLOW}Interactive attachment for systemd requires a session manager (tmux or screen).${NC}"
echo ""
echo -e "${BLUE}Current session manager: $SESSION_MANAGER${NC}"
echo ""
@@ -1145,9 +1310,7 @@ function attach_tmux_session() {
else
echo -e "${RED}Error: tmux session '$tmux_session' not found${NC}"
echo -e "${YELLOW}Available tmux sessions:${NC}"
tmux list-sessions 2>/dev/null || echo "No active tmux sessions"
echo -e "${BLUE}Starting new interactive session instead...${NC}"
attach_interactive_shell "$service_name" "$provider"
tmux list-sessions 2>/dev/null || echo "No active tmux sessions (is it stopped or restarting?)"
fi
}
@@ -1173,9 +1336,7 @@ function attach_screen_session() {
else
echo -e "${RED}Error: screen session '$screen_session' not found${NC}"
echo -e "${YELLOW}Available screen sessions:${NC}"
screen -list 2>/dev/null || echo "No active screen sessions"
echo -e "${BLUE}Starting new interactive session instead...${NC}"
attach_interactive_shell "$service_name" "$provider"
screen -list 2>/dev/null || echo "No active screen sessions (is it stopped or restarting?)"
fi
}
@@ -31,11 +31,9 @@ CRASHES_PATH="$8"
BINARY="$BINPATH/$BINFILE"
# Default values (same as starter)
DEFAULT_CRASHES_PATH="./crashes"
DEFAULT_GDB_FILE="$CURRENT_PATH/gdb.conf"
# Set defaults if not provided
CRASHES_PATH="${CRASHES_PATH:-$DEFAULT_CRASHES_PATH}"
GDB_FILE="${GDB_FILE:-$DEFAULT_GDB_FILE}"
# Counters for crash detection
+56 -36
View File
@@ -3,16 +3,17 @@
# AzerothCore Starter Script
# This script handles the execution of AzerothCore binaries with optional GDB support
#
# Usage: starter <binary> [gdb_file] [config] [syslog] [syserr] [gdb_enabled] [crashes_path]
# Usage: starter <binpath> <binfile> [gdb_file] [config] [syslog] [syserr] [gdb_enabled] [crashes_path]
#
# Parameters:
# $1 - Binary to execute (required)
# $2 - GDB configuration file (optional)
# $3 - Configuration file path (optional)
# $4 - System log file (optional)
# $5 - System error file (optional)
# $6 - GDB enabled flag (0/1, optional)
# $7 - Crashes directory path (optional)
# $1 - Binary path (required)
# $2 - Binary file name (required)
# $3 - GDB configuration file (optional)
# $4 - Configuration file path (optional)
# $5 - System log file (optional)
# $6 - System error file (optional)
# $7 - GDB enabled flag (0/1, optional)
# $8 - Crashes directory path (optional)
BINPATH="$1"
BINFILE="$2"
@@ -23,25 +24,22 @@ SYSERR="$6"
GDB_ENABLED="${7:-0}"
CRASHES_PATH="$8"
BINARY=$(realpath "$BINPATH/$BINFILE")
# Default values
CURRENT_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_CRASHES_PATH="$CURRENT_PATH/logs/crashes"
DEFAULT_GDB_FILE="$CURRENT_PATH/gdb.conf"
DEFAULT_CRASHES_PATH=$(realpath "$BINPATH/crashes")
[ -n "$CONFIG" ] && CONFIG_ABS=$(realpath "$CONFIG")
# Set defaults if not provided
CONFIG="${CONFIG:-""}"
CRASHES_PATH="${CRASHES_PATH:-$DEFAULT_CRASHES_PATH}"
GDB_FILE="${GDB_FILE:-$DEFAULT_GDB_FILE}"
# Validate binary
if [ -z "$BINARY" ]; then
echo "Error: Binary parameter is required"
echo "Usage: $0 <binary> [gdb_file] [config] [syslog] [syserr] [gdb_enabled] [crashes_path]"
if [ -z "$BINPATH" ] || [ -z "$BINFILE" ]; then
echo "Error: Binary path and file are required"
echo "Usage: $0 <binpath> <binfile> [gdb_file] [config] [syslog] [syserr] [gdb_enabled] [crashes_path]"
exit 1
fi
BINARY="$BINPATH/$BINFILE"
if [ ! -f "$BINARY" ]; then
echo "Error: Binary '$BINARY' not found"
exit 1
@@ -50,7 +48,7 @@ fi
# Create crashes directory if it doesn't exist
mkdir -p "$CRASHES_PATH"
cd $BINPATH || {
cd "$BINPATH" || {
echo "Error: Could not change to binary path '$BINPATH'"
exit 1
}
@@ -59,22 +57,25 @@ EXECPATH=$(realpath "$BINFILE")
if [ "$GDB_ENABLED" -eq 1 ]; then
echo "Starting $EXECPATH with GDB enabled"
# Generate GDB configuration on the fly
TIMESTAMP=$(date +%Y-%m-%d-%H-%M-%S)
GDB_TEMP_FILE="$CRASHES_PATH/gdb-$TIMESTAMP.conf"
GDB_OUTPUT_FILE="$CRASHES_PATH/gdb-$TIMESTAMP.txt"
# Create GDB configuration
cat > "$GDB_TEMP_FILE" << EOF
# Create GDB configuration file if it is not defined
if [ -z "$GDB_FILE" ]; then
# Create GDB configuration
cat > "$GDB_TEMP_FILE" << EOF
set logging file $GDB_OUTPUT_FILE
set logging enabled on
set debug timestamp
EOF
# Add run command with config if specified
if [ -n "$CONFIG" ]; then
echo "run -c $CONFIG" >> "$GDB_TEMP_FILE"
if [ -n "$CONFIG_ABS" ]; then
echo "run -c $CONFIG_ABS" >> "$GDB_TEMP_FILE"
else
echo "run" >> "$GDB_TEMP_FILE"
fi
@@ -86,32 +87,51 @@ info thread
thread apply all backtrace full
EOF
GDB_FILE="$GDB_TEMP_FILE"
fi
# Create log files if specified
if [ -n "$SYSLOG" ]; then
[ ! -f "$SYSLOG" ] && touch "$SYSLOG"
fi
if [ -n "$SYSERR" ]; then
[ ! -f "$SYSERR" ] && touch "$SYSERR"
fi
# Execute with GDB
if [ "${WITH_CONSOLE:-0}" -eq 0 ] && [ -n "$SYSLOG" ] && [ -n "$SYSERR" ]; then
gdb -x "$GDB_TEMP_FILE" --batch "$EXECPATH" >> "$SYSLOG" 2>> "$SYSERR"
gdb -x "$GDB_FILE" --batch "$EXECPATH" >> "$SYSLOG" 2>> "$SYSERR"
else
echo "> Console enabled"
if [ -n "$SYSLOG" ] && [ -n "$SYSERR" ]; then
gdb -x "$GDB_TEMP_FILE" --batch "$EXECPATH" > >(tee "$SYSLOG") 2> >(tee "$SYSERR" >&2)
gdb -x "$GDB_FILE" --batch "$EXECPATH" > >(tee "$SYSLOG") 2> >(tee "$SYSERR" >&2)
else
gdb -x "$GDB_TEMP_FILE" --batch "$EXECPATH"
gdb -x "$GDB_FILE" --batch "$EXECPATH"
fi
fi
# Cleanup temporary GDB file
rm -f "$GDB_TEMP_FILE"
else
if [ -n "$CONFIG" ]; then
script -q -e -c "$EXECPATH -c \"$CONFIG\""
else
script -q -e -c "$EXECPATH"
# clean up temporary GDB file if it exists
if [ -n "$GDB_TEMP_FILE" ]; then
# Clean up temporary GDB file
rm -f "$GDB_TEMP_FILE"
fi
fi
else
echo "Starting $BINFILE without GDB"
if [[ "$AC_LAUNCHED_BY_PM2" == "1" ]]; then
echo "Running under PM2"
"$EXECPATH" ${CONFIG_ABS:+-c "$CONFIG_ABS"}
else
if command -v unbuffer >/dev/null 2>&1; then
export AC_DISABLE_INTERACTIVE=0
unbuffer "$EXECPATH" ${CONFIG_ABS:+-c "$CONFIG_ABS"}
else
echo "⚠️ unbuffer not found, the output may not be line-buffered. Try installing expect."
exec "$EXECPATH" ${CONFIG_ABS:+-c "$CONFIG_ABS"}
fi
fi
fi
@@ -23,7 +23,7 @@ teardown() {
@test "starter: should fail with missing parameters" {
run timeout 3s "$SCRIPT_DIR/starter" '' ''
[ "$status" -ne 0 ]
[[ "$output" =~ "Error: Binary '/' not found" ]]
[[ "$output" =~ "Error: Binary path and file are required" ]]
}
@test "starter: should start with valid binary" {
@@ -38,7 +38,16 @@ teardown() {
@test "starter: should validate binary path exists" {
run "$SCRIPT_DIR/starter" "/nonexistent/path" "test-server"
[ "$status" -ne 0 ]
[[ "$output" =~ "Binary parameter is required" ]] || [[ "$output" =~ "No such file or directory" ]]
[[ "$output" =~ "Binary '/nonexistent/path/test-server' not found" ]]
}
@test "starter: should detect PM2 environment properly" {
cd "$TEST_DIR"
# Test with AC_LAUNCHED_BY_PM2=1 (should not use script command)
AC_LAUNCHED_BY_PM2=1 run timeout 5s "$SCRIPT_DIR/starter" "$TEST_DIR/bin" "test-server" "" "$TEST_DIR/test-server.conf" "" "" 0
debug_on_failure
# Should start without using script command
[[ "$output" =~ "Test server starting" ]]
}
# ===== SIMPLE RESTARTER TESTS =====
@@ -46,7 +55,7 @@ teardown() {
@test "simple-restarter: should fail with missing parameters" {
run timeout 3s "$SCRIPT_DIR/simple-restarter" '' ''
[ "$status" -ne 0 ]
[[ "$output" =~ "Error: Binary '/' not found" ]]
[[ "$output" =~ "Error: Binary path and file are required" ]]
}
@test "simple-restarter: should fail with missing binary" {
@@ -109,6 +118,31 @@ teardown() {
[[ "$output" =~ "Missing required arguments" ]] || [[ "$output" =~ "Error:" ]]
}
@test "service-manager: should validate restart policy values" {
run "$SCRIPT_DIR/service-manager.sh" create auth test-auth --bin-path /nonexistent --restart-policy invalid
[ "$status" -ne 0 ]
[[ "$output" =~ "Invalid restart policy" ]]
}
@test "service-manager: should accept valid restart policy values" {
# Test on-failure (should be accepted)
run "$SCRIPT_DIR/service-manager.sh" create auth test-auth --bin-path /nonexistent --restart-policy on-failure
# Should fail due to missing binary, not restart policy validation
[[ ! "$output" =~ "Invalid restart policy" ]]
# Test always (should be accepted)
run "$SCRIPT_DIR/service-manager.sh" create auth test-auth2 --bin-path /nonexistent --restart-policy always
# Should fail due to missing binary, not restart policy validation
[[ ! "$output" =~ "Invalid restart policy" ]]
}
@test "service-manager: should include restart policy in help output" {
run "$SCRIPT_DIR/service-manager.sh" help
[ "$status" -eq 0 ]
[[ "$output" =~ "--restart-policy" ]]
[[ "$output" =~ "on-failure|always" ]]
}
# ===== EXAMPLE SCRIPTS TESTS =====
@test "examples: restarter-world should show configuration error" {
+26 -2
View File
@@ -7,6 +7,18 @@
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# Count cores for parallel execution
if [[ -z "$ACORE_TEST_CORES" ]]; then
if command -v nproc >/dev/null 2>&1; then
ACORE_TEST_CORES=$(nproc)
elif command -v sysctl >/dev/null 2>&1; then
ACORE_TEST_CORES=$(sysctl -n hw.ncpu)
else
ACORE_TEST_CORES=1 # Fallback to single core if detection fails
fi
export ACORE_TEST_CORES
fi
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
@@ -28,6 +40,7 @@ show_help() {
echo " -c, --count Show test count only"
echo " -d, --debug Enable debug mode (shows output on failure)"
echo " -l, --list List available test modules"
echo " -j, --jobs <num> Set number of parallel jobs (default: $ACORE_TEST_CORES)"
echo " --dir <path> Run tests in specific directory"
echo " --all Run all tests in all modules"
echo ""
@@ -103,6 +116,17 @@ while [[ $# -gt 0 ]]; do
RUN_ALL=true
shift
;;
-j|--jobs)
if [[ "$2" =~ ^[0-9]+$ ]]; then
ACORE_TEST_CORES="$2"
export ACORE_TEST_CORES
shift 2
else
echo -e "${RED}Error: Invalid number of jobs specified: $2${NC}"
echo "Please provide a valid number."
exit 1
fi
;;
*.bats)
# Individual test files
TEST_FILES+=("$1")
@@ -234,7 +258,7 @@ if [[ "$COUNT_ONLY" == true ]]; then
fi
# Build BATS command
BATS_CMD="bats"
BATS_CMD="bats --jobs $ACORE_TEST_CORES"
# Set output format
if [[ "$TAP" == true ]]; then
@@ -256,7 +280,7 @@ fi
# Add test files
BATS_CMD+=" ${TEST_FILES[*]}"
echo -e "${BLUE}Running AzerothCore Tests${NC}"
echo -e "${BLUE}Running AzerothCore Tests with ${ACORE_TEST_CORES} jobs${NC}"
echo -e "${YELLOW}Test directories: ${TEST_SEARCH_PATHS[*]}${NC}"
echo -e "${YELLOW}Test files: ${#TEST_FILES[@]}${NC}"
if [[ -n "$FILTER" ]]; then
@@ -0,0 +1,3 @@
-- DB update 2025_03_09_00 -> 2025_07_11_00
--
ALTER TABLE `world_state` ENGINE = InnoDB;
@@ -0,0 +1,8 @@
-- DB update 2025_07_03_03 -> 2025_07_07_00
-- On death remove aura
UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` = 28511;
DELETE FROM `smart_scripts` WHERE (`entryorguid` = 28511) AND (`source_type` = 0) AND (`id` IN (3));
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(28511, 0, 3, 0, 6, 0, 100, 512, 0, 0, 0, 0, 0, 0, 28, 51852, 0, 0, 0, 0, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 'Eye of Acherus - On Just Died - Remove Aura \'The Eye of Acherus\'');
@@ -0,0 +1,68 @@
-- DB update 2025_07_07_00 -> 2025_07_08_00
-- Set Spell Script
DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_chapter2_persuasive_strike';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
(52781, 'spell_chapter2_persuasive_strike');
-- Remove Flag Stunned from Scarlet Commanders
UPDATE `creature_template` SET `unit_flags` = `unit_flags` &~ 262144 WHERE (`entry` = 28936);
-- Remove c++ script and set SmartAI for Scarlet Crusaders/Preachers/Commanders/Marksmen
UPDATE `creature_template` SET `ScriptName` = '', `AIName` = 'SmartAI' WHERE `entry` IN (28610, 28936, 28939, 28940);
-- Scarlet Commander SmartAI
DELETE FROM `smart_scripts` WHERE (`entryorguid` = 28936) AND (`source_type` = 0) AND (`id` IN (0));
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(28936, 0, 0, 0, 0, 0, 100, 0, 4000, 8000, 4000, 8000, 0, 0, 11, 52221, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Commander - In Combat - Cast \'Heroic Strike\'');
-- Scarlet Crusader SmartAI
DELETE FROM `smart_scripts` WHERE (`entryorguid` = 28940) AND (`source_type` = 0) AND (`id` IN (0));
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(28940, 0, 0, 0, 0, 0, 100, 0, 4000, 8000, 4000, 8000, 0, 0, 11, 52221, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Crusader - In Combat - Cast \'Heroic Strike\'');
-- Scarlet Marksman SmartAI
DELETE FROM `smart_scripts` WHERE (`entryorguid` = 28610) AND (`source_type` = 0) AND (`id` IN (0));
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(28610, 0, 0, 0, 0, 0, 100, 0, 4000, 8000, 4000, 8000, 0, 0, 11, 32915, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Marksman - In Combat - Cast \'Raptor Strike\'');
-- Scarlet Preacher SmartAI
DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 28939);
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(28939, 0, 0, 0, 60, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 34809, 32, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Preacher - On Update - Cast \'Holy Fury\''),
(28939, 0, 1, 0, 0, 0, 100, 0, 8000, 12000, 8000, 12000, 0, 0, 11, 34809, 33, 0, 1, 0, 0, 26, 10, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Preacher - In Combat - Cast \'Holy Fury\''),
(28939, 0, 2, 0, 0, 0, 100, 0, 0, 0, 2500, 2500, 0, 0, 11, 15498, 64, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Preacher - In Combat - Cast \'Holy Smite\''),
(28939, 0, 3, 0, 0, 0, 100, 0, 6000, 12000, 20000, 25000, 0, 0, 11, 19725, 32, 0, 1, 0, 0, 9, 28897, 0, 20, 1, 0, 0, 0, 0, 'Scarlet Preacher - In Combat - Cast \'Turn Undead\'');
-- Condition for Scarlet Commander
DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 17) AND (`SourceGroup` = 0) AND (`SourceEntry` = 52781) AND (`SourceId` = 0) AND (`ElseGroup` = 3) AND (`ConditionTypeOrReference` = 31) AND (`ConditionTarget` = 1) AND (`ConditionValue1` = 3) AND (`ConditionValue2` = 28936) AND (`ConditionValue3` = 0);
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES
(17, 0, 52781, 0, 3, 31, 1, 3, 28936, 0, 0, 27, 0, '', 'Persuasive Strike - Scarlet Commander');
-- Condition for Jesseriah McCree
DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 17) AND (`SourceGroup` = 0) AND (`SourceEntry` = 52781) AND (`SourceId` = 0) AND (`ElseGroup` = 4) AND (`ConditionTypeOrReference` = 31) AND (`ConditionTarget` = 1) AND (`ConditionValue1` = 3) AND (`ConditionValue2` = 28964) AND (`ConditionValue3` = 0);
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES
(17, 0, 52781, 0, 4, 31, 1, 3, 28964, 0, 0, 27, 0, '', 'Persuasive Strike - Scarlet Lord Jesseriah McCree');
-- Add Texts inside McCree's table (for a future implementation).
DELETE FROM `creature_text` WHERE (`CreatureID` = 28964);
INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES
(28964, 1, 0, 'You\'ll be hanging in the gallows shortly, Scourge fiend!', 12, 0, 100, 0, 0, 0, 29160, 0, 'crusader SAY_CURSADER1'),
(28964, 1, 1, 'You\'ll have to kill me, monster. I will tell you NOTHING!', 12, 0, 100, 0, 0, 0, 29142, 0, 'crusader SAY_CURSADER2'),
(28964, 1, 2, 'You hit like a girl. Honestly. Is that the best you can do?', 12, 0, 100, 0, 0, 0, 29146, 0, 'crusader SAY_CURSADER3'),
(28964, 1, 3, 'ARGH! You burned my last good tabard!', 12, 0, 100, 0, 0, 0, 29162, 0, 'crusader SAY_CURSADER4'),
(28964, 1, 4, 'Argh... The pain... The pain is almost as unbearable as the lashings I received in grammar school when I was but a child.', 12, 0, 100, 0, 0, 0, 29143, 0, 'crusader SAY_CURSADER5'),
(28964, 1, 5, 'I used to work for Grand Inquisitor Isillien! Your idea of pain is a normal mid-afternoon for me!', 12, 0, 100, 0, 0, 0, 29161, 0, 'crusader SAY_CURSADER6'),
(28964, 2, 0, 'I\'ll tell you everything! STOP! PLEASE!', 12, 0, 100, 0, 0, 0, 29149, 0, 'break crusader SAY_PERSUADED1'),
(28964, 3, 0, 'We... We have only been told that the "Crimson Dawn" is an awakening. You... You see, the Light speaks to the High General. It is the Light...', 12, 0, 100, 0, 0, 0, 29150, 0, 'break crusader SAY_PERSUADED2'),
(28964, 4, 0, 'The Light that guides us. This movement was set in motion before you came... We... We do as we are told. It is what must be done.', 12, 0, 100, 0, 0, 0, 29151, 0, 'break crusader SAY_PERSUADED3'),
(28964, 5, 0, 'I know very litte else... The High General chooses who may go and who must stay behind. There\'s nothing else... You must believe me!', 12, 0, 100, 0, 0, 0, 29152, 0, 'break crusader SAY_PERSUADED4'),
(28964, 6, 0, 'LIES! The pain you are about to endure will be talked about for years to come!', 12, 0, 100, 0, 0, 0, 29163, 0, 'break crusader SAY_PERSUADED5'),
(28964, 7, 0, 'NO! PLEASE! There is one more thing that I forgot to mention... A courier comes soon... From Hearthglen. It...', 12, 0, 100, 0, 0, 0, 29153, 0, 'break crusader SAY_PERSUADED6'),
(28964, 8, 0, 'I\'ll tear the secrets from your soul! Tell me about the "Crimson Dawn" and your life may be spared!', 12, 0, 100, 0, 0, 0, 29138, 0, 'player SAY_PERSUADE1'),
(28964, 8, 1, 'Tell me what you know about "Crimson Dawn" or the beatings will continue!', 12, 0, 100, 0, 0, 0, 29134, 0, 'player SAY_PERSUADE2'),
(28964, 8, 2, 'I\'m through being courteous with your kind, human! What is the "Crimson Dawn?"', 12, 0, 100, 0, 0, 0, 29135, 0, 'player SAY_PERSUADE3'),
(28964, 8, 3, 'Is your life worth so little? Just tell me what I need to know about "Crimson Dawn" and I\'ll end your suffering quickly.', 12, 0, 100, 0, 0, 0, 29139, 0, 'player SAY_PERSUADE4'),
(28964, 8, 4, 'I can keep this up for a very long time, Scarlet dog! Tell me about the "Crimson Dawn!"', 12, 0, 100, 0, 0, 0, 29137, 0, 'player SAY_PERSUADE'),
(28964, 8, 5, 'What is the "Crimson Dawn?"', 12, 0, 100, 0, 0, 0, 29133, 0, 'player SAY_PERSUADE6'),
(28964, 8, 6, '"Crimson Dawn!" What is it! Speak!', 12, 0, 100, 0, 0, 0, 29136, 0, 'player SAY_PERSUADE7');
@@ -0,0 +1,97 @@
-- DB update 2025_07_08_00 -> 2025_07_08_01
DELETE FROM `creature_formations` WHERE `leaderGUID` IN (96764, 96786, 96763, 96776, 96775, 96828, 96825, 96831, 96778, 96777, 96824, 96780, 96767, 96769, 96773, 96779, 96771, 96781);
INSERT INTO `creature_formations` (`leaderGUID`, `memberGUID`, `dist`, `angle`, `groupAI`, `point_1`, `point_2`) VALUES
(96764, 96764, 0, 0, 3, 0, 0),
(96764, 96765, 0, 0, 3, 0, 0),
(96786, 96786, 0, 0, 515, 0, 0),
(96786, 96814, 2, 270, 515, 0, 0),
(96763, 96763, 0, 0, 3, 0, 0),
(96763, 96788, 0, 0, 3, 0, 0),
(96763, 96801, 0, 0, 3, 0, 0),
(96763, 96816, 0, 0, 3, 0, 0),
(96776, 96776, 0, 0, 3, 0, 0),
(96776, 96787, 0, 0, 3, 0, 0),
(96776, 96799, 0, 0, 3, 0, 0),
(96776, 96815, 0, 0, 3, 0, 0),
(96775, 96775, 0, 0, 3, 0, 0),
(96775, 96813, 0, 0, 3, 0, 0),
(96775, 96797, 0, 0, 3, 0, 0),
(96775, 96785, 0, 0, 3, 0, 0),
(96828, 96828, 0, 0, 3, 0, 0),
(96828, 96834, 0, 0, 3, 0, 0),
(96828, 96829, 0, 0, 3, 0, 0),
(96828, 96833, 0, 0, 3, 0, 0),
(96828, 96837, 0, 0, 3, 0, 0),
(96828, 96838, 0, 0, 3, 0, 0),
(96825, 96825, 0, 0, 3, 0, 0),
(96825, 96830, 0, 0, 3, 0, 0),
(96831, 96831, 0, 0, 3, 0, 0),
(96831, 96832, 0, 0, 3, 0, 0),
(96831, 96826, 0, 0, 3, 0, 0),
(96831, 96836, 0, 0, 3, 0, 0),
(96831, 96827, 0, 0, 3, 0, 0),
(96831, 96835, 0, 0, 3, 0, 0),
(96778, 96778, 0, 0, 3, 0, 0),
(96778, 96805, 0, 0, 3, 0, 0),
(96778, 96768, 0, 0, 3, 0, 0),
(96778, 96843, 0, 0, 3, 0, 0),
(96778, 96789, 0, 0, 3, 0, 0),
(96777, 96777, 0, 0, 3, 0, 0),
(96777, 96802, 0, 0, 3, 0, 0),
(96777, 96766, 0, 0, 3, 0, 0),
(96777, 96817, 0, 0, 3, 0, 0),
(96824, 96824, 0, 0, 3, 0, 0),
(96824, 96774, 0, 0, 3, 0, 0),
(96824, 96796, 0, 0, 3, 0, 0),
(96824, 96812, 0, 0, 3, 0, 0),
(96780, 96780, 0, 0, 3, 0, 0),
(96780, 96808, 0, 0, 3, 0, 0),
(96780, 96849, 0, 0, 3, 0, 0),
(96780, 96791, 0, 0, 3, 0, 0),
(96780, 96846, 0, 0, 3, 0, 0),
(96767, 96767, 0, 0, 3, 0, 0),
(96767, 96839, 0, 0, 3, 0, 0),
(96767, 96803, 0, 0, 3, 0, 0),
(96767, 96818, 0, 0, 3, 0, 0),
(96767, 96842, 0, 0, 3, 0, 0),
(96769, 96769, 0, 0, 3, 0, 0),
(96769, 96806, 0, 0, 3, 0, 0),
(96769, 96820, 0, 0, 3, 0, 0),
(96769, 96840, 0, 0, 3, 0, 0),
(96769, 96844, 0, 0, 3, 0, 0),
(96773, 96773, 0, 0, 3, 0, 0),
(96773, 96784, 0, 0, 3, 0, 0),
(96773, 96811, 0, 0, 3, 0, 0),
(96773, 96795, 0, 0, 3, 0, 0),
(96779, 96779, 0, 0, 3, 0, 0),
(96779, 96848, 0, 0, 3, 0, 0),
(96779, 96807, 0, 0, 3, 0, 0),
(96779, 96790, 0, 0, 3, 0, 0),
(96779, 96845, 0, 0, 3, 0, 0),
(96771, 96771, 0, 0, 3, 0, 0),
(96771, 96810, 0, 0, 3, 0, 0),
(96771, 96793, 0, 0, 3, 0, 0),
(96771, 96821, 0, 0, 3, 0, 0),
(96781, 96781, 0, 0, 3, 0, 0),
(96781, 96850, 0, 0, 3, 0, 0),
(96781, 96847, 0, 0, 3, 0, 0),
(96781, 96770, 0, 0, 3, 0, 0),
(96781, 96809, 0, 0, 3, 0, 0),
(96781, 96841, 0, 0, 3, 0, 0);
@@ -0,0 +1,9 @@
-- DB update 2025_07_08_01 -> 2025_07_09_00
-- Edit Spell_Area
UPDATE `spell_area` SET `quest_start` = 12801, `quest_start_status` = `quest_start_status` &~ 10 WHERE `spell` = 58354;
-- Add the second Darion Mograine quest starter
DELETE FROM `creature_queststarter` WHERE (`quest` = 13165) AND (`id` IN (31084));
INSERT INTO `creature_queststarter` (`id`, `quest`) VALUES
(31084, 13165);
@@ -0,0 +1,23 @@
-- DB update 2025_07_09_00 -> 2025_07_10_00
-- Set SmartAI
UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE (`entry` IN (28610, 28936, 28940, 28939, 28964));
-- Scarlet Commander, Crusader and Marksman.
DELETE FROM `smart_scripts` WHERE (`entryorguid` IN (28610, 28936, 28940)) AND (`source_type` = 0) AND (`id` IN (1));
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(28610, 0, 1, 0, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 19, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Marksman - On Respawn - Remove Flags Not Attackable'),
(28940, 0, 1, 0, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 19, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Crusader - On Respawn - Remove Flags Not Attackable'),
(28936, 0, 1, 0, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 19, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Commander - On Respawn - Remove Flags Not Attackable');
-- Scarlet Preacher
DELETE FROM `smart_scripts` WHERE (`entryorguid` = 28939) AND (`source_type` = 0) AND (`id` IN (4));
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(28939, 0, 4, 0, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 19, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Preacher - On Respawn - Remove Flags Not Attackable');
-- Scarlet Lord Jesseriah McCree.
DELETE FROM `smart_scripts` WHERE (`entryorguid` = 28964) AND (`source_type` = 0) AND (`id` IN (0, 1, 2));
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
(28964, 0, 0, 0, 0, 0, 100, 0, 4000, 8000, 8000, 12000, 0, 0, 11, 52835, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Lord Jesseriah McCree - In Combat - Cast \'Cleave\''),
(28964, 0, 1, 0, 0, 0, 100, 0, 12000, 18000, 30000, 35000, 0, 0, 11, 52836, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Lord Jesseriah McCree - In Combat - Cast \'Holy Wrath\''),
(28964, 0, 2, 0, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 19, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Scarlet Lord Jesseriah McCree - On Respawn - Remove Flags Not Attackable');
@@ -0,0 +1,5 @@
-- DB update 2025_07_10_00 -> 2025_07_12_00
--
DELETE FROM `spell_script_names` WHERE `spell_id` = 53099 AND `ScriptName` = 'spell_portal_effect_acherus';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES(53099, 'spell_portal_effect_acherus');
DELETE FROM `spell_scripts` WHERE `id`=53099 AND `effIndex`=0;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,13 @@
-- DB update 2025_07_12_01 -> 2025_07_12_02
--
-- Creates the reference loot (40110) for Haunted Memento (40110)
DELETE FROM `reference_loot_template` WHERE `Entry` = 40110 AND `Item` = 40110;
INSERT INTO `reference_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES
(40110, 40110, 0, 100, 0, 1, 1, 1, 1, 'Scourge Invasion Event - Item: Haunted Memento');
-- Adds the reference loot (40110) above for the Haunted Memento (40110) to Ghoul Berserker (16141), Skeletal Shocktrooper (16298) and Spectral Soldier (16299)
DELETE FROM `creature_loot_template` WHERE `Entry` IN (16141, 16298, 16299) AND `Item` = 40110 AND `Reference` = 40110;
INSERT INTO `creature_loot_template` (`Entry`, `Item`, `Reference`, `Chance`, `QuestRequired`, `LootMode`, `GroupId`, `MinCount`, `MaxCount`, `Comment`) VALUES
(16141, 40110, 40110, 2, 0, 1, 1, 1, 1, 'Reference Loot: Scourge Invasion Event - Item: Haunted Memento'),
(16298, 40110, 40110, 2, 0, 1, 1, 1, 1, 'Reference Loot: Scourge Invasion Event - Item: Haunted Memento'),
(16299, 40110, 40110, 2, 0, 1, 1, 1, 1, 'Reference Loot: Scourge Invasion Event - Item: Haunted Memento');
@@ -0,0 +1,3 @@
-- DB update 2025_07_12_02 -> 2025_07_14_00
UPDATE `creature_addon` SET `auras` = '' WHERE (`guid` IN(128584, 129808, 129811, 129812, 129688, 129698, 129700, 129701, 129703, 129705, 129706, 129707, 129708, 129713, 129718, 129720, 129721, 129231, 129244));
@@ -0,0 +1,4 @@
-- DB update 2025_07_14_00 -> 2025_07_15_00
--
DELETE FROM `spell_script_names` WHERE `spell_id` = -53301 AND `ScriptName` = 'spell_hun_explosive_shot';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (-53301, 'spell_hun_explosive_shot');
+1
View File
@@ -36,6 +36,7 @@ static const unsigned int DT_BUFFER_TOO_SMALL = 1 << 4; // Result buffer for the
static const unsigned int DT_OUT_OF_NODES = 1 << 5; // Query ran out of nodes during search.
static const unsigned int DT_PARTIAL_RESULT = 1 << 6; // Query did not reach the end location, returning best guess.
static const unsigned int DT_ALREADY_OCCUPIED = 1 << 7; // A tile has already been assigned to the given x,y coordinate
static const unsigned int DT_SLOPE_TOO_STEEP = 1 << 8; // Surface slope too steep to be walkable.
// Returns true of status is success.
+41 -8
View File
@@ -99,25 +99,48 @@ void Metric::LoadFromConfigs()
// Cancel any scheduled operation if the config changed from Enabled to Disabled.
if (_enabled && !previousValue)
{
std::string connectionInfo = sConfigMgr->GetOption<std::string>("Metric.ConnectionInfo", "");
std::string connectionInfo = sConfigMgr->GetOption<std::string>("Metric.InfluxDB.Connection", "");
if (connectionInfo.empty())
{
LOG_ERROR("metric", "'Metric.ConnectionInfo' not specified in configuration file.");
LOG_ERROR("metric", "Metric.InfluxDB.Connection not specified in configuration file.");
return;
}
std::vector<std::string_view> tokens = Acore::Tokenize(connectionInfo, ';', true);
if (tokens.size() != 3)
if (tokens.size() != 2)
{
LOG_ERROR("metric", "'Metric.ConnectionInfo' specified with wrong format in configuration file.");
LOG_ERROR("metric", "Metric.InfluxDB.Connection specified with wrong format in configuration file.");
return;
}
_hostname.assign(tokens[0]);
_port.assign(tokens[1]);
_databaseName.assign(tokens[2]);
Connect();
_useV2 = sConfigMgr->GetOption<bool>("Metric.InfluxDB.v2", false);
if (_useV2)
{
_org = sConfigMgr->GetOption<std::string>("Metric.InfluxDB.Org", "");
_bucket = sConfigMgr->GetOption<std::string>("Metric.InfluxDB.Bucket", "");
_token = sConfigMgr->GetOption<std::string>("Metric.InfluxDB.Token", "");
if (_org.empty() || _bucket.empty() || _token.empty())
{
LOG_ERROR("metric", "InfluxDB v2 parameters missing: org, bucket, or token.");
return;
}
}
else
{
if (tokens.size() != 3)
{
LOG_ERROR("metric", "Metric.InfluxDB.Connection specified with wrong format in configuration file.");
return;
}
_databaseName.assign(tokens[2]);
}
Connect();
ScheduleSend();
ScheduleOverallStatusLog();
}
@@ -206,8 +229,18 @@ void Metric::SendBatch()
if (!GetDataStream().good() && !Connect())
return;
GetDataStream() << "POST " << "/write?db=" << _databaseName << " HTTP/1.1\r\n";
GetDataStream() << "Host: " << _hostname << ":" << _port << "\r\n";
if (_useV2)
{
GetDataStream() << "POST " << "/api/v2/write?bucket=" << _bucket
<< "&org=" << _org << "&precision=ns HTTP/1.1\r\n";
GetDataStream() << "Host: " << _hostname << ":" << _port << "\r\n";
GetDataStream() << "Authorization: Token " << _token << "\r\n";
}
else
{
GetDataStream() << "POST " << "/write?db=" << _databaseName << " HTTP/1.1\r\n";
GetDataStream() << "Host: " << _hostname << ":" << _port << "\r\n";
}
GetDataStream() << "Accept: */*\r\n";
GetDataStream() << "Content-Type: application/octet-stream\r\n";
GetDataStream() << "Content-Transfer-Encoding: binary\r\n";
+4
View File
@@ -71,6 +71,10 @@ private:
std::string _hostname;
std::string _port;
std::string _databaseName;
bool _useV2 = false;
std::string _org;
std::string _bucket;
std::string _token;
std::function<void()> _overallStatusLogger;
std::string _realmName;
std::unordered_map<std::string, int64> _thresholds;
@@ -679,6 +679,7 @@ Appender.Errors=2,5,0,Errors.log,w
#
Logger.root=2,Console Server
#Logger.metric=2,Console Server
#Logger.commands.gm=4,Console GM
Logger.diff=3,Console Server
Logger.mmaps=4,Server
@@ -813,6 +814,37 @@ Log.Async.Enable = 0
Metric.Enable = 0
#
# Metric.InfluxDB
# Description: Connection settings for InfluxDB.
#
# For InfluxDB v1:
# Only fill in Metric.InfluxDB.Connection.
#
# Example:
# Metric.InfluxDB.Connection = "hostname;port;database"
#
# For InfluxDB v2:
# Fill in every field.
#
# NOTE: Currently, Grafana will not work with the provided json files to visualize
# data from InfluxDB v2.
#
# Example:
# Metric.InfluxDB.Connection = "hostname;port"
# Metric.InfluxDB.v2 = 0 - (Disabled)
# 1 - (Enabled)
# Metric.InfluxDB.Org = "my-org"
# Metric.InfluxDB.Bucket = "my-bucket"
# Metric.InfluxDB.Token = "my-token"
#
Metric.InfluxDB.Connection = "127.0.0.1;8086;worldserver"
Metric.InfluxDB.v2 = 0
Metric.InfluxDB.Org = ""
Metric.InfluxDB.Bucket = ""
Metric.InfluxDB.Token = ""
#
# Metric.Interval
# Description: Interval between every batch of data sent in seconds.
@@ -823,15 +855,6 @@ Metric.Enable = 0
Metric.Interval = 1
#
# Metric.ConnectionInfo
# Description: Connection settings for metric database (currently InfluxDB).
# Example: "hostname;port;database"
# Default: "127.0.0.1;8086;worldserver"
#
Metric.ConnectionInfo = "127.0.0.1;8086;worldserver"
#
# Metric.OverallStatusInterval
# Description: Interval between every gathering of overall worldserver status data in seconds
@@ -1398,18 +1421,6 @@ CheckGameObjectLoS = 1
PreloadAllNonInstancedMapGrids = 0
#
# SetAllCreaturesWithWaypointMovementActive
# Description: Set all creatures with waypoint movement active. This means that they will start
# movement once they are loaded (which happens on grid load) and keep moving even
# when no player is near. This will increase CPU usage significantly and can be
# used with enabled "PreloadAllNonInstancedMapGrids" to start waypoint movement on
# server startup.
# Default: 0 - (Disabled)
# 1 - (Enabled)
SetAllCreaturesWithWaypointMovementActive = 0
#
# DontCacheRandomMovementPaths
# Description: Random movement paths (calculated using MoveMaps) can be cached to save cpu time,
@@ -3835,11 +3846,18 @@ Arena.QueueAnnouncer.Detail = 3
#
# Arena.ArenaStartRating
# Description: Start rating for new arena teams.
# Description: Start rating for new arena teams. (Applies to season 6 and higher)
# Default: 0
Arena.ArenaStartRating = 0
#
# Arena.LegacyArenaStartRating
# Description: Start rating for new arena teams. (Only applies to season 1 - 5)
# Default: 1500
Arena.LegacyArenaStartRating = 1500
#
# Arena.ArenaStartPersonalRating
# Description: Start personal rating when joining a team.
+8
View File
@@ -282,6 +282,14 @@ void PetAI::UpdateAI(uint32 diff)
}
}
if (spellInfo->HasEffect(SPELL_EFFECT_JUMP_DEST))
{
if (!spellUsed)
delete spell;
continue; // Pets must only jump to target
}
// No enemy, check friendly
if (!spellUsed)
{
+3 -1
View File
@@ -36,7 +36,9 @@ ArenaTeam::ArenaTeam()
Stats.WeekGames = 0;
Stats.SeasonGames = 0;
Stats.Rank = 0;
Stats.Rating = sWorld->getIntConfig(CONFIG_ARENA_START_RATING);
Stats.Rating = (sArenaSeasonMgr->GetCurrentSeason() < 6)
? sWorld->getIntConfig(CONFIG_LEGACY_ARENA_START_RATING)
: sWorld->getIntConfig(CONFIG_ARENA_START_RATING);
Stats.WeekWins = 0;
Stats.SeasonWins = 0;
}
@@ -419,6 +419,9 @@ TeamId BattlegroundAB::GetPrematureWinner()
bool BattlegroundAB::SetupBattleground()
{
_honorTics = BattlegroundMgr::IsBGWeekend(GetBgTypeID(true)) ? BG_AB_HONOR_TICK_WEEKEND : BG_AB_HONOR_TICK_NORMAL;
_reputationTics = BattlegroundMgr::IsBGWeekend(GetBgTypeID(true)) ? BG_AB_REP_TICK_WEEKEND : BG_AB_REP_TICK_NORMAL;
for (uint32 i = 0; i < BG_AB_DYNAMIC_NODES_COUNT; ++i)
{
AddObject(BG_AB_OBJECT_BANNER_NEUTRAL + BG_AB_OBJECTS_PER_NODE * i, BG_AB_OBJECTID_NODE_BANNER_0 + i, BG_AB_NodePositions[i][0], BG_AB_NodePositions[i][1], BG_AB_NodePositions[i][2], BG_AB_NodePositions[i][3], 0, 0, std::sin(BG_AB_NodePositions[i][3] / 2), cos(BG_AB_NodePositions[i][3] / 2), RESPAWN_ONE_DAY);
@@ -468,9 +471,6 @@ void BattlegroundAB::Init()
_bgEvents.Reset();
_honorTics = BattlegroundMgr::IsBGWeekend(GetBgTypeID(true)) ? BG_AB_HONOR_TICK_WEEKEND : BG_AB_HONOR_TICK_NORMAL;
_reputationTics = BattlegroundMgr::IsBGWeekend(GetBgTypeID(true)) ? BG_AB_REP_TICK_WEEKEND : BG_AB_REP_TICK_NORMAL;
_capturePointInfo[BG_AB_NODE_STABLES]._iconNone = WORLD_STATE_BATTLEGROUND_AB_STABLE_ICON;
_capturePointInfo[BG_AB_NODE_FARM]._iconNone = WORLD_STATE_BATTLEGROUND_AB_FARM_ICON;
_capturePointInfo[BG_AB_NODE_BLACKSMITH]._iconNone = WORLD_STATE_BATTLEGROUND_AB_BLACKSMITH_ICON;
@@ -166,7 +166,7 @@ enum BG_AB_Misc
BG_AB_HONOR_TICK_NORMAL = 260,
BG_AB_HONOR_TICK_WEEKEND = 160,
BG_AB_REP_TICK_NORMAL = 160,
BG_AB_REP_TICK_WEEKEND = 120,
BG_AB_REP_TICK_WEEKEND = 150,
BG_AB_WARNING_NEAR_VICTORY_SCORE = 1400,
BG_AB_MAX_TEAM_SCORE = 1600,
@@ -16,6 +16,7 @@
*/
#include "BattlegroundAV.h"
#include "BattlegroundMgr.h"
#include "CreatureTextMgr.h"
#include "Formulas.h"
#include "GameEventMgr.h"
@@ -56,6 +57,14 @@ BattlegroundAV::BattlegroundAV()
m_Mine_Timer = 0;
_reputationTower = 0;
_reputationCaptain = 0;
_reputationBoss = 0;
_reputationPerOwnedGraveyard = 0;
_reputationSurvivingCaptain = 0;
_reputationSurvivingTower = 0;
_reputationPerOwnedMine = 0;
for (BG_AV_Nodes i = BG_AV_NODES_FIRSTAID_STATION; i < BG_AV_NODES_MAX; ++i)
InitNode(i, TEAM_NEUTRAL, false);
@@ -93,7 +102,7 @@ void BattlegroundAV::HandleKillUnit(Creature* unit, Player* killer)
if (entry == BG_AV_CreatureInfo[AV_NPC_A_BOSS])
{
CastSpellOnTeam(23658, TEAM_HORDE); //this is a spell which finishes a quest where a player has to kill the boss
RewardReputationToTeam(729, BG_AV_REP_BOSS, TEAM_HORDE);
RewardReputationToTeam(729, _reputationBoss, TEAM_HORDE);
RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_BOSS), TEAM_HORDE);
EndBattleground(TEAM_HORDE);
DelCreature(AV_CPLACE_TRIGGER17);
@@ -101,7 +110,7 @@ void BattlegroundAV::HandleKillUnit(Creature* unit, Player* killer)
else if (entry == BG_AV_CreatureInfo[AV_NPC_H_BOSS])
{
CastSpellOnTeam(23658, TEAM_ALLIANCE); //this is a spell which finishes a quest where a player has to kill the boss
RewardReputationToTeam(730, BG_AV_REP_BOSS, TEAM_ALLIANCE);
RewardReputationToTeam(730, _reputationBoss, TEAM_ALLIANCE);
RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_BOSS), TEAM_ALLIANCE);
EndBattleground(TEAM_ALLIANCE);
DelCreature(AV_CPLACE_TRIGGER19);
@@ -114,7 +123,7 @@ void BattlegroundAV::HandleKillUnit(Creature* unit, Player* killer)
return;
}
m_CaptainAlive[0] = false;
RewardReputationToTeam(729, BG_AV_REP_CAPTAIN, TEAM_HORDE);
RewardReputationToTeam(729, _reputationCaptain, TEAM_HORDE);
RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_CAPTAIN), TEAM_HORDE);
UpdateScore(TEAM_ALLIANCE, (-1)*BG_AV_RES_CAPTAIN);
//spawn destroyed aura
@@ -132,7 +141,7 @@ void BattlegroundAV::HandleKillUnit(Creature* unit, Player* killer)
return;
}
m_CaptainAlive[1] = false;
RewardReputationToTeam(730, BG_AV_REP_CAPTAIN, TEAM_ALLIANCE);
RewardReputationToTeam(730, _reputationCaptain, TEAM_ALLIANCE);
RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_CAPTAIN), TEAM_ALLIANCE);
UpdateScore(TEAM_HORDE, (-1)*BG_AV_RES_CAPTAIN);
//spawn destroyed aura
@@ -174,7 +183,7 @@ void BattlegroundAV::HandleQuestComplete(uint32 questid, Player* player)
{
LOG_DEBUG("bg.battleground", "BG_AV Quest {} completed starting with unit upgrading..", questid);
for (BG_AV_Nodes i = BG_AV_NODES_FIRSTAID_STATION; i <= BG_AV_NODES_FROSTWOLF_HUT; ++i)
if (m_Nodes[i].OwnerId == player->GetTeamId() && m_Nodes[i].State == POINT_CONTROLED)
if (m_Nodes[i].OwnerId == player->GetTeamId() && m_Nodes[i].State == POINT_CONTROLLED)
{
DePopulateNode(i);
PopulateNode(i);
@@ -503,29 +512,49 @@ void BattlegroundAV::EndBattleground(TeamId winnerTeamId)
//first towers:
uint8 kills[2] = {0, 0}; // 0 = Alliance 1 = Horde
uint8 rep[2] = {0, 0}; // 0 = Alliance 1 = Horde
if (BattlegroundMgr::IsBGWeekend(GetBgTypeID(true)))
kills[winnerTeamId] += 4;
for (BG_AV_Nodes i = BG_AV_NODES_DUNBALDAR_SOUTH; i <= BG_AV_NODES_FROSTWOLF_WTOWER; ++i)
{
if (m_Nodes[i].State == POINT_CONTROLED)
if (m_Nodes[i].State == POINT_CONTROLLED)
{
if (m_Nodes[i].OwnerId == TEAM_ALLIANCE)
{
rep[0] += BG_AV_REP_SURVIVING_TOWER;
kills[0] += BG_AV_KILL_SURVIVING_TOWER;
}
if (m_Nodes[i].OwnerId == TEAM_NEUTRAL)
continue;
else
{
rep[0] += BG_AV_KILL_SURVIVING_TOWER;
kills[1] += BG_AV_KILL_SURVIVING_TOWER;
rep[m_Nodes[i].OwnerId] += _reputationSurvivingTower;
kills[m_Nodes[i].OwnerId] += BG_AV_KILL_SURVIVING_TOWER;
}
}
}
for (uint8 i = BG_AV_NODES_FIRSTAID_STATION; i <= BG_AV_NODES_FROSTWOLF_HUT; ++i)
{
if (m_Nodes[i].State == POINT_CONTROLLED)
{
if (m_Nodes[i].OwnerId == TEAM_NEUTRAL)
continue;
else
rep[m_Nodes[i].OwnerId] += _reputationPerOwnedGraveyard;
}
}
for (uint8 mine = 0; mine < 2; mine++)
{
if (m_Mine_Owner[mine] == TEAM_NEUTRAL)
continue;
rep[m_Mine_Owner[mine]] += _reputationPerOwnedMine;
}
for (TeamId iTeamId = TEAM_ALLIANCE; iTeamId <= TEAM_HORDE; iTeamId = TeamId(iTeamId + 1))
{
if (m_CaptainAlive[iTeamId])
{
kills[iTeamId] += BG_AV_KILL_SURVIVING_CAPTAIN;
rep[iTeamId] += BG_AV_REP_SURVIVING_CAPTAIN;
rep[iTeamId] += _reputationSurvivingCaptain;
}
if (rep[iTeamId] != 0)
RewardReputationToTeam(iTeamId == TEAM_ALLIANCE ? 730 : 729, rep[iTeamId], iTeamId);
@@ -629,7 +658,7 @@ void BattlegroundAV::EventPlayerDestroyedPoint(BG_AV_Nodes node)
SpawnBGObject(BG_AV_OBJECT_BURN_DUNBALDAR_SOUTH + i + (tmp * 10), RESPAWN_IMMEDIATELY);
UpdateScore((ownerId == TEAM_ALLIANCE) ? TEAM_HORDE : TEAM_ALLIANCE, -1 * BG_AV_RES_TOWER);
RewardReputationToTeam(ownerId == TEAM_ALLIANCE ? 730 : 729, BG_AV_REP_TOWER, ownerId);
RewardReputationToTeam(ownerId == TEAM_ALLIANCE ? 730 : 729, _reputationTower, ownerId);
RewardHonorToTeam(GetBonusHonorFromKill(BG_AV_KILL_TOWER), ownerId);
SpawnBGObject(static_cast<uint8>(BG_AV_OBJECT_TAURA_A_DUNBALDAR_SOUTH) + ownerId + (2 * tmp), RESPAWN_ONE_DAY);
@@ -863,7 +892,7 @@ uint32 BattlegroundAV::GetObjectThroughNode(BG_AV_Nodes node)
if (node >= BG_AV_NODES_ICEBLOOD_TOWER && node <= BG_AV_NODES_FROSTWOLF_WTOWER)
return node + 7;
}
else if (m_Nodes[node].State == POINT_CONTROLED)
else if (m_Nodes[node].State == POINT_CONTROLLED)
if (node <= BG_AV_NODES_STONEHEART_BUNKER)
return node;
}
@@ -874,7 +903,7 @@ uint32 BattlegroundAV::GetObjectThroughNode(BG_AV_Nodes node)
if (node <= BG_AV_NODES_STONEHEART_BUNKER)
return node + 22;
}
else if (m_Nodes[node].State == POINT_CONTROLED)
else if (m_Nodes[node].State == POINT_CONTROLLED)
{
if (node <= BG_AV_NODES_FROSTWOLF_HUT)
return node + 33;
@@ -1010,7 +1039,7 @@ void BattlegroundAV::EventPlayerAssaultsPoint(Player* player, uint32 object)
}
else if (m_Nodes[node].TotalOwnerId == TEAM_NEUTRAL) //recapping, when no team owns this node realy
{
if (!(m_Nodes[node].State != POINT_CONTROLED))
if (!(m_Nodes[node].State != POINT_CONTROLLED))
return;
if (teamId == TEAM_ALLIANCE)
@@ -1138,14 +1167,14 @@ uint8 BattlegroundAV::GetWorldStateType(uint8 state, TeamId teamId) //this is us
//a_c a_a h_c h_a the positions in worldstate-array
if (teamId == TEAM_ALLIANCE)
{
if (state == POINT_CONTROLED || state == POINT_DESTROYED)
if (state == POINT_CONTROLLED || state == POINT_DESTROYED)
return 0;
if (state == POINT_ASSAULTED)
return 1;
}
if (teamId == TEAM_HORDE)
{
if (state == POINT_DESTROYED || state == POINT_CONTROLED)
if (state == POINT_DESTROYED || state == POINT_CONTROLLED)
return 2;
if (state == POINT_ASSAULTED)
return 3;
@@ -1193,7 +1222,7 @@ GraveyardStruct const* BattlegroundAV::GetClosestGraveyard(Player* player)
minDist = (pGraveyard->x - x) * (pGraveyard->x - x) + (pGraveyard->y - y) * (pGraveyard->y - y);
for (uint8 i = BG_AV_NODES_FIRSTAID_STATION; i <= BG_AV_NODES_FROSTWOLF_HUT; ++i)
if (m_Nodes[i].OwnerId == player->GetTeamId() && m_Nodes[i].State == POINT_CONTROLED)
if (m_Nodes[i].OwnerId == player->GetTeamId() && m_Nodes[i].State == POINT_CONTROLLED)
{
entry = sGraveyard->GetGraveyard(BG_AV_GraveyardIds[i]);
if (entry)
@@ -1211,6 +1240,27 @@ GraveyardStruct const* BattlegroundAV::GetClosestGraveyard(Player* player)
bool BattlegroundAV::SetupBattleground()
{
if (sBattlegroundMgr->IsBGWeekend(GetBgTypeID(true)))
{
_reputationTower = 18;
_reputationCaptain = 185;
_reputationBoss = 525;
_reputationPerOwnedGraveyard = 18;
_reputationSurvivingCaptain = 175;
_reputationSurvivingTower = 18;
_reputationPerOwnedMine = 36;
}
else
{
_reputationTower = 12;
_reputationCaptain = 125;
_reputationBoss = sWorld->getIntConfig(CONFIG_BATTLEGROUND_ALTERAC_REP_ONBOSSDEATH);
_reputationPerOwnedGraveyard = 12;
_reputationSurvivingCaptain = 125;
_reputationSurvivingTower = 12;
_reputationPerOwnedMine = 24;
}
// Create starting objects
//spawn node-objects
@@ -1735,7 +1785,7 @@ void BattlegroundAV::DestroyNode(BG_AV_Nodes node)
m_Nodes[node].TotalOwnerId = m_Nodes[node].OwnerId;
m_Nodes[node].PrevOwnerId = m_Nodes[node].OwnerId;
m_Nodes[node].PrevState = m_Nodes[node].State;
m_Nodes[node].State = (m_Nodes[node].Tower) ? POINT_DESTROYED : POINT_CONTROLED;
m_Nodes[node].State = (m_Nodes[node].Tower) ? POINT_DESTROYED : POINT_CONTROLLED;
m_Nodes[node].Timer = 0;
}
@@ -1744,7 +1794,7 @@ void BattlegroundAV::InitNode(BG_AV_Nodes node, TeamId teamId, bool tower)
m_Nodes[node].TotalOwnerId = teamId;
m_Nodes[node].OwnerId = teamId;
m_Nodes[node].PrevOwnerId = TEAM_NEUTRAL;
m_Nodes[node].State = POINT_CONTROLED;
m_Nodes[node].State = POINT_CONTROLLED;
m_Nodes[node].PrevState = m_Nodes[node].State;
m_Nodes[node].Timer = 0;
m_Nodes[node].Tower = tower;
@@ -1754,11 +1804,11 @@ void BattlegroundAV::DefendNode(BG_AV_Nodes node, TeamId teamId)
{
ASSERT(m_Nodes[node].TotalOwnerId == teamId);
ASSERT(m_Nodes[node].OwnerId != teamId);
ASSERT(m_Nodes[node].State != POINT_CONTROLED && m_Nodes[node].State != POINT_DESTROYED);
ASSERT(m_Nodes[node].State != POINT_CONTROLLED && m_Nodes[node].State != POINT_DESTROYED);
m_Nodes[node].PrevOwnerId = m_Nodes[node].OwnerId;
m_Nodes[node].OwnerId = teamId;
m_Nodes[node].PrevState = m_Nodes[node].State;
m_Nodes[node].State = POINT_CONTROLED;
m_Nodes[node].State = POINT_CONTROLLED;
m_Nodes[node].Timer = 0;
}
@@ -1805,7 +1855,7 @@ bool BattlegroundAV::IsAllTowersControlledAndCaptainAlive(TeamId teamId) const
{
for (BG_AV_Nodes i = BG_AV_NODES_DUNBALDAR_SOUTH; i <= BG_AV_NODES_STONEHEART_BUNKER; ++i) // alliance towers controlled
{
if (m_Nodes[i].State == POINT_CONTROLED)
if (m_Nodes[i].State == POINT_CONTROLLED)
{
if (m_Nodes[i].OwnerId != TEAM_ALLIANCE)
return false;
@@ -1824,7 +1874,7 @@ bool BattlegroundAV::IsAllTowersControlledAndCaptainAlive(TeamId teamId) const
{
for (BG_AV_Nodes i = BG_AV_NODES_ICEBLOOD_TOWER; i <= BG_AV_NODES_FROSTWOLF_WTOWER; ++i) // horde towers controlled
{
if (m_Nodes[i].State == POINT_CONTROLED)
if (m_Nodes[i].State == POINT_CONTROLLED)
{
if (m_Nodes[i].OwnerId != TEAM_HORDE)
return false;
@@ -29,23 +29,18 @@
#define SEND_MSG_NEAR_LOSE 120
#define BG_AV_KILL_BOSS 4
#define BG_AV_REP_BOSS (sWorld->getIntConfig(CONFIG_BATTLEGROUND_ALTERAC_REP_ONBOSSDEATH)) // Blizzlike default is 350
#define BG_AV_KILL_CAPTAIN 3
#define BG_AV_REP_CAPTAIN 125
#define BG_AV_RES_CAPTAIN 100
#define BG_AV_KILL_TOWER 3
#define BG_AV_REP_TOWER 12
#define BG_AV_RES_TOWER 75
#define BG_AV_GET_COMMANDER 1 //for a safely returned wingcommander
//bonushonor at the end
#define BG_AV_KILL_SURVIVING_TOWER 2
#define BG_AV_REP_SURVIVING_TOWER 12
#define BG_AV_KILL_SURVIVING_CAPTAIN 2
#define BG_AV_REP_SURVIVING_CAPTAIN 125
#define AV_EVENT_START_BATTLE 9166 // Achievement: The Alterac Blitz
@@ -1546,7 +1541,7 @@ enum BG_AV_States
POINT_NEUTRAL = 0,
POINT_ASSAULTED = 1,
POINT_DESTROYED = 2,
POINT_CONTROLED = 3
POINT_CONTROLLED = 3
};
//alliance_control neutral_control horde_control
@@ -1848,6 +1843,14 @@ private:
uint32 m_CaptainBuffTimer[2] {};
bool m_CaptainAlive[2] {};
uint32 _reputationTower = 0; // 12, 18
uint32 _reputationCaptain = 0; // 125, 185
uint32 _reputationBoss = 0; // 350, 525
uint32 _reputationPerOwnedGraveyard = 0; // 12, 18
uint32 _reputationSurvivingCaptain = 0; // 125, 175
uint32 _reputationSurvivingTower = 0; // 12, 18
uint32 _reputationPerOwnedMine = 0; // 24, 36
bool m_IsInformedNearVictory[2] {};
};
@@ -263,6 +263,8 @@ void BattlegroundEY::HandleAreaTrigger(Player* player, uint32 trigger)
bool BattlegroundEY::SetupBattleground()
{
_honorTics = BattlegroundMgr::IsBGWeekend(GetBgTypeID(true)) ? BG_EY_HONOR_TICK_WEEKEND : BG_EY_HONOR_TICK_NORMAL;
// doors
AddObject(BG_EY_OBJECT_DOOR_A, BG_OBJECT_A_DOOR_EY_ENTRY, 2527.6f, 1596.91f, 1262.13f, -3.12414f, -0.173642f, -0.001515f, 0.98477f, -0.008594f, RESPAWN_IMMEDIATELY);
AddObject(BG_EY_OBJECT_DOOR_H, BG_OBJECT_H_DOOR_EY_ENTRY, 1803.21f, 1539.49f, 1261.09f, 3.14159f, 0.173648f, 0, 0.984808f, 0, RESPAWN_IMMEDIATELY);
@@ -355,7 +357,6 @@ void BattlegroundEY::Init()
Battleground::Init();
_bgEvents.Reset();
_honorTics = BattlegroundMgr::IsBGWeekend(GetBgTypeID(true)) ? BG_EY_HONOR_TICK_WEEKEND : BG_EY_HONOR_TICK_NORMAL;
_ownedPointsCount[TEAM_ALLIANCE] = 0;
_ownedPointsCount[TEAM_HORDE] = 0;
_flagKeeperGUID.Clear();
@@ -426,6 +426,19 @@ void BattlegroundWS::HandleAreaTrigger(Player* player, uint32 trigger)
bool BattlegroundWS::SetupBattleground()
{
if (sBattlegroundMgr->IsBGWeekend(GetBgTypeID(true)))
{
_reputationCapture = 45;
_honorWinKills = 3;
_honorEndKills = 4;
}
else
{
_reputationCapture = 35;
_honorWinKills = 1;
_honorEndKills = 2;
}
// flags
AddObject(BG_WS_OBJECT_A_FLAG, BG_OBJECT_A_FLAG_WS_ENTRY, 1540.423f, 1481.325f, 351.8284f, 3.089233f, 0, 0, 0.9996573f, 0.02617699f, RESPAWN_IMMEDIATELY);
AddObject(BG_WS_OBJECT_H_FLAG, BG_OBJECT_H_FLAG_WS_ENTRY, 916.0226f, 1434.405f, 345.413f, 0.01745329f, 0, 0, 0.008726535f, 0.9999619f, RESPAWN_IMMEDIATELY);
@@ -486,19 +499,6 @@ void BattlegroundWS::Init()
_flagState[TEAM_HORDE] = BG_WS_FLAG_STATE_ON_BASE;
_lastFlagCaptureTeam = TEAM_NEUTRAL;
if (sBattlegroundMgr->IsBGWeekend(GetBgTypeID(true)))
{
_reputationCapture = 45;
_honorWinKills = 3;
_honorEndKills = 4;
}
else
{
_reputationCapture = 35;
_honorWinKills = 1;
_honorEndKills = 2;
}
uint32 bgWarsongFlagsConfig = sWorld->getIntConfig(CONFIG_BATTLEGROUND_WARSONG_FLAGS);
_configurableMaxTeamScore = bgWarsongFlagsConfig > 0
? bgWarsongFlagsConfig
+1 -1
View File
@@ -573,7 +573,7 @@ bool Condition::Meets(ConditionSourceInfo& sourceInfo)
}
case CONDITION_WORLD_SCRIPT:
{
condMeets = sWorldState->IsConditionFulfilled(static_cast<WorldStateCondition>(ConditionValue1), static_cast<WorldStateConditionState>(ConditionValue2));
condMeets = sWorldState->IsConditionFulfilled(ConditionValue1, ConditionValue2);
break;
}
default:
+8 -19
View File
@@ -1496,10 +1496,6 @@ namespace lfg
{
uint32 dungeonId = (it2->first & 0x00FFFFFF); // Compare dungeon ids
// Skip faction-specific locks if cross-faction is enabled
if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP) && (it2->second == LFG_LOCKSTATUS_QUEST_NOT_COMPLETED || it2->second == LFG_LOCKSTATUS_MISSING_ITEM))
continue;
LfgDungeonSet::iterator itDungeon = dungeons.find(dungeonId);
if (itDungeon != dungeons.end())
{
@@ -2687,24 +2683,17 @@ namespace lfg
LFGQueue& LFGMgr::GetQueue(ObjectGuid guid)
{
uint8 queueId = 0;
if (sWorld->getBoolConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP))
if (guid.IsGroup())
{
queueId = TEAM_ALLIANCE;
LfgGuidSet const& players = GetPlayers(guid);
ObjectGuid pguid = players.empty() ? ObjectGuid::Empty : (*players.begin());
if (pguid)
queueId = GetTeam(pguid);
else
queueId = GetTeam(GetLeader(guid));
}
else
{
if (guid.IsGroup())
{
LfgGuidSet const& players = GetPlayers(guid);
ObjectGuid pguid = players.empty() ? ObjectGuid::Empty : (*players.begin());
if (pguid)
queueId = GetTeam(pguid);
else
queueId = GetTeam(GetLeader(guid));
}
else
queueId = GetTeam(guid);
}
queueId = GetTeam(guid);
return QueuesStore[queueId];
}
+24 -4
View File
@@ -2773,11 +2773,7 @@ bool Creature::LoadCreaturesAddon(bool reload)
//Load Path
if (cainfo->path_id != 0)
{
if (sWorld->getBoolConfig(CONFIG_SET_ALL_CREATURES_WITH_WAYPOINT_MOVEMENT_ACTIVE))
setActive(true);
m_path_id = cainfo->path_id;
}
if (!cainfo->auras.empty())
{
@@ -3897,3 +3893,27 @@ std::string Creature::GetDebugInfo() const
<< " WaypointPath: " << GetWaypointPath() << " SpawnId: " << GetSpawnId();
return sstr.str();
}
// Note: This is called in a tight (heavy) loop, is it critical that all checks are FAST and are hopefully only simple conditionals.
bool Creature::IsUpdateNeeded()
{
if (WorldObject::IsUpdateNeeded())
return true;
if (GetMap()->isCellMarked(GetCurrentCell().GetCellCoord().GetId()))
return true;
if (IsInCombat())
return true;
if (IsVisibilityOverridden())
return true;
if (GetMotionMaster()->HasMovementGeneratorType(WAYPOINT_MOTION_TYPE))
return true;
if (HasUnitState(UNIT_STATE_EVADE))
return true;
return false;
}
+3 -1
View File
@@ -39,7 +39,7 @@ class CreatureGroup;
#define MAX_VENDOR_ITEMS 150 // Limitation in 3.x.x item count in SMSG_LIST_INVENTORY
class Creature : public Unit, public GridObject<Creature>, public MovableMapObject
class Creature : public Unit, public GridObject<Creature>, public MovableMapObject, public UpdatableMapObject
{
public:
explicit Creature(bool isWorldObject = false);
@@ -436,6 +436,8 @@ public:
std::string GetDebugInfo() const override;
bool IsUpdateNeeded() override;
protected:
bool CreateFromProto(ObjectGuid::LowType guidlow, uint32 Entry, uint32 vehId, const CreatureData* data = nullptr);
bool InitEntry(uint32 entry, const CreatureData* data = nullptr);
@@ -31,7 +31,7 @@ enum DynamicObjectType
DYNAMIC_OBJECT_FARSIGHT_FOCUS = 0x2,
};
class DynamicObject : public WorldObject, public GridObject<DynamicObject>, public MovableMapObject
class DynamicObject : public WorldObject, public GridObject<DynamicObject>, public MovableMapObject, public UpdatableMapObject
{
public:
DynamicObject(bool isWorldObject);
@@ -3076,3 +3076,21 @@ std::string GameObject::GetDebugInfo() const
<< "SpawnId: " << GetSpawnId() << " GoState: " << std::to_string(GetGoState()) << " ScriptId: " << GetScriptId() << " AIName: " << GetAIName();
return sstr.str();
}
// Note: This is called in a tight (heavy) loop, is it critical that all checks are FAST and are hopefully only simple conditionals.
bool GameObject::IsUpdateNeeded()
{
if (WorldObject::IsUpdateNeeded())
return true;
if (GetMap()->isCellMarked(GetCurrentCell().GetCellCoord().GetId()))
return true;
if (IsVisibilityOverridden())
return true;
if (IsTransport())
return true;
return false;
}
@@ -116,7 +116,7 @@ enum LootState
// 5 sec for bobber catch
#define FISHING_BOBBER_READY_TIME 5
class GameObject : public WorldObject, public GridObject<GameObject>, public MovableMapObject
class GameObject : public WorldObject, public GridObject<GameObject>, public MovableMapObject, public UpdatableMapObject
{
public:
explicit GameObject();
@@ -362,6 +362,8 @@ public:
void SaveStateToDB();
std::string GetDebugInfo() const override;
bool IsUpdateNeeded() override;
protected:
bool AIM_Initialize();
GameObjectModel* CreateModel();
@@ -1188,6 +1188,7 @@ void WorldObject::AddToWorld()
{
Object::AddToWorld();
GetMap()->GetZoneAndAreaId(GetPhaseMask(), _zoneId, _areaId, GetPositionX(), GetPositionY(), GetPositionZ());
GetMap()->AddObjectToPendingUpdateList(this);
}
void WorldObject::RemoveFromWorld()
@@ -2510,6 +2511,13 @@ void WorldObject::GetGameObjectListWithEntryInGrid(std::list<GameObject*>& gameo
Cell::VisitGridObjects(this, searcher, maxSearchRange);
}
void WorldObject::GetGameObjectListWithEntryInGrid(std::list<GameObject*>& gameobjectList, std::vector<uint32> const& entries, float maxSearchRange) const
{
Acore::AllGameObjectsMatchingOneEntryInRange check(this, entries, maxSearchRange);
Acore::GameObjectListSearcher searcher(this, gameobjectList, check);
Cell::VisitGridObjects(this, searcher, maxSearchRange);
}
void WorldObject::GetCreatureListWithEntryInGrid(std::list<Creature*>& creatureList, uint32 entry, float maxSearchRange) const
{
Acore::AllCreaturesOfEntryInRange check(this, entry, maxSearchRange);
@@ -2517,6 +2525,13 @@ void WorldObject::GetCreatureListWithEntryInGrid(std::list<Creature*>& creatureL
Cell::VisitGridObjects(this, searcher, maxSearchRange);
}
void WorldObject::GetCreatureListWithEntryInGrid(std::list<Creature*>& creatureList, std::vector<uint32> const& entries, float maxSearchRange) const
{
Acore::AllCreaturesMatchingOneEntryInRange check(this, entries, maxSearchRange);
Acore::CreatureListSearcher searcher(this, creatureList, check);
Cell::VisitGridObjects(this, searcher, maxSearchRange);
}
void WorldObject::GetDeadCreatureListInGrid(std::list<Creature*>& creaturedeadList, float maxSearchRange, bool alive /*= false*/) const
{
Acore::AllDeadCreaturesInRange check(this, maxSearchRange, alive);
@@ -3220,3 +3235,27 @@ void WorldObject::RemoveAllowedLooter(ObjectGuid guid)
{
_allowedLooters.erase(guid);
}
bool WorldObject::IsUpdateNeeded()
{
if (isActiveObject())
return true;
return false;
}
bool WorldObject::CanBeAddedToMapUpdateList()
{
switch (GetTypeId())
{
case TYPEID_UNIT:
return IsCreature();
case TYPEID_DYNAMICOBJECT:
case TYPEID_GAMEOBJECT:
return true;
default:
return false;
}
return false;
}
+56 -1
View File
@@ -230,6 +230,12 @@ public:
DataMap CustomData;
template<typename... T>
[[nodiscard]] bool EntryEquals(T... entries) const
{
return ((GetEntry() == entries) || ...);
}
protected:
Object();
@@ -399,14 +405,58 @@ class MovableMapObject
protected:
MovableMapObject() = default;
private:
[[nodiscard]] Cell const& GetCurrentCell() const { return _currentCell; }
private:
void SetCurrentCell(Cell const& cell) { _currentCell = cell; }
Cell _currentCell;
MapObjectCellMoveState _moveState{MAP_OBJECT_CELL_MOVE_NONE};
};
class UpdatableMapObject
{
friend class Map;
public:
enum UpdateState : uint8
{
NotUpdating,
PendingAdd,
Updating
};
protected:
UpdatableMapObject() : _mapUpdateListOffset(0), _mapUpdateState(NotUpdating) { }
private:
void SetMapUpdateListOffset(std::size_t const offset)
{
ASSERT(_mapUpdateState == Updating, "Attempted to set update list offset when object is not in map update list");
_mapUpdateListOffset = offset;
}
size_t GetMapUpdateListOffset() const
{
ASSERT(_mapUpdateState == Updating, "Attempted to get update list offset when object is not in map update list");
return _mapUpdateListOffset;
}
void SetUpdateState(UpdateState state)
{
_mapUpdateState = state;
}
UpdateState GetUpdateState() const
{
return _mapUpdateState;
}
private:
std::size_t _mapUpdateListOffset;
UpdateState _mapUpdateState;
};
class WorldObject : public Object, public WorldLocation
{
protected:
@@ -554,7 +604,9 @@ public:
[[nodiscard]] Player* SelectNearestPlayer(float distance = 0) const;
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& lList, uint32 uiEntry, float fMaxSearchRange) const;
void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& gameobjectList, std::vector<uint32> const& entries, float maxSearchRange) const;
void GetCreatureListWithEntryInGrid(std::list<Creature*>& lList, uint32 uiEntry, float fMaxSearchRange) const;
void GetCreatureListWithEntryInGrid(std::list<Creature*>& creatureList, std::vector<uint32> const& entries, float maxSearchRange) const;
void GetDeadCreatureListInGrid(std::list<Creature*>& lList, float maxSearchRange, bool alive = false) const;
void DestroyForNearbyPlayers();
@@ -633,6 +685,9 @@ public:
[[nodiscard]] GuidUnorderedSet const& GetAllowedLooters() const;
void RemoveAllowedLooter(ObjectGuid guid);
virtual bool IsUpdateNeeded();
bool CanBeAddedToMapUpdateList();
std::string GetDebugInfo() const override;
// Event handler
@@ -1684,6 +1684,8 @@ template <class T>
void Player::UpdateVisibilityOf(T* target, UpdateData& data,
std::vector<Unit*>& visibleNow)
{
GetMap()->AddObjectToPendingUpdateList(target);
if (HaveAtClient(target))
{
if (!CanSeeOrDetect(target, false, true))
+12 -4
View File
@@ -5910,9 +5910,17 @@ uint32 Unit::GetDiseasesByCaster(ObjectGuid casterGUID, uint8 mode)
else if (mode == 2)
{
Aura* aura = (*i)->GetBase();
if (aura && !aura->IsRemoved() && aura->GetDuration() > 0)
if ((aura->GetApplyTime() + aura->GetMaxDuration() / 1000 + 8) > (GameTime::GetGameTime().count() + aura->GetDuration() / 1000))
aura->SetDuration(aura->GetDuration() + 3000);
uint32 countMin = aura->GetMaxDuration();
uint32 countMax = aura->GetSpellInfo()->GetMaxDuration() + 9000;
if (AuraEffect const* epidemic = (*i)->GetCaster()->GetAuraEffectOfRankedSpell(49036, EFFECT_0))
countMax += epidemic->GetAmount();
if (countMin < countMax)
{
aura->SetDuration(uint32(aura->GetDuration() + 3000));
aura->SetMaxDuration(countMin + 3000);
}
}
}
++i;
@@ -13739,7 +13747,7 @@ void Unit::CombatStart(Unit* victim, bool initialAggro)
victim->SetInCombatWith(this);
// Update leash timer when attacking creatures
if (victim->IsCreature())
if (victim->IsCreature() && this != victim)
victim->ToCreature()->UpdateLeashExtensionTime();
// Xinef: If pet started combat - put owner in combat
@@ -116,6 +116,9 @@ void GridObjectUnloader::Visit(GridRefMgr<T>& m)
//Example: Flame Leviathan Turret 33139 is summoned when a creature is deleted
//TODO: Check if that script has the correct logic. Do we really need to summons something before deleting?
obj->CleanupsBeforeDelete();
obj->GetMap()->RemoveObjectFromMapUpdateList(obj);
///- object will get delinked from the manager when deleted
delete obj;
}
@@ -354,19 +354,6 @@ void MessageDistDelivererToHostile::Visit(DynamicObjectMapType& m)
}
}
template<class T>
void ObjectUpdater::Visit(GridRefMgr<T>& m)
{
T* obj;
for (typename GridRefMgr<T>::iterator iter = m.begin(); iter != m.end(); )
{
obj = iter->GetSource();
++iter;
if (obj->IsInWorld() && (i_largeOnly == obj->IsVisibilityOverridden()))
obj->Update(i_timeDiff);
}
}
bool AnyDeadUnitObjectInRangeCheck::operator()(Player* u)
{
return !u->IsAlive() && !u->HasGhostAura() && i_searchObj->IsWithinDistInMap(u, i_range);
@@ -396,7 +383,3 @@ bool AnyDeadUnitSpellTargetInRangeCheck::operator()(Creature* u)
{
return AnyDeadUnitObjectInRangeCheck::operator()(u) && i_check(u);
}
template void ObjectUpdater::Visit<Creature>(CreatureMapType&);
template void ObjectUpdater::Visit<GameObject>(GameObjectMapType&);
template void ObjectUpdater::Visit<DynamicObject>(DynamicObjectMapType&);
+36 -10
View File
@@ -154,16 +154,6 @@ namespace Acore
}
};
struct ObjectUpdater
{
uint32 i_timeDiff;
bool i_largeOnly;
explicit ObjectUpdater(const uint32 diff, bool largeOnly) : i_timeDiff(diff), i_largeOnly(largeOnly) {}
template<class T> void Visit(GridRefMgr<T>& m);
void Visit(PlayerMapType&) {}
void Visit(CorpseMapType&) {}
};
// SEARCHERS & LIST SEARCHERS & WORKERS
// WorldObject searchers & workers
@@ -1457,6 +1447,24 @@ namespace Acore
float m_fRange;
};
class AllGameObjectsMatchingOneEntryInRange
{
public:
AllGameObjectsMatchingOneEntryInRange(WorldObject const* object, std::vector<uint32> entries, float maxRange) : m_pObject(object), m_uiEntries(entries), m_fRange(maxRange) {}
bool operator()(GameObject *go)
{
if (std::ranges::any_of( m_uiEntries, [go](uint32 entry) { return go->GetEntry() == entry; }) && m_pObject->IsWithinDist(go, m_fRange, false))
return true;
return false;
}
private:
WorldObject const* m_pObject;
std::vector<uint32> m_uiEntries;
float m_fRange;
};
class AllCreaturesOfEntryInRange
{
public:
@@ -1475,6 +1483,24 @@ namespace Acore
float m_fRange;
};
class AllCreaturesMatchingOneEntryInRange
{
public:
AllCreaturesMatchingOneEntryInRange(WorldObject const* object, std::vector<uint32> entries, float maxRange) : m_pObject(object), m_uiEntries(entries), m_fRange(maxRange) {}
bool operator()(Unit* unit)
{
if (std::ranges::any_of(m_uiEntries, [unit](uint32 entry) { return unit->GetEntry() == entry; }) && m_pObject->IsWithinDist(unit, m_fRange, false))
return true;
return false;
}
private:
WorldObject const* m_pObject;
std::vector<uint32> m_uiEntries;
float m_fRange;
};
class MostHPMissingGroupInRange
{
public:
+1 -1
View File
@@ -51,7 +51,7 @@ void GroupMgr::InitGroupIds()
}
}
void GroupMgr::RegisterGroupId(uint32 groupId)
void GroupMgr::RegisterGroupId(ObjectGuid::LowType groupId)
{
// Allocation was done in InitGroupIds()
_groupIds[groupId] = true;
+1 -1
View File
@@ -390,7 +390,7 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData)
bool senderIsPlayer = AccountMgr::IsPlayerAccount(GetSecurity());
bool receiverIsPlayer = AccountMgr::IsPlayerAccount(receiver ? receiver->GetSession()->GetSecurity() : SEC_PLAYER);
if (sender->GetLevel() < sWorld->getIntConfig(CONFIG_CHAT_WHISPER_LEVEL_REQ) && receiver != sender)
if (sender->GetLevel() < sWorld->getIntConfig(CONFIG_CHAT_WHISPER_LEVEL_REQ) && receiver != sender && receiver && !receiver->IsGameMaster())
{
ChatHandler(this).SendNotification(LANG_WHISPER_REQ, sWorld->getIntConfig(CONFIG_CHAT_WHISPER_LEVEL_REQ));
return;
+1
View File
@@ -31,6 +31,7 @@ enum AreaTableIDs : uint32
AREA_ELWYNN_FOREST = 12,
AREA_DUROTAR = 14,
AREA_DUSTWALLOW_MARSH = 15,
AREA_AZSHARA = 16,
AREA_THE_BARRENS = 17,
AREA_WESTERN_PLAGUELANDS = 28,
AREA_STRANGLETHORN_VALE = 33,
+125 -122
View File
@@ -80,6 +80,7 @@ Map::Map(uint32 id, uint32 InstanceId, uint8 SpawnMode, Map* _parent) :
m_parentMap = (_parent ? _parent : this);
_zonePlayerCountMap.clear();
_updatableObjectListRecheckTimer.SetInterval(UPDATABLE_OBJECT_LIST_RECHECK_TIMER);
//lets initialize visibility distance for map
Map::InitVisibilityDistance();
@@ -492,45 +493,7 @@ bool Map::AddToMap(MotionTransport* obj, bool /*checkTransport*/)
return true;
}
void Map::VisitNearbyCellsOfPlayer(Player* player, TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& gridVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& worldVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& largeGridVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& largeWorldVisitor)
{
// check for valid position
if (!player->IsPositionValid())
return;
// check normal grid activation range of the player
VisitNearbyCellsOf(player, gridVisitor, worldVisitor, largeGridVisitor, largeWorldVisitor);
// check maximum visibility distance for large creatures
CellArea area = Cell::CalculateCellArea(player->GetPositionX(), player->GetPositionY(), MAX_VISIBILITY_DISTANCE);
for (uint32 x = area.low_bound.x_coord; x <= area.high_bound.x_coord; ++x)
{
for (uint32 y = area.low_bound.y_coord; y <= area.high_bound.y_coord; ++y)
{
// marked cells are those that have been visited
// don't visit the same cell twice
uint32 cell_id = (y * TOTAL_NUMBER_OF_CELLS_PER_MAP) + x;
if (isCellMarkedLarge(cell_id))
continue;
markCellLarge(cell_id);
CellCoord pair(x, y);
Cell cell(pair);
Visit(cell, largeGridVisitor);
Visit(cell, largeWorldVisitor);
}
}
}
void Map::VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& gridVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& worldVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& largeGridVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& largeWorldVisitor)
void Map::MarkNearbyCellsOf(WorldObject* obj)
{
// Check for valid position
if (!obj->IsPositionValid())
@@ -538,30 +501,13 @@ void Map::VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitor<Acore::Objec
// Update mobs/objects in ALL visible cells around object!
CellArea area = Cell::CalculateCellArea(obj->GetPositionX(), obj->GetPositionY(), obj->GetGridActivationRange());
for (uint32 x = area.low_bound.x_coord; x <= area.high_bound.x_coord; ++x)
{
for (uint32 y = area.low_bound.y_coord; y <= area.high_bound.y_coord; ++y)
{
// marked cells are those that have been visited
// don't visit the same cell twice
uint32 cell_id = (y * TOTAL_NUMBER_OF_CELLS_PER_MAP) + x;
if (isCellMarked(cell_id))
continue;
markCell(cell_id);
CellCoord pair(x, y);
Cell cell(pair);
Visit(cell, gridVisitor);
Visit(cell, worldVisitor);
if (!isCellMarkedLarge(cell_id))
{
markCellLarge(cell_id);
Visit(cell, largeGridVisitor);
Visit(cell, largeWorldVisitor);
}
}
}
}
@@ -613,39 +559,10 @@ void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
return;
}
/// update active cells around players and active objects
_updatableObjectListRecheckTimer.Update(t_diff);
resetMarkedCells();
resetMarkedCellsLarge();
// Prepare object updaters
Acore::ObjectUpdater updater(t_diff, false);
// For creature
TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer> grid_object_update(updater);
// For pets
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer> world_object_update(updater);
// For large creatures
Acore::ObjectUpdater largeObjectUpdater(t_diff, true);
TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer> grid_large_object_update(largeObjectUpdater);
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer> world_large_object_update(largeObjectUpdater);
// pussywizard: container for far creatures in combat with players
std::vector<Creature*> updateList;
updateList.reserve(10);
// Update non-player active objects
for (m_activeNonPlayersIter = m_activeNonPlayers.begin(); m_activeNonPlayersIter != m_activeNonPlayers.end();)
{
WorldObject* obj = *m_activeNonPlayersIter;
++m_activeNonPlayersIter;
if (obj && obj->IsInWorld())
VisitNearbyCellsOf(obj, grid_object_update, world_object_update, grid_large_object_update, world_large_object_update);
}
// Update players and their associated objects
// Update players
for (m_mapRefIter = m_mapRefMgr.begin(); m_mapRefIter != m_mapRefMgr.end(); ++m_mapRefIter)
{
Player* player = m_mapRefIter->GetSource();
@@ -654,53 +571,37 @@ void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
continue;
player->Update(s_diff);
VisitNearbyCellsOfPlayer(player, grid_object_update, world_object_update, grid_large_object_update, world_large_object_update);
// If player is using far sight, update viewpoint
if (WorldObject* viewPoint = player->GetViewpoint())
if (_updatableObjectListRecheckTimer.Passed())
{
if (Creature* viewCreature = viewPoint->ToCreature())
{
VisitNearbyCellsOf(viewCreature, grid_object_update, world_object_update, grid_large_object_update, world_large_object_update);
}
else if (DynamicObject* viewObject = viewPoint->ToDynObject())
{
VisitNearbyCellsOf(viewObject, grid_object_update, world_object_update, grid_large_object_update, world_large_object_update);
}
}
MarkNearbyCellsOf(player);
// handle updates for creatures in combat with player and are more than X yards away
if (player->IsInCombat())
{
updateList.clear();
float rangeSq = player->GetGridActivationRange() - 1.0f;
rangeSq *= rangeSq;
HostileReference* ref = player->getHostileRefMgr().getFirst();
while (ref)
// If player is using far sight, update viewpoint
if (WorldObject* viewPoint = player->GetViewpoint())
{
if (Unit* unit = ref->GetSource()->GetOwner())
if (Creature* cre = unit->ToCreature())
if (cre->FindMap() == player->FindMap() && cre->GetExactDist2dSq(player) > rangeSq)
updateList.push_back(cre);
ref = ref->next();
if (Creature* viewCreature = viewPoint->ToCreature())
MarkNearbyCellsOf(viewCreature);
else if (DynamicObject* viewObject = viewPoint->ToDynObject())
MarkNearbyCellsOf(viewObject);
}
for (Creature* cre : updateList)
VisitNearbyCellsOf(cre, grid_object_update, world_object_update, grid_large_object_update, world_large_object_update);
}
}
// Update transports - pussywizard: transports updated after VisitNearbyCellsOf, grids around are loaded, everything ok
for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();)
if (_updatableObjectListRecheckTimer.Passed())
{
MotionTransport* transport = *_transportsUpdateIter;
++_transportsUpdateIter;
// Mark all cells near active objects
for (m_activeNonPlayersIter = m_activeNonPlayers.begin(); m_activeNonPlayersIter != m_activeNonPlayers.end(); ++m_activeNonPlayersIter)
{
WorldObject* obj = *m_activeNonPlayersIter;
if (!obj || !obj->IsInWorld())
continue;
if (transport->IsInWorld())
transport->Update(t_diff);
MarkNearbyCellsOf(obj);
}
}
UpdateNonPlayerObjects(t_diff);
SendObjectUpdates();
///- Process necessary scripts
@@ -728,6 +629,101 @@ void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
METRIC_TAG("map_instanceid", std::to_string(GetInstanceId())));
}
void Map::UpdateNonPlayerObjects(uint32 const diff)
{
for (WorldObject* obj : _pendingAddUpdatableObjectList)
_AddObjectToUpdateList(obj);
_pendingAddUpdatableObjectList.clear();
if (_updatableObjectListRecheckTimer.Passed())
{
for (uint32 i = 0; i < _updatableObjectList.size();)
{
WorldObject* obj = _updatableObjectList[i];
if (!obj->IsInWorld())
{
++i;
continue;
}
obj->Update(diff);
if (!obj->IsUpdateNeeded())
{
_RemoveObjectFromUpdateList(obj);
// Intentional no iteration here, obj is swapped with last element in
// _updatableObjectList so next loop will update that object at the same index
}
else
++i;
}
_updatableObjectListRecheckTimer.Reset();
}
else
{
for (uint32 i = 0; i < _updatableObjectList.size(); ++i)
{
WorldObject* obj = _updatableObjectList[i];
if (!obj->IsInWorld())
continue;
obj->Update(diff);
}
}
}
void Map::AddObjectToPendingUpdateList(WorldObject* obj)
{
if (!obj->CanBeAddedToMapUpdateList())
return;
UpdatableMapObject* mapUpdatableObject = dynamic_cast<UpdatableMapObject*>(obj);
if (mapUpdatableObject->GetUpdateState() != UpdatableMapObject::UpdateState::NotUpdating)
return;
_pendingAddUpdatableObjectList.insert(obj);
mapUpdatableObject->SetUpdateState(UpdatableMapObject::UpdateState::PendingAdd);
}
// Internal use only
void Map::_AddObjectToUpdateList(WorldObject* obj)
{
UpdatableMapObject* mapUpdatableObject = dynamic_cast<UpdatableMapObject*>(obj);
ASSERT(mapUpdatableObject && mapUpdatableObject->GetUpdateState() == UpdatableMapObject::UpdateState::PendingAdd);
mapUpdatableObject->SetUpdateState(UpdatableMapObject::UpdateState::Updating);
mapUpdatableObject->SetMapUpdateListOffset(_updatableObjectList.size());
_updatableObjectList.push_back(obj);
}
// Internal use only
void Map::_RemoveObjectFromUpdateList(WorldObject* obj)
{
UpdatableMapObject* mapUpdatableObject = dynamic_cast<UpdatableMapObject*>(obj);
ASSERT(mapUpdatableObject && mapUpdatableObject->GetUpdateState() == UpdatableMapObject::UpdateState::Updating);
if (obj != _updatableObjectList.back())
{
dynamic_cast<UpdatableMapObject*>(_updatableObjectList.back())->SetMapUpdateListOffset(mapUpdatableObject->GetMapUpdateListOffset());
std::swap(_updatableObjectList[mapUpdatableObject->GetMapUpdateListOffset()], _updatableObjectList.back());
}
_updatableObjectList.pop_back();
mapUpdatableObject->SetUpdateState(UpdatableMapObject::UpdateState::NotUpdating);
}
void Map::RemoveObjectFromMapUpdateList(WorldObject* obj)
{
if (!obj->CanBeAddedToMapUpdateList())
return;
UpdatableMapObject* mapUpdatableObject = dynamic_cast<UpdatableMapObject*>(obj);
if (mapUpdatableObject->GetUpdateState() == UpdatableMapObject::UpdateState::PendingAdd)
_pendingAddUpdatableObjectList.erase(obj);
else if (mapUpdatableObject->GetUpdateState() == UpdatableMapObject::UpdateState::Updating)
_RemoveObjectFromUpdateList(obj);
}
void Map::HandleDelayedVisibility()
{
if (i_objectsForDelayedVisibility.empty())
@@ -795,7 +791,10 @@ void Map::RemoveFromMap(T* obj, bool remove)
obj->ResetMap();
if (remove)
{
RemoveObjectFromMapUpdateList(obj);
DeleteFromWorld(obj);
}
}
template<>
@@ -831,6 +830,10 @@ void Map::RemoveFromMap(MotionTransport* obj, bool remove)
obj->ResetMap();
// Transports are never actually deleted, but it *should* be safe to clear
// from update list when removing from world
RemoveObjectFromMapUpdateList(obj);
if (remove)
{
// if option set then object already saved at this moment
+18 -12
View File
@@ -34,6 +34,7 @@
#include "Position.h"
#include "SharedDefines.h"
#include "TaskScheduler.h"
#include "Timer.h"
#include "GridTerrainData.h"
#include <bitset>
#include <list>
@@ -85,6 +86,7 @@ struct ScriptAction
#define DEFAULT_HEIGHT_SEARCH 50.0f // default search distance to find height at nearby locations
#define MIN_UNLOAD_DELAY 1 // immediate unload
#define UPDATABLE_OBJECT_LIST_RECHECK_TIMER 30 * IN_MILLISECONDS // Time to recheck update object list
struct PositionFullTerrainStatus
{
@@ -181,14 +183,7 @@ public:
template<class T> bool AddToMap(T*, bool checkTransport = false);
template<class T> void RemoveFromMap(T*, bool);
void VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& gridVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& worldVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& largeGridVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& largeWorldVisitor);
void VisitNearbyCellsOfPlayer(Player* player, TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& gridVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& worldVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, GridTypeMapContainer>& largeGridVisitor,
TypeContainerVisitor<Acore::ObjectUpdater, WorldTypeMapContainer>& largeWorldVisitor);
void MarkNearbyCellsOf(WorldObject* obj);
virtual void Update(const uint32, const uint32, bool thread = true);
@@ -317,9 +312,6 @@ public:
void resetMarkedCells() { marked_cells.reset(); }
bool isCellMarked(uint32 pCellId) { return marked_cells.test(pCellId); }
void markCell(uint32 pCellId) { marked_cells.set(pCellId); }
void resetMarkedCellsLarge() { marked_cells_large.reset(); }
bool isCellMarkedLarge(uint32 pCellId) { return marked_cells_large.test(pCellId); }
void markCellLarge(uint32 pCellId) { marked_cells_large.set(pCellId); }
[[nodiscard]] bool HavePlayers() const { return !m_mapRefMgr.IsEmpty(); }
[[nodiscard]] uint32 GetPlayersCountExceptGMs() const;
@@ -512,6 +504,12 @@ public:
uint32 GetCreatedCellsInGridCount(uint16 const x, uint16 const y);
uint32 GetCreatedCellsInMapCount();
void AddObjectToPendingUpdateList(WorldObject* obj);
void RemoveObjectFromMapUpdateList(WorldObject* obj);
typedef std::vector<WorldObject*> UpdatableObjectList;
typedef std::unordered_set<WorldObject*> PendingAddUpdatableObjectList;
private:
template<class T> void InitializeObject(T* obj);
@@ -576,7 +574,6 @@ private:
Map* m_parentMap;
std::bitset<TOTAL_NUMBER_OF_CELLS_PER_MAP * TOTAL_NUMBER_OF_CELLS_PER_MAP> marked_cells;
std::bitset<TOTAL_NUMBER_OF_CELLS_PER_MAP * TOTAL_NUMBER_OF_CELLS_PER_MAP> marked_cells_large;
bool i_scriptLock;
std::unordered_set<WorldObject*> i_objectsToRemove;
@@ -610,6 +607,11 @@ private:
m_activeNonPlayers.erase(obj);
}
void UpdateNonPlayerObjects(uint32 const diff);
void _AddObjectToUpdateList(WorldObject* obj);
void _RemoveObjectFromUpdateList(WorldObject* obj);
std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _creatureRespawnTimes;
std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _goRespawnTimes;
@@ -637,6 +639,10 @@ private:
std::unordered_set<Corpse*> _corpseBones;
std::unordered_set<Object*> _updateObjects;
UpdatableObjectList _updatableObjectList;
PendingAddUpdatableObjectList _pendingAddUpdatableObjectList;
IntervalTimer _updatableObjectListRecheckTimer;
};
enum InstanceResetMethod
+14
View File
@@ -969,6 +969,20 @@ MovementGeneratorType MotionMaster::GetMotionSlotType(int slot) const
return Impl[slot]->GetMovementGeneratorType();
}
bool MotionMaster::HasMovementGeneratorType(MovementGeneratorType type) const
{
if (empty() && type == IDLE_MOTION_TYPE)
return true;
for (int i = _top; i >= 0; --i)
{
if (Impl[i] && Impl[i]->GetMovementGeneratorType() == type)
return true;
}
return false;
}
// Xinef: Escort system
uint32 MotionMaster::GetCurrentSplineId() const
{
+1
View File
@@ -243,6 +243,7 @@ public:
#endif
[[nodiscard]] MovementGeneratorType GetCurrentMovementGeneratorType() const;
[[nodiscard]] MovementGeneratorType GetMotionSlotType(int slot) const;
bool HasMovementGeneratorType(MovementGeneratorType type) const;
[[nodiscard]] uint32 GetCurrentSplineId() const; // Xinef: Escort system
void propagateSpeedChange();
@@ -137,10 +137,16 @@ void FleeingMovementGenerator<T>::SetTargetLocation(T* owner)
_path->Clear();
}
if (owner->IsPlayer())
_path->SetSlopeCheck(true);
_path->SetPathLengthLimit(30.0f);
bool result = _path->CalculatePath(destination.GetPositionX(), destination.GetPositionY(), destination.GetPositionZ());
if (!result || (_path->GetPathType() & PathType(PATHFIND_NOPATH | PATHFIND_SHORTCUT | PATHFIND_FARFROMPOLY)))
if (!result || (_path->GetPathType() & PathType(PATHFIND_NOPATH | PATHFIND_SHORTCUT | PATHFIND_FARFROMPOLY | PATHFIND_NOT_USING_PATH)))
{
if (_fleeTargetGUID)
++_invalidPathsCount;
_timer.Reset(100);
return;
}
@@ -149,15 +155,13 @@ void FleeingMovementGenerator<T>::SetTargetLocation(T* owner)
if (_path->getPathLength() < MIN_PATH_LENGTH)
{
if (_fleeTargetGUID)
{
++_shortPathsCount;
}
++_invalidPathsCount;
_timer.Reset(100);
return;
}
_shortPathsCount = 0;
_invalidPathsCount = 0;
Movement::MoveSplineInit init(owner);
init.MovebyPath(_path->GetPath());
@@ -172,7 +176,7 @@ void FleeingMovementGenerator<T>::GetPoint(T* owner, Position& position)
float casterDistance = 0.f;
float casterAngle = 0.f;
Unit* fleeTarget = nullptr;
if (_shortPathsCount < 5)
if (_invalidPathsCount < 5)
fleeTarget = ObjectAccessor::GetUnit(*owner, _fleeTargetGUID);
if (fleeTarget)
@@ -26,7 +26,7 @@ template<class T>
class FleeingMovementGenerator : public MovementGeneratorMedium< T, FleeingMovementGenerator<T> >
{
public:
explicit FleeingMovementGenerator(ObjectGuid fleeTargetGUID) : _path(nullptr), _fleeTargetGUID(fleeTargetGUID), _timer(0), _interrupt(false), _shortPathsCount(0) { }
explicit FleeingMovementGenerator(ObjectGuid fleeTargetGUID) : _path(nullptr), _fleeTargetGUID(fleeTargetGUID), _timer(0), _interrupt(false), _invalidPathsCount(0) { }
MovementGeneratorType GetMovementGeneratorType() override { return FLEEING_MOTION_TYPE; }
@@ -43,7 +43,7 @@ class FleeingMovementGenerator : public MovementGeneratorMedium< T, FleeingMovem
ObjectGuid _fleeTargetGUID;
TimeTracker _timer;
bool _interrupt;
uint8 _shortPathsCount;
uint8 _invalidPathsCount;
};
class TimedFleeingMovementGenerator : public FleeingMovementGenerator<Creature>
@@ -556,7 +556,7 @@ void PathGenerator::BuildPointPath(const float* startPoint, const float* endPoin
}
// Special case with start and end positions very close to each other
if (_polyLength == 1 && pointCount == 1)
if (_polyLength == 1 && pointCount == 1 && !(dtResult & DT_SLOPE_TOO_STEEP))
{
// First point is start position, append end position
dtVcopy(&pathPoints[1 * VERTEX_SIZE], endPoint);
@@ -564,6 +564,22 @@ void PathGenerator::BuildPointPath(const float* startPoint, const float* endPoin
}
else if (pointCount < 2 || dtStatusFailed(dtResult))
{
// If its too steep, just return incomplete path.
if (pointCount > 0 && dtResult & DT_SLOPE_TOO_STEEP)
{
_pathPoints.resize(pointCount);
for (uint32 i = 0; i < pointCount; ++i)
_pathPoints[i] = G3D::Vector3(pathPoints[i * VERTEX_SIZE + 2], pathPoints[i * VERTEX_SIZE], pathPoints[i * VERTEX_SIZE + 1]);
NormalizePath();
// first point is always our current location - we need the next one
SetActualEndPosition(_pathPoints[pointCount - 1]);
_type = PathType(_type | PATHFIND_INCOMPLETE);
return;
}
// only happens if pass bad data to findStraightPath or navmesh is broken
// single point paths can be generated here
/// @todo check the exact cases
@@ -884,7 +900,9 @@ dtStatus PathGenerator::FindSmoothPath(float const* startPos, float const* endPo
if (canCheckSlope && !IsSwimmableSegment(iterPos, steerPos) && !IsWalkableClimb(iterPos, steerPos))
{
return DT_FAILURE;
nsmoothPath--;
*smoothPathSize = nsmoothPath;
return DT_FAILURE | DT_SLOPE_TOO_STEEP;
}
// Handle end of path and off-mesh links when close enough.
+1 -1
View File
@@ -5405,7 +5405,7 @@ void Spell::TakePower()
void Spell::TakeAmmo()
{
if (m_attackType == RANGED_ATTACK && m_caster->IsPlayer())
if (m_attackType == RANGED_ATTACK && m_caster->IsPlayer() && !m_spellInfo->HasAttribute(SPELL_ATTR6_DO_NOT_CONSUME_RESOURCES))
{
Item* pItem = m_caster->ToPlayer()->GetWeaponForAttack(RANGED_ATTACK);
+120 -121
View File
@@ -27,115 +27,115 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<uint32>(CONFIG_ENABLE_SINFO_LOGIN, "Server.LoginInfo", 0);
///- Read all rates from the config file
SetConfigValue<float>(RATE_HEALTH, "Rate.Health", 1, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value > 0; }, "> 0");
SetConfigValue<float>(RATE_POWER_MANA, "Rate.Mana", 1, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value > 0; }, "> 0");
SetConfigValue<float>(RATE_POWER_RAGE_INCOME, "Rate.Rage.Income", 1);
SetConfigValue<float>(RATE_POWER_RAGE_LOSS, "Rate.Rage.Loss", 1, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value > 0; }, "> 0");
SetConfigValue<float>(RATE_POWER_RUNICPOWER_INCOME, "Rate.RunicPower.Income", 1);
SetConfigValue<float>(RATE_POWER_RUNICPOWER_LOSS, "Rate.RunicPower.Loss", 1, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value > 0; }, "> 0");
SetConfigValue<float>(RATE_POWER_FOCUS, "Rate.Focus", 1);
SetConfigValue<float>(RATE_POWER_ENERGY, "Rate.Energy", 1);
SetConfigValue<float>(RATE_HEALTH, "Rate.Health", 1.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value > 0.0f; }, "> 0");
SetConfigValue<float>(RATE_POWER_MANA, "Rate.Mana", 1.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value > 0.0f; }, "> 0");
SetConfigValue<float>(RATE_POWER_RAGE_INCOME, "Rate.Rage.Income", 1.0f);
SetConfigValue<float>(RATE_POWER_RAGE_LOSS, "Rate.Rage.Loss", 1.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value > 0.0f; }, "> 0");
SetConfigValue<float>(RATE_POWER_RUNICPOWER_INCOME, "Rate.RunicPower.Income", 1.0f);
SetConfigValue<float>(RATE_POWER_RUNICPOWER_LOSS, "Rate.RunicPower.Loss", 1, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value > 0.0f; }, "> 0");
SetConfigValue<float>(RATE_POWER_FOCUS, "Rate.Focus", 1.0f);
SetConfigValue<float>(RATE_POWER_ENERGY, "Rate.Energy", 1.0f);
SetConfigValue<float>(RATE_SKILL_DISCOVERY, "Rate.Skill.Discovery", 1);
SetConfigValue<float>(RATE_SKILL_DISCOVERY, "Rate.Skill.Discovery", 1.0f);
SetConfigValue<float>(RATE_DROP_ITEM_POOR, "Rate.Drop.Item.Poor", 1);
SetConfigValue<float>(RATE_DROP_ITEM_NORMAL, "Rate.Drop.Item.Normal", 1);
SetConfigValue<float>(RATE_DROP_ITEM_UNCOMMON, "Rate.Drop.Item.Uncommon", 1);
SetConfigValue<float>(RATE_DROP_ITEM_RARE, "Rate.Drop.Item.Rare", 1);
SetConfigValue<float>(RATE_DROP_ITEM_EPIC, "Rate.Drop.Item.Epic", 1);
SetConfigValue<float>(RATE_DROP_ITEM_LEGENDARY, "Rate.Drop.Item.Legendary", 1);
SetConfigValue<float>(RATE_DROP_ITEM_ARTIFACT, "Rate.Drop.Item.Artifact", 1);
SetConfigValue<float>(RATE_DROP_ITEM_REFERENCED, "Rate.Drop.Item.Referenced", 1);
SetConfigValue<float>(RATE_DROP_ITEM_REFERENCED_AMOUNT, "Rate.Drop.Item.ReferencedAmount", 1);
SetConfigValue<float>(RATE_DROP_ITEM_GROUP_AMOUNT, "Rate.Drop.Item.GroupAmount", 1);
SetConfigValue<float>(RATE_DROP_MONEY, "Rate.Drop.Money", 1);
SetConfigValue<float>(RATE_DROP_ITEM_POOR, "Rate.Drop.Item.Poor", 1.0f);
SetConfigValue<float>(RATE_DROP_ITEM_NORMAL, "Rate.Drop.Item.Normal", 1.0f);
SetConfigValue<float>(RATE_DROP_ITEM_UNCOMMON, "Rate.Drop.Item.Uncommon", 1.0f);
SetConfigValue<float>(RATE_DROP_ITEM_RARE, "Rate.Drop.Item.Rare", 1.0f);
SetConfigValue<float>(RATE_DROP_ITEM_EPIC, "Rate.Drop.Item.Epic", 1.0f);
SetConfigValue<float>(RATE_DROP_ITEM_LEGENDARY, "Rate.Drop.Item.Legendary", 1.0f);
SetConfigValue<float>(RATE_DROP_ITEM_ARTIFACT, "Rate.Drop.Item.Artifact", 1.0f);
SetConfigValue<float>(RATE_DROP_ITEM_REFERENCED, "Rate.Drop.Item.Referenced", 1.0f);
SetConfigValue<float>(RATE_DROP_ITEM_REFERENCED_AMOUNT, "Rate.Drop.Item.ReferencedAmount", 1.0f);
SetConfigValue<float>(RATE_DROP_ITEM_GROUP_AMOUNT, "Rate.Drop.Item.GroupAmount", 1.0f);
SetConfigValue<float>(RATE_DROP_MONEY, "Rate.Drop.Money", 1.0f);
SetConfigValue<float>(RATE_REWARD_QUEST_MONEY, "Rate.RewardQuestMoney", 1);
SetConfigValue<float>(RATE_REWARD_BONUS_MONEY, "Rate.RewardBonusMoney", 1);
SetConfigValue<float>(RATE_XP_KILL, "Rate.XP.Kill", 1);
SetConfigValue<float>(RATE_XP_BG_KILL_AV, "Rate.XP.BattlegroundKillAV", 1);
SetConfigValue<float>(RATE_XP_BG_KILL_WSG, "Rate.XP.BattlegroundKillWSG", 1);
SetConfigValue<float>(RATE_XP_BG_KILL_AB, "Rate.XP.BattlegroundKillAB", 1);
SetConfigValue<float>(RATE_XP_BG_KILL_EOTS, "Rate.XP.BattlegroundKillEOTS", 1);
SetConfigValue<float>(RATE_XP_BG_KILL_SOTA, "Rate.XP.BattlegroundKillSOTA", 1);
SetConfigValue<float>(RATE_XP_BG_KILL_IC, "Rate.XP.BattlegroundKillIC", 1);
SetConfigValue<float>(RATE_XP_QUEST, "Rate.XP.Quest", 1);
SetConfigValue<float>(RATE_XP_QUEST_DF, "Rate.XP.Quest.DF", 1);
SetConfigValue<float>(RATE_XP_EXPLORE, "Rate.XP.Explore", 1);
SetConfigValue<float>(RATE_XP_PET, "Rate.XP.Pet", 1);
SetConfigValue<float>(RATE_XP_PET_NEXT_LEVEL, "Rate.Pet.LevelXP", 0);
SetConfigValue<float>(RATE_REPAIRCOST, "Rate.RepairCost", 1, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0; }, ">= 0");
SetConfigValue<float>(RATE_REWARD_QUEST_MONEY, "Rate.RewardQuestMoney", 1.0f);
SetConfigValue<float>(RATE_REWARD_BONUS_MONEY, "Rate.RewardBonusMoney", 1.0f);
SetConfigValue<float>(RATE_XP_KILL, "Rate.XP.Kill", 1.0f);
SetConfigValue<float>(RATE_XP_BG_KILL_AV, "Rate.XP.BattlegroundKillAV", 1.0f);
SetConfigValue<float>(RATE_XP_BG_KILL_WSG, "Rate.XP.BattlegroundKillWSG", 1.0f);
SetConfigValue<float>(RATE_XP_BG_KILL_AB, "Rate.XP.BattlegroundKillAB", 1.0f);
SetConfigValue<float>(RATE_XP_BG_KILL_EOTS, "Rate.XP.BattlegroundKillEOTS", 1.0f);
SetConfigValue<float>(RATE_XP_BG_KILL_SOTA, "Rate.XP.BattlegroundKillSOTA", 1.0f);
SetConfigValue<float>(RATE_XP_BG_KILL_IC, "Rate.XP.BattlegroundKillIC", 1.0f);
SetConfigValue<float>(RATE_XP_QUEST, "Rate.XP.Quest", 1.0f);
SetConfigValue<float>(RATE_XP_QUEST_DF, "Rate.XP.Quest.DF", 1.0f);
SetConfigValue<float>(RATE_XP_EXPLORE, "Rate.XP.Explore", 1.0f);
SetConfigValue<float>(RATE_XP_PET, "Rate.XP.Pet", 1.0f);
SetConfigValue<float>(RATE_XP_PET_NEXT_LEVEL, "Rate.Pet.LevelXP", 0.05f);
SetConfigValue<float>(RATE_REPAIRCOST, "Rate.RepairCost", 1.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0");
SetConfigValue<float>(RATE_SELLVALUE_ITEM_POOR, "Rate.SellValue.Item.Poor", 1);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_NORMAL, "Rate.SellValue.Item.Normal", 1);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_UNCOMMON, "Rate.SellValue.Item.Uncommon", 1);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_RARE, "Rate.SellValue.Item.Rare", 1);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_EPIC, "Rate.SellValue.Item.Epic", 1);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_LEGENDARY, "Rate.SellValue.Item.Legendary", 1);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_ARTIFACT, "Rate.SellValue.Item.Artifact", 1);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_HEIRLOOM, "Rate.SellValue.Item.Heirloom", 1);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_POOR, "Rate.SellValue.Item.Poor", 1.0f);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_NORMAL, "Rate.SellValue.Item.Normal", 1.0f);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_UNCOMMON, "Rate.SellValue.Item.Uncommon", 1.0f);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_RARE, "Rate.SellValue.Item.Rare", 1.0f);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_EPIC, "Rate.SellValue.Item.Epic", 1.0f);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_LEGENDARY, "Rate.SellValue.Item.Legendary", 1.0f);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_ARTIFACT, "Rate.SellValue.Item.Artifact", 1.0f);
SetConfigValue<float>(RATE_SELLVALUE_ITEM_HEIRLOOM, "Rate.SellValue.Item.Heirloom", 1.0f);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_POOR, "Rate.BuyValue.Item.Poor", 1);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_NORMAL, "Rate.BuyValue.Item.Normal", 1);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_UNCOMMON, "Rate.BuyValue.Item.Uncommon", 1);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_RARE, "Rate.BuyValue.Item.Rare", 1);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_EPIC, "Rate.BuyValue.Item.Epic", 1);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_LEGENDARY, "Rate.BuyValue.Item.Legendary", 1);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_ARTIFACT, "Rate.BuyValue.Item.Artifact", 1);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_HEIRLOOM, "Rate.BuyValue.Item.Heirloom", 1);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_POOR, "Rate.BuyValue.Item.Poor", 1.0f);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_NORMAL, "Rate.BuyValue.Item.Normal", 1.0f);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_UNCOMMON, "Rate.BuyValue.Item.Uncommon", 1.0f);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_RARE, "Rate.BuyValue.Item.Rare", 1.0f);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_EPIC, "Rate.BuyValue.Item.Epic", 1.0f);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_LEGENDARY, "Rate.BuyValue.Item.Legendary", 1.0f);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_ARTIFACT, "Rate.BuyValue.Item.Artifact", 1.0f);
SetConfigValue<float>(RATE_BUYVALUE_ITEM_HEIRLOOM, "Rate.BuyValue.Item.Heirloom", 1.0f);
SetConfigValue<float>(RATE_REPUTATION_GAIN, "Rate.Reputation.Gain", 1);
SetConfigValue<float>(RATE_REPUTATION_LOWLEVEL_KILL, "Rate.Reputation.LowLevel.Kill", 1);
SetConfigValue<float>(RATE_REPUTATION_LOWLEVEL_QUEST, "Rate.Reputation.LowLevel.Quest", 1);
SetConfigValue<float>(RATE_REPUTATION_RECRUIT_A_FRIEND_BONUS, "Rate.Reputation.RecruitAFriendBonus", 0);
SetConfigValue<float>(RATE_CREATURE_NORMAL_DAMAGE, "Rate.Creature.Normal.Damage", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_ELITE_DAMAGE, "Rate.Creature.Elite.Elite.Damage", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_RAREELITE_DAMAGE, "Rate.Creature.Elite.RAREELITE.Damage", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_WORLDBOSS_DAMAGE, "Rate.Creature.Elite.WORLDBOSS.Damage", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_RARE_DAMAGE, "Rate.Creature.Elite.RARE.Damage", 1);
SetConfigValue<float>(RATE_CREATURE_NORMAL_HP, "Rate.Creature.Normal.HP", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_ELITE_HP, "Rate.Creature.Elite.Elite.HP", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_RAREELITE_HP, "Rate.Creature.Elite.RAREELITE.HP", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_WORLDBOSS_HP, "Rate.Creature.Elite.WORLDBOSS.HP", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_RARE_HP, "Rate.Creature.Elite.RARE.HP", 1);
SetConfigValue<float>(RATE_CREATURE_NORMAL_SPELLDAMAGE, "Rate.Creature.Normal.SpellDamage", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_ELITE_SPELLDAMAGE, "Rate.Creature.Elite.Elite.SpellDamage", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_RAREELITE_SPELLDAMAGE, "Rate.Creature.Elite.RAREELITE.SpellDamage", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_WORLDBOSS_SPELLDAMAGE, "Rate.Creature.Elite.WORLDBOSS.SpellDamage", 1);
SetConfigValue<float>(RATE_CREATURE_ELITE_RARE_SPELLDAMAGE, "Rate.Creature.Elite.RARE.SpellDamage", 1);
SetConfigValue<float>(RATE_CREATURE_AGGRO, "Rate.Creature.Aggro", 1);
SetConfigValue<float>(RATE_REST_INGAME, "Rate.Rest.InGame", 1);
SetConfigValue<float>(RATE_REST_OFFLINE_IN_TAVERN_OR_CITY, "Rate.Rest.Offline.InTavernOrCity", 1);
SetConfigValue<float>(RATE_REST_OFFLINE_IN_WILDERNESS, "Rate.Rest.Offline.InWilderness", 1);
SetConfigValue<float>(RATE_REST_MAX_BONUS, "Rate.Rest.MaxBonus", 1);
SetConfigValue<float>(RATE_DAMAGE_FALL, "Rate.Damage.Fall", 1);
SetConfigValue<float>(RATE_AUCTION_TIME, "Rate.Auction.Time", 1);
SetConfigValue<float>(RATE_AUCTION_DEPOSIT, "Rate.Auction.Deposit", 1);
SetConfigValue<float>(RATE_AUCTION_CUT, "Rate.Auction.Cut", 1);
SetConfigValue<float>(RATE_HONOR, "Rate.Honor", 1);
SetConfigValue<float>(RATE_ARENA_POINTS, "Rate.ArenaPoints", 1);
SetConfigValue<float>(RATE_INSTANCE_RESET_TIME, "Rate.InstanceResetTime", 1);
SetConfigValue<float>(RATE_REPUTATION_GAIN, "Rate.Reputation.Gain", 1.0f);
SetConfigValue<float>(RATE_REPUTATION_LOWLEVEL_KILL, "Rate.Reputation.LowLevel.Kill", 1.0f);
SetConfigValue<float>(RATE_REPUTATION_LOWLEVEL_QUEST, "Rate.Reputation.LowLevel.Quest", 1.0f);
SetConfigValue<float>(RATE_REPUTATION_RECRUIT_A_FRIEND_BONUS, "Rate.Reputation.RecruitAFriendBonus", 0.1f);
SetConfigValue<float>(RATE_CREATURE_NORMAL_DAMAGE, "Rate.Creature.Normal.Damage", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_ELITE_DAMAGE, "Rate.Creature.Elite.Elite.Damage", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_RAREELITE_DAMAGE, "Rate.Creature.Elite.RAREELITE.Damage", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_WORLDBOSS_DAMAGE, "Rate.Creature.Elite.WORLDBOSS.Damage", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_RARE_DAMAGE, "Rate.Creature.Elite.RARE.Damage", 1.0f);
SetConfigValue<float>(RATE_CREATURE_NORMAL_HP, "Rate.Creature.Normal.HP", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_ELITE_HP, "Rate.Creature.Elite.Elite.HP", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_RAREELITE_HP, "Rate.Creature.Elite.RAREELITE.HP", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_WORLDBOSS_HP, "Rate.Creature.Elite.WORLDBOSS.HP", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_RARE_HP, "Rate.Creature.Elite.RARE.HP", 1.0f);
SetConfigValue<float>(RATE_CREATURE_NORMAL_SPELLDAMAGE, "Rate.Creature.Normal.SpellDamage", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_ELITE_SPELLDAMAGE, "Rate.Creature.Elite.Elite.SpellDamage", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_RAREELITE_SPELLDAMAGE, "Rate.Creature.Elite.RAREELITE.SpellDamage", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_WORLDBOSS_SPELLDAMAGE, "Rate.Creature.Elite.WORLDBOSS.SpellDamage", 1.0f);
SetConfigValue<float>(RATE_CREATURE_ELITE_RARE_SPELLDAMAGE, "Rate.Creature.Elite.RARE.SpellDamage", 1.0f);
SetConfigValue<float>(RATE_CREATURE_AGGRO, "Rate.Creature.Aggro", 1.0f);
SetConfigValue<float>(RATE_REST_INGAME, "Rate.Rest.InGame", 1.0f);
SetConfigValue<float>(RATE_REST_OFFLINE_IN_TAVERN_OR_CITY, "Rate.Rest.Offline.InTavernOrCity", 1.0f);
SetConfigValue<float>(RATE_REST_OFFLINE_IN_WILDERNESS, "Rate.Rest.Offline.InWilderness", 1.0f);
SetConfigValue<float>(RATE_REST_MAX_BONUS, "Rate.Rest.MaxBonus", 1.5f);
SetConfigValue<float>(RATE_DAMAGE_FALL, "Rate.Damage.Fall", 1.0f);
SetConfigValue<float>(RATE_AUCTION_TIME, "Rate.Auction.Time", 1.0f);
SetConfigValue<float>(RATE_AUCTION_DEPOSIT, "Rate.Auction.Deposit", 1.0f);
SetConfigValue<float>(RATE_AUCTION_CUT, "Rate.Auction.Cut", 1.0f);
SetConfigValue<float>(RATE_HONOR, "Rate.Honor", 1.0f);
SetConfigValue<float>(RATE_ARENA_POINTS, "Rate.ArenaPoints", 1.0f);
SetConfigValue<float>(RATE_INSTANCE_RESET_TIME, "Rate.InstanceResetTime", 1.0f);
SetConfigValue<float>(RATE_MISS_CHANCE_MULTIPLIER_TARGET_CREATURE, "Rate.MissChanceMultiplier.TargetCreature", 11);
SetConfigValue<float>(RATE_MISS_CHANCE_MULTIPLIER_TARGET_PLAYER, "Rate.MissChanceMultiplier.TargetPlayer", 7);
SetConfigValue<float>(RATE_MISS_CHANCE_MULTIPLIER_TARGET_CREATURE, "Rate.MissChanceMultiplier.TargetCreature", 11.0f);
SetConfigValue<float>(RATE_MISS_CHANCE_MULTIPLIER_TARGET_PLAYER, "Rate.MissChanceMultiplier.TargetPlayer", 7.0f);
SetConfigValue<bool>(CONFIG_MISS_CHANCE_MULTIPLIER_ONLY_FOR_PLAYERS, "Rate.MissChanceMultiplier.OnlyAffectsPlayer", false);
SetConfigValue<float>(RATE_TALENT, "Rate.Talent", 1, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0; }, ">= 0");
SetConfigValue<float>(RATE_TALENT_PET, "Rate.Talent.Pet", 1, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0; }, ">= 0");
SetConfigValue<float>(RATE_TALENT, "Rate.Talent", 1.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0");
SetConfigValue<float>(RATE_TALENT_PET, "Rate.Talent.Pet", 1.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0");
// Controls Player movespeed rate.
SetConfigValue<float>(RATE_MOVESPEED_PLAYER, "Rate.MoveSpeed.Player", 1, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0; }, ">= 0");
SetConfigValue<float>(RATE_MOVESPEED_PLAYER, "Rate.MoveSpeed.Player", 1.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0");
// Controls all npc movespeed rate.
SetConfigValue<float>(RATE_MOVESPEED_NPC, "Rate.MoveSpeed.NPC", 1, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0; }, ">= 0");
SetConfigValue<float>(RATE_MOVESPEED_NPC, "Rate.MoveSpeed.NPC", 1.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0");
SetConfigValue<float>(RATE_CORPSE_DECAY_LOOTED, "Rate.Corpse.Decay.Looted", 0);
SetConfigValue<float>(RATE_CORPSE_DECAY_LOOTED, "Rate.Corpse.Decay.Looted", 0.5f);
SetConfigValue<float>(RATE_DURABILITY_LOSS_ON_DEATH, "DurabilityLoss.OnDeath", 10, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0 && value <= 100; }, ">= 0 && <= 100");
SetConfigValue<float>(RATE_DURABILITY_LOSS_ON_DEATH, "DurabilityLoss.OnDeath", 10.0f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f && value <= 100.0f; }, ">= 0 && <= 100");
SetConfigValue<float>(RATE_DURABILITY_LOSS_DAMAGE, "DurabilityLossChance.Damage", 0, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0; }, ">= 0");
SetConfigValue<float>(RATE_DURABILITY_LOSS_ABSORB, "DurabilityLossChance.Absorb", 0, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0; }, ">= 0");
SetConfigValue<float>(RATE_DURABILITY_LOSS_PARRY, "DurabilityLossChance.Parry", 0, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0; }, ">= 0");
SetConfigValue<float>(RATE_DURABILITY_LOSS_BLOCK, "DurabilityLossChance.Block", 0, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0; }, ">= 0");
SetConfigValue<float>(RATE_DURABILITY_LOSS_DAMAGE, "DurabilityLossChance.Damage", 0.5f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0");
SetConfigValue<float>(RATE_DURABILITY_LOSS_ABSORB, "DurabilityLossChance.Absorb", 0.5f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0");
SetConfigValue<float>(RATE_DURABILITY_LOSS_PARRY, "DurabilityLossChance.Parry", 0.05f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0");
SetConfigValue<float>(RATE_DURABILITY_LOSS_BLOCK, "DurabilityLossChance.Block", 0.05f, ConfigValueCache::Reloadable::Yes, [](float const& value) { return value >= 0.0f; }, ">= 0");
///- Read other configuration items from the config file
@@ -157,7 +157,7 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<bool>(CONFIG_ALLOW_PLAYER_COMMANDS, "AllowPlayerCommands", 1);
SetConfigValue<bool>(CONFIG_PRESERVE_CUSTOM_CHANNELS, "PreserveCustomChannels", false);
SetConfigValue<uint32>(CONFIG_PRESERVE_CUSTOM_CHANNEL_DURATION, "PreserveCustomChannelDuration", 14);
SetConfigValue<uint32>(CONFIG_INTERVAL_SAVE, "PlayerSaveInterval", 15);
SetConfigValue<uint32>(CONFIG_INTERVAL_SAVE, "PlayerSaveInterval", 900000);
SetConfigValue<uint32>(CONFIG_INTERVAL_DISCONNECT_TOLERANCE, "DisconnectToleranceInterval", 0);
SetConfigValue<bool>(CONFIG_STATS_SAVE_ONLY_ON_LOGOUT, "PlayerSave.Stats.SaveOnlyOnLogout", true);
@@ -165,7 +165,7 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<uint32>(CONFIG_INTERVAL_MAPUPDATE, "MapUpdateInterval", 10, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value >= MIN_MAP_UPDATE_DELAY; }, ">= MIN_MAP_UPDATE_DELAY");
SetConfigValue<uint32>(CONFIG_INTERVAL_CHANGEWEATHER, "ChangeWeatherInterval", 10);
SetConfigValue<uint32>(CONFIG_INTERVAL_CHANGEWEATHER, "ChangeWeatherInterval", 600000);
SetConfigValue<uint32>(CONFIG_PORT_WORLD, "WorldServerPort", 8085, ConfigValueCache::Reloadable::No);
@@ -174,10 +174,10 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<uint32>(CONFIG_SOCKET_TIMEOUTTIME_ACTIVE, "SocketTimeOutTimeActive", 60000);
SetConfigValue<uint32>(CONFIG_SESSION_ADD_DELAY, "SessionAddDelay", 10000);
SetConfigValue<float>(CONFIG_GROUP_XP_DISTANCE, "MaxGroupXPDistance", 74);
SetConfigValue<float>(CONFIG_MAX_RECRUIT_A_FRIEND_DISTANCE, "MaxRecruitAFriendBonusDistance", 100);
SetConfigValue<float>(CONFIG_GROUP_XP_DISTANCE, "MaxGroupXPDistance", 74.0f);
SetConfigValue<float>(CONFIG_MAX_RECRUIT_A_FRIEND_DISTANCE, "MaxRecruitAFriendBonusDistance", 100.0f);
SetConfigValue<float>(CONFIG_SIGHT_MONSTER, "MonsterSight", 50);
SetConfigValue<float>(CONFIG_SIGHT_MONSTER, "MonsterSight", 50.0f);
SetConfigValue<uint32>(CONFIG_GAME_TYPE, "GameType", 0, ConfigValueCache::Reloadable::No);
SetConfigValue<uint32>(CONFIG_REALM_ZONE, "RealmZone", REALM_ZONE_DEVELOPMENT, ConfigValueCache::Reloadable::No);
@@ -258,11 +258,11 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<bool>(CONFIG_INSTANCE_IGNORE_LEVEL, "Instance.IgnoreLevel", false);
SetConfigValue<bool>(CONFIG_INSTANCE_IGNORE_RAID, "Instance.IgnoreRaid", false);
SetConfigValue<bool>(CONFIG_INSTANCE_GMSUMMON_PLAYER, "Instance.GMSummonPlayer", false);
SetConfigValue<bool>(CONFIG_INSTANCE_SHARED_ID, "Instance.SharedNormalHeroicId", false);
SetConfigValue<bool>(CONFIG_INSTANCE_SHARED_ID, "Instance.SharedNormalHeroicId", true);
SetConfigValue<uint32>(CONFIG_INSTANCE_RESET_TIME_HOUR, "Instance.ResetTimeHour", 4);
SetConfigValue<uint32>(CONFIG_INSTANCE_RESET_TIME_RELATIVE_TIMESTAMP, "Instance.ResetTimeRelativeTimestamp", 1135814400);
SetConfigValue<uint32>(CONFIG_INSTANCE_UNLOAD_DELAY, "Instance.UnloadDelay", 30);
SetConfigValue<uint32>(CONFIG_INSTANCE_UNLOAD_DELAY, "Instance.UnloadDelay", 1800000);
SetConfigValue<uint32>(CONFIG_MAX_PRIMARY_TRADE_SKILL, "MaxPrimaryTradeSkill", 2);
SetConfigValue<uint32>(CONFIG_MIN_PETITION_SIGNS, "MinPetitionSigns", 9, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value <= 9; }, "<= 9");
@@ -279,7 +279,7 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<bool>(CONFIG_ALLOW_GM_GROUP, "GM.AllowInvite", false);
SetConfigValue<bool>(CONFIG_ALLOW_GM_FRIEND, "GM.AllowFriend", false);
SetConfigValue<bool>(CONFIG_GM_LOWER_SECURITY, "GM.LowerSecurity", false);
SetConfigValue<float>(CONFIG_CHANCE_OF_GM_SURVEY, "GM.TicketSystem.ChanceOfGMSurvey", 50);
SetConfigValue<float>(CONFIG_CHANCE_OF_GM_SURVEY, "GM.TicketSystem.ChanceOfGMSurvey", 50.0f);
SetConfigValue<uint32>(CONFIG_GROUP_VISIBILITY, "Visibility.GroupMode", 1);
@@ -344,8 +344,8 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<uint32>(CONFIG_EVENT_ANNOUNCE, "Event.Announce", 0);
SetConfigValue<float>(CONFIG_CREATURE_LEASH_RADIUS, "CreatureLeashRadius", 30.0f);
SetConfigValue<float>(CONFIG_CREATURE_FAMILY_FLEE_ASSISTANCE_RADIUS, "CreatureFamilyFleeAssistanceRadius", 30);
SetConfigValue<float>(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS, "CreatureFamilyAssistanceRadius", 10);
SetConfigValue<float>(CONFIG_CREATURE_FAMILY_FLEE_ASSISTANCE_RADIUS, "CreatureFamilyFleeAssistanceRadius", 30.0f);
SetConfigValue<float>(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS, "CreatureFamilyAssistanceRadius", 10.0f);
SetConfigValue<uint32>(CONFIG_CREATURE_FAMILY_ASSISTANCE_DELAY, "CreatureFamilyAssistanceDelay", 2000);
SetConfigValue<uint32>(CONFIG_CREATURE_FAMILY_ASSISTANCE_PERIOD, "CreatureFamilyAssistancePeriod", 3000);
SetConfigValue<uint32>(CONFIG_CREATURE_FAMILY_FLEE_DELAY, "CreatureFamilyFleeDelay", 7000);
@@ -402,9 +402,9 @@ void WorldConfig::BuildConfigCache()
// always use declined names in the russian client
SetConfigValue<bool>(CONFIG_DECLINED_NAMES_USED, "DeclinedNames", GetConfigValue<uint32>(CONFIG_REALM_ZONE) == REALM_ZONE_RUSSIAN);
SetConfigValue<float>(CONFIG_LISTEN_RANGE_SAY, "ListenRange.Say", 25);
SetConfigValue<float>(CONFIG_LISTEN_RANGE_TEXTEMOTE, "ListenRange.TextEmote", 25);
SetConfigValue<float>(CONFIG_LISTEN_RANGE_YELL, "ListenRange.Yell", 300);
SetConfigValue<float>(CONFIG_LISTEN_RANGE_SAY, "ListenRange.Say", 40.0f);
SetConfigValue<float>(CONFIG_LISTEN_RANGE_TEXTEMOTE, "ListenRange.TextEmote", 40.0f);
SetConfigValue<float>(CONFIG_LISTEN_RANGE_YELL, "ListenRange.Yell", 300.0f);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_OVERRIDE_LOWLEVELS_MINPLAYERS, "Battleground.Override.LowLevels.MinPlayers", 0);
SetConfigValue<bool>(CONFIG_BATTLEGROUND_DISABLE_QUEST_SHARE_IN_BG, "Battleground.DisableQuestShareInBG", false);
@@ -419,9 +419,9 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_TIMER, "Battleground.QueueAnnouncer.Timer", 30000);
SetConfigValue<bool>(CONFIG_BATTLEGROUND_STORE_STATISTICS_ENABLE, "Battleground.StoreStatistics.Enable", false);
SetConfigValue<bool>(CONFIG_BATTLEGROUND_TRACK_DESERTERS, "Battleground.TrackDeserters.Enable", false);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_PREMATURE_FINISH_TIMER, "Battleground.PrematureFinishTimer", 5);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_PREMATURE_FINISH_TIMER, "Battleground.PrematureFinishTimer", 300000);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_INVITATION_TYPE, "Battleground.InvitationType", 0);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_PREMADE_GROUP_WAIT_FOR_MATCH, "Battleground.PremadeGroupWaitForMatch", 30);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_PREMADE_GROUP_WAIT_FOR_MATCH, "Battleground.PremadeGroupWaitForMatch", 1800000);
SetConfigValue<bool>(CONFIG_BG_XP_FOR_KILL, "Battleground.GiveXPForKills", false);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_REPORT_AFK_TIMER, "Battleground.ReportAFK.Timer", 4);
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_REPORT_AFK, "Battleground.ReportAFK", 3, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value > 0 && value <= 9; }, "> 0 && value <= 9");
@@ -437,25 +437,26 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<uint32>(CONFIG_BATTLEGROUND_EYEOFTHESTORM_CAPTUREPOINTS, "Battleground.EyeOfTheStorm.CapturePoints", 1600);
SetConfigValue<uint32>(CONFIG_ARENA_MAX_RATING_DIFFERENCE, "Arena.MaxRatingDifference", 150);
SetConfigValue<uint32>(CONFIG_ARENA_RATING_DISCARD_TIMER, "Arena.RatingDiscardTimer", 10);
SetConfigValue<uint32>(CONFIG_ARENA_PREV_OPPONENTS_DISCARD_TIMER, "Arena.PreviousOpponentsDiscardTimer", 2);
SetConfigValue<uint32>(CONFIG_ARENA_RATING_DISCARD_TIMER, "Arena.RatingDiscardTimer", 600000);
SetConfigValue<uint32>(CONFIG_ARENA_PREV_OPPONENTS_DISCARD_TIMER, "Arena.PreviousOpponentsDiscardTimer", 120000);
SetConfigValue<bool>(CONFIG_ARENA_AUTO_DISTRIBUTE_POINTS, "Arena.AutoDistributePoints", false);
SetConfigValue<uint32>(CONFIG_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS, "Arena.AutoDistributeInterval", 7);
SetConfigValue<uint32>(CONFIG_ARENA_GAMES_REQUIRED, "Arena.GamesRequired", 10);
SetConfigValue<uint32>(CONFIG_ARENA_START_RATING, "Arena.ArenaStartRating", 0);
SetConfigValue<uint32>(CONFIG_LEGACY_ARENA_START_RATING, "Arena.LegacyArenaStartRating", 1500);
SetConfigValue<uint32>(CONFIG_LEGACY_ARENA_POINTS_CALC, "Arena.LegacyArenaPoints", 0);
SetConfigValue<uint32>(CONFIG_ARENA_START_PERSONAL_RATING, "Arena.ArenaStartPersonalRating", 0);
SetConfigValue<uint32>(CONFIG_ARENA_START_MATCHMAKER_RATING, "Arena.ArenaStartMatchmakerRating", 1500);
SetConfigValue<float>(CONFIG_ARENA_WIN_RATING_MODIFIER_1, "Arena.ArenaWinRatingModifier1", 48);
SetConfigValue<float>(CONFIG_ARENA_WIN_RATING_MODIFIER_2, "Arena.ArenaWinRatingModifier2", 24);
SetConfigValue<float>(CONFIG_ARENA_LOSE_RATING_MODIFIER, "Arena.ArenaLoseRatingModifier", 24);
SetConfigValue<float>(CONFIG_ARENA_MATCHMAKER_RATING_MODIFIER, "Arena.ArenaMatchmakerRatingModifier", 24);
SetConfigValue<float>(CONFIG_ARENA_WIN_RATING_MODIFIER_1, "Arena.ArenaWinRatingModifier1", 48.0f);
SetConfigValue<float>(CONFIG_ARENA_WIN_RATING_MODIFIER_2, "Arena.ArenaWinRatingModifier2", 24.0f);
SetConfigValue<float>(CONFIG_ARENA_LOSE_RATING_MODIFIER, "Arena.ArenaLoseRatingModifier", 24.0f);
SetConfigValue<float>(CONFIG_ARENA_MATCHMAKER_RATING_MODIFIER, "Arena.ArenaMatchmakerRatingModifier", 24.0f);
SetConfigValue<bool>(CONFIG_ARENA_QUEUE_ANNOUNCER_ENABLE, "Arena.QueueAnnouncer.Enable", false);
SetConfigValue<bool>(CONFIG_ARENA_QUEUE_ANNOUNCER_PLAYERONLY, "Arena.QueueAnnouncer.PlayerOnly", false);
SetConfigValue<uint32>(CONFIG_ARENA_QUEUE_ANNOUNCER_DETAIL, "Arena.QueueAnnouncer.Detail", 3);
SetConfigValue<bool>(CONFIG_OFFHAND_CHECK_AT_SPELL_UNLEARN, "OffhandCheckAtSpellUnlearn", true);
SetConfigValue<uint32>(CONFIG_CREATURE_STOP_FOR_PLAYER, "Creature.MovingStopTimeForPlayer", 3);
SetConfigValue<uint32>(CONFIG_CREATURE_STOP_FOR_PLAYER, "Creature.MovingStopTimeForPlayer", 180000);
SetConfigValue<uint32>(CONFIG_WATER_BREATH_TIMER, "WaterBreath.Timer", 180000, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value > 0; }, "> 0");
@@ -501,10 +502,10 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<uint32>(CONFIG_DAILY_RBG_MIN_LEVEL_AP_REWARD, "DailyRBGArenaPoints.MinLevel", 71);
// Respawn
SetConfigValue<float>(CONFIG_RESPAWN_DYNAMICRATE_CREATURE, "Respawn.DynamicRateCreature", 1);
SetConfigValue<float>(CONFIG_RESPAWN_DYNAMICRATE_CREATURE, "Respawn.DynamicRateCreature", 1.0f);
SetConfigValue<uint32>(CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE, "Respawn.DynamicMinimumCreature", 10);
SetConfigValue<float>(CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT, "Respawn.DynamicRateGameObject", 1);
SetConfigValue<float>(CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT, "Respawn.DynamicRateGameObject", 1.0f);
SetConfigValue<uint32>(CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT, "Respawn.DynamicMinimumGameObject", 10);
SetConfigValue<bool>(CONFIG_VMAP_INDOOR_CHECK, "vmap.enableIndoorCheck", true);
@@ -602,8 +603,6 @@ void WorldConfig::BuildConfigCache()
SetConfigValue<uint32>(CONFIG_ICC_BUFF_HORDE, "ICC.Buff.Horde", 73822);
SetConfigValue<uint32>(CONFIG_ICC_BUFF_ALLIANCE, "ICC.Buff.Alliance", 73828);
SetConfigValue<bool>(CONFIG_SET_ALL_CREATURES_WITH_WAYPOINT_MOVEMENT_ACTIVE, "SetAllCreaturesWithWaypointMovementActive", false);
// packet spoof punishment
SetConfigValue<uint32>(CONFIG_PACKET_SPOOF_BANMODE, "PacketSpoof.BanMode", 0, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value == 0; }, "== 0");
SetConfigValue<uint32>(CONFIG_PACKET_SPOOF_BANDURATION, "PacketSpoof.BanDuration", 86400);
@@ -640,7 +639,7 @@ void WorldConfig::BuildConfigCache()
// LFG group mechanics.
SetConfigValue<uint32>(CONFIG_LFG_MAX_KICK_COUNT, "LFG.MaxKickCount", 2, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value <= 3; }, "<= 3");
SetConfigValue<uint32>(CONFIG_LFG_KICK_PREVENTION_TIMER, "LFG.KickPreventionTimer", 15, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value <= 15 * MINUTE * IN_MILLISECONDS; }, "<= 15 * MINUTE * IN_MILLISECONDS");
SetConfigValue<uint32>(CONFIG_LFG_KICK_PREVENTION_TIMER, "LFG.KickPreventionTimer", 900, ConfigValueCache::Reloadable::Yes, [](uint32 const& value) { return value <= 15 * MINUTE * IN_MILLISECONDS; }, "<= 15 * MINUTE * IN_MILLISECONDS");
// Realm Availability
SetConfigValue<bool>(CONFIG_REALM_LOGIN_ENABLED, "World.RealmAvailability", true);
+1 -1
View File
@@ -117,7 +117,6 @@ enum ServerConfigs
CONFIG_ALLOW_TWO_SIDE_INTERACTION_EMOTE,
CONFIG_ITEMDELETE_METHOD,
CONFIG_ITEMDELETE_VENDOR,
CONFIG_SET_ALL_CREATURES_WITH_WAYPOINT_MOVEMENT_ACTIVE,
CONFIG_DEBUG_BATTLEGROUND,
CONFIG_DEBUG_ARENA,
CONFIG_DUNGEON_ACCESS_REQUIREMENTS_PORTAL_CHECK_ILVL,
@@ -281,6 +280,7 @@ enum ServerConfigs
CONFIG_ARENA_AUTO_DISTRIBUTE_INTERVAL_DAYS,
CONFIG_ARENA_GAMES_REQUIRED,
CONFIG_ARENA_START_RATING,
CONFIG_LEGACY_ARENA_START_RATING,
CONFIG_LEGACY_ARENA_POINTS_CALC,
CONFIG_ARENA_START_PERSONAL_RATING,
CONFIG_ARENA_START_MATCHMAKER_RATING,
+874 -1
View File
@@ -15,13 +15,18 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "AreaDefines.h"
#include "CreatureAIImpl.h"
#include "GameEventMgr.h"
#include "Log.h"
#include "MapMgr.h"
#include "Player.h"
#include "SharedDefines.h"
#include "UnitAI.h"
#include "Weather.h"
#include "WorldState.h"
#include "WorldStateDefines.h"
#include <chrono>
WorldState* WorldState::instance()
{
@@ -87,12 +92,43 @@ void WorldState::Load()
}
break;
}
case SAVE_ID_SCOURGE_INVASION:
if (data.size())
{
try
{
uint32 state;
loadStream >> state;
m_siData.m_state = SIState(state);
for (TimePoint& m_timer : m_siData.m_timers)
{
uint64 time;
loadStream >> time;
m_timer = TimePoint(std::chrono::milliseconds(time));
}
loadStream >> m_siData.m_battlesWon >> m_siData.m_lastAttackZone;
for (unsigned int& i : m_siData.m_remaining)
loadStream >> i;
}
catch (std::exception& e)
{
LOG_ERROR("scripts", "WorldState::Load: Exception reading ScourgeInvasion data {}", e.what());
m_siData.Reset();
}
}
break;
}
} while (result->NextRow());
}
StartSunsReachPhase(true);
StartSunwellGatePhase();
HandleSunsReachSubPhaseTransition(m_sunsReachData.m_subphaseMask, true);
if (m_siData.m_state == STATE_1_ENABLED)
{
StartScourgeInvasion(false);
HandleDefendedZones();
}
}
void WorldState::LoadWorldStates()
@@ -156,6 +192,12 @@ void WorldState::Save(WorldStateSaveIds saveId)
SaveHelper(expansionData, SAVE_ID_QUEL_DANAS);
break;
}
case SAVE_ID_SCOURGE_INVASION:
{
std::string siData = m_siData.GetData();
SaveHelper(siData, SAVE_ID_SCOURGE_INVASION);
break;
}
default:
break;
}
@@ -167,7 +209,7 @@ void WorldState::SaveHelper(std::string& stringToSave, WorldStateSaveIds saveId)
CharacterDatabase.Execute("INSERT INTO world_state(Id,Data) VALUES('{}','{}')", saveId, stringToSave.data());
}
bool WorldState::IsConditionFulfilled(WorldStateCondition conditionId, WorldStateConditionState state) const
bool WorldState::IsConditionFulfilled(uint32 conditionId, uint32 state) const
{
switch (conditionId)
{
@@ -179,6 +221,18 @@ bool WorldState::IsConditionFulfilled(WorldStateCondition conditionId, WorldStat
case WORLD_STATE_CONDITION_THE_PURPLE_PRINCESS:
case WORLD_STATE_CONDITION_THE_THUNDERCALLER:
return _transportStates.at(conditionId) == state;
case WORLD_STATE_SCOURGE_INVASION_WINTERSPRING:
return GetSIRemaining(SI_REMAINING_WINTERSPRING) > 0;
case WORLD_STATE_SCOURGE_INVASION_AZSHARA:
return GetSIRemaining(SI_REMAINING_AZSHARA) > 0;
case WORLD_STATE_SCOURGE_INVASION_BLASTED_LANDS:
return GetSIRemaining(SI_REMAINING_BLASTED_LANDS) > 0;
case WORLD_STATE_SCOURGE_INVASION_BURNING_STEPPES:
return GetSIRemaining(SI_REMAINING_BURNING_STEPPES) > 0;
case WORLD_STATE_SCOURGE_INVASION_TANARIS:
return GetSIRemaining(SI_REMAINING_TANARIS) > 0;
case WORLD_STATE_SCOURGE_INVASION_EASTERN_PLAGUELANDS:
return GetSIRemaining(SI_REMAINING_EASTERN_PLAGUELANDS) > 0;
default:
LOG_ERROR("scripts", "WorldState::IsConditionFulfilled: Unhandled WorldStateCondition {}", conditionId);
return false;
@@ -190,6 +244,18 @@ void WorldState::HandleConditionStateChange(WorldStateCondition conditionId, Wor
_transportStates[conditionId] = state;
}
Map* WorldState::GetMap(uint32 mapId, Position const& invZone)
{
Map* map = sMapMgr->FindBaseMap(mapId);
if (!map)
LOG_ERROR("scripts",
"ScourgeInvasionEvent::GetMap found no map with mapId {} , x: {}, y: {}.",
mapId,
invZone.GetPositionX(),
invZone.GetPositionY());
return map;
}
void WorldState::HandleExternalEvent(WorldStateEvent eventId, uint32 param)
{
std::lock_guard<std::mutex> guard(_mutex);
@@ -245,6 +311,32 @@ void WorldState::Update(uint32 diff)
_adalSongOfBattleTimer -= diff;
}
}
if (m_siData.m_state != STATE_0_DISABLED)
{
if (m_siData.m_broadcastTimer <= diff)
{
m_siData.m_broadcastTimer = 10000;
BroadcastSIWorldstates();
if (m_siData.m_state == STATE_1_ENABLED)
{
for (auto& zone : m_siData.m_cityAttacks)
{
if (zone.second.zoneId == AREA_UNDERCITY)
StartNewCityAttackIfTime(SI_TIMER_UNDERCITY, zone.second.zoneId);
else if (zone.second.zoneId == AREA_STORMWIND_CITY)
StartNewCityAttackIfTime(SI_TIMER_STORMWIND, zone.second.zoneId);
}
TimePoint now = std::chrono::steady_clock::now();
for (auto& zone : m_siData.m_activeInvasions)
HandleActiveZone(GetTimerIdForZone(zone.second.zoneId), zone.second.zoneId, zone.second.remainingNecropoli, now);
}
}
else
m_siData.m_broadcastTimer -= diff;
}
}
void WorldState::HandlePlayerEnterZone(Player* player, AreaTableIDs zoneId)
@@ -939,6 +1031,67 @@ std::string WorldState::GetSunsReachPrintout()
return output;
}
std::string WorldState::GetScourgeInvasionPrintout()
{
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
std::string output = "Scourge Invasion Status:\n";
auto formatState = [this]() -> std::string
{
switch (m_siData.m_state)
{
case STATE_0_DISABLED:
return "Disabled";
case STATE_1_ENABLED:
return "Enabled";
default:
return "Unknown";
}
};
auto formatRemaining = [this](SIRemaining val, char const* name) { return " " + std::string(name) + ": " + std::to_string(m_siData.m_remaining[val]) + "\n"; };
output += "State: " + formatState() + " (" + std::to_string(static_cast<uint32>(m_siData.m_state)) + ")\n";
output += "Battles Won: " + std::to_string(m_siData.m_battlesWon) + "\n";
output += "Last Attack Zone ID: " + std::to_string(m_siData.m_lastAttackZone) + "\n";
output += "Zone Necropolis Count Remaining:\n";
output += formatRemaining(SI_REMAINING_WINTERSPRING, "Winterspring");
output += formatRemaining(SI_REMAINING_AZSHARA, "Azshara");
output += formatRemaining(SI_REMAINING_BLASTED_LANDS, "Blasted Lands");
output += formatRemaining(SI_REMAINING_BURNING_STEPPES, "Burning Steppes");
output += formatRemaining(SI_REMAINING_TANARIS, "Tanaris");
output += formatRemaining(SI_REMAINING_EASTERN_PLAGUELANDS, "Eastern Plaguelands");
output += "Zone Timers (time until next event):\n";
TimePoint now = std::chrono::steady_clock::now();
auto formatTimer = [this](SITimers timerId, char const* name, TimePoint now)
{
TimePoint tp = m_siData.m_timers[timerId];
std::string timerStr;
if (tp.time_since_epoch().count() == 0)
timerStr = "0 (not set)";
else if (tp <= now)
timerStr = "Elapsed";
else
{
auto diff = std::chrono::duration_cast<std::chrono::seconds>(tp - now);
timerStr = std::to_string(diff.count()) + "s remaining";
}
return " " + std::string(name) + ": " + timerStr + "\n";
};
output += formatTimer(SI_TIMER_WINTERSPRING, "Winterspring Invasion", now);
output += formatTimer(SI_TIMER_AZSHARA, "Azshara Invasion", now);
output += formatTimer(SI_TIMER_BLASTED_LANDS, "Blasted Lands Invasion", now);
output += formatTimer(SI_TIMER_BURNING_STEPPES, "Burning Steppes Invasion", now);
output += formatTimer(SI_TIMER_TANARIS, "Tanaris Invasion", now);
output += formatTimer(SI_TIMER_EASTERN_PLAGUELANDS, "Eastern Plaguelands Invasion", now);
output += formatTimer(SI_TIMER_STORMWIND, "Stormwind City Attack", now);
output += formatTimer(SI_TIMER_UNDERCITY, "Undercity Attack", now);
return output;
}
std::string SunsReachReclamationData::GetData()
{
std::string output = std::to_string(m_phase) + " " + std::to_string(m_subphaseMask);
@@ -1087,8 +1240,726 @@ uint32 SunsReachReclamationData::GetSunwellGatePercentage(uint32 gate)
return percentage < 0 ? 0 : uint32(percentage);
}
void WorldState::SetScourgeInvasionState(SIState state)
{
SIState oldState = m_siData.m_state;
if (oldState == state)
return;
m_siData.m_state = state;
if (oldState == STATE_0_DISABLED)
StartScourgeInvasion(true);
else if (state == STATE_0_DISABLED)
StopScourgeInvasion();
Save(SAVE_ID_SCOURGE_INVASION);
}
void WorldState::SendScourgeInvasionMail()
{
QueryResult result = CharacterDatabase.Query("SELECT guid FROM characters WHERE level >= 50");
if (result)
{
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
MailDraft draft(MAIL_TEMPLATE_ARGENT_DAWN_NEEDS_YOUR_HELP);
uint32 count = 0;
do
{
Field* fields = result->Fetch();
ObjectGuid playerGUID = ObjectGuid::Create<HighGuid::Player>(fields[0].Get<uint32>());
// Add item manually. SendMailTo does not add items for offline players
if (Item* item = Item::CreateItem(ITEM_A_LETTER_FROM_THE_KEEPER_OF_THE_ROLLS, 1))
{
item->SaveToDB(trans);
draft.AddItem(item);
}
draft.SendMailTo(trans, MailReceiver(playerGUID.GetCounter()), NPC_ARGENT_EMISSARY, MAIL_CHECK_MASK_HAS_BODY);
++count;
} while (result->NextRow());
CharacterDatabase.CommitTransaction(trans);
LOG_INFO("WorldState", "SendScourgeInvasionMail sent to {} characters", count);
}
}
void WorldState::StartScourgeInvasion(bool sendMail)
{
sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION, false);
if (sendMail)
SendScourgeInvasionMail();
BroadcastSIWorldstates();
if (m_siData.m_state == STATE_1_ENABLED)
{
for (auto& zone : m_siData.m_cityAttacks)
{
if (zone.second.zoneId == AREA_UNDERCITY)
StartNewCityAttackIfTime(SI_TIMER_UNDERCITY, zone.second.zoneId);
else if (zone.second.zoneId == AREA_STORMWIND_CITY)
StartNewCityAttackIfTime(SI_TIMER_STORMWIND, zone.second.zoneId);
}
// randomization of init so that not every invasion starts the same way
std::vector<uint32> randomIds;
randomIds.reserve(m_siData.m_activeInvasions.size());
for (auto const& [zoneId, _] : m_siData.m_activeInvasions)
randomIds.push_back(zoneId);
Acore::Containers::RandomShuffle(randomIds);
for (uint32 id : randomIds)
OnEnable(m_siData.m_activeInvasions[id]);
}
}
ScourgeInvasionData::ScourgeInvasionData()
: m_state(STATE_0_DISABLED), m_battlesWon(0), m_lastAttackZone(0), m_remaining{}, m_broadcastTimer(10000)
{
m_activeInvasions.emplace(
AREA_WINTERSPRING,
InvasionZone{
.map = MAP_KALIMDOR,
.zoneId = AREA_WINTERSPRING,
.necropolisCount = 3,
.remainingNecropoli = SI_REMAINING_WINTERSPRING,
.mouth = { Position{7736.56f, -4033.75f, 696.327f, 5.51524f} }
}
);
m_activeInvasions.emplace(
AREA_TANARIS,
InvasionZone{
.map = MAP_KALIMDOR,
.zoneId = AREA_TANARIS,
.necropolisCount = 3,
.remainingNecropoli = SI_REMAINING_TANARIS,
.mouth = { Position{-8352.68f, -3972.68f, 10.0753f, 2.14675f} }
}
);
m_activeInvasions.emplace(
AREA_AZSHARA,
InvasionZone{
.map = MAP_KALIMDOR,
.zoneId = AREA_AZSHARA,
.necropolisCount = 2,
.remainingNecropoli = SI_REMAINING_AZSHARA,
.mouth = { Position{3273.75f, -4276.98f, 125.509f, 5.44543f} }
}
);
m_activeInvasions.emplace(
AREA_BLASTED_LANDS,
InvasionZone{
.map = MAP_EASTERN_KINGDOMS,
.zoneId = AREA_BLASTED_LANDS,
.necropolisCount = 2,
.remainingNecropoli = SI_REMAINING_BLASTED_LANDS,
.mouth = { Position{-11429.3f, -3327.82f, 7.73628f, 1.0821f} }
}
);
m_activeInvasions.emplace(
AREA_EASTERN_PLAGUELANDS,
InvasionZone{
.map = MAP_EASTERN_KINGDOMS,
.zoneId = AREA_EASTERN_PLAGUELANDS,
.necropolisCount = 2,
.remainingNecropoli = SI_REMAINING_EASTERN_PLAGUELANDS,
.mouth = { Position{2014.55f, -4934.52f, 73.9846f, 0.0698132f} }
}
);
m_activeInvasions.emplace(
AREA_BURNING_STEPPES,
InvasionZone{
.map = MAP_EASTERN_KINGDOMS,
.zoneId = AREA_BURNING_STEPPES,
.necropolisCount = 2,
.remainingNecropoli = SI_REMAINING_BURNING_STEPPES,
.mouth = { Position{-8229.53f, -1118.11f, 144.012f, 6.17846f} }
}
);
m_cityAttacks.emplace(
AREA_UNDERCITY,
CityAttack{
.map = MAP_EASTERN_KINGDOMS,
.zoneId = AREA_UNDERCITY,
.pallid = {
Position{1595.87f, 440.539f, -46.3349f, 2.28207f}, // Royal Quarter
Position{1659.2f, 265.988f, -62.1788f, 3.64283f} // Trade Quarter
}
}
);
m_cityAttacks.emplace(
AREA_STORMWIND_CITY,
CityAttack{
.map = MAP_EASTERN_KINGDOMS,
.zoneId = AREA_STORMWIND_CITY,
.pallid = {
Position{-8578.15f, 886.382f, 87.3148f, 0.586275f}, // Stormwind Keep
Position{-8578.15f, 886.382f, 87.3148f, 0.586275f} // Trade District
}
}
);
}
void ScourgeInvasionData::Reset()
{
std::lock_guard<std::mutex> guard(m_siMutex);
for (auto& timepoint : m_timers)
timepoint = TimePoint();
m_battlesWon = 0;
m_lastAttackZone = 0;
m_broadcastTimer = 10000;
memset(m_remaining, 0, sizeof(m_remaining));
}
std::string ScourgeInvasionData::GetData()
{
std::string output = std::to_string(m_state) + " ";
for (auto& timer : m_timers)
output += std::to_string(timer.time_since_epoch().count()) + " ";
output += std::to_string(m_battlesWon) + " " + std::to_string(m_lastAttackZone) + " ";
for (auto& remaining : m_remaining)
output += std::to_string(remaining) + " ";
return output;
}
void WorldState::StopScourgeInvasion()
{
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION);
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_WINTERSPRING);
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_TANARIS);
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_AZSHARA);
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_BLASTED_LANDS);
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_EASTERN_PLAGUELANDS);
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_BURNING_STEPPES);
BroadcastSIWorldstates();
m_siData.Reset();
for (auto& [_, cityData] : m_siData.m_cityAttacks)
OnDisable(cityData);
for (auto& [_, zoneData] : m_siData.m_activeInvasions)
OnDisable(zoneData);
}
uint32 WorldState::GetSIRemaining(SIRemaining remaining) const
{
return m_siData.m_remaining[remaining];
}
uint32 WorldState::GetSIRemainingByZone(uint32 zoneId) const
{
SIRemaining remainingId;
switch (zoneId)
{
case AREA_WINTERSPRING: remainingId = SI_REMAINING_WINTERSPRING; break;
case AREA_AZSHARA: remainingId = SI_REMAINING_AZSHARA; break;
case AREA_EASTERN_PLAGUELANDS: remainingId = SI_REMAINING_EASTERN_PLAGUELANDS; break;
case AREA_BLASTED_LANDS: remainingId = SI_REMAINING_BLASTED_LANDS; break;
case AREA_BURNING_STEPPES: remainingId = SI_REMAINING_BURNING_STEPPES; break;
case AREA_TANARIS: remainingId = SI_REMAINING_TANARIS; break;
default:
LOG_ERROR("WorldState", "GetSIRemainingByZone called with invalid zone ID: {}", zoneId);
return 0;
}
return GetSIRemaining(remainingId);
}
void WorldState::SetSIRemaining(SIRemaining remaining, uint32 value)
{
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
m_siData.m_remaining[remaining] = value;
Save(SAVE_ID_SCOURGE_INVASION);
}
TimePoint WorldState::GetSITimer(SITimers timer)
{
return m_siData.m_timers[timer];
}
void WorldState::SetSITimer(SITimers timer, TimePoint timePoint)
{
m_siData.m_timers[timer] = timePoint;
}
uint32 WorldState::GetBattlesWon()
{
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
return m_siData.m_battlesWon;
}
void WorldState::AddBattlesWon(int32 count)
{
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
m_siData.m_battlesWon += count;
HandleDefendedZones();
Save(SAVE_ID_SCOURGE_INVASION);
}
uint32 WorldState::GetLastAttackZone()
{
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
return m_siData.m_lastAttackZone;
}
void WorldState::SetLastAttackZone(uint32 zoneId)
{
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
m_siData.m_lastAttackZone = zoneId;
}
void WorldState::BroadcastSIWorldstates()
{
uint32 victories = GetBattlesWon();
uint32 remainingAzshara = GetSIRemaining(SI_REMAINING_AZSHARA);
uint32 remainingBlastedLands = GetSIRemaining(SI_REMAINING_BLASTED_LANDS);
uint32 remainingBurningSteppes = GetSIRemaining(SI_REMAINING_BURNING_STEPPES);
uint32 remainingEasternPlaguelands = GetSIRemaining(SI_REMAINING_EASTERN_PLAGUELANDS);
uint32 remainingTanaris = GetSIRemaining(SI_REMAINING_TANARIS);
uint32 remainingWinterspring = GetSIRemaining(SI_REMAINING_WINTERSPRING);
sMapMgr->DoForAllMaps([&](Map* map) -> void
{
switch (map->GetId())
{
case MAP_EASTERN_KINGDOMS:
case MAP_KALIMDOR:
map->DoForAllPlayers([&](Player* pl)
{
// do not process players which are not in world
if (!pl->IsInWorld())
return;
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_AZSHARA, remainingAzshara > 0 ? 1 : 0);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_BLASTED_LANDS, remainingBlastedLands > 0 ? 1 : 0);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_BURNING_STEPPES, remainingBurningSteppes > 0 ? 1 : 0);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_EASTERN_PLAGUELANDS, remainingEasternPlaguelands > 0 ? 1 : 0);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_TANARIS, remainingTanaris > 0 ? 1 : 0);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_WINTERSPRING, remainingWinterspring > 0 ? 1 : 0);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_VICTORIES, victories);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_AZSHARA, remainingAzshara);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_BLASTED_LANDS, remainingBlastedLands);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_BURNING_STEPPES, remainingBurningSteppes);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_EASTERN_PLAGUELANDS, remainingEasternPlaguelands);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_TANARIS, remainingTanaris);
pl->SendUpdateWorldState(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_WINTERSPRING, remainingWinterspring);
});
default:
break;
}
});
}
void WorldState::HandleDefendedZones()
{
if (m_siData.m_battlesWon < 50)
{
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_50_INVASIONS);
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_100_INVASIONS);
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_150_INVASIONS);
}
else if (m_siData.m_battlesWon >= 50 && m_siData.m_battlesWon < 100)
sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_50_INVASIONS);
else if (m_siData.m_battlesWon >= 100 && m_siData.m_battlesWon < 150)
{
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_50_INVASIONS);
sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_100_INVASIONS);
}
else if (m_siData.m_battlesWon >= 150)
{
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION);
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_50_INVASIONS);
sGameEventMgr->StopEvent(GAME_EVENT_SCOURGE_INVASION_100_INVASIONS);
sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_INVASIONS_DONE);
sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_150_INVASIONS);
}
}
void WorldState::StartZoneEvent(SIZoneIds eventId)
{
switch (eventId)
{
case SI_ZONE_AZSHARA: StartNewInvasion(AREA_AZSHARA); break;
case SI_ZONE_BLASTED_LANDS: StartNewInvasion(AREA_BLASTED_LANDS); break;
case SI_ZONE_BURNING_STEPPES: StartNewInvasion(AREA_BURNING_STEPPES); break;
case SI_ZONE_EASTERN_PLAGUELANDS: StartNewInvasion(AREA_EASTERN_PLAGUELANDS); break;
case SI_ZONE_TANARIS: StartNewInvasion(AREA_TANARIS); break;
case SI_ZONE_WINTERSPRING: StartNewInvasion(AREA_WINTERSPRING); break;
case SI_ZONE_STORMWIND: StartNewCityAttack(AREA_STORMWIND_CITY); break;
case SI_ZONE_UNDERCITY: StartNewCityAttack(AREA_UNDERCITY); break;
default:
break;
}
}
void WorldState::StartNewInvasionIfTime(uint32 attackTimeVar, uint32 zoneId)
{
TimePoint now = std::chrono::steady_clock::now();
// Not yet time
if (now < sWorldState->GetSITimer(SITimers(attackTimeVar)))
return;
StartNewInvasion(zoneId);
}
void WorldState::StartNewCityAttackIfTime(uint32 attackTimeVar, uint32 zoneId)
{
TimePoint now = std::chrono::steady_clock::now();
// Not yet time
if (now < sWorldState->GetSITimer(SITimers(attackTimeVar)))
return;
StartNewCityAttack(zoneId);
uint32 cityAttackTimer = urand(CITY_ATTACK_TIMER_MIN, CITY_ATTACK_TIMER_MAX);
TimePoint next_attack = now + std::chrono::seconds(cityAttackTimer);
sWorldState->SetSITimer(SITimers(attackTimeVar), next_attack);
}
void WorldState::StartNewInvasion(uint32 zoneId)
{
if (IsActiveZone(zoneId))
return;
// Don't attack same zone as before.
if (zoneId == sWorldState->GetLastAttackZone())
return;
// If we have at least one victory and more than 1 active zones stop here.
if (GetActiveZones() > 1 && sWorldState->GetBattlesWon() > 0)
return;
LOG_DEBUG("gameevent", "Scourge Invasion Event: Starting new invasion in {}.", zoneId);
ScourgeInvasionData::InvasionZone& zone = m_siData.m_activeInvasions[zoneId];
Map* map = GetMap(zone.map, zone.mouth[0]);
if (!map)
{
LOG_ERROR("gameevent", "ScourgeInvasionEvent::StartNewInvasion unable to access required map ({}). Retrying next update.", zone.map);
return;
}
switch (zoneId)
{
case AREA_AZSHARA: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_AZSHARA); break;
case AREA_BLASTED_LANDS: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_BLASTED_LANDS); break;
case AREA_BURNING_STEPPES: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_BURNING_STEPPES); break;
case AREA_EASTERN_PLAGUELANDS: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_EASTERN_PLAGUELANDS); break;
case AREA_TANARIS: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_TANARIS); break;
case AREA_WINTERSPRING: sGameEventMgr->StartEvent(GAME_EVENT_SCOURGE_INVASION_WINTERSPRING); break;
default:
LOG_ERROR("gameevent", "ScourgeInvasionEvent::StartNewInvasion unknown zoneId {}.", zoneId);
return;
}
if (map)
SummonMouth(map, zone, zone.mouth[0], true);
}
void WorldState::StartNewCityAttack(uint32 zoneId)
{
LOG_DEBUG("gameevent", "Scourge Invasion Event: Starting new City attack in zone {}.", zoneId);
ScourgeInvasionData::CityAttack& zone = m_siData.m_cityAttacks[zoneId];
uint32 SpawnLocationID = urand(0, static_cast<uint32>(zone.pallid.size() - 1));
Map* map = GetMap(zone.map, zone.pallid[SpawnLocationID]);
// If any of the required maps are not available we return. Will cause the invasion to be started
// on next update instead
if (!map)
{
LOG_ERROR("gameevent", "ScourgeInvasionEvent::StartNewCityAttackIfTime unable to access required map (%{}). Retrying next update.", zone.map);
return;
}
if (m_siData.m_pendingPallids.find(zoneId) != m_siData.m_pendingPallids.end())
return;
if (map && SummonPallid(map, zone, zone.pallid[SpawnLocationID], SpawnLocationID))
LOG_DEBUG("gameevent", "ScourgeInvasionEvent::StartNewCityAttackIfTime pallid spawned in {}.", zone.map);
else
LOG_DEBUG("gameevent", "ScourgeInvasionEvent::StartNewCityAttackIfTime unable to spawn pallid in {}.", zone.map);
}
bool WorldState::ResumeInvasion(ScourgeInvasionData::InvasionZone& zone)
{
// Dont have a save variable to know which necropolises had already been destroyed, so we
// just summon the same amount, but not necessarily the same necropolises
LOG_DEBUG("gameevent", "Scourge Invasion Event: Resuming Scourge invasion in zone {}", zone.zoneId);
uint32 num_necropolises_remaining = sWorldState->GetSIRemaining(SIRemaining(zone.remainingNecropoli));
// Just making sure we can access all maps before starting the invasion
for (uint32 i = 0; i < num_necropolises_remaining; i++)
{
if (!GetMap(zone.map, zone.mouth[0]))
{
LOG_ERROR("gameevent", "ScourgeInvasionEvent::ResumeInvasion map {} not accessible. Retry next update.", zone.map);
return false;
}
}
Map* mapPtr = GetMap(zone.map, zone.mouth[0]);
if (!mapPtr)
{
LOG_ERROR("gameevent", "ScourgeInvasionEvent::ResumeInvasion failed getting map, even after making sure they were loaded....");
return false;
}
SummonMouth(mapPtr, zone, zone.mouth[0], false);
return true;
}
bool WorldState::SummonMouth(Map* map, ScourgeInvasionData::InvasionZone& zone, Position position, bool newInvasion)
{
AddPendingInvasion(zone.zoneId);
// Remove old mouth if required.
if (Creature* existingMouth = map->GetCreature(zone.mouthGuid))
existingMouth->AddObjectToRemoveList();
if (Creature* mouth = map->SummonCreature(NPC_HERALD_OF_THE_LICH_KING, position))
{
mouth->GetAI()->DoAction(EVENT_HERALD_OF_THE_LICH_KING_ZONE_START);
sWorldState->SetMouthGuid(zone.zoneId, mouth->GetGUID());
if (newInvasion)
sWorldState->SetSIRemaining(SIRemaining(zone.remainingNecropoli), zone.necropolisCount);
}
sWorldState->RemovePendingInvasion(zone.zoneId);
return true;
}
enum PallidHorrorPaths
{
PATH_STORMWIND_KEEP = 163941,
PATH_STORMWIND_TRADE_DISTRICT = 163942,
PATH_UNDERCITY_TRADE_QUARTER = 163943,
PATH_UNDERCITY_ROYAL_QUARTER = 163944,
};
bool WorldState::SummonPallid(Map* map, ScourgeInvasionData::CityAttack& zone, const Position& position, uint32 spawnLoc)
{
AddPendingPallid(zone.zoneId);
// Remove old pallid if required.
uint32 pathID = 0;
if (Creature* existingPallid = map->GetCreature(zone.pallidGuid))
existingPallid->AddObjectToRemoveList();
// if (Creature* pallid = map->SummonCreature(RAND(NPC_PALLID_HORROR, NPC_PATCHWORK_TERROR), position))
if (Creature* pallid = map->SummonCreature(NPC_PALLID_HORROR, position))
{
pallid->GetMotionMaster()->Clear(false);
if (pallid->GetZoneId() == AREA_UNDERCITY)
pathID = spawnLoc == 0 ? PATH_UNDERCITY_ROYAL_QUARTER : PATH_UNDERCITY_TRADE_QUARTER;
else
pathID = spawnLoc == 0 ? PATH_STORMWIND_KEEP : PATH_STORMWIND_TRADE_DISTRICT;
pallid->GetMotionMaster()->MovePath(pathID, false);
sWorldState->SetPallidGuid(zone.zoneId, pallid->GetGUID());
}
sWorldState->RemovePendingPallid(zone.zoneId);
return true;
}
void WorldState::HandleActiveZone(uint32 attackTimeVar, uint32 zoneId, uint32 remainingVar, TimePoint now)
{
TimePoint timePoint = sWorldState->GetSITimer(SITimers(attackTimeVar));
ScourgeInvasionData::InvasionZone& zone = m_siData.m_activeInvasions[zoneId];
Map* map = sMapMgr->FindMap(zone.map, 0);
if (zone.zoneId != zoneId)
return;
uint32 remaining = GetSIRemaining(SIRemaining(remainingVar));
// Calculate the next possible attack between ZONE_ATTACK_TIMER_MIN and ZONE_ATTACK_TIMER_MAX.
uint32 zoneAttackTimer = urand(ZONE_ATTACK_TIMER_MIN, ZONE_ATTACK_TIMER_MAX);
TimePoint next_attack = now + std::chrono::seconds(zoneAttackTimer);
uint64 timeToNextAttack = std::chrono::duration_cast<std::chrono::minutes>(next_attack-now).count();
if (zone.mouthGuid)
{
// Handles the inactive zone, without a Mouth of Kel'Thuzad summoned (which spawns the whole zone event).
Creature* mouth = map->GetCreature(zone.mouthGuid);
if (!mouth)
sWorldState->SetMouthGuid(zone.zoneId, ObjectGuid()); // delays spawning until next tick
// Handles the active zone that has no necropolis left.
else if (timePoint < now && remaining == 0)
{
sWorldState->SetSITimer(SITimers(attackTimeVar), next_attack);
sWorldState->AddBattlesWon(1);
sWorldState->SetLastAttackZone(zoneId);
LOG_INFO("gameevent", "[Scourge Invasion Event] The Scourge has been defeated in {}, next attack starting in {} minutes.", zoneId, timeToNextAttack);
LOG_DEBUG("gameevent", "[Scourge Invasion Event] {} victories", sWorldState->GetBattlesWon());
if (mouth)
mouth->GetAI()->DoAction(EVENT_HERALD_OF_THE_LICH_KING_ZONE_STOP);
else
LOG_ERROR("gameevent", "ScourgeInvasionEvent::HandleActiveZone ObjectGuid {} not found.", zone.mouthGuid.ToString());
}
}
else
{
// If more than one zones are alreay being attacked, set the timer again to ZONE_ATTACK_TIMER.
if (GetActiveZones() > 1)
sWorldState->SetSITimer(SITimers(attackTimeVar), next_attack);
// Try to start the zone if attackTimeVar is 0.
StartNewInvasionIfTime(attackTimeVar, zoneId);
}
}
void WorldState::SetPallidGuid(uint32 zoneId, ObjectGuid guid)
{
m_siData.m_cityAttacks[zoneId].pallidGuid = guid;
}
void WorldState::SetMouthGuid(uint32 zoneId, ObjectGuid guid)
{
m_siData.m_activeInvasions[zoneId].mouthGuid = guid;
}
void WorldState::AddPendingInvasion(uint32 zoneId)
{
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
m_siData.m_pendingInvasions.insert(zoneId);
}
void WorldState::RemovePendingInvasion(uint32 zoneId)
{
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
m_siData.m_pendingInvasions.erase(zoneId);
}
void WorldState::AddPendingPallid(uint32 zoneId)
{
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
m_siData.m_pendingPallids.insert(zoneId);
}
void WorldState::RemovePendingPallid(uint32 zoneId)
{
std::lock_guard<std::mutex> guard(m_siData.m_siMutex);
m_siData.m_pendingPallids.erase(zoneId);
}
void WorldState::OnEnable(ScourgeInvasionData::InvasionZone& zone)
{
// If there were remaining necropolises in the old zone before shutdown, we
// restore that zone
if (sWorldState->GetSIRemaining(SIRemaining(zone.remainingNecropoli)) > 0)
ResumeInvasion(zone);
// Otherwise we start a new Invasion
else
StartNewInvasionIfTime(GetTimerIdForZone(zone.zoneId), zone.zoneId);
}
void WorldState::OnDisable(ScourgeInvasionData::InvasionZone& zone)
{
if (!zone.mouthGuid)
return;
Map* map = GetMap(zone.map, zone.mouth[0]);
if (Creature* mouth = map->GetCreature(zone.mouthGuid))
mouth->DespawnOrUnsummon();
}
void WorldState::OnDisable(ScourgeInvasionData::CityAttack& zone)
{
if (!zone.pallidGuid)
return;
Map* map = GetMap(zone.map, zone.pallid[0]);
if (Creature* pallid = map->GetCreature(zone.pallidGuid))
pallid->DespawnOrUnsummon();
}
bool WorldState::IsActiveZone(uint32 /*zoneId*/)
{
return false;
}
// returns the amount of pending zones and active zones with a mouth creature on the map
uint32 WorldState::GetActiveZones()
{
size_t i = m_siData.m_pendingInvasions.size();
for (auto const& [zoneId, invasionData] : m_siData.m_activeInvasions)
{
Map* map = GetMap(invasionData.map, invasionData.mouth[0]);
if (!map)
{
LOG_ERROR("gameevent", "ScourgeInvasionEvent::GetActiveZones no map for zone {}.", invasionData.map);
continue;
}
Creature* mouth = map->GetCreature(invasionData.mouthGuid);
if (mouth)
i++;
}
return i;
}
uint32 WorldState::GetTimerIdForZone(uint32 zoneId)
{
uint32 attackTime = 0;
switch (zoneId)
{
case AREA_TANARIS: attackTime = SI_TIMER_TANARIS; break;
case AREA_BLASTED_LANDS: attackTime = SI_TIMER_BLASTED_LANDS; break;
case AREA_EASTERN_PLAGUELANDS: attackTime = SI_TIMER_EASTERN_PLAGUELANDS; break;
case AREA_BURNING_STEPPES: attackTime = SI_TIMER_BURNING_STEPPES; break;
case AREA_WINTERSPRING: attackTime = SI_TIMER_WINTERSPRING; break;
case AREA_AZSHARA: attackTime = SI_TIMER_AZSHARA; break;
default:
LOG_ERROR("gameevent", "ScourgeInvasionEvent::GetTimerIdForZone unknown zoneId {}.", zoneId);
return 0;
}
return attackTime;
}
void WorldState::FillInitialWorldStates(WorldPackets::WorldState::InitWorldStates& packet, uint32 zoneId, uint32 /*areaId*/)
{
if (m_siData.m_state != STATE_0_DISABLED) // scourge invasion active - need to send all worldstates
{
uint32 victories = GetBattlesWon();
uint32 remainingAzshara = GetSIRemaining(SI_REMAINING_AZSHARA);
uint32 remainingBlastedLands = GetSIRemaining(SI_REMAINING_BLASTED_LANDS);
uint32 remainingBurningSteppes = GetSIRemaining(SI_REMAINING_BURNING_STEPPES);
uint32 remainingEasternPlaguelands = GetSIRemaining(SI_REMAINING_EASTERN_PLAGUELANDS);
uint32 remainingTanaris = GetSIRemaining(SI_REMAINING_TANARIS);
uint32 remainingWinterspring = GetSIRemaining(SI_REMAINING_WINTERSPRING);
packet.Worldstates.reserve(13);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_AZSHARA, remainingAzshara > 0 ? 1 : 0);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_BLASTED_LANDS, remainingBlastedLands > 0 ? 1 : 0);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_BURNING_STEPPES, remainingBurningSteppes > 0 ? 1 : 0);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_EASTERN_PLAGUELANDS, remainingEasternPlaguelands > 0 ? 1 : 0);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_TANARIS, remainingTanaris > 0 ? 1 : 0);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_WINTERSPRING, remainingWinterspring > 0 ? 1 : 0);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_VICTORIES, victories);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_AZSHARA, remainingAzshara);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_BLASTED_LANDS, remainingBlastedLands);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_BURNING_STEPPES, remainingBurningSteppes);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_EASTERN_PLAGUELANDS, remainingEasternPlaguelands);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_TANARIS, remainingTanaris);
packet.Worldstates.emplace_back(WORLD_STATE_SCOURGE_INVASION_NECROPOLIS_WINTERSPRING, remainingWinterspring);
}
switch (zoneId)
{
case AREA_ISLE_OF_QUEL_DANAS:
@@ -1136,5 +2007,7 @@ void WorldState::FillInitialWorldStates(WorldPackets::WorldState::InitWorldState
}
break;
}
default:
break;
}
}
+176 -2
View File
@@ -20,6 +20,7 @@
#include "AreaDefines.h"
#include "Player.h"
#include "WorldStateDefines.h"
#include <atomic>
enum WorldStateCondition
@@ -56,11 +57,33 @@ enum WorldStateSpells
enum WorldStateSaveIds
{
SAVE_ID_SCOURGE_INVASION = 3,
SAVE_ID_QUEL_DANAS = 20,
};
enum WorldStateGameEvents
{
// Scourge Invasion
GAME_EVENT_SCOURGE_INVASION = 17,
GAME_EVENT_SCOURGE_INVASION_WINTERSPRING = 121,
GAME_EVENT_SCOURGE_INVASION_TANARIS = 122,
GAME_EVENT_SCOURGE_INVASION_AZSHARA = 123,
GAME_EVENT_SCOURGE_INVASION_BLASTED_LANDS = 124,
GAME_EVENT_SCOURGE_INVASION_EASTERN_PLAGUELANDS = 125,
GAME_EVENT_SCOURGE_INVASION_BURNING_STEPPES = 126,
GAME_EVENT_SCOURGE_INVASION_50_INVASIONS = 127,
GAME_EVENT_SCOURGE_INVASION_100_INVASIONS = 128,
GAME_EVENT_SCOURGE_INVASION_150_INVASIONS = 129,
GAME_EVENT_SCOURGE_INVASION_INVASIONS_DONE = 130,
// Zombie infestation
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_1 = 110,
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_2 = 111,
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_3 = 112,
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_4 = 113,
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_5 = 114,
GAME_EVENT_ZOMBIE_INFESTATION_PHASE_6 = 115,
// Isle phases
GAME_EVENT_QUEL_DANAS_PHASE_1 = 101,
GAME_EVENT_QUEL_DANAS_PHASE_2_ONLY = 102,
@@ -84,6 +107,110 @@ enum WorldStateGameEvents
GAME_EVENT_SWP_GATES_PHASE_3 = 119, // All Gates Open
};
enum SIMisc
{
EVENT_HERALD_OF_THE_LICH_KING_ZONE_START = 7,
EVENT_HERALD_OF_THE_LICH_KING_ZONE_STOP = 8,
NPC_PALLID_HORROR = 16394,
NPC_PATCHWORK_TERROR = 16382,
NPC_HERALD_OF_THE_LICH_KING = 16995,
ITEM_A_LETTER_FROM_THE_KEEPER_OF_THE_ROLLS = 22723,
NPC_ARGENT_EMISSARY = 16285,
MAIL_TEMPLATE_ARGENT_DAWN_NEEDS_YOUR_HELP = 171,
};
enum SIState : uint32
{
STATE_0_DISABLED,
STATE_1_ENABLED,
SI_STATE_MAX,
};
enum SIZoneIds
{
SI_ZONE_AZSHARA,
SI_ZONE_BLASTED_LANDS,
SI_ZONE_BURNING_STEPPES,
SI_ZONE_EASTERN_PLAGUELANDS,
SI_ZONE_TANARIS,
SI_ZONE_WINTERSPRING,
SI_ZONE_STORMWIND,
SI_ZONE_UNDERCITY
};
enum SITimers
{
SI_TIMER_AZSHARA,
SI_TIMER_BLASTED_LANDS,
SI_TIMER_BURNING_STEPPES,
SI_TIMER_EASTERN_PLAGUELANDS,
SI_TIMER_TANARIS,
SI_TIMER_WINTERSPRING,
SI_TIMER_STORMWIND,
SI_TIMER_UNDERCITY,
SI_TIMER_MAX,
};
enum SIRemaining
{
SI_REMAINING_AZSHARA,
SI_REMAINING_BLASTED_LANDS,
SI_REMAINING_BURNING_STEPPES,
SI_REMAINING_EASTERN_PLAGUELANDS,
SI_REMAINING_TANARIS,
SI_REMAINING_WINTERSPRING,
SI_REMAINING_MAX,
};
enum SICityTimers
{
// These timers may fail if you set it under 1 minute.
ZONE_ATTACK_TIMER_MIN = 60 * 45, // 45 min.
ZONE_ATTACK_TIMER_MAX = 60 * 60, // 60 min.
CITY_ATTACK_TIMER_MIN = 60 * 45, // 45 min.
CITY_ATTACK_TIMER_MAX = 60 * 60, // 60 min.
};
struct ScourgeInvasionData
{
struct InvasionZone
{
uint32 map;
uint32 zoneId;
uint32 necropolisCount;
uint32 remainingNecropoli;
std::vector<Position> mouth;
ObjectGuid mouthGuid {};
};
struct CityAttack
{
uint32 map;
uint32 zoneId;
std::vector<Position> pallid;
ObjectGuid pallidGuid {};
};
SIState m_state;
TimePoint m_timers[SI_TIMER_MAX];
uint32 m_battlesWon;
uint32 m_lastAttackZone;
uint32 m_remaining[SI_REMAINING_MAX];
uint64 m_broadcastTimer;
std::mutex m_siMutex;
std::set<uint32> m_pendingInvasions;
std::set<uint32> m_pendingPallids;
std::map<uint32, InvasionZone> m_activeInvasions;
std::map<uint32, CityAttack> m_cityAttacks;
ScourgeInvasionData();
void Reset();
std::string GetData();
};
enum SunsReachPhases
{
SUNS_REACH_PHASE_1_STAGING_AREA,
@@ -166,7 +293,7 @@ class WorldState
void SaveHelper(std::string& stringToSave, WorldStateSaveIds saveId);
void HandlePlayerEnterZone(Player* player, AreaTableIDs zoneId);
void HandlePlayerLeaveZone(Player* player, AreaTableIDs zoneId);
bool IsConditionFulfilled(WorldStateCondition conditionId, WorldStateConditionState state = WORLD_STATE_CONDITION_STATE_NONE) const;
bool IsConditionFulfilled(uint32 conditionId, uint32 state = WORLD_STATE_CONDITION_STATE_NONE) const;
void HandleConditionStateChange(WorldStateCondition conditionId, WorldStateConditionState state);
void HandleExternalEvent(WorldStateEvent eventId, uint32 param);
void Update(uint32 diff);
@@ -195,8 +322,55 @@ class WorldState
bool _isMagtheridonHeadSpawnedHorde;
bool _isMagtheridonHeadSpawnedAlliance;
SunsReachReclamationData m_sunsReachData;
std::map<WorldStateCondition, std::atomic<WorldStateConditionState>> _transportStates; // atomic to avoid having to lock
std::map<uint32, std::atomic<WorldStateConditionState>> _transportStates; // atomic to avoid having to lock
std::mutex _mutex; // all World State operations are threat unsafe
// Scourge Invasion
public:
void SetScourgeInvasionState(SIState state);
void StartScourgeInvasion(bool sendMail);
void StopScourgeInvasion();
[[nodiscard]] uint32 GetSIRemaining(SIRemaining remaining) const;
[[nodiscard]] uint32 GetSIRemainingByZone(uint32 zoneId) const;
void SetSIRemaining(SIRemaining remaining, uint32 value);
TimePoint GetSITimer(SITimers timer);
void SetSITimer(SITimers timer, TimePoint timePoint);
uint32 GetBattlesWon();
void AddBattlesWon(int32 count);
uint32 GetLastAttackZone();
void SetLastAttackZone(uint32 zoneId);
void BroadcastSIWorldstates();
void HandleDefendedZones();
std::string GetScourgeInvasionPrintout();
void StartZoneEvent(SIZoneIds eventId);
void StartNewInvasionIfTime(uint32 attackTimeVar, uint32 zoneId);
void StartNewCityAttackIfTime(uint32 attackTimeVar, uint32 zoneId);
void StartNewInvasion(uint32 zoneId);
void StartNewCityAttack(uint32 zoneId);
bool ResumeInvasion(ScourgeInvasionData::InvasionZone& zone);
bool SummonMouth(Map* map, ScourgeInvasionData::InvasionZone& zone, Position position, bool newInvasion);
bool SummonPallid(Map* map, ScourgeInvasionData::CityAttack& zone, const Position& position, uint32 spawnLoc);
void HandleActiveZone(uint32 attackTimeVar, uint32 zoneId, uint32 remainingVar, TimePoint now);
Map* GetMap(uint32 mapId, Position const& invZone);
bool IsActiveZone(uint32 zoneId);
uint32 GetActiveZones();
uint32 GetTimerIdForZone(uint32 zoneId);
void SetPallidGuid(uint32 zoneId, ObjectGuid guid);
void SetMouthGuid(uint32 zoneId, ObjectGuid guid);
void AddPendingInvasion(uint32 zoneId);
void RemovePendingInvasion(uint32 zoneId);
void AddPendingPallid(uint32 zoneId);
void RemovePendingPallid(uint32 zoneId);
void OnEnable(ScourgeInvasionData::InvasionZone& zone);
void OnDisable(ScourgeInvasionData::InvasionZone& zone);
void OnDisable(ScourgeInvasionData::CityAttack& zone);
private:
void SendScourgeInvasionMail();
ScourgeInvasionData m_siData;
};
#define sWorldState WorldState::instance()
+50 -1
View File
@@ -39,9 +39,18 @@ public:
{ "gatecounter", HandleSunwellGateCounterCommand, SEC_ADMINISTRATOR, Console::Yes },
};
static ChatCommandTable scourgeInvasionCommandTable =
{
{ "show", HandleScourgeInvasionCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "state", HandleScourgeInvasionStateCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "battleswon", HandleScourgeInvasionBattlesWonCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "startzone", HandleScourgeInvasionStartZone, SEC_ADMINISTRATOR, Console::Yes },
};
static ChatCommandTable worldStateCommandTable =
{
{ "sunsreach", sunsreachCommandTable }
{ "sunsreach", sunsreachCommandTable },
{ "scourgeinvasion", scourgeInvasionCommandTable }
};
static ChatCommandTable commandTable =
@@ -118,6 +127,46 @@ public:
handler->PSendSysMessage(sWorldState->GetSunsReachPrintout());
return true;
}
static bool HandleScourgeInvasionCommand(ChatHandler* handler)
{
handler->PSendSysMessage(sWorldState->GetScourgeInvasionPrintout());
return true;
}
static bool HandleScourgeInvasionStateCommand(ChatHandler* handler, uint32 value)
{
if (value >= SI_STATE_MAX)
{
handler->PSendSysMessage("Syntax: .worldstate scourgeinvasion state <value>.");
handler->PSendSysMessage("Valid values are: 0 (Disabled), 1 (Enabled).");
return true;
}
sWorldState->SetScourgeInvasionState(SIState(value));
handler->PSendSysMessage("Scourge Invasion state set to {}.", value);
handler->PSendSysMessage(sWorldState->GetScourgeInvasionPrintout());
return true;
}
static bool HandleScourgeInvasionBattlesWonCommand(ChatHandler* /* handler */, int32 value)
{
sWorldState->AddBattlesWon(value);
return true;
}
static bool HandleScourgeInvasionStartZone(ChatHandler* handler, uint32 value)
{
if (value >= SI_TIMER_MAX)
{
handler->PSendSysMessage("Syntax: .worldstate scourgeinvasion startzone <value>.\nvalid values: 0-7");
return true;
}
sWorldState->StartZoneEvent(SIZoneIds(value));
handler->PSendSysMessage("Scourge Invasion event started for zone {}.", value);
handler->PSendSysMessage(sWorldState->GetScourgeInvasionPrintout());
return true;
}
};
void AddSC_worldstate_commandscript()
@@ -28,161 +28,6 @@
#include "ObjectAccessor.h"
#include <limits>
//How to win friends and influence enemies
// texts signed for creature 28939 but used for 28939, 28940, 28610
enum win_friends
{
SAY_AGGRO = 0,
SAY_CRUSADER = 1,
SAY_PERSUADED1 = 2,
SAY_PERSUADED2 = 3,
SAY_PERSUADED3 = 4,
SAY_PERSUADED4 = 5,
SAY_PERSUADED5 = 6,
SAY_PERSUADED6 = 7,
SAY_PERSUADE_RAND = 8,
SPELL_PERSUASIVE_STRIKE = 52781,
SPELL_THREAT_PULSE = 58111,
QUEST_HOW_TO_WIN_FRIENDS = 12720,
};
class npc_crusade_persuaded : public CreatureScript
{
public:
npc_crusade_persuaded() : CreatureScript("npc_crusade_persuaded") { }
CreatureAI* GetAI(Creature* creature) const override
{
return new npc_crusade_persuadedAI(creature);
}
struct npc_crusade_persuadedAI : public CombatAI
{
npc_crusade_persuadedAI(Creature* creature) : CombatAI(creature) { }
const uint32 SAY_AGGRO_CHANCE = 33;
const uint32 PERSUADE_SUCCESS_CHANCE = 3; // 30% chance
const uint32 SPEECH_TIMER_DEFAULT = 1000;
const uint32 SPEECH_TIMER_FOR_ROLEPLAY = 8000;
const uint32 SPEECH_COUNTER_PREVENT_SUCCESS_ROLEPLAY = 0;
const uint32 SPEECH_COUNTER_START_SUCCESS_ROLEPLAY = 1;
uint32 speechTimer;
uint32 speechCounter;
ObjectGuid playerGUID;
bool persuaded;
void Reset() override
{
speechTimer = 0;
speechCounter = SPEECH_COUNTER_PREVENT_SUCCESS_ROLEPLAY;
playerGUID.Clear();
me->SetReactState(REACT_AGGRESSIVE);
me->RestoreFaction();
}
void JustEngagedWith(Unit*) override
{
if (roll_chance_i(SAY_AGGRO_CHANCE))
Talk(SAY_AGGRO);
}
void SpellHit(Unit* caster, SpellInfo const* spell) override
{
if (spell->Id == SPELL_PERSUASIVE_STRIKE && caster->IsPlayer() && me->IsAlive() && !speechCounter)
{
if (Player* player = caster->ToPlayer())
{
playerGUID = player->GetGUID();
speechTimer = SPEECH_TIMER_DEFAULT;
speechCounter = SPEECH_COUNTER_START_SUCCESS_ROLEPLAY;
uint32 persuadeRoll = urand(1, 10);
sCreatureTextMgr->SendChat(me, SAY_PERSUADE_RAND, nullptr, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_NEUTRAL, false, player);
if (persuadeRoll <= PERSUADE_SUCCESS_CHANCE && player->GetQuestStatus(QUEST_HOW_TO_WIN_FRIENDS) == QUEST_STATUS_INCOMPLETE)
{
persuaded = true;
me->SetFaction(player->GetFaction());
me->CombatStop(true);
me->GetMotionMaster()->MoveIdle();
me->SetReactState(REACT_PASSIVE);
}
else
persuaded = false;
}
}
}
void UpdateAI(uint32 diff) override
{
if (speechCounter)
{
if (speechTimer <= diff)
{
Player* player = ObjectAccessor::GetPlayer(*me, playerGUID);
if (!player)
{
EnterEvadeMode();
return;
}
if (persuaded)
{
switch (speechCounter)
{
case 1:
Talk(SAY_PERSUADED1);
speechTimer = SPEECH_TIMER_FOR_ROLEPLAY;
break;
case 2:
Talk(SAY_PERSUADED2);
speechTimer = SPEECH_TIMER_FOR_ROLEPLAY;
break;
case 3:
Talk(SAY_PERSUADED3);
speechTimer = SPEECH_TIMER_FOR_ROLEPLAY;
break;
case 4:
Talk(SAY_PERSUADED4);
speechTimer = SPEECH_TIMER_FOR_ROLEPLAY;
break;
case 5:
sCreatureTextMgr->SendChat(me, SAY_PERSUADED5, nullptr, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_NEUTRAL, false, player);
speechTimer = SPEECH_TIMER_FOR_ROLEPLAY;
break;
case 6:
Talk(SAY_PERSUADED6);
Unit::Kill(player, me);
speechCounter = SPEECH_COUNTER_PREVENT_SUCCESS_ROLEPLAY;
player->GroupEventHappens(QUEST_HOW_TO_WIN_FRIENDS, me);
return;
}
++speechCounter;
}
else
{
Talk(SAY_CRUSADER);
speechCounter = SPEECH_COUNTER_PREVENT_SUCCESS_ROLEPLAY;
}
}
else
speechTimer = (speechTimer > diff) ? (speechTimer - diff) : 0;
return;
}
CombatAI::UpdateAI(diff);
}
};
};
/*######
## npc_koltira_deathweaver
######*/
@@ -1098,12 +943,135 @@ public:
}
};
//How to win friends and influence enemies
// texts signed for creature 28939 but used for 28939, 28940, 28610
enum win_friends
{
SAY_CRUSADER = 1,
SAY_PERSUADED1 = 2,
SAY_PERSUADED2 = 3,
SAY_PERSUADED3 = 4,
SAY_PERSUADED4 = 5,
SAY_PERSUADED5 = 6,
SAY_PERSUADED6 = 7,
SAY_PERSUADE_RAND = 8,
QUEST_HOW_TO_WIN_FRIENDS = 12720,
NPC_SCARLET_PREACHER = 28939,
NPC_SCARLET_COMMANDER = 28936,
NPC_SCARLET_CRUSADER = 28940,
NPC_SCARLET_MARKSMAN = 28610,
NPC_SCARLET_LORD_MCCREE = 28964
};
// 52781 - Persuasive Strike
class spell_chapter2_persuasive_strike : public SpellScript
{
PrepareSpellScript(spell_chapter2_persuasive_strike);
bool Load() override
{
return GetCaster() && GetCaster()->IsPlayer()
&& GetCaster()->ToPlayer()->GetQuestStatus(QUEST_HOW_TO_WIN_FRIENDS) == QUEST_STATUS_INCOMPLETE;
}
void HandleHit(SpellEffIndex /*effIndex*/)
{
Creature* creature = GetHitCreature();
Player* player = GetCaster()->ToPlayer();
if (!creature || !player)
return;
if (!creature->EntryEquals(NPC_SCARLET_PREACHER, NPC_SCARLET_COMMANDER, NPC_SCARLET_CRUSADER, NPC_SCARLET_MARKSMAN, NPC_SCARLET_LORD_MCCREE))
return;
sCreatureTextMgr->SendChat(creature, SAY_PERSUADE_RAND, nullptr, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_NEUTRAL, false, player);
if (roll_chance_f(30.0f))
{
creature->CombatStop(true);
creature->GetMotionMaster()->MoveIdle();
creature->SetImmuneToPC(true);
creature->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
creature->SetReactState(REACT_PASSIVE);
creature->AI()->Talk(SAY_PERSUADED1, 8s);
creature->AI()->Talk(SAY_PERSUADED2, 16s);
creature->AI()->Talk(SAY_PERSUADED3, 24s);
creature->AI()->Talk(SAY_PERSUADED4, 32s);
creature->m_Events.AddEventAtOffset([creature, player]
{
if (player)
sCreatureTextMgr->SendChat(creature, SAY_PERSUADED5, nullptr, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_NEUTRAL, false, player);
}, 40s);
creature->m_Events.AddEventAtOffset([creature, player]
{
creature->AI()->Talk(SAY_PERSUADED6);
if (player)
{
Unit::Kill(player, creature);
player->GroupEventHappens(QUEST_HOW_TO_WIN_FRIENDS, creature);
}
}, 48s);
}
else
creature->AI()->Talk(SAY_CRUSADER, 1s);
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_chapter2_persuasive_strike::HandleHit, EFFECT_0, SPELL_EFFECT_DUMMY);
}
};
enum AcherusPortal
{
SPELL_PORTAL_EFFECT_ACHERUS = 53098,
QUEST_SCARLET_ARMIES_APPROACH = 12757
};
class spell_portal_effect_acherus : public SpellScript
{
PrepareSpellScript(spell_portal_effect_acherus);
bool Validate(SpellInfo const* /*spellInfo*/) override
{
return ValidateSpellInfo({ SPELL_PORTAL_EFFECT_ACHERUS });
}
SpellCastResult CheckCast()
{
Unit* target = GetExplTargetUnit();
if (target && target->IsPlayer() && target->ToPlayer()->HasQuest(QUEST_SCARLET_ARMIES_APPROACH))
return SPELL_CAST_OK;
return SPELL_FAILED_DONT_REPORT;
}
void HandleScriptEffect(SpellEffIndex /*effIndex*/)
{
if (Unit* caster = GetCaster())
if (Player* player = GetHitPlayer())
caster->CastSpell(player, GetEffectValue(), true);
}
void Register() override
{
OnCheckCast += SpellCheckCastFn(spell_portal_effect_acherus::CheckCast);
OnEffectHitTarget += SpellEffectFn(spell_portal_effect_acherus::HandleScriptEffect, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
}
};
void AddSC_the_scarlet_enclave_c2()
{
new npc_crusade_persuaded();
new npc_scarlet_courier();
new npc_koltira_deathweaver();
new npc_a_special_surprise();
new npc_acherus_necromancer();
new npc_gothik_the_harvester();
RegisterSpellScript(spell_chapter2_persuasive_strike);
RegisterSpellScript(spell_portal_effect_acherus);
}
@@ -548,6 +548,7 @@ public:
summons.DespawnAll();
me->SetImmuneToAll(true);
me->LoadEquipment(1, true);
me->ReplaceAllNpcFlags(UNIT_NPC_FLAG_GOSSIP | UNIT_NPC_FLAG_QUESTGIVER);
me->SetStandState(UNIT_STAND_STATE_STAND);
me->SetVisible(true);
@@ -699,6 +700,7 @@ public:
{
summon->CombatStop(true);
summon->GetThreatMgr().ClearAllThreat();
summon->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
summon->SetImmuneToAll(true);
summon->SetReactState(REACT_PASSIVE);
summon->GetMotionMaster()->Clear(false);
@@ -949,6 +951,7 @@ public:
if (summon->GetEntry() <= NPC_RIMBLAT_EARTHSHATTER && summon->GetEntry() != NPC_HIGHLORD_TIRION_FORDRING)
{
float o = lk->GetAngle(summon);
summon->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
summon->GetMotionMaster()->MovePoint(3, lk->GetPositionX() + 2.0f * cos(o), lk->GetPositionY() + 2.0f * std::sin(o), lk->GetPositionZ());
summon->ToTempSummon()->SetTempSummonType(TEMPSUMMON_MANUAL_DESPAWN);
}
+28 -12
View File
@@ -25,17 +25,29 @@
## npcs_riverbreeze_and_silversky
######*/
#define GOSSIP_ITEM_BEACON "Please make me a Cenarion Beacon"
enum RiverbreezeAndSilversky
{
SPELL_CENARION_BEACON = 15120,
ITEM_CENARION_BEACON = 11511,
ACTION_CREATE_CENARION_BEACON = GOSSIP_ACTION_INFO_DEF + 1,
NPC_ARATHANDRIS_SILVERSKY = 9528,
NPC_MAYBESS_RIVERBREEZE = 9529,
QUEST_CLEASING_FELWOOD_A = 4101,
QUEST_CLEASING_FELWOOD_H = 4102
QUEST_CLEASING_FELWOOD_H = 4102,
// Texts
GOSSIP_MENU_SILVERSKY = 2208,
GOSSIP_MENU_RIVERBREEZE = 21400,
GOSSIP_OPTION_BEACON = 0,
TEXT_SILVERSKY_1 = 2848,
TEXT_SILVERSKY_2 = 2845,
TEXT_SILVERSKY_3 = 2844,
TEXT_RIVERBREEZE_1 = 2849,
TEXT_RIVERBREEZE_2 = 2843,
TEXT_RIVERBREEZE_3 = 2842,
};
class npcs_riverbreeze_and_silversky : public CreatureScript
@@ -59,32 +71,36 @@ public:
if (creature->IsQuestGiver())
player->PrepareQuestMenu(creature->GetGUID());
uint32 creatureId = creature->GetEntry();
uint32 const creatureId = creature->GetEntry();
if (creatureId == NPC_ARATHANDRIS_SILVERSKY)
{
if (player->GetQuestRewardStatus(QUEST_CLEASING_FELWOOD_A))
{
AddGossipItemFor(player, GOSSIP_ICON_CHAT, GOSSIP_ITEM_BEACON, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1);
SendGossipMenuFor(player, 2848, creature->GetGUID());
if (!player->HasItemCount(ITEM_CENARION_BEACON, 1, true))
AddGossipItemFor(player, GOSSIP_MENU_SILVERSKY, GOSSIP_OPTION_BEACON, GOSSIP_SENDER_MAIN, ACTION_CREATE_CENARION_BEACON);
SendGossipMenuFor(player, TEXT_SILVERSKY_1, creature->GetGUID());
}
else if (player->GetTeamId() == TEAM_HORDE)
SendGossipMenuFor(player, 2845, creature->GetGUID());
SendGossipMenuFor(player, TEXT_SILVERSKY_2, creature->GetGUID());
else
SendGossipMenuFor(player, 2844, creature->GetGUID());
SendGossipMenuFor(player, TEXT_SILVERSKY_3, creature->GetGUID());
}
if (creatureId == NPC_MAYBESS_RIVERBREEZE)
{
if (player->GetQuestRewardStatus(QUEST_CLEASING_FELWOOD_H))
{
AddGossipItemFor(player, GOSSIP_ICON_CHAT, GOSSIP_ITEM_BEACON, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1);
SendGossipMenuFor(player, 2849, creature->GetGUID());
if (!player->HasItemCount(ITEM_CENARION_BEACON, 1, true))
AddGossipItemFor(player, GOSSIP_MENU_RIVERBREEZE, GOSSIP_OPTION_BEACON, GOSSIP_SENDER_MAIN, ACTION_CREATE_CENARION_BEACON);
SendGossipMenuFor(player, TEXT_RIVERBREEZE_1, creature->GetGUID());
}
else if (player->GetTeamId() == TEAM_ALLIANCE)
SendGossipMenuFor(player, 2843, creature->GetGUID());
SendGossipMenuFor(player, TEXT_RIVERBREEZE_2, creature->GetGUID());
else
SendGossipMenuFor(player, 2842, creature->GetGUID());
SendGossipMenuFor(player, TEXT_RIVERBREEZE_3, creature->GetGUID());
}
return true;
@@ -1347,6 +1347,27 @@ class spell_hun_target_self_and_pet : public SpellScript
}
};
// -53301 - Explosive Shot
class spell_hun_explosive_shot : public SpellScript
{
PrepareSpellScript(spell_hun_explosive_shot);
void HandleFinish()
{
// Handling of explosive shot initial cast without LnL proc
if (!GetCaster() || !GetCaster()->IsPlayer())
return;
if (!GetCaster()->HasAura(SPELL_LOCK_AND_LOAD_TRIGGER))
GetSpell()->TakeAmmo();
}
void Register() override
{
AfterCast += SpellCastFn(spell_hun_explosive_shot::HandleFinish);
}
};
void AddSC_hunter_spell_scripts()
{
RegisterSpellScript(spell_hun_check_pet_los);
@@ -1378,4 +1399,5 @@ void AddSC_hunter_spell_scripts()
RegisterSpellScript(spell_hun_intimidation);
RegisterSpellScript(spell_hun_bestial_wrath);
RegisterSpellScript(spell_hun_target_self_and_pet);
RegisterSpellScript(spell_hun_explosive_shot);
}
File diff suppressed because it is too large Load Diff
+427
View File
@@ -0,0 +1,427 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by the
* Free Software Foundation; either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SCOURGE_INVASION_H
#define SCOURGE_INVASION_H
enum ScourgeInvasionSpells
{
SPELL_SPIRIT_PARTICLES_PURPLE = 28126, // Purple Minions Aura.
// GameObject Necropolis
SPELL_SUMMON_NECROPOLIS_CRITTERS = 27866, // Spawns NPCs Necropolis Health and Necropolis.
// Necropolis Health -> Necropolis
SPELL_DESPAWNER_OTHER = 28349, // Casted by the NPC "Necropolis health" after getting hit by,
// on the NPC "Necropolis" which destroys itself and the Necropolis Object.
// Necropolis Health
SPELL_ZAP_NECROPOLIS = 28386, // There are always 3 Necrotic Shards spawns per Necropolis. This Spell is castet on the NPC "Necropolis Health" if a Shard dies and does 40 Physical damage.
// NPC "Necropolis Health" has 42 health. 42 health / 3 Shards = 14 damage.
// We have set the armor value from the NPC "Necropolis Health" to 950 to reduce the damage from 40 to 14.
// Necropolis -> Proxy
SPELL_COMMUNIQUE_TIMER_NECROPOLIS = 28395, // Periodically triggers 28373 Communique, Necropolis-to-Proxies every 15 seconds.
SPELL_COMMUNIQUE_NECROPOLIS_TO_PROXIES = 28373, // purple bolt Visual (BIG).
// Proxy -> Necropolis
SPELL_COMMUNIQUE_PROXY_TO_NECROPOLIS = 28367, // Purple bolt Visual (SMALL).
// Proxy -> Relay
SPELL_COMMUNIQUE_PROXY_TO_RELAY = 28366, // purple bolt Visual (BIG).
// Relay -> Proxy
SPELL_COMMUNIQUE_RELAY_TO_PROXY = 28365, // Purple bolt Visual (SMALL).
// Relay -> Shard
SPELL_COMMUNIQUE_RELAY_TO_CAMP = 28326, // Purple bolt Visual (BIG).
// Shard
SPELL_CREATE_CRYSTAL = 28344, // Spawn a Necrotic Shard.
SPELL_CREATE_CRYSTAL_CORPSE = 27895, // Summon (Damaged Necrotic Shard).
SPELL_CAMP_RECEIVES_COMMUNIQUE = 28449, // Impact Visual.
SPELL_COMMUNIQUE_TIMER_CAMP = 28346, // Cast on npc_necrotic_shard on spawn? Periodically triggers 28345 Communique Trigger every 35 seconds.
SPELL_COMMUNIQUE_TRIGGER = 28345, // Triggers 28281 SPELL_COMMUNIQUE_CAMP_TO_RELAY via void Spell::EffectDummy.
SPELL_DAMAGE_CRYSTAL = 28041, // 100 Damage (Physical). Casted on itself, if 16143 (Shadow of Doom) spawns.
SPELL_SOUL_REVIVAL = 28681, // Increases all damage caused by 10%.
SPELL_CAMP_TYPE_GHOST_SKELETON = 28197, // Camp Type, tells the NPC "Scourge Invasion Minion, finder" which Camp type the Shard has.
SPELL_CAMP_TYPE_GHOST_GHOUL = 28198, // ""
SPELL_CAMP_TYPE_GHOUL_SKELETON = 28199, // ""
SPELL_MINION_SPAWNER_SMALL = 27887, // Triggers 27885 (Disturb Minion Trap, small) every 5 seconds. Activates up to 3 unknown Objects wich spawns the Minions.
SPELL_MINION_SPAWNER_BUTTRESS = 27888, // Triggers 27886 (Disturb Minion Trap, Buttress) every 1 hour. Activates unknown Objects, They may also spawn the Cultists.
SPELL_CHOOSE_CAMP_TYPE = 28201, // casted by Necrotic Shard.
// Shard -> Relay
SPELL_COMMUNIQUE_CAMP_TO_RELAY = 28281, // Purple bolt Visual (SMALL)
SPELL_COMMUNIQUE_CAMP_TO_RELAY_DEATH = 28351, // Visual when Damaged Necrotic Shard dies.
// Camp - Minion spawning system
SPELL_FIND_CAMP_TYPE = 28203, // casted by Scourge Invasion Minion, finder.
// Scourge Invasion Minion, spawner, Ghost/Ghoul
SPELL_PH_SUMMON_MINION_TRAP_GHOST_GHOUL = 27883,
// Scourge Invasion Minion, spawner, Ghost/Skeleton
SPELL_PH_SUMMON_MINION_TRAP_GHOST_SKELETON = 28186,
// Scourge Invasion Minion, spawner, Ghoul/Skeleton
SPELL_PH_SUMMON_MINION_TRAP_GHOUL_SKELETON = 28187,
// Minions Spells
SPELL_ZAP_CRYSTAL = 28032, // 15 damage to a Necrotic Shard on death.
SPELL_MINION_SPAWN_IN = 28234, // Pink Lightning.
SPELL_SPIRIT_SPAWN_OUT = 17680, // Makes invisible.
SPELL_MINION_DESPAWN_TIMER = 28090, // Triggers 28091 (Despawner, self) every 150 seconds. Triggers 17680 SPELL_SPIRIT_SPAWN_OUT via void Spell::EffectDummy.
SPELL_CONTROLLER_TIMER = 28095, // Triggers 28091 (Despawner, self) every 60 seconds for 1 hour. (Unknown who is casting this).
SPELL_DESPAWNER_SELF = 28091, // Trigger from Spell above.
SPELL_SUMMON_SCOURGE_CONTROLLER = 28092,
// Minion Abilities
SPELL_SCOURGE_STRIKE = 28265, // Pink Lightning (Instakill).
SPELL_ENRAGE = 8599, // Used by 16141 (Ghoul Berserker).
SPELL_BONE_SHARDS = 17014, // [shortest sniff CD: 16,583 seconds] Used by 16299 (Skeletal Shocktrooper).
SPELL_INFECTED_BITE = 7367, // [shortest sniff CD: 13,307 seconds] Used by 16141 (Ghoul Berserker).
SPELL_DEMORALIZING_SHOUT = 16244, // [shortest sniff CD: 19,438 seconds] Used by 16298 (Spectral Soldier).
SPELL_SUNDER_ARMOR = 21081, // [shortest sniff CD: 6,489 seconds] Used by 16298 (Spectral Soldier).
SPELL_SHADOW_WORD_PAIN = 589, // Used by 16438 (Skeletal Trooper).
SPELL_DUAL_WIELD = 674, // Used by Skeletal Soldier and Skeletal Shocktrooper.
// Marks of the Dawn
SPELL_CREATE_LESSER_MARK_OF_THE_DAWN = 28319, // Create Lesser Mark of the Dawn.
SPELL_CREATE_MARK_OF_THE_DAWN = 28320, // Create Mark of the Dawn.
SPELL_CREATE_GREATER_MARK_OF_THE_DAWN = 28321, // Create Greater Mark of the Dawn.
// Rare Minions
SPELL_KNOCKDOWN = 16790, // Used by 14697 (Lumbering Horror).
SPELL_TRAMPLE = 5568, // Used by 14697 (Lumbering Horror).
SPELL_AURA_OF_FEAR = 28313, // Used by 14697 (Lumbering Horror).
SPELL_RIBBON_OF_SOULS = 16243, // [shortest sniff CD: 1,638 seconds] Used by 16379 (Spirit of the Damned).
SPELL_PSYCHIC_SCREAM = 22884, // or 26042, used by 16379 (Spirit of the Damned).
SPELL_MINION_DESPAWN_TIMER_UNCOMMON = 28292, // Triggers 28091 (Despawner, self) every 10 minutes. Triggers 17680 SPELL_SPIRIT_SPAWN_OUT via void Spell::EffectDummy.
SPELL_ARCANE_BOLT = 13748, /* 20720 Used by 16380 (Bone Witch).
https://classicdb.ch/?npc=16380#abilities says 13748 but 20720 is the only "Arcane Bolt" whichs requires no mana.
Danage is very high, so i guess it has a very long cd.
Spell description in the Bestiary is: Hurls a magical bolt at an enemy, inflicting Arcane damage.
*/
// Cultist Engineer
SPELL_CREATE_SUMMONER_SHIELD = 28132, // Summon Object - Temporary (181142),
// Casted exactly the same time with 28234 (Minion Spawn-in) on spawn.
SPELL_BUTTRESS_CHANNEL = 28078, // Channeled by Cultist Engineer on Damaged Necrotic Shard shortly after spawning.
SPELL_BUTTRESS_TRAP = 28054, // Unknown.
SPELL_KILL_SUMMONER_SUMMON_BOSS = 28250, // Reagents, 1 Necrotic Rune
// Probably used to spawn Shadow of Doom. Casting sequence (All these [x] spells are being casted the following order within 1-2 seconds):
SPELL_PH_KILL_SUMMONER_BUFF = 27852, // [1] Casted by Cultist on Player.
SPELL_KILL_SUMMONER_WHO_WILL_SUMMON_BOSS = 27894, // [2] Casted by Player on Cultist.
SPELL_QUIET_SUICIDE = 3617, // [3] Instakill, casted exactly same time as 31316 (Summon Boss Buff).
SPELL_SUMMON_BOSS_BUFF = 31316, // [4] Summon Boss Buff, casted on Player
SPELL_SUMMON_BOSS = 31315, /* [5] Reagents, 8 Necrotic Rune, Summon (Shadow of Doom) for 1 hour.
The question is: What happens after this hour if the Shadow of Doom despawns?
Do the cultists respawn and channeling again on the damaged shard or
Does the Necrotic crystal respawn without Cultists or Shadows of Doom?
*/
// Shadow of Doom
SPELL_SPAWN_SMOKE = 10389, // Spawning Visual.
SPELL_ZAP_CRYSTAL_CORPSE = 28056, // Casted on Shard if Shadow of Doom dies.
SPELL_MINDFLAY = 16568,
SPELL_FEAR = 12542,
// Pallid Horror - Patchwerk Terror (also uses: 28315)
SPELL_SUMMON_CRACKED_NECROTIC_CRYSTAL = 28424, // Alliance.
SPELL_SUMMON_FAINT_NECROTIC_CRYSTAL = 28699, // Horde.
SPELL_DAMAGE_VS_GUARDS = 28364, // [shortest sniff CD: 11 seconds, longest 81 sec] hits 13839 (Royal Dreadguard).
// Flameshocker (also uses: 28234, 17680)
SPELL_FLAMESHOCKERS_TOUCH = 28314, // [shortest sniff CD: 30 seconds]
SPELL_FLAMESHOCKERS_REVENGE = 28323, // On death.
SPELL_FLAMESHOCKERS_TOUCH2 = 28329, // [shortest sniff CD: 30 seconds]
SPELL_FLAMESHOCKER_IMMOLATE_VISUAL = 28330
/*
These spells are not used by any NPCs or GameObjects.
The [PH] in the name means it's a placeholder. B often adds that to the names of things they add to the game but haven't finalized.
The fact that the [PH] is still there means the quest was never finished. (Google)
SPELL_PH_SUMMON_MINION_PARENT_GHOST_GHOUL = 28183,
SPELL_PH_SUMMON_MINION_PARENT_GHOST_SKELETON = 28184,
SPELL_PH_SUMMON_MINION_PARENT_GHOUL_SKELETON = 28185,
SPELL_PH_GET_TOKEN = 27922, // Create Item "Necrotic Rune".
SPELL_PH_BUTTRESS_ACTIVATOR = 28086,
SPELL_PH_CRYSTAL_CORPSE_DESPAWN = 28020,
SPELL_PH_CRYSTAL_CORPSE_TIMER = 28018, // Triggers 28020 ([PH] Crystal Corpse Despawn) after 2 hours.
SPELL_PH_CYSTAL_BAZOOKA = 27849,
SPELL_PH_SUMMON_BUTTRESS = 28024, // Summon (Cultist Engineer) for 1 hour.
SPELL_DND_SUMMON_CRYSTAL_MINION_FINDER = 28227,
*/
};
enum ScourgeInvasionNPC
{
// Visible NPCs
NPC_NECROTIC_SHARD = 16136,
NPC_DAMAGED_NECROTIC_SHARD = 16172,
NPC_CULTIST_ENGINEER = 16230,
NPC_SHADOW_OF_DOOM = 16143,
// Camp Helpers (invisible)
NPC_SCOURGE_INVASION_MINION_FINDER = 16356, // Casting 28203 (Find Camp Type).
NPC_SCOURGE_INVASION_MINION_SPAWNER_GHOST_GHOUL = 16306,
NPC_SCOURGE_INVASION_MINION_SPAWNER_GHOST_SKELETON = 16336,
NPC_SCOURGE_INVASION_MINION_SPAWNER_GHOUL_SKELETON = 16338,
// Necropolis Helpers (invisible)
NPC_NECROPOLIS = 16401,
NPC_NECROPOLIS_HEALTH = 16421,
NPC_NECROPOLIS_PROXY = 16398,
NPC_NECROPOLIS_RELAY = 16386,
// Minions
NPC_SKELETAL_SHOCKTROOPER = 16299,
NPC_GHOUL_BERSERKER = 16141,
NPC_SPECTRAL_SOLDIER = 16298,
// Rare Minions
NPC_LUMBERING_HORROR = 14697,
NPC_BONE_WITCH = 16380,
NPC_SPIRIT_OF_THE_DAMNED = 16379,
// 50 Zones cleared
NPC_ARGENT_DAWN_INITIATE = 16384,
NPC_ARGENT_DAWN_CLERIC = 16435,
// 100 Zones cleared
NPC_ARGENT_DAWN_PRIEST = 16436,
NPC_ARGENT_DAWN_PALADIN = 16395,
// 150 Zones cleared
NPC_ARGENT_DAWN_CRUSADER = 16433,
NPC_ARGENT_DAWN_CHAMPION = 16434,
// Low level Minions
NPC_SKELETAL_TROOPER = 16438,
NPC_SPECTRAL_SPIRIT = 16437,
NPC_SKELETAL_SOLDIER = 16422,
NPC_SPECTRAL_APPARITATION = 16423,
// Stormwind - Undercity Attacks https://www.youtube.com/watch?v=c0QjLqHVPRU&t=17s
// NPC_PALLID_HORROR = 16394,
// NPC_PATCHWORK_TERROR = 16382,
NPC_CRACKED_NECROTIC_CRYSTAL = 16431,
NPC_FAINT_NECROTIC_CRYSTAL = 16531,
NPC_FLAMESHOCKER = 16383,
NPC_HIGHLORD_BOLVAR_FORDRAGON = 1748,
NPC_VARIAN = 29611,
NPC_LADY_SYLVANAS_WINDRUNNER = 10181,
NPC_VARIMATHRAS = 2425,
NPC_ROYAL_DREADGUARD = 13839,
NPC_STORMWIND_ROYAL_GUARD = 1756,
NPC_UNDERCITY_ELITE_GUARDIAN = 16432,
NPC_UNDERCITY_GUARDIAN = 5624,
NPC_DEATHGUARD_ELITE = 7980,
NPC_STORMWIND_CITY_GUARD = 68,
NPC_STORMWIND_ELITE_GUARD = 16396,
// Citizens
NPC_RENATO_GALLINA = 1432,
NPC_MICHAEL_GARRETT = 4551,
NPC_HANNAH_AKELEY = 4575,
NPC_INNKEEPER_NORMAN = 6741,
NPC_OFFICER_MALOOF = 15766,
NPC_STEPHANIE_TURNER = 6174,
NPC_THOMAS_MILLER = 3518,
NPC_WILLIAM_MONTAGUE = 4549
};
enum ScourgeInvasionObjects
{
// Invisible Objects
GO_BUTTRESS_TRAP = 181112, // [Guessed] Those objects can't be sniffed and are not available in any database.
GO_SUMMON_MINION_TRAP_GHOST_GHOUL = 181111, // Object is not in sniffed files or any database such as WoWHead, but spell 28196 (Create Minion Trap: Ghost/Skeleton) should probably summon them.
GO_SUMMON_MINION_TRAP_GHOST_SKELETON = 181155, // ""
GO_SUMMON_MINION_TRAP_GHOUL_SKELETON = 181156, // ""
// Visible Objects
GO_SUMMON_CIRCLE = 181136,
GO_SUMMONER_SHIELD = 181142,
GO_UNDEAD_FIRE = 181173,
GO_UNDEAD_FIRE_AURA = 181174,
GO_SKULLPILE_01 = 181191,
GO_SKULLPILE_02 = 181192,
GO_SKULLPILE_03 = 181193,
GO_SKULLPILE_04 = 181194,
GO_NECROPOLIS_TINY = 181154, // Necropolis (scale 1.0).
GO_NECROPOLIS_SMALL = 181373, // Necropolis (scale 1.5).
GO_NECROPOLIS_MEDIUM = 181374, // Necropolis (scale 2.0).
GO_NECROPOLIS_BIG = 181215, // Necropolis (scale 2.5).
GO_NECROPOLIS_HUGE = 181223, // Necropolis (scale 3.5).
GO_NECROPOLIS_CITY = 181172, // Necropolis at the Citys (scale 2.5).
};
enum ScourgeInvasionMisc
{
ITEM_NECROTIC_RUNE = 22484,
ACTION_FLAMESHOCKER_SCHEDULE_DESPAWN = 1, // Used by Flameshocker to schedule despawn.
};
enum ScourgeInvasionNPCEvents
{
EVENT_SHARD_MINION_SPAWNER_SMALL = 1,
EVENT_SHARD_MINION_SPAWNER_BUTTRESS = 2,
EVENT_SPAWNER_SUMMON_MINION = 3,
EVENT_SHARD_FIND_DAMAGED_SHARD = 4,
EVENT_CULTIST_CHANNELING = 5,
EVENT_HERALD_OF_THE_LICH_KING_YELL = 6,
// EVENT_HERALD_OF_THE_LICH_KING_ZONE_START = 7,
// EVENT_HERALD_OF_THE_LICH_KING_ZONE_STOP = 8,
EVENT_HERALD_OF_THE_LICH_KING_UPDATE = 9,
// Shadow of Doom Events
EVENT_DOOM_MINDFLAY = 20,
EVENT_DOOM_FEAR = 21,
EVENT_DOOM_START_ATTACK = 22,
// Rare Events
EVENT_RARE_KNOCKDOWN = 31,
EVENT_RARE_TRAMPLE = 32,
EVENT_RARE_RIBBON_OF_SOULS = 33,
// Minion Events
EVENT_MINION_ENRAGE = 40,
EVENT_MINION_BONE_SHARDS = 41,
EVENT_MINION_INFECTED_BITE = 42,
EVENT_MINION_DAZED = 43,
EVENT_MINION_DEMORALIZING_SHOUT = 44,
EVENT_MINION_SUNDER_ARMOR = 45,
EVENT_MINION_ARCANE_BOLT = 46,
EVENT_MINION_PSYCHIC_SCREAM = 47,
EVENT_MINION_SCOURGE_STRIKE = 48,
EVENT_MINION_SHADOW_WORD_PAIN = 49,
EVENT_MINION_FLAMESHOCKERS_TOUCH = 50,
EVENT_MINION_FLAMESHOCKERS_DESPAWN = 51,
// Pallid Horror Events
EVENT_PALLID_RANDOM_YELL = 52,
EVENT_PALLID_SPELL_DAMAGE_VS_GUARDS = 53,
EVENT_SYLVANAS_ANSWER_YELL = 54,
EVENT_PALLID_RANDOM_SAY = 55,
EVENT_PALLID_SUMMON_FLAMESHOCKER = 56
};
enum ScourgeInvasionQuests
{
QUEST_UNDER_THE_SHADOW = 9153,
QUEST_CRACKED_NECROTIC_CRYSTAL = 9292,
QUEST_FAINT_NECROTIC_CRYSTAL = 9310
};
enum ScourgeInvasionTalk
{
HERALD_OF_THE_LICH_KING_SAY_ATTACK_START = 0,
HERALD_OF_THE_LICH_KING_SAY_ATTACK_END = 1,
HERALD_OF_THE_LICH_KING_SAY_ATTACK_RANDOM = 2,
PALLID_HORROR_SAY_RANDOM_YELL = 0,
SHADOW_OF_DOOM_SAY_AGGRO = 0,
SYLVANAS_SAY_ATTACK_END = 3,
VARIAN_SAY_ATTACK_END = 3
};
enum ScourgeInvasionLang
{
// Pallid Horror random yelling every 65-300 seconds
BCT_PALLID_HORROR_YELL1 = 12329, // What? This not Naxxramas! We not like this place... destroy!
BCT_PALLID_HORROR_YELL2 = 12327, // Raaarrrrggghhh! We come for you!
BCT_PALLID_HORROR_YELL3 = 12326, // Kel'Thuzad say to tell you... DIE!
BCT_PALLID_HORROR_YELL4 = 12342, // Why you run away? We make your corpse into Scourge.
BCT_PALLID_HORROR_YELL5 = 12343, // No worry, we find you.
BCT_PALLID_HORROR_YELL6 = 12330, // You spare parts! We make more Scourge in necropolis.
BCT_PALLID_HORROR_YELL7 = 12328, // Hahaha, your guards no match for Scourge!
BCT_PALLID_HORROR_YELL8 = 12325, // We come destroy puny ones!
// Undercity Guardian
BCT_UNDERCITY_GUARDIAN_ROGUES_QUARTER = 12336, // Rogues' Quarter attacked by Scourge! Help!
BCT_UNDERCITY_GUARDIAN_MAGIC_QUARTER = 12335, // Scourge attack Magic Quarter!
BCT_UNDERCITY_GUARDIAN_TRADE_QUARTER = 12353, // There Scourge outside Trade Quarter!
BCT_UNDERCITY_GUARDIAN_SEWERS = 12334, // Scourge in sewers! We need help!
// Undercity Elite Guardian
BCT_UNDERCITY_ELITE_GUARDIAN_1 = 12354, // Scourge inside Trade Quarter! Destroy!
// Royal Dreadguard
BCT_UNDERCITY_ROYAL_DREADGUARD_1 = 12337, // The Scourge are at the entrance to the Royal Quarter! Kill them!!
// Varimathras
BCT_UNDERCITY_VARIMATHRAS_1 = 12333, // Dreadguard, hold your line. Halt the advance of those Scourge!
// Lady Sylvanas Windrunner
BCT_UNDERCITY_SYLVANAS_1 = 12331, // The Scourge attack against my court has been eliminated. You may go about your business.
BCT_UNDERCITY_SYLVANAS_2 = 12332, // My Royal Dreadguard, you will deal with this matter as befits your station. That, or you will wish that you had.
// Citizens
BCT_UNDERCITY_RANDOM_1 = 12355, // Scourge spotted nearby!
BCT_STORMWIND_RANDOM_1 = 12366, // Scourge spotted nearby! Renato Gallina
BCT_UNDERCITY_RANDOM_2 = 12356, // I just saw a Scourge! Kill it!
BCT_STORMWIND_RANDOM_2 = 12367, // I just saw a Scourge! Kill it! Thomas Miller
BCT_UNDERCITY_RANDOM_3 = 12357, // Did you see that? There's a Scourge over there! Michael Garrett, Hannah Akeley
BCT_STORMWIND_RANDOM_3 = 12368, // Did you see that? There's a Scourge over there! Thomas Miller
BCT_UNDERCITY_RANDOM_4 = 12359, // There's one of the Scourge, right over there! Innkeeper Norman, Michael Garrett
BCT_STORMWIND_RANDOM_4 = 12370, // There's one of the Scourge, right over there!
BCT_UNDERCITY_RANDOM_5 = 12357, // Did you see that? There's a Scourge over there! Michael Garrett, Hannah Akeley
BCT_STORMWIND_RANDOM_5 = 12368, // Did you see that? There's a Scourge over there! Thomas Miller
BCT_UNDERCITY_RANDOM_6 = 12361, // Will these unrelenting Scourge attacks never end? Innkeeper Norman, William Montague
BCT_STORMWIND_RANDOM_6 = 12372, // Will these unrelenting Scourge attacks never end?
BCT_UNDERCITY_RANDOM_7 = 12360, // This has gone too far. How dare the Scourge attack Undercity! Destroy it before more come! Innkeeper Norman
BCT_STORMWIND_RANDOM_7 = 12371, // This has gone too far. How dare the Scourge attack Stormwind! Destroy it before more come! Stephanie Turner
BCT_UNDERCITY_RANDOM_8 = 12362, // Destroy the Scourge invader now, before it's too late! Michael Garrett
BCT_STORMWIND_RANDOM_8 = 12373, // Destroy the Scourge invader now, before it's too late! Officer Maloof
BCT_UNDERCITY_RANDOM_9 = 12358, // How can I get anything done with the Scourge running amok in here?! Innkeeper Norman
BCT_STORMWIND_RANDOM_9 = 12369, // How can I get anything done with the Scourge running amok around here?! Stephanie Turner
// Stormwind City Guard
BCT_STORMWIND_CITY_GUARD_1 = 12310, // To arms! Scourge spotted in the Cathedral of Light!
BCT_STORMWIND_CITY_GUARD_2 = 12311, // Scourge in the Trade District! Have at them!
BCT_STORMWIND_CITY_GUARD_3 = 12315, // Light help us... the Scourge are in the Park!
// Stormwind Royal Guard
BCT_STORMWIND_CITY_GUARD_4 = 12316, // The Scourge are at the castle entrance! For Stormwind! For King Anduin!
// Highlord Bolvar Fordragon?
BCT_STORMWIND_BOLVAR_1 = 12317, // Hold the line! Protect the King at all costs!
BCT_STORMWIND_BOLVAR_2 = 12318, // Good work, one and all! The Scourge at the castle have been defeated.
// Misc
BCT_CULTIST_ENGINEER_OPTION = 12112, // Use 8 necrotic runes and disrupt his ritual.
BCT_GIVE_MAGIC_ITEM_OPTION = 12302, // Give me one of your magic items.
BCT_SHADOW_OF_DOOM_TEXT_0 = 12420, // Our dark master has noticed your trifling, and sends me to bring a message... of doom!
BCT_SHADOW_OF_DOOM_TEXT_1 = 12421, // These heroics mean nothing, $c. Your future is sealed and your soul is doomed to servitude!
BCT_SHADOW_OF_DOOM_TEXT_2 = 12422, // Your battle here is but the smallest mote of a world wide invasion, whelp! It is time you learned of the powers you face!
BCT_SHADOW_OF_DOOM_TEXT_3 = 12243, // You will not stop our deepening shadow, $c. Now... join us! Join the ranks of the Chosen!
BCT_HERALD_OF_THE_LICH_KING_ZONE_ATTACK_START_1 = 13121, // Spawn.
BCT_HERALD_OF_THE_LICH_KING_ZONE_ATTACK_START_2 = 13125, // Spawn. 53 min between 2-3 in sniffs.
BCT_HERALD_OF_THE_LICH_KING_ZONE_ATTACK_ENDS_1 = 13165, // Despawn.
BCT_HERALD_OF_THE_LICH_KING_ZONE_ATTACK_ENDS_2 = 13164, // Despawn.
BCT_HERALD_OF_THE_LICH_KING_ZONE_ATTACK_ENDS_3 = 13163, // Despawn.
BCT_HERALD_OF_THE_LICH_KING_RANDOM_1 = 13126, // Random.
BCT_HERALD_OF_THE_LICH_KING_RANDOM_2 = 13124, // Random.
BCT_HERALD_OF_THE_LICH_KING_RANDOM_3 = 13122, // 180 seconds between 5-6 in sniffs.
BCT_HERALD_OF_THE_LICH_KING_RANDOM_4 = 13123, // Random. 30 min between 8-2 in sniffs.
BCT_CULTIST_ENGINEER_GOSSIP = 8436, // 12111 - This cultist is in a deep trance...
};
#endif
@@ -34,6 +34,7 @@ void AddSC_npc_stave_of_ancients();
void AddSC_server_mail();
void AddSC_transport_zeppelins();
void AddSC_suns_reach_reclamation();
void AddSC_scourge_invasion();
// The name of this function should match:
// void Add${NameOfDirectory}Scripts()
@@ -57,4 +58,5 @@ void AddWorldScripts()
AddSC_server_mail();
AddSC_transport_zeppelins();
AddSC_suns_reach_reclamation();
AddSC_scourge_invasion();
}
+239
View File
@@ -2726,6 +2726,245 @@ enum CreatureTypeFlags
CREATURE_TYPE_FLAG_QUEST_BOSS = 0x80000000 // Not verified
};
enum class CreatureStaticFlags : uint32
{
MOUNTABLE = 0x00000001,
NO_XP = 0x00000002,
NO_LOOT = 0x00000004,
UNKILLABLE = 0x00000008,
TAMEABLE = 0x00000010, // CREATURE_TYPE_FLAG_TAMEABLE
IMMUNE_TO_PC = 0x00000020, // UNIT_FLAG_IMMUNE_TO_PC
IMMUNE_TO_NPC = 0x00000040, // UNIT_FLAG_IMMUNE_TO_NPC
CAN_WIELD_LOOT = 0x00000080,
SESSILE = 0x00000100, // creature_template_movement.Rooted = 1
UNINTERACTIBLE = 0x00000200, // UNIT_FLAG_UNINTERACTIBLE
NO_AUTOMATIC_REGEN = 0x00000400, // Creatures with that flag uses no UNIT_FLAG2_REGENERATE_POWER
DESPAWN_INSTANTLY = 0x00000800, // Creature instantly disappear when killed
CORPSE_RAID = 0x00001000,
CREATOR_LOOT = 0x00002000, // Lootable only by creator(engineering dummies)
NO_DEFENSE = 0x00004000,
NO_SPELL_DEFENSE = 0x00008000,
BOSS_MOB = 0x00010000, // CREATURE_TYPE_FLAG_BOSS_MOB, original description: Raid Boss Mob
COMBAT_PING = 0x00020000,
AQUATIC = 0x00040000, // aka Water Only, creature_template_movement.Ground = 0
AMPHIBIOUS = 0x00080000, // creature_template_movement.Swim = 1
NO_MELEE_FLEE = 0x00100000, // Prevents melee and makes npc flee if it enters combat - also misused to just prevent melee on npcs that cant move
VISIBLE_TO_GHOSTS = 0x00200000, // CREATURE_TYPE_FLAG_VISIBLE_TO_GHOSTS
PVP_ENABLING = 0x00400000, // Old UNIT_FLAG_PVP_ENABLING, now UNIT_BYTES_2_OFFSET_PVP_FLAG from UNIT_FIELD_BYTES_2
DO_NOT_PLAY_WOUND_ANIM = 0x00800000, // CREATURE_TYPE_FLAG_DO_NOT_PLAY_WOUND_ANIM
NO_FACTION_TOOLTIP = 0x01000000, // CREATURE_TYPE_FLAG_NO_FACTION_TOOLTIP
IGNORE_COMBAT = 0x02000000, // Actually only changes react state to passive
ONLY_ATTACK_PVP_ENABLING = 0x04000000, // "Only attack targets that are PvP enabling"
CALLS_GUARDS = 0x08000000, // Creature will summon a guard if player is within its aggro range (even if creature doesn't attack per se)
CAN_SWIM = 0x10000000, // UnitFlags 0x8000 UNIT_FLAG_CAN_SWIM
FLOATING = 0x20000000, // creature_template_movement.Flight = 1
MORE_AUDIBLE = 0x40000000, // CREATURE_TYPE_FLAG_MORE_AUDIBLE
LARGE_AOI = 0x80000000 // UnitFlags2 0x200000
};
enum class CreatureStaticFlags2 : uint32
{
NO_PET_SCALING = 0x00000001,
FORCE_PARTY_MEMBERS_INTO_COMBAT = 0x00000002, // Original description: Force Raid Combat
LOCK_TAPPERS_TO_RAID_ON_DEATH = 0x00000004, // "Lock Tappers To Raid On Death", toggleable by 'Set "RAID_LOCK_ON_DEATH" flag for unit(s)' action, CREATURE_FLAG_EXTRA_INSTANCE_BIND
SPELL_ATTACKABLE = 0x00000008, // CREATURE_TYPE_FLAG_SPELL_ATTACKABLE, original description(not valid anymore?): No Harmful Vertex Coloring
NO_CRUSHING_BLOWS = 0x00000010,
NO_OWNER_THREAT = 0x00000020,
NO_WOUNDED_SLOWDOWN = 0x00000040,
USE_CREATOR_BONUSES = 0x00000080,
IGNORE_FEIGN_DEATH = 0x00000100,
IGNORE_SANCTUARY = 0x00000200,
ACTION_TRIGGERS_WHILE_CHARMED = 0x00000400,
INTERACT_WHILE_DEAD = 0x00000800, // CREATURE_TYPE_FLAG_INTERACT_WHILE_DEAD
NO_INTERRUPT_SCHOOL_COOLDOWN = 0x00001000,
RETURN_SOUL_SHARD_TO_MASTER_OF_PET = 0x00002000,
SKIN_WITH_HERBALISM = 0x00004000, // CREATURE_TYPE_FLAG_SKIN_WITH_HERBALISM
SKIN_WITH_MINING = 0x00008000, // CREATURE_TYPE_FLAG_SKIN_WITH_MINING
ALERT_CONTENT_TEAM_ON_DEATH = 0x00010000,
ALERT_CONTENT_TEAM_AT_90_PCT_HP = 0x00020000,
ALLOW_MOUNTED_COMBAT = 0x00040000, // CREATURE_TYPE_FLAG_ALLOW_MOUNTED_COMBAT
PVP_ENABLING_OOC = 0x00080000,
NO_DEATH_MESSAGE = 0x00100000, // CREATURE_TYPE_FLAG_NO_DEATH_MESSAGE
IGNORE_PATHING_FAILURE = 0x00200000,
FULL_SPELL_LIST = 0x00400000,
DOES_NOT_REDUCE_REPUTATION_FOR_RAIDS = 0x00800000,
IGNORE_MISDIRECTION = 0x01000000,
HIDE_BODY = 0x02000000, // UNIT_FLAG2_HIDE_BODY
SPAWN_DEFENSIVE = 0x04000000,
SERVER_ONLY = 0x08000000,
CAN_SAFE_FALL = 0x10000000, // Original description: No Collision
CAN_ASSIST = 0x20000000, // CREATURE_TYPE_FLAG_CAN_ASSIST, original description: Player Can Heal/Buff
NO_SKILL_GAINS = 0x40000000,
NO_PET_BAR = 0x80000000 // CREATURE_TYPE_FLAG_NO_PET_BAR
};
enum class CreatureStaticFlags3 : uint32
{
NO_DAMAGE_HISTORY = 0x00000001,
DONT_PVP_ENABLE_OWNER = 0x00000002,
DO_NOT_FADE_IN = 0x00000004, // UNIT_FLAG2_DO_NOT_FADE_IN
MASK_UID = 0x00000008, // CREATURE_TYPE_FLAG_MASK_UID, original description: Non-Unique In Combat Log
SKIN_WITH_ENGINEERING = 0x00000010, // CREATURE_TYPE_FLAG_SKIN_WITH_ENGINEERING
NO_AGGRO_ON_LEASH = 0x00000020,
NO_FRIENDLY_AREA_AURAS = 0x00000040,
EXTENDED_CORPSE_DURATION = 0x00000080,
CANNOT_SWIM = 0x00000100, // UNIT_FLAG_CANNOT_SWIM
TAMEABLE_EXOTIC = 0x00000200, // CREATURE_TYPE_FLAG_TAMEABLE_EXOTIC
GIGANTIC_AOI = 0x00000400, // Since MoP, creatures with that flag have UnitFlags2 0x400000
INFINITE_AOI = 0x00000800, // Since MoP, creatures with that flag have UnitFlags2 0x40000000
CANNOT_PENETRATE_WATER = 0x00001000, // Waterwalking
NO_NAME_PLATE = 0x00002000, // CREATURE_TYPE_FLAG_NO_NAME_PLATE
CHECKS_LIQUIDS = 0x00004000,
NO_THREAT_FEEDBACK = 0x00008000,
USE_MODEL_COLLISION_SIZE = 0x00010000, // CREATURE_TYPE_FLAG_USE_MODEL_COLLISION_SIZE
ATTACKER_IGNORES_FACING = 0x00020000, // In 3.3.5 used only by Rocket Propelled Warhead
ALLOW_INTERACTION_WHILE_IN_COMBAT = 0x00040000, // CREATURE_TYPE_FLAG_ALLOW_INTERACTION_WHILE_IN_COMBAT
SPELL_CLICK_FOR_PARTY_ONLY = 0x00080000,
FACTION_LEADER = 0x00100000,
IMMUNE_TO_PLAYER_BUFFS = 0x00200000,
COLLIDE_WITH_MISSILES = 0x00400000, // CREATURE_TYPE_FLAG_COLLIDE_WITH_MISSILES
CAN_BE_MULTITAPPED = 0x00800000, // Original description: Do Not Tap (Credit to threat list)
DO_NOT_PLAY_MOUNTED_ANIMATIONS = 0x01000000, // CREATURE_TYPE_FLAG_DO_NOT_PLAY_MOUNTED_ANIMATIONS, original description: Disable Dodge, Parry and Block Animations
CANNOT_TURN = 0x02000000, // UNIT_FLAG2_CANNOT_TURN
ENEMY_CHECK_IGNORES_LOS = 0x04000000,
FOREVER_CORPSE_DURATION = 0x08000000, // 7 days
PETS_ATTACK_WITH_3D_PATHING = 0x10000000, // "Pets attack with 3d pathing (Kologarn)"
LINK_ALL = 0x20000000, // CREATURE_TYPE_FLAG_LINK_ALL
AI_CAN_AUTO_TAKEOFF_IN_COMBAT = 0x40000000,
AI_CAN_AUTO_LAND_IN_COMBAT = 0x80000000
};
enum class CreatureStaticFlags4 : uint32
{
NO_BIRTH_ANIM = 0x00000001, // SMSG_UPDATE_OBJECT's "NoBirthAnim"
TREAT_AS_PLAYER_FOR_DIMINISHING_RETURNS = 0x00000002, // Primarily used by ToC champions
TREAT_AS_PLAYER_FOR_PVP_DEBUFF_DURATION = 0x00000004, // Primarily used by ToC champions
INTERACT_ONLY_WITH_CREATOR = 0x00000008, // CREATURE_TYPE_FLAG_INTERACT_ONLY_WITH_CREATOR, original description: Only Display Gossip for Summoner
DO_NOT_PLAY_UNIT_EVENT_SOUNDS = 0x00000010, // CREATURE_TYPE_FLAG_DO_NOT_PLAY_UNIT_EVENT_SOUNDS, original description: No Death Scream
HAS_NO_SHADOW_BLOB = 0x00000020, // CREATURE_TYPE_FLAG_HAS_NO_SHADOW_BLOB, original description(wrongly linked type flag or behavior was changed?): Can be Healed by Enemies
DEALS_TRIPLE_DAMAGE_TO_PC_CONTROLLED_PETS = 0x00000040,
NO_NPC_DAMAGE_BELOW_85PTC = 0x00000080,
OBEYS_TAUNT_DIMINISHING_RETURNS = 0x00000100, // CREATURE_FLAG_EXTRA_OBEYS_TAUNT_DIMINISHING_RETURNS
NO_MELEE_APPROACH = 0x00000200,
UPDATE_CREATURE_RECORD_WHEN_INSTANCE_CHANGES_DIFFICULTY = 0x00000400, // Used only by Snobold Vassal
CANNOT_DAZE = 0x00000800, // "Cannot Daze (Combat Stun)"
FLAT_HONOR_AWARD = 0x00001000,
IGNORE_LOS_WHEN_CASTING_ON_ME = 0x00002000, // "Other objects can ignore line of sight requirements when casting spells on me", used only by Ice Tomb in 3.3.5
GIVE_QUEST_KILL_CREDIT_WHILE_OFFLINE = 0x00004000,
TREAT_AS_RAID_UNIT_FOR_HELPFUL_SPELLS = 0x00008000, // CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT, "Treat as Raid Unit For Helpful Spells (Instances ONLY)", used by Valithria Dreamwalker
DONT_REPOSITION_IF_MELEE_TARGET_IS_TOO_CLOSE = 0x00010000, // "Don't reposition because melee target is too close"
PET_OR_GUARDIAN_AI_DONT_GO_BEHIND_TARGET = 0x00020000,
MINUTE_5_LOOT_ROLL_TIMER = 0x00040000, // Used by Lich King
FORCE_GOSSIP = 0x00080000, // CREATURE_TYPE_FLAG_FORCE_GOSSIP
DONT_REPOSITION_WITH_FRIENDS_IN_COMBAT = 0x00100000,
DO_NOT_SHEATHE = 0x00200000, // CREATURE_TYPE_FLAG_DO_NOT_SHEATHE, original description: Manual Sheathing control
IGNORE_SPELL_MIN_RANGE_RESTRICTIONS = 0x00400000, // UnitFlags2 0x8000000, original description: Attacker Ignores Minimum Ranges
SUPPRESS_INSTANCE_WIDE_RELEASE_IN_COMBAT = 0x00800000,
PREVENT_SWIM = 0x01000000, // UnitFlags2 0x1000000, original description: AI will only swim if target swims
HIDE_IN_COMBAT_LOG = 0x02000000, // UnitFlags2 0x2000000, original description: Don't generate combat log when engaged with NPC's
ALLOW_NPC_COMBAT_WHILE_UNINTERACTIBLE = 0x04000000,
PREFER_NPCS_WHEN_SEARCHING_FOR_ENEMIES = 0x08000000,
ONLY_GENERATE_INITIAL_THREAT = 0x10000000,
DO_NOT_TARGET_ON_INTERACTION = 0x20000000, // CREATURE_TYPE_FLAG_DO_NOT_TARGET_ON_INTERACTION, original description: Doesn't change target on right click
DO_NOT_RENDER_OBJECT_NAME = 0x40000000, // CREATURE_TYPE_FLAG_DO_NOT_RENDER_OBJECT_NAME, original description: Hide name in world frame
QUEST_BOSS = 0x80000000 // CREATURE_TYPE_FLAG_QUEST_BOSS
};
enum class CreatureStaticFlags5 : uint32
{
UNTARGETABLE_BY_CLIENT = 0x00000001, // UnitFlags2 0x4000000 UNIT_FLAG2_UNTARGETABLE_BY_CLIENT
FORCE_SELF_MOUNTING = 0x00000002,
UNINTERACTIBLE_IF_HOSTILE = 0x00000004, // UnitFlags2 0x10000000
DISABLES_XP_AWARD = 0x00000008,
DISABLE_AI_PREDICTION = 0x00000010,
NO_LEAVECOMBAT_STATE_RESTORE = 0x00000020,
BYPASS_INTERACT_INTERRUPTS = 0x00000040,
DEGREE_BACK_ARC_240 = 0x00000080,
INTERACT_WHILE_HOSTILE = 0x00000100, // UnitFlags2 0x4000 UNIT_FLAG2_INTERACT_WHILE_HOSTILE
DONT_DISMISS_ON_FLYING_MOUNT = 0x00000200,
PREDICTIVE_POWER_REGEN = 0x00000400, // CREATURE_TYPEFLAGS_2_UNK1
HIDE_LEVEL_INFO_IN_TOOLTIP = 0x00000800, // CREATURE_TYPEFLAGS_2_UNK2
HIDE_HEALTH_BAR_UNDER_TOOLTIP = 0x00001000, // CREATURE_TYPEFLAGS_2_UNK3
SUPPRESS_HIGHLIGHT_WHEN_TARGETED_OR_MOUSED_OVER = 0x00002000, // UnitFlags2 0x80000
AI_PREFER_PATHABLE_TARGETS = 0x00004000,
FREQUENT_AREA_TRIGGER_CHECKS = 0x00008000,
ASSIGN_KILL_CREDIT_TO_ENCOUNTER_LIST = 0x00010000,
NEVER_EVADE = 0x00020000,
AI_CANT_PATH_ON_STEEP_SLOPES = 0x00040000,
AI_IGNORE_LOS_TO_MELEE_TARGET = 0x00080000,
NO_TEXT_IN_CHAT_BUBBLE = 0x00100000, // "Never display emote or chat text in a chat bubble", CREATURE_TYPEFLAGS_2_UNK4
CLOSE_IN_ON_UNPATHABLE_TARGET = 0x00200000, // "AI Pets close in on unpathable target"
DONT_GO_BEHIND_ME = 0x00400000, // "Pet/Guardian AI Don't Go Behind Me (use on target)"
NO_DEATH_THUD = 0x00800000, // CREATURE_TYPEFLAGS_2_UNK5
CLIENT_LOCAL_CREATURE = 0x01000000,
CAN_DROP_LOOT_WHILE_IN_A_CHALLENGE_MODE_INSTANCE = 0x02000000,
HAS_SAFE_LOCATION = 0x04000000,
NO_HEALTH_REGEN = 0x08000000,
NO_POWER_REGEN = 0x10000000,
NO_PET_UNIT_FRAME = 0x20000000,
NO_INTERACT_ON_LEFT_CLICK = 0x40000000, // CREATURE_TYPEFLAGS_2_UNK6
GIVE_CRITERIA_KILL_CREDIT_WHEN_CHARMED = 0x80000000
};
enum class CreatureStaticFlags6 : uint32
{
DO_NOT_AUTO_RESUMMON = 0x00000001, // "Do not auto-resummon this companion creature"
REPLACE_VISIBLE_UNIT_IF_AVAILABLE = 0x00000002, // "Smooth Phasing: Replace visible unit if available"
IGNORE_REALM_COALESCING_HIDING_CODE = 0x00000004, // "Ignore the realm coalescing hiding code (always show)"
TAPS_TO_FACTION = 0x00000008,
ONLY_QUESTGIVER_FOR_SUMMONER = 0x00000010,
AI_COMBAT_RETURN_PRECISE = 0x00000020,
HOME_REALM_ONLY_LOOT = 0x00000040,
NO_INTERACT_RESPONSE = 0x00000080, // TFLAG2_UNK7
NO_INITIAL_POWER = 0x00000100,
DONT_CANCEL_CHANNEL_ON_MASTER_MOUNTING = 0x00000200,
CAN_TOGGLE_BETWEEN_DEATH_AND_PERSONAL_LOOT = 0x00000400,
ALWAYS_STAND_ON_TOP_OF_TARGET = 0x00000800, // "Always, ALWAYS tries to stand right on top of his move to target. ALWAYS!!", toggleable by 'Set "Always Stand on Target" flag for unit(s)' or not same?
UNCONSCIOUS_ON_DEATH = 0x00001000,
DONT_REPORT_TO_LOCAL_DEFENSE_CHANNEL_ON_DEATH = 0x00002000,
PREFER_UNENGAGED_MONSTERS = 0x00004000, // "Prefer unengaged monsters when picking a target"
USE_PVP_POWER_AND_RESILIENCE = 0x00008000, // "Use PVP power and resilience when players attack this creature"
DONT_CLEAR_DEBUFFS_ON_LEAVE_COMBAT = 0x00010000,
PERSONAL_LOOT_HAS_FULL_SECURITY = 0x00020000, // "Personal loot has full security (guaranteed push/mail delivery)"
TRIPLE_SPELL_VISUALS = 0x00040000,
USE_GARRISON_OWNER_LEVEL = 0x00080000,
IMMEDIATE_AOI_UPDATE_ON_SPAWN = 0x00100000,
UI_CAN_GET_POSITION = 0x00200000,
SEAMLESS_TRANSFER_PROHIBITED = 0x00400000,
ALWAYS_USE_GROUP_LOOT_METHOD = 0x00800000,
NO_BOSS_KILL_BANNER = 0x01000000,
FORCE_TRIGGERING_PLAYER_LOOT_ONLY = 0x02000000,
SHOW_BOSS_FRAME_WHILE_UNINTERACTABLE = 0x04000000,
SCALES_TO_PLAYER_LEVEL = 0x08000000,
AI_DONT_LEAVE_MELEE_FOR_RANGED_WHEN_TARGET_GETS_ROOTED = 0x10000000,
DONT_USE_COMBAT_REACH_FOR_CHAINING = 0x20000000,
DO_NOT_PLAY_PROCEDURAL_WOUND_ANIM = 0x40000000,
APPLY_PROCEDURAL_WOUND_ANIM_TO_BASE = 0x80000000 // TFLAG2_UNK14
};
enum class CreatureStaticFlags7 : uint32
{
IMPORTANT_NPC = 0x00000001,
IMPORTANT_QUEST_NPC = 0x00000002,
LARGE_NAMEPLATE = 0x00000004,
TRIVIAL_PET = 0x00000008,
AI_ENEMIES_DONT_BACKUP_WHEN_I_GET_ROOTED = 0x00000010,
NO_AUTOMATIC_COMBAT_ANCHOR = 0x00000020,
ONLY_TARGETABLE_BY_CREATOR = 0x00000040,
TREAT_AS_PLAYER_FOR_ISPLAYERCONTROLLED = 0x00000080,
GENERATE_NO_THREAT_OR_DAMAGE = 0x00000100,
INTERACT_ONLY_ON_QUEST = 0x00000200,
DISABLE_KILL_CREDIT_FOR_OFFLINE_PLAYERS = 0x00000400,
AI_ADDITIONAL_PATHING = 0x00080000,
};
enum class CreatureStaticFlags8 : uint32
{
FORCE_CLOSE_IN_ON_PATH_FAIL_BEHAVIOR = 0x00000002,
USE_2D_CHASING_CALCULATION = 0x00000020,
USE_FAST_CLASSIC_HEARTBEAT = 0x00000040,
};
enum CreatureEliteType
{
CREATURE_ELITE_NORMAL = 0,