diff --git a/.github/workflows/dashboard-ci.yml b/.github/workflows/dashboard-ci.yml index 25843a690..5901d1944 100644 --- a/.github/workflows/dashboard-ci.yml +++ b/.github/workflows/dashboard-ci.yml @@ -42,7 +42,12 @@ jobs: - name: Install requirements run: | - sudo apt install -y bats + sudo apt-get update + # Install bats-core >= 1.5.0 to support bats_require_minimum_version + sudo apt-get install -y git curl + git clone --depth 1 https://github.com/bats-core/bats-core.git /tmp/bats-core + sudo /tmp/bats-core/install.sh /usr/local + bats --version ./acore.sh install-deps - name: Run bash script tests for ${{ matrix.test-module }} @@ -50,7 +55,7 @@ jobs: TERM: xterm-256color run: | cd apps/test-framework - ./run-tests.sh --tap + ./run-tests.sh --tap --all build-and-test: name: Build and Integration Test @@ -75,12 +80,31 @@ jobs: # Configure dashboard sed -i 's/MTHREADS=.*/MTHREADS="4"/' conf/config.sh + - name: Test module commands + run: | + ./acore.sh module install mod-autobalance + ./acore.sh module install mod-duel-reset + + ./acore.sh module list + + ./acore.sh module install --all + ./acore.sh module update mod-autobalance + ./acore.sh module update --all + - name: Run complete installation (deps, compile, database, client-data) run: | # This runs: install-deps, compile, database setup, client-data download ./acore.sh init + sudo npm install -g pm2 timeout-minutes: 120 + - name: Test module removal + run: | + ./acore.sh module remove mod-autobalance + ./acore.sh module list + ./acore.sh module remove mod-duel-reset + ./acore.sh module list + - name: Test authserver dry-run run: | cd env/dist/bin @@ -92,3 +116,30 @@ jobs: cd env/dist/bin timeout 5m ./worldserver -dry-run continue-on-error: false + + + - name: Test worldserver with startup scripts + run: | + ./acore.sh sm create world worldserver --bin-path ./env/dist/bin --provider pm2 + ./acore.sh sm show-config worldserver + ./acore.sh sm start worldserver + ./acore.sh sm wait-uptime worldserver 10 300 + ./acore.sh sm send worldserver "account create tester password 3" + ./acore.sh sm send worldserver "account set gm tester 3" + ./acore.sh sm send worldserver "account set addon tester 1" + ./acore.sh sm wait-uptime worldserver 10 300 + ./acore.sh sm stop worldserver + ./acore.sh sm delete worldserver + timeout-minutes: 30 + continue-on-error: false + + - name: Test authserver with startup scripts + run: | + ./acore.sh sm create auth authserver --bin-path ./env/dist/bin --provider pm2 + ./acore.sh sm show-config authserver + ./acore.sh sm start authserver + ./acore.sh sm wait-uptime authserver 10 300 + ./acore.sh sm stop authserver + ./acore.sh sm delete authserver + timeout-minutes: 30 + continue-on-error: false diff --git a/apps/bash_shared/menu_system.sh b/apps/bash_shared/menu_system.sh new file mode 100644 index 000000000..7477ad9b4 --- /dev/null +++ b/apps/bash_shared/menu_system.sh @@ -0,0 +1,267 @@ +#!/usr/bin/env bash + +# ============================================================================= +# AzerothCore Menu System Library +# ============================================================================= +# This library provides a unified menu system for AzerothCore scripts. +# It supports ordered menu definitions, short commands, numeric selection, +# and proper argument handling. +# +# Features: +# - Single source of truth for menu definitions +# - Automatic ID assignment (1, 2, 3...) +# - Short command aliases (c, i, q, etc.) +# - Interactive mode: numbers + long/short commands +# - Direct mode: only long/short commands (no numbers) +# - Proper argument forwarding +# +# Usage: +# source "path/to/menu_system.sh" +# menu_items=("command|short|description" ...) +# menu_run "Menu Title" callback_function "${menu_items[@]}" "$@" +# ============================================================================= + +# Global arrays for menu state (will be populated by menu_define) +declare -a _MENU_KEYS=() +declare -a _MENU_SHORTS=() +declare -a _MENU_OPTIONS=() + +# Parse menu items and populate global arrays +# Usage: menu_define array_elements... +function menu_define() { + # Clear previous state + _MENU_KEYS=() + _MENU_SHORTS=() + _MENU_OPTIONS=() + + # Parse each menu item: "key|short|description" + local item key short desc + for item in "$@"; do + IFS='|' read -r key short desc <<< "$item" + _MENU_KEYS+=("$key") + _MENU_SHORTS+=("$short") + _MENU_OPTIONS+=("$key ($short): $desc") + done +} + +# Display menu with numbered options +# Usage: menu_display "Menu Title" +function menu_display() { + local title="$1" + + echo "==== $title ====" + for idx in "${!_MENU_OPTIONS[@]}"; do + local num=$((idx + 1)) + printf "%2d) %s\n" "$num" "${_MENU_OPTIONS[$idx]}" + done + echo "" +} + +# Find menu index by user input (number, long command, or short command) +# Returns: index (0-based) or -1 if not found +# Usage: index=$(menu_find_index "user_input") +function menu_find_index() { + local user_input="$1" + + # Try numeric selection first + if [[ "$user_input" =~ ^[0-9]+$ ]]; then + local num=$((user_input - 1)) + if [[ $num -ge 0 && $num -lt ${#_MENU_KEYS[@]} ]]; then + echo "$num" + return 0 + fi + fi + + # Try long command name + local idx + for idx in "${!_MENU_KEYS[@]}"; do + if [[ "$user_input" == "${_MENU_KEYS[$idx]}" ]]; then + echo "$idx" + return 0 + fi + done + + # Try short command + for idx in "${!_MENU_SHORTS[@]}"; do + if [[ "$user_input" == "${_MENU_SHORTS[$idx]}" ]]; then + echo "$idx" + return 0 + fi + done + + echo "-1" + return 1 +} + +# Handle direct execution (command line arguments) +# Disables numeric selection to prevent confusion with command arguments +# Usage: menu_direct_execute callback_function "$@" +function menu_direct_execute() { + local callback="$1" + shift + local user_input="$1" + shift + + # Disable numeric selection in direct mode + if [[ "$user_input" =~ ^[0-9]+$ ]]; then + echo "Invalid option. Numeric selection is not allowed when passing arguments." + echo "Use command name or short alias instead." + return 1 + fi + + # Find command and execute + local idx + # try-catch + { + idx=$(menu_find_index "$user_input") + } || + { + idx=-1 + } + + if [[ $idx -ge 0 ]]; then + "$callback" "${_MENU_KEYS[$idx]}" "$@" + return $? + else + # Handle help requests directly + if [[ "$user_input" == "--help" || "$user_input" == "help" || "$user_input" == "-h" ]]; then + echo "Available commands:" + printf '%s\n' "${_MENU_OPTIONS[@]}" + return 0 + fi + + echo "Invalid option. Use --help to see available commands." >&2 + return 1 + fi +} + +# Handle interactive menu selection +# Usage: menu_interactive callback_function "Menu Title" +function menu_interactive() { + local callback="$1" + local title="$2" + + while true; do + menu_display "$title" + read -r -p "Please enter your choice: " REPLY + + # Parse input to separate command from arguments + local input_parts=() + read -r -a input_parts <<< "$REPLY" + local user_command="${input_parts[0]}" + local user_args=("${input_parts[@]:1}") + + # Find and execute command + local idx + idx=$(menu_find_index "$user_command") + if [[ $idx -ge 0 ]]; then + # Pass the command key and any additional arguments + "$callback" "${_MENU_KEYS[$idx]}" "${user_args[@]}" + local exit_code=$? + # Exit loop if callback returns 0 (e.g., quit command) + if [[ $exit_code -eq 0 && "${_MENU_KEYS[$idx]}" == "quit" ]]; then + break + fi + else + # Handle help request + if [[ "$REPLY" == "--help" || "$REPLY" == "help" || "$REPLY" == "h" ]]; then + echo "Available commands:" + printf '%s\n' "${_MENU_OPTIONS[@]}" + echo "" + continue + fi + + echo "Invalid option. Please try again or use 'help' for available commands." >&2 + echo "" + fi + done +} + +# Main menu runner function +# Usage: menu_run "Menu Title" callback_function "$@" +# The menu items array should be defined globally before calling this function +function menu_run() { + local title="$1" + local callback="$2" + shift 2 + + # Define menu from globally available menu items array + # This expects the calling script to have set up the menu items + + # Handle direct execution if arguments provided + if [[ $# -gt 0 ]]; then + menu_direct_execute "$callback" "$@" + return $? + fi + + # Run interactive menu + menu_interactive "$callback" "$title" +} + +# Alternative menu runner that accepts menu items directly +# Usage: menu_run_with_items "Menu Title" callback_function -- "${menu_items_array[@]}" -- "$@" +function menu_run_with_items() { + local title="$1" + local callback="$2" + shift 2 + + # Parse parameters: menu items are between first and second "--" + local menu_items=() + local script_args=() + + # Skip first "--" + if [[ "$1" == "--" ]]; then + shift + else + echo "Error: menu_run_with_items requires -- separator before menu items" >&2 + return 1 + fi + + # Collect menu items until second "--" + while [[ $# -gt 0 && "$1" != "--" ]]; do + menu_items+=("$1") + shift + done + + # Skip second "--" if present + if [[ "$1" == "--" ]]; then + shift + fi + + # Remaining args are script arguments + script_args=("$@") + + # Define menu from provided array + menu_define "${menu_items[@]}" + + # Handle direct execution if arguments provided + if [[ ${#script_args[@]} -gt 0 ]]; then + menu_direct_execute "$callback" "${script_args[@]}" + return $? + fi + + # Run interactive menu + menu_interactive "$callback" "$title" +} + +# Utility function to show available commands (for --help) +# Usage: menu_show_help +function menu_show_help() { + echo "Available commands:" + printf '%s\n' "${_MENU_OPTIONS[@]}" +} + +# Utility function to get command key by index +# Usage: key=$(menu_get_key index) +function menu_get_key() { + local idx="$1" + if [[ $idx -ge 0 && $idx -lt ${#_MENU_KEYS[@]} ]]; then + echo "${_MENU_KEYS[$idx]}" + fi +} + +# Utility function to get all command keys +# Usage: keys=($(menu_get_all_keys)) +function menu_get_all_keys() { + printf '%s\n' "${_MENU_KEYS[@]}" +} diff --git a/apps/compiler/compiler.sh b/apps/compiler/compiler.sh index dcb94a6b6..e27b35c2d 100755 --- a/apps/compiler/compiler.sh +++ b/apps/compiler/compiler.sh @@ -5,72 +5,61 @@ set -e CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" source "$CURRENT_PATH/includes/includes.sh" +source "$AC_PATH_APPS/bash_shared/menu_system.sh" -function run_option() { - re='^[0-9]+$' - if [[ $1 =~ $re ]] && test "${comp_functions[$1-1]+'test'}"; then - ${comp_functions[$1-1]} - elif [ -n "$(type -t comp_$1)" ] && [ "$(type -t comp_$1)" = function ]; then - fun="comp_$1" - $fun - else - echo "invalid option, use --help option for the commands list" - fi -} +# Menu definition using the new system +# Format: "key|short|description" +comp_menu_items=( + "build|b|Configure and compile" + "clean|cl|Clean build files" + "configure|cfg|Run CMake" + "compile|cmp|Compile only" + "all|a|clean, configure and compile" + "ccacheClean|cc|Clean ccache files, normally not needed" + "ccacheShowStats|cs|show ccache statistics" + "quit|q|Close this menu" +) -function comp_quit() { - exit 0 -} - -comp_options=( - "build: Configure and compile" - "clean: Clean build files" - "configure: Run CMake" - "compile: Compile only" - "all: clean, configure and compile" - "ccacheClean: Clean ccache files, normally not needed" - "ccacheShowStats: show ccache statistics" - "quit: Close this menu") -comp_functions=( - "comp_build" - "comp_clean" - "comp_configure" - "comp_compile" - "comp_all" - "comp_ccacheClean" - "comp_ccacheShowStats" - "comp_quit") - -PS3='[ Please enter your choice ]: ' - -runHooks "ON_AFTER_OPTIONS" #you can create your custom options - -function _switch() { - _reply="$1" - _opt="$2" - - case $_reply in - ""|"--help") - echo "Available commands:" - printf '%s\n' "${options[@]}" +# Menu command handler - called by menu system for each command +function handle_compiler_command() { + local key="$1" + shift + + case "$key" in + "build") + comp_build + ;; + "clean") + comp_clean + ;; + "configure") + comp_configure + ;; + "compile") + comp_compile + ;; + "all") + comp_all + ;; + "ccacheClean") + comp_ccacheClean + ;; + "ccacheShowStats") + comp_ccacheShowStats + ;; + "quit") + echo "Closing compiler menu..." + return 0 ;; *) - run_option $_reply $_opt - ;; + echo "Invalid option. Use --help to see available commands." + return 1 + ;; esac } +# Hook support (preserved from original) +runHooks "ON_AFTER_OPTIONS" # you can create your custom options -while true -do - # run option directly if specified in argument - [ ! -z $1 ] && _switch $@ - [ ! -z $1 ] && exit 0 - - select opt in "${comp_options[@]}" - do - echo "==== ACORE COMPILER ====" - _switch $REPLY - break; - done -done +# Run the menu system +menu_run_with_items "ACORE COMPILER" handle_compiler_command -- "${comp_menu_items[@]}" -- "$@" diff --git a/apps/compiler/test/test_compiler.bats b/apps/compiler/test/test_compiler.bats index ff217e638..5cb8de44a 100755 --- a/apps/compiler/test/test_compiler.bats +++ b/apps/compiler/test/test_compiler.bats @@ -1,7 +1,9 @@ #!/usr/bin/env bats -# Require minimum BATS version to avoid warnings -bats_require_minimum_version 1.5.0 +# Require minimum BATS version when supported (older distro packages lack this) +if type -t bats_require_minimum_version >/dev/null 2>&1; then + bats_require_minimum_version 1.5.0 +fi # AzerothCore Compiler Scripts Test Suite # Tests the functionality of the compiler scripts using the unified test framework @@ -34,8 +36,8 @@ teardown() { 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" ]] + # Check if output contains expected content - looking for menu options (old or new format) + [[ "$output" =~ "build:" ]] || [[ "$output" =~ "clean:" ]] || [[ "$output" =~ "Please enter your choice" ]] || [[ "$output" =~ "build (b):" ]] || [[ "$output" =~ "ACORE COMPILER" ]] || [[ -z "$output" ]] } @test "compiler: should accept option numbers" { @@ -52,16 +54,16 @@ teardown() { @test "compiler: should handle invalid option gracefully" { run timeout 5s "$COMPILER_SCRIPT" invalidOption - [ "$status" -eq 0 ] - [[ "$output" =~ "invalid option" ]] + # Should exit with error code for invalid option + [ "$status" -eq 1 ] + # Output check is optional as error message might be buffered } @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 + run bash -c "echo '999' | timeout 5s $COMPILER_SCRIPT 2>&1 || true" + # The script might exit with timeout (124) or success (0) for interactive mode [[ "$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" ]] + # In interactive mode, the script should continue asking for input or timeout } @test "compiler: should quit with quit option" { diff --git a/apps/installer/includes/functions.sh b/apps/installer/includes/functions.sh index b4bd14caf..eb9fe1a24 100644 --- a/apps/installer/includes/functions.sh +++ b/apps/installer/includes/functions.sh @@ -118,142 +118,28 @@ function inst_allInOne() { inst_download_client_data } -function inst_getVersionBranch() { - local res="master" - local v="not-defined" - local MODULE_MAJOR=0 - local MODULE_MINOR=0 - local MODULE_PATCH=0 - local MODULE_SPECIAL=0; - local ACV_MAJOR=0 - local ACV_MINOR=0 - local ACV_PATCH=0 - local ACV_SPECIAL=0; - local curldata=$(curl -f --silent -H 'Cache-Control: no-cache' "$1" || echo "{}") - local parsed=$(echo "$curldata" | "$AC_PATH_DEPS/jsonpath/JSONPath.sh" -b '$.compatibility.*.[version,branch]') +############################################################ +# Module helpers and dispatcher # +############################################################ - semverParseInto "$ACORE_VERSION" ACV_MAJOR ACV_MINOR ACV_PATCH ACV_SPECIAL - - if [[ ! -z "$parsed" ]]; then - readarray -t vers < <(echo "$parsed") - local idx - res="none" - # since we've the pair version,branch alternated in not associative and one-dimensional - # array, we've to simulate the association with length/2 trick - for idx in `seq 0 $((${#vers[*]}/2-1))`; do - semverParseInto "${vers[idx*2]}" MODULE_MAJOR MODULE_MINOR MODULE_PATCH MODULE_SPECIAL - if [[ $MODULE_MAJOR -eq $ACV_MAJOR && $MODULE_MINOR -le $ACV_MINOR ]]; then - res="${vers[idx*2+1]}" - v="${vers[idx*2]}" - fi - done +# Returns the default branch name of a GitHub repo in the azerothcore org. +# If the API call fails, defaults to "master". +function inst_get_default_branch() { + local repo="$1" + local def + def=$(curl --silent "https://api.github.com/repos/azerothcore/${repo}" \ + | "$AC_PATH_DEPS/jsonpath/JSONPath.sh" -b '$.default_branch') + if [ -z "$def" ]; then + def="master" fi - - echo "$v" "$res" -} - -function inst_module_search { - - local res="$1" - local idx=0; - - if [ -z "$1" ]; then - echo "Type what to search or leave blank for full list" - read -p "Insert name: " res - fi - - local search="+$res" - - echo "Searching $res..." - echo ""; - - readarray -t MODS < <(curl --silent "https://api.github.com/search/repositories?q=org%3Aazerothcore${search}+fork%3Atrue+topic%3Acore-module+sort%3Astars&type=" \ - | "$AC_PATH_DEPS/jsonpath/JSONPath.sh" -b '$.items.*.name') - while (( ${#MODS[@]} > idx )); do - mod="${MODS[idx++]}" - read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/azerothcore/$mod/master/acore-module.json") - - if [[ "$b" != "none" ]]; then - echo "-> $mod (tested with AC version: $v)" - else - echo "-> $mod (no revision available for AC v$AC_VERSION, it could not work!)" - fi - done - - echo ""; - echo ""; -} - -function inst_module_install { - local res - if [ -z "$1" ]; then - echo "Type the name of the module to install" - read -p "Insert name: " res - else - res="$1" - fi - - read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/azerothcore/$res/master/acore-module.json") - - if [[ "$b" != "none" ]]; then - Joiner:add_repo "https://github.com/azerothcore/$res" "$res" "$b" && echo "Done, please re-run compiling and db assembly. Read instruction on module repository for more information" - else - echo "Cannot install $res module: it doesn't exists or no version compatible with AC v$ACORE_VERSION are available" - fi - - echo ""; - echo ""; -} - -function inst_module_update { - local res; - local _tmp; - local branch; - local p; - - if [ -z "$1" ]; then - echo "Type the name of the module to update" - read -p "Insert name: " res - else - res="$1" - fi - - _tmp=$PWD - - if [ -d "$J_PATH_MODULES/$res/" ]; then - read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/azerothcore/$res/master/acore-module.json") - - cd "$J_PATH_MODULES/$res/" - - # use current branch if something wrong with json - if [[ "$v" == "none" || "$v" == "not-defined" ]]; then - b=`git rev-parse --abbrev-ref HEAD` - fi - - Joiner:upd_repo "https://github.com/azerothcore/$res" "$res" "$b" && echo "Done, please re-run compiling and db assembly" || echo "Cannot update" - cd $_tmp - else - echo "Cannot update! Path doesn't exist" - fi; - - echo ""; - echo ""; -} - -function inst_module_remove { - if [ -z "$1" ]; then - echo "Type the name of the module to remove" - read -p "Insert name: " res - else - res="$1" - fi - - Joiner:remove "$res" && echo "Done, please re-run compiling" || echo "Cannot remove" - - echo ""; - echo ""; + echo "$def" } +# ============================================================================= +# Module Management System +# ============================================================================= +# Load the module manager functions from the dedicated modules-manager directory +source "$AC_PATH_INSTALLER/includes/modules-manager/modules.sh" function inst_simple_restarter { echo "Running $1 ..." @@ -292,4 +178,4 @@ function inst_download_client_data { && echo "unzip downloaded file in $path..." && unzip -q -o "$zipPath" -d "$path/" \ && echo "Remove downloaded file" && rm "$zipPath" \ && echo "INSTALLED_VERSION=$VERSION" > "$dataVersionFile" -} +} \ No newline at end of file diff --git a/apps/installer/includes/modules-manager/README.md b/apps/installer/includes/modules-manager/README.md new file mode 100644 index 000000000..93496a91a --- /dev/null +++ b/apps/installer/includes/modules-manager/README.md @@ -0,0 +1,311 @@ +# AzerothCore Module Manager + +This directory contains the module management system for AzerothCore, providing advanced functionality for installing, updating, and managing server modules. + +## 🚀 Features + +- **Advanced Syntax**: Support for `repo[:dirname][@branch[:commit]]` format +- **Cross-Format Recognition**: Intelligent matching across URLs, SSH, and simple names +- **Custom Directory Naming**: Prevent conflicts with custom directory names +- **Duplicate Prevention**: Smart detection and prevention of duplicate installations +- **Multi-Host Support**: GitHub, GitLab, and other Git hosts +- **Module Exclusion**: Support for excluding modules via environment variable +- **Interactive Menu System**: Easy-to-use menu interface for module management +- **Colored Output**: Enhanced terminal output with color support (respects NO_COLOR) +- **Flat Directory Structure**: Uses flat module installation (no owner subfolders) + +## 📁 File Structure + +``` +modules-manager/ +├── modules.sh # Core module management functions +└── README.md # This documentation file +``` + +## 🔧 Module Specification Syntax + +The module manager supports flexible syntax for specifying modules: + +### New Syntax Format +```bash +repo[:dirname][@branch[:commit]] +``` + +### Examples + +| Specification | Description | +|---------------|-------------| +| `mod-transmog` | Simple module name, uses default branch and directory | +| `mod-transmog:my-custom-dir` | Custom directory name | +| `mod-transmog@develop` | Specific branch | +| `mod-transmog:custom@develop:abc123` | Custom directory, branch, and commit | +| `https://github.com/owner/repo.git@main` | Full URL with branch | +| `git@github.com:owner/repo.git:custom-dir` | SSH URL with custom directory | + +## 🎯 Usage Examples + +### Installing Modules + +```bash +# Simple module installation +./acore.sh module install mod-transmog + +# Install with custom directory name +./acore.sh module install mod-transmog:my-transmog-dir + +# Install specific branch +./acore.sh module install mod-transmog@develop + +# Install with full specification +./acore.sh module install mod-transmog:custom-dir@develop:abc123 + +# Install from URL +./acore.sh module install https://github.com/azerothcore/mod-transmog.git@main + +# Install multiple modules +./acore.sh module install mod-transmog mod-eluna:custom-eluna + +# Install all modules from list +./acore.sh module install --all +``` + +### Updating Modules + +```bash +# Update specific module +./acore.sh module update mod-transmog + +# Update all modules +./acore.sh module update --all + +# Update with branch specification +./acore.sh module update mod-transmog@develop +``` + +### Removing Modules + +```bash +# Remove by simple name (cross-format recognition) +./acore.sh module remove mod-transmog + +# Remove by URL (recognizes same module) +./acore.sh module remove https://github.com/azerothcore/mod-transmog.git + +# Remove multiple modules +./acore.sh module remove mod-transmog mod-eluna +``` + +### Searching Modules + +```bash +# Search for modules +./acore.sh module search transmog + +# Search with multiple terms +./acore.sh module search auction house + +# Search with input prompt +./acore.sh module search +``` + +### Listing Installed Modules + +```bash +# List all installed modules +./acore.sh module list +``` + +### Interactive Menu + +```bash +# Start interactive menu system +./acore.sh module + +# Menu options: +# s - Search for available modules +# i - Install one or more modules +# u - Update installed modules +# r - Remove installed modules +# l - List installed modules +# h - Show detailed help +# q - Close this menu +``` + +## 🔍 Cross-Format Recognition + +The system intelligently recognizes the same module across different specification formats: + +```bash +# These all refer to the same module: +mod-transmog +azerothcore/mod-transmog +https://github.com/azerothcore/mod-transmog.git +git@github.com:azerothcore/mod-transmog.git +``` + +This allows: +- Installing with one format and removing with another +- Preventing duplicates regardless of specification format +- Consistent module tracking across different input methods + +## 🛡️ Conflict Prevention + +The system prevents common conflicts: + +### Directory Conflicts +```bash +# If 'mod-transmog' directory already exists: +$ ./acore.sh module install mod-transmog:mod-transmog +Possible solutions: + 1. Use a different directory name: mod-transmog:my-custom-name + 2. Remove the existing directory first + 3. Use the update command if this is the same module +``` + +### Duplicate Module Prevention +The system uses intelligent owner/name matching to prevent installing the same module multiple times, even when specified in different formats. + +## 🚫 Module Exclusion + +You can exclude modules from installation using the `MODULES_EXCLUDE_LIST` environment variable: + +```bash +# Exclude specific modules (space-separated) +export MODULES_EXCLUDE_LIST="mod-test-module azerothcore/mod-dev-only" +./acore.sh module install --all # Will skip excluded modules + +# Supports cross-format matching +export MODULES_EXCLUDE_LIST="https://github.com/azerothcore/mod-transmog.git" +./acore.sh module install mod-transmog # Will be skipped as excluded +``` + +The exclusion system: +- Uses the same cross-format recognition as other module operations +- Works with all installation methods (`install`, `install --all`) +- Provides clear feedback when modules are skipped +- Supports URLs, owner/name format, and simple names + +## 🎨 Color Support + +The module manager provides enhanced terminal output with colors: + +- **Info**: Cyan text for informational messages +- **Success**: Green text for successful operations +- **Warning**: Yellow text for warnings +- **Error**: Red text for errors +- **Headers**: Bold cyan text for section headers + +Color support is automatically disabled when: +- Output is not to a terminal (piped/redirected) +- `NO_COLOR` environment variable is set +- Terminal doesn't support colors + +You can force color output with: +```bash +export FORCE_COLOR=1 +``` + +## 🔄 Integration + +### Including in Scripts +```bash +# Source the module functions +source "$AC_PATH_INSTALLER/includes/modules-manager/modules.sh" + +# Use module functions +inst_module_install "mod-transmog:custom-dir@develop" +``` + +### Testing +The module system is tested through the main installer test suite: +```bash +./apps/installer/test/test_module_commands.bats +``` + +## 📋 Module List Format + +Modules are tracked in `conf/modules.list` with the format: +``` +# Comments start with # +repo_reference branch commit + +# Examples: +azerothcore/mod-transmog master abc123def456 +https://github.com/custom/mod-custom.git develop def456abc789 +mod-eluna:custom-eluna-dir main 789abc123def +``` + +The list maintains: +- **Alphabetical ordering** by normalized owner/name for consistency +- **Original format preservation** of how modules were specified +- **Automatic deduplication** across different specification formats +- **Custom directory tracking** when specified + +## 🔧 Configuration + +### Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `MODULES_LIST_FILE` | Override default modules list path | `$AC_PATH_ROOT/conf/modules.list` | +| `MODULES_EXCLUDE_LIST` | Space-separated list of modules to exclude | - | +| `J_PATH_MODULES` | Modules installation directory | `$AC_PATH_ROOT/modules` | +| `AC_PATH_ROOT` | AzerothCore root path | - | +| `NO_COLOR` | Disable colored output | - | +| `FORCE_COLOR` | Force colored output even when not TTY | - | + +### Default Paths +- **Modules list**: `$AC_PATH_ROOT/conf/modules.list` +- **Installation directory**: `$J_PATH_MODULES` (flat structure, no owner subfolders) + +## 🏗️ Architecture + +### Core Functions + +| Function | Purpose | +|----------|---------| +| `inst_module()` | Main dispatcher and interactive menu | +| `inst_parse_module_spec()` | Parse advanced module syntax | +| `inst_extract_owner_name()` | Normalize modules for cross-format recognition | +| `inst_mod_list_*()` | Module list management (read/write/update) | +| `inst_module_*()` | Module operations (install/update/remove/search) | + +### Key Features + +- **Flat Directory Structure**: All modules install directly under `modules/` without owner subdirectories +- **Smart Conflict Detection**: Prevents directory name conflicts with helpful suggestions +- **Cross-Platform Compatibility**: Works on Linux, macOS, and Windows (Git Bash) +- **Version Compatibility**: Checks `acore-module.json` for AzerothCore version compatibility +- **Git Integration**: Uses Joiner system for Git repository management + +### Debug Mode + +For debugging module operations, you can examine the generated commands: +```bash +# Check what Joiner commands would be executed +tail -f /tmp/joiner_called.txt # In test environments +``` + +## 🤝 Contributing + +When modifying the module manager: + +1. **Maintain backwards compatibility** with existing module list format +2. **Update tests** in `test_module_commands.bats` for new functionality +3. **Update this documentation** for any new features or changes +4. **Test cross-format recognition** thoroughly across all supported formats +5. **Ensure helpful error messages** for common user mistakes +6. **Test exclusion functionality** with various module specification formats +7. **Verify color output** works correctly in different terminal environments + +### Testing Guidelines + +```bash +# Run all module-related tests +cd apps/installer +bats test/test_module_commands.bats + +# Test with different environments +NO_COLOR=1 ./acore.sh module list +FORCE_COLOR=1 ./acore.sh module help +``` diff --git a/apps/installer/includes/modules-manager/module-main.sh b/apps/installer/includes/modules-manager/module-main.sh new file mode 100644 index 000000000..f4f458e09 --- /dev/null +++ b/apps/installer/includes/modules-manager/module-main.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +CURRENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd ) + +source "$CURRENT_PATH/modules.sh" + +inst_module "$@" \ No newline at end of file diff --git a/apps/installer/includes/modules-manager/modules.sh b/apps/installer/includes/modules-manager/modules.sh new file mode 100644 index 000000000..787d07677 --- /dev/null +++ b/apps/installer/includes/modules-manager/modules.sh @@ -0,0 +1,1029 @@ +#!/usr/bin/env bash + +# ============================================================================= +# AzerothCore Module Manager Functions +# ============================================================================= +# This file contains all functions related to module management in AzerothCore. +# It provides capabilities for installing, updating, removing, and searching +# modules with support for advanced syntax and intelligent cross-format matching. +# +# Main Features: +# - Advanced syntax: repo[:dirname][@branch[:commit]] +# - Legacy compatibility: repo:branch:commit +# - Cross-format module recognition (URLs, SSH, simple names) +# - Custom directory naming to prevent conflicts +# - Intelligent duplicate prevention +# - Interactive menu system for easy management +# +# Usage: +# source "path/to/modules.sh" +# inst_module_install "mod-transmog:my-custom-dir@develop:abc123" +# inst_module # Interactive menu +# inst_module search "transmog" # Direct command +# +# ============================================================================= +CURRENT_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd ) + +source "$CURRENT_PATH/../../../bash_shared/includes.sh" +source "$CURRENT_PATH/../includes.sh" +source "$AC_PATH_APPS/bash_shared/menu_system.sh" + +# ----------------------------------------------------------------------------- +# Color support (disabled when not a TTY or NO_COLOR is set) +# ----------------------------------------------------------------------------- +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + if command -v tput >/dev/null 2>&1; then + _ac_cols=$(tput colors 2>/dev/null || echo 0) + else + _ac_cols=0 + fi +else + _ac_cols=0 +fi + +if [ "${FORCE_COLOR:-}" != "" ] || [ "${_ac_cols}" -ge 8 ]; then + C_RESET='\033[0m' + C_BOLD='\033[1m' + C_DIM='\033[2m' + C_RED='\033[31m' + C_GREEN='\033[32m' + C_YELLOW='\033[33m' + C_BLUE='\033[34m' + C_MAGENTA='\033[35m' + C_CYAN='\033[36m' +else + C_RESET='' + C_BOLD='' + C_DIM='' + C_RED='' + C_GREEN='' + C_YELLOW='' + C_BLUE='' + C_MAGENTA='' + C_CYAN='' +fi + +# Simple helpers for consistent colored output +function print_info() { printf "%b\n" "${C_CYAN}$*${C_RESET}"; } +function print_warn() { printf "%b\n" "${C_YELLOW}$*${C_RESET}"; } +function print_error() { printf "%b\n" "${C_RED}$*${C_RESET}"; } +function print_success() { printf "%b\n" "${C_GREEN}$*${C_RESET}"; } +function print_skip() { printf "%b\n" "${C_BLUE}$*${C_RESET}"; } +function print_header() { printf "%b\n" "${C_BOLD}${C_CYAN}$*${C_RESET}"; } + +# Module management menu definition +# Format: "key|short|description" +module_menu_items=( + "search|s|Search for available modules" + "install|i|Install one or more modules" + "update|u|Update installed modules" + "remove|r|Remove installed modules" + "list|l|List installed modules" + "help|h|Show detailed help" + "quit|q|Close this menu" +) + +# Menu command handler for module operations +function handle_module_command() { + local key="$1" + shift + + case "$key" in + "search") + inst_module_search "$@" + ;; + "install") + inst_module_install "$@" + ;; + "update") + inst_module_update "$@" + ;; + "remove") + inst_module_remove "$@" + ;; + "list") + inst_module_list "$@" + ;; + "help") + inst_module_help + ;; + "quit") + print_info "Exiting module manager..." + return 0 + ;; + *) + print_error "Invalid option. Use 'help' to see available commands." + return 1 + ;; + esac +} + +# Show detailed module help +function inst_module_help() { + print_header "AzerothCore Module Manager Help" + echo "===============================" + echo "" + echo "Usage:" + echo " ./acore.sh module # Interactive menu" + echo " ./acore.sh module search [terms...]" + echo " ./acore.sh module install [--all | modules...]" + echo " ./acore.sh module update [--all | modules...]" + echo " ./acore.sh module remove [modules...]" + echo " ./acore.sh module list # List installed modules" + echo "" + echo "Module Specification Syntax:" + echo " name # Simple name (e.g., mod-transmog)" + echo " owner/name # GitHub repository" + echo " name:branch # Specific branch" + echo " name:branch:commit # Specific commit" + echo " name:dirname@branch # Custom directory name" + echo " https://github.com/... # Full URL" + echo "" + echo "Examples:" + echo " ./acore.sh module install mod-transmog" + echo " ./acore.sh module install azerothcore/mod-transmog:develop" + echo " ./acore.sh module update --all" + echo " ./acore.sh module remove mod-transmog" + echo "" +} + +# List installed modules +function inst_module_list() { + print_header "Installed Modules" + echo "==================" + local count=0 + while read -r repo_ref branch commit; do + [[ -z "$repo_ref" ]] && continue + count=$((count + 1)) + printf " %s. %b (%s)%b\n" "$count" "${C_GREEN}${repo_ref}" "${branch}" "${C_RESET}" + if [[ "$commit" != "-" ]]; then + printf " %bCommit:%b %s\n" "${C_DIM}" "${C_RESET}" "$commit" + fi + done < <(inst_mod_list_read) + + if [[ $count -eq 0 ]]; then + print_warn " No modules installed." + fi + echo "" +} + +# Dispatcher for the unified `module` command. +# Usage: ./acore.sh module [args...] +# ./acore.sh module # Interactive menu +function inst_module() { + # If no arguments provided, start interactive menu + if [[ $# -eq 0 ]]; then + menu_run_with_items "MODULE MANAGER" handle_module_command -- "${module_menu_items[@]}" -- + return $? + fi + + # Normalize arguments into an array + local tokens=() + read -r -a tokens <<< "$*" + local cmd="${tokens[0]}" + local args=("${tokens[@]:1}") + + case "$cmd" in + ""|"help"|"-h"|"--help") + inst_module_help + ;; + "search"|"s") + inst_module_search "${args[@]}" + ;; + "install"|"i") + inst_module_install "${args[@]}" + ;; + "update"|"u") + inst_module_update "${args[@]}" + ;; + "remove"|"r") + inst_module_remove "${args[@]}" + ;; + "list"|"l") + inst_module_list "${args[@]}" + ;; + *) + print_error "Unknown module command: $cmd. Use 'help' to see available commands." + return 1 + ;; + esac +} + +# ============================================================================= +# Module Specification Parsing +# ============================================================================= + +# Parse a module spec with advanced syntax: +# - New syntax: repo[:dirname][@branch[:commit]] +# +# Examples: +# "mod-transmog" -> uses default branch, directory name = mod-transmog +# "mod-transmog:custom-dir" -> uses default branch, directory name = custom-dir +# "mod-transmog@develop" -> uses develop branch, directory name = mod-transmog +# "mod-transmog:custom-dir@develop:abc123" -> custom directory, develop branch, specific commit +# +# Output: "repo_ref owner name branch commit url dirname" +function inst_parse_module_spec() { + local spec="$1" + + local dirname="" branch="" commit="" repo_part="" + + # Parse the new syntax: repo[:dirname][@branch[:commit]] + + # First, check if this is a URL (contains :// or starts with git@) + local is_url=0 + if [[ "$spec" =~ :// ]] || [[ "$spec" =~ ^git@ ]]; then + is_url=1 + fi + + # Parse directory and branch differently for URLs vs simple names + local repo_with_branch="$spec" + if [[ $is_url -eq 1 ]]; then + # For URLs, look for :dirname pattern, but be careful about ports + # Strategy: only match :dirname if it's clearly after the repository path + + # Look for :dirname patterns at the end, but not if it looks like a port + if [[ "$spec" =~ ^(.*\.git):([^@/:]+)(@.*)?$ ]]; then + # Repo ending with .git:dirname + repo_with_branch="${BASH_REMATCH[1]}${BASH_REMATCH[3]}" + dirname="${BASH_REMATCH[2]}" + elif [[ "$spec" =~ ^(.*://[^/]+/[^:]*[^0-9]):([^@/:]+)(@.*)?$ ]]; then + # URL with path ending in non-digit:dirname (avoid matching ports) + repo_with_branch="${BASH_REMATCH[1]}${BASH_REMATCH[3]}" + dirname="${BASH_REMATCH[2]}" + fi + # If no custom dirname found, repo_with_branch remains the original spec + else + # For simple names, use the original logic + if [[ "$spec" =~ ^([^@:]+):([^@:]+)(@.*)?$ ]]; then + repo_with_branch="${BASH_REMATCH[1]}${BASH_REMATCH[3]}" + dirname="${BASH_REMATCH[2]}" + fi + fi + + # Now parse branch and commit from the repo part + # Be careful not to confuse URL @ with branch @ + if [[ "$repo_with_branch" =~ :// ]]; then + # For URLs, look for @ after the authority part + if [[ "$repo_with_branch" =~ ^[^/]*//[^/]+/.*@([^:]+)(:(.+))?$ ]]; then + # @ found in path part - treat as branch + repo_part="${repo_with_branch%@*}" + branch="${BASH_REMATCH[1]}" + commit="${BASH_REMATCH[3]:-}" + elif [[ "$repo_with_branch" =~ ^([^@]*@[^/]+/.*)@([^:]+)(:(.+))?$ ]]; then + # @ found after URL authority @ - treat as branch + repo_part="${BASH_REMATCH[1]}" + branch="${BASH_REMATCH[2]}" + commit="${BASH_REMATCH[4]:-}" + else + repo_part="$repo_with_branch" + fi + elif [[ "$repo_with_branch" =~ ^git@ ]]; then + # Git SSH format - look for @ after the initial git@host: part + if [[ "$repo_with_branch" =~ ^git@[^:]+:.*@([^:]+)(:(.+))?$ ]]; then + repo_part="${repo_with_branch%@*}" + branch="${BASH_REMATCH[1]}" + commit="${BASH_REMATCH[3]:-}" + else + repo_part="$repo_with_branch" + fi + else + # Non-URL format - use original logic + if [[ "$repo_with_branch" =~ ^([^@]+)@([^:]+)(:(.+))?$ ]]; then + repo_part="${BASH_REMATCH[1]}" + branch="${BASH_REMATCH[2]}" + commit="${BASH_REMATCH[4]:-}" + else + repo_part="$repo_with_branch" + fi + fi + + # Normalize repo reference and extract owner/name. + local repo_ref owner name url owner_repo + repo_ref="$repo_part" + + # If repo_ref is a URL, extract owner/name from path when possible + if [[ "$repo_ref" =~ :// ]] || [[ "$repo_ref" =~ ^git@ ]]; then + # Handle various URL formats + local path_part="" + if [[ "$repo_ref" =~ ^https?://[^/]+:?[0-9]*/(.+)$ ]]; then + # HTTPS URL (with or without port) + path_part="${BASH_REMATCH[1]}" + elif [[ "$repo_ref" =~ ^ssh://.*@[^/]+:?[0-9]*/(.+)$ ]]; then + # SSH URL with user@host:port/path format + path_part="${BASH_REMATCH[1]}" + elif [[ "$repo_ref" =~ ^ssh://[^@/]+:?[0-9]*/(.+)$ ]]; then + # SSH URL with host:port/path format (no user@) + path_part="${BASH_REMATCH[1]}" + elif [[ "$repo_ref" =~ ^git@[^:]+:(.+)$ ]]; then + # Git SSH format (git@host:path) + path_part="${BASH_REMATCH[1]}" + fi + + # Extract owner/name from path + if [[ -n "$path_part" ]]; then + # Remove .git suffix and any :dirname suffix + path_part="${path_part%.git}" + path_part="${path_part%:*}" + + if [[ "$path_part" == *"/"* ]]; then + owner="$(echo "$path_part" | awk -F'/' '{print $(NF-1)}')" + name="$(echo "$path_part" | awk -F'/' '{print $NF}')" + else + owner="unknown" + name="$path_part" + fi + else + owner="unknown" + name="unknown" + fi + else + owner_repo="$repo_ref" + if [[ "$owner_repo" == *"/"* ]]; then + owner="$(echo "$owner_repo" | cut -d'/' -f1)" + name="$(echo "$owner_repo" | cut -d'/' -f2)" + else + owner="azerothcore" + name="$owner_repo" + repo_ref="$owner/$name" + fi + fi + + # Build URL only if repo_ref is not already a URL + if [[ "$repo_ref" =~ :// ]] || [[ "$repo_ref" =~ ^git@ ]]; then + url="$repo_ref" + else + url="https://github.com/${repo_ref}" + fi + + # Use custom dirname if provided, otherwise default to module name + if [ -z "$dirname" ]; then + dirname="$name" + fi + + echo "$repo_ref" "$owner" "$name" "${branch:--}" "${commit:--}" "$url" "$dirname" +} + +# ============================================================================= +# Cross-Format Module Recognition +# ============================================================================= + +# Extract owner/name from any repository reference for intelligent matching. +# This enables recognizing the same module regardless of specification format. +# +# Supported formats: +# - GitHub HTTPS: https://github.com/owner/name.git +# - GitHub SSH: git@github.com:owner/name.git +# - GitLab HTTPS: https://gitlab.com/owner/name.git +# - Owner/name: owner/name +# - Simple name: mod-name (assumes azerothcore namespace) +# +# Returns: "owner/name" format for consistent comparison +function inst_extract_owner_name { + local repo_ref="$1" + + # For URLs, don't remove dirname suffix since : is part of the URL + local base_ref="$repo_ref" + if [[ ! "$repo_ref" =~ :// ]] && [[ ! "$repo_ref" =~ ^git@ ]]; then + # Only remove dirname suffix for non-URL formats + base_ref="${repo_ref%%:*}" + fi + + # Handle various URL formats with possible ports + if [[ "$base_ref" =~ ^https?://[^/]+:?[0-9]*/([^/]+)/([^/?]+) ]]; then + # HTTPS URL format (with or without port) - matches github.com, gitlab.com, custom hosts + local owner="${BASH_REMATCH[1]}" + local name="${BASH_REMATCH[2]}" + name="${name%:*}" # Remove any :dirname suffix first + name="${name%.git}" # Then remove .git suffix if present + echo "$owner/$name" + elif [[ "$base_ref" =~ ^ssh://[^/]+:?[0-9]*/([^/]+)/([^/?]+) ]]; then + # SSH URL format (with or without port) + local owner="${BASH_REMATCH[1]}" + local name="${BASH_REMATCH[2]}" + name="${name%:*}" # Remove any :dirname suffix first + name="${name%.git}" # Then remove .git suffix if present + echo "$owner/$name" + elif [[ "$base_ref" =~ ^git@[^:]+:([^/]+)/([^/?]+) ]]; then + # Git SSH format (git@host:owner/repo) + local owner="${BASH_REMATCH[1]}" + local name="${BASH_REMATCH[2]}" + name="${name%:*}" # Remove any :dirname suffix first + name="${name%.git}" # Then remove .git suffix if present + echo "$owner/$name" + elif [[ "$base_ref" =~ ^[^/]+/[^/]+$ ]]; then + # Format: owner/name (check after URL patterns) + echo "$base_ref" + elif [[ "$base_ref" =~ ^(mod-|module-)?([a-zA-Z0-9-]+)$ ]]; then + # Simple module name, assume azerothcore namespace + local modname="${BASH_REMATCH[2]}" + if [[ "$base_ref" == mod-* ]]; then + modname="$base_ref" + else + modname="mod-$modname" + fi + echo "azerothcore/$modname" + else + # Unknown format, return as-is + echo "$base_ref" + fi +} + +# ============================================================================= +# Module List Management +# ============================================================================= + +# Returns path to modules list file (configurable via MODULES_LIST_FILE). +function inst_modules_list_path() { + local path="${MODULES_LIST_FILE:-"$AC_PATH_ROOT/conf/modules.list"}" + echo "$path" +} + +# Ensure the modules list file exists and its directory is created. +function inst_mod_list_ensure() { + local file + file="$(inst_modules_list_path)" + mkdir -p "$(dirname "$file")" + [ -f "$file" ] || touch "$file" +} + +# Read modules list into stdout as triplets: "name branch commit" +# Skips comments (# ...) and blank lines. +function inst_mod_list_read() { + local file + file="$(inst_modules_list_path)" + [ -f "$file" ] || return 0 + # shellcheck disable=SC2013 + while IFS= read -r line; do + [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue + echo "$line" + done < "$file" +} + +# Check whether a module spec matches the exclusion list. +# - Reads space/newline separated items from env var MODULES_EXCLUDE_LIST +# - Supports cross-format matching via inst_extract_owner_name +# Returns 0 if excluded, 1 otherwise. +function inst_mod_is_excluded() { + local spec="$1" + local target_owner_name + target_owner_name=$(inst_extract_owner_name "$spec") + + # No exclusions configured + if [[ -z "${MODULES_EXCLUDE_LIST:-}" ]]; then + return 1 + fi + + # Split on default IFS (space, tab, newline) + local items=() + # Use mapfile to split MODULES_EXCLUDE_LIST on newlines; fallback to space if no newlines + if [[ "${MODULES_EXCLUDE_LIST}" == *$'\n'* ]]; then + mapfile -t items <<< "${MODULES_EXCLUDE_LIST}" + else + read -r -a items <<< "${MODULES_EXCLUDE_LIST}" + fi + + local it it_owner + for it in "${items[@]}"; do + [[ -z "$it" ]] && continue + it_owner=$(inst_extract_owner_name "$it") + if [[ "$it_owner" == "$target_owner_name" ]]; then + return 0 + fi + done + return 1 +} + +# Add or update an entry in the list: repo_ref branch commit +# Removes any existing entries with the same owner/name to avoid duplicates +function inst_mod_list_upsert() { + local repo_ref="$1"; shift + local branch="$1"; shift + local commit="$1"; shift + local target_owner_name + target_owner_name=$(inst_extract_owner_name "$repo_ref") + + inst_mod_list_ensure + local file tmp tmp_uns tmp_sorted + file="$(inst_modules_list_path)" + tmp="${file}.tmp" + tmp_uns="${file}.unsorted" + tmp_sorted="${file}.sorted" + + # Build a list without existing duplicates + : > "$tmp_uns" + while read -r existing_ref existing_branch existing_commit; do + [[ -z "$existing_ref" ]] && continue + local existing_owner_name + existing_owner_name=$(inst_extract_owner_name "$existing_ref") + if [[ "$existing_owner_name" != "$target_owner_name" ]]; then + echo "$existing_ref $existing_branch $existing_commit" >> "$tmp_uns" + fi + done < <(inst_mod_list_read) + # Add/replace the new entry (preserving original repo_ref format) + echo "$repo_ref $branch $commit" >> "$tmp_uns" + + # Create key-prefixed lines to sort by normalized owner/name + : > "$tmp" + while read -r r b c; do + [[ -z "$r" ]] && continue + local k + k=$(inst_extract_owner_name "$r") + printf "%s\t%s %s %s\n" "$k" "$r" "$b" "$c" >> "$tmp" + done < "$tmp_uns" + + # Stable sort by key and strip the key + LC_ALL=C sort -t $'\t' -k1,1 -s "$tmp" | cut -f2- > "$tmp_sorted" && mv "$tmp_sorted" "$file" + rm -f "$tmp" "$tmp_uns" "$tmp_sorted" 2>/dev/null || true +} + +# Remove an entry from the list by matching owner/name. +# This allows removing modules regardless of how they were specified (URL vs owner/name) +function inst_mod_list_remove() { + local repo_ref="$1" + local target_owner_name + target_owner_name=$(inst_extract_owner_name "$repo_ref") + + local file + file="$(inst_modules_list_path)" + [ -f "$file" ] || return 0 + + local tmp_uns="${file}.unsorted" + local tmp="${file}.tmp" + local tmp_sorted="${file}.sorted" + + # Keep only lines where owner/name doesn't match + : > "$tmp_uns" + while read -r existing_ref existing_branch existing_commit; do + [[ -z "$existing_ref" ]] && continue + local existing_owner_name + existing_owner_name=$(inst_extract_owner_name "$existing_ref") + if [[ "$existing_owner_name" != "$target_owner_name" ]]; then + echo "$existing_ref $existing_branch $existing_commit" >> "$tmp_uns" + fi + done < <(inst_mod_list_read) + + # Key-prefix and sort for deterministic alphabetical order + : > "$tmp" + while read -r r b c; do + [[ -z "$r" ]] && continue + local k + k=$(inst_extract_owner_name "$r") + printf "%s\t%s %s %s\n" "$k" "$r" "$b" "$c" >> "$tmp" + done < "$tmp_uns" + + LC_ALL=C sort -t $'\t' -k1,1 -s "$tmp" | cut -f2- > "$tmp_sorted" && mv "$tmp_sorted" "$file" + rm -f "$tmp" "$tmp_uns" "$tmp_sorted" 2>/dev/null || true +} + +# Check if a module is already installed by comparing owner/name +# Returns the existing repo_ref if found, empty if not found +function inst_mod_is_installed() { + local spec="$1" + local target_owner_name + target_owner_name=$(inst_extract_owner_name "$spec") + + # Use a different approach: read into a variable first, then process + local modules_content + modules_content=$(inst_mod_list_read) + + # Process each line + while IFS= read -r line; do + [[ -z "$line" ]] && continue + read -r repo_ref branch commit <<< "$line" + local existing_owner_name + existing_owner_name=$(inst_extract_owner_name "$repo_ref") + if [[ "$existing_owner_name" == "$target_owner_name" ]]; then + echo "$repo_ref" # Return the existing entry + return 0 + fi + done <<< "$modules_content" + + return 1 +} + +# ============================================================================= +# Conflict Detection and Validation +# ============================================================================= + +# Check for module directory conflicts with helpful error messages +function inst_check_module_conflict { + local dirname="$1" + local repo_ref="$2" + + if [ -d "$J_PATH_MODULES/$dirname" ]; then + print_error "Error: Directory '$dirname' already exists." + print_warn "Possible solutions:" + echo " 1. Use a different directory name: $repo_ref:my-custom-name" + echo " 2. Remove the existing directory first" + echo " 3. Use the update command if this is the same module" + return 1 + fi + return 0 +} + +# ============================================================================= +# Module Operations +# ============================================================================= + +# Get version and branch information from acore-module.json +function inst_getVersionBranch() { + local res="master" + local v="not-defined" + local MODULE_MAJOR=0 + local MODULE_MINOR=0 + local MODULE_PATCH=0 + local MODULE_SPECIAL=0; + local ACV_MAJOR=0 + local ACV_MINOR=0 + local ACV_PATCH=0 + local ACV_SPECIAL=0; + local curldata=$(curl -f --silent -H 'Cache-Control: no-cache' "$1" || echo "{}") + local parsed=$(echo "$curldata" | "$AC_PATH_DEPS/jsonpath/JSONPath.sh" -b '$.compatibility.*.[version,branch]') + + semverParseInto "$ACORE_VERSION" ACV_MAJOR ACV_MINOR ACV_PATCH ACV_SPECIAL + + if [[ ! -z "$parsed" ]]; then + readarray -t vers < <(echo "$parsed") + local idx + res="none" + # since we've the pair version,branch alternated in not associative and one-dimensional + # array, we've to simulate the association with length/2 trick + for idx in `seq 0 $((${#vers[*]}/2-1))`; do + semverParseInto "${vers[idx*2]}" MODULE_MAJOR MODULE_MINOR MODULE_PATCH MODULE_SPECIAL + if [[ $MODULE_MAJOR -eq $ACV_MAJOR && $MODULE_MINOR -le $ACV_MINOR ]]; then + res="${vers[idx*2+1]}" + v="${vers[idx*2]}" + fi + done + fi + + echo "$v" "$res" +} + +# Search for modules in the AzerothCore repository +function inst_module_search { + # Accept 0..N search terms; if none provided, prompt the user. + local terms=("$@") + if [ ${#terms[@]} -eq 0 ]; then + echo "Type what to search (blank for full list)" + read -p "Insert name(s): " _line + if [ -n "$_line" ]; then + read -r -a terms <<< "$_line" + fi + fi + + local CATALOG_URL="https://www.azerothcore.org/data/catalogue.json" + + print_header "Searching ${terms[*]}..." + echo "" + + # Build candidate list from catalogue (full_name = owner/repo) + local MODS=() + if command -v jq >/dev/null 2>&1; then + mapfile -t MODS < <(curl --silent -L "$CATALOG_URL" \ + | jq -r ' + [ .. | objects + | select(.full_name and .topics) + | select(.topics | index("azerothcore-module")) + ] + | unique_by(.full_name) + | sort_by(.stargazers_count // 0) | reverse + | .[].full_name + ') + else + # Fallback without jq: best-effort extraction of owner/repo + mapfile -t MODS < <(curl --silent -L "$CATALOG_URL" \ + | grep -oE '\"full_name\"\\s*:\\s*\"[^\"/[:space:]]+/[^\"[:space:]]+\"' \ + | sed -E 's/.*\"full_name\"\\s*:\\s*\"([^\"]+)\".*/\\1/' \ + | sort -u) + fi + + # Local AND filter on user terms (case-insensitive) against full_name + if (( ${#terms[@]} > 0 )); then + local filtered=() + local item + for item in "${MODS[@]}"; do + local keep=1 + local lower="${item,,}" + local t + for t in "${terms[@]}"; do + [ -z "$t" ] && continue + if [[ "$lower" != *"${t,,}"* ]]; then + keep=0; break + fi + done + (( keep )) && filtered+=("$item") + done + MODS=("${filtered[@]}") + fi + + if (( ${#MODS[@]} == 0 )); then + echo "No results." + echo "" + return 0 + fi + + local idx=0 + while (( ${#MODS[@]} > idx )); do + local mod_full="${MODS[idx++]}" # owner/repo + local mod="${mod_full##*/}" # repo name only for display + read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/${mod_full}/master/acore-module.json") + + if [[ "$b" != "none" ]]; then + printf "%b -> %b (tested with AC version: %s)%b\n" "" "${C_GREEN}${mod}${C_RESET}" "$v" "" + else + printf "%b -> %b %b(NOTE: The module latest tested AC revision is Unknown)%b\n" "" "${C_GREEN}${mod}${C_RESET}" "${C_YELLOW}" "${C_RESET}" + fi + done + + echo "" + echo "" +} + + +# Install one or more modules with advanced syntax support +function inst_module_install { + # Support multiple modules and the --all flag; prompt if none specified. + local args=("$@") + local use_all=false + if [ ${#args[@]} -gt 0 ] && { [ "${args[0]}" = "--all" ] || [ "${args[0]}" = "-a" ]; }; then + use_all=true + shift || true + fi + + local modules=("$@") + + print_header "Installing modules: ${modules[*]}" + + if $use_all; then + # Install all modules from the list (respecting recorded branch and commit). + inst_mod_list_ensure + local line repo_ref branch commit url owner modname dirname + + # First pass: detect duplicate target directories (flat structure) + declare -A _seen _first + local dup_error=0 + while read -r repo_ref branch commit; do + [ -z "$repo_ref" ] && continue + # Skip excluded modules when checking duplicates + if inst_mod_is_excluded "$repo_ref"; then + continue + fi + parsed_output=$(inst_parse_module_spec "$repo_ref") + IFS=' ' read -r _ owner modname _ _ url dirname <<< "$parsed_output" + # dirname defaults to repo name; flat install path uses dirname only + if [[ -n "${_seen[$dirname]:-}" ]]; then + print_error "Error: duplicate module target directory '$dirname' detected in modules.list:" + echo " - ${_first[$dirname]}" + echo " - ${repo_ref}" + print_warn "Use a custom folder name to disambiguate, e.g.: ${repo_ref}:$dirname-alt" + dup_error=1 + else + _seen[$dirname]=1 + _first[$dirname]="$repo_ref" + fi + done < <(inst_mod_list_read) + if [[ "$dup_error" -ne 0 ]]; then + return 1 + fi + + # Second pass: install in flat modules directory (no owner subfolders) + while read -r repo_ref branch commit; do + [ -z "$repo_ref" ] && continue + # Skip excluded entries during installation + if inst_mod_is_excluded "$repo_ref"; then + print_warn "[$repo_ref] Excluded by MODULES_EXCLUDE_LIST (skipping)." + continue + fi + parsed_output=$(inst_parse_module_spec "$repo_ref") + IFS=' ' read -r _ owner modname _ _ url dirname <<< "$parsed_output" + + if [ -d "$J_PATH_MODULES/$dirname" ]; then + print_skip "[$repo_ref] Already installed (skipping)." + continue + fi + + if Joiner:add_repo "$url" "$dirname" "$branch" ""; then + # Checkout the recorded commit if present + if [ -n "$commit" ]; then + git -C "$J_PATH_MODULES/$dirname" fetch --all --quiet || true + if git -C "$J_PATH_MODULES/$dirname" rev-parse --verify "$commit" >/dev/null 2>&1; then + git -C "$J_PATH_MODULES/$dirname" checkout --quiet "$commit" + fi + fi + local curCommit + curCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") + inst_mod_list_upsert "$repo_ref" "$branch" "$curCommit" + print_success "[$repo_ref] Installed." + else + print_error "[$repo_ref] Install failed." + exit 1; + fi + done < <(inst_mod_list_read) + else + # Install specified modules; prompt if none specified. + if [ ${#modules[@]} -eq 0 ]; then + echo "Type the name(s) of the module(s) to install" + read -p "Insert name(s): " _line + read -r -a modules <<< "$_line" + fi + + local spec name override_branch override_commit v b def curCommit existing_repo_ref dirname + for spec in "${modules[@]}"; do + [ -z "$spec" ] && continue + + # Check if module is already installed (by owner/name matching) + existing_repo_ref=$(inst_mod_is_installed "$spec" || true) + if [ -n "$existing_repo_ref" ]; then + print_skip "[$spec] Already installed as [$existing_repo_ref] (skipping)." + continue + fi + + parsed_output=$(inst_parse_module_spec "$spec") + IFS=' ' read -r repo_ref owner modname override_branch override_commit url dirname <<< "$parsed_output" + [ -z "$repo_ref" ] && continue + + # Check for directory conflicts with custom directory names + if ! inst_check_module_conflict "$dirname" "$repo_ref"; then + continue + fi + + # override_branch takes precedence; otherwise consult acore-module.json on azerothcore unless repo_ref contains owner or URL + if [ -n "$override_branch" ] && [ "$override_branch" != "-" ]; then + b="$override_branch" + else + # For GitHub repositories, use raw.githubusercontent.com to check acore-module.json + if [[ "$url" =~ github.com ]] || [[ "$repo_ref" =~ ^[^/]+/[^/]+$ ]]; then + read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/${owner}/${modname}/master/acore-module.json") + else + # Unknown host: try the repository URL as-is (may fail) + read v b < <(inst_getVersionBranch "${url}/master/acore-module.json") + fi + if [[ "$v" == "none" || "$v" == "not-defined" || "$b" == "none" ]]; then + def="$(inst_get_default_branch "$repo_ref")" + print_warn "Warning: $repo_ref has no compatible acore-module.json; installing from branch '$def' (latest commit)." + b="$def" + fi + fi + + # Use flat directory structure with custom directory name + if [ -d "$J_PATH_MODULES/$dirname" ]; then + print_skip "[$repo_ref] Already installed (skipping)." + curCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") + inst_mod_list_upsert "$repo_ref" "$b" "$curCommit" + continue + fi + + if Joiner:add_repo "$url" "$dirname" "$b" ""; then + # If a commit was provided, try to checkout it + if [ -n "$override_commit" ] && [ "$override_commit" != "-" ]; then + git -C "$J_PATH_MODULES/$dirname" fetch --all --quiet || true + if git -C "$J_PATH_MODULES/$dirname" rev-parse --verify "$override_commit" >/dev/null 2>&1; then + git -C "$J_PATH_MODULES/$dirname" checkout --quiet "$override_commit" + else + print_warn "[$repo_ref] provided commit '$override_commit' not found; staying on branch '$b' HEAD." + fi + fi + curCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") + inst_mod_list_upsert "$repo_ref" "$b" "$curCommit" + print_success "[$repo_ref] Installed in '$dirname'. Please re-run compiling and db assembly." + else + print_error "[$repo_ref] Install failed or module not found" + exit 1; + fi + done + fi + + echo "" + echo "" +} + +# Update one or more modules +function inst_module_update { + # Handle help request + if [[ "$1" == "--help" || "$1" == "-h" ]]; then + inst_module_help + return 0 + fi + + # Support multiple modules and the --all flag; prompt if none specified. + local args=("$@") + local use_all=false + if [ ${#args[@]} -gt 0 ] && { [ "${args[0]}" = "--all" ] || [ "${args[0]}" = "-a" ]; }; then + use_all=true + shift || true + fi + + local _tmp=$PWD + + if $use_all; then + local line repo_ref branch commit newCommit owner modname url dirname + while read -r repo_ref branch commit; do + [ -z "$repo_ref" ] && continue + # Skip excluded modules during update --all + if inst_mod_is_excluded "$repo_ref"; then + print_warn "[$repo_ref] Excluded by MODULES_EXCLUDE_LIST (skipping)." + continue + fi + parsed_output=$(inst_parse_module_spec "$repo_ref") + IFS=' ' read -r _ owner modname _ _ url dirname <<< "$parsed_output" + + dirname="${dirname:-$modname}" + if [ ! -d "$J_PATH_MODULES/$dirname/" ]; then + print_skip "[$repo_ref] Not installed locally, skipping." + continue + fi + + if Joiner:upd_repo "$url" "$dirname" "$branch" ""; then + newCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") + inst_mod_list_upsert "$repo_ref" "$branch" "$newCommit" + print_success "[$repo_ref] Updated to latest commit on '$branch'." + else + print_error "[$repo_ref] Cannot update" + fi + done < <(inst_mod_list_read) + else + local modules=("$@") + if [ ${#modules[@]} -eq 0 ]; then + echo "Type the name(s) of the module(s) to update" + read -p "Insert name(s): " _line + read -r -a modules <<< "$_line" + fi + + local spec repo_ref override_branch override_commit owner modname url dirname v b branch def newCommit + for spec in "${modules[@]}"; do + [ -z "$spec" ] && continue + parsed_output=$(inst_parse_module_spec "$spec") + IFS=' ' read -r repo_ref owner modname override_branch override_commit url dirname <<< "$parsed_output" + + dirname="${dirname:-$modname}" + if [ -d "$J_PATH_MODULES/$dirname/" ]; then + # determine preferred branch if not provided + if [ -n "$override_branch" ] && [ "$override_branch" != "-" ]; then + b="$override_branch" + else + # try reading acore-module.json for this repo + if [[ "$url" =~ github.com ]]; then + read v b < <(inst_getVersionBranch "https://raw.githubusercontent.com/${owner}/${modname}/master/acore-module.json") + else + read v b < <(inst_getVersionBranch "${url}/master/acore-module.json") + fi + if [[ "$v" == "none" || "$v" == "not-defined" || "$b" == "none" ]]; then + if branch=$(git -C "$J_PATH_MODULES/$dirname" rev-parse --abbrev-ref HEAD 2>/dev/null); then + print_warn "Warning: $repo_ref has no compatible acore-module.json; updating current branch '$branch'." + b="$branch" + else + def="$(inst_get_default_branch "$repo_ref")" + print_warn "Warning: $repo_ref has no compatible acore-module.json and no git branch detected; updating default branch '$def'." + b="$def" + fi + fi + fi + + if Joiner:upd_repo "$url" "$dirname" "$b" ""; then + newCommit=$(git -C "$J_PATH_MODULES/$dirname" rev-parse HEAD 2>/dev/null || echo "") + inst_mod_list_upsert "$repo_ref" "$b" "$newCommit" + print_success "[$repo_ref] Done, please re-run compiling and db assembly" + else + print_error "[$repo_ref] Cannot update" + fi + else + print_error "[$repo_ref] Cannot update! Path doesn't exist ($J_PATH_MODULES/$dirname/)" + fi + done + fi + + echo "" + echo "" +} + +# Remove one or more modules +function inst_module_remove { + # Support multiple modules; prompt if none specified. + local modules=("$@") + if [ ${#modules[@]} -eq 0 ]; then + echo "Type the name(s) of the module(s) to remove" + read -p "Insert name(s): " _line + read -r -a modules <<< "$_line" + fi + + local spec repo_ref owner modname url override_branch override_commit dirname + for spec in "${modules[@]}"; do + [ -z "$spec" ] && continue + parsed_output=$(inst_parse_module_spec "$spec") + IFS=' ' read -r repo_ref owner modname override_branch override_commit url dirname <<< "$parsed_output" + [ -z "$repo_ref" ] && continue + + dirname="${dirname:-$modname}" + if Joiner:remove "$dirname" ""; then + inst_mod_list_remove "$repo_ref" + print_success "[$repo_ref] Done, please re-run compiling" + else + print_error "[$repo_ref] Cannot remove" + fi + done + + echo "" + echo "" +} diff --git a/apps/installer/main.sh b/apps/installer/main.sh index 396dafaad..b1cba33e8 100644 --- a/apps/installer/main.sh +++ b/apps/installer/main.sh @@ -1,105 +1,107 @@ #!/usr/bin/env bash +# AzerothCore Dashboard Script +# +# This script provides an interactive menu system for AzerothCore management +# using the unified menu system library. +# +# Usage: +# ./acore.sh - Interactive mode with numeric and text selection +# ./acore.sh [args] - Direct command execution (only text commands, no numbers) +# +# Interactive Mode: +# - Select options by number (1, 2, 3...), command name (init, compiler, etc.), +# or short alias (i, c, etc.) +# - All selection methods work in interactive mode +# +# Direct Command Mode: +# - Only command names and short aliases are accepted (e.g., './acore.sh compiler build', './acore.sh c build') +# - Numeric selection is disabled to prevent confusion with command arguments +# - Examples: './acore.sh init', './acore.sh compiler clean', './acore.sh module install mod-name' +# +# Menu System: +# - Uses unified menu system from bash_shared/menu_system.sh +# - Single source of truth for menu definitions +# - Consistent behavior across all AzerothCore tools + CURRENT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - source "$CURRENT_PATH/includes/includes.sh" +source "$AC_PATH_APPS/bash_shared/menu_system.sh" -PS3='[Please enter your choice]: ' -options=( - "init (i): First Installation" # 1 - "install-deps (d): Configure OS dep" # 2 - "pull (u): Update Repository" # 3 - "reset (r): Reset & Clean Repository" # 4 - "compiler (c): Run compiler tool" # 5 - "module-search (ms): Module Search by keyword" # 6 - "module-install (mi): Module Install by name" # 7 - "module-update (mu): Module Update by name" # 8 - "module-remove: (mr): Module Remove by name" # 9 - "client-data: (gd): download client data from github repository (beta)" # 10 - "run-worldserver (rw): execute a simple restarter for worldserver" # 11 - "run-authserver (ra): execute a simple restarter for authserver" # 12 - "docker (dr): Run docker tools" # 13 - "version (v): Show AzerothCore version" # 14 - "service-manager (sm): Run service manager to run authserver and worldserver in background" # 15 - "quit: Exit from this menu" # 16 - ) +# Menu: single ordered source of truth (no functions in strings) +# Format: "key|short|description" +menu_items=( + "init|i|First Installation" + "install-deps|d|Configure OS dep" + "pull|u|Update Repository" + "reset|r|Reset & Clean Repository" + "compiler|c|Run compiler tool" + "module|m|Module manager (search/install/update/remove)" + "client-data|gd|download client data from github repository (beta)" + "run-worldserver|rw|execute a simple restarter for worldserver" + "run-authserver|ra|execute a simple restarter for authserver" + "docker|dr|Run docker tools" + "version|v|Show AzerothCore version" + "service-manager|sm|Run service manager to run authserver and worldserver in background" + "quit|q|Exit from this menu" +) -function _switch() { - _reply="$1" - _opt="$2" - case $_reply in - ""|"i"|"init"|"1") - inst_allInOne +# Menu command handler - called by menu system for each command +function handle_menu_command() { + local key="$1" + shift + + case "$key" in + "init") + inst_allInOne ;; - ""|"d"|"install-deps"|"2") - inst_configureOS + "install-deps") + inst_configureOS ;; - ""|"u"|"pull"|"3") - inst_updateRepo + "pull") + inst_updateRepo ;; - ""|"r"|"reset"|"4") - inst_resetRepo + "reset") + inst_resetRepo ;; - ""|"c"|"compiler"|"5") - bash "$AC_PATH_APPS/compiler/compiler.sh" $_opt + "compiler") + bash "$AC_PATH_APPS/compiler/compiler.sh" "$@" ;; - ""|"ms"|"module-search"|"6") - inst_module_search "$_opt" + "module") + bash "$AC_PATH_APPS/installer/includes/modules-manager/module-main.sh" "$@" ;; - ""|"mi"|"module-install"|"7") - inst_module_install "$_opt" + "client-data") + inst_download_client_data ;; - ""|"mu"|"module-update"|"8") - inst_module_update "$_opt" + "run-worldserver") + inst_simple_restarter worldserver ;; - ""|"mr"|"module-remove"|"9") - inst_module_remove "$_opt" + "run-authserver") + inst_simple_restarter authserver ;; - ""|"gd"|"client-data"|"10") - inst_download_client_data + "docker") + DOCKER=1 bash "$AC_PATH_ROOT/apps/docker/docker-cmd.sh" "$@" + exit ;; - ""|"rw"|"run-worldserver"|"11") - inst_simple_restarter worldserver - ;; - ""|"ra"|"run-authserver"|"12") - inst_simple_restarter authserver - ;; - ""|"dr"|"docker"|"13") - DOCKER=1 bash "$AC_PATH_ROOT/apps/docker/docker-cmd.sh" "${@:2}" - exit - ;; - ""|"v"|"version"|"14") - # denoRunFile "$AC_PATH_APPS/installer/main.ts" "version" + "version") printf "AzerothCore Rev. %s\n" "$ACORE_VERSION" - exit + exit ;; - ""|"sm"|"service-manager"|"15") - bash "$AC_PATH_APPS/startup-scripts/src/service-manager.sh" "${@:2}" - exit + "service-manager") + bash "$AC_PATH_APPS/startup-scripts/src/service-manager.sh" "$@" + exit ;; - ""|"quit"|"16") + "quit") echo "Goodbye!" - exit + exit ;; - ""|"--help") - echo "Available commands:" - printf '%s\n' "${options[@]}" + *) + echo "Invalid option. Use --help to see available commands." + return 1 ;; - *) echo "invalid option, use --help option for the commands list";; esac } -while true -do - # run option directly if specified in argument - [ ! -z $1 ] && _switch $@ # old method: "${options[$cmdopt-1]}" - [ ! -z $1 ] && exit 0 - - echo "==== ACORE DASHBOARD ====" - select opt in "${options[@]}" - do - _switch $REPLY - break - done -done +# Run the menu system +menu_run_with_items "ACORE DASHBOARD" handle_menu_command -- "${menu_items[@]}" -- "$@" diff --git a/apps/installer/test/bats.conf b/apps/installer/test/bats.conf new file mode 100644 index 000000000..8034254e7 --- /dev/null +++ b/apps/installer/test/bats.conf @@ -0,0 +1,14 @@ +# BATS Test Configuration + +# Set test timeout (in seconds) +export BATS_TEST_TIMEOUT=30 + +# 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 diff --git a/apps/installer/test/test_module_commands.bats b/apps/installer/test/test_module_commands.bats new file mode 100755 index 000000000..1223a80a6 --- /dev/null +++ b/apps/installer/test/test_module_commands.bats @@ -0,0 +1,755 @@ +#!/usr/bin/env bats + +# Tests for installer module commands (search/install/update/remove) +# Focused on installer:module install behavior using a mocked joiner + +load '../../test-framework/bats_libs/acore-support' +load '../../test-framework/bats_libs/acore-assert' + +setup() { + acore_test_setup + # Point to the installer src directory (not needed in this test) + + # Set installer/paths environment for the test + export AC_PATH_APPS="$TEST_DIR/apps" + export AC_PATH_ROOT="$TEST_DIR" + export AC_PATH_DEPS="$TEST_DIR/deps" + export AC_PATH_MODULES="$TEST_DIR/modules" + export MODULES_LIST_FILE="$TEST_DIR/conf/modules.list" + + # Create stubbed deps: joiner.sh (sourced by includes) and semver + mkdir -p "$TEST_DIR/deps/acore/joiner" + cat > "$TEST_DIR/deps/acore/joiner/joiner.sh" << 'EOF' +#!/usr/bin/env bash +# Stub joiner functions used by installer +Joiner:add_repo() { + # arguments: url name branch basedir + echo "ADD $@" > "$TEST_DIR/joiner_called.txt" + return 0 +} +Joiner:upd_repo() { + echo "UPD $@" > "$TEST_DIR/joiner_called.txt" + return 0 +} +Joiner:remove() { + echo "REM $@" > "$TEST_DIR/joiner_called.txt" + return 0 +} +EOF + chmod +x "$TEST_DIR/deps/acore/joiner/joiner.sh" + + mkdir -p "$TEST_DIR/deps/semver_bash" + # Minimal semver stub + cat > "$TEST_DIR/deps/semver_bash/semver.sh" << 'EOF' +#!/usr/bin/env bash +# semver stub +semver::satisfies() { return 0; } +EOF + chmod +x "$TEST_DIR/deps/semver_bash/semver.sh" + + # Provide a minimal compiler includes file expected by installer + mkdir -p "$TEST_DIR/apps/compiler/includes" + touch "$TEST_DIR/apps/compiler/includes/includes.sh" + + # Provide minimal bash_shared includes to satisfy installer include + mkdir -p "$TEST_DIR/apps/bash_shared" + cat > "$TEST_DIR/apps/bash_shared/includes.sh" << 'EOF' +#!/usr/bin/env bash +# minimal stub +EOF + + # Copy the menu system needed by modules.sh + cp "$AC_TEST_ROOT/apps/bash_shared/menu_system.sh" "$TEST_DIR/apps/bash_shared/" + + # Copy the real installer app into the test apps dir + mkdir -p "$TEST_DIR/apps" + cp -r "$(cd "$AC_TEST_ROOT/apps/installer" && pwd)" "$TEST_DIR/apps/installer" +} + +teardown() { + acore_test_teardown +} + +@test "module install should call joiner and record entry in modules list" { + cd "$TEST_DIR" + + # Source installer includes and call the install function directly to avoid menu interaction + run bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_install example-module@main:abcd1234" + + # Check that joiner was called + [ -f "$TEST_DIR/joiner_called.txt" ] + grep -q "ADD" "$TEST_DIR/joiner_called.txt" + + # Check modules list was created and contains the repo_ref and branch + [ -f "$TEST_DIR/conf/modules.list" ] + grep -q "azerothcore/example-module main" "$TEST_DIR/conf/modules.list" +} + +@test "module install with owner/name format should work" { + cd "$TEST_DIR" + + # Test with owner/name format + run bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_install myorg/mymodule" + + # Check that joiner was called with correct URL + [ -f "$TEST_DIR/joiner_called.txt" ] + grep -q "ADD https://github.com/myorg/mymodule mymodule" "$TEST_DIR/joiner_called.txt" + + # Check modules list contains the entry + [ -f "$TEST_DIR/conf/modules.list" ] + grep -q "myorg/mymodule" "$TEST_DIR/conf/modules.list" +} + +@test "module remove should call joiner remove and update modules list" { + cd "$TEST_DIR" + + # First install a module + bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_install test-module" + + # Then remove it + run bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_remove test-module" + + # Check that joiner remove was called + [ -f "$TEST_DIR/joiner_called.txt" ] + # With flat structure, basedir is empty; ensure name is present + grep -q "REM test-module" "$TEST_DIR/joiner_called.txt" + + # Check modules list no longer contains the entry + [ -f "$TEST_DIR/conf/modules.list" ] + ! grep -q "azerothcore/test-module" "$TEST_DIR/conf/modules.list" +} + +# Tests for intelligent module management (duplicate prevention and cross-format removal) + +@test "inst_extract_owner_name should extract owner/name from various formats" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test simple name + run inst_extract_owner_name "mod-transmog" + [ "$output" = "azerothcore/mod-transmog" ] + + # Test owner/name format + run inst_extract_owner_name "azerothcore/mod-transmog" + [ "$output" = "azerothcore/mod-transmog" ] + + # Test HTTPS URL + run inst_extract_owner_name "https://github.com/azerothcore/mod-transmog.git" + [ "$output" = "azerothcore/mod-transmog" ] + + # Test SSH URL + run inst_extract_owner_name "git@github.com:azerothcore/mod-transmog.git" + [ "$output" = "azerothcore/mod-transmog" ] + + # Test GitLab URL + run inst_extract_owner_name "https://gitlab.com/myorg/mymodule.git" + [ "$output" = "myorg/mymodule" ] +} + +@test "inst_extract_owner_name should handle URLs with ports correctly" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test HTTPS URL with port + run inst_extract_owner_name "https://example.com:8080/user/repo.git" + [ "$output" = "user/repo" ] + + # Test SSH URL with port + run inst_extract_owner_name "ssh://git@example.com:2222/owner/module" + [ "$output" = "owner/module" ] + + # Test URL with port and custom directory (should ignore the directory part) + run inst_extract_owner_name "https://gitlab.internal:9443/team/project.git:custom-dir" + [ "$output" = "team/project" ] + + # Test complex URL with port (should extract owner/name correctly) + run inst_extract_owner_name "https://git.company.com:8443/department/awesome-module.git" + [ "$output" = "department/awesome-module" ] +} + +@test "duplicate module entries should be prevented across different formats" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Add module via simple name + inst_mod_list_upsert "mod-transmog" "master" "abc123" + + # Verify it's in the list + grep -q "mod-transmog master abc123" "$TEST_DIR/conf/modules.list" + + # Add same module via owner/name format - should replace, not duplicate + inst_mod_list_upsert "azerothcore/mod-transmog" "dev" "def456" + + # Should only have one entry (the new one) + [ "$(grep -c "azerothcore/mod-transmog" "$TEST_DIR/conf/modules.list")" -eq 1 ] + grep -q "azerothcore/mod-transmog dev def456" "$TEST_DIR/conf/modules.list" + ! grep -q "mod-transmog master abc123" "$TEST_DIR/conf/modules.list" +} + +@test "module installed via URL should be recognized when checking with different formats" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Install via HTTPS URL + inst_mod_list_upsert "https://github.com/azerothcore/mod-transmog.git" "master" "abc123" + + # Should be detected as installed using simple name + run inst_mod_is_installed "mod-transmog" + [ "$status" -eq 0 ] + + # Should be detected as installed using owner/name + run inst_mod_is_installed "azerothcore/mod-transmog" + [ "$status" -eq 0 ] + + # Should be detected as installed using SSH URL + run inst_mod_is_installed "git@github.com:azerothcore/mod-transmog.git" + [ "$status" -eq 0 ] + + # Non-existent module should not be detected + run inst_mod_is_installed "mod-nonexistent" + [ "$status" -ne 0 ] +} + +@test "module installed via URL with port should be recognized correctly" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Install via URL with port + inst_mod_list_upsert "https://gitlab.internal:9443/myorg/my-module.git" "master" "abc123" + + # Should be detected as installed using normalized owner/name + run inst_mod_is_installed "myorg/my-module" + [ "$status" -eq 0 ] + + # Should be detected when checking with different URL format + run inst_mod_is_installed "ssh://git@gitlab.internal:9443/myorg/my-module" + [ "$status" -eq 0 ] + + # Should be detected when checking with custom directory syntax + run inst_mod_is_installed "myorg/my-module:custom-dir" + [ "$status" -eq 0 ] + + # Different module should not be detected + run inst_mod_is_installed "myorg/different-module" + [ "$status" -ne 0 ] +} + +@test "cross-format module removal should work" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Install via SSH URL + inst_mod_list_upsert "git@github.com:azerothcore/mod-transmog.git" "master" "abc123" + + # Verify it's installed + grep -q "git@github.com:azerothcore/mod-transmog.git" "$TEST_DIR/conf/modules.list" + + # Remove using simple name + inst_mod_list_remove "mod-transmog" + + # Should be completely removed + ! grep -q "azerothcore/mod-transmog" "$TEST_DIR/conf/modules.list" + ! grep -q "git@github.com:azerothcore/mod-transmog.git" "$TEST_DIR/conf/modules.list" +} + +@test "module installation should prevent duplicates when already installed" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Install via simple name first + inst_mod_list_upsert "mod-worldchat" "master" "abc123" + + # Try to install same module via URL - should detect it's already installed + run inst_mod_is_installed "https://github.com/azerothcore/mod-worldchat.git" + [ "$status" -eq 0 ] + + # Add via URL should replace the existing entry + inst_mod_list_upsert "https://github.com/azerothcore/mod-worldchat.git" "dev" "def456" + + # Should only have one entry + [ "$(grep -c "azerothcore/mod-worldchat" "$TEST_DIR/conf/modules.list")" -eq 1 ] + grep -q "https://github.com/azerothcore/mod-worldchat.git dev def456" "$TEST_DIR/conf/modules.list" +} + +@test "module update --all uses flat structure (no branch subfolders)" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Prepare modules.list with one entry and a matching local directory + mkdir -p "$TEST_DIR/conf" + echo "azerothcore/mod-transmog master abc123" > "$TEST_DIR/conf/modules.list" + mkdir -p "$TEST_DIR/modules/mod-transmog" + + # Run update all + run bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_update --all" + + # Verify Joiner:upd_repo received flat structure args (no basedir) + [ -f "$TEST_DIR/joiner_called.txt" ] + grep -q "UPD https://github.com/azerothcore/mod-transmog mod-transmog master" "$TEST_DIR/joiner_called.txt" +} + +@test "module update specific uses flat structure with override branch" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Create local directory so update proceeds + mkdir -p "$TEST_DIR/modules/mymodule" + + # Run update specifying owner/name and branch + run bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_update myorg/mymodule@dev" + + # Should call joiner with name 'mymodule' and branch 'dev' (no basedir) + [ -f "$TEST_DIR/joiner_called.txt" ] + grep -q "UPD https://github.com/myorg/mymodule mymodule dev" "$TEST_DIR/joiner_called.txt" +} + +@test "custom directory names should work with new syntax" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test parsing with custom directory name + run inst_parse_module_spec "mod-transmog:my-custom-dir@develop:abc123" + [ "$status" -eq 0 ] + # Should output: repo_ref owner name branch commit url dirname + IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output" + [ "$repo_ref" = "azerothcore/mod-transmog" ] + [ "$owner" = "azerothcore" ] + [ "$name" = "mod-transmog" ] + [ "$branch" = "develop" ] + [ "$commit" = "abc123" ] + [ "$url" = "https://github.com/azerothcore/mod-transmog" ] + [ "$dirname" = "my-custom-dir" ] +} + +@test "directory conflict detection should work" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Create a fake existing directory + mkdir -p "$TEST_DIR/modules/existing-dir" + + # Should detect conflict + run inst_check_module_conflict "existing-dir" "mod-test" + [ "$status" -eq 1 ] + [[ "$output" =~ "Directory 'existing-dir' already exists" ]] + [[ "$output" =~ "Use a different directory name: mod-test:my-custom-name" ]] + + # Should not detect conflict for non-existing directory + run inst_check_module_conflict "non-existing-dir" "mod-test" + [ "$status" -eq 0 ] +} + +@test "module update should work with custom directories" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # First add module with custom directory to list + inst_mod_list_upsert "azerothcore/mod-transmog:custom-dir" "master" "abc123" + + # Create fake module directory structure + mkdir -p "$TEST_DIR/modules/custom-dir/.git" + echo "ref: refs/heads/master" > "$TEST_DIR/modules/custom-dir/.git/HEAD" + + # Mock git commands in the fake module directory + cat > "$TEST_DIR/modules/custom-dir/.git/config" << 'EOF' +[core] + repositoryformatversion = 0 + filemode = true + bare = false +[remote "origin"] + url = https://github.com/azerothcore/mod-transmog + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master +EOF + + # Test update with custom directory should work + # Note: This would require more complex mocking for full integration test + # For now, just test the parsing recognizes the custom directory + run inst_parse_module_spec "azerothcore/mod-transmog:custom-dir" + [ "$status" -eq 0 ] + IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output" + [ "$dirname" = "custom-dir" ] +} + +@test "URL formats should be properly normalized" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test various URL formats produce same owner/name + run inst_extract_owner_name "https://github.com/azerothcore/mod-transmog" + local url_format="$output" + + run inst_extract_owner_name "https://github.com/azerothcore/mod-transmog.git" + local url_git_format="$output" + + run inst_extract_owner_name "git@github.com:azerothcore/mod-transmog.git" + local ssh_format="$output" + + run inst_extract_owner_name "azerothcore/mod-transmog" + local owner_name_format="$output" + + run inst_extract_owner_name "mod-transmog" + local simple_format="$output" + + # All should normalize to the same owner/name + [ "$url_format" = "azerothcore/mod-transmog" ] + [ "$url_git_format" = "azerothcore/mod-transmog" ] + [ "$ssh_format" = "azerothcore/mod-transmog" ] + [ "$owner_name_format" = "azerothcore/mod-transmog" ] + [ "$simple_format" = "azerothcore/mod-transmog" ] +} + +# Tests for module exclusion functionality + +@test "module exclusion should work with MODULES_EXCLUDE_LIST" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test exclusion with simple name + export MODULES_EXCLUDE_LIST="mod-test-module" + run inst_mod_is_excluded "mod-test-module" + [ "$status" -eq 0 ] + + # Test exclusion with owner/name format + export MODULES_EXCLUDE_LIST="azerothcore/mod-test" + run inst_mod_is_excluded "mod-test" + [ "$status" -eq 0 ] + + # Test exclusion with space-separated list + export MODULES_EXCLUDE_LIST="mod-one mod-two mod-three" + run inst_mod_is_excluded "mod-two" + [ "$status" -eq 0 ] + + # Test exclusion with newline-separated list + export MODULES_EXCLUDE_LIST=" +mod-alpha +mod-beta +mod-gamma +" + run inst_mod_is_excluded "mod-beta" + [ "$status" -eq 0 ] + + # Test exclusion with URL format + export MODULES_EXCLUDE_LIST="https://github.com/azerothcore/mod-transmog.git" + run inst_mod_is_excluded "mod-transmog" + [ "$status" -eq 0 ] + + # Test non-excluded module + export MODULES_EXCLUDE_LIST="mod-other" + run inst_mod_is_excluded "mod-transmog" + [ "$status" -eq 1 ] + + # Test empty exclusion list + unset MODULES_EXCLUDE_LIST + run inst_mod_is_excluded "mod-transmog" + [ "$status" -eq 1 ] +} + +@test "install --all should skip excluded modules" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Setup modules list with excluded module + mkdir -p "$TEST_DIR/conf" + cat > "$TEST_DIR/conf/modules.list" << 'EOF' +azerothcore/mod-transmog master abc123 +azerothcore/mod-excluded master def456 +EOF + + # Set exclusion list + export MODULES_EXCLUDE_LIST="mod-excluded" + + # Mock the install process to capture output + run bash -c "source '$TEST_DIR/apps/installer/includes/includes.sh' && inst_module_install --all 2>&1" + + # Should show that excluded module was skipped + [[ "$output" == *"azerothcore/mod-excluded"* && "$output" == *"Excluded by MODULES_EXCLUDE_LIST"* && "$output" == *"skipping"* ]] +} + +@test "exclusion should work with multiple formats in same list" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test multiple exclusion formats + export MODULES_EXCLUDE_LIST="mod-test https://github.com/azerothcore/mod-transmog.git custom/mod-other" + + run inst_mod_is_excluded "mod-test" + [ "$status" -eq 0 ] + + run inst_mod_is_excluded "azerothcore/mod-transmog" + [ "$status" -eq 0 ] + + run inst_mod_is_excluded "custom/mod-other" + [ "$status" -eq 0 ] + + run inst_mod_is_excluded "mod-allowed" + [ "$status" -eq 1 ] +} + +# Tests for color support functionality + +@test "color functions should work correctly" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test that print functions exist and work + run print_info "test message" + [ "$status" -eq 0 ] + + run print_warn "test warning" + [ "$status" -eq 0 ] + + run print_error "test error" + [ "$status" -eq 0 ] + + run print_success "test success" + [ "$status" -eq 0 ] + + run print_skip "test skip" + [ "$status" -eq 0 ] + + run print_header "test header" + [ "$status" -eq 0 ] +} + +@test "color support should respect NO_COLOR environment variable" { + cd "$TEST_DIR" + + # Test with NO_COLOR set + export NO_COLOR=1 + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Colors should be empty when NO_COLOR is set + [ -z "$C_RED" ] + [ -z "$C_GREEN" ] + [ -z "$C_RESET" ] +} + +# Tests for interactive menu system + +@test "module help should display comprehensive help" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + run inst_module_help + [ "$status" -eq 0 ] + + # Should contain key sections + [[ "$output" =~ "Module Manager Help" ]] + [[ "$output" =~ "Usage:" ]] + [[ "$output" =~ "Module Specification Syntax:" ]] + [[ "$output" =~ "Examples:" ]] +} + +@test "module list should show installed modules correctly" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Setup modules list + mkdir -p "$TEST_DIR/conf" + cat > "$TEST_DIR/conf/modules.list" << 'EOF' +azerothcore/mod-transmog master abc123 +custom/mod-test develop def456 +EOF + + run inst_module_list + [ "$status" -eq 0 ] + + # Should show both modules + [[ "$output" =~ "mod-transmog" ]] + [[ "$output" =~ "custom/mod-test" ]] + [[ "$output" =~ "master" ]] + [[ "$output" =~ "develop" ]] +} + +@test "module list should handle empty list gracefully" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Ensure empty modules list + mkdir -p "$TEST_DIR/conf" + touch "$TEST_DIR/conf/modules.list" + + run inst_module_list + [ "$status" -eq 0 ] + [[ "$output" =~ "No modules installed" ]] +} + +# Tests for advanced parsing edge cases + +@test "parsing should handle complex URL formats" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test GitLab URL with custom directory and branch + run inst_parse_module_spec "https://gitlab.com/myorg/mymodule.git:custom-dir@develop:abc123" + [ "$status" -eq 0 ] + IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output" + [ "$repo_ref" = "https://gitlab.com/myorg/mymodule.git" ] + [ "$owner" = "myorg" ] + [ "$name" = "mymodule" ] + [ "$branch" = "develop" ] + [ "$commit" = "abc123" ] + [ "$dirname" = "custom-dir" ] +} + +@test "parsing should handle URLs with ports correctly (fix for port/dirname confusion)" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test HTTPS URL with port - should NOT treat port as dirname + run inst_parse_module_spec "https://example.com:8080/user/repo.git" + [ "$status" -eq 0 ] + IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output" + [ "$repo_ref" = "https://example.com:8080/user/repo.git" ] + [ "$owner" = "user" ] + [ "$name" = "repo" ] + [ "$branch" = "-" ] + [ "$commit" = "-" ] + [ "$url" = "https://example.com:8080/user/repo.git" ] + [ "$dirname" = "repo" ] # Should default to repo name, NOT port number +} + +@test "parsing should handle URLs with ports and custom directory correctly" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test URL with port AND custom directory - should parse custom directory correctly + run inst_parse_module_spec "https://example.com:8080/user/repo.git:custom-dir" + [ "$status" -eq 0 ] + IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output" + [ "$repo_ref" = "https://example.com:8080/user/repo.git" ] + [ "$owner" = "user" ] + [ "$name" = "repo" ] + [ "$branch" = "-" ] + [ "$commit" = "-" ] + [ "$url" = "https://example.com:8080/user/repo.git" ] + [ "$dirname" = "custom-dir" ] # Should be custom-dir, not port number +} + +@test "parsing should handle SSH URLs with ports correctly" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test SSH URL with port + run inst_parse_module_spec "ssh://git@example.com:2222/user/repo" + [ "$status" -eq 0 ] + IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output" + [ "$repo_ref" = "ssh://git@example.com:2222/user/repo" ] + [ "$owner" = "user" ] + [ "$name" = "repo" ] + [ "$dirname" = "repo" ] # Should be repo name, not port number +} + +@test "parsing should handle SSH URLs with ports and custom directory" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test SSH URL with port and custom directory + run inst_parse_module_spec "ssh://git@example.com:2222/user/repo:my-custom-dir@develop" + [ "$status" -eq 0 ] + IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output" + [ "$repo_ref" = "ssh://git@example.com:2222/user/repo" ] + [ "$owner" = "user" ] + [ "$name" = "repo" ] + [ "$branch" = "develop" ] + [ "$dirname" = "my-custom-dir" ] +} + +@test "parsing should handle complex URLs with ports, custom dirs, and branches" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Test comprehensive URL with port, custom directory, branch, and commit + run inst_parse_module_spec "https://gitlab.example.com:9443/myorg/myrepo.git:custom-name@feature-branch:abc123def" + [ "$status" -eq 0 ] + IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output" + [ "$repo_ref" = "https://gitlab.example.com:9443/myorg/myrepo.git" ] + [ "$owner" = "myorg" ] + [ "$name" = "myrepo" ] + [ "$branch" = "feature-branch" ] + [ "$commit" = "abc123def" ] + [ "$url" = "https://gitlab.example.com:9443/myorg/myrepo.git" ] + [ "$dirname" = "custom-name" ] +} + +@test "URL port parsing regression test - ensure ports are not confused with directory names" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # These are the problematic cases that the fix addresses + local test_cases=( + "https://example.com:8080/repo.git" + "https://gitlab.internal:9443/group/project.git" + "ssh://git@server.com:2222/owner/repo" + "https://git.company.com:8443/team/module.git" + ) + + for spec in "${test_cases[@]}"; do + run inst_parse_module_spec "$spec" + [ "$status" -eq 0 ] + IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output" + + # Critical: dirname should NEVER be a port number + [[ ! "$dirname" =~ ^[0-9]+$ ]] || { + echo "FAIL: Port number '$dirname' incorrectly parsed as directory name for spec: $spec" + return 1 + } + + # dirname should be the repository name by default + local expected_name + if [[ "$spec" =~ /([^/]+)(\.git)?$ ]]; then + expected_name="${BASH_REMATCH[1]}" + expected_name="${expected_name%.git}" + fi + [ "$dirname" = "$expected_name" ] || { + echo "FAIL: Expected dirname '$expected_name' but got '$dirname' for spec: $spec" + return 1 + } + done +} + +@test "parsing should handle URL with custom directory but no branch" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + run inst_parse_module_spec "https://github.com/owner/repo.git:my-dir" + [ "$status" -eq 0 ] + IFS=' ' read -r repo_ref owner name branch commit url dirname <<< "$output" + [ "$repo_ref" = "https://github.com/owner/repo.git" ] + [ "$dirname" = "my-dir" ] + [ "$branch" = "-" ] +} + +@test "modules list should maintain alphabetical order" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + # Add modules in random order + inst_mod_list_upsert "zeta/mod-z" "master" "abc" + inst_mod_list_upsert "alpha/mod-a" "master" "def" + inst_mod_list_upsert "beta/mod-b" "master" "ghi" + + # Read the list and verify alphabetical order + local entries=() + while read -r repo_ref branch commit; do + [[ -z "$repo_ref" ]] && continue + entries+=("$repo_ref") + done < <(inst_mod_list_read) + + # Should be in alphabetical order by owner/name + [ "${entries[0]}" = "alpha/mod-a" ] + [ "${entries[1]}" = "beta/mod-b" ] + [ "${entries[2]}" = "zeta/mod-z" ] +} + +@test "module dispatcher should handle unknown commands gracefully" { + cd "$TEST_DIR" + source "$TEST_DIR/apps/installer/includes/includes.sh" + + run inst_module "unknown-command" + [ "$status" -eq 1 ] + [[ "$output" =~ "Unknown module command" ]] +} \ No newline at end of file diff --git a/apps/startup-scripts/README.md b/apps/startup-scripts/README.md index 176ec3ed6..0c7cb0940 100644 --- a/apps/startup-scripts/README.md +++ b/apps/startup-scripts/README.md @@ -305,6 +305,31 @@ Services support two restart policies: ./service-manager.sh delete auth ``` +#### Health and Console Commands + +Use these commands to programmatically check service health and interact with the console (used by CI workflows): + +```bash +# Check if service is currently running (exit 0 if running) +./service-manager.sh is-running world + +# Print current uptime in seconds (fails if not running) +./service-manager.sh uptime-seconds world + +# Wait until uptime >= 10s (optional timeout 240s) +./service-manager.sh wait-uptime world 10 240 + +# Send a console command (uses pm2 send or tmux/screen) +./service-manager.sh send world "server info" + +# Show provider, configs and run-engine settings +./service-manager.sh show-config world +``` + +Notes: +- For `send`, PM2 provider uses `pm2 send` with the process ID; systemd provider requires a session manager (tmux/screen). If no attachable session is configured, the command fails. +- `wait-uptime` fails with a non-zero exit code if the service does not reach the requested uptime within the timeout window. + #### Service Configuration ```bash # Update service settings @@ -624,4 +649,3 @@ sudo npm install -g pm2 ``` - diff --git a/apps/startup-scripts/src/service-manager.sh b/apps/startup-scripts/src/service-manager.sh index 34dc4c4d5..ccc4e8e35 100755 --- a/apps/startup-scripts/src/service-manager.sh +++ b/apps/startup-scripts/src/service-manager.sh @@ -279,6 +279,11 @@ function print_help() { echo " $base_name start|stop|restart|status " echo " $base_name logs [--follow]" echo " $base_name attach " + echo " $base_name is-running # exit 0 if running, 1 otherwise" + echo " $base_name uptime-seconds # print uptime in seconds (fails if not running)" + echo " $base_name wait-uptime [t] # wait until uptime >= seconds (timeout t, default 120)" + echo " $base_name send # send console command to service" + echo " $base_name show-config # print current service + run-engine config" echo " $base_name edit-config " echo "" echo "Providers:" @@ -735,7 +740,7 @@ EOF systemctl --user enable "$service_name.service" fi - echo -e "${GREEN}Systemd service '$service_name' created successfully${NC}" + echo -e "${GREEN}Systemd service '$service_name' created successfully with session manager '$session_manager'${NC}" # Add to registry add_service_to_registry "$service_name" "systemd" "service" "$command" "" "$systemd_type" "$restart_policy" "$session_manager" "$gdb_enabled" "" "$server_config" @@ -1473,6 +1478,253 @@ function attach_to_service() { fi } +######################################### +# Runtime helpers: status / send / show # +######################################### + +function service_is_running() { + local service_name="$1" + + local service_info=$(get_service_info "$service_name") + if [ -z "$service_info" ]; then + echo -e "${RED}Error: Service '$service_name' not found${NC}" >&2 + return 1 + fi + + local provider=$(echo "$service_info" | jq -r '.provider') + + if [ "$provider" = "pm2" ]; then + # pm2 jlist -> JSON array with .name and .pm2_env.status + if pm2 jlist | jq -e ".[] | select(.name==\"$service_name\" and .pm2_env.status==\"online\")" >/dev/null; then + return 0 + else + return 1 + fi + elif [ "$provider" = "systemd" ]; then + # Check user service first, then system + if systemctl --user is-active --quiet "$service_name.service" 2>/dev/null; then + return 0 + elif systemctl is-active --quiet "$service_name.service" 2>/dev/null; then + return 0 + else + return 1 + fi + else + return 1 + fi +} + +function service_send_command() { + local service_name="$1"; shift || true + local cmd_str="$*" + if [ -z "$service_name" ] || [ -z "$cmd_str" ]; then + echo -e "${RED}Error: send requires and ${NC}" >&2 + return 1 + fi + + local service_info=$(get_service_info "$service_name") + if [ -z "$service_info" ]; then + echo -e "${RED}Error: Service '$service_name' not found${NC}" >&2 + return 1 + fi + + local provider=$(echo "$service_info" | jq -r '.provider') + local config_file="$CONFIG_DIR/$service_name.conf" + + if [ ! -f "$config_file" ]; then + echo -e "${RED}Error: Service configuration file not found: $config_file${NC}" >&2 + return 1 + fi + + # Load run-engine config path + # shellcheck source=/dev/null + source "$config_file" + if [ -z "${RUN_ENGINE_CONFIG_FILE:-}" ] || [ ! -f "$RUN_ENGINE_CONFIG_FILE" ]; then + echo -e "${RED}Error: Run-engine configuration file not found for $service_name${NC}" >&2 + return 1 + fi + + # shellcheck source=/dev/null + if ! source "$RUN_ENGINE_CONFIG_FILE"; then + echo -e "${RED}Error: Failed to source run-engine configuration file: $RUN_ENGINE_CONFIG_FILE${NC}" >&2 + return 1 + fi + + local session_manager="${SESSION_MANAGER:-auto}" + local session_name="${SESSION_NAME:-$service_name}" + + if [ "$provider" = "pm2" ]; then + # Use pm2 send (requires pm2 >= 5) + local pm2_id_json + pm2_id_json=$(pm2 id "$service_name" 2>/dev/null || true) + local numeric_id + numeric_id=$(echo "$pm2_id_json" | jq -r '.[0] // empty') + if [ -z "$numeric_id" ]; then + echo -e "${RED}Error: PM2 process '$service_name' not found${NC}" >&2 + return 1 + fi + echo -e "${YELLOW}Sending to PM2 process $service_name (ID: $numeric_id): $cmd_str${NC}" + pm2 send "$numeric_id" "$cmd_str" ENTER + return $? + fi + + # systemd provider: need a session manager to interact with the console + case "$session_manager" in + tmux|auto) + if command -v tmux >/dev/null 2>&1 && tmux has-session -t "$session_name" 2>/dev/null; then + echo -e "${YELLOW}Sending to tmux session $session_name: $cmd_str${NC}" + tmux send-keys -t "$session_name" "$cmd_str" C-m + return $? + elif [ "$session_manager" = "tmux" ]; then + echo -e "${RED}Error: tmux session '$session_name' not available${NC}" >&2 + return 1 + fi + ;;& + screen|auto) + if command -v screen >/dev/null 2>&1; then + echo -e "${YELLOW}Sending to screen session $session_name: $cmd_str${NC}" + screen -S "$session_name" -X stuff "$cmd_str\n" + return $? + elif [ "$session_manager" = "screen" ]; then + echo -e "${RED}Error: screen not installed${NC}" >&2 + return 1 + fi + ;; + none|*) + echo -e "${RED}Error: No session manager configured (SESSION_MANAGER=$session_manager). Cannot send command.${NC}" >&2 + return 1 + ;; + esac + + echo -e "${RED}Error: Unable to find usable session (tmux/screen) to send command.${NC}" >&2 + return 1 +} + +function show_config() { + local service_name="$1" + if [ -z "$service_name" ]; then + echo -e "${RED}Error: Service name required for show-config${NC}" + return 1 + fi + + local service_info=$(get_service_info "$service_name") + if [ -z "$service_info" ]; then + echo -e "${RED}Error: Service '$service_name' not found${NC}" + return 1 + fi + + local provider=$(echo "$service_info" | jq -r '.provider') + local cfg_file="$CONFIG_DIR/$service_name.conf" + echo -e "${BLUE}Service: $service_name${NC}" + echo "Provider: $provider" + echo "Config file: $cfg_file" + if [ -f "$cfg_file" ]; then + # shellcheck source=/dev/null + source "$cfg_file" + echo "RUN_ENGINE_CONFIG_FILE: ${RUN_ENGINE_CONFIG_FILE:-}" + if [ -n "${RUN_ENGINE_CONFIG_FILE:-}" ] && [ -f "$RUN_ENGINE_CONFIG_FILE" ]; then + # shellcheck source=/dev/null + source "$RUN_ENGINE_CONFIG_FILE" + echo "Session manager: ${SESSION_MANAGER:-}" + echo "Session name: ${SESSION_NAME:-}" + echo "BINPATH: ${BINPATH:-}" + echo "SERVERBIN: ${SERVERBIN:-}" + echo "CONFIG: ${CONFIG:-}" + echo "RESTART_POLICY: ${RESTART_POLICY:-}" + fi + else + echo "Config file not found" + fi +} + +# Return uptime in seconds for a service (echo integer), non-zero exit if not running +function service_uptime_seconds() { + local service_name="$1" + local service_info=$(get_service_info "$service_name") + if [ -z "$service_info" ]; then + echo -e "${RED}Error: Service '$service_name' not found${NC}" >&2 + return 1 + fi + + local provider=$(echo "$service_info" | jq -r '.provider') + + if [ "$provider" = "pm2" ]; then + check_pm2 || return 1 + local info_json + info_json=$(pm2 jlist 2>/dev/null) + local pm_uptime_ms + pm_uptime_ms=$(echo "$info_json" | jq -r ".[] | select(.name==\"$service_name\").pm2_env.pm_uptime // empty") + local status + status=$(echo "$info_json" | jq -r ".[] | select(.name==\"$service_name\").pm2_env.status // empty") + if [ -z "$pm_uptime_ms" ] || [ "$status" != "online" ]; then + return 1 + fi + # Current time in ms (fallback to seconds*1000 if %N unsupported) + local now_ms + if date +%s%N >/dev/null 2>&1; then + now_ms=$(( $(date +%s%N) / 1000000 )) + else + now_ms=$(( $(date +%s) * 1000 )) + fi + local diff_ms=$(( now_ms - pm_uptime_ms )) + [ "$diff_ms" -lt 0 ] && diff_ms=0 + echo $(( diff_ms / 1000 )) + return 0 + elif [ "$provider" = "systemd" ]; then + check_systemd || return 1 + local systemd_type="--user" + [ -f "/etc/systemd/system/$service_name.service" ] && systemd_type="--system" + + # Get ActiveEnterTimestampMonotonic in usec and ActiveState + local prop + if [ "$systemd_type" = "--system" ]; then + prop=$(systemctl show "$service_name.service" --property=ActiveEnterTimestampMonotonic,ActiveState 2>/dev/null) + else + prop=$(systemctl --user show "$service_name.service" --property=ActiveEnterTimestampMonotonic,ActiveState 2>/dev/null) + fi + local state + state=$(echo "$prop" | awk -F= '/^ActiveState=/{print $2}') + [ "$state" != "active" ] && return 1 + local enter_us + enter_us=$(echo "$prop" | awk -F= '/^ActiveEnterTimestampMonotonic=/{print $2}') + # Current monotonic time in seconds since boot + local now_s + now_s=$(cut -d' ' -f1 /proc/uptime) + # Compute uptime = now_monotonic - enter_monotonic + # enter_us may be empty on some systems; fallback to 0 + enter_us=${enter_us:-0} + # Convert now_s to microseconds using awk for precision, then compute diff + local diff_s + diff_s=$(awk -v now="$now_s" -v enter="$enter_us" 'BEGIN{printf "%d", (now*1000000 - enter)/1000000}') + [ "$diff_s" -lt 0 ] && diff_s=0 + echo "$diff_s" + return 0 + fi + + return 1 +} + +# Wait until service has at least uptime. Optional timeout seconds (default 120) +function wait_service_uptime() { + local service_name="$1" + local min_seconds="$2" + local timeout="${3:-120}" + local waited=0 + + while [ "$waited" -le "$timeout" ]; do + if secs=$(service_uptime_seconds "$service_name" 2>/dev/null); then + if [ "$secs" -ge "$min_seconds" ]; then + echo -e "${GREEN}Service '$service_name' has reached ${secs}s uptime (required: ${min_seconds}s)${NC}" + return 0 + fi + fi + sleep 1 + waited=$((waited + 1)) + done + echo -e "${RED}Timeout: $service_name did not reach ${min_seconds}s uptime within ${timeout}s${NC}" >&2 + return 1 +} + function attach_pm2_process() { local service_name="$1" @@ -1629,7 +1881,7 @@ case "${1:-help}" in delete_service "$2" ;; list) - list_services "$2" + list_services "${2:-}" ;; restore) restore_missing_services @@ -1670,6 +1922,52 @@ case "${1:-help}" in fi attach_to_service "$2" ;; + uptime-seconds) + if [ $# -lt 2 ]; then + echo -e "${RED}Error: Service name required for uptime-seconds command${NC}" + print_help + exit 1 + fi + service_uptime_seconds "$2" + ;; + wait-uptime) + if [ $# -lt 3 ]; then + echo -e "${RED}Error: Usage: $0 wait-uptime [timeout]${NC}" + print_help + exit 1 + fi + wait_service_uptime "$2" "$3" "${4:-120}" + ;; + is-running) + if [ $# -lt 2 ]; then + echo -e "${RED}Error: Service name required for is-running command${NC}" + print_help + exit 1 + fi + if service_is_running "$2"; then + echo -e "${GREEN}Service '$2' is running${NC}" + exit 0 + else + echo -e "${YELLOW}Service '$2' is not running${NC}" + exit 1 + fi + ;; + send) + if [ $# -lt 3 ]; then + echo -e "${RED}Error: Not enough arguments for send command${NC}" + print_help + exit 1 + fi + service_send_command "$2" "${@:3}" + ;; + show-config) + if [ $# -lt 2 ]; then + echo -e "${RED}Error: Service name required for show-config command${NC}" + print_help + exit 1 + fi + show_config "$2" + ;; help|--help|-h) print_help ;; diff --git a/apps/startup-scripts/test/test_startup_scripts.bats b/apps/startup-scripts/test/test_startup_scripts.bats index 2bbca2ffd..119a8c80c 100644 --- a/apps/startup-scripts/test/test_startup_scripts.bats +++ b/apps/startup-scripts/test/test_startup_scripts.bats @@ -143,6 +143,130 @@ teardown() { [[ "$output" =~ "on-failure|always" ]] } +@test "service-manager: help lists health and console commands" { + run "$SCRIPT_DIR/service-manager.sh" help + [ "$status" -eq 0 ] + [[ "$output" =~ "is-running " ]] + [[ "$output" =~ "uptime-seconds " ]] + [[ "$output" =~ "wait-uptime " ]] + [[ "$output" =~ "send " ]] + [[ "$output" =~ "show-config " ]] +} + +@test "service-manager: pm2 uptime and wait-uptime work with mocked pm2" { + command -v jq >/dev/null 2>&1 || skip "jq not installed" + export AC_SERVICE_CONFIG_DIR="$TEST_DIR/services" + mkdir -p "$AC_SERVICE_CONFIG_DIR" + # Create registry with pm2 provider service + cat > "$AC_SERVICE_CONFIG_DIR/service_registry.json" << 'EOF' +[ + {"name":"test-world","provider":"pm2","type":"service","bin_path":"/bin/worldserver","args":"","systemd_type":"--user","restart_policy":"always"} +] +EOF + # Create minimal service config and run-engine config files required by 'send' + echo "RUN_ENGINE_CONFIG_FILE=\"$AC_SERVICE_CONFIG_DIR/test-world-run-engine.conf\"" > "$AC_SERVICE_CONFIG_DIR/test-world.conf" + cat > "$AC_SERVICE_CONFIG_DIR/test-world-run-engine.conf" << 'EOF' +export SESSION_MANAGER="none" +export SESSION_NAME="test-world" +EOF + # Mock pm2 + cat > "$TEST_DIR/bin/pm2" << 'EOF' +#!/usr/bin/env bash +case "$1" in + jlist) + # Produce a JSON with uptime ~20 seconds + if date +%s%N >/dev/null 2>&1; then + nowms=$(( $(date +%s%N) / 1000000 )) + else + nowms=$(( $(date +%s) * 1000 )) + fi + up=$(( nowms - 20000 )) + echo "[{\"name\":\"test-world\",\"pm2_env\":{\"status\":\"online\",\"pm_uptime\":$up}}]" + ;; + id) + echo "[1]" + ;; + attach|send|list|describe|logs) + exit 0 + ;; + *) + exit 0 + ;; +esac +EOF + chmod +x "$TEST_DIR/bin/pm2" + + run "$SCRIPT_DIR/service-manager.sh" uptime-seconds test-world + debug_on_failure + [ "$status" -eq 0 ] + # Output should be a number >= 10 + [[ "$output" =~ ^[0-9]+$ ]] + [ "$output" -ge 10 ] + + run "$SCRIPT_DIR/service-manager.sh" wait-uptime test-world 10 5 + debug_on_failure + [ "$status" -eq 0 ] +} + +@test "service-manager: send works under pm2 with mocked pm2" { + command -v jq >/dev/null 2>&1 || skip "jq not installed" + export AC_SERVICE_CONFIG_DIR="$TEST_DIR/services" + mkdir -p "$AC_SERVICE_CONFIG_DIR" + # Create registry and config as in previous test + cat > "$AC_SERVICE_CONFIG_DIR/service_registry.json" << 'EOF' +[ + {"name":"test-world","provider":"pm2","type":"service","bin_path":"/bin/worldserver","args":"","systemd_type":"--user","restart_policy":"always"} +] +EOF + echo "RUN_ENGINE_CONFIG_FILE=\"$AC_SERVICE_CONFIG_DIR/test-world-run-engine.conf\"" > "$AC_SERVICE_CONFIG_DIR/test-world.conf" + cat > "$AC_SERVICE_CONFIG_DIR/test-world-run-engine.conf" << 'EOF' +export SESSION_MANAGER="none" +export SESSION_NAME="test-world" +EOF + # pm2 mock + cat > "$TEST_DIR/bin/pm2" << 'EOF' +#!/usr/bin/env bash +case "$1" in + jlist) + if date +%s%N >/dev/null 2>&1; then + nowms=$(( $(date +%s%N) / 1000000 )) + else + nowms=$(( $(date +%s) * 1000 )) + fi + up=$(( nowms - 15000 )) + echo "[{\"name\":\"test-world\",\"pm2_env\":{\"status\":\"online\",\"pm_uptime\":$up}}]" + ;; + id) + echo "[1]" + ;; + send) + # simulate success + exit 0 + ;; + attach|list|describe|logs) + exit 0 + ;; + *) + exit 0 + ;; +esac +EOF + chmod +x "$TEST_DIR/bin/pm2" + + run "$SCRIPT_DIR/service-manager.sh" send test-world "server info" + debug_on_failure + [ "$status" -eq 0 ] +} + +@test "service-manager: wait-uptime times out for unknown service" { + command -v jq >/dev/null 2>&1 || skip "jq not installed" + export AC_SERVICE_CONFIG_DIR="$TEST_DIR/services" + mkdir -p "$AC_SERVICE_CONFIG_DIR" + echo "[]" > "$AC_SERVICE_CONFIG_DIR/service_registry.json" + run "$SCRIPT_DIR/service-manager.sh" wait-uptime unknown 2 1 + [ "$status" -ne 0 ] +} + # ===== EXAMPLE SCRIPTS TESTS ===== @test "examples: restarter-world should show configuration error" { diff --git a/conf/dist/config.sh b/conf/dist/config.sh index df7ddd3fc..1f956ca70 100644 --- a/conf/dist/config.sh +++ b/conf/dist/config.sh @@ -149,4 +149,27 @@ export CPUPROFILESIGNAL=${CPUPROFILESIGNAL:-12} # Other values for HEAPCHECK: minimal, normal (equivalent to "1"), strict, draconian #export HEAPCHECK=${HEAPCHECK:-normal} +############################################## +# +# MODULES LIST FILE (for installer `module` commands) +# +# Path to the file where the installer records installed modules +# with their branch and commit. You can override this path by +# setting the MODULES_LIST_FILE inside your config.sh or as an environment variable. +# By default it points inside the repository conf folder. +# Format of each line: +# +# Lines starting with '#' and empty lines are ignored. +export MODULES_LIST_FILE=${MODULES_LIST_FILE:-"$AC_PATH_ROOT/conf/modules.list"} + +# Space/newline separated list of modules to exclude when using +# 'module install --all' and 'module update --all'. Items can be specified +# as simple names (e.g., mod-transmog), owner/name, or full URLs. +# Example: +# export MODULES_EXCLUDE_LIST="azerothcore/mod-transmog azerothcore/mod-autobalance" +export MODULES_EXCLUDE_LIST="" + +NO_COLOR=${NO_COLOR:-} +FORCE_COLOR=${FORCE_COLOR:-} + diff --git a/data/sql/updates/db_characters/2025_09_03_00.sql b/data/sql/updates/db_characters/2025_09_03_00.sql new file mode 100644 index 000000000..c9ae6307d --- /dev/null +++ b/data/sql/updates/db_characters/2025_09_03_00.sql @@ -0,0 +1,15 @@ +-- DB update 2025_07_29_00 -> 2025_09_03_00 +-- Add petition_id column to petition table +ALTER TABLE `petition` ADD COLUMN `petition_id` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `petitionguid`; +-- Populate petition_id based on petitionguid +UPDATE `petition` SET `petition_id` = CASE WHEN `petitionguid` <= 2147483647 THEN `petitionguid` ELSE `petitionguid` - 2147483648 END WHERE `petition_id` = 0; +-- Add index on petition_id +ALTER TABLE `petition` ADD INDEX `idx_petition_id` (`petition_id`); +-- Add petition_id column to petition_sign table +ALTER TABLE `petition_sign` ADD COLUMN `petition_id` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `petitionguid`; +-- Populate petition_id in petition_sign from petition table +UPDATE `petition_sign` AS `ps` JOIN `petition` AS `p` ON `p`.`petitionguid` = `ps`.`petitionguid` SET `ps`.`petition_id` = `p`.`petition_id` WHERE `ps`.`petition_id` = 0; +-- Add index on petition_id and playerguid in petition_sign +ALTER TABLE `petition_sign` ADD INDEX `idx_petition_id_player` (`petition_id`, `playerguid`); +-- Update enchantments in item_instance with petition_id prefix +UPDATE `item_instance` AS `ii` JOIN `petition` AS `p` ON `p`.`petitionguid` = `ii`.`guid` SET `ii`.`enchantments` = CONCAT(`p`.`petition_id`, SUBSTRING(`ii`.`enchantments`, LOCATE(' ', `ii`.`enchantments`))) WHERE `ii`.`enchantments` IS NOT NULL AND `ii`.`enchantments` <> ''; diff --git a/data/sql/updates/db_world/2025_09_02_00.sql b/data/sql/updates/db_world/2025_09_02_00.sql new file mode 100644 index 000000000..08ea0a4fe --- /dev/null +++ b/data/sql/updates/db_world/2025_09_02_00.sql @@ -0,0 +1,14 @@ +-- DB update 2025_08_30_03 -> 2025_09_02_00 + +-- Morbid Carcass, Vault Geist, Rabid Cannibal, Death Knight Master +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE (`entry` IN (29719, 29720, 29722, 29738)); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0) AND (`entryorguid` IN (29719, 29720, 29722, 29738)); +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 +(29719, 0, 0, 0, 0, 0, 100, 0, 8000, 12000, 8000, 12000, 0, 0, 11, 40504, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Morbid Carcass - In Combat - Cast \'Cleave\''), +(29719, 0, 1, 0, 9, 0, 100, 0, 8000, 12000, 8000, 12000, 8, 40, 11, 50335, 0, 0, 0, 0, 0, 5, 40, 0, 0, 0, 0, 0, 0, 0, 'Morbid Carcass - Within 8-40 Range - Cast \'Scourge Hook\''), +(29720, 0, 0, 0, 4, 0, 100, 0, 0, 0, 0, 0, 0, 0, 97, 20, 10, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Vault Geist - On Aggro - Jump To Pos'), +(29720, 0, 1, 0, 0, 0, 100, 0, 4000, 6000, 18000, 24000, 0, 0, 11, 36590, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Vault Geist - In Combat - Cast \'Rip\''), +(29738, 0, 0, 0, 0, 0, 100, 0, 0, 0, 20000, 24000, 0, 0, 11, 50688, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Master - In Combat - Cast \'Plague Strike\''), +(29738, 0, 1, 0, 60, 0, 100, 0, 0, 0, 30000, 30000, 0, 0, 11, 50689, 32, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Death Knight Master - On Update - Cast \'Blood Presence\''), +(29722, 0, 0, 0, 0, 0, 100, 0, 2000, 4000, 18000, 22000, 0, 0, 11, 30639, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Rabid Cannibal - In Combat - Cast \'Carnivorous Bite\''); diff --git a/data/sql/updates/db_world/2025_09_02_01.sql b/data/sql/updates/db_world/2025_09_02_01.sql new file mode 100644 index 000000000..58a544d75 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_02_01.sql @@ -0,0 +1,4 @@ +-- DB update 2025_09_02_00 -> 2025_09_02_01 +DELETE FROM `creature` WHERE `guid` = 1741 AND `id1` = 14724; +INSERT INTO `creature` (`guid`, `id1`, `id2`, `id3`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `equipment_id`, `position_x`, `position_y`, `position_z`, `orientation`, `spawntimesecs`, `wander_distance`, `currentwaypoint`, `curhealth`, `curmana`, `MovementType`, `npcflag`, `unit_flags`, `dynamicflags`, `ScriptName`, `VerifiedBuild`, `CreateObject`, `Comment`) VALUES +(1741, 14724, 0, 0, 0, 0, 0, 1, 1, 0, -4823.649, -1299.3605, 501.95117, 0.959931075572967529, 300, 0, 0, 1220, 0, 0, 0, 0, 0, '', 45613, 1, NULL); diff --git a/data/sql/updates/db_world/2025_09_02_02.sql b/data/sql/updates/db_world/2025_09_02_02.sql new file mode 100644 index 000000000..922f9cb8d --- /dev/null +++ b/data/sql/updates/db_world/2025_09_02_02.sql @@ -0,0 +1,5 @@ +-- DB update 2025_09_02_01 -> 2025_09_02_02 +-- +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 28851) AND (`source_type` = 0) AND (`id` IN (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 +(28851, 0, 2, 0, 28, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 3000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Enraged Mammoth - On Passenger Removed - Despawn In 3000 ms'); diff --git a/data/sql/updates/db_world/2025_09_03_00.sql b/data/sql/updates/db_world/2025_09_03_00.sql new file mode 100644 index 000000000..0630c0561 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_03_00.sql @@ -0,0 +1,151 @@ +-- DB update 2025_09_02_02 -> 2025_09_03_00 +-- Update gameobject 'various doodad' with sniffed values +-- updated spawns +DELETE FROM `gameobject` WHERE (`id` IN (193692, 193766, 193765, 193764, 193763, 193762, 193761, 193760, 193759, 193758, 193757, 193756, 193755, 193754, 193753, 193752, 193751, 193750, 193749, 193748, 193747, 193746, 193745, 193744, 193743, 193742, 193741, 193740, 193739, 193738, 193737, 193736, 193735, 193734, 193733, 193732, 193731, 193730, 193729, 193728, 193727, 193726, 193725, 193724, 193723, 193722, 193721, 193720, 193719, 193718, 193717, 193716, 193715, 193714, 193713, 193712, 193711, 193710, 193709, 193708, 193707, 193706, 193705, 193704, 193703, 193702, 193701, 193700, 193699, 193698, 193697, 193696, 193693, 193691, 193678, 193677, 193676, 193674, 193669, 193668, 193666, 193665, 193664, 193663, 193659, 193658, 193657, 193662, 193660, 193661, 193654, 193652, 193651, 193649, 193650, 193671, 193670, 193645, 193644, 193643, 193642, 193637, 193632, 193631, 193630, 193653, 193636, 193635, 193634, 193633, 193681, 193680)) AND (`guid` IN (255513, 268696, 268697, 268698, 268699, 268700, 268701, 268702, 268703, 268704, 268705, 268706, 268707, 268708, 268709, 268710, 268711, 268712, 268713, 268714, 268715, 268716, 268717, 268718, 268719, 268720, 268721, 268722, 268723, 268724, 268725, 268726, 268727, 268728, 268729, 268730, 268731, 268732, 268733, 268734, 268735, 268736, 268737, 268738, 268739, 268740, 268741, 268742, 268743, 268744, 268745, 268746, 268747, 268748, 268749, 268750, 268751, 268752, 268753, 268754, 268755, 268756, 268757, 268758, 268759, 268760, 268761, 268762, 268763, 268764, 268765, 268766, 268772, 268773, 268774, 268775, 268779, 268780, 268781, 268782, 268783, 268784, 268785, 268786, 268790, 268791, 268792, 268793, 268794, 268795, 268799, 268800, 268807, 268808, 268809, 268815, 268816, 268818, 268819, 268820, 268821, 268822, 268825, 268826, 268827, 268829, 268832, 268833, 268834, 268835, 268839, 268840)); +INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `position_x`, `position_y`, `position_z`, `orientation`, `rotation0`, `rotation1`, `rotation2`, `rotation3`, `spawntimesecs`, `animprogress`, `state`, `ScriptName`, `VerifiedBuild`, `Comment`) VALUES +(255513, 193692, 571, 0, 0, 1, 1, 7882.8623046875, 2043.8250732421875, 600.73297119140625, 4.665524959564208984, 0.187406539916992187, -0.31766891479492187, -0.64971733093261718, 0.66470491886138916, 120, 255, 1, "", 46368, NULL), +(268696, 193766, 571, 0, 0, 1, 1, 7637.41064453125, 2072.35791015625, 600.27178955078125, 1.248236894607543945, 0.51220560073852539, 0.357756614685058593, 0.46212005615234375, 0.629365265369415283, 120, 255, 1, "", 50664, NULL), +(268697, 193765, 571, 0, 0, 1, 1, 7635.556640625, 2046.7196044921875, 601.6680908203125, 1.269469738006591796, -0.03150653839111328, 0.077538490295410156, 0.589531898498535156, 0.803397297859191894, 120, 255, 1, "", 50664, NULL), +(268698, 193764, 571, 0, 0, 1, 1, 7625.869140625, 2060.052978515625, 604.26983642578125, 0.078540004789829254, 0, 0, 0.039259910583496093, 0.999229073524475097, 120, 255, 1, "", 50664, NULL), +(268699, 193763, 571, 0, 0, 1, 1, 7625.7724609375, 2060.06494140625, 600.8868408203125, 0.063891783356666564, 0, 0, 0.031940460205078125, 0.999489784240722656, 120, 255, 1, "", 50664, NULL), +(268700, 193762, 571, 0, 0, 1, 1, 7625.6640625, 2060.03564453125, 604.1954345703125, 3.22885894775390625, 0, 0, -0.99904823303222656, 0.043619260191917419, 120, 255, 1, "", 50664, NULL), +(268701, 193761, 571, 0, 0, 1, 1, 7629.734375, 2062.71484375, 600.2579345703125, 2.95832991600036621, 0, 0, 0.995804786682128906, 0.091503240168094635, 120, 255, 1, "", 50664, NULL), +(268702, 193760, 571, 0, 0, 1, 1, 7630.5419921875, 2062.549560546875, 600.24835205078125, 4.249876976013183593, 0, 0, -0.85035228729248046, 0.526213824748992919, 120, 255, 1, "", 50664, NULL), +(268703, 193759, 571, 0, 0, 1, 1, 7629.98193359375, 2061.771240234375, 600.24383544921875, 3.351046562194824218, 0, 0, -0.99452114105224609, 0.104535527527332305, 120, 255, 1, "", 50664, NULL), +(268704, 193758, 571, 0, 0, 1, 1, 7628.8798828125, 2060.107666015625, 600.49481201171875, 4.721115589141845703, 0, 0, -0.70401477813720703, 0.71018528938293457, 120, 255, 1, "", 50664, NULL), +(268705, 193757, 571, 0, 0, 1, 1, 7628.60400390625, 2060.201171875, 599.6319580078125, 4.616395950317382812, 0, 0, -0.74021816253662109, 0.672366797924041748, 120, 255, 1, "", 50664, NULL), +(268706, 193756, 571, 0, 0, 1, 1, 7628.60400390625, 2060.201171875, 598.53387451171875, 6.143558979034423828, 0, 0, -0.06975650787353515, 0.997564077377319335, 120, 255, 1, "", 50664, NULL), +(268707, 193755, 571, 0, 0, 1, 1, 7617.7353515625, 2050.1357421875, 600.6690673828125, 5.480334281921386718, 0, 0, -0.39073085784912109, 0.920504987239837646, 120, 255, 1, "", 50664, NULL), +(268708, 193754, 571, 0, 0, 1, 1, 7630.58837890625, 2060.197998046875, 600.16461181640625, 0.017452461645007133, 0, 0, 0.008726119995117187, 0.999961912631988525, 120, 255, 1, "", 50664, NULL), +(268709, 193753, 571, 0, 0, 1, 1, 7615.00048828125, 2053.274658203125, 601.31890869140625, 4.834563255310058593, -0.07124805450439453, -0.11837959289550781, -0.65368366241455078, 0.744048118591308593, 120, 255, 1, "", 50664, NULL), +(268710, 193752, 571, 0, 0, 1, 1, 7614.63818359375, 2053.234130859375, 601.3212890625, 4.1538848876953125, 0, 0, -0.8746194839477539, 0.484810054302215576, 120, 255, 1, "", 50664, NULL), +(268711, 193751, 571, 0, 0, 1, 1, 7614.73583984375, 2052.607177734375, 601.3494873046875, 5.52397012710571289, 0.025708198547363281, 0.1858978271484375, -0.35620498657226562, 0.915368258953094482, 120, 255, 1, "", 50664, NULL), +(268712, 193750, 571, 0, 0, 1, 1, 7614.7763671875, 2052.47705078125, 600.12860107421875, 0.759216904640197753, 0, 0, 0.370556831359863281, 0.928809821605682373, 120, 255, 1, "", 50664, NULL), +(268713, 193749, 571, 0, 0, 1, 1, 7621.33642578125, 2045.822998046875, 600.0079345703125, 5.864306926727294921, 0, 0, -0.20791149139404296, 0.978147625923156738, 120, 255, 1, "", 50664, NULL), +(268714, 193748, 571, 0, 0, 1, 1, 7621.1689453125, 2048.1103515625, 600.03460693359375, 3.194002151489257812, 0, 0, -0.99965667724609375, 0.026201646775007247, 120, 255, 1, "", 50664, NULL), +(268715, 193747, 571, 0, 0, 1, 1, 7619.73583984375, 2046.9991455078125, 600.139892578125, 4.127707481384277343, 0, 0, -0.880889892578125, 0.473321229219436645, 120, 255, 1, "", 50664, NULL), +(268716, 193746, 571, 0, 0, 1, 1, 7611.34130859375, 2059.42138671875, 600.2303466796875, 0.680676698684692382, 0, 0, 0.333806037902832031, 0.942641794681549072, 120, 255, 1, "", 50664, NULL), +(268717, 193745, 571, 0, 0, 1, 1, 7611.443359375, 2061.458740234375, 600.2303466796875, 2.076939344406127929, 0, 0, 0.861628532409667968, 0.50753939151763916, 120, 255, 1, "", 50664, NULL), +(268718, 193744, 571, 0, 0, 1, 1, 7611.46630859375, 2060.24267578125, 601.098876953125, 1.474833250045776367, 0.740206718444824218, 0.672366142272949218, 0.004041671752929687, 0.001196039956994354, 120, 255, 1, "", 50664, NULL), +(268719, 193743, 571, 0, 0, 1, 1, 7629.513671875, 2043.40283203125, 600.21649169921875, 2.303830623626708984, 0, 0, 0.913544654846191406, 0.406738430261611938, 120, 255, 1, "", 50664, NULL), +(268720, 193742, 571, 0, 0, 1, 1, 7627.474609375, 2043.3980712890625, 600.215576171875, 3.70009779930114746, 0, 0, -0.96126174926757812, 0.275637149810791015, 120, 255, 1, "", 50664, NULL), +(268721, 193741, 571, 0, 0, 1, 1, 7628.4716796875, 2043.3489990234375, 601.08172607421875, 3.097992658615112304, -0.02180719375610351, -0.99975299835205078, -0.00331974029541015, 0.002718634903430938, 120, 255, 1, "", 50664, NULL), +(268722, 193740, 571, 0, 0, 1, 1, 7620.10205078125, 2074.37548828125, 601.4208984375, 6.012660503387451171, 0, 0, -0.13485050201416015, 0.990865945816040039, 120, 255, 1, "", 50664, NULL), +(268723, 193739, 571, 0, 0, 1, 1, 7621.29052734375, 2074.291748046875, 601.4190673828125, 0.157077088952064514, 0, 0, 0.078457832336425781, 0.996917426586151123, 120, 255, 1, "", 50664, NULL), +(268724, 193738, 571, 0, 0, 1, 1, 7621.4072265625, 2072.64208984375, 601.4334716796875, 1.954769015312194824, 0, 0, 0.829037666320800781, 0.559192776679992675, 120, 255, 1, "", 50664, NULL), +(268725, 193737, 571, 0, 0, 1, 1, 7620.9501953125, 2073.94580078125, 600.21722412109375, 5.253442287445068359, 0, 0, -0.49242305755615234, 0.870355963706970214, 120, 255, 1, "", 50664, NULL), +(268726, 193736, 571, 0, 0, 1, 1, 7618.22607421875, 2070.371337890625, 600.55718994140625, 3.892086982727050781, 0, 0, -0.93041706085205078, 0.366502493619918823, 120, 255, 1, "", 50664, NULL), +(268727, 193735, 571, 0, 0, 1, 1, 7613.96484375, 2068.400634765625, 600.26336669921875, 5.410520076751708984, 0.18164825439453125, -0.05159950256347656, -0.42017078399658203, 0.887579798698425292, 120, 255, 1, "", 50664, NULL), +(268728, 193734, 571, 0, 0, 1, 1, 7614.1796875, 2069.150146484375, 600.2694091796875, 2.277705669403076171, 0.4829254150390625, 0.523255348205566406, -0.48152923583984375, 0.510995566844940185, 120, 255, 1, "", 50664, NULL), +(268729, 193733, 571, 0, 0, 1, 1, 7614.11865234375, 2068.62841796875, 600.2442626953125, 2.862335443496704101, 0, 0, 0.990267753601074218, 0.139175355434417724, 120, 255, 1, "", 50664, NULL), +(268730, 193732, 571, 0, 0, 1, 1, 7613.57958984375, 2067.849365234375, 600.361083984375, 0.758936166763305664, 0.710488319396972656, 0.047131538391113281, -0.70103359222412109, 0.039202630519866943, 120, 255, 1, "", 50664, NULL), +(268731, 193731, 571, 0, 0, 1, 1, 7613.70166015625, 2066.873779296875, 600.338623046875, 5.733424663543701171, -0.62461566925048828, 0.353822708129882812, 0.599942207336425781, 0.353177070617675781, 120, 255, 1, "", 50664, NULL), +(268732, 193730, 571, 0, 0, 1, 1, 7613.63330078125, 2067.5244140625, 600.229736328125, 2.809975385665893554, 0, 0, 0.986285209655761718, 0.165049895644187927, 120, 255, 1, "", 50664, NULL), +(268733, 193729, 571, 0, 0, 1, 1, 7627.837890625, 2076.871337890625, 600.24664306640625, 5.471607208251953125, 0, 0, -0.39474391937255859, 0.918791174888610839, 120, 255, 1, "", 50664, NULL), +(268734, 193728, 571, 0, 0, 1, 1, 7628.87841796875, 2076.95263671875, 601.11578369140625, 6.265766620635986328, 0.999953269958496093, -0.00871753692626953, 0.00246429443359375, 0.00337409065105021, 120, 255, 1, "", 50664, NULL), +(268735, 193727, 571, 0, 0, 1, 1, 7629.87646484375, 2076.929931640625, 600.24664306640625, 0.584683895111083984, 0, 0, 0.288195610046386718, 0.957571566104888916, 120, 255, 1, "", 50664, NULL), +(268736, 193726, 571, 0, 0, 1, 1, 7644.1845703125, 2052.05908203125, 600.23858642578125, 2.085667610168457031, 0, 0, 0.863835334777832031, 0.503774285316467285, 120, 255, 1, "", 50664, NULL), +(268737, 193725, 571, 0, 0, 1, 1, 7644.37548828125, 2052.462158203125, 600.2384033203125, 4.598945140838623046, 0, 0, -0.74605655670166015, 0.665882587432861328, 120, 255, 1, "", 50664, NULL), +(268738, 193724, 571, 0, 0, 1, 1, 7644.04248046875, 2052.38671875, 600.23297119140625, 0.706856131553649902, 0.038586616516113281, -0.14088153839111328, 0.340669631958007812, 0.928766727447509765, 120, 255, 1, "", 50664, NULL), +(268739, 193723, 571, 0, 0, 1, 1, 7636.345703125, 2046.3046875, 601.35015869140625, 0.575957715511322021, -0.09489917755126953, 0.161904335021972656, 0.286963462829589843, 0.939379096031188964, 120, 255, 1, "", 50664, NULL), +(268740, 193722, 571, 0, 0, 1, 1, 7636.70751953125, 2047.2916259765625, 601.309326171875, 3.010666131973266601, 0, 0, 0.997858047485351562, 0.065416477620601654, 120, 255, 1, "", 50664, NULL), +(268741, 193721, 571, 0, 0, 1, 1, 7636.48095703125, 2046.31298828125, 600.129638671875, 2.094393253326416015, 0, 0, 0.866024971008300781, 0.50000077486038208, 120, 255, 1, "", 50664, NULL), +(268742, 193720, 571, 0, 0, 1, 1, 7639.40576171875, 2049.9814453125, 600.68109130859375, 0.750488698482513427, 0, 0, 0.366499900817871093, 0.930418074131011962, 120, 255, 1, "", 50664, NULL), +(268743, 193719, 571, 0, 0, 1, 1, 7642.1875, 2066.251953125, 601.51141357421875, 4.319693565368652343, 0.329238414764404296, -0.62578010559082031, -0.54365253448486328, 0.452154040336608886, 120, 255, 1, "", 50664, NULL), +(268744, 193718, 571, 0, 0, 1, 1, 7641.9541015625, 2051.512939453125, 600.240234375, 2.923415660858154296, 0, 0, 0.994055747985839843, 0.108872212469577789, 120, 255, 1, "", 50664, NULL), +(268745, 193717, 571, 0, 0, 1, 1, 7642.1845703125, 2051.5986328125, 600.46063232421875, 5.174901962280273437, 0, 0, -0.52621364593505859, 0.850352406501770019, 120, 255, 1, "", 50664, NULL), +(268746, 193716, 571, 0, 0, 1, 1, 7642.3935546875, 2051.588134765625, 600.239990234375, 3.63901376724243164, 0, 0, -0.96923065185546875, 0.246154293417930603, 120, 255, 1, "", 50664, NULL), +(268747, 193715, 571, 0, 0, 1, 1, 7642.1396484375, 2051.818115234375, 600.2618408203125, 5.349435329437255859, 0, 0, -0.45009803771972656, 0.892979145050048828, 120, 255, 1, "", 50664, NULL), +(268748, 193714, 571, 0, 0, 1, 1, 7642.29345703125, 2051.72412109375, 600.26397705078125, 4.860742568969726562, 0, 0, -0.65275955200195312, 0.757565200328826904, 120, 255, 1, "", 50664, NULL), +(268749, 193713, 571, 0, 0, 1, 1, 7642.54052734375, 2051.8837890625, 600.25, 1.844759345054626464, 0.050289154052734375, 0.201838493347167968, 0.769841194152832031, 0.603387713432312011, 120, 255, 1, "", 50664, NULL), +(268750, 193712, 571, 0, 0, 1, 1, 7642.7177734375, 2051.58984375, 600.26141357421875, 1.710421562194824218, 0, 0, 0.754709243774414062, 0.656059443950653076, 120, 255, 1, "", 50664, NULL), +(268751, 193711, 571, 0, 0, 1, 1, 7645.83544921875, 2061.108642578125, 600.2529296875, 3.848450660705566406, 0, 0, -0.93819141387939453, 0.346116840839385986, 120, 255, 1, "", 50664, NULL), +(268752, 193710, 571, 0, 0, 1, 1, 7645.78662109375, 2059.0693359375, 600.252685546875, 5.244716167449951171, 0, 0, -0.4962158203125, 0.86819922924041748, 120, 255, 1, "", 50664, NULL), +(268753, 193709, 571, 0, 0, 1, 1, 7645.86181640625, 2060.064697265625, 601.121826171875, 4.642610549926757812, 0.681998252868652343, -0.73134136199951171, -0.00068855285644531, 0.004207443445920944, 120, 255, 1, "", 50664, NULL), +(268754, 193708, 571, 0, 0, 1, 1, 7636.177734375, 2073.49462890625, 601.4434814453125, 3.420847892761230468, -0.21741914749145507, -0.08422660827636718, -0.96116828918457031, 0.1476154625415802, 120, 255, 1, "", 50664, NULL), +(268755, 193707, 571, 0, 0, 1, 1, 7639.47998046875, 2069.831787109375, 600.65679931640625, 2.347463846206665039, 0, 0, 0.922200202941894531, 0.386712819337844848, 120, 255, 1, "", 50664, NULL), +(268756, 193706, 571, 0, 0, 1, 1, 7642.2158203125, 2066.698486328125, 601.4727783203125, 5.715955257415771484, 0, 0, -0.27982807159423828, 0.960050106048583984, 120, 255, 1, "", 50664, NULL), +(268757, 193705, 571, 0, 0, 1, 1, 7641.5390625, 2066.837890625, 601.4727783203125, 0.401424884796142578, 0, 0, 0.199367523193359375, 0.979924798011779785, 120, 255, 1, "", 50664, NULL), +(268758, 193704, 571, 0, 0, 1, 1, 7642.36376953125, 2067.4638671875, 601.4591064453125, 3.395873546600341796, 0.056763648986816406, 0.014508247375488281, -0.99017143249511718, 0.126995846629142761, 120, 255, 1, "", 50664, NULL), +(268759, 193703, 571, 0, 0, 1, 1, 7642.8466796875, 2067.182861328125, 601.47882080078125, 2.1816558837890625, -0.18238639831542968, 0.044202804565429687, 0.875073432922363281, 0.446125298738479614, 120, 255, 1, "", 50664, NULL), +(268760, 193702, 571, 0, 0, 1, 1, 7643.08251953125, 2067.653564453125, 601.462158203125, 3.307400941848754882, -0.08287525177001953, 0.042493820190429687, -0.99254989624023437, 0.078553743660449981, 120, 255, 1, "", 50664, NULL), +(268761, 193701, 571, 0, 0, 1, 1, 7642.833984375, 2067.318359375, 600.256591796875, 3.70009779930114746, 0, 0, -0.96126174926757812, 0.275637149810791015, 120, 255, 1, "", 50664, NULL), +(268762, 193700, 571, 0, 0, 1, 1, 7638.7451171875, 2073.489501953125, 600.251220703125, 1.247907638549804687, 0, 0, 0.584248542785644531, 0.811574757099151611, 120, 255, 1, "", 50664, NULL), +(268763, 193699, 571, 0, 0, 1, 1, 7637.20556640625, 2073.30859375, 601.41949462890625, 4.347476005554199218, 0.06531381607055664, 0.67337799072265625, -0.33799266815185546, 0.654260754585266113, 120, 255, 1, "", 50664, NULL), +(268764, 193698, 571, 0, 0, 1, 1, 7636.033203125, 2073.449951171875, 600.2333984375, 4.354224681854248046, -0.00527000427246093, -0.02503776550292968, -0.82129764556884765, 0.569925904273986816, 120, 255, 1, "", 50664, NULL), +(268765, 193697, 571, 0, 0, 1, 1, 7635.75830078125, 2074.88037109375, 600.2703857421875, 3.333590030670166015, 0, 0, -0.99539566040039062, 0.095851235091686248, 120, 255, 1, "", 50664, NULL), +(268766, 193696, 571, 0, 0, 1, 1, 7637.640625, 2074.039306640625, 600.2724609375, 2.609261274337768554, 0, 0, 0.964786529541015625, 0.263034075498580932, 120, 255, 1, "", 50664, NULL), +(268772, 193693, 571, 0, 0, 1, 1, 7892.458984375, 2073.525146484375, 601.7738037109375, 5.521906852722167968, -0.67214488983154296, 0.21199798583984375, -0.31161308288574218, 0.637318909168243408, 120, 255, 1, "", 46368, NULL), +(268773, 193691, 571, 0, 0, 1, 1, 7891.21435546875, 2057.974853515625, 604.2218017578125, 3.124123096466064453, 0, 0, 0.99996185302734375, 0.008734640665352344, 120, 255, 1, "", 46368, NULL), +(268774, 193678, 571, 0, 0, 1, 1, 7891.45458984375, 2058.0087890625, 600.86529541015625, 0.011531894095242023, 0, 0, 0.005765914916992187, 0.999983370304107666, 120, 255, 1, "", 46368, NULL), +(268775, 193677, 571, 0, 0, 1, 1, 7891.65234375, 2058.012939453125, 604.251953125, 6.265733242034912109, 0, 0, -0.00872611999511718, 0.999961912631988525, 120, 255, 1, "", 46368, NULL), +(268779, 193676, 571, 0, 0, 1, 1, 7888.271484375, 2058.06640625, 600.46380615234375, 1.579522013664245605, 0, 0, 0.710185050964355468, 0.704015016555786132, 120, 255, 1, "", 46368, NULL), +(268780, 193674, 571, 0, 0, 1, 1, 7888.546875, 2057.972900390625, 598.50286865234375, 3.001946926116943359, 0, 0, 0.997563362121582031, 0.069766148924827575, 120, 255, 1, "", 46368, NULL), +(268781, 193669, 571, 0, 0, 1, 1, 7877.67138671875, 2048.341796875, 600.62579345703125, 5.489060401916503906, 0, 0, -0.38671112060546875, 0.922200918197631835, 120, 255, 1, "", 46368, NULL), +(268782, 193668, 571, 0, 0, 1, 1, 7886.5634765625, 2057.9765625, 600.13427734375, 3.159062385559082031, 0, 0, -0.99996185302734375, 0.008734640665352344, 120, 255, 1, "", 46368, NULL), +(268783, 193666, 571, 0, 0, 1, 1, 7874.21435546875, 2051.378173828125, 601.40032958984375, 5.759586811065673828, 0.030743122100830078, 0.030928611755371093, -0.25936603546142578, 0.964794039726257324, 120, 255, 1, "", 46368, NULL), +(268784, 193665, 571, 0, 0, 1, 1, 7874.3037109375, 2050.991455078125, 601.44683837890625, 5.32325601577758789, 0.044202804565429687, 0.182386398315429687, -0.4461221694946289, 0.875075042247772216, 120, 255, 1, "", 46368, NULL), +(268785, 193664, 571, 0, 0, 1, 1, 7874.00244140625, 2050.78515625, 601.4483642578125, 4.642575740814208984, -0.07862043380737304, -0.12896251678466796, -0.71886634826660156, 0.678541600704193115, 120, 255, 1, "", 46368, NULL), +(268786, 193663, 571, 0, 0, 1, 1, 7874.31689453125, 2050.85595703125, 600.2255859375, 0.558503925800323486, 0, 0, 0.275636672973632812, 0.961261868476867675, 120, 255, 1, "", 46368, NULL), +(268790, 193659, 571, 0, 0, 1, 1, 7871.31591796875, 2057.065185546875, 600.2220458984375, 0.706856250762939453, 0, 0, 0.346116065979003906, 0.938191711902618408, 120, 255, 1, "", 46368, NULL), +(268791, 193658, 571, 0, 0, 1, 1, 7871.3642578125, 2059.104248046875, 600.22412109375, 2.103119850158691406, 0, 0, 0.868198394775390625, 0.496217250823974609, 120, 255, 1, "", 46368, NULL), +(268792, 193657, 571, 0, 0, 1, 1, 7871.28857421875, 2058.10986328125, 601.09088134765625, 1.501015067100524902, 0.731341838836669921, 0.681998252868652343, 0.00405120849609375, 0.001035800902172923, 120, 255, 1, "", 46368, NULL), +(268793, 193662, 571, 0, 0, 1, 1, 7889.31298828125, 2041.3023681640625, 600.216796875, 2.33001255989074707, 0, 0, 0.918790817260742187, 0.394744753837585449, 120, 255, 1, "", 46368, NULL), +(268794, 193660, 571, 0, 0, 1, 1, 7887.27392578125, 2041.244140625, 600.21728515625, 3.72628021240234375, 0, 0, -0.95757102966308593, 0.288197338581085205, 120, 255, 1, "", 46368, NULL), +(268795, 193661, 571, 0, 0, 1, 1, 7888.27294921875, 2041.2213134765625, 601.0849609375, 3.12417149543762207, -0.00871896743774414, -0.99995326995849609, -0.00328731536865234, 0.002583741443231701, 120, 255, 1, "", 46368, NULL), +(268799, 193654, 571, 0, 0, 1, 1, 7880.66943359375, 2071.861328125, 600.09869384765625, 5.235987663269042968, 0, 0, -0.5, 0.866025388240814208, 120, 255, 1, "", 46368, NULL), +(268800, 193652, 571, 0, 0, 1, 1, 7877.7451171875, 2068.192626953125, 600.65087890625, 3.892086982727050781, 0, 0, -0.93041706085205078, 0.366502493619918823, 120, 255, 1, "", 46368, NULL), +(268807, 193651, 571, 0, 0, 1, 1, 7887.63720703125, 2074.77099609375, 600.18682861328125, 5.445429801940917968, 0, 0, -0.40673542022705078, 0.91354602575302124, 120, 255, 1, "", 46368, NULL), +(268808, 193649, 571, 0, 0, 1, 1, 7888.68017578125, 2074.82470703125, 601.05072021484375, 6.239586830139160156, 0.999753475189208984, -0.0218057632446289, 0.002421379089355468, 0.00341796875, 120, 255, 1, "", 46368, NULL), +(268809, 193650, 571, 0, 0, 1, 1, 7889.67724609375, 2074.776123046875, 600.18499755859375, 0.558503925800323486, 0, 0, 0.275636672973632812, 0.961261868476867675, 120, 255, 1, "", 46368, NULL), +(268815, 193671, 571, 0, 0, 1, 1, 7896.20068359375, 2044.22802734375, 600.18603515625, 2.111847877502441406, 0, 0, 0.870355606079101562, 0.492423713207244873, 120, 255, 1, "", 46368, NULL), +(268816, 193670, 571, 0, 0, 1, 1, 7898.9248046875, 2047.802734375, 600.52520751953125, 0.750488698482513427, 0, 0, 0.366499900817871093, 0.930418074131011962, 120, 255, 1, "", 46368, NULL), +(268818, 193645, 571, 0, 0, 1, 1, 7903.21923828125, 2050.816650390625, 600.10931396484375, 1.256635904312133789, 0, 0, 0.587784767150878906, 0.809017360210418701, 120, 255, 1, "", 46368, NULL), +(268819, 193644, 571, 0, 0, 1, 1, 7903.2822265625, 2050.578857421875, 600.28997802734375, 3.508116960525512695, 0, 0, -0.98325443267822265, 0.182238012552261352, 120, 255, 1, "", 46368, NULL), +(268820, 193643, 571, 0, 0, 1, 1, 7903.251953125, 2050.372314453125, 599.96185302734375, 1.972219824790954589, 0, 0, 0.83388519287109375, 0.55193793773651123, 120, 255, 1, "", 46368, NULL), +(268821, 193642, 571, 0, 0, 1, 1, 7903.5048828125, 2050.60205078125, 600.26007080078125, 3.682650327682495117, 0, 0, -0.96362972259521484, 0.26724100112915039, 120, 255, 1, "", 46368, NULL), +(268822, 193637, 571, 0, 0, 1, 1, 7902.48046875, 2048.71728515625, 600.2122802734375, 5.166173934936523437, 0, 0, -0.52991962432861328, 0.848047912120819091, 120, 255, 1, "", 46368, NULL), +(268825, 193632, 571, 0, 0, 1, 1, 7905.810546875, 2058.752685546875, 600.1993408203125, 3.822272777557373046, 0, 0, -0.94264125823974609, 0.333807557821273803, 120, 255, 1, "", 46368, NULL), +(268826, 193631, 571, 0, 0, 1, 1, 7905.70849609375, 2056.715576171875, 600.1993408203125, 5.218533515930175781, 0, 0, -0.5075387954711914, 0.861628890037536621, 120, 255, 1, "", 46368, NULL), +(268827, 193630, 571, 0, 0, 1, 1, 7905.68505859375, 2057.931640625, 601.06793212890625, 4.616430282592773437, 0.672366619110107421, -0.74020576477050781, -0.00074100494384765, 0.00424973014742136, 120, 255, 1, "", 46368, NULL), +(268829, 193653, 571, 0, 0, 1, 1, 7899.41552734375, 2068.0380859375, 600.639404296875, 2.338739633560180664, 0, 0, 0.920504570007324218, 0.3907318115234375, 120, 255, 1, "", 46368, NULL), +(268832, 193636, 571, 0, 0, 1, 1, 7902.55126953125, 2066.074951171875, 601.30126953125, 3.508113622665405273, -0.08671522140502929, 0.033976554870605468, -0.97968578338623046, 0.177600175142288208, 120, 255, 1, "", 46368, NULL), +(268833, 193635, 571, 0, 0, 1, 1, 7902.6494140625, 2066.373046875, 601.30133056640625, 4.389505863189697265, -0.07650041580200195, -0.06483268737792968, -0.80701732635498046, 0.581951439380645751, 120, 255, 1, "", 46368, NULL), +(268834, 193634, 571, 0, 0, 1, 1, 7902.3046875, 2066.493408203125, 601.3028564453125, 3.708826541900634765, 0, 0, -0.96004962921142578, 0.279829770326614379, 120, 255, 1, "", 46368, NULL), +(268835, 193633, 571, 0, 0, 1, 1, 7902.375, 2065.69677734375, 600.09716796875, 3.900813102722167968, 0, 0, -0.92880916595458984, 0.370558410882949829, 120, 255, 1, "", 46368, NULL), +(268839, 193681, 571, 0, 0, 1, 1, 7895.1708984375, 2072.284423828125, 600.2469482421875, 1.517560839653015136, 0, 0, 0.688036918640136718, 0.725675702095031738, 120, 255, 1, "", 46368, NULL), +(268840, 193680, 571, 0, 0, 1, 1, 7893.27880859375, 2073.20849609375, 600.249755859375, 0.280996710062026977, 0, 0, 0.140036582946777343, 0.990146338939666748, 120, 255, 1, "", 46368, NULL); + +-- new spawns +DELETE FROM `gameobject` WHERE (`id` IN (193638, 193639, 193640, 193641, 193646, 193647, 193648, 193655, 193656, 193667, 193672, 193673, 193675, 193679, 193682, 193683, 193684, 193685, 193686, 193687, 193688, 193689, 193690, 193694, 193695)) AND (`guid` IN (2077, 2078, 2079, 2080, 2081, 2082, 2083, 2084, 2085, 2086, 2087, 2088, 2089, 2090, 2091, 2092, 2093, 2094, 2095, 2096, 2097, 2098, 2099, 2100, 2101)); +INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `position_x`, `position_y`, `position_z`, `orientation`, `rotation0`, `rotation1`, `rotation2`, `rotation3`, `spawntimesecs`, `animprogress`, `state`, `ScriptName`, `VerifiedBuild`, `Comment`) VALUES +(2077, 193638, 571, 0, 0, 1, 1, 7902.05078125, 2048.413330078125, 600.2374267578125, 4.581534385681152343, -0.28159475326538085, 0.654002189636230468, 0.270960807800292968, 0.647738933563232421, 120, 255, 1, "", 46368, NULL), +(2078, 193639, 571, 0, 0, 1, 1, 7903.22216796875, 2050.048828125, 599.98638916015625, 0.043632153421640396, 0, 0, 0.021814346313476562, 0.99976205825805664, 120, 255, 1, "", 46368, NULL), +(2079, 193640, 571, 0, 0, 1, 1, 7903.53271484375, 2050.19677734375, 600.09759521484375, 0.177969858050346374, 0.183217525482177734, 0.098484039306640625, 0.070977210998535156, 0.975548326969146728, 120, 255, 1, "", 46368, NULL), +(2080, 193641, 571, 0, 0, 1, 1, 7903.39697265625, 2050.458740234375, 600.12481689453125, 3.194002151489257812, 0, 0, -0.99965667724609375, 0.026201646775007247, 120, 255, 1, "", 46368, NULL), +(2081, 193646, 571, 0, 0, 1, 1, 7904.517578125, 2052.545166015625, 600.2119140625, 6.065019607543945312, 0, 0, -0.10886669158935546, 0.994056344032287597, 120, 255, 1, "", 46368, NULL), +(2082, 193647, 571, 0, 0, 1, 1, 7903.68505859375, 2052.069091796875, 600.21636962890625, 0.680676698684692382, 0, 0, 0.333806037902832031, 0.942641794681549072, 120, 255, 1, "", 46368, NULL), +(2083, 193648, 571, 0, 0, 1, 1, 7904.35107421875, 2051.583740234375, 600.22607421875, 5.672319889068603515, 0, 0, -0.3007059097290039, 0.953716933727264404, 120, 255, 1, "", 46368, NULL), +(2084, 193655, 571, 0, 0, 1, 1, 7880.44384765625, 2070.882568359375, 601.27783203125, 6.152286052703857421, 0, 0, -0.06540298461914062, 0.997858941555023193, 120, 255, 1, "", 46368, NULL), +(2085, 193656, 571, 0, 0, 1, 1, 7880.8056640625, 2071.86962890625, 601.318603515625, 3.717553138732910156, 0.161904335021972656, 0.094899177551269531, -0.93937873840332031, 0.286964625120162963, 120, 255, 1, "", 46368, NULL), +(2086, 193667, 571, 0, 0, 1, 1, 7874.93603515625, 2051.4755859375, 601.44091796875, 2.574358940124511718, 0, 0, 0.960049629211425781, 0.279829770326614379, 120, 255, 1, "", 46368, NULL), +(2087, 193672, 571, 0, 0, 1, 1, 7895.59130859375, 2044.85791015625, 601.402587890625, 4.127707481384277343, 0, 0, -0.880889892578125, 0.473321229219436645, 120, 255, 1, "", 46368, NULL), +(2088, 193673, 571, 0, 0, 1, 1, 7895.86083984375, 2043.8819580078125, 601.3876953125, 3.298687219619750976, 0, 0, -0.99691677093505859, 0.078466430306434631, 120, 255, 1, "", 46368, NULL), +(2089, 193675, 571, 0, 0, 1, 1, 7888.546875, 2057.972900390625, 599.6009521484375, 1.474801421165466308, 0, 0, 0.672366142272949218, 0.740218758583068847, 120, 255, 1, "", 46368, NULL), +(2090, 193679, 571, 0, 0, 1, 1, 7893.048828125, 2071.62255859375, 600.365478515625, 3.813161611557006835, -0.69073057174682617, -0.15002155303955078, -0.69123363494873046, 0.150269299745559692, 120, 255, 1, "", 46368, NULL), +(2091, 193682, 571, 0, 0, 1, 1, 7895.12890625, 2072.302978515625, 601.992919921875, 0.365644693374633789, 0, 0, 0.181805610656738281, 0.983334481716156005, 120, 255, 1, "", 46368, NULL), +(2092, 193683, 571, 0, 0, 1, 1, 7875.3193359375, 2053.161376953125, 600.2493896484375, 1.278458118438720703, 0.212913990020751953, -0.32244300842285156, 0.543003082275390625, 0.745550692081451416, 120, 255, 1, "", 46368, NULL), +(2093, 193684, 571, 0, 0, 1, 1, 7898.14697265625, 2066.417236328125, 600.31329345703125, 0.75534135103225708, 0, 0, 0.368756294250488281, 0.929526090621948242, 120, 255, 1, "", 46368, NULL), +(2094, 193685, 571, 0, 0, 1, 1, 7897.3955078125, 2049.253173828125, 600.3131103515625, 5.459003925323486328, 0, 0, -0.40052604675292968, 0.916285336017608642, 120, 255, 1, "", 46368, NULL), +(2095, 193686, 571, 0, 0, 1, 1, 7878.8671875, 2049.250244140625, 600.31298828125, 0.75534135103225708, 0, 0, 0.368756294250488281, 0.929526090621948242, 120, 255, 1, "", 46368, NULL), +(2096, 193687, 571, 0, 0, 1, 1, 7878.82861328125, 2066.62109375, 600.31317138671875, 5.432824611663818359, 0, 0, -0.41248512268066406, 0.910964369773864746, 120, 255, 1, "", 46368, NULL), +(2097, 193688, 571, 0, 0, 1, 1, 7881.25048828125, 2043.955078125, 600.2493896484375, 1.4564744234085083, 0, 0, 0.665555000305175781, 0.746348798274993896, 120, 255, 1, "", 46368, NULL), +(2098, 193689, 571, 0, 0, 1, 1, 7879.41748046875, 2044.993408203125, 600.2493896484375, 0.219910025596618652, 0, 0, 0.10973358154296875, 0.993961036205291748, 120, 255, 1, "", 46368, NULL), +(2099, 193690, 571, 0, 0, 1, 1, 7881.208984375, 2043.9761962890625, 600.2493896484375, 0.304556638002395629, 0, 0, 0.151690483093261718, 0.988428056240081787, 120, 255, 1, "", 46368, NULL), +(2100, 193694, 571, 0, 0, 1, 1, 7886.49609375, 2059.90869140625, 600.2626953125, 5.452062606811523437, 0, 0, -0.40370368957519531, 0.914889812469482421, 120, 255, 1, "", 46368, NULL), +(2101, 193695, 571, 0, 0, 1, 1, 7885.58935546875, 2059.190185546875, 600.76806640625, 2.337379932403564453, 0.248764514923095703, 0.376603126525878906, 0.836214065551757812, 0.311500102281570434, 120, 255, 1, "", 46368, NULL); + +-- remove duplicate spawns +DELETE FROM `gameobject` WHERE (`id` IN (193706, 193712, 193713, 193719, 193698, 193699, 193700, 193705, 193708, 193722, 193723, 193724, 193725, 193726, 193730, 193731, 193732, 193733, 193734, 193735, 193738, 193739, 193740, 193759, 193760, 193761, 193749, 193747, 193748, 193692)) AND (`guid` IN (268830, 268824, 268823, 268817, 268838, 268837, 268836, 268831, 268828, 268814, 268813, 268812, 268811, 268810, 268806, 268805, 268804, 268803, 268802, 268801, 268798, 268797, 268796, 268778, 268777, 268776, 268787, 268789, 268788, 256036, 257787, 258233, 260066, 260579, 261410, 261970, 262680, 263207, 263653, 265211, 266056, 267263, 267510, 267905, 268771)); +DELETE FROM `gameobject_addon` WHERE (`guid` IN (268830, 268824, 268823, 268817, 268838, 268837, 268836, 268831, 268828, 268814, 268813, 268812, 268811, 268810, 268806, 268805, 268804, 268803, 268802, 268801, 268798, 268797, 268796, 268778, 268777, 268776, 268787, 268789, 268788, 256036, 257787, 258233, 260066, 260579, 261410, 261970, 262680, 263207, 263653, 265211, 266056, 267263, 267510, 267905, 268771)); +DELETE FROM `game_event_gameobject` WHERE (`eventEntry` = 0) AND (`guid` IN (268830, 268824, 268823, 268817, 268838, 268837, 268836, 268831, 268828, 268814, 268813, 268812, 268811, 268810, 268806, 268805, 268804, 268803, 268802, 268801, 268798, 268797, 268796, 268778, 268777, 268776, 268787, 268789, 268788, 256036, 257787, 258233, 260066, 260579, 261410, 261970, 262680, 263207, 263653, 265211, 266056, 267263, 267510, 267905, 268771)); diff --git a/data/sql/updates/db_world/2025_09_03_01.sql b/data/sql/updates/db_world/2025_09_03_01.sql new file mode 100644 index 000000000..692c4337e --- /dev/null +++ b/data/sql/updates/db_world/2025_09_03_01.sql @@ -0,0 +1,12 @@ +-- DB update 2025_09_03_00 -> 2025_09_03_01 +-- +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 17) AND (`SourceGroup` = 0) AND (`SourceEntry` = 44407); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(17, 0, 44407, 0, 0, 31, 1, 3, 24747, 0, 0, 0, 0, '', 'Hawk Hunting must target Fjord Hawk'); + +UPDATE `creature_template` SET `AIName` = '' WHERE `entry` = 24747; +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 24747) AND (`source_type` = 0); + +DELETE FROM `spell_script_names` WHERE `ScriptName` = 'spell_hawk_hunting'; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(44407, 'spell_hawk_hunting'); diff --git a/data/sql/updates/db_world/2025_09_03_02.sql b/data/sql/updates/db_world/2025_09_03_02.sql new file mode 100644 index 000000000..98cfb21bd --- /dev/null +++ b/data/sql/updates/db_world/2025_09_03_02.sql @@ -0,0 +1,9 @@ +-- DB update 2025_09_03_01 -> 2025_09_03_02 +-- +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` IN (16423, 16437, 16438, 16422); +DELETE FROM `smart_scripts` WHERE `entryorguid` IN (16423, 16437, 16438, 16422); +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 +(16423, 0, 0, 0, 0, 0, 100, 0, 0, 0, 5000, 10000, 0, 0, 11, 28265, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Spectral Apparition - In Combat - Cast \'Scourge Strike\''), +(16437, 0, 0, 0, 0, 0, 100, 0, 0, 0, 5000, 10000, 0, 0, 11, 28265, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Spectral Spirit - In Combat - Cast \'Scourge Strike\''), +(16422, 0, 0, 0, 0, 0, 100, 0, 0, 0, 5000, 10000, 0, 0, 11, 28265, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Skeletal Soldier - In Combat - Cast \'Scourge Strike\''), +(16438, 0, 0, 0, 0, 0, 100, 0, 0, 0, 5000, 10000, 0, 0, 11, 28265, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Skeletal Trooper - In Combat - Cast \'Scourge Strike\''); diff --git a/data/sql/updates/db_world/2025_09_04_00.sql b/data/sql/updates/db_world/2025_09_04_00.sql new file mode 100644 index 000000000..fc9d4a9a2 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_04_00.sql @@ -0,0 +1,7 @@ +-- DB update 2025_09_03_02 -> 2025_09_04_00 +-- +UPDATE `gameobject_template` SET `AIName` = 'SmartGameObjectAI' WHERE `entry` = 188141; + +DELETE FROM `smart_scripts` WHERE (`source_type` = 1 AND `entryorguid` = 188141); +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 +(188141, 1, 0, 0, 64, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 3000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Frozen Phylactery - On Gossip Hello - Despawn In 3000 ms'); diff --git a/data/sql/updates/db_world/2025_09_04_01.sql b/data/sql/updates/db_world/2025_09_04_01.sql new file mode 100644 index 000000000..9a885fd6d --- /dev/null +++ b/data/sql/updates/db_world/2025_09_04_01.sql @@ -0,0 +1,2 @@ +-- DB update 2025_09_04_00 -> 2025_09_04_01 +UPDATE `creature_model_info` SET `DisplayID_Other_Gender` = 0 WHERE `DisplayID` IN (16292, 16294); diff --git a/data/sql/updates/db_world/2025_09_04_02.sql b/data/sql/updates/db_world/2025_09_04_02.sql new file mode 100644 index 000000000..91873ddd1 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_04_02.sql @@ -0,0 +1,18 @@ +-- DB update 2025_09_04_01 -> 2025_09_04_02 +-- Update gameobject 'Gravestone' with sniffed values +-- updated spawns +DELETE FROM `gameobject` WHERE (`id` IN (192256)) AND (`guid` IN (76993)); +INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `position_x`, `position_y`, `position_z`, `orientation`, `rotation0`, `rotation1`, `rotation2`, `rotation3`, `spawntimesecs`, `animprogress`, `state`, `ScriptName`, `VerifiedBuild`, `Comment`) VALUES +(76993, 192256, 571, 0, 0, 1, 1, 9025.6845703125, -1178.6163330078125, 1058.107666015625, 3.141592741012573242, 0, 0, -1, 0, 120, 255, 1, "", 46368, NULL); + +-- new spawns +DELETE FROM `gameobject` WHERE (`id` IN (192257, 192258, 192260, 192265, 192380)) AND (`guid` BETWEEN 265 AND 269); +INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `position_x`, `position_y`, `position_z`, `orientation`, `rotation0`, `rotation1`, `rotation2`, `rotation3`, `spawntimesecs`, `animprogress`, `state`, `ScriptName`, `VerifiedBuild`, `Comment`) VALUES +(265, 192257, 571, 0, 0, 1, 1, 8094.673828125, -995.29864501953125, 936.18206787109375, 3.141592741012573242, 0, 0, -1, 0, 120, 255, 1, "", 53788, NULL), +(266, 192258, 571, 0, 0, 1, 1, 7832.95556640625, -2018.984375, 1224.683349609375, 3.141592741012573242, 0, 0, -1, 0, 120, 255, 1, "", 52237, NULL), +(267, 192260, 571, 0, 0, 1, 1, 7463.06689453125, -3326.37841796875, 897.74884033203125, 3.141592741012573242, 0, 0, -1, 0, 120, 255, 1, "", 47720, NULL), +(268, 192265, 571, 0, 0, 1, 1, 6942.8603515625, -552.515625, 914.403564453125, 3.141592741012573242, 0, 0, -1, 0, 120, 255, 1, "", 50664, NULL), +(269, 192380, 571, 0, 0, 1, 1, 6431.64404296875, -1186.592041015625, 446.2081298828125, 3.141592741012573242, 0, 0, -1, 0, 120, 255, 1, "", 47720, NULL); + +-- remaining spawns (no sniffed values available) +-- (`guid` IN (77187)) diff --git a/data/sql/updates/db_world/2025_09_04_03.sql b/data/sql/updates/db_world/2025_09_04_03.sql new file mode 100644 index 000000000..07265fb35 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_04_03.sql @@ -0,0 +1,23 @@ +-- DB update 2025_09_04_02 -> 2025_09_04_03 +-- +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 24170); +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 +(24170, 0, 0, 3, 54, 0, 100, 512, 0, 0, 0, 0, 0, 0, 64, 1, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Draconis Gastritis Bunny - On Just Summoned - Store Targetlist'), +(24170, 0, 1, 4, 6, 0, 100, 0, 0, 0, 0, 0, 0, 0, 33, 24170, 0, 0, 0, 0, 0, 12, 1, 0, 0, 0, 0, 0, 0, 0, 'Draconis Gastritis Bunny - On Just Died - Quest Credit \'null\''), +(24170, 0, 2, 0, 54, 0, 100, 512, 0, 0, 0, 0, 0, 0, 47, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Draconis Gastritis Bunny - On Just Summoned - Set Visibility Off'), +(24170, 0, 3, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 50, 186598, 45, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Draconis Gastritis Bunny - On Just Summoned - Summon Gameobject \'Tillinghast\'s Plagued Meat\''), +(24170, 0, 4, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 1, 0, 0, 0, 20, 186598, 10, 0, 0, 0, 0, 0, 0, 'Draconis Gastritis Bunny - On Just Died - Despawn Instant'); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 23689); +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 +(23689, 0, 1, 4, 65, 0, 100, 512, 0, 0, 0, 0, 0, 0, 11, 36809, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Proto-Drake - On Follow Complete - Cast \'Overpowering Sickness\''), +(23689, 0, 3, 5, 1, 0, 100, 512, 10000, 10000, 10000, 10000, 0, 0, 29, 0, 0, 24170, 0, 0, 0, 19, 24170, 75, 0, 0, 0, 0, 0, 0, 'Proto-Drake - Out of Combat - Start Follow Closest Creature \'Draconis Gastritis Bunny\''), +(23689, 0, 4, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 19, 24170, 10, 0, 0, 0, 0, 0, 0, 'Proto-Drake - On Follow Complete - Kill Target'), +(23689, 0, 5, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Proto-Drake - Out of Combat - Set Event Phase 1'), +(23689, 0, 6, 0, 1, 1, 100, 512, 45000, 45000, 45000, 45000, 0, 0, 41, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Proto-Drake - Out of Combat - Despawn Instant (Phase 1)'), +-- update comments with Keira +(23689, 0, 8, 0, 8, 0, 100, 0, 40969, 0, 120000, 120000, 0, 0, 69, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Proto-Drake - On Spellhit \'Malister`s Frost Wand\' - Move To Invoker'), +(23689, 0, 9, 0, 9, 0, 100, 513, 0, 0, 0, 0, 0, 20, 101, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Proto-Drake - Within 0-20 Range - Set Home Position (No Repeat)'), +(23689, 0, 10, 0, 9, 0, 100, 0, 0, 0, 2000, 3500, 0, 5, 11, 51219, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Proto-Drake - Within 0-5 Range - Cast \'Flame Breath\''), +(23689, 0, 11, 0, 0, 0, 100, 0, 3000, 9000, 30000, 45000, 0, 0, 11, 42362, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Proto-Drake - In Combat - Cast \'Flames of Birth\''), +(23689, 0, 12, 0, 9, 0, 100, 0, 0, 0, 10000, 15000, 0, 20, 11, 41572, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Proto-Drake - Within 0-20 Range - Cast \'Wing Buffet\''); diff --git a/data/sql/updates/db_world/2025_09_05_00.sql b/data/sql/updates/db_world/2025_09_05_00.sql new file mode 100644 index 000000000..d5bb66322 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_05_00.sql @@ -0,0 +1,64 @@ +-- DB update 2025_09_04_03 -> 2025_09_05_00 +-- Prevent removal on evade +DELETE FROM `spell_custom_attr` WHERE `spell_id` IN (50665, 50681, 50695); +INSERT INTO `spell_custom_attr` (`spell_id`, `attributes`) VALUES +(50665, 2048), +(50681, 2048), +(50695, 2048); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 28148); +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 +(28148, 0, 0, 0, 25, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Reset - Set Event Phase 1'), +(28148, 0, 1, 2, 54, 0, 100, 512, 0, 0, 0, 0, 0, 0, 11, 50695, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Just Summoned - Cast \'Bleeding Out\''), +(28148, 0, 2, 3, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Just Summoned - Start Follow Invoker'), +(28148, 0, 3, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Just Summoned - Remove FlagStandstate Sit Down'), +(28148, 0, 4, 0, 23, 1, 100, 513, 50695, 0, 0, 0, 0, 0, 80, 2814800, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Aura \'Bleeding Out\' - Run Script (Phase 1) (No Repeat)'), +(28148, 0, 5, 6, 40, 0, 100, 513, 4, 0, 0, 0, 0, 0, 90, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Point 4 of Path Any Reached - Set Flag Standstate Sit Down (No Repeat)'), +(28148, 0, 6, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 41, 20000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Point 4 of Path Any Reached - Despawn In 20000 ms (No Repeat)'), +(28148, 0, 7, 8, 8, 1, 100, 512, 50669, 0, 0, 0, 0, 0, 22, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Spellhit \'Quest Credit\' - Set Event Phase 2 (Phase 1)'), +(28148, 0, 8, 9, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 11, 50698, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Spellhit \'Quest Credit\' - Cast \'Kill Credit Jospehine 01\' (Phase 1)'), +(28148, 0, 9, 10, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 11, 50711, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Spellhit \'Quest Credit\' - Cast \'Strip Aura Josephine 01\' (Phase 1)'), +(28148, 0, 10, 11, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 86, 50699, 2, 23, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Spellhit \'Quest Credit\' - Cross Cast \'Josephine Kill Credit\' (Phase 1)'), +(28148, 0, 11, 12, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 86, 50712, 2, 23, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Spellhit \'Quest Credit\' - Cross Cast \'Strip Aura Josephine\' (Phase 1)'), +(28148, 0, 12, 13, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Spellhit \'Quest Credit\' - Stop Follow (Phase 1)'), +(28148, 0, 13, 14, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Spellhit \'Quest Credit\' - Say Line 0 (Phase 1)'), +(28148, 0, 14, 15, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 53, 0, 28148, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Spellhit \'Quest Credit\' - Start Waypoint Path 28148 (Phase 1)'), +(28148, 0, 15, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Josephine - On Spellhit \'Quest Credit\' - Remove Npc Flags Gossip (Phase 1)'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 28142) AND (`source_type` = 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 +(28142, 0, 0, 0, 25, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Reset - Set Event Phase 1'), +(28142, 0, 1, 2, 54, 0, 100, 512, 0, 0, 0, 0, 0, 0, 11, 50681, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Just Summoned - Cast \'Bleeding Out\''), +(28142, 0, 2, 3, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Just Summoned - Start Follow Invoker'), +(28142, 0, 3, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Just Summoned - Remove FlagStandstate Sit Down'), +(28142, 0, 4, 0, 23, 1, 100, 513, 50681, 0, 0, 0, 0, 0, 80, 2814200, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Aura \'Bleeding Out\' - Run Script (Phase 1) (No Repeat)'), +(28142, 0, 5, 6, 40, 0, 100, 513, 5, 0, 0, 0, 0, 0, 90, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Point 5 of Path Any Reached - Set Flag Standstate Sit Down (No Repeat)'), +(28142, 0, 6, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 41, 20000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Point 5 of Path Any Reached - Despawn In 20000 ms (No Repeat)'), +(28142, 0, 7, 8, 8, 1, 100, 512, 50669, 0, 0, 0, 0, 0, 22, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Spellhit \'Quest Credit\' - Set Event Phase 2 (Phase 1)'), +(28142, 0, 8, 9, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 11, 50683, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Spellhit \'Quest Credit\' - Cast \'Kill Credit Lamoof 01\' (Phase 1)'), +(28142, 0, 9, 10, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 11, 50723, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Spellhit \'Quest Credit\' - Cast \'Strip Aura Lamoof 01\' (Phase 1)'), +(28142, 0, 10, 11, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 86, 50684, 2, 23, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Spellhit \'Quest Credit\' - Cross Cast \'Lamoof Kill Credit\' (Phase 1)'), +(28142, 0, 11, 12, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 86, 50722, 2, 23, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Spellhit \'Quest Credit\' - Cross Cast \'Strip Aura Lamoof\' (Phase 1)'), +(28142, 0, 12, 13, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Spellhit \'Quest Credit\' - Stop Follow (Phase 1)'), +(28142, 0, 13, 14, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Spellhit \'Quest Credit\' - Say Line 0 (Phase 1)'), +(28142, 0, 14, 15, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 53, 0, 28142, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Spellhit \'Quest Credit\' - Start Waypoint Path 28142 (Phase 1)'), +(28142, 0, 15, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Lamoof - On Spellhit \'Quest Credit\' - Remove Npc Flags Gossip (Phase 1)'); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 28136); +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 +(28136, 0, 0, 0, 25, 0, 100, 0, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Reset - Set Event Phase 1'), +(28136, 0, 1, 2, 54, 0, 100, 512, 0, 0, 0, 0, 0, 0, 11, 50665, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Just Summoned - Cast \'Bleeding Out\''), +(28136, 0, 2, 3, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Just Summoned - Start Follow Invoker'), +(28136, 0, 3, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 91, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Just Summoned - Remove FlagStandstate Sit Down'), +(28136, 0, 4, 0, 23, 1, 100, 513, 50665, 0, 0, 0, 0, 0, 80, 2813600, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Aura \'Bleeding Out\' - Run Script (Phase 1) (No Repeat)'), +(28136, 0, 5, 6, 40, 0, 100, 513, 5, 0, 0, 0, 0, 0, 90, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Point 5 of Path Any Reached - Set Flag Standstate Sit Down (No Repeat)'), +(28136, 0, 6, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 41, 20000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Point 5 of Path Any Reached - Despawn In 20000 ms (No Repeat)'), +(28136, 0, 7, 8, 8, 1, 100, 512, 50669, 0, 0, 0, 0, 0, 22, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Spellhit \'Quest Credit\' - Set Event Phase 2 (Phase 1)'), +(28136, 0, 8, 9, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 11, 50671, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Spellhit \'Quest Credit\' - Cast \'Kill Credit Jonathan 01\' (Phase 1)'), +(28136, 0, 9, 10, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 11, 50709, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Spellhit \'Quest Credit\' - Cast \'Strip Aura Jonathan 01\' (Phase 1)'), +(28136, 0, 10, 11, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 86, 50680, 2, 23, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Spellhit \'Quest Credit\' - Cross Cast \'Jonathan Kill Credit\' (Phase 1)'), +(28136, 0, 11, 12, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 86, 50710, 2, 23, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Spellhit \'Quest Credit\' - Cross Cast \'Strip Aura Jonanthan\' (Phase 1)'), +(28136, 0, 12, 13, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 29, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Spellhit \'Quest Credit\' - Stop Follow (Phase 1)'), +(28136, 0, 13, 14, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Spellhit \'Quest Credit\' - Say Line 0 (Phase 1)'), +(28136, 0, 14, 15, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 53, 0, 28136, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Spellhit \'Quest Credit\' - Start Waypoint Path 28136 (Phase 1)'), +(28136, 0, 15, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 83, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Crusader Jonathan - On Spellhit \'Quest Credit\' - Remove Npc Flags Gossip (Phase 1)'); diff --git a/data/sql/updates/db_world/2025_09_05_01.sql b/data/sql/updates/db_world/2025_09_05_01.sql new file mode 100644 index 000000000..0e87f6295 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_05_01.sql @@ -0,0 +1,5 @@ +-- DB update 2025_09_05_00 -> 2025_09_05_01 +DELETE FROM `creature_template_spell` WHERE `CreatureID` = 21750 AND `Index` IN (2, 3); +INSERT INTO `creature_template_spell` (`CreatureID`, `Index`, `Spell`, `VerifiedBuild`) VALUES +(21750, 2, 37463, 0), +(21750, 3, 37469, 0); diff --git a/data/sql/updates/db_world/2025_09_06_00.sql b/data/sql/updates/db_world/2025_09_06_00.sql new file mode 100644 index 000000000..04d9dde00 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_06_00.sql @@ -0,0 +1,9 @@ +-- DB update 2025_09_05_01 -> 2025_09_06_00 +-- +SET @CGUID:=12891; +DELETE FROM `creature` WHERE (`id1` = 16786) AND (`guid` = (@CGUID)); +INSERT INTO `creature` (`guid`, `id1`, `id2`, `id3`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `equipment_id`, `position_x`, `position_y`, `position_z`, `orientation`, `spawntimesecs`, `wander_distance`, `currentwaypoint`, `curhealth`, `curmana`, `MovementType`, `npcflag`, `unit_flags`, `dynamicflags`, `ScriptName`, `Comment`, `VerifiedBuild`) VALUES +(@CGUID, 16786, 0, 0, 0, 0, 0, 1, 1, 0, -4926.95, -981.718, 501.55, 2.0071299076080322, 120, 0, 0, 1, 0, 0, 0, 0, 0, '', '', 0); + +DELETE FROM `game_event_creature` WHERE `guid` = @CGUID; +INSERT INTO `game_event_creature` (`eventEntry`, `guid`) VALUES(17, @CGUID); diff --git a/data/sql/updates/db_world/2025_09_06_01.sql b/data/sql/updates/db_world/2025_09_06_01.sql new file mode 100644 index 000000000..6830e36d2 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_06_01.sql @@ -0,0 +1,3 @@ +-- DB update 2025_09_06_00 -> 2025_09_06_01 +-- +UPDATE `creature_template` SET `RegenHealth` = 0 WHERE (`entry` IN (16136, 16172)); diff --git a/data/sql/updates/db_world/2025_09_06_02.sql b/data/sql/updates/db_world/2025_09_06_02.sql new file mode 100644 index 000000000..372e87753 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_06_02.sql @@ -0,0 +1,80 @@ +-- DB update 2025_09_06_01 -> 2025_09_06_02 +-- +-- Changes Scorn's spawn from Event 17 (Scourge Invasion) to 120 (Scourge Invasion - Boss in instance activation) +UPDATE `game_event_creature` SET `eventEntry` = 120 WHERE `guid` = 248652; + +-- Adds "FORCE_GOSSIP" for Sever +UPDATE `creature_template` SET `type_flags` = 134217728 WHERE `entry` = 14682; +-- Adds "FORCE_GOSSIP" and changes from "Warrior" to "Paladin" for Balzaphon, Lady Falther'ess and Scorn +UPDATE `creature_template` SET `unit_class` = 2, `type_flags` = 134217728 WHERE `entry` IN (14684, 14686, 14690, 14693); +-- Makes Balzaphon and Revanchion immune to Charge +UPDATE `creature_template` SET `mechanic_immune_mask` = (`mechanic_immune_mask` | 2048) WHERE `entry` IN (14684, 14690); +-- Scorn immune to root +UPDATE `creature_template` SET `mechanic_immune_mask` = (`mechanic_immune_mask` | 64) WHERE `entry` IN (14693); + +-- Adds SAI to Sever, Balzaphon, Lady Falther'ess, Revanchion and Lord Blackwood +UPDATE `creature_template` SET `AIName` = 'SmartAI' WHERE `entry` IN (14682, 14684, 14686, 14695); + +-- Adds Spirit Particles (purple) to Lord Blackwood +UPDATE `creature_template_addon` SET `auras` = '28126' WHERE `entry` = 14695; + +-- Adds Spirit Particles (purple) and Frost Armor to Revanchion +UPDATE `creature_template_addon` SET `auras` = '28126 12556' WHERE `entry` = 14690 ; + +-- Adds SAI to Holding Pen (157819) +UPDATE `gameobject_template` SET `AIName` = 'SmartGameObjectAI' WHERE `entry` = 157819; + +-- Adds Spirit Particles (purple) to Sever, Balzaphon, Revanchion and Scorn +DELETE FROM `creature_template_addon` WHERE `entry` IN (14684, 14686, 14690, 14693, 14682); +INSERT INTO `creature_template_addon` (`entry`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES +(14682, 0, 0, 0, 1, 0, 0, '28126'), -- Sever +(14684, 0, 0, 0, 1, 0, 0, '28126'), -- Balzaphon +(14690, 0, 0, 0, 1, 0, 0, '28126'), -- Revanchion +(14693, 0, 0, 0, 1, 0, 0, '28126'); -- Scorn + +-- Adds Server and Lady Falther'ess texts. +DELETE FROM `creature_text` WHERE `CreatureID` IN (14682, 14686); +INSERT INTO `creature_text` (`CreatureID`, `GroupID`, `ID`, `Text`, `Type`, `Language`, `Probability`, `Emote`, `Duration`, `Sound`, `BroadcastTextId`, `TextRange`, `comment`) VALUES +(14682, 0, 0, '%s goes into a frenzy!', 16, 0, 100, 0, 0, 0, 1191, 0, '[Sever] Sever goes into a frenzy! / %s goes into a frenzy!'), +(14686, 0, 0, 'Thank you for becoming my next victim!', 14, 0, 100, 0, 0, 0, 12429, 0, '[Lady Falther\'ess] Thank you for becoming my next victim!'); + +-- SmartGameObjectAI +-- 157819 (Holding Pen), when the "holding pen" is opened, saves data variable and sends to lady father'ess so it can be used after as a initatior of encounter with the player. +DELETE FROM `smart_scripts` WHERE `source_type` = 1 AND `entryorguid` = 157819; +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 +(157819, 1, 0, 1, 70, 0, 100, 0, 2, 0, 0, 0, 0, 0, 64, 1, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Holding Pen - On Gameobject State Changed - Store Targetlist to Lady Falther\'ess'), +(157819, 1, 1, 2, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 100, 1, 0, 0, 0, 0, 0, 19, 14686, 0, 0, 0, 0, 0, 0, 0, 'Holding Pen - On Gameobject State Changed - Send Target 1 to Lady Falther\'ess'), +(157819, 1, 2, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 45, 1, 1, 0, 0, 0, 0, 19, 14686, 0, 0, 0, 0, 0, 0, 0, 'Holding Pen - On Gameobject State Changed - Set Data 1 1 to Lady Falther\'ess'); + +-- SmartAI +-- Adds SAI logic to Sever, Balzaphon, Lady Falther'ess, Revanchion and Lord Blackwood +DELETE FROM `smart_scripts` WHERE `source_type` = 0 AND `entryorguid` IN (14682, 14684, 14686, 14690, 14695); +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 +-- Sever +(14682, 0, 0, 0, 0, 0, 100, 0, 12000, 31000, 8000, 30000, 0, 0, 11, 17745, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Sever - In Combat - Cast \'Diseased Spit\''), +(14682, 0, 1, 2, 2, 0, 100, 0, 1, 50, 0, 0, 0, 0, 11, 8269, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Sever - Between 1-50% Health - Cast \'Frenzy\''), +(14682, 0, 2, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Sever - Between 1-50% Health - Say Line 0 "Sever goes into a frenzy!"'), +(14682, 0, 3, 0, 101, 0, 100, 0, 2, 10, 12500, 10000, 15000, 0, 11, 16508, 0, 0, 0, 0, 0, 17, 0, 10, 5, 0, 0, 0, 0, 0, 'Sever - On 2 or More Players in Range - Cast \'Intimidating Roar\''), +-- Balzaphon +(14684, 0, 0, 0, 0, 0, 100, 0, 2000, 7000, 2000, 5000, 0, 0, 11, 16799, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Balzaphon - In Combat - Cast \'Frostbolt\''), +(14684, 0, 1, 0, 0, 0, 100, 0, 6000, 12000, 7000, 15000, 0, 0, 11, 15244, 0, 0, 0, 0, 0, 5, 10, 0, 2, 0, 0, 0, 0, 0, 'Balzaphon - In Combat - Cast \'Cone of Cold\''), +(14684, 0, 2, 0, 0, 0, 100, 0, 10000, 20000, 12000, 20000, 0, 0, 11, 8398, 0, 0, 0, 0, 0, 5, 20, 0, 2, 0, 0, 0, 0, 0, 'Balzaphon - In Combat - Cast \'Frostbolt Volley\''), +-- Lady Falther'ess +(14686, 0, 0, 1, 37, 0, 100, 0, 0, 0, 0, 0, 0, 0, 11, 28533, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lady Falther\'ess - On Initialize - Cast \'Transform\' (Salma Saldean)'), +(14686, 0, 1, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 2, 35, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lady Falther\'ess - On Initialize - Set Faction 35 (Friendly)'), +(14686, 0, 2, 3, 38, 0, 100, 0, 1, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lady Falther\'ess - On Data Set 1 1 - Demorph'), +(14686, 0, 3, 4, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lady Falther\'ess - On Data Set 1 1 - Say Line 0 - Thank you for becoming my next victim! '), +(14686, 0, 4, 5, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 2, 21, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lady Falther\'ess - On Data Set 1 1 - Set Faction 21 (Undead, Scourge)'), +(14686, 0, 5, 6, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 49, 0, 0, 0, 0, 0, 0, 21, 30, 0, 0, 0, 0, 0, 0, 0, 'Lady Falther\'ess - On Data Set 1 1 - Start Attacking (Closest Player within 30 yards)'), +(14686, 0, 6, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 75, 28126, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lady Falther\'ess - On Data Set 1 1 - Add Aura \'Spirit Particles (purple)\''), +(14686, 0, 7, 0, 0, 0, 100, 0, 2500, 8000, 10000, 18000, 0, 0, 11, 22743, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Lady Falther\'ess - In Combat - Cast \'Ribbon of Souls\''), +(14686, 0, 8, 0, 0, 0, 100, 0, 17500, 20000, 19000, 22000, 0, 0, 11, 17105, 0, 0, 0, 0, 0, 5, 30, 1, 2, 17105, 0, 0, 0, 0, 'Lady Falther\'ess - In Combat - Cast \'Banshee Curse\''), +(14686, 0, 9, 0, 101, 0, 100, 0, 2, 10, 7500, 5000, 6000, 0, 11, 16838, 0, 0, 5, 0, 0, 17, 0, 5, 5, 0, 0, 0, 0, 0, 'Lady Falther\'ess - On 2 or More Players in Range - Cast \'Banshee Shriek\''), +-- Revanchion +(14690, 0, 0, 0, 0, 0, 100, 0, 10000, 15000, 12500, 14000, 0, 0, 11, 15245, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Revanchion - In Combat - Cast \'Shadow Bolt Volley\''), +(14690, 0, 1, 0, 0, 0, 100, 0, 13000, 16000, 14000, 18000, 0, 0, 11, 14907, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Revanchion - In Combat - Cast \'Frost Nova\''), +-- Lord Blackwood +(14695, 0, 0, 0, 0, 0, 100, 0, 8000, 16000, 20000, 20000, 0, 0, 11, 7964, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Blackwood - In Combat - Cast \'Smoke Bomb\''), +(14695, 0, 1, 0, 105, 0, 100, 0, 10000, 12000, 10000, 12000, 0, 5, 11, 11972, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Blackwood - On Hostile Casting in Range - Cast \'Shield Bash\''), +(14695, 0, 2, 0, 110, 0, 100, 0, 2000, 20000, 20000, 20000, 0, 1, 11, 20733, 64, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Blackwood - On Melee Range Target - Cast \'Black Arrow\''), +(14695, 0, 3, 0, 110, 0, 100, 0, 0, 0, 2400, 2400, 0, 1, 11, 16496, 64, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Lord Blackwood - On Melee Range Target - Cast \'Shoot\''); diff --git a/data/sql/updates/db_world/2025_09_06_03.sql b/data/sql/updates/db_world/2025_09_06_03.sql new file mode 100644 index 000000000..3bac2621c --- /dev/null +++ b/data/sql/updates/db_world/2025_09_06_03.sql @@ -0,0 +1,30 @@ +-- DB update 2025_09_06_02 -> 2025_09_06_03 +-- Remove heroic casts as it is already handled by spelldifficulty_dbc and add on aggro engage +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 28729); +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 +(28729, 0, 0, 0, 0, 0, 100, 0, 2000, 6000, 15000, 20000, 0, 0, 11, 52524, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Narjil - In Combat - Cast \'Blinding Webs\''), +(28729, 0, 2, 0, 0, 0, 100, 0, 6000, 15000, 20000, 25000, 0, 0, 11, 52086, 0, 0, 0, 0, 0, 5, 30, 0, 0, 0, 0, 0, 0, 0, 'Watcher Narjil - In Combat - Cast \'Web Wrap\''), +(28729, 0, 3, 0, 0, 0, 100, 0, 4000, 12000, 9000, 15000, 0, 0, 11, 52469, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Narjil - In Combat - Cast \'Infected Bite\''), +(28729, 0, 5, 0, 8, 0, 100, 0, 52343, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Narjil - On Spellhit \'Krik`Thir Subboss Aggro Trigger\' - Set In Combat With Zone'), +(28729, 0, 6, 0, 0, 0, 100, 1, 500, 500, 0, 0, 0, 0, 39, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Narjil - In Combat - Call For Help (No Repeat)'), +(28729, 0, 1, 0, 4, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 1, 0, 0, 0, 0, 0, 205, 0, 1, 0, 0, 0, 0, 0, 0, 'Watcher Narjil - On Aggro - Do Action ID 1'); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 28730); +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 +(28730, 0, 0, 0, 0, 0, 100, 0, 2000, 6000, 15000, 20000, 0, 0, 11, 52470, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Gashra - In Combat - Cast \'Enrage\''), +(28730, 0, 1, 0, 0, 0, 100, 0, 6000, 15000, 20000, 25000, 0, 0, 11, 52086, 0, 0, 0, 0, 0, 5, 30, 0, 0, 0, 0, 0, 0, 0, 'Watcher Gashra - In Combat - Cast \'Web Wrap\''), +(28730, 0, 2, 0, 0, 0, 100, 0, 4000, 12000, 9000, 15000, 0, 0, 11, 52469, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Gashra - In Combat - Cast \'Infected Bite\''), +(28730, 0, 4, 0, 8, 0, 100, 0, 52343, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Gashra - On Spellhit \'Krik`Thir Subboss Aggro Trigger\' - Set In Combat With Zone'), +(28730, 0, 5, 0, 0, 0, 100, 1, 500, 500, 0, 0, 0, 0, 39, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Gashra - In Combat - Call For Help (No Repeat)'), +(28730, 0, 3, 0, 4, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 1, 0, 0, 0, 0, 0, 205, 0, 1, 0, 0, 0, 0, 0, 0, 'Watcher Gashra - On Aggro - Do Action ID 1'); + +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 28731); +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 +(28731, 0, 0, 0, 0, 0, 100, 0, 2000, 6000, 15000, 20000, 0, 0, 11, 52493, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Silthik - In Combat - Cast \'Poison Spray\''), +(28731, 0, 2, 0, 0, 0, 100, 0, 6000, 15000, 20000, 25000, 0, 0, 11, 52086, 0, 0, 0, 0, 0, 5, 30, 0, 0, 0, 0, 0, 0, 0, 'Watcher Silthik - In Combat - Cast \'Web Wrap\''), +(28731, 0, 3, 0, 0, 0, 100, 0, 4000, 12000, 9000, 15000, 0, 0, 11, 52469, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Silthik - In Combat - Cast \'Infected Bite\''), +(28731, 0, 5, 0, 8, 0, 100, 0, 52343, 0, 0, 0, 0, 0, 38, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Silthik - On Spellhit \'Krik`Thir Subboss Aggro Trigger\' - Set In Combat With Zone'), +(28731, 0, 6, 0, 0, 0, 100, 1, 500, 500, 0, 0, 0, 0, 39, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Watcher Silthik - In Combat - Call For Help (No Repeat)'), +(28731, 0, 1, 0, 4, 0, 100, 0, 0, 0, 0, 0, 0, 0, 223, 1, 0, 0, 0, 0, 0, 205, 0, 1, 0, 0, 0, 0, 0, 0, 'Watcher Silthik - On Aggro - Do Action ID 1'); + +UPDATE `creature_template` SET `mechanic_immune_mask` = `mechanic_immune_mask` &~ 33554432 WHERE `entry` IN (28684, 31612); diff --git a/data/sql/updates/db_world/2025_09_06_04.sql b/data/sql/updates/db_world/2025_09_06_04.sql new file mode 100644 index 000000000..aef37356c --- /dev/null +++ b/data/sql/updates/db_world/2025_09_06_04.sql @@ -0,0 +1,13 @@ +-- DB update 2025_09_06_03 -> 2025_09_06_04 + +-- Arzeth the Merciless (Charm, Fear, Root, Snare, Banish, Horror) +UPDATE `creature_template` SET `mechanic_immune_mask` = `mechanic_immune_mask` |1|16|64|1024|131072|8388608 WHERE (`entry` = 19354); + +-- Illidari Dreadlord (Charm, Fear, Snare) +UPDATE `creature_template` SET `mechanic_immune_mask` = `mechanic_immune_mask` |1|16|1024 WHERE (`entry` = 21166); + +-- Wrath Master (Charm, Snare) +UPDATE `creature_template` SET `mechanic_immune_mask` = `mechanic_immune_mask` |1|1024 WHERE (`entry` = 19005); + +-- Arazzius the Cruel (Charm, Fear, Root, Snare, Stun, Freeze, Polymorph, Banish) +UPDATE `creature_template` SET `mechanic_immune_mask` = `mechanic_immune_mask` |1|16|64|1024|2048|4096|65536|131072 WHERE (`entry` = 19191); diff --git a/data/sql/updates/db_world/2025_09_06_05.sql b/data/sql/updates/db_world/2025_09_06_05.sql new file mode 100644 index 000000000..fea71f267 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_06_05.sql @@ -0,0 +1,13 @@ +-- DB update 2025_09_06_04 -> 2025_09_06_05 +-- Update gameobject 'Doodad_FrostGiantIceShard' with sniffed values +-- updated spawns +DELETE FROM `gameobject` WHERE (`id` IN (192193, 192194, 192195)) AND (`guid` IN (20924, 20925, 20926)); +INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `position_x`, `position_y`, `position_z`, `orientation`, `rotation0`, `rotation1`, `rotation2`, `rotation3`, `spawntimesecs`, `animprogress`, `state`, `ScriptName`, `VerifiedBuild`, `Comment`) VALUES +(20924, 192193, 571, 0, 0, 1, 4, 7325.5732421875, -2044.4727783203125, 760.7373046875, 5.478795528411865234, 0.056999683380126953, 0.307971954345703125, -0.35146713256835937, 0.882255733013153076, 120, 255, 1, "", 46158, NULL), +(20925, 192194, 571, 0, 0, 1, 4, 7320.685546875, -2053.649169921875, 761.33929443359375, 5.478795528411865234, 0.056999683380126953, 0.307971954345703125, -0.35146713256835937, 0.882255733013153076, 120, 255, 1, "", 46158, NULL), +(20926, 192195, 571, 0, 0, 1, 4, 7321.001953125, -2054.294677734375, 760.8995361328125, 4.88195037841796875, 0.128789901733398437, -0.01092052459716796, -0.64533519744873046, 0.752885341644287109, 120, 255, 1, "", 46158, NULL); + +-- new spawns +DELETE FROM `gameobject` WHERE (`id` IN (192186)) AND (`guid` IN (40)); +INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `position_x`, `position_y`, `position_z`, `orientation`, `rotation0`, `rotation1`, `rotation2`, `rotation3`, `spawntimesecs`, `animprogress`, `state`, `ScriptName`, `VerifiedBuild`, `Comment`) VALUES +(40, 192186, 571, 0, 0, 1, 4, 7299.98193359375, -2056.776123046875, 760.91754150390625, 3.129810810089111328, 0.336331367492675781, 0.04759979248046875, -0.9404611587524414, 0.012177699245512485, 120, 255, 1, "", 46158, NULL); diff --git a/data/sql/updates/db_world/2025_09_07_00.sql b/data/sql/updates/db_world/2025_09_07_00.sql new file mode 100644 index 000000000..2162949fb --- /dev/null +++ b/data/sql/updates/db_world/2025_09_07_00.sql @@ -0,0 +1,16 @@ +-- DB update 2025_09_06_05 -> 2025_09_07_00 +-- +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 22) AND (`SourceGroup` = 1) AND (`SourceEntry` = 27409) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 32) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 16) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(22, 1, 27409, 0, 0, 32, 0, 16, 0, 0, 0, 0, 0, '', 'Ducal\'s horse only run sai if boarding passenger is player'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 2740900) AND (`source_type` = 9) 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 +(2740900, 9, 0, 0, 0, 0, 100, 512, 3000, 3000, 0, 0, 0, 0, 53, 1, 27409, 0, 12308, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Ducal\'s Horse - On Script - Start Waypoint'); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 27409) AND (`source_type` = 0) AND (`id` IN (1, 8, 9, 10)); +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 +(27409, 0, 1, 10, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Ducal\'s Horse - On Passenger Boarded - Set Reactstate Passive'), +(27409, 0, 8, 0, 25, 0, 100, 0, 0, 0, 0, 0, 0, 0, 67, 1, 10000, 10000, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Ducal\'s Horse - On Reset - Create Timed Event'), +(27409, 0, 9, 0, 59, 0, 100, 0, 1, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Ducal\'s Horse - On Timed Event 1 Triggered - Despawn Instant'), +(27409, 0, 10, 0, 61, 0, 100, 0, 0, 0, 0, 0, 0, 0, 74, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Ducal\'s Horse - On Passenger Boarded - Remove Timed Event 1'); diff --git a/data/sql/updates/db_world/2025_09_09_00.sql b/data/sql/updates/db_world/2025_09_09_00.sql new file mode 100644 index 000000000..a16856bd5 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_09_00.sql @@ -0,0 +1,4 @@ +-- DB update 2025_09_07_00 -> 2025_09_09_00 +-- +DELETE FROM `spell_script_names` WHERE `spell_id`=45612; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (45612, 'spell_necropolis_beam'); diff --git a/data/sql/updates/db_world/2025_09_09_01.sql b/data/sql/updates/db_world/2025_09_09_01.sql new file mode 100644 index 000000000..38d8b7a40 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_09_01.sql @@ -0,0 +1,36 @@ +-- DB update 2025_09_09_00 -> 2025_09_09_01 +-- Update gameobject 'Meat Wagon' with sniffed values +-- updated spawns +DELETE FROM `gameobject` WHERE (`id` IN (193616, 193618, 193620)) AND (`guid` IN (62425, 62426, 62430, 62435, 62436, 62437)); +INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `position_x`, `position_y`, `position_z`, `orientation`, `rotation0`, `rotation1`, `rotation2`, `rotation3`, `spawntimesecs`, `animprogress`, `state`, `ScriptName`, `VerifiedBuild`, `Comment`) VALUES +(62425, 193616, 571, 0, 0, 1, 1, 6879.06787109375, 1607.7725830078125, 388.887359619140625, 3.612837791442871093, 0, 0, -0.97236919403076171, 0.233448356389999389, 120, 255, 1, "", 46368, NULL), +(62426, 193616, 571, 0, 0, 1, 1, 6883.10888671875, 1600.4949951171875, 389.033172607421875, 0.506144583225250244, 0, 0, 0.250379562377929687, 0.968147754669189453, 120, 255, 1, "", 46368, NULL), +(62430, 193618, 571, 0, 0, 1, 1, 6878.4189453125, 1602.437744140625, 389.033172607421875, 0.471238493919372558, 0, 0, 0.233445167541503906, 0.972369968891143798, 120, 255, 1, "", 46368, NULL), +(62435, 193620, 571, 0, 0, 1, 1, 6881.7578125, 1604.779541015625, 388.380401611328125, 3.612837791442871093, 0, 0, -0.97236919403076171, 0.233448356389999389, 120, 255, 1, "", 46368, NULL), +(62436, 193620, 571, 0, 0, 1, 1, 6896.3095703125, 1609.5350341796875, 388.3387451171875, 4.049167633056640625, 0, 0, -0.89879322052001953, 0.438372820615768432, 120, 255, 1, "", 46368, NULL), +(62437, 193620, 571, 0, 0, 1, 1, 6886.94091796875, 1619.73095703125, 388.269287109375, 2.007128477096557617, 0, 0, 0.84339141845703125, 0.537299633026123046, 120, 255, 1, "", 46368, NULL); + +-- new spawns +DELETE FROM `gameobject` WHERE (`id` IN (193616, 193617, 193618, 193619, 193620)) AND (`guid` BETWEEN 2211 AND 2231); +INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `position_x`, `position_y`, `position_z`, `orientation`, `rotation0`, `rotation1`, `rotation2`, `rotation3`, `spawntimesecs`, `animprogress`, `state`, `ScriptName`, `VerifiedBuild`, `Comment`) VALUES +(2211, 193616, 571, 0, 0, 1, 1, 6876.56005859375, 1539.6214599609375, 389.033172607421875, 6.213373661041259765, 0, 0, -0.03489875793457031, 0.999390840530395507, 120, 255, 1, "", 46368, NULL), +(2212, 193616, 571, 0, 0, 1, 1, 6880.126953125, 1540.478759765625, 389.033172607421875, 3.45575571060180664, 0, 0, -0.98768806457519531, 0.156436234712600708, 120, 255, 1, "", 46368, NULL), +(2213, 193616, 571, 0, 0, 1, 1, 6883.654296875, 1540.2552490234375, 389.033172607421875, 1.134462952613830566, 0, 0, 0.537299156188964843, 0.843391716480255126, 120, 255, 1, "", 46368, NULL), +(2214, 193616, 571, 0, 0, 1, 1, 6884.5859375, 1573.8497314453125, 389.033172607421875, 0.052358884364366531, 0, 0, 0.02617645263671875, 0.999657332897186279, 120, 255, 1, "", 46368, NULL), +(2215, 193616, 571, 0, 0, 1, 1, 6884.59228515625, 1582.0037841796875, 389.033172607421875, 3.159062385559082031, 0, 0, -0.99996185302734375, 0.008734640665352344, 120, 255, 1, "", 46368, NULL), +(2216, 193616, 571, 0, 0, 1, 1, 6888.81982421875, 1541.5426025390625, 389.033172607421875, 3.036838293075561523, 0, 0, 0.998628616333007812, 0.052353221923112869, 120, 255, 1, "", 46368, NULL), +(2217, 193617, 571, 0, 0, 1, 1, 6875.7060546875, 1601.95068359375, 389.033172607421875, 3.612837791442871093, 0, 0, -0.97236919403076171, 0.233448356389999389, 120, 255, 1, "", 46368, NULL), +(2218, 193617, 571, 0, 0, 1, 1, 6879.490234375, 1578.3424072265625, 389.033172607421875, 3.176533222198486328, 0, 0, -0.999847412109375, 0.017469281330704689, 120, 255, 1, "", 46368, NULL), +(2219, 193617, 571, 0, 0, 1, 1, 6903.123046875, 1554.924072265625, 389.033172607421875, 2.286378860473632812, 0, 0, 0.909960746765136718, 0.414694398641586303, 120, 255, 1, "", 46368, NULL), +(2220, 193617, 571, 0, 0, 1, 1, 6907.595703125, 1567.492919921875, 389.033172607421875, 6.161012649536132812, 0, 0, -0.06104850769042968, 0.998134791851043701, 120, 255, 1, "", 46368, NULL), +(2221, 193617, 571, 0, 0, 1, 1, 6912.046875, 1560.1182861328125, 389.033203125, 1.291541695594787597, 0, 0, 0.60181427001953125, 0.798636078834533691, 120, 255, 1, "", 46368, NULL), +(2222, 193618, 571, 0, 0, 1, 1, 6881.86181640625, 1578.113037109375, 389.033172607421875, 0.052358884364366531, 0, 0, 0.02617645263671875, 0.999657332897186279, 120, 255, 1, "", 46368, NULL), +(2223, 193618, 571, 0, 0, 1, 1, 6906.19384765625, 1579.285400390625, 389.033172607421875, 0.610863447189331054, 0, 0, 0.3007049560546875, 0.953717231750488281, 120, 255, 1, "", 46368, NULL), +(2224, 193618, 571, 0, 0, 1, 1, 6906.30126953125, 1589.4271240234375, 389.033172607421875, 1.151916384696960449, 0, 0, 0.544638633728027343, 0.838670849800109863, 120, 255, 1, "", 46368, NULL), +(2225, 193619, 571, 0, 0, 1, 1, 6872.22021484375, 1602.7569580078125, 389.033172607421875, 3.682650327682495117, 0, 0, -0.96362972259521484, 0.26724100112915039, 120, 255, 1, "", 46368, NULL), +(2226, 193619, 571, 0, 0, 1, 1, 6872.970703125, 1600.5621337890625, 389.033172607421875, 3.665196180343627929, 0, 0, -0.96592521667480468, 0.258821308612823486, 120, 255, 1, "", 46368, NULL), +(2227, 193619, 571, 0, 0, 1, 1, 6874.19140625, 1598.2149658203125, 389.033172607421875, 3.665196180343627929, 0, 0, -0.96592521667480468, 0.258821308612823486, 120, 255, 1, "", 46368, NULL), +(2228, 193619, 571, 0, 0, 1, 1, 6875.76220703125, 1577.950927734375, 389.033172607421875, 3.22885894775390625, 0, 0, -0.99904823303222656, 0.043619260191917419, 120, 255, 1, "", 46368, NULL), +(2229, 193619, 571, 0, 0, 1, 1, 6875.81689453125, 1580.9786376953125, 389.033172607421875, 3.106652259826660156, 0, 0, 0.999847412109375, 0.017469281330704689, 120, 255, 1, "", 46368, NULL), +(2230, 193619, 571, 0, 0, 1, 1, 6876.02099609375, 1575.36083984375, 389.033172607421875, 3.333590030670166015, 0, 0, -0.99539566040039062, 0.095851235091686248, 120, 255, 1, "", 46368, NULL), +(2231, 193620, 571, 0, 0, 1, 1, 6885.62060546875, 1578.173583984375, 388.435943603515625, 3.176533222198486328, 0, 0, -0.999847412109375, 0.017469281330704689, 120, 255, 1, "", 46368, NULL); diff --git a/data/sql/updates/db_world/2025_09_09_02.sql b/data/sql/updates/db_world/2025_09_09_02.sql new file mode 100644 index 000000000..3ddeed53e --- /dev/null +++ b/data/sql/updates/db_world/2025_09_09_02.sql @@ -0,0 +1,8 @@ +-- DB update 2025_09_09_01 -> 2025_09_09_02 +-- Update gameobject 'Death's Gaze Orb' with sniffed values +-- new spawns +DELETE FROM `gameobject` WHERE (`id` IN (192917)) AND (`guid` IN (174, 175, 176)); +INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `position_x`, `position_y`, `position_z`, `orientation`, `rotation0`, `rotation1`, `rotation2`, `rotation3`, `spawntimesecs`, `animprogress`, `state`, `ScriptName`, `VerifiedBuild`, `Comment`) VALUES +(174, 192917, 571, 0, 0, 1, 1, 6475.67822265625, 3399.67529296875, 599.08349609375, 2.059488296508789062, 0, 0, 0.857167243957519531, 0.515038192272186279, 120, 255, 1, "", 46368, NULL), +(175, 192917, 571, 0, 0, 1, 1, 6514.7314453125, 3273.22216796875, 667.54388427734375, 3.926995515823364257, 0, 0, -0.92387866973876953, 0.38268551230430603, 120, 255, 1, "", 46368, NULL), +(176, 192917, 571, 0, 0, 1, 1, 6705.81982421875, 3528.986328125, 673.74957275390625, 0.715584874153137207, 0, 0, 0.350207328796386718, 0.936672210693359375, 120, 255, 1, "", 46368, NULL); diff --git a/data/sql/updates/db_world/2025_09_09_03.sql b/data/sql/updates/db_world/2025_09_09_03.sql new file mode 100644 index 000000000..e1be42dfc --- /dev/null +++ b/data/sql/updates/db_world/2025_09_09_03.sql @@ -0,0 +1,25 @@ +-- DB update 2025_09_09_02 -> 2025_09_09_03 +-- +DELETE FROM `spell_script_names` WHERE `spell_id`=48297; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES (48297, 'spell_handover_reins'); + +DELETE FROM `vehicle_seat_addon` WHERE `SeatEntry`=742; +INSERT INTO `vehicle_seat_addon` (`SeatEntry`, `SeatOrientation`, `ExitParamX`, `ExitParamY`, `ExitParamZ`, `ExitParamO`, `ExitParamValue`) VALUES +(742, 0, 0, -2, 0, 0, 1); + +-- Set Phase from 5 to 1 for id 2 +-- Generate comments with Keira +DELETE FROM `smart_scripts` WHERE (`source_type` = 0 AND `entryorguid` = 27213); +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 +(27213, 0, 0, 1, 11, 0, 100, 512, 0, 0, 0, 0, 0, 0, 211, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Respawn - Flag reset 0'), +(27213, 0, 1, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 22, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Respawn - Set Event Phase 1'), +(27213, 0, 2, 3, 28, 1, 100, 512, 0, 0, 0, 0, 0, 0, 22, 2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Passenger Removed - Set Event Phase 2 (Phase 1)'), +(27213, 0, 3, 4, 61, 2, 100, 512, 0, 0, 0, 0, 0, 0, 101, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Passenger Removed - Set Home Position (Phase 1)'), +(27213, 0, 4, 5, 61, 2, 100, 512, 0, 0, 0, 0, 0, 0, 2, 35, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Passenger Removed - Set Faction 35 (Phase 1)'), +(27213, 0, 5, 6, 61, 2, 100, 512, 0, 0, 0, 0, 0, 0, 18, 131072, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Passenger Removed - Set Flags Pacified (Phase 1)'), +(27213, 0, 6, 0, 61, 2, 100, 512, 0, 0, 0, 0, 0, 0, 80, 2721300, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Passenger Removed - Run Script (Phase 1)'), +(27213, 0, 7, 0, 59, 2, 100, 512, 1, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Timed Event 1 Triggered - Despawn Instant (Phase 2)'), +(27213, 0, 8, 9, 23, 2, 100, 512, 48290, 1, 500, 500, 0, 0, 74, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Aura \'Onslaught Riding Crop\' - Remove Timed Event 1 (Phase 2)'), +(27213, 0, 9, 0, 61, 2, 100, 512, 0, 0, 0, 0, 0, 0, 22, 3, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Aura \'Onslaught Riding Crop\' - Set Event Phase 3 (Phase 2)'), +(27213, 0, 10, 11, 31, 4, 100, 513, 48297, 0, 0, 0, 0, 0, 22, 4, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Target Spellhit \'Hand Over Reins\' - Set Event Phase 4 (Phase 3) (No Repeat)'), +(27213, 0, 11, 0, 61, 8, 100, 512, 0, 0, 0, 0, 0, 0, 80, 2721301, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'Onslaught Warhorse - On Target Spellhit \'Hand Over Reins\' - Run Script (Phase 3) (No Repeat)'); diff --git a/data/sql/updates/db_world/2025_09_09_04.sql b/data/sql/updates/db_world/2025_09_09_04.sql new file mode 100644 index 000000000..9b1aba172 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_09_04.sql @@ -0,0 +1,3 @@ +-- DB update 2025_09_09_03 -> 2025_09_09_04 +UPDATE `creature_template_addon` SET `auras` = '19818 34623' WHERE `entry` = 18733; +UPDATE `creature_addon` SET `visibilityDistanceType` = 5, `auras` = '19818 34623' WHERE `guid` IN (67001, 203341); diff --git a/data/sql/updates/db_world/2025_09_09_05.sql b/data/sql/updates/db_world/2025_09_09_05.sql new file mode 100644 index 000000000..ff68f7f4b --- /dev/null +++ b/data/sql/updates/db_world/2025_09_09_05.sql @@ -0,0 +1,7 @@ +-- DB update 2025_09_09_04 -> 2025_09_09_05 +-- +UPDATE `creature_template` SET `AIName` = '' WHERE `entry` = 29978; + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 29984) AND (`source_type` = 0) AND (`id` = 0); + +DELETE FROM `smart_scripts` WHERE (`entryorguid` = 29978) AND (`source_type` = 0); diff --git a/data/sql/updates/db_world/2025_09_10_00.sql b/data/sql/updates/db_world/2025_09_10_00.sql new file mode 100644 index 000000000..ca05e35c1 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_10_00.sql @@ -0,0 +1,6 @@ +-- DB update 2025_09_09_05 -> 2025_09_10_00 +-- +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 18) AND (`SourceGroup` = 25334) AND (`SourceEntry` IN (47917, 46598)) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 9) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 11652) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(18, 25334, 47917, 0, 0, 9, 0, 11652, 0, 0, 0, 0, 0, '', 'Horde Siege Tank requires player to be on quest The Plains of Nasam'), +(18, 25334, 46598, 0, 0, 9, 0, 11652, 0, 0, 0, 0, 0, '', 'Horde Siege Tank requires player to be on quest The Plains of Nasam'); diff --git a/data/sql/updates/db_world/2025_09_10_01.sql b/data/sql/updates/db_world/2025_09_10_01.sql new file mode 100644 index 000000000..60a729439 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_10_01.sql @@ -0,0 +1,51 @@ +-- DB update 2025_09_10_00 -> 2025_09_10_01 +-- Update gameobject 'Weapon Rack' with sniffed values +-- updated spawns +DELETE FROM `gameobject` WHERE (`id` IN (181627, 183269, 183991, 105172, 105171, 105170, 105169, 188659)) AND (`guid` IN (11420, 24123, 24845, 24846, 45165, 45166, 45167, 45168, 61365, 61366, 61367, 61368, 61369, 61370, 61371, 61372, 61373, 61374, 61375, 61376, 61377, 61378, 61379, 61380, 61381, 61382, 61383, 61384, 61385, 61386)); +INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `position_x`, `position_y`, `position_z`, `orientation`, `rotation0`, `rotation1`, `rotation2`, `rotation3`, `spawntimesecs`, `animprogress`, `state`, `ScriptName`, `VerifiedBuild`, `Comment`) VALUES +(11420, 181627, 0, 0, 0, 1, 1, -6335.33544921875, -3115.09375, 299.723052978515625, 0.907570242881774902, 0, 0, 0.438370704650878906, 0.898794233798980712, 120, 255, 1, "", 48120, NULL), +(24123, 183269, 530, 0, 0, 1, 1, 2228.34716796875, 2312.203125, 89.11359405517578125, 2.321285009384155273, -0.00742387771606445, -0.15768909454345703, 0.902186393737792968, 0.401420950889587402, 120, 255, 1, "", 45704, NULL), +(24845, 183991, 530, 0, 0, 1, 1, -596.56427001953125, 2914.64013671875, 59.21495437622070312, 3.543023586273193359, 0, 0, -0.97992420196533203, 0.199370384216308593, 120, 255, 1, "", 45854, NULL), +(24846, 183991, 530, 0, 0, 1, 1, -584.88446044921875, 2896.530029296875, 59.20400619506835937, 3.700104713439941406, 0, 0, -0.96126079559326171, 0.275640487670898437, 120, 255, 1, "", 45854, NULL), +(45165, 105172, 0, 0, 0, 1, 1, 3023.028564453125, 653.81402587890625, 75.34989166259765625, 1.553341388702392578, 0, 0, 0.700908660888671875, 0.713251054286956787, 120, 255, 1, "", 46779, NULL), +(45166, 105171, 0, 0, 0, 1, 1, 3019.9033203125, 688.79742431640625, 66.45070648193359375, 4.747295856475830078, 0, 0, -0.69465827941894531, 0.719339847564697265, 120, 255, 1, "", 46779, NULL), +(45167, 105170, 0, 0, 0, 1, 1, 3063.673583984375, 697.9215087890625, 66.45069122314453125, 3.211419343948364257, 0, 0, -0.9993906021118164, 0.034906134009361267, 120, 255, 1, "", 46779, NULL), +(45168, 105169, 0, 0, 0, 1, 1, 3058.616455078125, 653.5849609375, 58.1085205078125, 3.996806621551513671, 0, 0, -0.90996074676513671, 0.414694398641586303, 120, 255, 1, "", 46779, NULL), +(61365, 188659, 571, 0, 0, 1, 1, 2953.047119140625, -451.505218505859375, 140.7652130126953125, 5.794494152069091796, 0, 0, -0.24192142486572265, 0.970295846462249755, 120, 255, 1, "", 46158, NULL), +(61366, 188659, 571, 0, 0, 1, 1, 2832.775390625, -279.4259033203125, 136.0123291015625, 2.426007747650146484, 0, 0, 0.936672210693359375, 0.350207358598709106, 120, 255, 1, "", 45942, NULL), +(61367, 188659, 571, 0, 0, 1, 1, 2741.550537109375, -114.055442810058593, 115.7203216552734375, 6.195919513702392578, 0, 0, -0.04361915588378906, 0.999048233032226562, 120, 255, 1, "", 46158, NULL), +(61368, 188659, 571, 0, 0, 1, 1, 2834.82958984375, -531.0296630859375, 121.3561477661132812, 4.48549652099609375, 0, 0, -0.7826080322265625, 0.622514784336090087, 120, 255, 1, "", 45942, NULL), +(61369, 188659, 571, 0, 0, 1, 1, 2782.08203125, -188.334426879882812, 139.139404296875, 3.577930212020874023, 0, 0, -0.97629547119140625, 0.216442063450813293, 120, 255, 1, "", 46158, NULL), +(61370, 188659, 571, 0, 0, 1, 1, 2809.780029296875, -324.545013427734375, 130.2048797607421875, 3.78736734390258789, 0, 0, -0.94832324981689453, 0.317305892705917358, 120, 255, 1, "", 46158, NULL), +(61371, 188659, 571, 0, 0, 1, 1, 2928.7626953125, -353.397918701171875, 112.4615936279296875, 2.932138919830322265, 0, 0, 0.994521141052246093, 0.104535527527332305, 120, 255, 1, "", 45942, NULL), +(61372, 188659, 571, 0, 0, 1, 1, 2878.55908203125, -431.913116455078125, 118.3675765991210937, 3.9793548583984375, 0, 0, -0.9135446548461914, 0.406738430261611938, 120, 255, 1, "", 45942, NULL), +(61373, 188659, 571, 0, 0, 1, 1, 2883.435546875, -373.7969970703125, 112.4618301391601562, 3.9793548583984375, 0, 0, -0.9135446548461914, 0.406738430261611938, 120, 255, 1, "", 46158, NULL), +(61374, 188659, 571, 0, 0, 1, 1, 2858.78125, -276.4066162109375, 114.0344467163085937, 1.553341388702392578, 0, 0, 0.700908660888671875, 0.713251054286956787, 120, 255, 1, "", 45942, NULL), +(61375, 188659, 571, 0, 0, 1, 1, 2864.294189453125, -278.00445556640625, 122.8568801879882812, 1.570795774459838867, 0, 0, 0.707106590270996093, 0.707106947898864746, 120, 255, 1, "", 45942, NULL), +(61376, 188659, 571, 0, 0, 1, 1, 2883.530029296875, -296.61376953125, 114.0345230102539062, 3.071766138076782226, 0, 0, 0.999390602111816406, 0.034906134009361267, 120, 255, 1, "", 46158, NULL), +(61377, 188659, 571, 0, 0, 1, 1, 2891.976318359375, -294.088714599609375, 122.85626220703125, 3.106652259826660156, 0, 0, 0.999847412109375, 0.017469281330704689, 120, 255, 1, "", 45942, NULL), +(61378, 188659, 571, 0, 0, 1, 1, 2889.322998046875, -290.53131103515625, 106.8801422119140625, 3.106652259826660156, 0, 0, 0.999847412109375, 0.017469281330704689, 120, 255, 1, "", 45942, NULL), +(61379, 188659, 571, 0, 0, 1, 1, 2901.346923828125, -320.123565673828125, 114.0344924926757812, 1.535889506340026855, 0, 0, 0.694658279418945312, 0.719339847564697265, 120, 255, 1, "", 45942, NULL), +(61380, 188659, 571, 0, 0, 1, 1, 2912.576416015625, -281.989471435546875, 138.0604248046875, 3.124123096466064453, 0, 0, 0.99996185302734375, 0.008734640665352344, 120, 255, 1, "", 45942, NULL), +(61381, 188659, 571, 0, 0, 1, 1, 2968.82080078125, -444.152923583984375, 125.9091415405273437, 1.780233979225158691, 0, 0, 0.7771453857421875, 0.629321098327636718, 120, 255, 1, "", 45942, NULL), +(61382, 188659, 571, 0, 0, 1, 1, 2942.835205078125, -348.2596435546875, 114.6573333740234375, 4.555310726165771484, 0, 0, -0.76040554046630859, 0.649448513984680175, 120, 255, 1, "", 45942, NULL), +(61383, 188659, 571, 0, 0, 1, 1, 2929.404052734375, -332.448272705078125, 113.43121337890625, 4.572763919830322265, 0, 0, -0.75470924377441406, 0.656059443950653076, 120, 255, 1, "", 45942, NULL), +(61384, 188659, 571, 0, 0, 1, 1, 2757.970458984375, -180.418655395507812, 138.998748779296875, 0.959929943084716796, 0, 0, 0.461748123168945312, 0.887011110782623291, 120, 255, 1, "", 46158, NULL), +(61385, 188659, 571, 0, 0, 1, 1, 2697.780517578125, -200.24945068359375, 140.154632568359375, 1.570795774459838867, 0, 0, 0.707106590270996093, 0.707106947898864746, 120, 255, 1, "", 46158, NULL), +(61386, 188659, 571, 0, 0, 1, 1, 2731.54833984375, -241.892257690429687, 141.5568389892578125, 2.199114561080932617, 0, 0, 0.8910064697265625, 0.453990638256072998, 120, 255, 1, "", 46158, NULL); + +-- new spawns +DELETE FROM `gameobject` WHERE (`id` IN (188426, 188659)) AND (`guid` BETWEEN 939 AND 947); +INSERT INTO `gameobject` (`guid`, `id`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `position_x`, `position_y`, `position_z`, `orientation`, `rotation0`, `rotation1`, `rotation2`, `rotation3`, `spawntimesecs`, `animprogress`, `state`, `ScriptName`, `VerifiedBuild`, `Comment`) VALUES +(939, 188426, 571, 0, 0, 1, 1, 2608.928955078125, 5263.6396484375, 39.4164886474609375, 1.239183306694030761, 0, 0, 0.580702781677246093, 0.814115643501281738, 120, 255, 1, "", 49345, NULL), +(940, 188426, 571, 0, 0, 1, 1, 2611.096923828125, 5260.75732421875, 39.40889358520507812, 2.164205789566040039, 0, 0, 0.882946968078613281, 0.469472706317901611, 120, 255, 1, "", 49345, NULL), +(941, 188426, 571, 0, 0, 1, 1, 2614.411376953125, 5258.751953125, 39.40840911865234375, 3.665196180343627929, 0, 0, -0.96592521667480468, 0.258821308612823486, 120, 255, 1, "", 49345, NULL), +(942, 188426, 571, 0, 0, 1, 1, 2615.94189453125, 5267.6845703125, 39.4207000732421875, 5.462882041931152343, 0, 0, -0.39874839782714843, 0.917060375213623046, 120, 255, 1, "", 49345, NULL), +(943, 188426, 571, 0, 0, 1, 1, 2623.84375, 5255.21875, 38.28279876708984375, 0.750490784645080566, 0, 0, 0.3665008544921875, 0.93041771650314331, 120, 255, 1, "", 49345, NULL), +(944, 188659, 571, 0, 0, 1, 1, 2444.703125, -394.670135498046875, 7.966825008392333984, 0.034906249493360519, 0, 0, 0.017452239990234375, 0.999847710132598876, 120, 255, 1, "", 46158, NULL), +(945, 188659, 571, 0, 0, 1, 1, 2477.215576171875, -350.638916015625, 1.422237038612365722, 0.820303261280059814, 0, 0, 0.398748397827148437, 0.917060375213623046, 120, 255, 1, "", 46158, NULL), +(946, 188659, 571, 0, 0, 1, 1, 2622.64111328125, -264.823455810546875, 2.252971887588500976, 4.066620349884033203, 0, 0, -0.89493370056152343, 0.44619917869567871, 120, 255, 1, "", 45854, NULL), +(947, 188659, 571, 0, 0, 1, 1, 2684.013427734375, -485.452423095703125, 50.89562606811523437, 3.595378875732421875, 0, 0, -0.97437000274658203, 0.224951311945915222, 120, 255, 1, "", 45854, NULL); + +-- remaining spawns (no sniffed values available) +-- (`guid` IN (99937)) diff --git a/data/sql/updates/db_world/2025_09_10_02.sql b/data/sql/updates/db_world/2025_09_10_02.sql new file mode 100644 index 000000000..04d4b34c3 --- /dev/null +++ b/data/sql/updates/db_world/2025_09_10_02.sql @@ -0,0 +1,14 @@ +-- DB update 2025_09_10_01 -> 2025_09_10_02 +-- +DELETE FROM `creature_template_addon` WHERE `entry`=29854; +INSERT INTO `creature_template_addon` (`entry`,`path_id`,`bytes1`,`mount`,`auras`) VALUES +(29854,0,1,0, ''); + +DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 13) AND (`SourceGroup` = 1) AND (`SourceEntry` = 56393); +INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES +(13, 1, 56393, 0, 0, 31, 0, 3, 29854, 0, 0, 0, 0, '', 'Feed Stormcrest Eagle target Stormcrest Eagle'), +(13, 1, 56393, 0, 0, 1, 0, 56393, 0, 0, 1, 0, 0, '', 'Feed Stormcrest Eagle target must not have Feed Stormcrest Eagle aura'); + +DELETE FROM `spell_script_names` WHERE `spell_id` = 56393; +INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES +(56393, 'spell_feed_stormcrest_eagle'); diff --git a/deps/acore/joiner/joiner.sh b/deps/acore/joiner/joiner.sh index be95b673a..1b1300716 100755 --- a/deps/acore/joiner/joiner.sh +++ b/deps/acore/joiner/joiner.sh @@ -94,11 +94,11 @@ function Joiner:add_repo() ( basedir="${4:-""}" [[ -z $url ]] && hasReq=false || hasReq=true - Joiner:_help $hasReq "$1" "Syntax: joiner.sh add-repo [-d] [-e] url name branch [basedir]" + Joiner:_help "$hasReq" "$1" "Syntax: joiner.sh add-repo [-d] [-e] url name branch [basedir]" # retrieving info from url if not set if [[ -z $name ]]; then - basename=$(basename $url) + basename=$(basename "$url") name=${basename%%.*} if [[ -z "$basedir" ]]; then @@ -115,10 +115,12 @@ function Joiner:add_repo() ( if [ -e "$path/.git/" ]; then # if exists , update - git --git-dir="$path/.git/" rev-parse && git --git-dir="$path/.git/" pull origin $branch | grep 'Already up-to-date.' && changed="no" || true + echo "Updating $name on branch $branch..." + git --git-dir="$path/.git/" --work-tree="$path" rev-parse && git --git-dir="$path/.git/" --work-tree="$path" pull origin "$branch" | grep 'Already up-to-date.' && changed="no" || true else # otherwise clone - git clone $url -c advice.detachedHead=0 -b $branch "$path" + echo "Cloning $name on branch $branch..." + git clone "$url" -c advice.detachedHead=0 -b "$branch" "$path" fi if [ "$?" -ne "0" ]; then @@ -140,16 +142,16 @@ function Joiner:add_git_submodule() ( basedir=${4:-""} [[ -z $url ]] && hasReq=false || hasReq=true - Joiner:_help $hasReq "$1" "Syntax: joiner.sh add-git-submodule [-d] [-e] url name branch [basedir]" + Joiner:_help "$hasReq" "$1" "Syntax: joiner.sh add-git-submodule [-d] [-e] url name branch [basedir]" # retrieving info from url if not set if [[ -z $name ]]; then - basename=$(basename $url) + basename=$(basename "$url") name=${basename%%.*} if [[ -z $basedir ]]; then - dir=$(dirname $url) - basedir=$(basename $dir) + dir=$(dirname "$url") + basedir=$(basename "$dir") fi name="${name,,}" #to lowercase @@ -158,17 +160,17 @@ function Joiner:add_git_submodule() ( path="$J_PATH_MODULES/$basedir/$name" valid_path=`Joiner:_searchFirstValiPath "$path"` - rel_path=${path#$valid_path} + rel_path=${path#"$valid_path"} rel_path=${rel_path#/} - if [ -e $path/ ]; then + if [ -e "$path/" ]; then # if exists , update - (cd "$path" && git pull origin $branch) - (cd "$valid_path" && git submodule update -f --init $rel_path) + (cd "$path" && git pull origin "$branch") + (cd "$valid_path" && git submodule update -f --init "$rel_path") else # otherwise add - (cd "$valid_path" && git submodule add -f -b $branch $url $rel_path) - (cd "$valid_path" && git submodule update -f --init $rel_path) + (cd "$valid_path" && git submodule add -f -b "$branch" "$url" "$rel_path") + (cd "$valid_path" && git submodule update -f --init "$rel_path") fi if [ "$?" -ne "0" ]; then @@ -324,7 +326,7 @@ function Joiner:self_update() { if [ ! -z "$J_VER_REQ" ]; then # if J_VER_REQ is defined then update only if tag is different _cur_branch=`git --git-dir="$J_PATH/.git/" --work-tree="$J_PATH/" rev-parse --abbrev-ref HEAD` - _cur_ver=`git --git-dir="$J_PATH/.git/" --work-tree="$J_PATH/" name-rev --tags --name-only $_cur_branch` + _cur_ver=`git --git-dir="$J_PATH/.git/" --work-tree="$J_PATH/" name-rev --tags --name-only "$_cur_branch"` if [ "$_cur_ver" != "$J_VER_REQ" ]; then git --git-dir="$J_PATH/.git/" --work-tree="$J_PATH/" rev-parse && git --git-dir="$J_PATH/.git/" fetch --tags origin "$_cur_branch" --quiet git --git-dir="$J_PATH/.git/" --work-tree="$J_PATH/" checkout "tags/$J_VER_REQ" -b "$_cur_branch" @@ -416,8 +418,8 @@ function Joiner:menu() { while true do # run option directly if specified in argument - [ ! -z $1 ] && _switch $@ - [ ! -z $1 ] && exit 0 + [ ! -z "$1" ] && _switch $@ + [ ! -z "$1" ] && exit 0 echo "" echo "==== JOINER MENU ====" diff --git a/deps/boost/CMakeLists.txt b/deps/boost/CMakeLists.txt index b6fd4ec1b..783356283 100644 --- a/deps/boost/CMakeLists.txt +++ b/deps/boost/CMakeLists.txt @@ -31,7 +31,8 @@ else() set(BOOST_REQUIRED_VERSION 1.74) endif() -find_package(Boost ${BOOST_REQUIRED_VERSION} REQUIRED system filesystem program_options iostreams regex thread) +# Boost.System is header-only since 1.69; do not require it explicitly. +find_package(Boost ${BOOST_REQUIRED_VERSION} REQUIRED COMPONENTS filesystem program_options iostreams regex thread) if(NOT Boost_FOUND) if(NOT DEFINED ENV{Boost_ROOT} AND NOT DEFINED Boost_DIR AND NOT DEFINED BOOST_ROOT AND NOT DEFINED BOOSTROOT) diff --git a/src/common/Dynamic/TypeContainer.h b/src/common/Dynamic/TypeContainer.h index 0c338de5d..6240eb48d 100644 --- a/src/common/Dynamic/TypeContainer.h +++ b/src/common/Dynamic/TypeContainer.h @@ -26,6 +26,7 @@ #include "Dynamic/TypeList.h" #include "GridRefMgr.h" #include +#include /* * @class ContainerMapList is a mulit-type container for map elements @@ -50,6 +51,24 @@ struct ContainerMapList> ContainerMapList _TailElements; }; +template +struct ContainerVector +{ + std::vector _element; +}; + +template<> +struct ContainerVector +{ +}; + +template +struct ContainerVector> +{ + ContainerVector _elements; + ContainerVector _TailElements; +}; + template struct ContainerUnorderedMap { @@ -123,6 +142,33 @@ private: ContainerMapList i_elements; }; +template +class TypeVectorContainer +{ +public: + template [[nodiscard]] std::size_t Count() const { return Acore::Count(i_elements, (SPECIFIC_TYPE*)nullptr); } + + template + bool Insert(SPECIFIC_TYPE* obj) + { + SPECIFIC_TYPE* t = Acore::Insert(i_elements, obj); + return (t != nullptr); + } + + template + bool Remove(SPECIFIC_TYPE* obj) + { + SPECIFIC_TYPE* t = Acore::Remove(i_elements, obj); + return (t != nullptr); + } + + ContainerVector& GetElements() { return i_elements; } + [[nodiscard]] const ContainerVector& GetElements() const { return i_elements; } + +private: + ContainerVector i_elements; +}; + template class TypeUnorderedMapContainer { diff --git a/src/common/Dynamic/TypeContainerFunctions.h b/src/common/Dynamic/TypeContainerFunctions.h index b517f4529..61735d7d8 100644 --- a/src/common/Dynamic/TypeContainerFunctions.h +++ b/src/common/Dynamic/TypeContainerFunctions.h @@ -239,5 +239,101 @@ namespace Acore // SPECIFIC_TYPE* t = Remove(elements._elements, obj); // return ( t != nullptr ? t : Remove(elements._TailElements, obj)); //} + + /* ContainerVector Helpers */ + // count functions + template + std::size_t Count(const ContainerVector& elements, SPECIFIC_TYPE* /*fake*/) + { + return elements._element.getSize(); + } + + template + std::size_t Count(const ContainerVector& /*elements*/, SPECIFIC_TYPE* /*fake*/) + { + return 0; + } + + template + std::size_t Count(const ContainerVector& /*elements*/, SPECIFIC_TYPE* /*fake*/) + { + return 0; + } + + template + std::size_t Count(const ContainerVector>& elements, SPECIFIC_TYPE* fake) + { + return Count(elements._elements, fake); + } + + template + std::size_t Count(const ContainerVector>& elements, SPECIFIC_TYPE* fake) + { + return Count(elements._TailElements, fake); + } + + // non-const insert functions + template + SPECIFIC_TYPE* Insert(ContainerVector& elements, SPECIFIC_TYPE* obj) + { + elements._element.push_back(obj); + return obj; + } + + template + SPECIFIC_TYPE* Insert(ContainerVector& /*elements*/, SPECIFIC_TYPE* /*obj*/) + { + return nullptr; + } + + // this is a missed + template + SPECIFIC_TYPE* Insert(ContainerVector& /*elements*/, SPECIFIC_TYPE* /*obj*/) + { + return nullptr; // a missed + } + + // Recursion + template + SPECIFIC_TYPE* Insert(ContainerVector>& elements, SPECIFIC_TYPE* obj) + { + SPECIFIC_TYPE* t = Insert(elements._elements, obj); + return (t != nullptr ? t : Insert(elements._TailElements, obj)); + } + + // non-const remove method + template SPECIFIC_TYPE* Remove(ContainerVector& elements, SPECIFIC_TYPE *obj) + { + // Simple vector find/swap/pop, this container should be very lightly used + // so I don't suspect the linear search complexity to be an issue + auto itr = std::find(elements._element.begin(), elements._element.end(), obj); + if (itr != elements._element.end()) + { + // Swap the element to be removed with the last element + std::swap(*itr, elements._element.back()); + + // Remove the last element (which is now the element we wanted to remove) + elements._element.pop_back(); + } + return obj; + } + + template SPECIFIC_TYPE* Remove(ContainerVector &/*elements*/, SPECIFIC_TYPE * /*obj*/) + { + return nullptr; + } + + // this is a missed + template SPECIFIC_TYPE* Remove(ContainerVector &/*elements*/, SPECIFIC_TYPE * /*obj*/) + { + return nullptr; // a missed + } + + template SPECIFIC_TYPE* Remove(ContainerVector > &elements, SPECIFIC_TYPE *obj) + { + // The head element is bad + SPECIFIC_TYPE* t = Remove(elements._elements, obj); + return ( t != nullptr ? t : Remove(elements._TailElements, obj)); + } } #endif diff --git a/src/common/Dynamic/TypeContainerVisitor.h b/src/common/Dynamic/TypeContainerVisitor.h index 1553d918a..066f6f308 100644 --- a/src/common/Dynamic/TypeContainerVisitor.h +++ b/src/common/Dynamic/TypeContainerVisitor.h @@ -56,6 +56,27 @@ template void VisitorHelper(VISITOR& v, TypeM VisitorHelper(v, c.GetElements()); } +// VectorContainer +template void VisitorHelper(VISITOR& /*v*/, ContainerVector& /*c*/) {} + +template void VisitorHelper(VISITOR& v, ContainerVector& c) +{ + v.Visit(c._element); +} + +// recursion container map list +template void VisitorHelper(VISITOR& v, ContainerVector>& c) +{ + VisitorHelper(v, c._elements); + VisitorHelper(v, c._TailElements); +} + +// for TypeMapContainer +template void VisitorHelper(VISITOR& v, TypeVectorContainer& c) +{ + VisitorHelper(v, c.GetElements()); +} + // TypeUnorderedMapContainer template void VisitorHelper(VISITOR& /*v*/, ContainerUnorderedMap& /*c*/) { } diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp index 5f304eac0..ab31d3cd7 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.cpp +++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp @@ -353,8 +353,8 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_UPD_REM_AT_LOGIN_FLAG, "UPDATE characters set at_login = at_login & ~ ? WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ALL_AT_LOGIN_FLAGS, "UPDATE characters SET at_login = at_login | ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_BUG_REPORT, "INSERT INTO bugreport (type, content) VALUES(?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_UPD_PETITION_NAME, "UPDATE petition SET name = ? WHERE petitionguid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_PETITION_SIGNATURE, "INSERT INTO petition_sign (ownerguid, petitionguid, playerguid, player_account) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_UPD_PETITION_NAME, "UPDATE petition SET name = ? WHERE petition_id = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_PETITION_SIGNATURE, "INSERT INTO petition_sign (ownerguid, petition_id, playerguid, player_account) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_ACCOUNT_ONLINE, "UPDATE characters SET online = 0 WHERE account = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_OFFLINE, "UPDATE characters SET online = 0 WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_GROUP, "INSERT INTO `groups` (guid, leaderGuid, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raidDifficulty, masterLooterGuid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); @@ -457,9 +457,9 @@ void CharacterDatabaseConnection::DoPrepareStatements() PrepareStatement(CHAR_INS_CHAR_GIFT, "INSERT INTO character_gifts (guid, item_guid, entry, flags) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_INSTANCE_BY_INSTANCE, "DELETE FROM instance WHERE id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_MAIL_ITEM_BY_ID, "DELETE FROM mail_items WHERE mail_id = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_INS_PETITION, "INSERT INTO petition (ownerguid, petitionguid, name, type) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_PETITION_BY_GUID, "DELETE FROM petition WHERE petitionguid = ?", CONNECTION_ASYNC); - PrepareStatement(CHAR_DEL_PETITION_SIGNATURE_BY_GUID, "DELETE FROM petition_sign WHERE petitionguid = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_INS_PETITION, "INSERT INTO petition (petition_id, ownerguid, petitionguid, name, type) VALUES (?, ?, ?, ?, ?)", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_PETITION_BY_ID, "DELETE FROM petition WHERE petition_id = ?", CONNECTION_ASYNC); + PrepareStatement(CHAR_DEL_PETITION_SIGNATURE_BY_ID, "DELETE FROM petition_sign WHERE petition_id = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_DEL_CHAR_DECLINED_NAME, "DELETE FROM character_declinedname WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(CHAR_INS_CHAR_DECLINED_NAME, "INSERT INTO character_declinedname (guid, genitive, dative, accusative, instrumental, prepositional) VALUES (?, ?, ?, ?, ?, ?)", CONNECTION_ASYNC); PrepareStatement(CHAR_UPD_CHAR_RACE, "UPDATE characters SET race = ? WHERE guid = ?", CONNECTION_ASYNC); diff --git a/src/server/database/Database/Implementation/CharacterDatabase.h b/src/server/database/Database/Implementation/CharacterDatabase.h index 801826b48..20c9d9892 100644 --- a/src/server/database/Database/Implementation/CharacterDatabase.h +++ b/src/server/database/Database/Implementation/CharacterDatabase.h @@ -382,8 +382,8 @@ enum CharacterDatabaseStatements : uint32 CHAR_DEL_INSTANCE_BY_INSTANCE, CHAR_DEL_MAIL_ITEM_BY_ID, CHAR_INS_PETITION, - CHAR_DEL_PETITION_BY_GUID, - CHAR_DEL_PETITION_SIGNATURE_BY_GUID, + CHAR_DEL_PETITION_BY_ID, + CHAR_DEL_PETITION_SIGNATURE_BY_ID, CHAR_DEL_CHAR_DECLINED_NAME, CHAR_INS_CHAR_DECLINED_NAME, CHAR_UPD_CHAR_RACE, diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index 4c2b252e2..f7e30d339 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -660,6 +660,7 @@ void SmartAI::EnterEvadeMode(EvadeReason /*why*/) if (me->GetCharmerGUID().IsPlayer() || me->HasUnitFlag(UNIT_FLAG_POSSESSED)) { me->AttackStop(); + me->RemoveUnitFlag(UNIT_FLAG_IN_COMBAT); return; } diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index e0a6f4fb8..c980f4f70 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -3834,19 +3834,20 @@ void SmartScript::GetTargets(ObjectVector& targets, SmartScriptHolder const& e, { targets.clear(); - if (owner->ToCreature()) + if (IsCreature(owner)) { if (Unit* base = ObjectAccessor::GetUnit(*owner, owner->ToCreature()->GetCharmerOrOwnerGUID())) - { targets.push_back(base); - } } - else + else if (IsGameObject(owner)) { if (Unit* base = ObjectAccessor::GetUnit(*owner, owner->ToGameObject()->GetOwnerGUID())) - { targets.push_back(base); - } + } + else if (IsPlayer(owner)) + { + if (Unit* base = owner->ToPlayer()->GetCharmerOrOwner()) + targets.push_back(base); } } } diff --git a/src/server/game/Entities/Creature/CreatureGroups.cpp b/src/server/game/Entities/Creature/CreatureGroups.cpp index c4ef53ae6..c603b51da 100644 --- a/src/server/game/Entities/Creature/CreatureGroups.cpp +++ b/src/server/game/Entities/Creature/CreatureGroups.cpp @@ -361,8 +361,9 @@ void CreatureGroup::LeaderMoveTo(float x, float y, float z, uint32 move_type) if (member == m_leader || !member->IsAlive() || member->GetVictim() || !pFormationInfo.HasGroupFlag(std::underlying_type_t(GroupAIFlags::GROUP_AI_FLAG_FOLLOW_LEADER))) continue; - // Xinef: If member is stunned / rooted etc don't allow to move him - if (member->HasUnitState(UNIT_STATE_NOT_MOVE)) + // If member is stunned / rooted etc don't allow to move him + // Or if charmed/controlled + if (member->HasUnitState(UNIT_STATE_NOT_MOVE) || member->isPossessed() || member->HasUnitFlag(UNIT_FLAG_PLAYER_CONTROLLED)) continue; // Xinef: this should be automatized, if turn angle is greater than PI/2 (90�) we should swap formation angle diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index 1fa5a4177..f258db1ca 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -431,15 +431,11 @@ bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, u // Check if GameObject is Large if (goinfo->IsLargeGameObject()) - { SetVisibilityDistanceOverride(VisibilityDistanceType::Large); - } // Check if GameObject is Infinite if (goinfo->IsInfiniteGameObject()) - { SetVisibilityDistanceOverride(VisibilityDistanceType::Infinite); - } return true; } diff --git a/src/server/game/Entities/GameObject/GameObjectData.h b/src/server/game/Entities/GameObject/GameObjectData.h index fac8fa9d2..fd95aa514 100644 --- a/src/server/game/Entities/GameObject/GameObjectData.h +++ b/src/server/game/Entities/GameObject/GameObjectData.h @@ -629,6 +629,8 @@ struct GameObjectTemplate return true; case GAMEOBJECT_TYPE_TRAPDOOR: return true; + case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING: + return true; default: return false; } diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 3081013be..a183d1467 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1037,7 +1037,7 @@ void MovementInfo::OutDebug() } WorldObject::WorldObject() : WorldLocation(), - LastUsedScriptID(0), m_name(""), m_isActive(false), m_visibilityDistanceOverride(), m_zoneScript(nullptr), + LastUsedScriptID(0), m_name(""), m_isActive(false), _visibilityDistanceOverrideType(VisibilityDistanceType::Normal), m_zoneScript(nullptr), _zoneId(0), _areaId(0), _floorZ(INVALID_HEIGHT), _outdoors(false), _liquidData(), _updatePositionData(false), m_transport(nullptr), m_currMap(nullptr), _heartbeatTimer(HEARTBEAT_INTERVAL), m_InstanceId(0), m_phaseMask(PHASEMASK_NORMAL), m_useCombinedPhases(true), m_notifyflags(0), m_executed_notifies(0), _objectVisibilityContainer(this) @@ -1082,15 +1082,36 @@ void WorldObject::setActive(bool on) map->AddObjectToPendingUpdateList(this); } +float WorldObject::GetVisibilityOverrideDistance() const +{ + ASSERT(_visibilityDistanceOverrideType < VisibilityDistanceType::Max); + return VisibilityDistances[AsUnderlyingType(_visibilityDistanceOverrideType)]; +} + void WorldObject::SetVisibilityDistanceOverride(VisibilityDistanceType type) { ASSERT(type < VisibilityDistanceType::Max); - if (IsPlayer()) - { + + if (type == GetVisibilityOverrideType()) return; + + if (IsPlayer()) + return; + + if (IsVisibilityOverridden()) + { + if (IsFarVisible()) + GetMap()->RemoveWorldObjectFromFarVisibleMap(this); + else if (IsZoneWideVisible()) + GetMap()->RemoveWorldObjectFromZoneWideVisibleMap(GetZoneId(), this); } - m_visibilityDistanceOverride = VisibilityDistances[AsUnderlyingType(type)]; + if (type == VisibilityDistanceType::Large || type == VisibilityDistanceType::Gigantic) + GetMap()->AddWorldObjectToFarVisibleMap(this); + else if (type == VisibilityDistanceType::Infinite) + GetMap()->AddWorldObjectToZoneWideVisibleMap(GetZoneId(), this); + + _visibilityDistanceOverrideType = type; } void WorldObject::CleanupsBeforeDelete(bool /*finalCleanup*/) @@ -1127,6 +1148,8 @@ void WorldObject::UpdatePositionData() void WorldObject::ProcessPositionDataChanged(PositionFullTerrainStatus const& data) { + uint32 const oldZoneId = _zoneId; + _zoneId = _areaId = data.areaId; if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(_areaId)) @@ -1136,6 +1159,17 @@ void WorldObject::ProcessPositionDataChanged(PositionFullTerrainStatus const& da _outdoors = data.outdoors; _floorZ = data.floorZ; _liquidData = data.liquidInfo; + + // Has zone ID changed? + if (oldZoneId != _zoneId) + { + // If so, check if we are far visibility overridden object and refresh maps if needed. + if (IsZoneWideVisible()) + { + GetMap()->RemoveWorldObjectFromZoneWideVisibleMap(oldZoneId, this); + GetMap()->AddWorldObjectToZoneWideVisibleMap(_zoneId, this); + } + } } void WorldObject::AddToWorld() @@ -1150,6 +1184,9 @@ void WorldObject::RemoveFromWorld() if (!IsInWorld()) return; + if (IsZoneWideVisible()) + GetMap()->RemoveWorldObjectFromZoneWideVisibleMap(GetZoneId(), this); + DestroyForVisiblePlayers(); GetObjectVisibilityContainer().CleanVisibilityReferences(); @@ -1612,26 +1649,16 @@ float WorldObject::GetGridActivationRange() const float WorldObject::GetVisibilityRange() const { - if (IsVisibilityOverridden() && IsCreature()) - { - return *m_visibilityDistanceOverride; - } + if (IsCreature() && IsVisibilityOverridden()) + return GetVisibilityOverrideDistance(); else if (IsGameObject()) { - { - if (IsInWintergrasp()) - { - return VISIBILITY_DIST_WINTERGRASP + VISIBILITY_INC_FOR_GOBJECTS; - } - else if (IsVisibilityOverridden()) - { - return *m_visibilityDistanceOverride; - } - else - { - return GetMap()->GetVisibilityRange() + VISIBILITY_INC_FOR_GOBJECTS; - } - } + if (IsInWintergrasp()) + return VISIBILITY_DIST_WINTERGRASP; + else if (IsVisibilityOverridden()) + return GetVisibilityOverrideDistance(); + else + return GetMap()->GetVisibilityRange(); } else return IsInWintergrasp() ? VISIBILITY_DIST_WINTERGRASP : GetMap()->GetVisibilityRange(); @@ -1645,28 +1672,18 @@ float WorldObject::GetSightRange(WorldObject const* target) const { if (target) { - if (target->IsVisibilityOverridden() && target->IsCreature()) - { - return *target->m_visibilityDistanceOverride; - } + if (target->IsCreature() && target->IsVisibilityOverridden()) + return target->GetVisibilityOverrideDistance(); else if (target->IsGameObject()) { if (IsInWintergrasp() && target->IsInWintergrasp()) - { - return VISIBILITY_DIST_WINTERGRASP + VISIBILITY_INC_FOR_GOBJECTS; - } + return VISIBILITY_DIST_WINTERGRASP; else if (target->IsVisibilityOverridden()) - { - return *target->m_visibilityDistanceOverride; - } + return target->GetVisibilityOverrideDistance(); else if (ToPlayer()->GetCinematicMgr()->IsOnCinematic()) - { return DEFAULT_VISIBILITY_INSTANCE; - } else - { - return GetMap()->GetVisibilityRange() + VISIBILITY_INC_FOR_GOBJECTS; - } + return GetMap()->GetVisibilityRange(); } return IsInWintergrasp() && target->IsInWintergrasp() ? VISIBILITY_DIST_WINTERGRASP : GetMap()->GetVisibilityRange(); @@ -1674,19 +1691,13 @@ float WorldObject::GetSightRange(WorldObject const* target) const return IsInWintergrasp() ? VISIBILITY_DIST_WINTERGRASP : GetMap()->GetVisibilityRange(); } else if (ToCreature()) - { return ToCreature()->m_SightDistance; - } else - { return SIGHT_RANGE_UNIT; - } } if (ToDynObject() && isActiveObject()) - { return GetMap()->GetVisibilityRange(); - } return 0.0f; } @@ -1790,7 +1801,7 @@ bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth, boo } // Xinef: check reversely obj vs viewpoint, object could be a gameObject which overrides _IsWithinDist function to include gameobject size - if (!corpseCheck && !viewpoint->IsWithinDist(obj, GetSightRange(obj), true)) + if (!corpseCheck && !viewpoint->IsWithinDist(obj, GetSightRange(obj), false)) return false; } diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index 1a2cdf384..23784c9c6 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -358,9 +358,20 @@ template class GridObject { public: - [[nodiscard]] bool IsInGrid() const { return _gridRef.isValid(); } - void AddToGrid(GridRefMgr& m) { ASSERT(!IsInGrid()); _gridRef.link(&m, (T*)this); } - void RemoveFromGrid() { ASSERT(IsInGrid()); _gridRef.unlink(); } + bool IsInGrid() const + { + return _gridRef.isValid(); + } + void AddToGrid(GridRefMgr& m) + { + ASSERT(!IsInGrid()); + _gridRef.link(&m, (T*)this); + } + void RemoveFromGrid() + { + ASSERT(IsInGrid()); + _gridRef.unlink(); + } private: GridReference _gridRef; }; @@ -654,8 +665,11 @@ public: [[nodiscard]] bool isActiveObject() const { return m_isActive; } void setActive(bool isActiveObject); - [[nodiscard]] bool IsFarVisible() const { return m_isFarVisible; } - [[nodiscard]] bool IsVisibilityOverridden() const { return m_visibilityDistanceOverride.has_value(); } + VisibilityDistanceType GetVisibilityOverrideType() const { return _visibilityDistanceOverrideType; } + bool IsVisibilityOverridden() const { return _visibilityDistanceOverrideType > VisibilityDistanceType::Normal; } + bool IsZoneWideVisible() const { return _visibilityDistanceOverrideType == VisibilityDistanceType::Infinite; } + bool IsFarVisible() const { return _visibilityDistanceOverrideType == VisibilityDistanceType::Large || _visibilityDistanceOverrideType == VisibilityDistanceType::Gigantic; } + float GetVisibilityOverrideDistance() const; void SetVisibilityDistanceOverride(VisibilityDistanceType type); [[nodiscard]] bool IsInWintergrasp() const @@ -719,8 +733,7 @@ public: protected: std::string m_name; bool m_isActive; - bool m_isFarVisible; - Optional m_visibilityDistanceOverride; + VisibilityDistanceType _visibilityDistanceOverrideType; ZoneScript* m_zoneScript; virtual void ProcessPositionDataChanged(PositionFullTerrainStatus const& data); diff --git a/src/server/game/Entities/Object/ObjectDefines.h b/src/server/game/Entities/Object/ObjectDefines.h index bfae02203..fb0daa500 100644 --- a/src/server/game/Entities/Object/ObjectDefines.h +++ b/src/server/game/Entities/Object/ObjectDefines.h @@ -25,7 +25,6 @@ #define ATTACK_DISTANCE 5.0f #define VISIBILITY_COMPENSATION 15.0f // increase searchers #define INSPECT_DISTANCE 28.0f -#define VISIBILITY_INC_FOR_GOBJECTS 30.0f // pussywizard #define SPELL_SEARCHER_COMPENSATION 30.0f // increase searchers size in case we have large npc near cell border #define TRADE_DISTANCE 11.11f #define MAX_VISIBILITY_DISTANCE 250.0f // max distance for visible objects, experimental diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 1969d4c67..a58df5939 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -7078,6 +7078,14 @@ void Player::ApplyItemObtainSpells(Item* item, bool apply) } } +void Player::UpdateItemObtainSpells(Item* item, uint8 bag, uint8 slot) +{ + if (IsBankPos(bag, slot)) + ApplyItemObtainSpells(item, false); + else if (bag == INVENTORY_SLOT_BAG_0 || (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END)) + ApplyItemObtainSpells(item, true); +} + SpellSchoolMask Player::GetMeleeDamageSchoolMask(WeaponAttackType attackType /*= BASE_ATTACK*/, uint8 damageIndex /*= 0*/) const { if (Item const* weapon = GetWeaponForAttack(attackType, true)) @@ -16314,13 +16322,25 @@ float Player::GetSightRange(WorldObject const* target) const { float sightRange = WorldObject::GetSightRange(target); if (_farSightDistance) - { sightRange += *_farSightDistance; - } return sightRange; } +bool Player::IsWorldObjectOutOfSightRange(WorldObject const* target) const +{ + // Special handling for Infinite visibility override objects -> they are zone wide visible + if (target->GetVisibilityOverrideType() == VisibilityDistanceType::Infinite) + { + // Same zone, always visible + if (target->GetZoneId() == GetZoneId()) + return false; + } + + // Check if out of range + return !m_seer->IsWithinDist(target, GetSightRange(target), false); +} + std::string Player::GetPlayerName() { std::string name = GetName(); diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 8ec139381..9dd87b39b 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -2199,6 +2199,7 @@ public: void CastAllObtainSpells(); void ApplyItemObtainSpells(Item* item, bool apply); + void UpdateItemObtainSpells(Item* item, uint8 bag, uint8 slot); SpellSchoolMask GetMeleeDamageSchoolMask(WeaponAttackType attackType = BASE_ATTACK, uint8 damageIndex = 0) const override; @@ -2620,6 +2621,7 @@ public: [[nodiscard]] Optional GetFarSightDistance() const; float GetSightRange(WorldObject const* target = nullptr) const override; + bool IsWorldObjectOutOfSightRange(WorldObject const* target) const; std::string GetPlayerName(); diff --git a/src/server/game/Entities/Player/PlayerStorage.cpp b/src/server/game/Entities/Player/PlayerStorage.cpp index 4836811e2..1423afc98 100644 --- a/src/server/game/Entities/Player/PlayerStorage.cpp +++ b/src/server/game/Entities/Player/PlayerStorage.cpp @@ -2654,9 +2654,7 @@ Item* Player::_StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool AddEnchantmentDurations(pItem); AddItemDurations(pItem); - - if (bag == INVENTORY_SLOT_BAG_0 || (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END)) - ApplyItemObtainSpells(pItem, true); + UpdateItemObtainSpells(pItem, bag, slot); return pItem; } @@ -2694,8 +2692,7 @@ Item* Player::_StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool pItem2->SetState(ITEM_CHANGED, this); - if (bag == INVENTORY_SLOT_BAG_0 || (bag >= INVENTORY_SLOT_BAG_START && bag < INVENTORY_SLOT_BAG_END)) - ApplyItemObtainSpells(pItem2, true); + UpdateItemObtainSpells(pItem2, bag, slot); return pItem2; } diff --git a/src/server/game/Entities/Player/PlayerUpdates.cpp b/src/server/game/Entities/Player/PlayerUpdates.cpp index ace8b0726..9b4925541 100644 --- a/src/server/game/Entities/Player/PlayerUpdates.cpp +++ b/src/server/game/Entities/Player/PlayerUpdates.cpp @@ -1595,21 +1595,12 @@ void Player::UpdateVisibilityForPlayer(bool mapChange) // After added to map seer must be a player - there is no possibility to // still have different seer (all charm auras must be already removed) if (mapChange && m_seer != this) - { m_seer = this; - } - Acore::VisibleNotifier notifierNoLarge( - *this, mapChange, - false); // visit only objects which are not large; default distance - Cell::VisitObjects(m_seer, notifierNoLarge, - GetSightRange() + VISIBILITY_INC_FOR_GOBJECTS); - notifierNoLarge.SendToSelf(); - - Acore::VisibleNotifier notifierLarge( - *this, mapChange, true); // visit only large objects; maximum distance - Cell::VisitObjects(m_seer, notifierLarge, GetSightRange()); - notifierLarge.SendToSelf(); + Acore::VisibleNotifier notifier(*this, mapChange); + Cell::VisitObjects(m_seer, notifier, GetSightRange()); + Cell::VisitFarVisibleObjects(m_seer, notifier, VISIBILITY_DISTANCE_GIGANTIC); + notifier.SendToSelf(); if (mapChange) m_last_notify_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f); diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 4c12a3c9b..62d148312 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -20315,12 +20315,10 @@ void Unit::ExecuteDelayedUnitRelocationEvent() //active->m_last_notify_position.Relocate(active->GetPositionX(), active->GetPositionY(), active->GetPositionZ()); } - Acore::PlayerRelocationNotifier relocateNoLarge(*player, false); // visit only objects which are not large; default distance - Cell::VisitObjects(viewPoint, relocateNoLarge, player->GetSightRange() + VISIBILITY_INC_FOR_GOBJECTS); - relocateNoLarge.SendToSelf(); - Acore::PlayerRelocationNotifier relocateLarge(*player, true); // visit only large objects; maximum distance - Cell::VisitObjects(viewPoint, relocateLarge, MAX_VISIBILITY_DISTANCE); - relocateLarge.SendToSelf(); + Acore::PlayerRelocationNotifier notifier(*player); + Cell::VisitObjects(viewPoint, notifier, player->GetSightRange()); + Cell::VisitFarVisibleObjects(viewPoint, notifier, VISIBILITY_DISTANCE_GIGANTIC); + notifier.SendToSelf(); } if (Player* player = this->ToPlayer()) @@ -20354,16 +20352,10 @@ void Unit::ExecuteDelayedUnitRelocationEvent() GetMap()->LoadGridsInRange(*player, MAX_VISIBILITY_DISTANCE); - Acore::PlayerRelocationNotifier relocateNoLarge(*player, false); // visit only objects which are not large; default distance - Cell::VisitObjects(viewPoint, relocateNoLarge, player->GetSightRange() + VISIBILITY_INC_FOR_GOBJECTS); - relocateNoLarge.SendToSelf(); - - if (!player->GetFarSightDistance()) - { - Acore::PlayerRelocationNotifier relocateLarge(*player, true); // visit only large objects; maximum distance - Cell::VisitObjects(viewPoint, relocateLarge, MAX_VISIBILITY_DISTANCE); - relocateLarge.SendToSelf(); - } + Acore::PlayerRelocationNotifier notifier(*player); + Cell::VisitObjects(viewPoint, notifier, player->GetSightRange()); + Cell::VisitFarVisibleObjects(viewPoint, notifier, VISIBILITY_DISTANCE_GIGANTIC); + notifier.SendToSelf(); this->AddToNotify(NOTIFY_AI_RELOCATION); } @@ -20383,7 +20375,7 @@ void Unit::ExecuteDelayedUnitRelocationEvent() unit->m_last_notify_position.Relocate(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ()); Acore::CreatureRelocationNotifier relocate(*unit); - Cell::VisitObjects(unit, relocate, unit->GetVisibilityRange() + VISIBILITY_COMPENSATION); + Cell::VisitObjects(unit, relocate, unit->GetVisibilityRange()); this->AddToNotify(NOTIFY_AI_RELOCATION); } diff --git a/src/server/game/Grids/Cells/Cell.h b/src/server/game/Grids/Cells/Cell.h index 89676468d..a54f7f4cd 100644 --- a/src/server/game/Grids/Cells/Cell.h +++ b/src/server/game/Grids/Cells/Cell.h @@ -106,6 +106,8 @@ struct Cell template static void VisitObjects(WorldObject const* obj, T& visitor, float radius); template static void VisitObjects(float x, float y, Map* map, T& visitor, float radius); + template static void VisitFarVisibleObjects(WorldObject const* obj, T& visitor, float radius); + private: template void VisitCircle(TypeContainerVisitor&, Map&, CellCoord const&, CellCoord const&) const; }; diff --git a/src/server/game/Grids/Cells/CellImpl.h b/src/server/game/Grids/Cells/CellImpl.h index d3f97a09d..997840367 100644 --- a/src/server/game/Grids/Cells/CellImpl.h +++ b/src/server/game/Grids/Cells/CellImpl.h @@ -181,4 +181,14 @@ inline void Cell::VisitObjects(float x, float y, Map* map, T& visitor, float rad cell.Visit(p, gnotifier, *map, x, y, radius); } +template +inline void Cell::VisitFarVisibleObjects(WorldObject const* center_obj, T& visitor, float radius) +{ + CellCoord p(Acore::ComputeCellCoord(center_obj->GetPositionX(), center_obj->GetPositionY())); + Cell cell(p); + + TypeContainerVisitor gnotifier(visitor); + cell.Visit(p, gnotifier, *center_obj->GetMap(), *center_obj, radius); +} + #endif diff --git a/src/server/game/Grids/GridCell.h b/src/server/game/Grids/GridCell.h index 62ff76545..65e3a0315 100644 --- a/src/server/game/Grids/GridCell.h +++ b/src/server/game/Grids/GridCell.h @@ -33,16 +33,20 @@ #include "TypeContainer.h" #include "TypeContainerVisitor.h" +class WorldObject; + template < - class GRID_OBJECT_TYPES + class GRID_OBJECT_TYPES, + class FAR_VISIBLE_OBJECT_TYPES > class GridCell { public: ~GridCell() = default; - template void AddGridObject(SPECIFIC_OBJECT* obj) + template + void AddGridObject(SPECIFIC_OBJECT* obj) { _gridObjects.template insert(obj); ASSERT(obj->IsInGrid()); @@ -50,12 +54,32 @@ public: // Visit grid objects template - void Visit(TypeContainerVisitor >& visitor) + void Visit(TypeContainerVisitor>& visitor) { visitor.Visit(_gridObjects); } + template + void AddFarVisibleObject(SPECIFIC_OBJECT* obj) + { + _farVisibleObjects.template Insert(obj); + } + + template + void RemoveFarVisibleObject(SPECIFIC_OBJECT* obj) + { + _farVisibleObjects.template Remove(obj); + } + + // Visit far objects + template + void Visit(TypeContainerVisitor>& visitor) + { + visitor.Visit(_farVisibleObjects); + } + private: TypeMapContainer _gridObjects; + TypeVectorContainer _farVisibleObjects; }; #endif diff --git a/src/server/game/Grids/GridDefines.h b/src/server/game/Grids/GridDefines.h index 6918d2b1a..5c1d09c63 100644 --- a/src/server/game/Grids/GridDefines.h +++ b/src/server/game/Grids/GridDefines.h @@ -57,6 +57,9 @@ typedef TYPELIST_5(GameObject, Player, Creature, Corpse, DynamicObject) AllMapGr // List of object types stored on map level typedef TYPELIST_4(Creature, GameObject, DynamicObject, Corpse) AllMapStoredObjectTypes; +// List of object types that can have far visible range +typedef TYPELIST_2(Creature, GameObject) AllFarVisibleObjectTypes; + typedef GridRefMgr CorpseMapType; typedef GridRefMgr CreatureMapType; typedef GridRefMgr DynamicObjectMapType; @@ -73,10 +76,11 @@ enum GridMapTypeMask GRID_MAP_TYPE_MASK_ALL = 0x1F }; -typedef GridCell GridCellType; -typedef MapGrid MapGridType; +typedef GridCell GridCellType; +typedef MapGrid MapGridType; typedef TypeMapContainer GridTypeMapContainer; +typedef TypeVectorContainer FarVisibleGridContainer; typedef TypeUnorderedMapContainer MapStoredObjectTypesContainer; template diff --git a/src/server/game/Grids/MapGrid.h b/src/server/game/Grids/MapGrid.h index b6dda486c..c80c3b4d3 100644 --- a/src/server/game/Grids/MapGrid.h +++ b/src/server/game/Grids/MapGrid.h @@ -25,12 +25,13 @@ class GridTerrainData; template < - class GRID_OBJECT_TYPES + class GRID_OBJECT_TYPES, + class FAR_VISIBLE_OBJECT_TYPES > class MapGrid { public: - typedef GridCell GridCellType; + typedef GridCell GridCellType; MapGrid(uint16 const x, uint16 const y) : _x(x), _y(y), _objectDataLoaded(false), _terrainData(nullptr) { } @@ -54,9 +55,19 @@ public: GetOrCreateCell(x, y).RemoveGridObject(obj); } + template void AddFarVisibleObject(uint16 const x, uint16 const y, SPECIFIC_OBJECT* obj) + { + GetOrCreateCell(x, y).AddFarVisibleObject(obj); + } + + template void RemoveFarVisibleObject(uint16 const x, uint16 const y, SPECIFIC_OBJECT* obj) + { + GetOrCreateCell(x, y).RemoveFarVisibleObject(obj); + } + // Visit all cells template - void VisitAllCells(TypeContainerVisitor >& visitor) + void VisitAllCells(TypeContainerVisitor& visitor) { for (auto& cellX : _cells) { @@ -72,7 +83,7 @@ public: // Visit single cell template - void VisitCell(uint16 const x, uint16 const y, TypeContainerVisitor >& visitor) + void VisitCell(uint16 const x, uint16 const y, TypeContainerVisitor& visitor) { GridCellType* gridCell = GetCell(x, y); if (!gridCell) @@ -81,7 +92,7 @@ public: gridCell->Visit(visitor); } - void link(GridRefMgr>* pTo) + void link(GridRefMgr>* pTo) { _gridReference.link(pTo, this); } @@ -134,7 +145,7 @@ private: bool _objectDataLoaded; std::array, MAX_NUMBER_OF_CELLS>, MAX_NUMBER_OF_CELLS> _cells; // N * N array - GridReference> _gridReference; + GridReference> _gridReference; // Instances will share a copy of the parent maps terrainData std::shared_ptr _terrainData; diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.cpp b/src/server/game/Grids/Notifiers/GridNotifiers.cpp index 563634929..5dd717f32 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.cpp +++ b/src/server/game/Grids/Notifiers/GridNotifiers.cpp @@ -29,38 +29,31 @@ void VisibleNotifier::Visit(GameObjectMapType& m) for (GameObjectMapType::iterator iter = m.begin(); iter != m.end(); ++iter) { GameObject* go = iter->GetSource(); - if (i_largeOnly != go->IsVisibilityOverridden()) - continue; - i_player.UpdateVisibilityOf(go, i_data, i_visibleNow); } } void VisibleNotifier::SendToSelf() { - // at this moment i_clientGUIDs have guids that not iterate at grid level checks - // but exist one case when this possible and object not out of range: transports - if (Transport* transport = i_player.GetTransport()) + // Update far visible objects + ZoneWideVisibleWorldObjectsSet const* zoneWideVisibleObjects = i_player.GetMap()->GetZoneWideVisibleWorldObjectsForZone(i_player.GetZoneId()); + if (zoneWideVisibleObjects) { - for (Transport::PassengerSet::const_iterator itr = transport->GetPassengers().begin(); itr != transport->GetPassengers().end(); ++itr) + for (WorldObject* obj : *zoneWideVisibleObjects) { - if (i_largeOnly != (*itr)->IsVisibilityOverridden()) - continue; - - switch ((*itr)->GetTypeId()) + switch (obj->GetTypeId()) { - case TYPEID_GAMEOBJECT: - i_player.UpdateVisibilityOf((*itr)->ToGameObject(), i_data, i_visibleNow); - break; - case TYPEID_PLAYER: - i_player.UpdateVisibilityOf((*itr)->ToPlayer(), i_data, i_visibleNow); - (*itr)->ToPlayer()->UpdateVisibilityOf(&i_player); - break; - case TYPEID_UNIT: - i_player.UpdateVisibilityOf((*itr)->ToCreature(), i_data, i_visibleNow); - break; - default: - break; + case TYPEID_GAMEOBJECT: + i_player.UpdateVisibilityOf(obj->ToGameObject(), i_data, i_visibleNow); + break; + case TYPEID_UNIT: + i_player.UpdateVisibilityOf(obj->ToCreature(), i_data, i_visibleNow); + break; + case TYPEID_DYNAMICOBJECT: + i_player.UpdateVisibilityOf(obj->ToDynObject(), i_data, i_visibleNow); + break; + default: + break; } } } @@ -69,26 +62,8 @@ void VisibleNotifier::SendToSelf() for (VisibleWorldObjectsMap::iterator itr = visibleWorldObjects->begin(); itr != visibleWorldObjects->end();) { WorldObject* obj = itr->second; - if (i_largeOnly != obj->IsVisibilityOverridden()) - { - ++itr; - continue; - } - - // pussywizard: static transports are removed only in RemovePlayerFromMap and here if can no longer detect (eg. phase changed) - if (itr->first.IsTransport()) - { - if (GameObject* staticTrans = obj->ToGameObject()) - { - if (i_player.CanSeeOrDetect(staticTrans, false, true)) - { - ++itr; - continue; - } - } - } - - if (i_player.m_seer->IsWithinDist(obj, i_player.GetSightRange(obj), true)) + if (!i_player.IsWorldObjectOutOfSightRange(obj) + || i_player.CanSeeOrDetect(obj, false, true)) { ++itr; continue; @@ -111,12 +86,7 @@ void VisibleNotifier::SendToSelf() i_player.GetSession()->SendPacket(&packet); for (std::vector::const_iterator it = i_visibleNow.begin(); it != i_visibleNow.end(); ++it) - { - if (i_largeOnly != (*it)->IsVisibilityOverridden()) - continue; - i_player.GetInitialVisiblePackets(*it); - } } void VisibleChangesNotifier::Visit(PlayerMapType& m) diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index 040c54244..d9556c77f 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -45,16 +45,16 @@ namespace Acore Player& i_player; std::vector& i_visibleNow; bool i_gobjOnly; - bool i_largeOnly; UpdateData i_data; - VisibleNotifier(Player& player, bool gobjOnly, bool largeOnly) : - i_player(player), i_visibleNow(player.m_newVisible), i_gobjOnly(gobjOnly), i_largeOnly(largeOnly) + VisibleNotifier(Player& player, bool gobjOnly) : + i_player(player), i_visibleNow(player.m_newVisible), i_gobjOnly(gobjOnly) { i_visibleNow.clear(); } void Visit(GameObjectMapType&); + template void Visit(std::vector& m); template void Visit(GridRefMgr& m); void SendToSelf(void); }; @@ -72,8 +72,9 @@ namespace Acore struct PlayerRelocationNotifier : public VisibleNotifier { - PlayerRelocationNotifier(Player& player, bool largeOnly): VisibleNotifier(player, false, largeOnly) { } + PlayerRelocationNotifier(Player& player): VisibleNotifier(player, false) { } + template void Visit(std::vector& m) { VisibleNotifier::Visit(m); } template void Visit(GridRefMgr& m) { VisibleNotifier::Visit(m); } void Visit(PlayerMapType&); }; diff --git a/src/server/game/Grids/Notifiers/GridNotifiersImpl.h b/src/server/game/Grids/Notifiers/GridNotifiersImpl.h index 592e584ea..a24e87374 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiersImpl.h +++ b/src/server/game/Grids/Notifiers/GridNotifiersImpl.h @@ -25,6 +25,13 @@ #include "WorldPacket.h" #include "WorldSession.h" +template +inline void Acore::VisibleNotifier::Visit(std::vector& m) +{ + for (typename std::vector::iterator iter = m.begin(); iter != m.end(); ++iter) + i_player.UpdateVisibilityOf((*iter), i_data, i_visibleNow); +} + template inline void Acore::VisibleNotifier::Visit(GridRefMgr& m) { @@ -33,12 +40,7 @@ inline void Acore::VisibleNotifier::Visit(GridRefMgr& m) return; for (typename GridRefMgr::iterator iter = m.begin(); iter != m.end(); ++iter) - { - if (i_largeOnly != iter->GetSource()->IsVisibilityOverridden()) - continue; - i_player.UpdateVisibilityOf(iter->GetSource(), i_data, i_visibleNow); - } } // SEARCHERS & LIST SEARCHERS & WORKERS diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp index 4ce4ceb95..ce5ca1081 100644 --- a/src/server/game/Handlers/ItemHandler.cpp +++ b/src/server/game/Handlers/ItemHandler.cpp @@ -29,91 +29,82 @@ #include "WorldSession.h" #include -void WorldSession::HandleSplitItemOpcode(WorldPacket& recvData) +#include "ItemPackets.h" + +void WorldSession::HandleSplitItemOpcode(WorldPackets::Item::SplitItem& packet) { //LOG_DEBUG("network.opcode", "WORLD: CMSG_SPLIT_ITEM"); - uint8 srcbag, srcslot, dstbag, dstslot; - uint32 count; - recvData >> srcbag >> srcslot >> dstbag >> dstslot >> count; - - uint16 src = ((srcbag << 8) | srcslot); - uint16 dst = ((dstbag << 8) | dstslot); + uint16 src = ((packet.SourceBag << 8) | packet.SourceSlot); + uint16 dst = ((packet.DestinationBag << 8) | packet.DestinationSlot); if (src == dst) return; - if (count == 0) - return; //check count - if zero it's fake packet + if (packet.Count == 0) + return; //check count - if zero it's fake packet - if (!_player->IsValidPos(srcbag, srcslot, true)) + if (!_player->IsValidPos(packet.SourceBag, packet.SourceSlot, true)) { _player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); return; } - if (!_player->IsValidPos(dstbag, dstslot, false)) // can be autostore pos + if (!_player->IsValidPos(packet.DestinationBag, packet.DestinationSlot, false)) // can be autostore pos { _player->SendEquipError(EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT, nullptr, nullptr); return; } - _player->SplitItem(src, dst, count); + _player->SplitItem(src, dst, packet.Count); } -void WorldSession::HandleSwapInvItemOpcode(WorldPacket& recvData) +void WorldSession::HandleSwapInvItemOpcode(WorldPackets::Item::SwapInventoryItem& packet) { //LOG_DEBUG("network.opcode", "WORLD: CMSG_SWAP_INV_ITEM"); - uint8 srcslot, dstslot; - recvData >> dstslot >> srcslot; - - // prevent attempt swap same item to current position generated by client at special checting sequence - if (srcslot == dstslot) + // prevent attempt swap same item to current position generated by client at special cheating sequence + if (packet.SourceSlot == packet.DestinationSlot) return; - if (!_player->IsValidPos(INVENTORY_SLOT_BAG_0, srcslot, true)) + if (!_player->IsValidPos(INVENTORY_SLOT_BAG_0, packet.SourceSlot, true)) { _player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); return; } - if (!_player->IsValidPos(INVENTORY_SLOT_BAG_0, dstslot, true)) + if (!_player->IsValidPos(INVENTORY_SLOT_BAG_0, packet.DestinationSlot, true)) { _player->SendEquipError(EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT, nullptr, nullptr); return; } - if (_player->IsBankPos(INVENTORY_SLOT_BAG_0, srcslot) && !CanUseBank()) + if (_player->IsBankPos(INVENTORY_SLOT_BAG_0, packet.SourceSlot) && !CanUseBank()) { //LOG_DEBUG("network", "WORLD: HandleSwapInvItemOpcode - Unit ({}) not found or you can't interact with him.", m_currentBankerGUID.ToString()); return; } - if (_player->IsBankPos(INVENTORY_SLOT_BAG_0, dstslot) && !CanUseBank()) + if (_player->IsBankPos(INVENTORY_SLOT_BAG_0, packet.DestinationSlot) && !CanUseBank()) { //LOG_DEBUG("network", "WORLD: HandleSwapInvItemOpcode - Unit ({}) not found or you can't interact with him.", m_currentBankerGUID.ToString()); return; } - uint16 src = ((INVENTORY_SLOT_BAG_0 << 8) | srcslot); - uint16 dst = ((INVENTORY_SLOT_BAG_0 << 8) | dstslot); + uint16 src = ((INVENTORY_SLOT_BAG_0 << 8) | packet.SourceSlot); + uint16 dst = ((INVENTORY_SLOT_BAG_0 << 8) | packet.DestinationSlot); _player->SwapItem(src, dst); } -void WorldSession::HandleAutoEquipItemSlotOpcode(WorldPacket& recvData) +void WorldSession::HandleAutoEquipItemSlotOpcode(WorldPackets::Item::AutoEquipItemSlot& packet) { - ObjectGuid itemguid; - uint8 dstslot; - recvData >> itemguid >> dstslot; - // cheating attempt, client should never send opcode in that case - if (!Player::IsEquipmentPos(INVENTORY_SLOT_BAG_0, dstslot)) + if (!Player::IsEquipmentPos(INVENTORY_SLOT_BAG_0, packet.DestinationSlot)) return; - Item* item = _player->GetItemByGuid(itemguid); - uint16 dstpos = dstslot | (INVENTORY_SLOT_BAG_0 << 8); + Item* item = _player->GetItemByGuid(packet.ItemGuid); + uint16 dstpos = packet.DestinationSlot | (INVENTORY_SLOT_BAG_0 << 8); if (!item || item->GetPos() == dstpos) return; @@ -121,39 +112,36 @@ void WorldSession::HandleAutoEquipItemSlotOpcode(WorldPacket& recvData) _player->SwapItem(item->GetPos(), dstpos); } -void WorldSession::HandleSwapItem(WorldPacket& recvData) +void WorldSession::HandleSwapItem(WorldPackets::Item::SwapItem& packet) { //LOG_DEBUG("network.opcode", "WORLD: CMSG_SWAP_ITEM"); - uint8 dstbag, dstslot, srcbag, srcslot; - recvData >> dstbag >> dstslot >> srcbag >> srcslot; + uint16 src = ((packet.SourceBag << 8) | packet.SourceSlot); + uint16 dst = ((packet.DestinationBag << 8) | packet.DestinationSlot); - uint16 src = ((srcbag << 8) | srcslot); - uint16 dst = ((dstbag << 8) | dstslot); - - // prevent attempt swap same item to current position generated by client at special checting sequence + // prevent attempt swap same item to current position generated by client at special cheating sequence if (src == dst) return; - if (!_player->IsValidPos(srcbag, srcslot, true)) + if (!_player->IsValidPos(packet.SourceBag, packet.SourceSlot, true)) { _player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); return; } - if (!_player->IsValidPos(dstbag, dstslot, true)) + if (!_player->IsValidPos(packet.DestinationBag, packet.DestinationSlot, true)) { _player->SendEquipError(EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT, nullptr, nullptr); return; } - if (_player->IsBankPos(srcbag, srcslot) && !CanUseBank()) + if (_player->IsBankPos(packet.SourceBag, packet.SourceSlot) && !CanUseBank()) { //LOG_DEBUG("network", "WORLD: HandleSwapItem - Unit ({}) not found or you can't interact with him.", m_currentBankerGUID.ToString()); return; } - if (_player->IsBankPos(dstbag, dstslot) && !CanUseBank()) + if (_player->IsBankPos(packet.DestinationBag, packet.DestinationSlot) && !CanUseBank()) { //LOG_DEBUG("network", "WORLD: HandleSwapItem - Unit ({}) not found or you can't interact with him.", m_currentBankerGUID.ToString()); return; @@ -162,14 +150,11 @@ void WorldSession::HandleSwapItem(WorldPacket& recvData) _player->SwapItem(src, dst); } -void WorldSession::HandleAutoEquipItemOpcode(WorldPacket& recvData) +void WorldSession::HandleAutoEquipItemOpcode(WorldPackets::Item::AutoEquipItem& packet) { //LOG_DEBUG("network.opcode", "WORLD: CMSG_AUTOEQUIP_ITEM"); - uint8 srcbag, srcslot; - recvData >> srcbag >> srcslot; - - Item* pSrcItem = _player->GetItemByPos(srcbag, srcslot); + Item* pSrcItem = _player->GetItemByPos(packet.SourceBag, packet.SourceSlot); if (!pSrcItem) return; // only at cheat @@ -219,7 +204,7 @@ void WorldSession::HandleAutoEquipItemOpcode(WorldPacket& recvData) if (!pDstItem) // empty slot, simple case { - _player->RemoveItem(srcbag, srcslot, true); + _player->RemoveItem(packet.SourceBag, packet.SourceSlot, true); _player->EquipItem(dest, pSrcItem, true); _player->AutoUnequipOffhandIfNeed(); } @@ -243,23 +228,23 @@ void WorldSession::HandleAutoEquipItemOpcode(WorldPacket& recvData) uint16 eSrc = 0; if (_player->IsInventoryPos(src)) { - msg = _player->CanStoreItem(srcbag, srcslot, sSrc, pDstItem, true); + msg = _player->CanStoreItem(packet.SourceBag, packet.SourceSlot, sSrc, pDstItem, true); if (msg != EQUIP_ERR_OK) - msg = _player->CanStoreItem(srcbag, NULL_SLOT, sSrc, pDstItem, true); + msg = _player->CanStoreItem(packet.SourceBag, NULL_SLOT, sSrc, pDstItem, true); if (msg != EQUIP_ERR_OK) msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, sSrc, pDstItem, true); } else if (_player->IsBankPos(src)) { - msg = _player->CanBankItem(srcbag, srcslot, sSrc, pDstItem, true); + msg = _player->CanBankItem(packet.SourceBag, packet.SourceSlot, sSrc, pDstItem, true); if (msg != EQUIP_ERR_OK) - msg = _player->CanBankItem(srcbag, NULL_SLOT, sSrc, pDstItem, true); + msg = _player->CanBankItem(packet.SourceBag, NULL_SLOT, sSrc, pDstItem, true); if (msg != EQUIP_ERR_OK) msg = _player->CanBankItem(NULL_BAG, NULL_SLOT, sSrc, pDstItem, true); } else if (_player->IsEquipmentPos(src)) { - msg = _player->CanEquipItem(srcslot, eSrc, pDstItem, true); + msg = _player->CanEquipItem(packet.SourceSlot, eSrc, pDstItem, true); if (msg == EQUIP_ERR_OK) msg = _player->CanUnequipItem(eSrc, true); } @@ -272,7 +257,7 @@ void WorldSession::HandleAutoEquipItemOpcode(WorldPacket& recvData) // now do moves, remove... _player->RemoveItem(dstbag, dstslot, true, true); - _player->RemoveItem(srcbag, srcslot, true, true); + _player->RemoveItem(packet.SourceBag, packet.SourceSlot, true, true); // add to dest _player->EquipItem(dest, pSrcItem, true); @@ -292,14 +277,11 @@ void WorldSession::HandleAutoEquipItemOpcode(WorldPacket& recvData) } } -void WorldSession::HandleDestroyItemOpcode(WorldPacket& recvData) +void WorldSession::HandleDestroyItemOpcode(WorldPackets::Item::DestroyItem& packet) { //LOG_DEBUG("network.opcode", "WORLD: CMSG_DESTROYITEM"); - uint8 bag, slot, count, data1, data2, data3; - recvData >> bag >> slot >> count >> data1 >> data2 >> data3; - - uint16 pos = (bag << 8) | slot; + uint16 pos = (packet.Bag << 8) | packet.Slot; // prevent drop unequipable items (in combat, for example) and non-empty bags if (_player->IsEquipmentPos(pos) || _player->IsBagPos(pos)) @@ -312,7 +294,7 @@ void WorldSession::HandleDestroyItemOpcode(WorldPacket& recvData) } } - Item* pItem = _player->GetItemByPos(bag, slot); + Item* pItem = _player->GetItemByPos(packet.Bag, packet.Slot); if (!pItem) { _player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); @@ -327,14 +309,14 @@ void WorldSession::HandleDestroyItemOpcode(WorldPacket& recvData) recoveryItem(pItem); - if (count) + if (packet.Count) { - uint32 i_count = count; + uint32 i_count = packet.Count; _player->DestroyItemCount(pItem, i_count, true); } else { - _player->DestroyItem(bag, slot, true); + _player->DestroyItem(packet.Bag, packet.Slot, true); } _player->SendQuestGiverStatusMultiple(); } @@ -692,15 +674,12 @@ void WorldSession::HandleItemQuerySingleOpcode(WorldPacket& recvData) } } -void WorldSession::HandleReadItem(WorldPacket& recvData) +void WorldSession::HandleReadItem(WorldPackets::Item::ReadItem& packet) { //LOG_DEBUG("network.opcode", "WORLD: CMSG_READ_ITEM"); - uint8 bag, slot; - recvData >> bag >> slot; - //LOG_DEBUG("network.opcode", "STORAGE: Read bag = {}, slot = {}", bag, slot); - Item* pItem = _player->GetItemByPos(bag, slot); + Item* pItem = _player->GetItemByPos(packet.Bag, packet.Slot); if (pItem && pItem->GetTemplate()->PageText) { @@ -725,27 +704,22 @@ void WorldSession::HandleReadItem(WorldPacket& recvData) _player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr); } -void WorldSession::HandleSellItemOpcode(WorldPacket& recvData) +void WorldSession::HandleSellItemOpcode(WorldPackets::Item::SellItem& packet) { - ObjectGuid vendorguid, itemguid; - uint32 count; - - recvData >> vendorguid >> itemguid >> count; - - if (!itemguid) + if (!packet.ItemGuid) return; - Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(vendorguid, UNIT_NPC_FLAG_VENDOR); + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.VendorGuid, UNIT_NPC_FLAG_VENDOR); if (!creature) { - LOG_DEBUG("network", "WORLD: HandleSellItemOpcode - Unit ({}) not found or you can not interact with him.", vendorguid.ToString()); - _player->SendSellError(SELL_ERR_CANT_FIND_VENDOR, nullptr, itemguid, 0); + LOG_DEBUG("network", "WORLD: HandleSellItemOpcode - Unit ({}) not found or you can not interact with him.", packet.VendorGuid.ToString()); + _player->SendSellError(SELL_ERR_CANT_FIND_VENDOR, nullptr, packet.ItemGuid, 0); return; } if (creature->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_SELL_VENDOR)) { - _player->SendSellError(SELL_ERR_CANT_SELL_TO_THIS_MERCHANT, creature, itemguid, 0); + _player->SendSellError(SELL_ERR_CANT_SELL_TO_THIS_MERCHANT, creature, packet.ItemGuid, 0); return; } @@ -753,7 +727,7 @@ void WorldSession::HandleSellItemOpcode(WorldPacket& recvData) if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - Item* pItem = _player->GetItemByGuid(itemguid); + Item* pItem = _player->GetItemByGuid(packet.ItemGuid); if (pItem) { if (!sScriptMgr->OnPlayerCanSellItem(_player, pItem, creature)) @@ -762,21 +736,21 @@ void WorldSession::HandleSellItemOpcode(WorldPacket& recvData) // prevent sell not owner item if (_player->GetGUID() != pItem->GetOwnerGUID()) { - _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, itemguid, 0); + _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGuid, 0); return; } // prevent sell non empty bag by drag-and-drop at vendor's item list if (pItem->IsNotEmptyBag()) { - _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, itemguid, 0); + _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGuid, 0); return; } // prevent sell currently looted item if (_player->GetLootGUID() == pItem->GetGUID()) { - _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, itemguid, 0); + _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGuid, 0); return; } @@ -787,16 +761,14 @@ void WorldSession::HandleSellItemOpcode(WorldPacket& recvData) return; // Therefore, no feedback to client // special case at auto sell (sell all) - if (count == 0) - { - count = pItem->GetCount(); - } + if (packet.Count == 0) + packet.Count = pItem->GetCount(); else { // prevent sell more items that exist in stack (possible only not from client) - if (count > pItem->GetCount()) + if (packet.Count > pItem->GetCount()) { - _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, itemguid, 0); + _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGuid, 0); return; } } @@ -806,11 +778,11 @@ void WorldSession::HandleSellItemOpcode(WorldPacket& recvData) { if (pProto->SellPrice > 0) { - uint32 money = pProto->SellPrice * count; + uint32 money = pProto->SellPrice * packet.Count; if (_player->GetMoney() >= MAX_MONEY_AMOUNT - money) // prevent exceeding gold limit { _player->SendEquipError(EQUIP_ERR_TOO_MUCH_GOLD, nullptr, nullptr); - _player->SendSellError(SELL_ERR_UNK, creature, itemguid, 0); + _player->SendSellError(SELL_ERR_UNK, creature, packet.ItemGuid, 0); return; } @@ -828,8 +800,8 @@ void WorldSession::HandleSellItemOpcode(WorldPacket& recvData) DurabilityCostsEntry const* dcost = sDurabilityCostsStore.LookupEntry(pProto->ItemLevel); if (!dcost) { - _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, itemguid, 0); - LOG_ERROR("network.opcode", "WORLD: HandleSellItemOpcode - Wrong item lvl {} for item {} count = {}", pProto->ItemLevel, pItem->GetEntry(), count); + _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGuid, 0); + LOG_ERROR("network.opcode", "WORLD: HandleSellItemOpcode - Wrong item lvl {} for item {} count = {}", pProto->ItemLevel, pItem->GetEntry(), packet.Count); return; } @@ -837,8 +809,8 @@ void WorldSession::HandleSellItemOpcode(WorldPacket& recvData) DurabilityQualityEntry const* dQualitymodEntry = sDurabilityQualityStore.LookupEntry(dQualitymodEntryId); if (!dQualitymodEntry) { - _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, itemguid, 0); - LOG_ERROR("network.opcode", "WORLD: HandleSellItemOpcode - Wrong dQualityModEntry {} for item {} count = {}", dQualitymodEntryId, pItem->GetEntry(), count); + _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGuid, 0); + LOG_ERROR("network.opcode", "WORLD: HandleSellItemOpcode - Wrong dQualityModEntry {} for item {} count = {}", dQualitymodEntryId, pItem->GetEntry(), packet.Count); return; } @@ -846,36 +818,30 @@ void WorldSession::HandleSellItemOpcode(WorldPacket& recvData) uint32 refund = uint32(std::ceil(LostDurability * dmultiplier * double(dQualitymodEntry->quality_mod))); if (!refund) - { refund = 1; - } //starter items can cost more to refund than vendorprice if (refund > money) - { money = 1; - } else - { money -= refund; - } } } - if (count < pItem->GetCount()) // need split items + if (packet.Count < pItem->GetCount()) // need split items { - Item* pNewItem = pItem->CloneItem(count, _player); + Item* pNewItem = pItem->CloneItem(packet.Count, _player); if (!pNewItem) { - LOG_ERROR("network.opcode", "WORLD: HandleSellItemOpcode - could not create clone of item {}; count = {}", pItem->GetEntry(), count); - _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, itemguid, 0); + LOG_ERROR("network.opcode", "WORLD: HandleSellItemOpcode - could not create clone of item {}; count = {}", pItem->GetEntry(), packet.Count); + _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGuid, 0); return; } pNewItem->SetUInt32Value(ITEM_FIELD_DURABILITY, pItem->GetUInt32Value(ITEM_FIELD_DURABILITY)); - pItem->SetCount(pItem->GetCount() - count); - _player->ItemRemovedQuestCheck(pItem->GetEntry(), count); + pItem->SetCount(pItem->GetCount() - packet.Count); + _player->ItemRemovedQuestCheck(pItem->GetEntry(), packet.Count); if (_player->IsInWorld()) pItem->SendUpdateToPlayer(_player); pItem->SetState(ITEM_CHANGED, _player); @@ -897,25 +863,20 @@ void WorldSession::HandleSellItemOpcode(WorldPacket& recvData) _player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_MONEY_FROM_VENDORS, money); } else - _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, itemguid, 0); + _player->SendSellError(SELL_ERR_CANT_SELL_ITEM, creature, packet.ItemGuid, 0); return; } } - _player->SendSellError(SELL_ERR_CANT_FIND_ITEM, creature, itemguid, 0); + _player->SendSellError(SELL_ERR_CANT_FIND_ITEM, creature, packet.ItemGuid, 0); return; } -void WorldSession::HandleBuybackItem(WorldPacket& recvData) +void WorldSession::HandleBuybackItem(WorldPackets::Item::BuybackItem& packet) { - ObjectGuid vendorguid; - uint32 slot; - - recvData >> vendorguid >> slot; - - Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(vendorguid, UNIT_NPC_FLAG_VENDOR); + Creature* creature = GetPlayer()->GetNPCIfCanInteractWith(packet.VendorGuid, UNIT_NPC_FLAG_VENDOR); if (!creature) { - LOG_DEBUG("network", "WORLD: HandleBuybackItem - Unit ({}) not found or you can not interact with him.", vendorguid.ToString()); + LOG_DEBUG("network", "WORLD: HandleBuybackItem - Unit ({}) not found or you can not interact with him.", packet.VendorGuid.ToString()); _player->SendSellError(SELL_ERR_CANT_FIND_VENDOR, nullptr, ObjectGuid::Empty, 0); return; } @@ -924,10 +885,10 @@ void WorldSession::HandleBuybackItem(WorldPacket& recvData) if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - Item* pItem = _player->GetItemFromBuyBackSlot(slot); + Item* pItem = _player->GetItemFromBuyBackSlot(packet.Slot); if (pItem) { - uint32 price = _player->GetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1 + slot - BUYBACK_SLOT_START); + uint32 price = _player->GetUInt32Value(PLAYER_FIELD_BUYBACK_PRICE_1 + packet.Slot - BUYBACK_SLOT_START); if (!_player->HasEnoughMoney(price)) { _player->SendBuyError(BUY_ERR_NOT_ENOUGHT_MONEY, creature, pItem->GetEntry(), 0); @@ -948,7 +909,7 @@ void WorldSession::HandleBuybackItem(WorldPacket& recvData) } _player->ModifyMoney(-(int32)price); - _player->RemoveItemFromBuyBackSlot(slot, false); + _player->RemoveItemFromBuyBackSlot(packet.Slot, false); _player->ItemAddedQuestCheck(pItem->GetEntry(), pItem->GetCount()); _player->StoreItem(dest, pItem, true); } @@ -960,24 +921,18 @@ void WorldSession::HandleBuybackItem(WorldPacket& recvData) _player->SendBuyError(BUY_ERR_CANT_FIND_ITEM, creature, 0, 0); } -void WorldSession::HandleBuyItemInSlotOpcode(WorldPacket& recvData) +void WorldSession::HandleBuyItemInSlotOpcode(WorldPackets::Item::BuyItemInSlot& packet) { - ObjectGuid vendorguid, bagguid; - uint32 item, slot, count; - uint8 bagslot; - - recvData >> vendorguid >> item >> slot >> bagguid >> bagslot >> count; - // client expects count starting at 1, and we send vendorslot+1 to client already - if (slot > 0) - --slot; + if (packet.Slot > 0) + --packet.Slot; else - return; // cheating + return; // cheating - uint8 bag = NULL_BAG; // init for case invalid bagGUID + uint8 bag = NULL_BAG; // init for case invalid bagGUID // find bag slot by bag guid - if (bagguid == _player->GetGUID()) + if (packet.BagGuid == _player->GetGUID()) bag = INVENTORY_SLOT_BAG_0; else { @@ -985,7 +940,7 @@ void WorldSession::HandleBuyItemInSlotOpcode(WorldPacket& recvData) { if (Bag* pBag = _player->GetBagByPos(i)) { - if (bagguid == pBag->GetGUID()) + if (packet.BagGuid == pBag->GetGUID()) { bag = i; break; @@ -998,38 +953,28 @@ void WorldSession::HandleBuyItemInSlotOpcode(WorldPacket& recvData) if (bag == NULL_BAG) return; - GetPlayer()->BuyItemFromVendorSlot(vendorguid, slot, item, count, bag, bagslot); + GetPlayer()->BuyItemFromVendorSlot(packet.VendorGuid, packet.Slot, packet.Item, packet.Count, bag, packet.BagSlot); } -void WorldSession::HandleBuyItemOpcode(WorldPacket& recvData) +void WorldSession::HandleBuyItemOpcode(WorldPackets::Item::BuyItem& packet) { - ObjectGuid vendorguid; - uint32 item, slot, count; - uint8 unk1; - - recvData >> vendorguid >> item >> slot >> count >> unk1; - // client expects count starting at 1, and we send vendorslot+1 to client already - if (slot > 0) - --slot; + if (packet.Slot > 0) + --packet.Slot; else return; // cheating - GetPlayer()->BuyItemFromVendorSlot(vendorguid, slot, item, count, NULL_BAG, NULL_SLOT); + GetPlayer()->BuyItemFromVendorSlot(packet.VendorGuid, packet.Slot, packet.Item, packet.Count, NULL_BAG, NULL_SLOT); } -void WorldSession::HandleListInventoryOpcode(WorldPacket& recvData) +void WorldSession::HandleListInventoryOpcode(WorldPackets::Item::ListInventory& packet) { - ObjectGuid guid; - - recvData >> guid; - if (!GetPlayer()->IsAlive()) return; LOG_DEBUG("network", "WORLD: Recvd CMSG_LIST_INVENTORY"); - SendListInventory(guid); + SendListInventory(packet.VendorGuid); } void WorldSession::SendListInventory(ObjectGuid vendorGuid, uint32 vendorEntry) @@ -1143,18 +1088,14 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid, uint32 vendorEntry) SendPacket(&data); } -void WorldSession::HandleAutoStoreBagItemOpcode(WorldPacket& recvData) +void WorldSession::HandleAutoStoreBagItemOpcode(WorldPackets::Item::AutoStoreBagItem& packet) { //LOG_DEBUG("network.opcode", "WORLD: CMSG_AUTOSTORE_BAG_ITEM"); - uint8 srcbag, srcslot, dstbag; - - recvData >> srcbag >> srcslot >> dstbag; - - Item* pItem = _player->GetItemByPos(srcbag, srcslot); + Item* pItem = _player->GetItemByPos(packet.SourceBag, packet.SourceSlot); if (!pItem) return; - if (!_player->IsValidPos(dstbag, NULL_SLOT, false)) // can be autostore pos + if (!_player->IsValidPos(packet.DestinationBag, NULL_SLOT, false)) // can be autostore pos { _player->SendEquipError(EQUIP_ERR_ITEM_DOESNT_GO_TO_SLOT, nullptr, nullptr); return; @@ -1174,7 +1115,7 @@ void WorldSession::HandleAutoStoreBagItemOpcode(WorldPacket& recvData) } ItemPosCountVec dest; - InventoryResult msg = _player->CanStoreItem(dstbag, NULL_SLOT, dest, pItem, false); + InventoryResult msg = _player->CanStoreItem(packet.DestinationBag, NULL_SLOT, dest, pItem, false); if (msg != EQUIP_ERR_OK) { _player->SendEquipError(msg, pItem, nullptr); @@ -1189,7 +1130,7 @@ void WorldSession::HandleAutoStoreBagItemOpcode(WorldPacket& recvData) return; } - _player->RemoveItem(srcbag, srcslot, true); + _player->RemoveItem(packet.SourceBag, packet.SourceSlot, true); _player->StoreItem(dest, pItem, true); _player->UpdateTitansGrip(); } @@ -1223,23 +1164,22 @@ void WorldSession::HandleSetAmmoOpcode(WorldPacket& recvData) void WorldSession::SendEnchantmentLog(ObjectGuid target, ObjectGuid caster, uint32 itemId, uint32 enchantId) { - WorldPacket data(SMSG_ENCHANTMENTLOG, (8 + 8 + 4 + 4)); // last check 2.0.10 - data << target.WriteAsPacked(); - data << caster.WriteAsPacked(); - data << uint32(itemId); - data << uint32(enchantId); - GetPlayer()->SendMessageToSet(&data, true); + WorldPackets::Item::EnchantmentLog enchantmentLog; + enchantmentLog.Target = target.WriteAsPacked(); + enchantmentLog.Caster = caster.WriteAsPacked(); + enchantmentLog.ItemId = itemId; + enchantmentLog.EnchantId = enchantId; + GetPlayer()->SendMessageToSet(enchantmentLog.Write(), true); } void WorldSession::SendItemEnchantTimeUpdate(ObjectGuid Playerguid, ObjectGuid Itemguid, uint32 slot, uint32 Duration) { - // last check 2.0.10 - WorldPacket data(SMSG_ITEM_ENCHANT_TIME_UPDATE, (8 + 4 + 4 + 8)); - data << Itemguid; - data << uint32(slot); - data << uint32(Duration); - data << Playerguid; - SendPacket(&data); + WorldPackets::Item::ItemEnchantTimeUpdate itemEnchantTimeUpdate; + itemEnchantTimeUpdate.ItemGuid = Itemguid; + itemEnchantTimeUpdate.Slot = slot; + itemEnchantTimeUpdate.Duration = Duration; + itemEnchantTimeUpdate.PlayerGuid = Playerguid; + SendPacket(itemEnchantTimeUpdate.Write()); } void WorldSession::HandleItemNameQueryOpcode(WorldPacket& recvData) @@ -1266,18 +1206,13 @@ void WorldSession::HandleItemNameQueryOpcode(WorldPacket& recvData) } } -void WorldSession::HandleWrapItemOpcode(WorldPacket& recvData) +void WorldSession::HandleWrapItemOpcode(WorldPackets::Item::WrapItem& packet) { LOG_DEBUG("network", "Received opcode CMSG_WRAP_ITEM"); - uint8 gift_bag, gift_slot, item_bag, item_slot; + LOG_DEBUG("network", "WRAP: receive GiftBag = {}, GiftSlot = {}, ItemBag = {}, ItemSlot = {}", packet.GiftBag, packet.GiftSlot, packet.ItemBag, packet.ItemSlot); - recvData >> gift_bag >> gift_slot; // paper - recvData >> item_bag >> item_slot; // item - - LOG_DEBUG("network", "WRAP: receive gift_bag = {}, gift_slot = {}, item_bag = {}, item_slot = {}", gift_bag, gift_slot, item_bag, item_slot); - - Item* gift = _player->GetItemByPos(gift_bag, gift_slot); + Item* gift = _player->GetItemByPos(packet.GiftBag, packet.GiftSlot); if (!gift) { _player->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, gift, nullptr); @@ -1290,7 +1225,7 @@ void WorldSession::HandleWrapItemOpcode(WorldPacket& recvData) return; } - Item* item = _player->GetItemByPos(item_bag, item_slot); + Item* item = _player->GetItemByPos(packet.ItemBag, packet.ItemSlot); if (!item) { @@ -1305,7 +1240,7 @@ void WorldSession::HandleWrapItemOpcode(WorldPacket& recvData) return; } - if (item == gift) // not possable with pacjket from real client + if (item == gift) // not possible with packet from real client { _player->SendEquipError(EQUIP_ERR_WRAPPED_CANT_BE_WRAPPED, item, nullptr); return; @@ -1399,26 +1334,19 @@ void WorldSession::HandleWrapItemOpcode(WorldPacket& recvData) _player->DestroyItemCount(gift, count, true); } -void WorldSession::HandleSocketOpcode(WorldPacket& recvData) +void WorldSession::HandleSocketOpcode(WorldPackets::Item::SocketGems& packet) { LOG_DEBUG("network", "WORLD: CMSG_SOCKET_GEMS"); - ObjectGuid item_guid; - ObjectGuid gem_guids[MAX_GEM_SOCKETS]; - - recvData >> item_guid; - if (!item_guid) + if (!packet.ItemGuid) return; - for (int i = 0; i < MAX_GEM_SOCKETS; ++i) - recvData >> gem_guids[i]; - //cheat -> tried to socket same gem multiple times - if ((gem_guids[0] && (gem_guids[0] == gem_guids[1] || gem_guids[0] == gem_guids[2])) || - (gem_guids[1] && (gem_guids[1] == gem_guids[2]))) + if ((packet.GemGuids[0] && (packet.GemGuids[0] == packet.GemGuids[1] || packet.GemGuids[0] == packet.GemGuids[2])) || + (packet.GemGuids[1] && (packet.GemGuids[1] == packet.GemGuids[2]))) return; - Item* itemTarget = _player->GetItemByGuid(item_guid); + Item* itemTarget = _player->GetItemByGuid(packet.ItemGuid); if (!itemTarget) //missing item to socket return; @@ -1431,7 +1359,7 @@ void WorldSession::HandleSocketOpcode(WorldPacket& recvData) Item* Gems[MAX_GEM_SOCKETS]; for (int i = 0; i < MAX_GEM_SOCKETS; ++i) - Gems[i] = gem_guids[i] ? _player->GetItemByGuid(gem_guids[i]) : nullptr; + Gems[i] = packet.GemGuids[i] ? _player->GetItemByGuid(packet.GemGuids[i]) : nullptr; GemPropertiesEntry const* GemProps[MAX_GEM_SOCKETS]; for (int i = 0; i < MAX_GEM_SOCKETS; ++i) //get geminfo from dbc storage @@ -1572,7 +1500,7 @@ void WorldSession::HandleSocketOpcode(WorldPacket& recvData) if (GemEnchants[i]) { itemTarget->SetEnchantment(EnchantmentSlot(SOCK_ENCHANTMENT_SLOT + i), GemEnchants[i], 0, 0, _player->GetGUID()); - if (Item* guidItem = _player->GetItemByGuid(gem_guids[i])) + if (Item* guidItem = _player->GetItemByGuid(packet.GemGuids[i])) _player->DestroyItem(guidItem->GetBagSlot(), guidItem->GetSlot(), true); } } @@ -1597,19 +1525,15 @@ void WorldSession::HandleSocketOpcode(WorldPacket& recvData) itemTarget->SendUpdateSockets(); } -void WorldSession::HandleCancelTempEnchantmentOpcode(WorldPacket& recvData) +void WorldSession::HandleCancelTempEnchantmentOpcode(WorldPackets::Item::CancelTempEnchantment& packet) { LOG_DEBUG("network", "WORLD: CMSG_CANCEL_TEMP_ENCHANTMENT"); - uint32 eslot; - - recvData >> eslot; - // apply only to equipped item - if (!Player::IsEquipmentPos(INVENTORY_SLOT_BAG_0, eslot)) + if (!Player::IsEquipmentPos(INVENTORY_SLOT_BAG_0, packet.EquipmentSlot)) return; - Item* item = GetPlayer()->GetItemByPos(INVENTORY_SLOT_BAG_0, eslot); + Item* item = GetPlayer()->GetItemByPos(INVENTORY_SLOT_BAG_0, packet.EquipmentSlot); if (!item) return; @@ -1621,14 +1545,11 @@ void WorldSession::HandleCancelTempEnchantmentOpcode(WorldPacket& recvData) item->ClearEnchantment(TEMP_ENCHANTMENT_SLOT); } -void WorldSession::HandleItemRefundInfoRequest(WorldPacket& recvData) +void WorldSession::HandleItemRefundInfoRequest(WorldPackets::Item::ItemRefundInfo& packet) { LOG_DEBUG("network", "WORLD: CMSG_ITEM_REFUND_INFO"); - ObjectGuid guid; - recvData >> guid; // item guid - - Item* item = _player->GetItemByGuid(guid); + Item* item = _player->GetItemByGuid(packet.ItemGuid); if (!item) { LOG_DEBUG("network", "Item refund: item not found!"); @@ -1638,13 +1559,11 @@ void WorldSession::HandleItemRefundInfoRequest(WorldPacket& recvData) GetPlayer()->SendRefundInfo(item); } -void WorldSession::HandleItemRefund(WorldPacket& recvData) +void WorldSession::HandleItemRefund(WorldPackets::Item::ItemRefund& packet) { LOG_DEBUG("network", "WORLD: CMSG_ITEM_REFUND"); - ObjectGuid guid; - recvData >> guid; // item guid - Item* item = _player->GetItemByGuid(guid); + Item* item = _player->GetItemByGuid(packet.ItemGuid); if (!item) { LOG_DEBUG("network", "Item refund: item not found!"); @@ -1652,7 +1571,7 @@ void WorldSession::HandleItemRefund(WorldPacket& recvData) } // Don't try to refund item currently being disenchanted - if (_player->GetLootGUID() == guid) + if (_player->GetLootGUID() == packet.ItemGuid) return; GetPlayer()->RefundItem(item); diff --git a/src/server/game/Handlers/PetitionsHandler.cpp b/src/server/game/Handlers/PetitionsHandler.cpp index 571390966..5a7e31ca1 100644 --- a/src/server/game/Handlers/PetitionsHandler.cpp +++ b/src/server/game/Handlers/PetitionsHandler.cpp @@ -183,7 +183,9 @@ void WorldSession::HandlePetitionBuyOpcode(WorldPacket& recvData) if (!charter) return; - charter->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1, charter->GetGUID().GetCounter()); + // Use a 31-bit safe petition id instead of the raw item guid + uint32 petitionId = sPetitionMgr->GeneratePetitionId(); + charter->SetUInt32Value(ITEM_FIELD_ENCHANTMENT_1_1, petitionId); // ITEM_FIELD_ENCHANTMENT_1_1 is guild/arenateam id // ITEM_FIELD_ENCHANTMENT_1_1+1 is current signatures count (showed on item) charter->SetState(ITEM_CHANGED, _player); @@ -211,16 +213,18 @@ void WorldSession::HandlePetitionBuyOpcode(WorldPacket& recvData) // xinef: petition pointer is invalid from now on CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PETITION); - stmt->SetData(0, _player->GetGUID().GetCounter()); - stmt->SetData(1, charter->GetGUID().GetCounter()); - stmt->SetData(2, name); - stmt->SetData(3, uint8(type)); + // petition_id, ownerguid, petitionguid(item guid), name, type + stmt->SetData(0, petitionId); + stmt->SetData(1, _player->GetGUID().GetCounter()); + stmt->SetData(2, charter->GetGUID().GetCounter()); + stmt->SetData(3, name); + stmt->SetData(4, uint8(type)); trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); - // xinef: fill petition store - sPetitionMgr->AddPetition(charter->GetGUID(), _player->GetGUID(), name, uint8(type)); + // xinef: fill petition store (include petitionId) + sPetitionMgr->AddPetition(charter->GetGUID(), _player->GetGUID(), name, uint8(type), petitionId); } void WorldSession::HandlePetitionShowSignOpcode(WorldPacket& recvData) @@ -249,7 +253,7 @@ void WorldSession::HandlePetitionShowSignOpcode(WorldPacket& recvData) WorldPacket data(SMSG_PETITION_SHOW_SIGNATURES, (8 + 8 + 4 + 1 + signs * 12)); data << petitionguid; // petition guid data << _player->GetGUID(); // owner guid - data << uint32(petitionguid.GetCounter()); // guild guid + data << uint32(petition->petitionId); // guild/team id (31-bit safe) data << uint8(signs); // sign's count if (signs) @@ -286,7 +290,7 @@ void WorldSession::SendPetitionQueryOpcode(ObjectGuid petitionguid) uint8 type = petition->petitionType; WorldPacket data(SMSG_PETITION_QUERY_RESPONSE, (4 + 8 + petition->petitionName.size() + 1 + 1 + 4 * 12 + 2 + 10)); - data << uint32(petitionguid.GetCounter()); // guild/team guid (in Trinity always same as petition low guid + data << uint32(petition->petitionId); // guild/team id (was item low guid) data << petition->ownerGuid; // charter owner guid data << petition->petitionName; // name (guild/arena team) data << uint8(0); // some string @@ -373,7 +377,7 @@ void WorldSession::HandlePetitionRenameOpcode(WorldPacket& recvData) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_PETITION_NAME); stmt->SetData(0, newName); - stmt->SetData(1, petitionGuid.GetCounter()); + stmt->SetData(1, petition->petitionId); CharacterDatabase.Execute(stmt); @@ -499,7 +503,7 @@ void WorldSession::HandlePetitionSignOpcode(WorldPacket& recvData) CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_PETITION_SIGNATURE); stmt->SetData(0, petition->ownerGuid.GetCounter()); - stmt->SetData(1, petitionGuid.GetCounter()); + stmt->SetData(1, petition->petitionId); stmt->SetData(2, playerGuid.GetCounter()); stmt->SetData(3, GetAccountId()); @@ -627,7 +631,7 @@ void WorldSession::HandleOfferPetitionOpcode(WorldPacket& recvData) WorldPacket data(SMSG_PETITION_SHOW_SIGNATURES, (8 + 8 + 4 + signs + signs * 12)); data << petitionguid; // petition guid data << _player->GetGUID(); // owner guid - data << uint32(petitionguid.GetCounter()); // guild guid + data << uint32(petition->petitionId); // guild/team id (31-bit safe) data << uint8(signs); // sign's count if (signs) @@ -792,12 +796,12 @@ void WorldSession::HandleTurnInPetitionOpcode(WorldPacket& recvData) CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction(); - CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_BY_GUID); - stmt->SetData(0, petitionGuid.GetCounter()); + CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_BY_ID); + stmt->SetData(0, petition->petitionId); trans->Append(stmt); - stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_SIGNATURE_BY_GUID); - stmt->SetData(0, petitionGuid.GetCounter()); + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_PETITION_SIGNATURE_BY_ID); + stmt->SetData(0, petition->petitionId); trans->Append(stmt); CharacterDatabase.CommitTransaction(trans); diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 4d1004efb..919a7f487 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -73,6 +73,8 @@ Map::Map(uint32 id, uint32 InstanceId, uint8 SpawnMode, Map* _parent) : //lets initialize visibility distance for map Map::InitVisibilityDistance(); + + _corpseUpdateTimer.SetInterval(20 * MINUTE * IN_MILLISECONDS); } // Hook called after map is created AND after added to map list @@ -116,6 +118,8 @@ void Map::AddToGrid(Creature* obj, Cell const& cell) { MapGridType* grid = GetMapGrid(cell.GridX(), cell.GridY()); grid->AddGridObject(cell.CellX(), cell.CellY(), obj); + if (obj->IsFarVisible()) + grid->AddFarVisibleObject(cell.CellX(), cell.CellY(), obj); obj->SetCurrentCell(cell); } @@ -125,6 +129,8 @@ void Map::AddToGrid(GameObject* obj, Cell const& cell) { MapGridType* grid = GetMapGrid(cell.GridX(), cell.GridY()); grid->AddGridObject(cell.CellX(), cell.CellY(), obj); + if (obj->IsFarVisible()) + grid->AddFarVisibleObject(cell.CellX(), cell.CellY(), obj); obj->SetCurrentCell(cell); } @@ -494,6 +500,8 @@ void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/) HandleDelayedVisibility(); + UpdateExpiredCorpses(t_diff); + sScriptMgr->OnMapUpdate(this, t_diff); METRIC_VALUE("map_creatures", uint64(GetObjectsStore().Size()), @@ -600,6 +608,69 @@ void Map::RemoveObjectFromMapUpdateList(WorldObject* obj) _RemoveObjectFromUpdateList(obj); } +// Used in VisibilityDistanceType::Large and VisibilityDistanceType::Gigantic +void Map::AddWorldObjectToFarVisibleMap(WorldObject* obj) +{ + if (Creature* creature = obj->ToCreature()) + { + if (!creature->IsInGrid()) + return; + + Cell curr_cell = creature->GetCurrentCell(); + MapGridType* grid = GetMapGrid(curr_cell.GridX(), curr_cell.GridY()); + grid->AddFarVisibleObject(curr_cell.CellX(), curr_cell.CellY(), creature); + } + else if (GameObject* go = obj->ToGameObject()) + { + if (!go->IsInGrid()) + return; + + Cell curr_cell = go->GetCurrentCell(); + MapGridType* grid = GetMapGrid(curr_cell.GridX(), curr_cell.GridY()); + grid->AddFarVisibleObject(curr_cell.CellX(), curr_cell.CellY(), go); + } +} + +void Map::RemoveWorldObjectFromFarVisibleMap(WorldObject* obj) +{ + if (Creature* creature = obj->ToCreature()) + { + Cell curr_cell = creature->GetCurrentCell(); + MapGridType* grid = GetMapGrid(curr_cell.GridX(), curr_cell.GridY()); + grid->RemoveFarVisibleObject(curr_cell.CellX(), curr_cell.CellY(), creature); + } + else if (GameObject* go = obj->ToGameObject()) + { + Cell curr_cell = go->GetCurrentCell(); + MapGridType* grid = GetMapGrid(curr_cell.GridX(), curr_cell.GridY()); + grid->RemoveFarVisibleObject(curr_cell.CellX(), curr_cell.CellY(), go); + } +} + +// Used in VisibilityDistanceType::Infinite +void Map::AddWorldObjectToZoneWideVisibleMap(uint32 zoneId, WorldObject* obj) +{ + _zoneWideVisibleWorldObjectsMap[zoneId].insert(obj); +} + +void Map::RemoveWorldObjectFromZoneWideVisibleMap(uint32 zoneId, WorldObject* obj) +{ + ZoneWideVisibleWorldObjectsMap::iterator itr = _zoneWideVisibleWorldObjectsMap.find(zoneId); + if (itr == _zoneWideVisibleWorldObjectsMap.end()) + return; + + itr->second.erase(obj); +} + +ZoneWideVisibleWorldObjectsSet const* Map::GetZoneWideVisibleWorldObjectsForZone(uint32 zoneId) const +{ + ZoneWideVisibleWorldObjectsMap::const_iterator itr = _zoneWideVisibleWorldObjectsMap.find(zoneId); + if (itr == _zoneWideVisibleWorldObjectsMap.end()) + return nullptr; + + return &itr->second; +} + void Map::HandleDelayedVisibility() { if (i_objectsForDelayedVisibility.empty()) @@ -652,6 +723,8 @@ void Map::RemoveFromMap(T* obj, bool remove) obj->RemoveFromWorld(); obj->RemoveFromGrid(); + if (obj->IsFarVisible()) + RemoveWorldObjectFromFarVisibleMap(obj); obj->ResetMap(); @@ -848,6 +921,11 @@ void Map::MoveAllCreaturesInMoveList() Cell const& old_cell = c->GetCurrentCell(); Cell new_cell(c->GetPositionX(), c->GetPositionY()); + if (c->IsFarVisible()) + { + // Removes via GetCurrentCell, added back in AddToGrid + RemoveWorldObjectFromFarVisibleMap(c); + } c->RemoveFromGrid(); if (old_cell.DiffGrid(new_cell)) @@ -878,6 +956,12 @@ void Map::MoveAllGameObjectsInMoveList() Cell const& old_cell = go->GetCurrentCell(); Cell new_cell(go->GetPositionX(), go->GetPositionY()); + if (go->IsFarVisible()) + { + // Removes via GetCurrentCell, added back in AddToGrid + RemoveWorldObjectFromFarVisibleMap(go); + } + go->RemoveFromGrid(); if (old_cell.DiffGrid(new_cell)) EnsureGridLoaded(new_cell); @@ -1580,6 +1664,17 @@ void Map::SendInitSelf(Player* player) player->SendDirectMessage(&packet); } +void Map::UpdateExpiredCorpses(uint32 const diff) +{ + _corpseUpdateTimer.Update(diff); + if (!_corpseUpdateTimer.Passed()) + return; + + RemoveOldCorpses(); + + _corpseUpdateTimer.Reset(); +} + void Map::SendInitTransports(Player* player) { if (_transports.empty()) diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index f43111633..c875e4eaa 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -147,6 +147,8 @@ struct ZoneDynamicInfo typedef std::map CreatureGroupHolderType; typedef std::unordered_map ZoneDynamicInfoMap; typedef std::unordered_set TransportsContainer; +typedef std::unordered_set ZoneWideVisibleWorldObjectsSet; +typedef std::unordered_map ZoneWideVisibleWorldObjectsMap; enum EncounterCreditType : uint8 { @@ -443,6 +445,8 @@ public: void SendZoneDynamicInfo(Player* player); void SendInitSelf(Player* player); + void UpdateExpiredCorpses(uint32 const diff); + void PlayDirectSoundToMap(uint32 soundId, uint32 zoneId = 0); void SetZoneMusic(uint32 zoneId, uint32 musicId); void SetZoneWeather(uint32 zoneId, WeatherState weatherId, float weatherGrade); @@ -494,6 +498,12 @@ public: typedef std::vector UpdatableObjectList; typedef std::unordered_set PendingAddUpdatableObjectList; + void AddWorldObjectToFarVisibleMap(WorldObject* obj); + void RemoveWorldObjectFromFarVisibleMap(WorldObject* obj); + void AddWorldObjectToZoneWideVisibleMap(uint32 zoneId, WorldObject* obj); + void RemoveWorldObjectFromZoneWideVisibleMap(uint32 zoneId, WorldObject* obj); + ZoneWideVisibleWorldObjectsSet const* GetZoneWideVisibleWorldObjectsForZone(uint32 zoneId) const; + private: template void InitializeObject(T* obj); @@ -576,6 +586,8 @@ private: ZoneDynamicInfoMap _zoneDynamicInfo; uint32 _defaultLight; + IntervalTimer _corpseUpdateTimer; + template inline ObjectGuidGeneratorBase& GetGuidSequenceGenerator() { @@ -599,6 +611,7 @@ private: UpdatableObjectList _updatableObjectList; PendingAddUpdatableObjectList _pendingAddUpdatableObjectList; IntervalTimer _updatableObjectListRecheckTimer; + ZoneWideVisibleWorldObjectsMap _zoneWideVisibleWorldObjectsMap; }; enum InstanceResetMethod diff --git a/src/server/game/Petitions/PetitionMgr.cpp b/src/server/game/Petitions/PetitionMgr.cpp index a3c5c69ff..7e946923e 100644 --- a/src/server/game/Petitions/PetitionMgr.cpp +++ b/src/server/game/Petitions/PetitionMgr.cpp @@ -41,8 +41,9 @@ void PetitionMgr::LoadPetitions() { uint32 oldMSTime = getMSTime(); PetitionStore.clear(); + PetitionIdToItemGuid.clear(); - QueryResult result = CharacterDatabase.Query("SELECT ownerguid, petitionguid, name, type FROM petition"); + QueryResult result = CharacterDatabase.Query("SELECT ownerguid, petitionguid, name, type, petition_id FROM petition"); if (!result) { LOG_WARN("server.loading", ">> Loaded 0 Petitions!"); @@ -51,13 +52,24 @@ void PetitionMgr::LoadPetitions() } uint32 count = 0; + uint32 maxId = 0; do { Field* fields = result->Fetch(); - AddPetition(ObjectGuid::Create(fields[1].Get()), ObjectGuid::Create(fields[0].Get()), fields[2].Get(), fields[3].Get()); + uint32 itemLow = fields[1].Get(); + uint32 petitionId = fields[4].Get(); + ObjectGuid itemGuid = ObjectGuid::Create(itemLow); + ObjectGuid ownerGuid = ObjectGuid::Create(fields[0].Get()); + AddPetition(itemGuid, ownerGuid, fields[2].Get(), fields[3].Get(), petitionId); + PetitionIdToItemGuid[petitionId] = itemGuid; + if (petitionId > maxId) + maxId = petitionId; ++count; } while (result->NextRow()); + // initialize next id (keep within 31-bit safe range) + _nextPetitionId = std::min(std::max(maxId + 1, 1), 0x7FFFFFFFu); + LOG_INFO("server.loading", ">> Loaded {} Petitions in {} ms", count, GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); } @@ -67,7 +79,7 @@ void PetitionMgr::LoadSignatures() uint32 oldMSTime = getMSTime(); SignatureStore.clear(); - QueryResult result = CharacterDatabase.Query("SELECT petitionguid, playerguid, player_account FROM petition_sign"); + QueryResult result = CharacterDatabase.Query("SELECT petition_id, playerguid, player_account FROM petition_sign"); if (!result) { LOG_WARN("server.loading", ">> Loaded 0 Petition signs!"); @@ -79,7 +91,11 @@ void PetitionMgr::LoadSignatures() do { Field* fields = result->Fetch(); - AddSignature(ObjectGuid::Create(fields[0].Get()), fields[2].Get(), ObjectGuid::Create(fields[1].Get())); + uint32 petitionId = fields[0].Get(); + auto it = PetitionIdToItemGuid.find(petitionId); + if (it == PetitionIdToItemGuid.end()) + continue; // orphan signature, skip + AddSignature(it->second, fields[2].Get(), ObjectGuid::Create(fields[1].Get())); ++count; } while (result->NextRow()); @@ -87,10 +103,11 @@ void PetitionMgr::LoadSignatures() LOG_INFO("server.loading", " "); } -void PetitionMgr::AddPetition(ObjectGuid petitionGUID, ObjectGuid ownerGuid, std::string const& name, uint8 type) +void PetitionMgr::AddPetition(ObjectGuid petitionGUID, ObjectGuid ownerGuid, std::string const& name, uint8 type, uint32 petitionId) { Petition& p = PetitionStore[petitionGUID]; p.petitionGuid = petitionGUID; + p.petitionId = petitionId; p.ownerGuid = ownerGuid; p.petitionName = name; p.petitionType = type; @@ -102,7 +119,12 @@ void PetitionMgr::AddPetition(ObjectGuid petitionGUID, ObjectGuid ownerGuid, std void PetitionMgr::RemovePetition(ObjectGuid petitionGUID) { - PetitionStore.erase(petitionGUID); + auto it = PetitionStore.find(petitionGUID); + if (it != PetitionStore.end()) + { + PetitionIdToItemGuid.erase(it->second.petitionId); + PetitionStore.erase(it); + } // remove signatures SignatureStore.erase(petitionGUID); @@ -143,6 +165,15 @@ Petition const* PetitionMgr::GetPetition(ObjectGuid petitionGUID) const return nullptr; } +Petition const* PetitionMgr::GetPetitionById(uint32 petitionId) const +{ + auto it = PetitionIdToItemGuid.find(petitionId); + if (it == PetitionIdToItemGuid.end()) + return nullptr; + + return GetPetition(it->second); +} + Petition const* PetitionMgr::GetPetitionByOwnerWithType(ObjectGuid ownerGuid, uint8 type) const { for (PetitionContainer::const_iterator itr = PetitionStore.begin(); itr != PetitionStore.end(); ++itr) @@ -189,3 +220,37 @@ void PetitionMgr::RemoveSignaturesByPlayerAndType(ObjectGuid playerGuid, uint8 t itr->second.signatureMap.erase(signItr); } } + +uint32 PetitionMgr::GeneratePetitionId() +{ + // ensure 31-bit range and avoid collisions with already loaded petitions + if (_nextPetitionId == 0 || _nextPetitionId >= 0x7FFFFFFF) + _nextPetitionId = 1; + + // find first free id + while (PetitionIdToItemGuid.count(_nextPetitionId)) + { + ++_nextPetitionId; + if (_nextPetitionId >= 0x7FFFFFFF) + _nextPetitionId = 1; + } + + uint32 id = _nextPetitionId++; + if (_nextPetitionId >= 0x7FFFFFFF) + _nextPetitionId = 1; + return id; +} + +uint32 PetitionMgr::GetPetitionIdByItemGuid(ObjectGuid petitionItemGuid) const +{ + Petition const* p = GetPetition(petitionItemGuid); + return p ? p->petitionId : 0; +} + +ObjectGuid PetitionMgr::GetItemGuidByPetitionId(uint32 petitionId) const +{ + auto it = PetitionIdToItemGuid.find(petitionId); + if (it == PetitionIdToItemGuid.end()) + return ObjectGuid::Empty; + return it->second; +} diff --git a/src/server/game/Petitions/PetitionMgr.h b/src/server/game/Petitions/PetitionMgr.h index 97491fb3a..cd7d47ead 100644 --- a/src/server/game/Petitions/PetitionMgr.h +++ b/src/server/game/Petitions/PetitionMgr.h @@ -20,6 +20,7 @@ #include "ObjectGuid.h" +#include #include #define CHARTER_DISPLAY_ID 16161 @@ -37,14 +38,21 @@ typedef std::map SignatureMap; struct Petition { + // Item GUID of the charter item (used to find the item in inventory) ObjectGuid petitionGuid; + // New 31-bit safe petition identifier used in packets/DB relations + uint32 petitionId; + // Owner character GUID ObjectGuid ownerGuid; + // Petition type (guild / arena) uint8 petitionType; + // Name associated with the petition (guild/arena name) std::string petitionName; }; struct Signatures { + // Keep keying by item-guid for backward compatibility in code paths ObjectGuid petitionGuid; SignatureMap signatureMap; }; @@ -65,10 +73,11 @@ public: void LoadSignatures(); // Petitions - void AddPetition(ObjectGuid petitionGUID, ObjectGuid ownerGuid, std::string const& name, uint8 type); + void AddPetition(ObjectGuid petitionGUID, ObjectGuid ownerGuid, std::string const& name, uint8 type, uint32 petitionId); void RemovePetition(ObjectGuid petitionGUID); void RemovePetitionByOwnerAndType(ObjectGuid ownerGuid, uint8 type); Petition const* GetPetition(ObjectGuid petitionGUID) const; + Petition const* GetPetitionById(uint32 petitionId) const; Petition const* GetPetitionByOwnerWithType(ObjectGuid ownerGuid, uint8 type) const; PetitionContainer* GetPetitionStore() { return &PetitionStore; } @@ -79,9 +88,17 @@ public: Signatures const* GetSignature(ObjectGuid petitionGUID) const; SignatureContainer* GetSignatureStore() { return &SignatureStore; } + uint32 GeneratePetitionId(); + uint32 GetPetitionIdByItemGuid(ObjectGuid petitionItemGuid) const; + ObjectGuid GetItemGuidByPetitionId(uint32 petitionId) const; + protected: PetitionContainer PetitionStore; SignatureContainer SignatureStore; + // Mapping id -> item-guid to support DB-id lookups + std::unordered_map PetitionIdToItemGuid; + // Next petition id (kept < 2^31) + uint32 _nextPetitionId = 1; }; #define sPetitionMgr PetitionMgr::instance() diff --git a/src/server/game/Server/Packets/AllPackets.h b/src/server/game/Server/Packets/AllPackets.h index 30aecbb6f..498dafb90 100644 --- a/src/server/game/Server/Packets/AllPackets.h +++ b/src/server/game/Server/Packets/AllPackets.h @@ -24,6 +24,7 @@ #include "CombatLogPackets.h" #include "CombatPackets.h" #include "GuildPackets.h" +#include "ItemPackets.h" #include "LFGPackets.h" #include "MiscPackets.h" #include "PetPackets.h" diff --git a/src/server/game/Server/Packets/ItemPackets.cpp b/src/server/game/Server/Packets/ItemPackets.cpp new file mode 100644 index 000000000..e86ae3879 --- /dev/null +++ b/src/server/game/Server/Packets/ItemPackets.cpp @@ -0,0 +1,163 @@ +/* + * 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 . + */ + +#include "ItemPackets.h" + +void WorldPackets::Item::SplitItem::Read() +{ + _worldPacket >> SourceBag; + _worldPacket >> SourceSlot; + _worldPacket >> DestinationBag; + _worldPacket >> DestinationSlot; + _worldPacket >> Count; +} + +void WorldPackets::Item::SwapInventoryItem::Read() +{ + _worldPacket >> DestinationSlot; + _worldPacket >> SourceSlot; +} + +void WorldPackets::Item::AutoEquipItemSlot::Read() +{ + _worldPacket >> ItemGuid; + _worldPacket >> DestinationSlot; +} + +void WorldPackets::Item::SwapItem::Read() +{ + _worldPacket >> DestinationBag; + _worldPacket >> DestinationSlot; + _worldPacket >> SourceBag; + _worldPacket >> SourceSlot; +} + +void WorldPackets::Item::AutoEquipItem::Read() +{ + _worldPacket >> SourceBag; + _worldPacket >> SourceSlot; +} + +void WorldPackets::Item::DestroyItem::Read() +{ + _worldPacket >> Bag; + _worldPacket >> Slot; + _worldPacket >> Count; + _worldPacket >> Data1; + _worldPacket >> Data2; + _worldPacket >> Data3; +} + +void WorldPackets::Item::ReadItem::Read() +{ + _worldPacket >> Bag; + _worldPacket >> Slot; +} + +void WorldPackets::Item::SellItem::Read() +{ + _worldPacket >> VendorGuid; + _worldPacket >> ItemGuid; + _worldPacket >> Count; +} + +void WorldPackets::Item::BuybackItem::Read() +{ + _worldPacket >> VendorGuid; + _worldPacket >> Slot; +} + +void WorldPackets::Item::BuyItemInSlot::Read() +{ + _worldPacket >> VendorGuid; + _worldPacket >> Item; + _worldPacket >> Slot; + _worldPacket >> BagGuid; + _worldPacket >> BagSlot; + _worldPacket >> Count; +} + +void WorldPackets::Item::BuyItem::Read() +{ + _worldPacket >> VendorGuid; + _worldPacket >> Item; + _worldPacket >> Slot; + _worldPacket >> Count; + _worldPacket >> Unk; +} + +void WorldPackets::Item::ListInventory::Read() +{ + _worldPacket >> VendorGuid; +} + +void WorldPackets::Item::AutoStoreBagItem::Read() +{ + _worldPacket >> SourceBag; + _worldPacket >> SourceSlot; + _worldPacket >> DestinationBag; +} + +WorldPacket const* WorldPackets::Item::EnchantmentLog::Write() +{ + _worldPacket << Target; + _worldPacket << Caster; + _worldPacket << ItemId; + _worldPacket << EnchantId; + + return &_worldPacket; +} + +WorldPacket const* WorldPackets::Item::ItemEnchantTimeUpdate::Write() +{ + _worldPacket << ItemGuid; + _worldPacket << Slot; + _worldPacket << Duration; + _worldPacket << PlayerGuid; + + return &_worldPacket; +} + +void WorldPackets::Item::WrapItem::Read() +{ + _worldPacket >> GiftBag; + _worldPacket >> GiftSlot; + _worldPacket >> ItemBag; + _worldPacket >> ItemSlot; +} + +void WorldPackets::Item::SocketGems::Read() +{ + _worldPacket >> ItemGuid; + for (int i = 0; i < MAX_GEM_SOCKETS; ++i) + _worldPacket >> GemGuids[i]; +} + +void WorldPackets::Item::CancelTempEnchantment::Read() +{ + _worldPacket >> EquipmentSlot; +} + +void WorldPackets::Item::ItemRefundInfo::Read() +{ + _worldPacket >> ItemGuid; +} + +void WorldPackets::Item::ItemRefund::Read() +{ + _worldPacket >> ItemGuid; +} diff --git a/src/server/game/Server/Packets/ItemPackets.h b/src/server/game/Server/Packets/ItemPackets.h new file mode 100644 index 000000000..04d884188 --- /dev/null +++ b/src/server/game/Server/Packets/ItemPackets.h @@ -0,0 +1,272 @@ +/* + * 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 . + */ + +#ifndef ItemPackets_h__ +#define ItemPackets_h__ + +#include "Item.h" +#include "ObjectGuid.h" +#include "Packet.h" + +namespace WorldPackets +{ + namespace Item + { + class SplitItem final : public ClientPacket + { + public: + SplitItem(WorldPacket&& packet) : ClientPacket(CMSG_SPLIT_ITEM, std::move(packet)) {} + + void Read() override; + + uint8 SourceBag = 0; + uint8 SourceSlot = 0; + uint8 DestinationBag = 0; + uint8 DestinationSlot = 0; + uint32 Count = 0; + }; + + class SwapInventoryItem final : public ClientPacket + { + public: + SwapInventoryItem(WorldPacket&& packet) : ClientPacket(CMSG_SWAP_INV_ITEM, std::move(packet)) {} + + void Read() override; + + uint8 DestinationSlot = 0; + uint8 SourceSlot = 0; + }; + + class AutoEquipItemSlot final : public ClientPacket + { + public: + AutoEquipItemSlot(WorldPacket&& packet) : ClientPacket(CMSG_AUTOEQUIP_ITEM_SLOT, std::move(packet)) {} + + void Read() override; + + ObjectGuid ItemGuid; + uint8 DestinationSlot = 0; + }; + + class SwapItem final : public ClientPacket + { + public: + SwapItem(WorldPacket&& packet) : ClientPacket(CMSG_SWAP_ITEM, std::move(packet)) {} + + void Read() override; + + uint8 DestinationBag = 0; + uint8 DestinationSlot = 0; + uint8 SourceBag = 0; + uint8 SourceSlot = 0; + }; + + class AutoEquipItem final : public ClientPacket + { + public: + AutoEquipItem(WorldPacket&& packet) : ClientPacket(CMSG_AUTOEQUIP_ITEM, std::move(packet)) {} + + void Read() override; + + uint8 SourceBag = 0; + uint8 SourceSlot = 0; + }; + + class DestroyItem final : public ClientPacket + { + public: + DestroyItem(WorldPacket&& packet) : ClientPacket(CMSG_DESTROYITEM, std::move(packet)) {} + + void Read() override; + + uint8 Bag = 0; + uint8 Slot = 0; + uint8 Count = 0; + uint8 Data1 = 0; + uint8 Data2 = 0; + uint8 Data3 = 0; + }; + + class ReadItem final : public ClientPacket + { + public: + ReadItem(WorldPacket&& packet) : ClientPacket(CMSG_READ_ITEM, std::move(packet)) {} + + void Read() override; + + uint8 Bag = 0; + uint8 Slot = 0; + }; + + class SellItem final : public ClientPacket + { + public: + SellItem(WorldPacket&& packet) : ClientPacket(CMSG_SELL_ITEM, std::move(packet)) {} + + void Read() override; + + ObjectGuid VendorGuid; + ObjectGuid ItemGuid; + uint32 Count = 0; + }; + + class BuybackItem final : public ClientPacket + { + public: + BuybackItem(WorldPacket&& packet) : ClientPacket(CMSG_BUYBACK_ITEM, std::move(packet)) {} + + void Read() override; + + ObjectGuid VendorGuid; + uint32 Slot = 0; + }; + + class BuyItemInSlot final : public ClientPacket + { + public: + BuyItemInSlot(WorldPacket&& packet) : ClientPacket(CMSG_BUY_ITEM_IN_SLOT, std::move(packet)) {} + + void Read() override; + + ObjectGuid VendorGuid; + uint32 Item = 0; + uint32 Slot = 0; + ObjectGuid BagGuid; + uint8 BagSlot = 0; + uint32 Count = 0; + }; + + class BuyItem final : public ClientPacket + { + public: + BuyItem(WorldPacket&& packet) : ClientPacket(CMSG_BUY_ITEM, std::move(packet)) {} + + void Read() override; + + ObjectGuid VendorGuid; + uint32 Item = 0; + uint32 Slot = 0; + uint32 Count = 0; + uint8 Unk = 0; + }; + + class ListInventory final : public ClientPacket + { + public: + ListInventory(WorldPacket&& packet) : ClientPacket(CMSG_LIST_INVENTORY, std::move(packet)) {} + + void Read() override; + + ObjectGuid VendorGuid; + }; + + class AutoStoreBagItem final : public ClientPacket + { + public: + AutoStoreBagItem(WorldPacket&& packet) : ClientPacket(CMSG_AUTOSTORE_BAG_ITEM, std::move(packet)) {} + + void Read() override; + + uint8 SourceBag = 0; + uint8 SourceSlot = 0; + uint8 DestinationBag = 0; + }; + + class EnchantmentLog final : public ServerPacket + { + public: + EnchantmentLog() : ServerPacket(SMSG_ENCHANTMENTLOG, 8 + 8 + 4 + 4) {} + + WorldPacket const* Write() override; + + PackedGuid Target; + PackedGuid Caster; + uint32 ItemId = 0; + uint32 EnchantId = 0; + }; + + class ItemEnchantTimeUpdate final : public ServerPacket + { + public: + ItemEnchantTimeUpdate() : ServerPacket(SMSG_ITEM_ENCHANT_TIME_UPDATE, 8 + 4 + 4 + 8) {} + + WorldPacket const* Write() override; + + // last check 2.0.10 + ObjectGuid ItemGuid; + uint32 Slot = 0; + uint32 Duration = 0; + ObjectGuid PlayerGuid; + }; + + class WrapItem final : public ClientPacket + { + public: + WrapItem(WorldPacket&& packet) : ClientPacket(CMSG_WRAP_ITEM, std::move(packet)) {} + + void Read() override; + + uint8 GiftBag = 0; + uint8 GiftSlot = 0; + uint8 ItemBag = 0; + uint8 ItemSlot = 0; + }; + + class SocketGems final : public ClientPacket + { + public: + SocketGems(WorldPacket&& packet) : ClientPacket(CMSG_SOCKET_GEMS, std::move(packet)) {} + + void Read() override; + + ObjectGuid ItemGuid; + ObjectGuid GemGuids[MAX_GEM_SOCKETS]; + }; + + class CancelTempEnchantment final : public ClientPacket + { + public: + CancelTempEnchantment(WorldPacket&& packet) : ClientPacket(CMSG_CANCEL_TEMP_ENCHANTMENT, std::move(packet)) {} + + void Read() override; + + uint32 EquipmentSlot = 0; + }; + + class ItemRefundInfo final : public ClientPacket + { + public: + ItemRefundInfo(WorldPacket&& packet) : ClientPacket(CMSG_ITEM_REFUND_INFO, std::move(packet)) {} + + void Read() override; + + ObjectGuid ItemGuid; + }; + + class ItemRefund final : public ClientPacket + { + public: + ItemRefund(WorldPacket&& packet) : ClientPacket(CMSG_ITEM_REFUND, std::move(packet)) {} + + void Read() override; + + ObjectGuid ItemGuid; + }; + } +} + +#endif // ItemPackets_h__ diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index 4e99a0a65..2b7a08047 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -167,6 +167,28 @@ namespace WorldPackets class TimeQuery; class CorpseMapPositionQuery; } + + namespace Item + { + class SplitItem; + class SwapInventoryItem; + class AutoEquipItemSlot; + class SwapItem; + class AutoEquipItem; + class DestroyItem; + class ReadItem; + class SellItem; + class BuybackItem; + class BuyItemInSlot; + class BuyItem; + class ListInventory; + class AutoStoreBagItem; + class WrapItem; + class SocketGems; + class CancelTempEnchantment; + class ItemRefundInfo; + class ItemRefund; + } } enum AccountDataType @@ -830,21 +852,21 @@ public: // opcodes handlers void HandleQueryNextMailTime(WorldPacket& recvData); void HandleCancelChanneling(WorldPacket& recvData); - void HandleSplitItemOpcode(WorldPacket& recvPacket); - void HandleSwapInvItemOpcode(WorldPacket& recvPacket); - void HandleDestroyItemOpcode(WorldPacket& recvPacket); - void HandleAutoEquipItemOpcode(WorldPacket& recvPacket); + void HandleSplitItemOpcode(WorldPackets::Item::SplitItem& packet); + void HandleSwapInvItemOpcode(WorldPackets::Item::SwapInventoryItem& packet); + void HandleDestroyItemOpcode(WorldPackets::Item::DestroyItem& packet); + void HandleAutoEquipItemOpcode(WorldPackets::Item::AutoEquipItem& packet); void HandleItemQuerySingleOpcode(WorldPacket& recvPacket); - void HandleSellItemOpcode(WorldPacket& recvPacket); - void HandleBuyItemInSlotOpcode(WorldPacket& recvPacket); - void HandleBuyItemOpcode(WorldPacket& recvPacket); - void HandleListInventoryOpcode(WorldPacket& recvPacket); - void HandleAutoStoreBagItemOpcode(WorldPacket& recvPacket); - void HandleReadItem(WorldPacket& recvPacket); - void HandleAutoEquipItemSlotOpcode(WorldPacket& recvPacket); - void HandleSwapItem(WorldPacket& recvPacket); - void HandleBuybackItem(WorldPacket& recvPacket); - void HandleWrapItemOpcode(WorldPacket& recvPacket); + void HandleSellItemOpcode(WorldPackets::Item::SellItem& packet); + void HandleBuyItemInSlotOpcode(WorldPackets::Item::BuyItemInSlot& packet); + void HandleBuyItemOpcode(WorldPackets::Item::BuyItem& packet); + void HandleListInventoryOpcode(WorldPackets::Item::ListInventory& packet); + void HandleAutoStoreBagItemOpcode(WorldPackets::Item::AutoStoreBagItem& packet); + void HandleReadItem(WorldPackets::Item::ReadItem& packet); + void HandleAutoEquipItemSlotOpcode(WorldPackets::Item::AutoEquipItemSlot& packet); + void HandleSwapItem(WorldPackets::Item::SwapItem& packet); + void HandleBuybackItem(WorldPackets::Item::BuybackItem& packet); + void HandleWrapItemOpcode(WorldPackets::Item::WrapItem& packet); void HandleAttackSwingOpcode(WorldPacket& recvPacket); void HandleAttackStopOpcode(WorldPacket& recvPacket); @@ -1034,12 +1056,12 @@ public: // opcodes handlers void HandleRequestPetInfo(WorldPackets::Pet::RequestPetInfo& packet); // Socket gem - void HandleSocketOpcode(WorldPacket& recvData); + void HandleSocketOpcode(WorldPackets::Item::SocketGems& packet); - void HandleCancelTempEnchantmentOpcode(WorldPacket& recvData); + void HandleCancelTempEnchantmentOpcode(WorldPackets::Item::CancelTempEnchantment& packet); - void HandleItemRefundInfoRequest(WorldPacket& recvData); - void HandleItemRefund(WorldPacket& recvData); + void HandleItemRefundInfoRequest(WorldPackets::Item::ItemRefundInfo& packet); + void HandleItemRefund(WorldPackets::Item::ItemRefund& packet); void HandleChannelVoiceOnOpcode(WorldPacket& recvData); void HandleVoiceSessionEnableOpcode(WorldPacket& recvData); diff --git a/src/server/game/Spells/SpellInfoCorrections.cpp b/src/server/game/Spells/SpellInfoCorrections.cpp index 4ba8f62c6..e5360809f 100644 --- a/src/server/game/Spells/SpellInfoCorrections.cpp +++ b/src/server/game/Spells/SpellInfoCorrections.cpp @@ -5143,6 +5143,14 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(21); // -1 }); + ApplySpellFix({ + 28032, // Zap Crystal + 28056, // Zap Crystal Corpse + }, [](SpellInfo* spellInfo) + { + spellInfo->AttributesEx3 |= SPELL_ATTR3_ALWAYS_HIT; + }); + for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i) { SpellInfo* spellInfo = mSpellInfoMap[i]; diff --git a/src/server/game/World/IWorld.h b/src/server/game/World/IWorld.h index 3ef1bfdb1..025d23e39 100644 --- a/src/server/game/World/IWorld.h +++ b/src/server/game/World/IWorld.h @@ -114,7 +114,6 @@ public: virtual void ResetEventSeasonalQuests(uint16 event_id) = 0; [[nodiscard]] virtual std::string const& GetRealmName() const = 0; virtual void SetRealmName(std::string name) = 0; - virtual void RemoveOldCorpses() = 0; virtual SQLQueryHolderCallback& AddQueryHolderCallback(SQLQueryHolderCallback&& callback) = 0; }; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index b98e4d32b..9cb8cef47 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -889,8 +889,6 @@ void World::SetInitialWorldSettings() _timers[WUPDATE_UPTIME].SetInterval(getIntConfig(CONFIG_UPTIME_UPDATE)*MINUTE * IN_MILLISECONDS); //Update "uptime" table based on configuration entry in minutes. - _timers[WUPDATE_CORPSES].SetInterval(20 * MINUTE * IN_MILLISECONDS); - //erase corpses every 20 minutes _timers[WUPDATE_CLEANDB].SetInterval(getIntConfig(CONFIG_LOGDB_CLEARINTERVAL)*MINUTE * IN_MILLISECONDS); // clean logs table every 14 days by default _timers[WUPDATE_AUTOBROADCAST].SetInterval(getIntConfig(CONFIG_AUTOBROADCAST_INTERVAL)); @@ -1281,18 +1279,6 @@ void World::Update(uint32 diff) LoginDatabase.Execute(stmt); } - ///- Erase corpses once every 20 minutes - if (_timers[WUPDATE_CORPSES].Passed()) - { - METRIC_TIMER("world_update_time", METRIC_TAG("type", "Remove old corpses")); - _timers[WUPDATE_CORPSES].Reset(); - - sMapMgr->DoForAllMaps([](Map* map) - { - map->RemoveOldCorpses(); - }); - } - ///- Process Game events when necessary if (_timers[WUPDATE_EVENTS].Passed()) { @@ -1835,11 +1821,6 @@ SQLQueryHolderCallback& World::AddQueryHolderCallback(SQLQueryHolderCallback&& c return _queryHolderProcessor.AddCallback(std::move(callback)); } -void World::RemoveOldCorpses() -{ - _timers[WUPDATE_CORPSES].SetCurrent(_timers[WUPDATE_CORPSES].GetInterval()); -} - bool World::IsPvPRealm() const { return getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_PVP || getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_RPPVP || getIntConfig(CONFIG_GAME_TYPE) == REALM_TYPE_FFA_PVP; diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 228a97fe1..94351f825 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -60,7 +60,6 @@ enum WorldTimers { WUPDATE_WEATHERS, WUPDATE_UPTIME, - WUPDATE_CORPSES, WUPDATE_EVENTS, WUPDATE_CLEANDB, WUPDATE_AUTOBROADCAST, @@ -244,8 +243,6 @@ public: [[nodiscard]] std::string const& GetRealmName() const override { return _realmName; } // pussywizard void SetRealmName(std::string name) override { _realmName = name; } // pussywizard - void RemoveOldCorpses() override; - protected: void _UpdateGameTime(); // callback for UpdateRealmCharacters diff --git a/src/server/game/World/WorldState.cpp b/src/server/game/World/WorldState.cpp index a1f71a2a5..106e8fd99 100644 --- a/src/server/game/World/WorldState.cpp +++ b/src/server/game/World/WorldState.cpp @@ -1072,8 +1072,8 @@ std::string WorldState::GetScourgeInvasionPrintout() { TimePoint tp = m_siData.m_timers[timerId]; std::string timerStr; - if (tp.time_since_epoch().count() == 0) - timerStr = "0 (not set)"; + if (tp == TimePoint()) + timerStr = "Not set"; else if (tp <= now) timerStr = "Elapsed"; else @@ -1420,10 +1420,15 @@ void ScourgeInvasionData::Reset() 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()) + " "; + for (TimePoint& timer : m_timers) + { + if (timer == TimePoint()) + output += "0 "; + else + output += std::to_string(std::chrono::duration_cast(timer.time_since_epoch()).count()) + " "; + } output += std::to_string(m_battlesWon) + " " + std::to_string(m_lastAttackZone) + " "; - for (auto& remaining : m_remaining) + for (uint32& remaining : m_remaining) output += std::to_string(remaining) + " "; return output; } diff --git a/src/server/scripts/Commands/cs_debug.cpp b/src/server/scripts/Commands/cs_debug.cpp index a18e01e0b..40bb89b93 100644 --- a/src/server/scripts/Commands/cs_debug.cpp +++ b/src/server/scripts/Commands/cs_debug.cpp @@ -99,7 +99,8 @@ public: { "objectcount", HandleDebugObjectCountCommand, SEC_ADMINISTRATOR, Console::Yes}, { "dummy", HandleDebugDummyCommand, SEC_ADMINISTRATOR, Console::No }, { "mapdata", HandleDebugMapDataCommand, SEC_ADMINISTRATOR, Console::No }, - { "boundary", HandleDebugBoundaryCommand, SEC_ADMINISTRATOR, Console::No } + { "boundary", HandleDebugBoundaryCommand, SEC_ADMINISTRATOR, Console::No }, + { "visibilitydata", HandleDebugVisibilityDataCommand, SEC_ADMINISTRATOR, Console::No } }; static ChatCommandTable commandTable = { @@ -1403,6 +1404,36 @@ public: return true; } + + static bool HandleDebugVisibilityDataCommand(ChatHandler* handler) + { + Player* player = handler->GetPlayer(); + if (!player) + return false; + + std::array objectByTypeCount = {}; + + ObjectVisibilityContainer const& objectVisibilityContainer = player->GetObjectVisibilityContainer(); + for (auto const& kvPair : *objectVisibilityContainer.GetVisibleWorldObjectsMap()) + { + WorldObject const* obj = kvPair.second; + ++objectByTypeCount[obj->GetTypeId()]; + } + + uint32 zoneWideVisibleObjectsInZone = 0; + if (ZoneWideVisibleWorldObjectsSet const* farVisibleSet = player->GetMap()->GetZoneWideVisibleWorldObjectsForZone(player->GetZoneId())) + zoneWideVisibleObjectsInZone = farVisibleSet->size(); + + handler->PSendSysMessage("Visibility Range: {}", player->GetVisibilityRange()); + handler->PSendSysMessage("Visible Creatures: {}", objectByTypeCount[TYPEID_UNIT]); + handler->PSendSysMessage("Visible Players: {}", objectByTypeCount[TYPEID_PLAYER]); + handler->PSendSysMessage("Visible GameObjects: {}", objectByTypeCount[TYPEID_GAMEOBJECT]); + handler->PSendSysMessage("Visible DynamicObjects: {}", objectByTypeCount[TYPEID_DYNAMICOBJECT]); + handler->PSendSysMessage("Visible Corpses: {}", objectByTypeCount[TYPEID_CORPSE]); + handler->PSendSysMessage("Players we are visible to: {}", objectVisibilityContainer.GetVisiblePlayersMap().size()); + handler->PSendSysMessage("Zone wide visible objects in zone: {}", zoneWideVisibleObjectsInZone); + return true; + } }; void AddSC_debug_commandscript() diff --git a/src/server/scripts/Commands/cs_event.cpp b/src/server/scripts/Commands/cs_event.cpp index 742bc6789..0f5264e8c 100644 --- a/src/server/scripts/Commands/cs_event.cpp +++ b/src/server/scripts/Commands/cs_event.cpp @@ -71,8 +71,6 @@ public: if (counter == 0) handler->SendSysMessage(LANG_NOEVENTFOUND); - handler->SetSentErrorMessage(true); - return true; } diff --git a/src/server/scripts/Commands/cs_server.cpp b/src/server/scripts/Commands/cs_server.cpp index 72bb9a2a3..737d35561 100644 --- a/src/server/scripts/Commands/cs_server.cpp +++ b/src/server/scripts/Commands/cs_server.cpp @@ -21,6 +21,7 @@ #include "GameTime.h" #include "GitRevision.h" #include "Log.h" +#include "MapMgr.h" #include "ModuleMgr.h" #include "MotdMgr.h" #include "MySQLThreading.h" @@ -101,7 +102,10 @@ public: // Triggering corpses expire check in world static bool HandleServerCorpsesCommand(ChatHandler* /*handler*/) { - sWorld->RemoveOldCorpses(); + sMapMgr->DoForAllMaps([](Map* map) + { + map->RemoveOldCorpses(); + }); return true; } diff --git a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp index a3325c58d..9c71aad73 100644 --- a/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp +++ b/src/server/scripts/EasternKingdoms/ScarletEnclave/chapter2.cpp @@ -1001,20 +1001,25 @@ class spell_chapter2_persuasive_strike : public SpellScript creature->AI()->Talk(SAY_PERSUADED3, 24s); creature->AI()->Talk(SAY_PERSUADED4, 32s); - creature->m_Events.AddEventAtOffset([creature, player] + ObjectGuid playerGuid = player->GetGUID(); + + creature->m_Events.AddEventAtOffset([creature, playerGuid] { - if (player) - sCreatureTextMgr->SendChat(creature, SAY_PERSUADED5, nullptr, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_NEUTRAL, false, player); + if (Player* caster = ObjectAccessor::GetPlayer(*creature, playerGuid)) + sCreatureTextMgr->SendChat(creature, SAY_PERSUADED5, nullptr, CHAT_MSG_ADDON, LANG_ADDON, TEXT_RANGE_NORMAL, 0, TEAM_NEUTRAL, false, caster); }, 40s); - creature->m_Events.AddEventAtOffset([creature, player] + creature->m_Events.AddEventAtOffset([creature, playerGuid] { creature->AI()->Talk(SAY_PERSUADED6); - if (player) + + if (Player* caster = ObjectAccessor::GetPlayer(*creature, playerGuid)) { - Unit::Kill(player, creature); - player->GroupEventHappens(QUEST_HOW_TO_WIN_FRIENDS, creature); + Unit::Kill(caster, creature); + caster->GroupEventHappens(QUEST_HOW_TO_WIN_FRIENDS, creature); } + else + creature->KillSelf(); }, 48s); } else diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h index c124b4457..39f15f6ab 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/azjol_nerub.h @@ -26,14 +26,17 @@ enum ANData { - DATA_KRIKTHIR_THE_GATEWATCHER_EVENT = 0, - DATA_HADRONOX_EVENT = 1, + DATA_KRIKTHIR = 0, + DATA_HADRONOX = 1, DATA_ANUBARAK_EVENT = 2, MAX_ENCOUNTERS = 3 }; enum ANIds { + NPC_WATCHER_NARJIL = 28729, + NPC_WATCHER_GASHRA = 28730, + NPC_WATCHER_SILTHIK = 28731, NPC_SKITTERING_SWARMER = 28735, NPC_SKITTERING_INFECTIOR = 28736, NPC_KRIKTHIR_THE_GATEWATCHER = 28684, diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp index daa36c306..f3f9396ba 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_hadronox.cpp @@ -84,7 +84,7 @@ public: struct boss_hadronoxAI : public BossAI { - boss_hadronoxAI(Creature* creature) : BossAI(creature, DATA_HADRONOX_EVENT) + boss_hadronoxAI(Creature* creature) : BossAI(creature, DATA_HADRONOX) { } @@ -99,7 +99,7 @@ public: { if (param == ACTION_START_EVENT) { - instance->SetBossState(DATA_HADRONOX_EVENT, IN_PROGRESS); + instance->SetBossState(DATA_HADRONOX, IN_PROGRESS); me->setActive(true); events.ScheduleEvent(EVENT_HADRONOX_MOVE1, 20s); events.ScheduleEvent(EVENT_HADRONOX_MOVE2, 40s); @@ -342,7 +342,7 @@ public: PreventDefaultAction(); Unit* owner = GetUnitOwner(); if (InstanceScript* instance = owner->GetInstanceScript()) - if (instance->GetBossState(DATA_HADRONOX_EVENT) != DONE) + if (!instance->IsBossDone(DATA_HADRONOX)) { if (!owner->HasAura(SPELL_WEB_FRONT_DOORS)) owner->CastSpell(owner, _spellEntry, true); diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp index c32f6490c..36fd31ff1 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/boss_krikthir_the_gatewatcher.cpp @@ -29,24 +29,8 @@ enum Spells SPELL_FRENZY = 28747 }; -enum Events -{ - EVENT_KRIK_START_WAVE = 1, - EVENT_KRIK_SUMMON = 2, - EVENT_KRIK_MIND_FLAY = 3, - EVENT_KRIK_CURSE = 4, - EVENT_KRIK_HEALTH_CHECK = 5, - EVENT_KRIK_ENTER_COMBAT = 6, - EVENT_KILL_TALK = 7, - EVENT_CALL_ADDS = 8, - EVENT_KRIK_CHECK_EVADE = 9 -}; - enum Npcs { - NPC_WATCHER_NARJIL = 28729, - NPC_WATCHER_GASHRA = 28730, - NPC_WATCHER_SILTHIK = 28731, NPC_WARRIOR = 28732, NPC_SKIRMISHER = 28734, NPC_SHADOWCASTER = 28733 @@ -62,6 +46,12 @@ enum Yells SAY_SEND_GROUP = 5 }; +enum MiscActions +{ + ACTION_MINION_ENGAGED = 1, + GROUP_SWARM = 1 +}; + class boss_krik_thir : public CreatureScript { public: @@ -69,18 +59,21 @@ public: struct boss_krik_thirAI : public BossAI { - boss_krik_thirAI(Creature* creature) : BossAI(creature, DATA_KRIKTHIR_THE_GATEWATCHER_EVENT) + boss_krik_thirAI(Creature* creature) : BossAI(creature, DATA_KRIKTHIR) { _initTalk = false; - } + _canTalk = true; + _minionInCombat = false; - EventMap events2; - bool _initTalk; + scheduler.SetValidator([this] + { + return !me->HasUnitState(UNIT_STATE_CASTING); + }); + } void Reset() override { BossAI::Reset(); - events2.Reset(); me->SummonCreature(NPC_WATCHER_NARJIL, 511.8f, 666.493f, 776.278f, 0.977f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000); me->SummonCreature(NPC_SHADOWCASTER, 511.63f, 672.44f, 775.71f, 0.90f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000); @@ -91,6 +84,22 @@ public: me->SummonCreature(NPC_WATCHER_SILTHIK, 543.826f, 665.123f, 776.245f, 1.55f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000); me->SummonCreature(NPC_SKIRMISHER, 547.5f, 669.96f, 776.1f, 2.3f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000); me->SummonCreature(NPC_SHADOWCASTER, 548.64f, 664.27f, 776.74f, 1.77f, TEMPSUMMON_CORPSE_TIMED_DESPAWN, 30000); + + ScheduleHealthCheckEvent(25, [&] { + DoCastSelf(SPELL_FRENZY, true); + + scheduler.CancelGroup(GROUP_SWARM); + + scheduler.Schedule(7s, 17s, GROUP_SWARM, [&](TaskContext context) + { + Talk(SAY_SWARM); + DoCastAOE(SPELL_SWARM); + context.Repeat(20s); + }); + }); + + _canTalk = true; + _minionInCombat = false; } void MoveInLineOfSight(Unit* who) override @@ -103,21 +112,25 @@ public: _initTalk = true; Talk(SAY_PREFIGHT); } + } - if (events.Empty() && who->GetPositionZ() < 785.0f) + void DoAction(int32 actionId) override + { + if (actionId == ACTION_MINION_ENGAGED && !_minionInCombat) { - events2.ScheduleEvent(EVENT_KRIK_START_WAVE, 10s); - events2.ScheduleEvent(EVENT_KRIK_START_WAVE, 40s); - events2.ScheduleEvent(EVENT_KRIK_START_WAVE, 70s); - events2.ScheduleEvent(EVENT_KRIK_ENTER_COMBAT, 100s); - events2.ScheduleEvent(EVENT_KRIK_CHECK_EVADE, 5s); + _minionInCombat = true; - events.ScheduleEvent(EVENT_KRIK_HEALTH_CHECK, 1s); - events.ScheduleEvent(EVENT_KRIK_MIND_FLAY, 13s); - events.ScheduleEvent(EVENT_KRIK_SUMMON, 17s); - events.ScheduleEvent(EVENT_KRIK_CURSE, 8s); - events.ScheduleEvent(EVENT_CALL_ADDS, 1s); - me->setActive(true); + for (Seconds const& timer : { 10s, 40s, 70s }) + { + me->m_Events.AddEventAtOffset([this] { + me->CastCustomSpell(SPELL_SUBBOSS_AGGRO_TRIGGER, SPELLVALUE_MAX_TARGETS, 1, me, true); + Talk(SAY_SEND_GROUP); + }, timer); + } + + me->m_Events.AddEventAtOffset([this] { + me->SetInCombatWithZone(); + }, 100s); } } @@ -132,7 +145,28 @@ public: { BossAI::JustEngagedWith(who); Talk(SAY_AGGRO); - events2.Reset(); + + me->m_Events.KillAllEvents(false); + + scheduler.Schedule(8s, 14s, [&](TaskContext context) + { + DoCastVictim(SPELL_MIND_FLAY); + if (!IsInFrenzy()) + context.Repeat(8s, 14s); + else + context.Repeat(5s, 9s); + }).Schedule(10s, 13s, GROUP_SWARM, [&](TaskContext context) + { + Talk(SAY_SWARM); + DoCastAOE(SPELL_SWARM); + context.Repeat(26s, 30s); + }); + + ScheduleTimedEvent(27s, 35s, [&] { + DoCastRandomTarget(SPELL_CURSE_OF_FATIGUE); + }, 27s, 35s); + + summons.DoZoneInCombat(); } void JustDied(Unit* killer) override @@ -141,12 +175,15 @@ public: Talk(SAY_DEATH); } - void KilledUnit(Unit* ) override + void KilledUnit(Unit* /*victim*/) override { - if (events.GetNextEventTime(EVENT_KILL_TALK) == 0) + if (_canTalk) { + _canTalk = false; Talk(SAY_SLAY); - events.ScheduleEvent(EVENT_KILL_TALK, 6s); + me->m_Events.AddEventAtOffset([&] { + _canTalk = true; + }, 6s); } } @@ -161,66 +198,12 @@ public: summons.Despawn(summon); } - void UpdateAI(uint32 diff) override - { - events2.Update(diff); - switch (events2.ExecuteEvent()) - { - case EVENT_KRIK_START_WAVE: - me->CastCustomSpell(SPELL_SUBBOSS_AGGRO_TRIGGER, SPELLVALUE_MAX_TARGETS, 1, me, true); - Talk(SAY_SEND_GROUP); - break; - case EVENT_KRIK_ENTER_COMBAT: - me->SetInCombatWithZone(); - break; - case EVENT_KRIK_CHECK_EVADE: - if (!SelectTargetFromPlayerList(100.0f)) - { - EnterEvadeMode(); - return; - } - events2.ScheduleEvent(EVENT_KRIK_CHECK_EVADE, 5s); - break; - } + private: + bool _initTalk; + bool _canTalk; + bool _minionInCombat; - if (!UpdateVictim()) - return; - - events.Update(diff); - if (me->HasUnitState(UNIT_STATE_CASTING)) - return; - - switch (events.ExecuteEvent()) - { - case EVENT_KRIK_HEALTH_CHECK: - if (HealthBelowPct(10)) - { - me->CastSpell(me, SPELL_FRENZY, true); - break; - } - events.ScheduleEvent(EVENT_KRIK_HEALTH_CHECK, 1s); - break; - case EVENT_KRIK_SUMMON: - Talk(SAY_SWARM); - me->CastSpell(me, SPELL_SWARM, false); - events.ScheduleEvent(EVENT_KRIK_SUMMON, 20s); - break; - case EVENT_KRIK_MIND_FLAY: - me->CastSpell(me->GetVictim(), SPELL_MIND_FLAY, false); - events.ScheduleEvent(EVENT_KRIK_MIND_FLAY, 15s); - break; - case EVENT_KRIK_CURSE: - if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 100, true)) - me->CastSpell(target, SPELL_CURSE_OF_FATIGUE, true); - events.ScheduleEvent(EVENT_KRIK_CURSE, 10s); - break; - case EVENT_CALL_ADDS: - summons.DoZoneInCombat(); - break; - } - - DoMeleeAttackIfReady(); - } + [[nodiscard]] bool IsInFrenzy() const { return me->HasAura(SPELL_FRENZY); } }; CreatureAI* GetAI(Creature* creature) const override diff --git a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp index 9de3f63be..3ee2f91f9 100644 --- a/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp +++ b/src/server/scripts/Northrend/AzjolNerub/AzjolNerub/instance_azjol_nerub.cpp @@ -25,7 +25,7 @@ DoorData const doorData[] = { - { GO_KRIKTHIR_DOORS, DATA_KRIKTHIR_THE_GATEWATCHER_EVENT, DOOR_TYPE_PASSAGE }, + { GO_KRIKTHIR_DOORS, DATA_KRIKTHIR, DOOR_TYPE_PASSAGE }, { GO_ANUBARAK_DOORS1, DATA_ANUBARAK_EVENT, DOOR_TYPE_ROOM }, { GO_ANUBARAK_DOORS2, DATA_ANUBARAK_EVENT, DOOR_TYPE_ROOM }, { GO_ANUBARAK_DOORS3, DATA_ANUBARAK_EVENT, DOOR_TYPE_ROOM }, @@ -34,15 +34,25 @@ DoorData const doorData[] = ObjectData const creatureData[] = { - { NPC_KRIKTHIR_THE_GATEWATCHER, DATA_KRIKTHIR_THE_GATEWATCHER_EVENT }, - { NPC_HADRONOX, DATA_HADRONOX_EVENT }, - { 0, 0 } + { NPC_KRIKTHIR_THE_GATEWATCHER, DATA_KRIKTHIR }, + { NPC_HADRONOX, DATA_HADRONOX }, + { 0, 0 } +}; + +ObjectData const summonData[] = +{ + { NPC_SKITTERING_SWARMER, DATA_KRIKTHIR }, + { NPC_SKITTERING_INFECTIOR, DATA_KRIKTHIR }, + { NPC_ANUB_AR_CHAMPION, DATA_HADRONOX }, + { NPC_ANUB_AR_NECROMANCER, DATA_HADRONOX }, + { NPC_ANUB_AR_CRYPTFIEND, DATA_HADRONOX }, + { 0, 0 } }; BossBoundaryData const boundaries = { - { DATA_KRIKTHIR_THE_GATEWATCHER_EVENT, new RectangleBoundary(400.0f, 580.0f, 623.5f, 810.0f) }, - { DATA_HADRONOX_EVENT, new ZRangeBoundary(666.0f, 776.0f) }, + { DATA_KRIKTHIR, new RectangleBoundary(400.0f, 580.0f, 623.5f, 810.0f) }, + { DATA_HADRONOX, new ZRangeBoundary(666.0f, 776.0f) }, { DATA_ANUBARAK_EVENT, new CircleBoundary(Position(550.6178f, 253.5917f), 26.0f) } }; @@ -60,52 +70,14 @@ public: LoadBossBoundaries(boundaries); LoadDoorData(doorData); LoadObjectData(creatureData, nullptr); + LoadSummonData(summonData); }; - void OnCreatureCreate(Creature* creature) override + void OnCreatureEvade(Creature* creature) override { - switch (creature->GetEntry()) - { - case NPC_SKITTERING_SWARMER: - case NPC_SKITTERING_INFECTIOR: - if (Creature* krikthir = GetCreature((DATA_KRIKTHIR_THE_GATEWATCHER_EVENT))) - krikthir->AI()->JustSummoned(creature); - break; - case NPC_ANUB_AR_CHAMPION: - case NPC_ANUB_AR_NECROMANCER: - case NPC_ANUB_AR_CRYPTFIEND: - if (Creature* hadronox = GetCreature(DATA_HADRONOX_EVENT)) - hadronox->AI()->JustSummoned(creature); - break; - } - - InstanceScript::OnCreatureCreate(creature); - } - - void OnGameObjectCreate(GameObject* go) override - { - switch (go->GetEntry()) - { - case GO_KRIKTHIR_DOORS: - case GO_ANUBARAK_DOORS1: - case GO_ANUBARAK_DOORS2: - case GO_ANUBARAK_DOORS3: - AddDoor(go); - break; - } - } - - void OnGameObjectRemove(GameObject* go) override - { - switch (go->GetEntry()) - { - case GO_KRIKTHIR_DOORS: - case GO_ANUBARAK_DOORS1: - case GO_ANUBARAK_DOORS2: - case GO_ANUBARAK_DOORS3: - RemoveDoor(go); - break; - } + if (creature->EntryEquals(NPC_WATCHER_NARJIL, NPC_WATCHER_GASHRA, NPC_WATCHER_SILTHIK)) + if (Creature* krikthir = GetCreature(DATA_KRIKTHIR)) + krikthir->AI()->EnterEvadeMode(); } }; diff --git a/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp b/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp index 17ac54f3b..01cdb73fb 100644 --- a/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp +++ b/src/server/scripts/Northrend/Nexus/Oculus/oculus.cpp @@ -75,8 +75,6 @@ enum DrakeGiverTexts class npc_oculus_drakegiver : public CreatureScript { public: - std::unordered_mapopenedMenu; - npc_oculus_drakegiver() : CreatureScript("npc_oculus_drakegiver") { } CreatureAI* GetAI(Creature* creature) const override @@ -193,20 +191,8 @@ public: SendGossipMenuFor(player, GOSSIP_TEXTID_VERDISA1, creature->GetGUID()); break; case NPC_BELGARISTRASZ: - if (HAS_ESSENCE(player)) - { - openedMenu[player->GetGUID()] = true; - } - - if (!openedMenu[player->GetGUID()]) - { - AddGossipItemFor(player, 9708, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); - SendGossipMenuFor(player, GOSSIP_TEXTID_DRAKES, creature->GetGUID()); - } - else - { - OnGossipSelect(player, creature, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); - } + AddGossipItemFor(player, 9708, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); + SendGossipMenuFor(player, GOSSIP_TEXTID_DRAKES, creature->GetGUID()); break; case NPC_ETERNOS: AddGossipItemFor(player, 9574, 0, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF); @@ -277,7 +263,6 @@ public: switch (uiAction) { case GOSSIP_ACTION_INFO_DEF: - openedMenu[player->GetGUID()] = true; if (player->HasItemCount(ITEM_AMBER_ESSENCE)) { AddGossipItemFor(player, 9575, 1, GOSSIP_SENDER_MAIN, GOSSIP_ACTION_INFO_DEF + 1); diff --git a/src/server/scripts/Northrend/zone_borean_tundra.cpp b/src/server/scripts/Northrend/zone_borean_tundra.cpp index 448f1aaca..88013c47c 100644 --- a/src/server/scripts/Northrend/zone_borean_tundra.cpp +++ b/src/server/scripts/Northrend/zone_borean_tundra.cpp @@ -2124,6 +2124,25 @@ public: } }; +// 45612 - Necropolis Beam +class spell_necropolis_beam: public SpellScript +{ + PrepareSpellScript(spell_necropolis_beam); + + void SetDest(SpellDestination& dest) + { + Unit* caster = GetCaster(); + float floorZ = caster->GetMapHeight(caster->GetPositionX(), caster->GetPositionY(), caster->GetPositionZ()); + if (floorZ > INVALID_HEIGHT) + dest._position.m_positionZ = floorZ; + } + + void Register() override + { + OnDestinationTargetSelect += SpellDestinationTargetSelectFn(spell_necropolis_beam::SetDest, EFFECT_0, TARGET_DEST_CASTER); + } +}; + void AddSC_borean_tundra() { RegisterSpellScript(spell_q11919_q11940_drake_hunt_aura); @@ -2148,4 +2167,5 @@ void AddSC_borean_tundra() RegisterSpellScript(spell_q11719_bloodspore_ruination_45997); new npc_bloodmage_laurith(); RegisterCreatureAI(npc_jenny); + RegisterSpellScript(spell_necropolis_beam); } diff --git a/src/server/scripts/Northrend/zone_dragonblight.cpp b/src/server/scripts/Northrend/zone_dragonblight.cpp index 2faac7047..9cf0cb7c0 100644 --- a/src/server/scripts/Northrend/zone_dragonblight.cpp +++ b/src/server/scripts/Northrend/zone_dragonblight.cpp @@ -2240,6 +2240,27 @@ class spell_dragonblight_corrosive_spit : public AuraScript } }; +// 48297 - Hand Over Reins +enum HandOverReins +{ + SPELL_ONSLAUGHT_RIDING_CROP = 48290 +}; + +class spell_handover_reins : public SpellScript +{ + PrepareSpellScript(spell_handover_reins); + + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + GetCaster()->RemoveAura(SPELL_ONSLAUGHT_RIDING_CROP); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_handover_reins::HandleScriptEffect, EFFECT_1, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + void AddSC_dragonblight() { new npc_conversing_with_the_depths_trigger(); @@ -2267,4 +2288,5 @@ void AddSC_dragonblight() RegisterSpellScript(spell_q12096_q12092_bark); new npc_torturer_lecraft(); RegisterSpellScript(spell_dragonblight_corrosive_spit); + RegisterSpellScript(spell_handover_reins); } diff --git a/src/server/scripts/Northrend/zone_howling_fjord.cpp b/src/server/scripts/Northrend/zone_howling_fjord.cpp index 2b0efc76f..c6405cc18 100644 --- a/src/server/scripts/Northrend/zone_howling_fjord.cpp +++ b/src/server/scripts/Northrend/zone_howling_fjord.cpp @@ -22,6 +22,7 @@ #include "ScriptedEscortAI.h" #include "ScriptedGossip.h" #include "SpellInfo.h" +#include "SpellScript.h" class npc_attracted_reef_bull : public CreatureScript { @@ -393,6 +394,36 @@ public: } }; +enum HawkHunting +{ + SPELL_HAWK_HUNTING_ITEM = 44408 +}; + +// 44407 - Spell hawk Hunting +class spell_hawk_hunting : public SpellScript +{ + PrepareSpellScript(spell_hawk_hunting); + + bool Validate(SpellInfo const* /*spellInfo*/) override + { + return ValidateSpellInfo({ SPELL_HAWK_HUNTING_ITEM }); + } + + void HandleScriptEffect(SpellEffIndex /*effIndex*/) + { + if (!GetCaster()) + return; + + GetCaster()->CastSpell(GetCaster(), SPELL_HAWK_HUNTING_ITEM, true); + GetHitUnit()->ToCreature()->DespawnOrUnsummon(); + } + + void Register() override + { + OnEffectHitTarget += SpellEffectFn(spell_hawk_hunting::HandleScriptEffect, EFFECT_1, SPELL_EFFECT_SCRIPT_EFFECT); + } +}; + void AddSC_howling_fjord() { new npc_attracted_reef_bull(); @@ -400,4 +431,5 @@ void AddSC_howling_fjord() new npc_apothecary_hanes(); new npc_plaguehound_tracker(); new npc_razael_and_lyana(); + RegisterSpellScript(spell_hawk_hunting); } diff --git a/src/server/scripts/Northrend/zone_storm_peaks.cpp b/src/server/scripts/Northrend/zone_storm_peaks.cpp index ec2da77ea..35fd65e31 100644 --- a/src/server/scripts/Northrend/zone_storm_peaks.cpp +++ b/src/server/scripts/Northrend/zone_storm_peaks.cpp @@ -1167,6 +1167,31 @@ public: return new npc_vehicle_d16_propelled_deliveryAI(creature); } }; + +enum StormcrestEagle +{ + NPC_STORMCREST_EAGLE = 29854 +}; + +// 56393 - Feed Stormcrest Eagle +class spell_feed_stormcrest_eagle : public SpellScript +{ + PrepareSpellScript(spell_feed_stormcrest_eagle); + + SpellCastResult CheckCast() + { + if (GetCaster()->FindNearestCreature(NPC_STORMCREST_EAGLE, 15.0f, true)) + return SPELL_CAST_OK; + + return SPELL_FAILED_BAD_TARGETS; + } + + void Register() override + { + OnCheckCast += SpellCheckCastFn(spell_feed_stormcrest_eagle::CheckCast); + } +}; + void AddSC_storm_peaks() { new npc_frosthound(); @@ -1183,4 +1208,5 @@ void AddSC_storm_peaks() RegisterSpellScript(spell_close_rift_aura); new npc_vehicle_d16_propelled_delivery(); RegisterSpellScript(spell_q12823_remove_collapsing_cave_aura); + RegisterSpellScript(spell_feed_stormcrest_eagle); } diff --git a/src/server/scripts/Spells/spell_quest.cpp b/src/server/scripts/Spells/spell_quest.cpp index b6dae13a2..37d438e50 100644 --- a/src/server/scripts/Spells/spell_quest.cpp +++ b/src/server/scripts/Spells/spell_quest.cpp @@ -1527,12 +1527,27 @@ class spell_q12805_lifeblood_dummy : public SpellScript void HandleScript(SpellEffIndex /*effIndex*/) { Player* caster = GetCaster()->ToPlayer(); - if (Creature* target = GetHitCreature()) + Creature* target = GetHitCreature(); + + if (!target) + return; + + if (Group* group = caster->GetGroup()) { - caster->KilledMonsterCredit(NPC_SHARD_KILL_CREDIT); - target->CastSpell(target, uint32(GetEffectValue()), true); - target->DespawnOrUnsummon(2000); + ObjectGuid targetGUID = target->GetGUID(); + group->DoForAllMembers([targetGUID](Player* player) + { + if (Creature* shard = ObjectAccessor::GetCreature(*player, targetGUID)) + if (player->IsAtGroupRewardDistance(shard)) + player->KilledMonsterCredit(NPC_SHARD_KILL_CREDIT); + + }); } + else + caster->KilledMonsterCredit(NPC_SHARD_KILL_CREDIT); + + target->CastSpell(target, uint32(GetEffectValue()), true); + target->DespawnOrUnsummon(2000); } void Register() override diff --git a/src/server/scripts/World/scourge_invasion.cpp b/src/server/scripts/World/scourge_invasion.cpp index 982049624..dc61a79ce 100644 --- a/src/server/scripts/World/scourge_invasion.cpp +++ b/src/server/scripts/World/scourge_invasion.cpp @@ -92,6 +92,7 @@ struct npc_herald_of_the_lich_king : public ScriptedAI Talk(HERALD_OF_THE_LICH_KING_SAY_ATTACK_END); ChangeZoneEventStatus(false); UpdateWeather(false); + me->DespawnOrUnsummon(); } } @@ -361,7 +362,6 @@ struct npc_necrotic_shard : public ScriptedAI { scheduler.Schedule(5s, [this](TaskContext context) // Spawn Cultists every 60 minutes. { - me->SetFullHealth(); DespawnShadowsOfDoom(); // Despawn all remaining Shadows before respawning the Cultists? SummonCultists(); context.Repeat(1h); @@ -519,7 +519,8 @@ struct npc_necrotic_shard : public ScriptedAI // Buff Players. DoCastSelf(SPELL_SOUL_REVIVAL, true); // Sending the Death Bolt. - DoCastAOE(SPELL_COMMUNIQUE_CAMP_TO_RELAY_DEATH, true); + if (Creature* relay = GetClosestCreatureWithEntry(me, NPC_NECROPOLIS_RELAY, 200.0f)) + me->CastSpell(relay, SPELL_COMMUNIQUE_CAMP_TO_RELAY_DEATH, true); DespawnCultists(); // Despawn remaining Cultists (should never happen). DespawnEventDoodads(); sWorldState->Save(SAVE_ID_SCOURGE_INVASION);