Merge branch 'master' into ovv/systemd-socket-activation

This commit is contained in:
Quentin Dawans 2025-06-30 11:28:28 +02:00 committed by GitHub
commit 2060c4cc87
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 696 additions and 691 deletions

View File

@ -1,234 +0,0 @@
########################################################################################
# SETTINGS #
########################################################################################
$mysql_host = "127.0.0.1"
$mysql_user = "export"
$mysql_password = "export"
$mysql_database_auth = "acore_auth"
$mysql_database_characters = "acore_characters"
$mysql_database_world = "acore_world"
########################################################################################
# SETTINGS END #
########################################################################################
# Set MySQL password as temporary env var
$env:MYSQL_PWD = $mysql_password
# Get the directory to sql\base directory
$scriptDirectory = $PSScriptRoot
$relativePath = "..\..\data\sql\base"
$combinedPath = Join-Path -Path $scriptDirectory -ChildPath $relativePath
$fullPath = Resolve-Path -Path $combinedPath
# Define the output directory (using database name)
$output_directory_auth = "$fullPath\db_auth"
$output_directory_characters = "$fullPath\db_characters"
$output_directory_world = "$fullPath\db_world"
Write-Host " ___ _ _ ___ "
Write-Host "/ \ ___ ___ _ _ ___ | |_ | |_ / __| ___ _ _ ___ "
Write-Host "| - ||_ // -_)| '_|/ _ \| _|| \ | (__ / _ \| '_|/ -_)"
Write-Host "|_|_|/__|\___||_| \___/ \__||_||_| \___|\___/|_| \___|"
Write-Host "AzerothCore 3.3.5a - www.azerothcore.org"
Write-Host ""
Write-Host "Welcome to the AzerothCore Database Exporter for database squashes!"
Write-Host ""
Write-Host "You have configured:"
Write-Host "Database Auth: '$mysql_database_auth'"
Write-Host "Database Characters: '$mysql_database_characters'"
Write-Host "Database World: '$mysql_database_world'"
Write-Host "Output Dir Auth: '$output_directory_auth'"
Write-Host "Output Dir Characters: '$output_directory_characters'"
Write-Host "Output Dir World: '$output_directory_world'"
Write-Host ""
Write-Host "Make sure you read the entire process before you continue."
Write-Host "https://github.com/azerothcore/azerothcore-wotlk/blob/master/data/sql/base/database-squash.md"
Write-Host "https://github.com/azerothcore/azerothcore-wotlk/blob/master/apps/DatabaseExporter/databaseexporter.md"
Write-Host ""
# Check if the user wants to continue using the tool
do {
$confirmation = Read-Host "Do you want to continue using the tool? (Y/N)"
if ($confirmation -eq 'Y' -or $confirmation -eq 'y') {
# Continue the script
Write-Host "AzerothCore Database Exporter starts."
$continue = $true
}
elseif ($confirmation -eq 'N' -or $confirmation -eq 'n') {
# Exit the script
Write-Host "Exiting the AzerothCore Database Exporter."
exit
}
else {
Write-Host "Invalid input. Please enter Y or N."
$continue = $null
}
} while ($continue -eq $null)
# Remove the output directory if it exist
if (Test-Path $output_directory_auth) {
Remove-Item -Path $output_directory_auth -Recurse -Force
Write-Host "Deleted directory $output_directory_auth"
}
if (Test-Path $output_directory_characters) {
Remove-Item -Path $output_directory_characters -Recurse -Force
Write-Host "Deleted directory $output_directory_characters"
}
if (Test-Path $output_directory_world) {
Remove-Item -Path $output_directory_world -Recurse -Force
Write-Host "Deleted directory $output_directory_world"
}
# Create the output directory if it doesn't exist
if (-not (Test-Path -Path $output_directory_auth)) {
New-Item -ItemType Directory -Force -Path $output_directory_auth
Write-Host "Created directory $output_directory_auth"
}
if (-not (Test-Path -Path $output_directory_characters)) {
New-Item -ItemType Directory -Force -Path $output_directory_characters
Write-Host "Created directory $output_directory_characters"
}
if (-not (Test-Path -Path $output_directory_world)) {
New-Item -ItemType Directory -Force -Path $output_directory_world
Write-Host "Created directory $output_directory_world"
}
# Fix for dumping TIMESTAMP data
$timezone = "+01:00"
$mysqlCommand = "SET time_zone = '$timezone';"
$mysqlExec = "mysql -h $mysql_host -u $mysql_user -p$mysql_password -e `"$mysqlCommand`""
Invoke-Expression -Command $mysqlExec
# PS script uses non-utf-8 encoding by default
# https://stackoverflow.com/a/58438716
# Save the current encoding and switch to UTF-8.
$prev = [Console]::OutputEncoding
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
Write-Host ""
Write-Host "#########################################################"
Write-Host "EXPORT AUTH DATABASE START"
Write-Host "#########################################################"
Write-Host ""
Write-Host "Please enter your password for user '$mysql_user'"
# Export Auth Database
# Connect to MySQL and get all the tables
$tables_auth = mysql -h $mysql_host -u $mysql_user -D $mysql_database_auth -e "SHOW TABLES;" | Select-Object -Skip 1
# Iterate through each table and export both the structure and contents into the same SQL file
foreach ($table in $tables_auth) {
# Define the output file path for this table
$output_file = "$output_directory_auth\$table.sql"
# Clear the content of the output file if it exists, or create a new one
if (Test-Path $output_file) {
Clear-Content -Path $output_file
}
# Export the table structure (CREATE TABLE) and table data (INSERT) to the SQL file
$create_table_command = "mysqldump -h $mysql_host -u $mysql_user --skip-tz-utc $mysql_database_auth $table"
$create_table_output = Invoke-Expression -Command $create_table_command
# write file with utf-8 encoding
# https://stackoverflow.com/a/32951824
[IO.File]::WriteAllLines($output_file, $create_table_output)
# Format the INSERT values to be on seperate lines.
$content = Get-Content -Raw $output_file
$formattedContent = $content -replace 'VALUES \(', "VALUES`r`n("
$formattedContent = $formattedContent -replace '\),', "),`r`n"
$formattedContent | Set-Content $output_file
Write-Host "Exported structure and data for table $table to $output_file"
}
Write-Host ""
Write-Host "#########################################################"
Write-Host "EXPORT AUTH DATABASE END"
Write-Host "#########################################################"
Write-Host ""
Write-Host "#########################################################"
Write-Host "EXPORT CHARACTERS DATABASE START"
Write-Host "#########################################################"
Write-Host ""
Write-Host "Please enter your password for user '$mysql_user'"
# Export Characters Database
# Connect to MySQL and get all the tables
$tables_characters = mysql -h $mysql_host -u $mysql_user -D $mysql_database_characters -e "SHOW TABLES;" | Select-Object -Skip 1
# Iterate through each table and export both the structure and contents into the same SQL file
foreach ($table in $tables_characters) {
# Define the output file path for this table
$output_file = "$output_directory_characters\$table.sql"
# Clear the content of the output file if it exists, or create a new one
if (Test-Path $output_file) {
Clear-Content -Path $output_file
}
# Export the table structure (CREATE TABLE) and table data (INSERT) to the SQL file
$create_table_command = "mysqldump -h $mysql_host -u $mysql_user --skip-tz-utc $mysql_database_characters $table"
$create_table_output = Invoke-Expression -Command $create_table_command
# write file with utf-8 encoding
# https://stackoverflow.com/a/32951824
[IO.File]::WriteAllLines($output_file, $create_table_output)
# Format the INSERT values to be on seperate lines.
$content = Get-Content -Raw $output_file
$formattedContent = $content -replace 'VALUES \(', "VALUES`r`n("
$formattedContent = $formattedContent -replace '\),', "),`r`n"
$formattedContent | Set-Content $output_file
Write-Host "Exported structure and data for table $table to $output_file"
}
Write-Host ""
Write-Host "#########################################################"
Write-Host "EXPORT CHARACTERS DATABASE END"
Write-Host "#########################################################"
Write-Host ""
Write-Host "#########################################################"
Write-Host "EXPORT WORLD DATABASE START"
Write-Host "#########################################################"
Write-Host ""
Write-Host "Please enter your password for user '$mysql_user'"
# Export World Database
# Connect to MySQL and get all the tables
$tables_world = mysql -h $mysql_host -u $mysql_user -D $mysql_database_world -e "SHOW TABLES;" | Select-Object -Skip 1
# Iterate through each table and export both the structure and contents into the same SQL file
foreach ($table in $tables_world) {
# Define the output file path for this table
$output_file = "$output_directory_world\$table.sql"
# Clear the content of the output file if it exists, or create a new one
if (Test-Path $output_file) {
Clear-Content -Path $output_file
}
# Export the table structure (CREATE TABLE) and table data (INSERT) to the SQL file
$create_table_command = "mysqldump -h $mysql_host -u $mysql_user --skip-tz-utc $mysql_database_world $table"
$create_table_output = Invoke-Expression -Command $create_table_command
# write file with utf-8 encoding
# https://stackoverflow.com/a/32951824
[IO.File]::WriteAllLines($output_file, $create_table_output)
# Format the INSERT values to be on seperate lines.
$content = Get-Content -Raw $output_file
$formattedContent = $content -replace 'VALUES \(', "VALUES`r`n("
$formattedContent = $formattedContent -replace '\),', "),`r`n"
$formattedContent | Set-Content $output_file
Write-Host "Exported structure and data for table $table to $output_file"
}
Write-Host ""
Write-Host "#########################################################"
Write-Host "EXPORT WORLD DATABASE END"
Write-Host "#########################################################"
Write-Host ""
Write-Host "Database Exporter completed."
Write-Host "Have a nice day :)"
# Restore the previous encoding.
[Console]::OutputEncoding = $prev

View File

@ -1,85 +0,0 @@
# The AzerothCore Database Exporter for Database Squashes
> [!CAUTION]
> These steps are only for project maintainers who intend to update base files.
## Manual setting updates
Update the settings in `DatabaseExporter.ps1` to reflect your setup by opening it with your preffered text editor.
> [!NOTE]
> Only update the settings within the SETTINGS block.
These are the default settings:
```ps
########################################################################################
# SETTINGS #
########################################################################################
$mysql_host = "127.0.0.1"
$mysql_user = "export"
$mysql_password = "export"
$mysql_database_auth = "acore_auth"
$mysql_database_characters = "acore_characters"
$mysql_database_world = "acore_world"
########################################################################################
# SETTINGS END #
########################################################################################
```
## Description of the tool
This tool updates the base files automatically. Hence, it must run from this directory.
This is how it works step-by-step:
1. Check that all paths look correct.
2. Accept to continue using the tool.
3. The tool will delete the `db_auth` `db_characters` `db_world` directories in `..\..\data\sql\base\`
4. The tool will create the `db_auth` `db_characters` `db_world` directories in `..\..\data\sql\base\`
5. The tool will export the `db_auth` table into `..\..\data\sql\base\db_auth\`
6. The tool will export the `db_characters` table into `..\..\data\sql\base\db_characters\`
7. The tool will export the `db_world` table into `..\..\data\sql\base\db_world\`
## Run the tool
> [!IMPORTANT]
> This tool CAN NOT be moved outside this directory. If you do it will create files in the wrong places.
1. Make sure you have MySQL installed on your system and that the mysqldump tool is accessible by your PATH system variable. If it is not set you will encounter errors.
- Go into System Variables
- Open the PATH variable
- Add the path to your $\MySQL Server\bin\ - e.g. C:\Program Files\MySQL\MySQL Server 8.4\bin\
2. If you haven't run PowerShell scripts before, you'll need to adjust the execution policy.
- Open PowerShell as an Administrator.
- Run the following command to allow running scripts:
```ps
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
```
- This allows scripts to run on your system, but they need to be locally created or downloaded from trusted sources.
3. Open PowerShell (PS)
- Press Win + X and select Windows PowerShell (Admin) / Terminal (Admin)
4. Navigate to the script
- In PS, use the `cd` command to change the directory
```ps
cd "C:\AzerothCore\apps\DatabaseExporter"
```
5. Run the script
- In PS, run the script
```ps
.\DatabaseExporter.ps1
```
6. Follow the instructions given by the tool.
7. Now refer back to the database-squash.md instructions. (Located in ..\..\data\sql\base\)
Completed :)

View File

@ -0,0 +1,69 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
if [[ "$PROJECT_ROOT" =~ ^/([a-zA-Z])/(.*) ]]; then
DRIVE_LETTER="${BASH_REMATCH[1]}"
PATH_REMAINDER="${BASH_REMATCH[2]}"
PROJECT_ROOT="${DRIVE_LETTER^^}:/${PATH_REMAINDER}"
fi
BASE_OUTPUT_DIR="$PROJECT_ROOT/data/sql/base"
read -p "Enter MySQL username: " DB_USER
read -p "Enter MySQL password: " DB_PASS
read -p "Enter MySQL host (default: localhost): " DB_HOST
DB_HOST=${DB_HOST:-localhost}
read -p "Enter MySQL port (default: 3306): " DB_PORT
DB_PORT=${DB_PORT:-3306}
# Prompt for database names
read -p "Enter name of Auth database [default: acore_auth]: " DB_AUTH
DB_AUTH=${DB_AUTH:-acore_auth}
read -p "Enter name of Characters database [default: acore_characters]: " DB_CHARACTERS
DB_CHARACTERS=${DB_CHARACTERS:-acore_characters}
read -p "Enter name of World database [default: acore_world]: " DB_WORLD
DB_WORLD=${DB_WORLD:-acore_world}
# Mapping for folder names
declare -A DB_MAP=(
["$DB_AUTH"]="db_auth"
["$DB_CHARACTERS"]="db_characters"
["$DB_WORLD"]="db_world"
)
# Dump each database
for DB_NAME in "${!DB_MAP[@]}"; do
FOLDER_NAME="${DB_MAP[$DB_NAME]}"
echo "📦 Dumping database '$DB_NAME' into folder '$FOLDER_NAME'"
echo "$BASE_OUTPUT_DIR/$FOLDER_NAME"
mkdir -p "$BASE_OUTPUT_DIR/$FOLDER_NAME"
TABLES=$(mysql -u "$DB_USER" -p"$DB_PASS" -h "$DB_HOST" -P "$DB_PORT" -N -e "SHOW TABLES FROM \`$DB_NAME\`;")
if [[ -z "$TABLES" ]]; then
echo "⚠️ No tables found or failed to connect to '$DB_NAME'. Skipping."
continue
fi
while IFS= read -r raw_table; do
TABLE=$(echo "$raw_table" | tr -d '\r"' | xargs)
if [[ -n "$TABLE" ]]; then
echo " ➤ Dumping table: $TABLE"
mysqldump -u $DB_USER -p$DB_PASS -h $DB_HOST -P $DB_PORT --extended-insert $DB_NAME $TABLE > $BASE_OUTPUT_DIR/$FOLDER_NAME/$TABLE.sql
# cleanup files
sed -E '
s/VALUES[[:space:]]*/VALUES\n/;
:a
s/\),\(/\),\n\(/g;
ta
' "$BASE_OUTPUT_DIR/$FOLDER_NAME/$TABLE.sql" > "$BASE_OUTPUT_DIR/$FOLDER_NAME/${TABLE}_formatted.sql"
mv "$BASE_OUTPUT_DIR/$FOLDER_NAME/${TABLE}_formatted.sql" "$BASE_OUTPUT_DIR/$FOLDER_NAME/$TABLE.sql"
fi
done <<< "$TABLES"
done
echo "✅ Done dumping all specified databases."

View File

@ -0,0 +1,16 @@
# The AzerothCore Database Exporter for Database Squashes
> [!CAUTION]
> These steps are only for project maintainers who intend to update base files.
## Requirements
1. MySQL
2. mysqldump
## Usage
1. Run DatabaseExporter.sh from the current directory.
2. Fill in required data within the CLI.
3. The tool will autopopulate the basefile directories.
4. Done.

View File

@ -0,0 +1,52 @@
#!/usr/bin/env bash
echo "❗CAUTION"
echo "This tool is only supposed to be used by AzerothCore Maintainers."
echo "The tool is used to prepare for, and generate a database squash."
echo
echo "Before you continue make sure you have read"
echo "https://github.com/azerothcore/azerothcore-wotlk/blob/master/data/sql/base/database-squash.md"
echo
read -p "Are you sure you want to continue (Y/N)?" choice
case "$choice" in
y|Y ) echo "Starting...";;
* ) echo "Aborted"; exit 0 ;;
esac
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
if [[ "$PROJECT_ROOT" =~ ^/([a-zA-Z])/(.*) ]]; then
DRIVE_LETTER="${BASH_REMATCH[1]}"
PATH_REMAINDER="${BASH_REMATCH[2]}"
PROJECT_ROOT="${DRIVE_LETTER^^}:/${PATH_REMAINDER}"
fi
VERSION_UPDATER_PATH="$PROJECT_ROOT/apps/DatabaseSquash/VersionUpdater/versionupdater.sh"
"$VERSION_UPDATER_PATH"
echo "✅ VersionUpdater Completed..."
echo
echo "❗IMPORTANT!"
echo "1. Before you continue you need to drop all your databases."
echo "2. Run WorldServer to populate the database."
echo
echo "❗DO NOT continue before you have completed the steps above!"
echo
echo "The next step will export your database and overwrite the base files."
echo
read -p "Are you sure you want to export your database (Y/N)?" choice
case "$choice" in
y|Y ) echo "Starting...";;
* ) echo "Aborted"; exit 0 ;;
esac
DATABASE_EXPORTER_PATH="$PROJECT_ROOT/apps/DatabaseSquash/DatabaseExporter/databaseexporter.sh"
"$DATABASE_EXPORTER_PATH"
echo "✅ DatabaseExporter Completed..."
echo "✅ DatabaseSquash Completed... "
echo
read -p "Press Enter to exit..."

View File

@ -0,0 +1,84 @@
#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
if [[ "$PROJECT_ROOT" =~ ^/([a-zA-Z])/(.*) ]]; then
DRIVE_LETTER="${BASH_REMATCH[1]}"
PATH_REMAINDER="${BASH_REMATCH[2]}"
PROJECT_ROOT="${DRIVE_LETTER^^}:/${PATH_REMAINDER}"
fi
ACORE_JSON_PATH="$PROJECT_ROOT/acore.json"
DB_WORLD_UPDATE_DIR="$PROJECT_ROOT/data/sql/updates/db_world"
VERSION_LINE=$(grep '"version"' "$ACORE_JSON_PATH")
VERSION=$(echo "$VERSION_LINE" | sed -E 's/.*"version": *"([^"]+)".*/\1/')
# Parse version into parts
if [[ "$VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(.*)$ ]]; then
MAJOR="${BASH_REMATCH[1]}"
SUFFIX="${BASH_REMATCH[4]}"
NEW_VERSION="$((MAJOR + 1)).0.0$SUFFIX"
# Replace version in file
sed -i.bak -E "s/(\"version\": *\")[^\"]+(\" *)/\1$NEW_VERSION\2/" "$ACORE_JSON_PATH"
rm -f "$ACORE_JSON_PATH.bak"
echo "✅ Version updated to $NEW_VERSION"
else
echo "Error: Could not parse version string: $VERSION"
exit 1
fi
# Extract the new major version from NEW_VERSION
if [[ "$NEW_VERSION" =~ ^([0-9]+)\. ]]; then
NEW_MAJOR="${BASH_REMATCH[1]}"
else
echo "Error: Unable to extract major version from $NEW_VERSION"
exit 1
fi
# Prepare SQL content
DB_VERSION_CONTENT="'ACDB 335.${NEW_MAJOR}-dev'"
SQL_QUERY="UPDATE \`version\` SET \`db_version\`=${DB_VERSION_CONTENT}, \`cache_id\`=${NEW_MAJOR} LIMIT 1;"
# Format date as yyyy_mm_dd
TODAY=$(date +%Y_%m_%d)
# Ensure directory exists
mkdir -p "$DB_WORLD_UPDATE_DIR"
# List existing files for today
existing_files=($(find "$DB_WORLD_UPDATE_DIR" -maxdepth 1 -type f -name "${TODAY}_*.sql" 2>/dev/null))
# Determine next xx counter
# Determine next xx
COUNTER="00"
if [ ${#existing_files[@]} -gt 0 ]; then
max=0
for file in "${existing_files[@]}"; do
basename=$(basename "$file")
if [[ "$basename" =~ ^${TODAY}_([0-9]{2})\.sql$ ]]; then
num=${BASH_REMATCH[1]}
if [[ "$num" =~ ^[0-9]+$ ]] && (( 10#$num > max )); then
max=$((10#$num))
fi
fi
done
COUNTER=$(printf "%02d" $((max + 1)))
fi
# Compose final file path
SQL_FILENAME="${TODAY}_${COUNTER}.sql"
SQL_FILE_PATH="$DB_WORLD_UPDATE_DIR/$SQL_FILENAME"
# Write to file
{
echo "-- Auto-generated by VersionUpdater.sh on $(date)"
echo "$SQL_QUERY"
} > "$SQL_FILE_PATH"
echo "✅ SQL file created at $SQL_FILE_PATH"

View File

@ -0,0 +1,10 @@
# The AzerothCore Version Updater for Database Squashes
> [!CAUTION]
> These steps are only for project maintainers who intend to update base files.
## Usage
1. Run VersionUpdater.sh from the current directory.
2. The tool will update acore.json and create a new update sql file.
3. Done.

View File

@ -0,0 +1,11 @@
# The AzerothCore DatabaseSquash tool for Database Squashes
> [!CAUTION]
> These steps are only for project maintainers who intend to update base files.
## Usage
1. Run DatabaseSquash.sh from the current directory.
2. The tool will run VersionUpdater.sh and DatabaseExporter.sh
3. Follow the instructions in the CLI.
4. Done.

View File

@ -1,132 +0,0 @@
# Get the directory to acore.json
$scriptDirectory = $PSScriptRoot
$relativePath = "..\.."
$combinedPath = Join-Path -Path $scriptDirectory -ChildPath $relativePath
$fullPath = Resolve-Path -Path $combinedPath
$jsonFilePath = "$fullPath\acore.json"
# Get the directory for SQL update
$relativePathDbWorldUpdate = "..\..\data\sql\updates\db_world"
$combinedPathDbWorldUpdate = Join-Path -Path $scriptDirectory -ChildPath $relativePathDbWorldUpdate
$fullPathDbWorldUpdate = Resolve-Path -Path $combinedPathDbWorldUpdate
Write-Host " ___ _ _ ___ "
Write-Host "/ \ ___ ___ _ _ ___ | |_ | |_ / __| ___ _ _ ___ "
Write-Host "| - ||_ // -_)| '_|/ _ \| _|| \ | (__ / _ \| '_|/ -_)"
Write-Host "|_|_|/__|\___||_| \___/ \__||_||_| \___|\___/|_| \___|"
Write-Host "AzerothCore 3.3.5a - www.azerothcore.org"
Write-Host ""
Write-Host "Welcome to the AzerothCore Version Updater for database squashes!"
Write-Host ""
Write-Host "You have configured:"
Write-Host "acore.json Path: '$jsonFilePath'"
Write-Host "World SQL Updates path: '$fullPathDbWorldUpdate'"
Write-Host ""
Write-Host "Make sure you read the entire process before you continue."
Write-Host "https://github.com/azerothcore/azerothcore-wotlk/blob/master/data/sql/base/database-squash.md"
Write-Host "https://github.com/azerothcore/azerothcore-wotlk/blob/master/apps/VersionUpdater/versionupdater.md"
Write-Host ""
# Check if the user wants to continue using the tool
do {
$confirmation = Read-Host "Do you want to continue using the tool? (Y/N)"
if ($confirmation -eq 'Y' -or $confirmation -eq 'y') {
# Continue the script
Write-Host "AzerothCore Version Updater starts."
Write-Host ""
$continue = $true
}
elseif ($confirmation -eq 'N' -or $confirmation -eq 'n') {
# Exit the script
Write-Host "Exiting the AzerothCore Version Updater."
exit
}
else {
Write-Host "Invalid input. Please enter Y or N."
$continue = $null
}
} while ($continue -eq $null)
# Read the JSON file and convert it to a PowerShell object
$jsonContent = Get-Content -Path $jsonFilePath | ConvertFrom-Json
# Get the current version
$currentVersion = $jsonContent.version
# Match version components (major.minor.patch and optional suffix like -dev or -alpha)
if ($currentVersion -match '(\d+)\.(\d+)\.(\d+)(-.*)?') {
$major = $matches[1]
$minor = $matches[2]
$patch = $matches[3]
$suffix = $matches[4]
# Increment the major version
$major = [int]$major + 1
# Reset minor and patch version to 0 (if incrementing major)
$minor = 0
$patch = 0
# Reassemble the version with the suffix if it exists
$newVersion = "$major.$minor.$patch$suffix"
# Update the version in the JSON object
$jsonContent.version = $newVersion
} else {
Write-Host "Unknown error in $jsonFilePath. Exiting."
exit
}
# Convert the updated object back to JSON format
$newJsonContent = $jsonContent | ConvertTo-Json -Depth 3
# Write the updated content back to the file
$newJsonContent | Set-Content -Path $jsonFilePath
Write-Host "acore.json version updated to $newVersion"
# Create the SQL Version update file.
# Get today's date in the format YYYY_MM_DD
$today = Get-Date -Format "yyyy_MM_dd"
# Get the list of files in the directory that match the pattern "YYYY_MM_DD_versionNumber.sql"
$existingFiles = Get-ChildItem -Path $fullPathDbWorldUpdate -Filter "$today*_*.sql"
# If no files exist for today, start with version number 00
if ($existingFiles.Count -eq 0) {
[int]$newVersionNumber = 0
} else {
# Extract the version number from the existing files (e.g., YYYY_MM_DD_versionNumber.sql)
$maxVersionNumber = $existingFiles | ForEach-Object {
if ($_ -match "$today_(\d{2})\.sql") {
[int]$matches[1]
}
} | Measure-Object -Maximum | Select-Object -ExpandProperty Maximum
# Increment the version number by 1
[int]$newVersionNumber = $maxVersionNumber + 1
}
# Format the new version number as a two-digit number (e.g., 01, 02, etc.)
$formattedVersionNumber = $newVersionNumber.ToString("D2")
# Define the new filename using the date and incremented version number
$newFileName = "$today" + "_$formattedVersionNumber.sql"
$newFilePath = Join-Path -Path $fullPathDbWorldUpdate -ChildPath $newFileName
# Define the SQL content to write to the file
$tableName = '`version`'
$db_version = '`db_version`'
$db_version_content = "'ACDB 335.$major-dev'"
$cache_id = '`cache_id`'
$sqlContent = "UPDATE $tableName SET $db_version=$db_version_content, $cache_id=$major LIMIT 1;"
# Write the content to the new SQL file
$sqlContent | Set-Content -Path $newFilePath
Write-Host "SQL file created: $newFilePath"
Write-Host "SQL content: $sqlContent"
Write-Host ""
Write-Host "Version Updater completed."
Write-Host "Have a nice day :)"

View File

@ -1,53 +0,0 @@
# The AzerothCore Version Updater for Database Squashes
> [!CAUTION]
> These steps are only for project maintainers who intend to update base files.
## Description of the tool
This tool updates the version in DB and acore.json automatically. Hence, it must run from this directory.
This is how it works step-by-step:
1. Check that all paths look correct.
2. Accept to continue using the tool.
3. The tool will update the acore.json file and increment it by 1.
4. The tool will create a file with the proper UPDATE for world database in `..\..\data\sql\updates\db_world`.
## Run the tool
> [!IMPORTANT]
> This tool CAN NOT be moved outside this directory. If you do it will create files in the wrong places.
1. If you haven't run PowerShell scripts before, you'll need to adjust the execution policy.
- Open PowerShell as an Administrator.
- Run the following command to allow running scripts:
```ps
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
```
- This allows scripts to run on your system, but they need to be locally created or downloaded from trusted sources.
2. Open PowerShell (PS)
- Press Win + X and select Windows PowerShell (Admin) / Terminal (Admin)
3. Navigate to the script
- In PS, use the `cd` command to change the directory
```ps
cd "C:\AzerothCore\apps\VersionUpdater"
```
4. Run the script
- In PS, run the script
```ps
.\VersionUpdater.ps1
```
5. Follow the instructions given by the tool.
6. Now refer back to the database-squash.md instructions. (Located in ..\..\data\sql\base\)
Completed :)

View File

@ -3,12 +3,22 @@ import os
import sys
import re
import glob
import subprocess
base_dir = os.getcwd()
# Get the pending directory of the project
base_dir = os.getcwd()
pattern = os.path.join(base_dir, 'data/sql/updates/pending_db_*')
src_directory = glob.glob(pattern)
# Get files from base dir
base_pattern = os.path.join(base_dir, 'data/sql/base/db_*')
base_directory = glob.glob(base_pattern)
# Get files from archive dir
archive_pattern = os.path.join(base_dir, 'data/sql/archive/db_*')
archive_directory = glob.glob(archive_pattern)
# Global variables
error_handler = False
results = {
@ -17,7 +27,8 @@ results = {
"SQL codestyle check": "Passed",
"INSERT & DELETE safety usage check": "Passed",
"Missing semicolon check": "Passed",
"Backtick check": "Passed"
"Backtick check": "Passed",
"Directory check": "Passed"
}
# Collect all files in all directories
@ -30,6 +41,24 @@ def collect_files_from_directories(directories: list) -> list:
all_files.append(os.path.join(root, file))
return all_files
# Used to find changed or added files compared to master.
def get_changed_files() -> list:
subprocess.run(["git", "fetch", "origin", "master"], check=True)
result = subprocess.run(
["git", "diff", "--name-status", "origin/master"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
changed_files = []
for line in result.stdout.strip().splitlines():
if not line:
continue
status, path = line.split(maxsplit=1)
if status in ("A", "M"):
changed_files.append(path)
return changed_files
# Main function to parse all the files of the project
def parsing_file(files: list) -> None:
print("Starting AzerothCore SQL Codestyle check...")
@ -38,19 +67,32 @@ def parsing_file(files: list) -> None:
print("https://www.azerothcore.org/wiki/sql-standards")
print(" ")
# Iterate over all files
# Iterate over all files in data/sql/updates/pending_db_*
for file_path in files:
try:
with open(file_path, 'r', encoding='utf-8') as file:
multiple_blank_lines_check(file, file_path)
trailing_whitespace_check(file, file_path)
sql_check(file, file_path)
insert_delete_safety_check(file, file_path)
semicolon_check(file, file_path)
backtick_check(file, file_path)
except UnicodeDecodeError:
print(f"\n❌ Could not decode file {file_path}")
sys.exit(1)
if "base" not in file_path and "archive" not in file_path:
try:
with open(file_path, 'r', encoding='utf-8') as file:
multiple_blank_lines_check(file, file_path)
trailing_whitespace_check(file, file_path)
sql_check(file, file_path)
insert_delete_safety_check(file, file_path)
semicolon_check(file, file_path)
backtick_check(file, file_path)
except UnicodeDecodeError:
print(f"\n❌ Could not decode file {file_path}")
sys.exit(1)
# Make sure we only check changed or added files when we work with base/archive paths
changed_files = get_changed_files()
# Iterate over all file paths
for file_path in changed_files:
if "base" in file_path or "archive" in file_path:
try:
with open(file_path, "r", encoding="utf-8") as f:
directory_check(f, file_path)
except UnicodeDecodeError:
print(f"\n❌ Could not decode file {file_path}")
sys.exit(1)
# Output the results
print("\n ")
@ -172,11 +214,6 @@ def semicolon_check(file: io, file_path: str) -> None:
file.seek(0) # Reset file pointer to the start
check_failed = False
sql_statement_regex = re.compile(r'^\s*(SELECT|INSERT|UPDATE|DELETE|REPLACE|SET)\b', re.IGNORECASE)
block_comment_start = re.compile(r'/\*')
block_comment_end = re.compile(r'\*/')
inline_comment = re.compile(r'--.*')
query_open = False
in_block_comment = False
inside_values_block = False
@ -323,8 +360,31 @@ def backtick_check(file: io, file_path: str) -> None:
error_handler = True
results["Backtick check"] = "Failed"
def directory_check(file: io, file_path: str) -> None:
global error_handler, results
file.seek(0)
check_failed = False
# Normalize path and split into parts
normalized_path = os.path.normpath(file_path) # handles / and \
path_parts = normalized_path.split(os.sep)
# Fail if '/base/' is part of the path
if "base" in path_parts:
print(f"{file_path} is changed/added in the base directory.\nIf this is intended, please notify a maintainer.")
check_failed = True
# Fail if '/archive/' is part of the path
if "archive" in path_parts:
print(f"{file_path} is changed/added in the archive directory.\nIf this is intended, please notify a maintainer.")
check_failed = True
if check_failed:
error_handler = True
results["Directory check"] = "Failed"
# Collect all files from matching directories
all_files = collect_files_from_directories(src_directory)
all_files = collect_files_from_directories(src_directory) + collect_files_from_directories(base_directory) + collect_files_from_directories(archive_directory)
# Main function
parsing_file(all_files)

View File

@ -1 +0,0 @@
config.sh

View File

@ -1,12 +0,0 @@
This script is used by devs to export the databases to base directories
You should use it on clean databases
## USAGE
NOTE: this script is only working under unix currently
1) You must create a config.sh file changing DB connection configurations
of /conf/config.sh.dist
2) Run the db_export.sh script and wait

View File

@ -1,52 +0,0 @@
#!/usr/bin/env bash
ROOTPATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../../" && pwd )"
source "$ROOTPATH/apps/bash_shared/includes.sh"
if [ -f "./config.sh" ]; then
source "./config.sh" # should overwrite previous
fi
echo "This is a dev-only procedure to export the DB into the SQL base files. All base files will be overwritten."
read -p "Are you sure you want to continue (y/N)? " choice
case "$choice" in
y|Y ) echo "Exporting the DB into the SQL base files...";;
* ) return;;
esac
echo "===== STARTING PROCESS ====="
function export() {
echo "Working on: "$1
database=$1
var_base_path="DB_"$database"_PATHS"
base_path=${!var_base_path%/}
base_conf="TPATH="$base_path";\
CLEANFOLDER=1; \
CHMODE=0; \
TEXTDUMPS=0; \
PARSEDUMP=1; \
FULL=0; \
DUMPOPTS='--skip-comments --skip-set-charset --routines --extended-insert --order-by-primary --single-transaction --quick'; \
"
var_base_conf="DB_"$database"_CONF"
base_conf=$base_conf${!var_base_conf}
var_base_name="DB_"$database"_NAME"
base_name=${!var_base_name}
bash "$AC_PATH_DEPS/acore/mysql-tools/mysql-tools" "dump" "" "$base_name" "" "$base_conf"
}
for db in ${DATABASES[@]}
do
export "$db"
done
echo "===== DONE ====="

View File

@ -3,23 +3,18 @@ New process around handling database squashes since https://github.com/azerothco
> [!CAUTION]
> These steps are only for project maintainers who intend to update base files.
How to do the squash.
## Requirements
1. MySQL
2. mysqldump
## Usage
> [!IMPORTANT]
> A squash needs to be done on a clean database. Drop all tables in Auth, Characters and World.
1. Update the acore.json and DB version by running VersionUpdater.ps1 (Located in ..\apps\VersionUpdater\)
> [!NOTE]
> Read the versionupdater.md file to use it properly.
2. Drop all your databases, and run Worldserver to populate a clean database.
3. Export the databases using the DatabaseExporter.ps1 (Located in ..\apps\DatabaseExporter\)
> [!NOTE]
> Read the databaseexporter.md file to use it properly.
6. Make a PR
1. Run DatabaseSquash.sh (Located in ..\apps\DatabaseSquash\)
2. Make a PR
> [!IMPORTANT]
> No DB PRs should be merged during this process!

View File

@ -0,0 +1,7 @@
-- DB update 2025_06_21_03 -> 2025_06_27_00
-- Bound Fire Elemental
UPDATE `creature_template` SET `dmgschool` = 2, `spell_school_immune_mask` = 4 WHERE `entry` IN (30416, 31453);
-- Bound Water Elemental
UPDATE `creature_template` SET `dmgschool` = 4, `spell_school_immune_mask` = 16 WHERE `entry` IN (30419, 31454);
-- Bound Air Elemental
UPDATE `creature_template` SET `dmgschool` = 3, `spell_school_immune_mask` = 8 WHERE `entry` IN (30418, 31452);

View File

@ -0,0 +1,9 @@
-- DB update 2025_06_27_00 -> 2025_06_27_01
--
DELETE FROM `acore_string` WHERE `entry` = 288;
INSERT INTO `acore_string` (`entry`,`content_default`) VALUES (288,'Cannot go to spawn {} as only {} exist');
UPDATE `command` SET `help`='Syntax: .go creature id #creature_entry [#spawn] Teleports you to first (if no #spawn provided) spawn the given creature entry. ' WHERE `name` = 'go creature id';
DELETE FROM `command` WHERE `name` = 'go gameobject id';
INSERT INTO `command` VALUES('go gameobject id',1,'Syntax: .go gameobject id #gameobject_entry [#spawn] Teleports you to first (if no #spawn provided) spawn the given gameobject entry.');

View File

@ -0,0 +1,2 @@
-- DB update 2025_06_27_01 -> 2025_06_27_02
UPDATE `command` SET `help`='Syntax: .account create $account $password $email\r\n\r\nCreate account and set password to it.\r\n$email is optional, can be left blank.' WHERE `name`='account create';

View File

@ -0,0 +1,64 @@
-- DB update 2025_06_27_02 -> 2025_06_28_00
-- Remove respawn time for Citizens of Havenshire (Not needed)
DELETE FROM `smart_scripts` WHERE (`source_type` = 0) AND (`entryorguid` IN (-128919, -128967, -128918, -128965, -128917, -128964, -128915, -128963, -128962, -128914, -128913, -128961, -128960, -128912, -128911, -128959, -128922, -128979, -128927, -128986, -128928, -128970, -128930, -128966, -128973, -128924, -128976, -128954, -128978, -128929, -128980, -128934, -128981, -128926, -128968, -128920, -128993, -128916, -128991, -128935, -128992, -128948, -128958, -128910)) AND (`id` IN (8));
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
(-128919, 0, 8, 0, 109, 0, 100, 0, 0, 12896700, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896700 Finished - Despawn In 2000 ms'),
(-128967, 0, 8, 0, 109, 0, 100, 0, 0, 12896700, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896700 Finished - Despawn In 2000 ms'),
(-128918, 0, 8, 0, 109, 0, 100, 0, 0, 12896500, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896500 Finished - Despawn In 2000 ms'),
(-128965, 0, 8, 0, 109, 0, 100, 0, 0, 12896500, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896500 Finished - Despawn In 2000 ms'),
(-128917, 0, 8, 0, 109, 0, 100, 0, 0, 12896400, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896400 Finished - Despawn In 2000 ms'),
(-128964, 0, 8, 0, 109, 0, 100, 0, 0, 12896400, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896400 Finished - Despawn In 2000 ms'),
(-128915, 0, 8, 0, 109, 0, 100, 0, 0, 12896300, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896300 Finished - Despawn In 2000 ms'),
(-128963, 0, 8, 0, 109, 0, 100, 0, 0, 12896300, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896300 Finished - Despawn In 2000 ms'),
(-128962, 0, 8, 0, 109, 0, 100, 0, 0, 12896200, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896200 Finished - Despawn In 2000 ms'),
(-128914, 0, 8, 0, 109, 0, 100, 0, 0, 12896200, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896200 Finished - Despawn In 2000 ms'),
(-128913, 0, 8, 0, 109, 0, 100, 0, 0, 12896100, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896100 Finished - Despawn In 2000 ms'),
(-128961, 0, 8, 0, 109, 0, 100, 0, 0, 12896100, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896100 Finished - Despawn In 2000 ms'),
(-128960, 0, 8, 0, 109, 0, 100, 0, 0, 12896000, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896000 Finished - Despawn In 2000 ms'),
(-128912, 0, 8, 0, 109, 0, 100, 0, 0, 12896000, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896000 Finished - Despawn In 2000 ms'),
(-128911, 0, 8, 0, 109, 0, 100, 0, 0, 12895900, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12895900 Finished - Despawn In 2000 ms'),
(-128959, 0, 8, 0, 109, 0, 100, 0, 0, 12895900, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12895900 Finished - Despawn In 2000 ms'),
(-128922, 0, 8, 0, 109, 0, 100, 0, 0, 12892200, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12892200 Finished - Despawn In 2000 ms'),
(-128979, 0, 8, 0, 109, 0, 100, 0, 0, 12892200, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12892200 Finished - Despawn In 2000 ms'),
(-128927, 0, 8, 0, 109, 0, 100, 0, 0, 12892700, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12892700 Finished - Despawn In 2000 ms'),
(-128986, 0, 8, 0, 109, 0, 100, 0, 0, 12892700, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12892700 Finished - Despawn In 2000 ms'),
(-128928, 0, 8, 0, 109, 0, 100, 0, 0, 12892800, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12892800 Finished - Despawn In 2000 ms'),
(-128970, 0, 8, 0, 109, 0, 100, 0, 0, 12892800, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12892800 Finished - Despawn In 2000 ms'),
(-128930, 0, 8, 0, 109, 0, 100, 0, 0, 12893000, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12893000 Finished - Despawn In 2000 ms'),
(-128966, 0, 8, 0, 109, 0, 100, 0, 0, 12893000, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12893000 Finished - Despawn In 2000 ms'),
(-128973, 0, 8, 0, 109, 0, 100, 0, 0, 12897300, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12897300 Finished - Despawn In 2000 ms'),
(-128924, 0, 8, 0, 109, 0, 100, 0, 0, 12897300, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12897300 Finished - Despawn In 2000 ms'),
(-128976, 0, 8, 0, 109, 0, 100, 0, 0, 12897600, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12897600 Finished - Despawn In 2000 ms'),
(-128954, 0, 8, 0, 109, 0, 100, 0, 0, 12897600, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12897600 Finished - Despawn In 2000 ms'),
(-128978, 0, 8, 0, 109, 0, 100, 0, 0, 12897800, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12897800 Finished - Despawn In 2000 ms'),
(-128929, 0, 8, 0, 109, 0, 100, 0, 0, 12897800, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12897800 Finished - Despawn In 2000 ms'),
(-128980, 0, 8, 0, 109, 0, 100, 0, 0, 12898000, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12898000 Finished - Despawn In 2000 ms'),
(-128934, 0, 8, 0, 109, 0, 100, 0, 0, 12898000, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12898000 Finished - Despawn In 2000 ms'),
(-128981, 0, 8, 0, 109, 0, 100, 0, 0, 12898100, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12898100 Finished - Despawn In 2000 ms'),
(-128926, 0, 8, 0, 109, 0, 100, 0, 0, 12898100, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12898100 Finished - Despawn In 2000 ms'),
(-128968, 0, 8, 0, 109, 0, 100, 0, 0, 12896800, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896800 Finished - Despawn In 2000 ms'),
(-128920, 0, 8, 0, 109, 0, 100, 0, 0, 12896800, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12896800 Finished - Despawn In 2000 ms'),
(-128993, 0, 8, 0, 109, 0, 100, 0, 0, 12899300, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12899300 Finished - Despawn In 2000 ms'),
(-128916, 0, 8, 0, 109, 0, 100, 0, 0, 12899300, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12899300 Finished - Despawn In 2000 ms'),
(-128991, 0, 8, 0, 109, 0, 100, 0, 0, 12899100, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12899100 Finished - Despawn In 2000 ms'),
(-128935, 0, 8, 0, 109, 0, 100, 0, 0, 12899100, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12899100 Finished - Despawn In 2000 ms'),
(-128992, 0, 8, 0, 109, 0, 100, 0, 0, 12899200, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12899200 Finished - Despawn In 2000 ms'),
(-128948, 0, 8, 0, 109, 0, 100, 0, 0, 12899200, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12899200 Finished - Despawn In 2000 ms'),
(-128958, 0, 8, 0, 109, 0, 100, 0, 0, 12895800, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12895800 Finished - Despawn In 2000 ms'),
(-128910, 0, 8, 0, 109, 0, 100, 0, 0, 12895800, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Citizen of Havenshire - On Path 12895800 Finished - Despawn In 2000 ms');
-- Remove Respawn Time for Stallions/Mares/Colts (Not needed)
DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = -129215);
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
(-129215, 0, 2, 0, 11, 0, 100, 0, 0, 0, 0, 0, 0, 0, 232, 12921500, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Respawn - Start Path 12921500'),
(-129215, 0, 3, 4, 109, 0, 100, 0, 0, 12921500, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Path 12921500 Finished - Despawn In 2000 ms'),
(-129215, 0, 4, 5, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 10, 129236, 28606, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Path 12921500 Finished - Despawn In 2000 ms'),
(-129215, 0, 5, 6, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 10, 129246, 28607, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Path 12921500 Finished - Despawn In 2000 ms'),
(-129215, 0, 6, 7, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 10, 129249, 28607, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Path 12921500 Finished - Despawn In 2000 ms'),
(-129215, 0, 7, 8, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 10, 129251, 28607, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Path 12921500 Finished - Despawn In 2000 ms'),
(-129215, 0, 8, 9, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 10, 129248, 28607, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Path 12921500 Finished - Despawn In 2000 ms'),
(-129215, 0, 9, 10, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 10, 129245, 28607, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Path 12921500 Finished - Despawn In 2000 ms'),
(-129215, 0, 10, 11, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 10, 129214, 28605, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Path 12921500 Finished - Despawn In 2000 ms'),
(-129215, 0, 11, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 2000, 0, 0, 0, 0, 0, 10, 129235, 28606, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Path 12921500 Finished - Despawn In 2000 ms'),
(-129215, 0, 12, 0, 25, 0, 100, 0, 0, 0, 0, 0, 0, 0, 48, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Havenshire Stallion - On Reset - Set Active On');

View File

@ -0,0 +1,4 @@
-- DB update 2025_06_28_00 -> 2025_06_29_00
--
DELETE FROM `spell_script_names` WHERE `spell_id` = 29313 AND `ScriptName` = 'spell_gen_cooldown_all';
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (29313 , 'spell_gen_cooldown_all');

View File

@ -0,0 +1,2 @@
-- DB update 2025_06_29_00 -> 2025_06_29_01
UPDATE `creature_template` SET `unit_flags` = `unit_flags` | 256 WHERE `entry` = 23741;

View File

@ -0,0 +1,6 @@
-- DB update 2025_06_29_01 -> 2025_06_29_02
--
DELETE FROM `acore_string` WHERE `entry` IN (1184,1185);
INSERT INTO `acore_string` (`entry`, `content_default`) VALUES
(1184, '| Guild Ranks:'),
(1185, '| {} - {}');

View File

@ -0,0 +1,3 @@
-- DB update 2025_06_29_02 -> 2025_06_29_03
--
DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_chapter5_rebuke' AND `spell_id` = 53680;

View File

@ -0,0 +1,6 @@
-- DB update 2025_06_29_03 -> 2025_06_30_00
--
DELETE FROM `acore_string` WHERE `entry` IN (6617, 6618);
INSERT INTO `acore_string` (`entry`, `content_default`) VALUES
(6617, 'GM Spectator is ON'),
(6618, 'GM Spectator is OFF');

View File

@ -78,7 +78,7 @@ void LoginDatabaseConnection::DoPrepareStatements()
PrepareStatement(LOGIN_DEL_REALM_CHARACTERS, "DELETE FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_REP_REALM_CHARACTERS, "REPLACE INTO realmcharacters (numchars, acctid, realmid) VALUES (?, ?, ?)", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_SUM_REALM_CHARACTERS, "SELECT SUM(numchars) FROM realmcharacters WHERE acctid = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_ACCOUNT, "INSERT INTO account(username, salt, verifier, expansion, joindate) VALUES(?, ?, ?, ?, NOW())", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_ACCOUNT, "INSERT INTO account(username, salt, verifier, expansion, reg_mail, email, joindate) VALUES(?, ?, ?, ?, ?, ?, NOW())", CONNECTION_ASYNC);
PrepareStatement(LOGIN_INS_REALM_CHARACTERS_INIT, "INSERT INTO realmcharacters (realmid, acctid, numchars) SELECT realmlist.id, account.id, 0 FROM realmlist, account LEFT JOIN realmcharacters ON acctid=account.id WHERE acctid IS NULL", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_EXPANSION, "UPDATE account SET expansion = ? WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_UPD_ACCOUNT_LOCK, "UPDATE account SET locked = ? WHERE id = ?", CONNECTION_ASYNC);

View File

@ -193,6 +193,9 @@ SpellCastResult UnitAI::DoCast(uint32 spellId)
{
DefaultTargetSelector targetSelector(me, spellInfo->GetMaxRange(false), false, true, 0);
target = SelectTarget(SelectTargetMethod::Random, 0, [&](Unit* target) {
if (!target)
return false;
if (target->IsPlayer())
{
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))
@ -225,6 +228,9 @@ SpellCastResult UnitAI::DoCast(uint32 spellId)
DefaultTargetSelector defaultTargetSelector(me, range, false, true, -(int32)spellId);
auto targetSelector = [&](Unit* target) {
if (!target)
return false;
if (target->IsPlayer())
{
if (spellInfo->HasAttribute(SPELL_ATTR5_NOT_ON_PLAYER))

View File

@ -165,9 +165,10 @@ struct PowerUsersSelector : public Acore::unary_function<Unit*, bool>
}
};
struct FarthestTargetSelector : public Acore::unary_function<Unit*, bool>
// Simple selector based on range and Los
struct RangeSelector : public Acore::unary_function<Unit*, bool>
{
FarthestTargetSelector(Unit const* unit, float maxDist, bool playerOnly, bool inLos, float minDist = 0.f) : _me(unit), _minDist(minDist), _maxDist(maxDist), _playerOnly(playerOnly), _inLos(inLos) {}
RangeSelector(Unit const* unit, float maxDist, bool playerOnly, bool inLos, float minDist = 0.f) : _me(unit), _minDist(minDist), _maxDist(maxDist), _playerOnly(playerOnly), _inLos(inLos) {}
bool operator()(Unit const* target) const
{

View File

@ -2506,12 +2506,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
}
case SMART_ACTION_START_CLOSEST_WAYPOINT:
{
std::vector<uint32> waypoints;
std::copy_if(e.action.closestWaypointFromList.wps.begin(), e.action.closestWaypointFromList.wps.end(),
std::back_inserter(waypoints), [](uint32 wp) { return wp != 0; });
float distanceToClosest = std::numeric_limits<float>::max();
WayPoint* closestWp = nullptr;
uint32 closestWpId = 0;
for (WorldObject* target : targets)
{
@ -2519,29 +2515,34 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
{
if (IsSmart(creature))
{
for (uint32 wp : waypoints)
for (uint32 wp = e.action.startClosestWaypoint.pathId1; wp <= e.action.startClosestWaypoint.pathId2; ++wp)
{
WPPath* path = sSmartWaypointMgr->GetPath(wp);
if (!path || path->empty())
continue;
auto itrWp = path->find(0);
auto itrWp = path->find(1);
if (itrWp != path->end())
{
if (WayPoint* wp = itrWp->second)
if (WayPoint* wpData = itrWp->second)
{
float distToThisPath = creature->GetDistance(wp->x, wp->y, wp->z);
float distToThisPath = creature->GetExactDistSq(wpData->x, wpData->y, wpData->z);
if (distToThisPath < distanceToClosest)
{
distanceToClosest = distToThisPath;
closestWp = wp;
closestWpId = wp;
}
}
}
}
if (closestWp)
CAST_AI(SmartAI, creature->AI())->StartPath(false, closestWp->id, true);
if (closestWpId)
{
bool repeat = e.action.startClosestWaypoint.repeat;
bool run = e.action.startClosestWaypoint.run;
CAST_AI(SmartAI, creature->AI())->StartPath(repeat, closestWpId, run);
}
}
}
}
@ -3515,7 +3516,7 @@ void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e,
case SMART_TARGET_FARTHEST:
if (me)
{
if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MinDistance, 0, FarthestTargetSelector(me, e.target.farthest.maxDist, e.target.farthest.playerOnly, e.target.farthest.isInLos, e.target.farthest.minDist)))
if (Unit* u = me->AI()->SelectTarget(SelectTargetMethod::MinDistance, 0, RangeSelector(me, e.target.farthest.maxDist, e.target.farthest.playerOnly, e.target.farthest.isInLos, e.target.farthest.minDist)))
targets.push_back(u);
}
break;

View File

@ -806,7 +806,7 @@ bool SmartAIMgr::CheckUnusedActionParams(SmartScriptHolder const& e)
case SMART_ACTION_REMOVE_POWER: return sizeof(SmartAction::power);
case SMART_ACTION_GAME_EVENT_STOP: return sizeof(SmartAction::gameEventStop);
case SMART_ACTION_GAME_EVENT_START: return sizeof(SmartAction::gameEventStart);
case SMART_ACTION_START_CLOSEST_WAYPOINT: return sizeof(SmartAction::closestWaypointFromList);
case SMART_ACTION_START_CLOSEST_WAYPOINT: return sizeof(SmartAction::startClosestWaypoint);
case SMART_ACTION_RISE_UP: return sizeof(SmartAction::moveRandom);
case SMART_ACTION_RANDOM_SOUND: return sizeof(SmartAction::randomSound);
case SMART_ACTION_SET_CORPSE_DELAY: return sizeof(SmartAction::corpseDelay);
@ -1536,15 +1536,22 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
break;
}
case SMART_ACTION_START_CLOSEST_WAYPOINT:
{
if (e.action.startClosestWaypoint.pathId1 == 0 || e.action.startClosestWaypoint.pathId2 == 0 || e.action.startClosestWaypoint.pathId2 < e.action.startClosestWaypoint.pathId1)
{
if (std::all_of(e.action.closestWaypointFromList.wps.begin(), e.action.closestWaypointFromList.wps.end(), [](uint32 wp) { return wp == 0; }))
{
LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} does not have any non-zero waypoint id",
e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
return false;
}
break;
LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} has invalid pathId1 or pathId2, it must be greater than 0 and pathId1 > pathId2",
e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType());
return false;
}
if (e.action.startClosestWaypoint.repeat > 1 || e.action.startClosestWaypoint.run > 1)
{
LOG_ERROR("sql.sql", "SmartAIMgr: Entry {} SourceType {} Event {} Action {} has invalid run ({}) or repeat ({}) parameter, must be 0 or 1.",
e.entryOrGuid, e.GetScriptType(), e.event_id, e.GetActionType(),
e.action.startClosestWaypoint.repeat, e.action.startClosestWaypoint.run);
return false;
}
break;
}
case SMART_ACTION_INVOKER_CAST:
if (e.GetScriptType() != SMART_SCRIPT_TYPE_TIMED_ACTIONLIST && e.GetEventType() != SMART_EVENT_LINK && !EventHasInvoker(e.event.type))
{

View File

@ -1284,8 +1284,11 @@ struct SmartAction
struct
{
std::array<uint32, SMART_ACTION_PARAM_COUNT> wps;
} closestWaypointFromList;
uint32 pathId1;
uint32 pathId2;
uint32 repeat;
uint32 run;
} startClosestWaypoint;
struct
{

View File

@ -27,7 +27,7 @@
namespace AccountMgr
{
AccountOpResult CreateAccount(std::string username, std::string password)
AccountOpResult CreateAccount(std::string username, std::string password, std::string email /*= ""*/)
{
if (utf8length(username) > MAX_ACCOUNT_STR)
return AOR_NAME_TOO_LONG; // username's too long
@ -35,8 +35,12 @@ namespace AccountMgr
if (utf8length(password) > MAX_PASS_STR)
return AOR_PASS_TOO_LONG; // password's too long
if (utf8length(email) > MAX_EMAIL_STR)
return AOR_EMAIL_TOO_LONG; // email is too long
Utf8ToUpperOnlyLatin(username);
Utf8ToUpperOnlyLatin(password);
Utf8ToUpperOnlyLatin(email);
if (GetId(username))
return AOR_NAME_ALREADY_EXIST; // username does already exist
@ -48,6 +52,8 @@ namespace AccountMgr
stmt->SetData(1, salt);
stmt->SetData(2, verifier);
stmt->SetData(3, uint8(sWorld->getIntConfig(CONFIG_EXPANSION)));
stmt->SetData(4, email);
stmt->SetData(5, email);
LoginDatabase.Execute(stmt);

View File

@ -38,7 +38,7 @@ enum AccountOpResult
namespace AccountMgr
{
AccountOpResult CreateAccount(std::string username, std::string password);
AccountOpResult CreateAccount(std::string username, std::string password, std::string email = "");
AccountOpResult DeleteAccount(uint32 accountId);
AccountOpResult ChangeUsername(uint32 accountId, std::string newUsername, std::string newPassword);
AccountOpResult ChangePassword(uint32 accountId, std::string newPassword);

View File

@ -788,9 +788,7 @@ void Creature::Update(uint32 diff)
m_moveBackwardsMovementTime = urand(MOVE_BACKWARDS_CHECK_INTERVAL, MOVE_BACKWARDS_CHECK_INTERVAL * 3);
}
else
{
m_moveBackwardsMovementTime -= diff;
}
// Circling the target
if (diff >= m_moveCircleMovementTime)
@ -799,9 +797,17 @@ void Creature::Update(uint32 diff)
m_moveCircleMovementTime = urand(MOVE_CIRCLE_CHECK_INTERVAL, MOVE_CIRCLE_CHECK_INTERVAL * 2);
}
else
{
m_moveCircleMovementTime -= diff;
// Periodically check if able to move, if not, extend leash timer
if (diff >= m_extendLeashTime)
{
if (!CanFreeMove())
UpdateLeashExtensionTime();
m_extendLeashTime = EXTEND_LEASH_CHECK_INTERVAL;
}
else
m_extendLeashTime -= diff;
}
// Call for assistance if not disabled

View File

@ -396,8 +396,10 @@ public:
bool IsFreeToMove();
static constexpr uint32 MOVE_CIRCLE_CHECK_INTERVAL = 3000;
static constexpr uint32 MOVE_BACKWARDS_CHECK_INTERVAL = 2000;
static constexpr uint32 EXTEND_LEASH_CHECK_INTERVAL = 3000;
uint32 m_moveCircleMovementTime = MOVE_CIRCLE_CHECK_INTERVAL;
uint32 m_moveBackwardsMovementTime = MOVE_BACKWARDS_CHECK_INTERVAL;
uint32 m_extendLeashTime = EXTEND_LEASH_CHECK_INTERVAL;
[[nodiscard]] bool HasSwimmingFlagOutOfCombat() const
{

View File

@ -10002,6 +10002,16 @@ void Player::RemoveSpellMods(Spell* spell)
continue;
}
}
// ROGUE MUTILATE WITH COLD BLOOD
if (spellInfo->Id == 5374)
{
SpellInfo const* sp = mod->ownerAura->GetSpellInfo();
if (sp->Id == 14177) // Cold Blood
{
mod->charges = 1;
continue;
}
}
if (mod->ownerAura->DropCharge(AURA_REMOVE_BY_EXPIRE))
itr = m_spellMods[i].begin();
@ -13631,7 +13641,11 @@ void Player::_LoadSkills(PreparedQueryResult result)
SkillRaceClassInfoEntry const* rcEntry = GetSkillRaceClassInfo(skill, getRace(), getClass());
if (!rcEntry)
{
LOG_ERROR("entities.player", "Character {} has skill {} that does not exist.", GetGUID().ToString(), skill);
LOG_ERROR("entities.player", "Player {} (GUID: {}), has skill ({}) that is invalid for the race/class combination (Race: {}, Class: {}). Will be deleted.",
GetName(), GetGUID().GetCounter(), skill, getRace(), getClass());
// Mark skill for deletion in the database
mSkillStatus.insert(SkillStatusMap::value_type(skill, SkillStatusData(0, SKILL_DELETED)));
continue;
}
@ -13652,7 +13666,8 @@ void Player::_LoadSkills(PreparedQueryResult result)
if (value == 0)
{
LOG_ERROR("entities.player", "Character {} has skill {} with value 0. Will be deleted.", GetGUID().ToString(), skill);
LOG_ERROR("entities.player", "Player {} (GUID: {}), has skill ({}) with value 0. Will be deleted.",
GetName(), GetGUID().GetCounter(), skill);
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHARACTER_SKILL);

View File

@ -597,6 +597,7 @@ enum PlayerExtraFlags
PLAYER_EXTRA_SPECTATOR_ON = 0x0080, // Marks if player is spectactor
PLAYER_EXTRA_PVP_DEATH = 0x0100, // store PvP death status until corpse creating.
PLAYER_EXTRA_SHOW_DK_PET = 0x0400, // Marks if player should see ghoul on login screen
PLAYER_EXTRA_GM_SPECTATOR = 0x0800,
};
// 2^n values
@ -1177,6 +1178,9 @@ public:
void SetGameMaster(bool on);
[[nodiscard]] bool isGMChat() const { return m_ExtraFlags & PLAYER_EXTRA_GM_CHAT; }
void SetGMChat(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_GM_CHAT; else m_ExtraFlags &= ~PLAYER_EXTRA_GM_CHAT; }
[[nodiscard]] bool IsGMSpectator() const { return m_ExtraFlags & PLAYER_EXTRA_GM_SPECTATOR; }
void SetGMSpectator(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_GM_SPECTATOR; else m_ExtraFlags &= ~PLAYER_EXTRA_GM_SPECTATOR; }
[[nodiscard]] bool isTaxiCheater() const { return m_ExtraFlags & PLAYER_EXTRA_TAXICHEAT; }
void SetTaxiCheater(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_TAXICHEAT; else m_ExtraFlags &= ~PLAYER_EXTRA_TAXICHEAT; }
[[nodiscard]] bool isGMVisible() const { return !(m_ExtraFlags & PLAYER_EXTRA_GM_INVISIBLE); }

View File

@ -20791,6 +20791,10 @@ void Unit::PatchValuesUpdate(ByteBuffer& valuesUpdateBuf, BuildValuesCachePosPoi
{
valuesUpdateBuf.put(posPointers.UnitFieldFactionTemplatePos, uint32(target->GetFaction()));
}
else if (target->IsGMSpectator() && IsControlledByPlayer())
{
valuesUpdateBuf.put(posPointers.UnitFieldFactionTemplatePos, uint32(target->GetFaction()));
}
}
sScriptMgr->OnPatchValuesUpdate(this, valuesUpdateBuf, posPointers, target);

View File

@ -331,7 +331,8 @@ enum AcoreStrings
LANG_COMMAND_WHISPERON = 285,
LANG_COMMAND_WHISPEROFF = 286,
LANG_COMMAND_CREATGUIDNOTFOUND = 287,
// TICKET STRINGS NEED REWRITE // 288-296 FREE
LANG_COMMAND_GONOTENOUGHSPAWNS = 288,
// TICKET STRINGS NEED REWRITE // 289-296 FREE
// END
LANG_COMMAND_WANDER_DISTANCE = 297,
@ -971,7 +972,9 @@ enum AcoreStrings
LANG_GUILD_INFO_BANK_GOLD = 1181,
LANG_GUILD_INFO_MOTD = 1182,
LANG_GUILD_INFO_EXTRA_INFO = 1183,
// Room for more level 3 1184-1198 not used
LANG_GUILD_INFO_RANKS = 1184,
LANG_GUILD_INFO_RANKS_LIST = 1185,
// Room for more level 3 1186-1198 not used
// Debug commands
LANG_DO_NOT_USE_6X_DEBUG_AREATRIGGER_LEFT = 1999,
@ -1168,8 +1171,10 @@ enum AcoreStrings
LANG_GM_ANNOUNCE_COLOR = 6615,
LANG_GM_SILENCE = 6616, // "Silence is ON for %s" - Spell 1852
LANG_GM_SPECTATOR_ON = 6617,
LANG_GM_SPECTATOR_OFF = 6618,
// Free strings 6617-7522
// Free strings 6619-7522
LANG_WORLD_CLOSED = 7523,
LANG_WORLD_OPENED = 7524,

View File

@ -153,18 +153,20 @@ bool ChaseMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff)
MovementInform(owner);
}
if (owner->movespline->Finalized())
{ // Mobs should chase you infinitely if you stop and wait every few seconds.
i_leashExtensionTimer.Update(time_diff);
if (i_leashExtensionTimer.Passed())
{
i_leashExtensionTimer.Reset(5000);
if (cOwner)
if (cOwner)
{
if (owner->movespline->Finalized() && cOwner->IsWithinMeleeRange(target))
{ // Mobs should chase you infinitely if you stop and wait every few seconds.
i_leashExtensionTimer.Update(time_diff);
if (i_leashExtensionTimer.Passed())
{
i_leashExtensionTimer.Reset(cOwner->GetAttackTime(BASE_ATTACK));
cOwner->UpdateLeashExtensionTime();
}
}
else if (i_recalculateTravel)
i_leashExtensionTimer.Reset(cOwner->GetAttackTime(BASE_ATTACK));
}
else if (i_recalculateTravel)
i_leashExtensionTimer.Reset(5000);
// if the target moved, we have to consider whether to adjust
if (!_lastTargetPosition || target->GetPosition() != _lastTargetPosition.value() || mutualChase != _mutualChase || !owner->IsWithinLOSInMap(target))
@ -298,6 +300,7 @@ void ChaseMovementGenerator<Creature>::DoInitialize(Creature* owner)
i_path = nullptr;
_lastTargetPosition.reset();
i_recheckDistance.Reset(0);
i_leashExtensionTimer.Reset(owner->GetAttackTime(BASE_ATTACK));
owner->SetWalk(false);
owner->AddUnitState(UNIT_STATE_CHASE);
}

View File

@ -24,7 +24,6 @@
class AchievementGlobalMgr;
class AchievementMgr;
class ArenaTeam;
class AuctionEntry;
class AuctionHouseMgr;
class AuctionHouseObject;
class Aura;
@ -98,6 +97,7 @@ enum WeatherState : uint32;
struct AchievementCriteriaEntry;
struct AchievementEntry;
struct AreaTrigger;
struct AuctionEntry;
struct CompletedAchievementData;
struct Condition;
struct ConditionSourceInfo;

View File

@ -4544,6 +4544,11 @@ void Spell::finish(bool ok)
// Xinef: Reset cooldown event in case of fail cast
if (m_spellInfo->IsCooldownStartedOnEvent())
m_caster->ToPlayer()->SendCooldownEvent(m_spellInfo, 0, 0, false);
// Rogue fix: Remove Cold Blood if Mutilate off-hand failed
if (m_spellInfo->Id == 27576) // Mutilate, off-hand
if (m_caster->HasAura(14177))
m_caster->RemoveAura(14177);
}
return;
}

View File

@ -5037,7 +5037,7 @@ void Spell::EffectLeapBack(SpellEffIndex effIndex)
float speedxy = m_spellInfo->Effects[effIndex].MiscValue / 10.0f;
float speedz = damage / 10.0f;
//1891: Disengage
m_caster->JumpTo(speedxy, speedz, m_spellInfo->SpellFamilyName != SPELLFAMILY_HUNTER);
unitTarget->JumpTo(speedxy, speedz, m_spellInfo->SpellFamilyName != SPELLFAMILY_HUNTER);
if (m_caster->IsPlayer())
{

View File

@ -271,10 +271,15 @@ public:
///- %Parse the command line arguments
char* accountName = strtok((char*)args, " ");
char* password = strtok(nullptr, " ");
char* email = strtok(nullptr, " ");
if (!accountName || !password)
return false;
AccountOpResult result = AccountMgr::CreateAccount(std::string(accountName), std::string(password));
// if email is not specified, use empty string
std::string emailStr = email ? email : "";
AccountOpResult result = AccountMgr::CreateAccount(std::string(accountName), std::string(password), emailStr);
switch (result)
{
case AOR_OK:

View File

@ -38,13 +38,14 @@ public:
{
static ChatCommandTable gmCommandTable =
{
{ "chat", HandleGMChatCommand, SEC_GAMEMASTER, Console::No },
{ "fly", HandleGMFlyCommand, SEC_GAMEMASTER, Console::No },
{ "ingame", HandleGMListIngameCommand, SEC_PLAYER, Console::Yes },
{ "list", HandleGMListFullCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "visible", HandleGMVisibleCommand, SEC_GAMEMASTER, Console::No },
{ "on", HandleGMOnCommand, SEC_MODERATOR, Console::No },
{ "off", HandleGMOffCommand, SEC_MODERATOR, Console::No }
{ "chat", HandleGMChatCommand, SEC_GAMEMASTER, Console::No },
{ "fly", HandleGMFlyCommand, SEC_GAMEMASTER, Console::No },
{ "ingame", HandleGMListIngameCommand, SEC_PLAYER, Console::Yes },
{ "list", HandleGMListFullCommand, SEC_ADMINISTRATOR, Console::Yes },
{ "visible", HandleGMVisibleCommand, SEC_GAMEMASTER, Console::No },
{ "on", HandleGMOnCommand, SEC_MODERATOR, Console::No },
{ "off", HandleGMOffCommand, SEC_MODERATOR, Console::No },
{ "spectator", HandleGMSpectatorCommand, SEC_GAMEMASTER, Console::No },
};
static ChatCommandTable commandTable =
{
@ -236,6 +237,19 @@ public:
handler->SendNotification(LANG_GM_OFF);
return true;
}
static bool HandleGMSpectatorCommand(ChatHandler* handler, Optional<bool> enable)
{
Player* player = handler->GetSession()->GetPlayer();
if (enable.has_value())
player->SetGMSpectator(*enable);
else
player->SetGMSpectator(!player->IsGMSpectator());
handler->SendNotification(player->IsGMSpectator() ? LANG_GM_SPECTATOR_ON : LANG_GM_SPECTATOR_OFF);
return true;
}
};
void AddSC_gm_commandscript()

View File

@ -86,16 +86,35 @@ public:
return true;
}
static bool HandleGoCreatureCIdCommand(ChatHandler* handler, Variant<Hyperlink<creature_entry>, uint32> cId)
static bool HandleGoCreatureCIdCommand(ChatHandler* handler, Variant<Hyperlink<creature_entry>, uint32> cId, Optional<uint32> _pos)
{
CreatureData const* spawnpoint = GetCreatureData(handler, *cId);
uint32 pos = 1;
if (_pos)
{
pos = *_pos;
if (pos < 1)
{
handler->SendErrorMessage(LANG_COMMAND_FACTION_INVPARAM, pos);
return false;
}
}
if (!spawnpoint)
std::vector<CreatureData const*> spawnpoints = GetCreatureDataList(*cId);
if (spawnpoints.empty())
{
handler->SendErrorMessage(LANG_COMMAND_GOCREATNOTFOUND);
return false;
}
if (spawnpoints.size() < pos)
{
handler->SendErrorMessage(LANG_COMMAND_GONOTENOUGHSPAWNS, pos, spawnpoints.size());
return false;
}
CreatureData const* spawnpoint = spawnpoints[--pos];
return DoTeleport(handler, { spawnpoint->posX, spawnpoint->posY, spawnpoint->posZ }, spawnpoint->mapid);
}
@ -153,16 +172,35 @@ public:
return DoTeleport(handler, { spawnpoint->posX, spawnpoint->posY, spawnpoint->posZ }, spawnpoint->mapid);
}
static bool HandleGoGameObjectGOIdCommand(ChatHandler* handler, uint32 goId)
static bool HandleGoGameObjectGOIdCommand(ChatHandler* handler, uint32 goId, Optional<uint32> _pos)
{
GameObjectData const* spawnpoint = GetGameObjectData(handler, goId);
uint32 pos = 1;
if (_pos)
{
pos = *_pos;
if (pos < 1)
{
handler->SendErrorMessage(LANG_COMMAND_FACTION_INVPARAM, pos);
return false;
}
}
if (!spawnpoint)
std::vector<GameObjectData const*> spawnpoints = GetGameObjectDataList(goId);
if (spawnpoints.empty())
{
handler->SendErrorMessage(LANG_COMMAND_GOOBJNOTFOUND);
return false;
}
if (spawnpoints.size() < pos)
{
handler->SendErrorMessage(LANG_COMMAND_GONOTENOUGHSPAWNS, pos, spawnpoints.size());
return false;
}
GameObjectData const* spawnpoint = spawnpoints[--pos];
return DoTeleport(handler, { spawnpoint->posX, spawnpoint->posY, spawnpoint->posZ }, spawnpoint->mapid);
}
@ -509,6 +547,22 @@ public:
return spawnpoint;
}
static std::vector<CreatureData const*> GetCreatureDataList(uint32 entry)
{
std::vector<CreatureData const*> spawnpoints;
for (auto const& pair : sObjectMgr->GetAllCreatureData())
{
if (pair.second.id1 != entry)
{
continue;
}
spawnpoints.emplace_back(&pair.second);
}
return spawnpoints;
}
static GameObjectData const* GetGameObjectData(ChatHandler* handler, uint32 entry)
{
GameObjectData const* spawnpoint = nullptr;
@ -532,6 +586,22 @@ public:
return spawnpoint;
}
static std::vector<GameObjectData const*> GetGameObjectDataList(uint32 entry)
{
std::vector<GameObjectData const*> spawnpoints;
for (auto const& pair : sObjectMgr->GetAllGOData())
{
if (pair.second.id != entry)
{
continue;
}
spawnpoints.emplace_back(&pair.second);
}
return spawnpoints;
}
};
void AddSC_go_commandscript()

View File

@ -74,7 +74,7 @@ public:
if (sGuildMgr->GetGuildByName(guildName))
{
handler->SendErrorMessage(LANG_GUILD_RENAME_ALREADY_EXISTS);
handler->SendErrorMessage(LANG_GUILD_RENAME_ALREADY_EXISTS, guildName);
return false;
}
@ -249,6 +249,20 @@ public:
handler->PSendSysMessage(LANG_GUILD_INFO_BANK_GOLD, guild->GetTotalBankMoney() / 100 / 100); // Bank Gold (in gold coins)
handler->PSendSysMessage(LANG_GUILD_INFO_MOTD, guild->GetMOTD()); // Message of the Day
handler->PSendSysMessage(LANG_GUILD_INFO_EXTRA_INFO, guild->GetInfo()); // Extra Information
QueryResult result = CharacterDatabase.Query("SELECT rid, rname FROM guild_rank WHERE guildid = {}", guild->GetId());
if (result)
{
handler->PSendSysMessage(LANG_GUILD_INFO_RANKS);
do
{
Field* fields = result->Fetch();
uint32 rid = fields[0].Get<uint32>();
std::string rname = fields[1].Get<std::string>();
handler->PSendSysMessage(LANG_GUILD_INFO_RANKS_LIST, rid, rname);
} while (result->NextRow());
}
return true;
}
};

View File

@ -74,7 +74,7 @@ struct boss_servant_quarters : public BossAI
context.Repeat(12s, 18s);
}).Schedule(10s, [this](TaskContext context)
{
if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, FarthestTargetSelector(me, 40.0f, false, true)))
if (Unit* target = SelectTarget(SelectTargetMethod::MinDistance, 0, RangeSelector(me, 40.0f, false, true)))
{
me->CastSpell(target, SPELL_DIVE);
}

View File

@ -1200,23 +1200,6 @@ class spell_chapter5_light_of_dawn_aura : public AuraScript
}
};
class spell_chapter5_rebuke : public SpellScript
{
PrepareSpellScript(spell_chapter5_rebuke);
void HandleLeapBack(SpellEffIndex effIndex)
{
PreventHitEffect(effIndex);
if (Unit* unitTarget = GetHitUnit())
unitTarget->KnockbackFrom(2282.86f, -5263.45f, 40.0f, 8.0f);
}
void Register() override
{
OnEffectLaunchTarget += SpellEffectFn(spell_chapter5_rebuke::HandleLeapBack, EFFECT_0, SPELL_EFFECT_LEAP_BACK);
}
};
// 58552 - Return to Orgrimmar
// 58533 - Return to Stormwind
enum ReturnToCapital
@ -1317,6 +1300,5 @@ void AddSC_the_scarlet_enclave_c5()
{
new npc_highlord_darion_mograine();
RegisterSpellScript(spell_chapter5_light_of_dawn_aura);
RegisterSpellScript(spell_chapter5_rebuke);
RegisterSpellScript(spell_chapter5_return_to_capital);
}

View File

@ -381,14 +381,8 @@ struct boss_kiljaeden : public BossAI
if (Creature* anveena = instance->GetCreature(DATA_ANVEENA))
{
anveena->RemoveAllAuras();
anveena->DespawnOrUnsummon(3500);
}
}, 34s);
me->m_Events.AddEventAtOffset([&] {
if (Creature* anveena = instance->GetCreature(DATA_ANVEENA))
{
anveena->CastSpell(anveena, SPELL_SACRIFICE_OF_ANVEENA, true);
anveena->DespawnOrUnsummon(1500);
DoCastSelf(SPELL_CUSTOM_08_STATE, true);
me->SetUnitFlag(UNIT_FLAG_PACIFIED);
scheduler.CancelAll();

View File

@ -213,7 +213,7 @@ struct npc_dark_fiend : public ScriptedAI
Unit* target = nullptr;
if (InstanceScript* instance = me->GetInstanceScript())
if (Creature* muru = instance->GetCreature(DATA_MURU))
target = muru->GetAI()->SelectTarget(SelectTargetMethod::Random, 0, FarthestTargetSelector(me, 50.0f, true, true));
target = muru->GetAI()->SelectTarget(SelectTargetMethod::Random, 0, RangeSelector(me, 50.0f, true, true));
if (target)
{

View File

@ -530,11 +530,21 @@ class spell_dk_bone_shield : public AuraScript
{
PrepareAuraScript(spell_dk_bone_shield);
uint32 lastChargeUsedTime = 0;
void HandleProc(ProcEventInfo& eventInfo)
{
PreventDefaultAction();
uint32 currentTime = getMSTime();
// Checks for 2 seconds between uses of bone shield charges
if ((currentTime - lastChargeUsedTime) < 2000)
return;
if (!eventInfo.GetSpellInfo() || !eventInfo.GetSpellInfo()->IsTargetingArea())
{
DropCharge();
lastChargeUsedTime = currentTime;
}
}
void Register() override

View File

@ -5431,6 +5431,22 @@ private:
uint32 _spellId;
};
class spell_gen_cooldown_all : public SpellScript
{
PrepareSpellScript(spell_gen_cooldown_all);
void HandleScript(SpellEffIndex /*effIndex*/)
{
if (Player* player = GetHitPlayer())
player->RemoveAllSpellCooldown();
}
void Register() override
{
OnEffectHitTarget += SpellEffectFn(spell_gen_cooldown_all::HandleScript, EFFECT_0, SPELL_EFFECT_SCRIPT_EFFECT);
}
};
void AddSC_generic_spell_scripts()
{
RegisterSpellScript(spell_silithyst);
@ -5593,4 +5609,5 @@ void AddSC_generic_spell_scripts()
RegisterSpellScript(spell_gen_proc_on_victim);
RegisterSpellScriptWithArgs(spell_gen_translocate, "spell_gen_translocate_down", SPELL_TRANSLOCATION_DOWN);
RegisterSpellScriptWithArgs(spell_gen_translocate, "spell_gen_translocate_up", SPELL_TRANSLOCATION_UP);
RegisterSpellScript(spell_gen_cooldown_all);
}