From 796078a5645c241a915328e8cfa57c7b0de65082 Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Mon, 8 Jul 2024 00:03:08 -0400 Subject: [PATCH] Autobalance updates from local --- .editorconfig | 8 + .git_commit_template.txt | 49 + .gitattributes | 105 + .github/ISSUE_TEMPLATE/bug_report.yml | 72 + .github/ISSUE_TEMPLATE/feature_request.yml | 33 + .github/workflows/core-build.yml | 12 + .gitignore | 51 + README.md | 12 + acore-module.json | 9 + conf/AutoBalance.conf.dist | 922 +++++ data/db-characters/base/.gitkeep | 0 data/db-characters/base/group_difficulty.sql | 6 + data/db-characters/updates/.gitkeep | 0 data/db-world/base/.gitkeep | 0 data/db-world/updates/.gitkeep | 0 icon.png | Bin 0 -> 35883 bytes include.sh | 0 pull_request_template.md | 25 + setup_git_commit_template.sh | 4 + src/AB_loader.cpp | 6 + src/AutoBalance.cpp | 3368 ++++++++++++++++++ src/AutoBalance.h | 43 + 22 files changed, 4725 insertions(+) create mode 100755 .editorconfig create mode 100755 .git_commit_template.txt create mode 100755 .gitattributes create mode 100755 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100755 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100755 .github/workflows/core-build.yml create mode 100755 .gitignore create mode 100755 README.md create mode 100755 acore-module.json create mode 100755 conf/AutoBalance.conf.dist create mode 100644 data/db-characters/base/.gitkeep create mode 100644 data/db-characters/base/group_difficulty.sql create mode 100644 data/db-characters/updates/.gitkeep create mode 100644 data/db-world/base/.gitkeep create mode 100644 data/db-world/updates/.gitkeep create mode 100755 icon.png create mode 100755 include.sh create mode 100755 pull_request_template.md create mode 100755 setup_git_commit_template.sh create mode 100755 src/AB_loader.cpp create mode 100755 src/AutoBalance.cpp create mode 100755 src/AutoBalance.h diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..eb64e2f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +tab_width = 4 +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 80 diff --git a/.git_commit_template.txt b/.git_commit_template.txt new file mode 100755 index 0000000..708b551 --- /dev/null +++ b/.git_commit_template.txt @@ -0,0 +1,49 @@ +### TITLE +## Type(Scope/Subscope): Commit ultra short explanation +## |---- Write below the examples with a maximum of 50 characters ----| +## Example 1: fix(DB/SAI): Missing spell to NPC Hogger +## Example 2: fix(CORE/Raid): Phase 2 of Ragnaros +## Example 3: feat(CORE/Commands): New GM command to do something + + +### DESCRIPTION +## Explain why this change is being made, what does it fix etc... +## |---- Write below the examples with a maximum of 72 characters per lines ----| +## Example: Hogger (id: 492) was not charging player when being engaged. + + +## Provide links to any issue, commit, pull request or other resource +## Example 1: Closes issue #23 +## Example 2: Ported from other project's commit (link) +## Example 3: References taken from wowpedia / wowhead / wowwiki / https://wowgaming.altervista.org/aowow/ + + + +## ======================================================= +## EXTRA INFOS +## ======================================================= +## "Type" can be: +## feat (new feature) +## fix (bug fix) +## refactor (refactoring production code) +## style (formatting, missing semi colons, etc; no code change) +## docs (changes to documentation) +## test (adding or refactoring tests; no production code change) +## chore (updating bash scripts, git files etc; no production code change) +## -------------------- +## Remember to +## Capitalize the subject line +## Use the imperative mood in the subject line +## Do not end the subject line with a period +## Separate subject from body with a blank line +## Use the body to explain what and why rather than how +## Can use multiple lines with "-" for bullet points in body +## -------------------- +## More info here https://www.conventionalcommits.org/en/v1.0.0-beta.2/ +## ======================================================= +## "Scope" can be: +## CORE (core related, c++) +## DB (database related, sql) +## ======================================================= +## "Subscope" is optional and depends on the nature of the commit. +## ======================================================= diff --git a/.gitattributes b/.gitattributes new file mode 100755 index 0000000..7ef9001 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,105 @@ +## AUTO-DETECT +## Handle line endings automatically for files detected as +## text and leave all files detected as binary untouched. +## This will handle all files NOT defined below. +* text=auto eol=lf + +# Text +*.conf text +*.conf.dist text +*.cmake text + +## Scripts +*.sh text +*.fish text +*.lua text + +## SQL +*.sql text + +## C++ +*.c text +*.cc text +*.cxx text +*.cpp text +*.c++ text +*.hpp text +*.h text +*.h++ text +*.hh text + + +## For documentation + +# Documents +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain + +## DOCUMENTATION +*.markdown text +*.md text +*.mdwn text +*.mdown text +*.mkd text +*.mkdn text +*.mdtxt text +*.mdtext text +*.txt text +AUTHORS text +CHANGELOG text +CHANGES text +CONTRIBUTING text +COPYING text +copyright text +*COPYRIGHT* text +INSTALL text +license text +LICENSE text +NEWS text +readme text +*README* text +TODO text + +## GRAPHICS +*.ai binary +*.bmp binary +*.eps binary +*.gif binary +*.ico binary +*.jng binary +*.jp2 binary +*.jpg binary +*.jpeg binary +*.jpx binary +*.jxr binary +*.pdf binary +*.png binary +*.psb binary +*.psd binary +*.svg text +*.svgz binary +*.tif binary +*.tiff binary +*.wbmp binary +*.webp binary + + +## ARCHIVES +*.7z binary +*.gz binary +*.jar binary +*.rar binary +*.tar binary +*.zip binary + +## EXECUTABLES +*.exe binary +*.pyc binary diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100755 index 0000000..5610d2b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,72 @@ +name: Bug report +description: Create a bug report to help us improve. +title: "Bug: " +body: + - type: textarea + id: current + attributes: + label: Current Behaviour + description: | + Description of the problem or issue here. + Include entries of affected creatures / items / quests / spells etc. + If this is a crash, post the crashlog (upload to https://gist.github.com/) and include the link here. + Never upload files! Use GIST for text and YouTube for videos! + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Behaviour + description: | + Tell us what should happen instead. + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce the problem + description: | + What does someone else need to do to encounter the same bug? + placeholder: | + 1. Step 1 + 2. Step 2 + 3. Step 3 + validations: + required: true + - type: textarea + id: extra + attributes: + label: Extra Notes + description: | + Do you have any extra notes that can help solve the issue that does not fit any other field? + placeholder: | + None + validations: + required: false + - type: textarea + id: commit + attributes: + label: AC rev. hash/commit + description: | + Copy the result of the `.server debug` command (if you need to run it from the client get a prat addon) + validations: + required: true + - type: input + id: os + attributes: + label: Operating system + description: | + The Operating System the Server is running on. + i.e. Windows 11 x64, Debian 10 x64, macOS 12, Ubuntu 20.04 + validations: + required: true + - type: textarea + id: custom + attributes: + label: Custom changes or Modules + description: | + List which custom changes or modules you have applied, i.e. Eluna module, etc. + placeholder: | + None + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100755 index 0000000..58f79dd --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,33 @@ +name: Feature request +description: Suggest an idea for this project +title: "Feature: " +body: + - type: markdown + attributes: + value: | + Thank you for taking your time to fill out a feature request. Remember to fill out all fields including the title above. + An issue that is not properly filled out will be closed. + - type: textarea + id: description + attributes: + label: Describe your feature request or suggestion in detail + description: | + A clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + id: solution + attributes: + label: Describe a possible solution to your feature or suggestion in detail + description: | + A clear and concise description of any alternative solutions or features you've considered. + validations: + required: false + - type: textarea + id: additional + attributes: + label: Additional context + description: | + Add any other context or screenshots about the feature request here. + validations: + required: false diff --git a/.github/workflows/core-build.yml b/.github/workflows/core-build.yml new file mode 100755 index 0000000..921c9eb --- /dev/null +++ b/.github/workflows/core-build.yml @@ -0,0 +1,12 @@ +name: core-build +on: + push: + branches: + - 'master' + pull_request: + +jobs: + build: + uses: azerothcore/reusable-workflows/.github/workflows/core_build_modules.yml@main + with: + module_repo: ${{ github.event.repository.name }} diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..7f023a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +!.gitignore + +# +#Generic +# + +.directory +.mailmap +*.orig +*.rej +*.*~ +.hg/ +*.kdev* +.DS_Store +CMakeLists.txt.user +*.bak +*.patch +*.diff +*.REMOTE.* +*.BACKUP.* +*.BASE.* +*.LOCAL.* + +# +# IDE & other softwares +# +/.settings/ +/.externalToolBuilders/* +# exclude in all levels +nbproject/ +.sync.ffs_db +*.kate-swp +.idea +cmake-build-debug +.vscode + +# +# Eclipse +# +*.pydevproject +.metadata +.gradle +tmp/ +*.tmp +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.project +.cproject diff --git a/README.md b/README.md new file mode 100755 index 0000000..b4f520f --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# ![logo](https://raw.githubusercontent.com/azerothcore/azerothcore.github.io/master/images/logo-github.png) AzerothCore +## AutoBalanceModule +- Latest build status with azerothcore: [![Build Status](https://github.com/azerothcore/mod-autobalance/workflows/core-build/badge.svg?branch=master&event=push)](https://github.com/azerothcore/mod-autobalance) + +This module is intended to scale based on number of players, instance mobs and bosses' health, mana, and damage. + +All settings are well-described in the configuration file. + +## References +- [Interactive Inflection Point Spreadsheet](https://docs.google.com/spreadsheets/d/100cmKIJIjCZ-ncWd0K9ykO8KUgwFTcwg4h2nfE_UeCc/copy) +- [InflectionPoint Curve Examples](https://i.imgur.com/x42UnUR.png) +- [Impact of CurveFloor and CurveCeiling on enemy multiplier](https://i.imgur.com/I8S4cwJ.png) diff --git a/acore-module.json b/acore-module.json new file mode 100755 index 0000000..9d1fd44 --- /dev/null +++ b/acore-module.json @@ -0,0 +1,9 @@ +{ + "name": "mod-autobalance", + "version": "2.2.0", + "compatibility" : [ + { "version": "1.0.0", "branch": "none" }, + { "version": "^2.0.0", "branch": "2.0" }, + { "version": "^3.0.0", "branch": "master" } + ] +} diff --git a/conf/AutoBalance.conf.dist b/conf/AutoBalance.conf.dist new file mode 100755 index 0000000..f19f52b --- /dev/null +++ b/conf/AutoBalance.conf.dist @@ -0,0 +1,922 @@ +[worldserver] +########################## +# +# Enable Settings +# +########################## + +# +# AutoBalance.Enable +# Enable/Disable all features globally. If this setting is off, all settings after this one do not take effect. +# Default: 1 (1 = ON, 0 = OFF) +AutoBalance.Enable.Global=1 + +# +# AutoBalance.Enabled.* +# Enable/Disable all features for each instance size and difficulty. +# If an instance size is set to 0 here, none of the other settings for that instance size will take effect. +# Default: 1 (1 = ON, 0 = OFF) +# +AutoBalance.Enable.5M=1 +AutoBalance.Enable.10M=0 +AutoBalance.Enable.15M=0 +AutoBalance.Enable.20M=0 +AutoBalance.Enable.25M=0 +AutoBalance.Enable.40M=0 +AutoBalance.Enable.OtherNormal=1 + +AutoBalance.Enable.5MHeroic=1 +AutoBalance.Enable.10MHeroic=0 +AutoBalance.Enable.25MHeroic=0 +AutoBalance.Enable.OtherHeroic=0 + +########################## +# +# Stat Scaling - Inflection Point +# +########################## + +# AutoBalance.InflectionPoint* series +# InflectionPoint +# Changes the inflection point of the Hyperbolic Tangent function that determines the enemy multiplier for a given player count. +# This adjusts the shape of the overall curve. A lower value means that difficulty will increase faster as you add players. +# +# This image provies a visual of several InflectionPoint settings and its affect on the enemy stat multipliers. +# https://i.imgur.com/x42UnUR.png +# +# Example: If InflectionPointRaid is 0.5, an enemy in a 40-player instance will have half its life with 20 players in the instance +# If InflectionPointRaid is 0.8, an enemy in a 40-player instance will have half its life with 12 players in the instance +# +# Default: 0.5 +# +# CurveFloor +# When the curve to determine the enemy multiplier is calculated, start the curve at this value. +# +# This allows you to make enemies have higher stats for lower player counts without adversely affecting the stats of +# higher player counts. Applied before `AutoBalance.StatModifier*` values. +# +# Value may be negative if needed to achieve your desired curve. +# +# To understand how CurveFloor and CurveCeiling affect the multiplier, see this image: +# https://i.imgur.com/I8S4cwJ.png +# +# Default: 0.0 +# +# CurveCeiling +# When the curve to determine the enemy multiplier is calculated, end the curve at this value. +# +# This allows you to make enemies have lower stats for higher player counts without adversely affecting the stats of +# lower player counts. Applied before `AutoBalance.StatModifier*` values. +# +# To understand how CurveFloor and CurveCeiling affect the multiplier, see this image: +# https://i.imgur.com/I8S4cwJ.png +# +# May be set to higher than 1.0 to increase difficulty of the instance over the stock values: +# https://i.imgur.com/DBPxT8E.png +# +# Default: 1.0 +# +# BossModifier +# InflectionPoint is multiplied by this value for creatures considered dungeon bosses (from dungeons or raids). +# It is a multiplier, not a new Inflection Point. Values higher than 1.0 will make enemies easier at lower player +# counts while lower than 1.0 will make enemies easier at lower player counts. +# +# To better understand this effect, see the interactive spreadsheet explained below. +# +# Default: 1.0 +# +# +# An interactive spreadsheet is available for you to visually see what effect your settings have on the difficulty curve. +# +# https://docs.google.com/spreadsheets/d/100cmKIJIjCZ-ncWd0K9ykO8KUgwFTcwg4h2nfE_UeCc/copy +# +# To use it, copy the sheet to your own Google Drive and edit the values on the left with the yellow background. +# The "Final Multiplier" value is the actual multiplier that will be applied to enemies in a given dungeon. + +### 5-player dungeons +AutoBalance.InflectionPoint=0.55 +AutoBalance.InflectionPoint.CurveFloor=0.25 +AutoBalance.InflectionPoint.CurveCeiling=1.0 +AutoBalance.InflectionPoint.BossModifier=1.0 + +### 5-player heroic dungeons +AutoBalance.InflectionPointHeroic=0.5 +AutoBalance.InflectionPointHeroic.CurveFloor=0.0 +AutoBalance.InflectionPointHeroic.CurveCeiling=1.0 +AutoBalance.InflectionPointHeroic.BossModifier=1.0 + +### Default for all raids +AutoBalance.InflectionPointRaid=0.5 +AutoBalance.InflectionPointRaid.CurveFloor=0.0 +AutoBalance.InflectionPointRaid.CurveCeiling=1.0 +AutoBalance.InflectionPointRaid.BossModifier=1.0 + +### Default for all heroic raids +AutoBalance.InflectionPointRaidHeroic=0.5 +AutoBalance.InflectionPointRaidHeroic.CurveFloor=0.0 +AutoBalance.InflectionPointRaidHeroic.CurveCeiling=1.0 +AutoBalance.InflectionPointRaidHeroic.BossModifier=1.0 + +### +### By default, all instances use the settings configured above. To customize settings for +### a particular instance size or difficulty, set the variables below. If the variable is +### blank, the broader settings above will apply. +### + +### 10-player raids +AutoBalance.InflectionPointRaid10M= +AutoBalance.InflectionPointRaid10M.CurveFloor= +AutoBalance.InflectionPointRaid10M.CurveCeiling= +AutoBalance.InflectionPointRaid10M.BossModifier= + +### 10-player heroic raids +AutoBalance.InflectionPointRaid10MHeroic= +AutoBalance.InflectionPointRaid10MHeroic.CurveFloor= +AutoBalance.InflectionPointRaid10MHeroic.CurveCeiling= +AutoBalance.InflectionPointRaid10MHeroic.BossModifier= + +### 15-player raids +AutoBalance.InflectionPointRaid15M= +AutoBalance.InflectionPointRaid15M.CurveFloor= +AutoBalance.InflectionPointRaid15M.CurveCeiling= +AutoBalance.InflectionPointRaid15M.BossModifier= + +### 20-player raids +AutoBalance.InflectionPointRaid20M= +AutoBalance.InflectionPointRaid20M.CurveFloor= +AutoBalance.InflectionPointRaid20M.CurveCeiling= +AutoBalance.InflectionPointRaid20M.BossModifier= + +### 25-player raids +AutoBalance.InflectionPointRaid25M= +AutoBalance.InflectionPointRaid25M.CurveFloor= +AutoBalance.InflectionPointRaid25M.CurveCeiling= +AutoBalance.InflectionPointRaid25M.BossModifier= + +### 25-player heroic raids +AutoBalance.InflectionPointRaid25MHeroic= +AutoBalance.InflectionPointRaid25MHeroic.CurveFloor= +AutoBalance.InflectionPointRaid25MHeroic.CurveCeiling= +AutoBalance.InflectionPointRaid25MHeroic.BossModifier= + +### 40-player raids +AutoBalance.InflectionPointRaid40M= +AutoBalance.InflectionPointRaid40M.CurveFloor= +AutoBalance.InflectionPointRaid40M.CurveCeiling= +AutoBalance.InflectionPointRaid40M.BossModifier= + +# +# AutoBalance.InflectionPoint.PerInstance +# Sets Inflection Point settings for specific `InstanceID`s. +# +# Get a list of Instance IDs here: https://wowpedia.fandom.com/wiki/InstanceID#Classic +# +# Specifying a value of `-1` will skip overriding of that value. For instances not listed, the default inflection value for the instance's +# difficulty and size will be used. You may omit entries from the end of the string if desired. Only one set of values should be specified per InstanceID. +# +# Format: "[InstanceID] [InflectionPoint] [CurveFloor] [CurveCeiling], [InstanceID] [InflectionPoint] [CurveFloor] [CurveCeiling], ..." +# +# Example: AutoBalance.InflectionPoint.PerInstance="229 0.4 0.0 1.5, 309 -1 0.0 1.1, 48 0.3" +# +# Default: "" +# +AutoBalance.InflectionPoint.PerInstance="" + +# +# AutoBalance.InflectionPoint.Boss.PerInstance +# Sets Inflection Point settings for all bosses in specific `InstanceID`s. Note that the first value is "InflectionPointMultiplier", which behaves +# identically to the BossModifier setting. The "normal" inflection point is multiplied by this value for bosses. To better understand this effect, +# see the interactive spreadsheet. +# +# Get a list of Instance IDs here: https://wowpedia.fandom.com/wiki/InstanceID#Classic +# +# For instances not listed, the default inflection value for the instance's difficulty and size will be used. Only one set of values should be specified +# per InstanceID. +# +# Format: "[InstanceID] [InflectionPointMultiplier], [InstanceID] [InflectionPointMultiplier], ..." +# +# Example: AutoBalance.InflectionPoint.Boss.PerInstance="229 1.2, 309 1.5, 48 1.25" +# +# Default: "" +# +AutoBalance.InflectionPoint.Boss.PerInstance="" + +# +# AutoBalance.playerCountDifficultyOffset +# Offset of players inside an instance. Affects all instance types. +# Default: 0 +AutoBalance.playerCountDifficultyOffset=0 + +########################## +# +# Stat Scaling - Stat Modifiers +# +########################## + +# AutoBalance.StatModifier* series +# The difficulty curve (as defined by the InflectionPoint settings) determines the base multiplier. The base +# multiplier is then adjusted (multiplied) by the appropriate StatModifier setting. This allows you to control +# the balance of how scaling affects the four adjustable stats: health, mana, armor, and damage. +# +# AutoBalance.StatModifier*. -- only affects non-boss creatures +# AutoBalance.StatModifier*.Boss. -- only affects bosses +# +# To see this in action, change the `StatModifier` value on the spreadsheet: +# https://docs.google.com/spreadsheets/d/100cmKIJIjCZ-ncWd0K9ykO8KUgwFTcwg4h2nfE_UeCc/copy +# +# To use it, copy the sheet to your own Google Drive and edit the values on the left with the yellow background. +# The "Final Multiplier" value is the actual multiplier that will be applied to enemies in a given instance. +# +# Global +# Multiply the other four settings by this value. Allows you to increase all the stats (health, mana, armor, +# damage) with a single adjustment. Both the global value and the stat-specific value are used at the same time. +# +# Example: If "Global" is 0.5, and "Health" is 1.5, the health of the creature will be multiplied by 0.75 of +# its value (after adjusting for the number of players). +# +# Default: 1.0 +# +# Health | Mana | Armor | Damage +# Adjusts the StatModifier for the appropriate stat. Affected by the Global StatModifier above. +# +# Default: 1.0 +# +# Boss.Global | Boss.Health | Boss.Mana | Boss.Armor | Boss.Damage +# Sets a SEPARATE stat multiplier for bosses. Is NOT affected by the non-boss modifiers. Only applies to creatures +# considered instance bosses (from dungeons or raids). +# +# Default: If not set for a given instance size/type, defaults to the dungeon/raid default values. +# +# To better understand this effect, see the interactive spreadsheet. +# + +### 5-player dungeons +AutoBalance.StatModifier.Global=1.0 +AutoBalance.StatModifier.Health=1.0 +AutoBalance.StatModifier.Mana=1.0 +AutoBalance.StatModifier.Armor=1.0 +AutoBalance.StatModifier.Damage=1.0 + +AutoBalance.StatModifier.Boss.Global=1.0 +AutoBalance.StatModifier.Boss.Health=1.0 +AutoBalance.StatModifier.Boss.Mana=1.0 +AutoBalance.StatModifier.Boss.Armor=1.0 +AutoBalance.StatModifier.Boss.Damage=1.0 + +### 5-player heroic dungeons +AutoBalance.StatModifierHeroic.Global=1.0 +AutoBalance.StatModifierHeroic.Health=1.0 +AutoBalance.StatModifierHeroic.Mana=1.0 +AutoBalance.StatModifierHeroic.Armor=1.0 +AutoBalance.StatModifierHeroic.Damage=1.0 + +AutoBalance.StatModifierHeroic.Boss.Global=1.0 +AutoBalance.StatModifierHeroic.Boss.Health=1.0 +AutoBalance.StatModifierHeroic.Boss.Mana=1.0 +AutoBalance.StatModifierHeroic.Boss.Armor=1.0 +AutoBalance.StatModifierHeroic.Boss.Damage=1.0 + +### Default for all raids +AutoBalance.StatModifierRaid.Global=1.0 +AutoBalance.StatModifierRaid.Health=1.0 +AutoBalance.StatModifierRaid.Mana=1.0 +AutoBalance.StatModifierRaid.Armor=1.0 +AutoBalance.StatModifierRaid.Damage=1.0 + +AutoBalance.StatModifierRaid.Boss.Global=1.0 +AutoBalance.StatModifierRaid.Boss.Health=1.0 +AutoBalance.StatModifierRaid.Boss.Mana=1.0 +AutoBalance.StatModifierRaid.Boss.Armor=1.0 +AutoBalance.StatModifierRaid.Boss.Damage=1.0 + +### Default for all heroic raids +AutoBalance.StatModifierRaidHeroic.Global=1.0 +AutoBalance.StatModifierRaidHeroic.Health=1.0 +AutoBalance.StatModifierRaidHeroic.Mana=1.0 +AutoBalance.StatModifierRaidHeroic.Armor=1.0 +AutoBalance.StatModifierRaidHeroic.Damage=1.0 + +AutoBalance.StatModifierRaidHeroic.Boss.Global=1.0 +AutoBalance.StatModifierRaidHeroic.Boss.Health=1.0 +AutoBalance.StatModifierRaidHeroic.Boss.Mana=1.0 +AutoBalance.StatModifierRaidHeroic.Boss.Armor=1.0 +AutoBalance.StatModifierRaidHeroic.Boss.Damage=1.0 + +### +### By default, all instances use the settings configured above. To customize settings for +### a particular instance size or difficulty, set the variables below. If the variable is +### blank, the broader settings above will apply. +### + +### 10-player raids +AutoBalance.StatModifierRaid10M.Global= +AutoBalance.StatModifierRaid10M.Health= +AutoBalance.StatModifierRaid10M.Mana= +AutoBalance.StatModifierRaid10M.Armor= +AutoBalance.StatModifierRaid10M.Damage= + +AutoBalance.StatModifierRaid10M.Boss.Global= +AutoBalance.StatModifierRaid10M.Boss.Health= +AutoBalance.StatModifierRaid10M.Boss.Mana= +AutoBalance.StatModifierRaid10M.Boss.Armor= +AutoBalance.StatModifierRaid10M.Boss.Damage= + +### 10-player heroic raids +AutoBalance.StatModifierRaid10MHeroic.Global= +AutoBalance.StatModifierRaid10MHeroic.Health= +AutoBalance.StatModifierRaid10MHeroic.Mana= +AutoBalance.StatModifierRaid10MHeroic.Armor= +AutoBalance.StatModifierRaid10MHeroic.Damage= + +AutoBalance.StatModifierRaid10MHeroic.Boss.Global= +AutoBalance.StatModifierRaid10MHeroic.Boss.Health= +AutoBalance.StatModifierRaid10MHeroic.Boss.Mana= +AutoBalance.StatModifierRaid10MHeroic.Boss.Armor= +AutoBalance.StatModifierRaid10MHeroic.Boss.Damage= + +### 15-player raids +AutoBalance.StatModifierRaid15M.Global= +AutoBalance.StatModifierRaid15M.Health= +AutoBalance.StatModifierRaid15M.Mana= +AutoBalance.StatModifierRaid15M.Armor= +AutoBalance.StatModifierRaid15M.Damage= + +AutoBalance.StatModifierRaid15M.Boss.Global= +AutoBalance.StatModifierRaid15M.Boss.Health= +AutoBalance.StatModifierRaid15M.Boss.Mana= +AutoBalance.StatModifierRaid15M.Boss.Armor= +AutoBalance.StatModifierRaid15M.Boss.Damage= + +### 20-player raids +AutoBalance.StatModifierRaid20M.Global= +AutoBalance.StatModifierRaid20M.Health= +AutoBalance.StatModifierRaid20M.Mana= +AutoBalance.StatModifierRaid20M.Armor= +AutoBalance.StatModifierRaid20M.Damage= + +AutoBalance.StatModifierRaid20M.Boss.Global= +AutoBalance.StatModifierRaid20M.Boss.Health= +AutoBalance.StatModifierRaid20M.Boss.Mana= +AutoBalance.StatModifierRaid20M.Boss.Armor= +AutoBalance.StatModifierRaid20M.Boss.Damage= + +### 25-player raids +AutoBalance.StatModifierRaid25M.Global= +AutoBalance.StatModifierRaid25M.Health= +AutoBalance.StatModifierRaid25M.Mana= +AutoBalance.StatModifierRaid25M.Armor= +AutoBalance.StatModifierRaid25M.Damage= + +AutoBalance.StatModifierRaid25M.Boss.Global= +AutoBalance.StatModifierRaid25M.Boss.Health= +AutoBalance.StatModifierRaid25M.Boss.Mana= +AutoBalance.StatModifierRaid25M.Boss.Armor= +AutoBalance.StatModifierRaid25M.Boss.Damage= + +### 25-player heroic raids +AutoBalance.StatModifierRaid25MHeroic.Global= +AutoBalance.StatModifierRaid25MHeroic.Health= +AutoBalance.StatModifierRaid25MHeroic.Mana= +AutoBalance.StatModifierRaid25MHeroic.Armor= +AutoBalance.StatModifierRaid25MHeroic.Damage= + +AutoBalance.StatModifierRaid25MHeroic.Boss.Global= +AutoBalance.StatModifierRaid25MHeroic.Boss.Health= +AutoBalance.StatModifierRaid25MHeroic.Boss.Mana= +AutoBalance.StatModifierRaid25MHeroic.Boss.Armor= +AutoBalance.StatModifierRaid25MHeroic.Boss.Damage= + +### 40-player raids +AutoBalance.StatModifierRaid40M.Global= +AutoBalance.StatModifierRaid40M.Health= +AutoBalance.StatModifierRaid40M.Mana= +AutoBalance.StatModifierRaid40M.Armor= +AutoBalance.StatModifierRaid40M.Damage= + +AutoBalance.StatModifierRaid40M.Boss.Global= +AutoBalance.StatModifierRaid40M.Boss.Health= +AutoBalance.StatModifierRaid40M.Boss.Mana= +AutoBalance.StatModifierRaid40M.Boss.Armor= +AutoBalance.StatModifierRaid40M.Boss.Damage= + +# AutoBalance.StatModifier*.CCDuration series +# These StatModifier values affect the duration of CC effects used against the players. CC effects are auras with one +# or more of these effects: +# +# - SPELL_AURA_MOD_CHARM +# - SPELL_AURA_MOD_CONFUSE +# - SPELL_AURA_MOD_DISARM +# - SPELL_AURA_MOD_FEAR +# - SPELL_AURA_MOD_PACIFY +# - SPELL_AURA_MOD_POSSESS +# - SPELL_AURA_MOD_SILENCE +# - SPELL_AURA_MOD_STUN +# - SPELL_AURA_MOD_SPEED_SLOW_ALL +# +# CCDuration +# Adjusts the duration of CC effects. Not affected by any global settings. +# +# After the InflectionPoint curve determines the base multiplier, it is multiplied by this value to determine the final +# CC duration multiplier. +# +# CCDuration IS affected by the InflectionPoint curve! +# CCDuration IS NOT affected by StatModifier*.Global values! +# CCDuration IS affected by AutoBalance.MinCCDurationModifier and AutoBalance.MaxCCDurationModifier! +# +# Example: +# Assume we are using the default InflectionPoint curve (0.5 with 0.0 floor and 1.0 ceiling), and there are 3 players in the instance. +# With 3 players, the base multiplier is '0.6843'. Let's say that CCDuration is set to '0.5'. +# The final CC Duration multiplier is 0.6843 * 0.5 = 0.34215 +# For a CC with a programmed duration of 8 seconds, the CC will last 8 * 0.34215 = 2.7372 seconds. +# +# Default: no value +# If no value is set for a given instance type and player count, and the generic values that apply to that instance +# are also blank, CC duration will be unchanged. +# +# Boss.CCDuration +# Sets a SEPARATE CCDuration multiplier for bosses. Is NOT affected by the non-boss modifiers. Only applies to creatures +# considered instance bosses (from dungeons or raids). +# +# Default: no value +# If no value is set for a given instance type and player count, and the generic values that apply to that instance +# are also blank, CC duration will be unchanged. +# + +### 5-player dungeons +AutoBalance.StatModifier.CCDuration= +AutoBalance.StatModifier.Boss.CCDuration= + +### 5-player heroic dungeons +AutoBalance.StatModifierHeroic.CCDuration= +AutoBalance.StatModifierHeroic.Boss.CCDuration= + +### Default for all raids +AutoBalance.StatModifierRaid.CCDuration= +AutoBalance.StatModifierRaid.Boss.CCDuration= + +### Default for all heroic raids +AutoBalance.StatModifierRaidHeroic.CCDuration= +AutoBalance.StatModifierRaidHeroic.Boss.CCDuration= + +### +### Configuring the CCDuration settings above this line will affect all dungeons and raids. +### To customize settings for a particular instance size, add your value to the settings below. +### + +### 10-player raids +AutoBalance.StatModifierRaid10M.CCDuration= +AutoBalance.StatModifierRaid10M.Boss.CCDuration= + +### 10-player heroic raids +AutoBalance.StatModifierRaid10MHeroic.CCDuration= +AutoBalance.StatModifierRaid10MHeroic.Boss.CCDuration= + +### 15-player raids +AutoBalance.StatModifierRaid15M.CCDuration= +AutoBalance.StatModifierRaid15M.Boss.CCDuration= + +### 20-player raids +AutoBalance.StatModifierRaid20M.CCDuration= +AutoBalance.StatModifierRaid20M.Boss.CCDuration= + +### 25-player raids +AutoBalance.StatModifierRaid25M.CCDuration= +AutoBalance.StatModifierRaid25M.Boss.CCDuration= + +### 25-player heroic raids +AutoBalance.StatModifierRaid25MHeroic.CCDuration= +AutoBalance.StatModifierRaid25MHeroic.Boss.CCDuration= + +### 40-player raids +AutoBalance.StatModifierRaid40M.CCDuration= +AutoBalance.StatModifierRaid40M.Boss.CCDuration= + +########################## +# +# Stat Scaling - Stat Modifier Overrides +# +########################## +# A note on how the stat modifier settings are prioritized. More specific values REPLACE less-specific values. +# Bosses and non-boss creatures pull from different settings but work in a similar way. +# +# Boss example: High Priest Venoxis (boss, CreatureID 14507) in the Zul'Gurub 20-player raid (InstanceID 309): +# +# StatModifierRaid.Boss.Global= 0.7 +# StatModifierRaid20M.Boss.Global= 0.8 +# StatModifier.Boss.PerInstance= "309 0.9" +# StatModifier.PerCreature= "14507 1.0" +# +# Settings are applied from top the bottom, with the bottom setting (1.0) being applied to all stats for the boss. +# Omitting the "PerCreature" setting would cause the per-instance setting for Zul'Gurub bosses (0.9) to be applied instead, and so on. +# +# Non-boss example: Molten Giant (non-boss, CreatureID 11658) in the Molten Core 40-player raid (InstanceID 409): +# +# StatModifierRaid.Damage = 2.1 +# StatModifierRaid40M.Damage = 2.2 +# StatModifier.PerInstance = "409 -1 -1 -1 -1 2.3" +# StatModifier.PerCreature= "11658 -1 -1 -1 -1 2.4" +# +# Settings are applied from top the bottom, with the bottom setting (2.4) being applied to the creature's damage multiplier. +# Omitting the "PerCreature" setting would cause the per-instance setting for Molten Core non-boss damage (2.3) to be applied instead, and so on. +# +# Keep in mind that you may use "-1" for any specific override stat to allow less-specific settings to come through. +# +# In this way you can make your configs as simple or complicated as you like - if you only want to set some generic dungeon and raid modifiers, everything will +# work as expected. If you want to tune specific creatures to your liking, you can do that too. +# + +# +# AutoBalance.StatModifier.PerInstance +# Allows setting per-instance stat modifier values. Specifying a value of `-1` will skip overriding of that value. +# You may omit entries from the end of the string if desired. Only one set of values should be specified per InstanceID. +# +# ONLY AFFECTS NON-BOSS CREATURES. +# +# Get a list of Instance IDs here: https://wowpedia.fandom.com/wiki/InstanceID#Classic +# +# Format: "[InstanceID] [Global] [Health] [Mana] [Armor] [Damage] [CCDuration], [InstanceID] [Global] [Health] [Mana] [Armor] [Damage] [CCDuration], ..." +# +# Example: AutoBalance.StatModifier.PerInstance="409 1.0 0.8 0.8 1.0 1.2 1.0, 568 -1 -1 -1 -1 1.35, 43 -1 1.2 1.2" +# +# Default: Empty string, which disables the feature. +AutoBalance.StatModifier.PerInstance="" + +# +# AutoBalance.StatModifier.Boss.PerInstance +# Allows setting per-instance stat modifier values for bosses only. Specifying a value of `-1` will skip overriding of that value. +# You may omit entries from the end of the string if desired. Only one set of values should be specified per InstanceID. +# +# ONLY AFFECTS BOSSES. +# +# Get a list of Instance IDs here: https://wowpedia.fandom.com/wiki/InstanceID#Classic +# +# Format: "[InstanceID] [Boss.Global] [Boss.Health] [Boss.Mana] [Boss.Armor] [Boss.Damage] [Boss.CCDuration], [InstanceID] [Boss.Global] [Boss.Health] [Boss.Mana] [Boss.Armor] [Boss.Damage] [Boss.CCDuration], ..." +# +# Example: AutoBalance.StatModifier.Boss.PerInstance="409 1.0 0.8 0.8 1.0 1.2 0.8, 568 -1 -1 -1 -1 1.35, 43 -1 1.2 1.2" +# +# Default: Empty string, which disables the feature. +AutoBalance.StatModifier.Boss.PerInstance="" + +# +# AutoBalance.StatModifier.Boss.PerCreature +# Allows setting per-creature stat modifier values. Specifying a value of `-1` will skip overriding of that value. +# You may omit entries from the end of the string if desired. Only one set of values should be specified per CreatureID. +# +# Format: "[CreatureID] [Global] [Health] [Mana] [Armor] [Damage] [CCDuration], [CreatureID] [Global] [Health] [Mana] [Armor] [Damage] [CCDuration], ..." +# +# Example: AutoBalance.StatModifier.Boss.PerInstance="14507 1.0 0.8 0.8 1.0 1.2 0.5, 11372 -1 -1 -1 -1 1.35, 43 -1 1.2 1.2" +# +# Default: Empty string, which disables the feature. +AutoBalance.StatModifier.PerCreature="" + +# +# AutoBalance.MinHPModifier +# Minimum Modifier setting for Health Modification. An enemy's health will not be multiplied by a value smaller than this. +# Default: 0.01 +AutoBalance.MinHPModifier=0.01 + +# +# AutoBalance.MinManaModifier +# Minimum Modifier setting for Mana Modification. An enemy's mana will not be multiplied by a value smaller than this. +# Default: 0.01 +AutoBalance.MinManaModifier=0.01 + +# +# AutoBalance.MinDamageModifier +# Minimum Modifier setting for Damage Modification. An enemy's damage will not be multiplied by a value smaller than this. +# Default: 0.01 +AutoBalance.MinDamageModifier=0.01 + +# +# AutoBalance.MinCCDurationModifier +# Minimum Modifier setting for Crowd Control Duration. The duration of an enemy's CC will not be multiplied by a value smaller than this. +# Default: 0.25 +AutoBalance.MinCCDurationModifier=0.25 + +# +# AutoBalance.MaxCCDurationModifier +# Maximum Modifier setting for Crowd Control Duration. The duration of an enemy's CC will not be multiplied by a value greater than this. +# Default: 1.0 +AutoBalance.MaxCCDurationModifier=1.0 + +########################## +# +# Misc Settings +# +########################## +# +# AutoBalance.PerDungeonPlayerCounts +# Allows setting a per-instance setting for minimum number of players to scale. For example, dungeons could remain at 5 players, but raids could be chosen to scale down to 5 players as well. +# Formatted as "[InstanceID] [playerCount], [InstanceID] [playerCount], [InstanceID] [playerCount], ..." +# Example: AutoBalance.PerDungeonPlayerCounts="33 1,34 1,36 1,43 1,47 1,48 1,70 1,90 1,109 1,129 1,189 1,209 1,349 1,389 1, 289 2, 329 2, 230 2, 429 2, 309 5, 409 5" +# For instances not listed, the instance's original player count (5, 10, 20, 25, or 40) is used as the minimum, meaning no scaling will take place. +# To disable, leave empty; all instances will then scale down to 1 player minimum. This is the default setting. +# Default: "" +# +AutoBalance.PerDungeonPlayerCounts="" + +# +# AutoBalance.ForcedIDXX +# Sets MobIDs for the group they belong to. +# All 5 Man Mobs should go in .AutoBalance.5.Name +# All 10 Man Mobs should go in .AutoBalance.10.Name etc. +AutoBalance.ForcedID40="11583,16441,30057,13020,15589,14435,18192,14889,14888,14887,14890,15302,15818,15742,15741,15740,18338" +AutoBalance.ForcedID25="22997,21966,21965,21964,21806,21215,21845,19728,12397,17711,18256,18192," +AutoBalance.ForcedID20="" +AutoBalance.ForcedID10="15689,15550,16152,17521,17225,16028,29324,31099" +AutoBalance.ForcedID5="8317,15203,15204,15205,15305,6109,26801,30508,26799,30495,26803,30497,27859,27249" +AutoBalance.ForcedID2="" + +# +# AutoBalance.DisabledID +# Disable scaling on specific creatures +# +AutoBalance.DisabledID="6867" + +########################## +# +# Level Scaling +# +########################## + +# +# AutoBalance.LevelScaling +# Scale creatures in instances based on the highest-level player. +# 0 = Disabled +# 1 = Enabled (only in dungeons/raids) +# Default: 1 +AutoBalance.LevelScaling=0 + +# +# AutoBalance.LevelScaling.Method +# Selects which method should be used when the level for scaled creatures. +# +# fixed: +# Creatures will be scaled to the level of the highest-level player in the group. +# +# dynamic: +# Creatures will be scaled to a level based on 1) their original assignments compared to the +# highest-level creature in the instance and 2) the highest-level player in the dungeon. This +# means that lower-level trash will still be lower level than you, and higher-level bosses +# will still be higher-level than you. +# +# Possible values: "fixed" or "dynamic" +# +# Default: "dynamic" +AutoBalance.LevelScaling.Method="dynamic" + +# +# AutoBalance.LevelScaling.SkipHigherLevels +# If an instance's average creature level is no more than (SkipHigherLevels) levels +# above the highest player level, do not scale down. +# +# To disable scaling instance levels DOWN, set this to the max level of your server (likely 80). +# To disable this feature entirely and scale all higher-level instances, set this to 0. +# +# Default: 3 +# +# AutoBalance.LevelScaling.SkipLowerLevels +# If an instance's average creature level is no more than (SkipLowerLevels) levels +# below of the Highest player level, do not scale up. +# +# To disable scaling instance levels UP, set this to the max level of your server (likely 80). +# To disable this feature entirely and scale all lower-level instances, set this to 0. +# +# Default: 5 +AutoBalance.LevelScaling.SkipHigherLevels = 3 +AutoBalance.LevelScaling.SkipLowerLevels = 5 + +# +# AutoBalance.LevelScaling.DynamicLevel.Ceiling.* +# Sets the maximum number of levels creatures scaled in "dynamic" mode can be OVER your highest-level +# player. The creature in the dungeon with the highest original level will be set to the highest-level +# player's level + this value. +# +# Only takes effect if AutoBalance.LevelScaling.Method = "dynamic" +# +# Default: 1 (Dungeons), 2 (Heroic Dungeons), 3 (Raids), 3 (Heroic Raids) +# +# AutoBalance.LevelScaling.DynamicLevel.Floor.* +# Sets the maximum number of levels creatures scaled in "dynamic" mode can be UNDER your highest-level +# player. For instances with wide level spreads, ensures that the level differences stay reasonable. +# +# Only takes effect if AutoBalance.LevelScaling.Method = "dynamic" +# +# Default: 5 +AutoBalance.LevelScaling.DynamicLevel.Ceiling.Dungeons = 1 +AutoBalance.LevelScaling.DynamicLevel.Floor.Dungeons = 5 + +AutoBalance.LevelScaling.DynamicLevel.Ceiling.HeroicDungeons = 2 +AutoBalance.LevelScaling.DynamicLevel.Floor.HeroicDungeons = 5 + +AutoBalance.LevelScaling.DynamicLevel.Ceiling.Raids = 3 +AutoBalance.LevelScaling.DynamicLevel.Floor.Raids = 5 + +AutoBalance.LevelScaling.DynamicLevel.Ceiling.HeroicRaids = 3 +AutoBalance.LevelScaling.DynamicLevel.Floor.HeroicRaids = 5 + +# +# AutoBalance.LevelScaling.DynamicLevel.PerInstance +# Allows setting per-instance Dynamic Level options. Specifying a value of `-1` will skip overriding of that value. +# You may omit entries from the end of the string if desired. Only one set of values should be specified per InstanceID. +# Set to a value of "" to disable the feature. +# +# Get a list of Instance IDs here: https://wowpedia.fandom.com/wiki/InstanceID#Classic +# +# Format: "[InstanceID] [SkipHigherLevels] [SkipLowerLevels] [DynamicLevelCeiling] [DynamicLevelFloor], [InstanceID] [SkipHigherLevels] [SkipLowerLevels] [DynamicLevelCeiling] [DynamicLevelFloor], ..." +# +# Example: AutoBalance.StatModifier.PerInstance="409 -1 -1 0 3, 568 3, 43 -1 8" +# +# Default: "229 -1 -1 1, 230 0 0" (Recommended adjustments) +# +# 229 - Blackrock Spire +# 230 - Blackrock Depths +AutoBalance.LevelScaling.DynamicLevel.PerInstance="229 -1 -1 1, 230 0 0" + +# +# AutoBalance.LevelScaling.DynamicLevel.DistanceCheck.PerInstance +# Some dungeons contain multiple wings that exist in the same InstanceID. These wings may have different level requirements. +# You may set this setting per-instance to ensure that only creatures within [WorldUnits] of any player are included in the +# instance level calculation. This will improve the accuracy of creature levels in instances that contain multiple wings. +# +# NOTE: If two players in a party enter two different wings of the same InstanceID at the same time, creature levels will be +# calculated incorrectly until one of those players leaves the instance. +# +# Get a list of Instance IDs here: https://wowpedia.fandom.com/wiki/InstanceID#Classic +# +# Format: "[InstanceID] [WorldUnits]" +# +# Example: AutoBalance.LevelScaling.DynamicLevel.DistanceCheck.PerInstance="189 500, 624 250" +# +# Default: "189 500" (Recommended adjustments) +# +# 189 - Scarlet Monastery +AutoBalance.LevelScaling.DynamicLevel.DistanceCheck.PerInstance="189 500" + +# +# AutoBalance.LevelScaling.LevelEndGameBoost +# End game creatures have an exponential (not linear) regression +# that is not correctly handled by db values. Keep this enabled +# to have stats as near possible to the official ones. +# +# Default: 1 (1 = ON, 0 = OFF) +AutoBalance.LevelScaling.EndGameBoost = 1 + +########################## +# +# Reward Scaling +# +########################## + +# +# AutoBalance.RewardScaling.Method +# Sets which method should be used when scaling down XP and Money in an instance. +# +# fixed: +# XP and Money will be divided by the maximum number of players in the group +# regardless of scaling settings. Each player will receive the calculated value. +# +# dynamic: +# XP and Money will be scaled based on the the health and damage modifiers that are applied to +# the creature. Level scaling is taken into account when determining the reward scaling. +# +# If scaling determines that a creature should have an XP scaling multiplier of .65, the creature +# will create 65% of the XP you would normally recieve from a creature at the scaled level. +# +# If scaling determines that a creature should have a money scaling multiplier of 1.5, the creature +# will create 150% of the money it would have at its original level and scaling. +# +# The XP and money is evenly split amongst all players in the instance. +# +# Possible values: "fixed" or "dynamic" +# +# Default: "dynamic" +AutoBalance.RewardScaling.Method="dynamic" + +# +# AutoBalance.RewardScaling.XP +# Scale XP based on the `AutoBalance.RewardScaling.Method` selection. +# +# Default: 1 (1 = ON, 0 = OFF) +# +# AutoBalance.RewardScaling.XP.Modifier +# Apply a flat modifier to the amount of XP that is rewarded to players. +# +# Only takes effect if `AutoBalance.RewardScaling.XP` is 1. +# +# If `RewardScaling.XP.Modifier` is set to 1.5 and RewardScaling determines that 100xp should be +# rewarded to each player, 150xp will be rewarded. +# +# Default: 1.0 (no change) +AutoBalance.RewardScaling.XP = 1 +AutoBalance.RewardScaling.XP.Modifier = 1.0 + +# +# AutoBalance.RewardScaling.Money +# Scale Money drops based on the `AutoBalance.RewardScaling.Method` selection. If set to 0, the full +# amount will be rewarded. +# +# Default: 1 (1 = ON, 0 = OFF) +# +# AutoBalance.RewardScaling.Money.Modifier +# Apply a flat modifier to the amount of money that is rewarded to players. +# +# Only takes effect if `AutoBalance.RewardScaling.Money` is 1. +# +# If `RewardScaling.Money.Modifier` is set to 1.5 and RewardScaling determines that 1 gold should be +# rewarded to each player, 1 gold 50 silver will be rewarded. +# +# Default: 1.0 (no change) +AutoBalance.RewardScaling.Money = 1 +AutoBalance.RewardScaling.Money.Modifier = 1.0 + +########################## +# +# Messages +# +########################## + +# +# AutoBalance.PlayerChangeNotify +# Set Auto Notifications to all players in Instance that player count has changed. +# Default: 1 (1 = ON, 0 = OFF) +AutoBalance.PlayerChangeNotify=1 + +########################## +# +# REWARD SYSTEM (experimental) +# +########################## + +# +# AutoBalance.reward.enable +# This is an experimental feature to reward a player that kill a boss when +# a pre-wotlk dungeon/raid is completed with creature levelling enabled. +# This is an idea to boost old contents even if you're end-game player. +# +# Default: 0 (1 = ON, 0 = OFF) +AutoBalance.reward.enable = 0 + + +# +# AutoBalance.reward.raidToken +# AutoBalance.reward.dungeonToken +# +# +# Default: +# raidToken -> emblem of frost (49426) +# dungeonToken -> emblem of triumph (47241) +AutoBalance.reward.raidToken = 49426 +AutoBalance.reward.dungeonToken = 47241 + +# +# AutoBalance.reward.MinPlayerReward +# This conf option checks how many players are in the same +# map before allowing the reward to be carried out +# if MinPlayerReward is set to two and player is soloing this conf doesn't reward then. +# this will give more a challenge to players for low level instances. +# +# Default: 1 +AutoBalance.reward.MinPlayerReward = 1 + +########################## +# +# Announcement +# +########################## + +# +# AutoBalanceAnnounce.enable +# Announce the module on login if it is enabled +# Default: 1 (1 = ON, 0 = OFF) +AutoBalanceAnnounce.enable=1 + +########################## +# +# Deprecated Settings +# +########################## + +# The following variables are deprecated and should not be used in new deployments. Their values should be left blank. +# They will still be applied to support backwards compatability, but will be removed entirely in a future release. +# Their entire functionality (and more) has been moved to new settings referenced in this config file. +# +AutoBalance.enable= +AutoBalance.PerDungeonScaling= +AutoBalance.PerDungeonBossScaling= +AutoBalance.BossInflectionMult= +AutoBalance.rate.global= +AutoBalance.rate.health= +AutoBalance.rate.mana= +AutoBalance.rate.armor= +AutoBalance.rate.damage= +AutoBalance.DungeonScaleDownXP= +AutoBalance.DungeonScaleDownMoney= +AutoBalance.LevelHigherOffset= +AutoBalance.LevelLowerOffset= + +# The following variables have been removed entirely and should not be used in a new or existing deployment. +# Their values should be left blank. +# Their entire functionality (and more) has been moved to new settings referenced in this config file. +AutoBalance.DungeonsOnly= +AutoBalance.levelUseDbValuesWhenExists= diff --git a/data/db-characters/base/.gitkeep b/data/db-characters/base/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/data/db-characters/base/group_difficulty.sql b/data/db-characters/base/group_difficulty.sql new file mode 100644 index 0000000..495b172 --- /dev/null +++ b/data/db-characters/base/group_difficulty.sql @@ -0,0 +1,6 @@ +DROP TABLE IF EXISTS acore_characters.group_difficulty; +CREATE TABLE acore_characters.group_difficulty +( + group_id int unsigned default '0' not null primary key, + difficulty tinyint unsigned default '0' not null +) diff --git a/data/db-characters/updates/.gitkeep b/data/db-characters/updates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/data/db-world/base/.gitkeep b/data/db-world/base/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/data/db-world/updates/.gitkeep b/data/db-world/updates/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/icon.png b/icon.png new file mode 100755 index 0000000000000000000000000000000000000000..6547b4ac55c5c8cbf93fc6d73c81c8b86821a174 GIT binary patch literal 35883 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 + +## Changes Proposed: +- +- + +## Issues Addressed: + +- Closes + +## SOURCE: + + +## Tests Performed: + +- +- + + +## How to Test the Changes: + + +1. +2. +3. diff --git a/setup_git_commit_template.sh b/setup_git_commit_template.sh new file mode 100755 index 0000000..7b52062 --- /dev/null +++ b/setup_git_commit_template.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +## Set a local git commit template +git config --local commit.template ".git_commit_template.txt" ; diff --git a/src/AB_loader.cpp b/src/AB_loader.cpp new file mode 100755 index 0000000..050de1e --- /dev/null +++ b/src/AB_loader.cpp @@ -0,0 +1,6 @@ +void AddAutoBalanceScripts(); + +void Addmod_autobalanceScripts() +{ + AddAutoBalanceScripts(); +} diff --git a/src/AutoBalance.cpp b/src/AutoBalance.cpp new file mode 100755 index 0000000..e75ed54 --- /dev/null +++ b/src/AutoBalance.cpp @@ -0,0 +1,3368 @@ +/* +* Copyright (C) 2018 AzerothCore +* Copyright (C) 2012 CVMagic +* Copyright (C) 2008-2010 TrinityCore +* Copyright (C) 2006-2009 ScriptDev2 +* Copyright (C) 1985-2010 KalCorp +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 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 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 . +*/ + +/* +* Script Name: AutoBalance +* Original Authors: KalCorp and Vaughner +* Maintainer(s): AzerothCore +* Original Script Name: AutoBalance +* Description: This script is intended to scale based on number of players, +* instance mobs & world bosses' level, health, mana, and damage. +*/ + +#include "Configuration/Config.h" +#include "Unit.h" +#include "Chat.h" +#include "Creature.h" +#include "Player.h" +#include "ObjectMgr.h" +#include "MapMgr.h" +#include "World.h" +#include "Map.h" +#include "ScriptMgr.h" +#include "Language.h" +#include +#include "AutoBalance.h" +#include "ScriptMgrMacros.h" +#include "Group.h" +#include "Log.h" +#include + +#if AC_COMPILER == AC_COMPILER_GNU +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + +using namespace Acore::ChatCommands; + +ABScriptMgr* ABScriptMgr::instance() +{ + static ABScriptMgr instance; + return &instance; +} + +bool ABScriptMgr::OnBeforeModifyAttributes(Creature *creature, uint32 & instancePlayerCount) +{ + auto ret = IsValidBoolScript([&](ABModuleScript* script) + { + return !script->OnBeforeModifyAttributes(creature, instancePlayerCount); + }); + + if (ret && *ret) + { + return false; + } + + return true; +} + +bool ABScriptMgr::OnAfterDefaultMultiplier(Creature *creature, float& defaultMultiplier) +{ + auto ret = IsValidBoolScript([&](ABModuleScript* script) + { + return !script->OnAfterDefaultMultiplier(creature, defaultMultiplier); + }); + + if (ret && *ret) + { + return false; + } + + return true; +} + +bool ABScriptMgr::OnBeforeUpdateStats(Creature* creature, uint32& scaledHealth, uint32& scaledMana, float& damageMultiplier, uint32& newBaseArmor) +{ + auto ret = IsValidBoolScript([&](ABModuleScript* script) + { + return !script->OnBeforeUpdateStats(creature, scaledHealth, scaledMana, damageMultiplier, newBaseArmor); + }); + + if (ret && *ret) + { + return false; + } + + return true; +} + +ABModuleScript::ABModuleScript(const char* name) + : ModuleScript(name) +{ + ScriptRegistry::AddScript(this); +} + + +class AutoBalanceCreatureInfo : public DataMap::Base +{ +public: + AutoBalanceCreatureInfo() {} + + uint64_t configTime; + + uint32 instancePlayerCount = 0; + uint8 selectedLevel = 0; + // this is used to detect creatures that update their entry + uint32 entry = 0; + float DamageMultiplier = 1.0f; + float HealthMultiplier = 1.0f; + float ManaMultiplier = 1.0f; + float ArmorMultiplier = 1.0f; + float CCDurationMultiplier = 1.0f; + + float XPModifier = 1.0f; + float MoneyModifier = 1.0f; + + uint8 UnmodifiedLevel = 0; + + bool isActive = false; + bool wasAliveNowDead = false; + bool isInCreatureList = false; +}; + +class AutoBalanceMapInfo : public DataMap::Base +{ +public: + AutoBalanceMapInfo() {} + + uint64_t configTime; + + uint32 playerCount = 0; + + uint8 mapLevel = 0; + uint8 lowestPlayerLevel = 0; + uint8 highestPlayerLevel = 0; + + uint8 lfgMinLevel = 0; + uint8 lfgTargetLevel = 80; + uint8 lfgMaxLevel = 80; + + bool enabled = false; + + std::vector allMapCreatures; + uint8 highestCreatureLevel = 0; + uint8 lowestCreatureLevel = 0; + float avgCreatureLevel; + uint32 activeCreatureCount = 0; + + bool isLevelScalingEnabled; + int levelScalingSkipHigherLevels, levelScalingSkipLowerLevels; + int levelScalingDynamicCeiling, levelScalingDynamicFloor; +}; + +class AutoBalanceStatModifiers : public DataMap::Base +{ +public: + AutoBalanceStatModifiers() {} + AutoBalanceStatModifiers(float global, float health, float mana, float armor, float damage, float ccduration) : + global(global), health(health), mana(mana), armor(armor), damage(damage), ccduration(ccduration) {} + float global; + float health; + float mana; + float armor; + float damage; + float ccduration; + + std::time_t configTime; +}; + +class AutoBalanceInflectionPointSettings : public DataMap::Base +{ +public: + AutoBalanceInflectionPointSettings() {} + AutoBalanceInflectionPointSettings(float value, float curveFloor, float curveCeiling) : + value(value), curveFloor(curveFloor), curveCeiling(curveCeiling) {} + float value; + float curveFloor; + float curveCeiling; +}; + +class AutoBalanceLevelScalingDynamicLevelSettings: public DataMap::Base +{ +public: + AutoBalanceLevelScalingDynamicLevelSettings() {} + AutoBalanceLevelScalingDynamicLevelSettings(int skipHigher, int skipLower, int ceiling, int floor) : + skipHigher(skipHigher), skipLower(skipLower), ceiling(ceiling), floor(floor) {} + int skipHigher; + int skipLower; + int ceiling; + int floor; +}; + +enum ScalingMethod { + AUTOBALANCE_SCALING_FIXED, + AUTOBALANCE_SCALING_DYNAMIC +}; + +// The map values correspond with the .AutoBalance.XX.Name entries in the configuration file. +static std::map forcedCreatureIds; +static std::map enabledDungeonIds; +static std::map dungeonOverrides; +static std::map bossOverrides; +static std::map statModifierOverrides; +static std::map statModifierBossOverrides; +static std::map statModifierCreatureOverrides; +static std::map levelScalingDynamicLevelOverrides; +static std::map levelScalingDistanceCheckOverrides; +// cheaphack for difficulty server-wide. +// Another value TODO in player class for the party leader's value to determine dungeon difficulty. +static int8 PlayerCountDifficultyOffset; +static bool LevelScaling; +static int8 LevelScalingSkipHigherLevels, LevelScalingSkipLowerLevels; +static int8 LevelScalingDynamicLevelCeilingDungeons, LevelScalingDynamicLevelFloorDungeons, LevelScalingDynamicLevelCeilingRaids, LevelScalingDynamicLevelFloorRaids; +static int8 LevelScalingDynamicLevelCeilingHeroicDungeons, LevelScalingDynamicLevelFloorHeroicDungeons, LevelScalingDynamicLevelCeilingHeroicRaids, LevelScalingDynamicLevelFloorHeroicRaids; +static ScalingMethod LevelScalingMethod; +static uint32 rewardRaid, rewardDungeon, MinPlayerReward; +static bool Announcement; +static bool LevelScalingEndGameBoost, PlayerChangeNotify, rewardEnabled; +static float MinHPModifier, MinManaModifier, MinDamageModifier, MinCCDurationModifier, MaxCCDurationModifier; + +// RewardScaling.* +static ScalingMethod RewardScalingMethod; +static bool RewardScalingXP, RewardScalingMoney; +static float RewardScalingXPModifier, RewardScalingMoneyModifier; + +// Track the last time the config was reloaded +static uint64_t lastConfigTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + +// Enable.* +static bool EnableGlobal; +static bool Enable5M, Enable10M, Enable15M, Enable20M, Enable25M, Enable40M; +static bool Enable5MHeroic, Enable10MHeroic, Enable25MHeroic; +static bool EnableOtherNormal, EnableOtherHeroic; + +// InflectionPoint* +static float InflectionPoint, InflectionPointCurveFloor, InflectionPointCurveCeiling, InflectionPointBoss; +static float InflectionPointHeroic, InflectionPointHeroicCurveFloor, InflectionPointHeroicCurveCeiling, InflectionPointHeroicBoss; +static float InflectionPointRaid, InflectionPointRaidCurveFloor, InflectionPointRaidCurveCeiling, InflectionPointRaidBoss; +static float InflectionPointRaidHeroic, InflectionPointRaidHeroicCurveFloor, InflectionPointRaidHeroicCurveCeiling, InflectionPointRaidHeroicBoss; + +static float InflectionPointRaid10M, InflectionPointRaid10MCurveFloor, InflectionPointRaid10MCurveCeiling, InflectionPointRaid10MBoss; +static float InflectionPointRaid10MHeroic, InflectionPointRaid10MHeroicCurveFloor, InflectionPointRaid10MHeroicCurveCeiling, InflectionPointRaid10MHeroicBoss; +static float InflectionPointRaid15M, InflectionPointRaid15MCurveFloor, InflectionPointRaid15MCurveCeiling, InflectionPointRaid15MBoss; +static float InflectionPointRaid20M, InflectionPointRaid20MCurveFloor, InflectionPointRaid20MCurveCeiling, InflectionPointRaid20MBoss; +static float InflectionPointRaid25M, InflectionPointRaid25MCurveFloor, InflectionPointRaid25MCurveCeiling, InflectionPointRaid25MBoss; +static float InflectionPointRaid25MHeroic, InflectionPointRaid25MHeroicCurveFloor, InflectionPointRaid25MHeroicCurveCeiling, InflectionPointRaid25MHeroicBoss; +static float InflectionPointRaid40M, InflectionPointRaid40MCurveFloor, InflectionPointRaid40MCurveCeiling, InflectionPointRaid40MBoss; + +// StatModifier* +static float StatModifier_Global, StatModifier_Health, StatModifier_Mana, StatModifier_Armor, StatModifier_Damage, StatModifier_CCDuration; +static float StatModifierHeroic_Global, StatModifierHeroic_Health, StatModifierHeroic_Mana, StatModifierHeroic_Armor, StatModifierHeroic_Damage, StatModifierHeroic_CCDuration; +static float StatModifierRaid_Global, StatModifierRaid_Health, StatModifierRaid_Mana, StatModifierRaid_Armor, StatModifierRaid_Damage, StatModifierRaid_CCDuration; +static float StatModifierRaidHeroic_Global, StatModifierRaidHeroic_Health, StatModifierRaidHeroic_Mana, StatModifierRaidHeroic_Armor, StatModifierRaidHeroic_Damage, StatModifierRaidHeroic_CCDuration; + +static float StatModifierRaid10M_Global, StatModifierRaid10M_Health, StatModifierRaid10M_Mana, StatModifierRaid10M_Armor, StatModifierRaid10M_Damage, StatModifierRaid10M_CCDuration; +static float StatModifierRaid10MHeroic_Global, StatModifierRaid10MHeroic_Health, StatModifierRaid10MHeroic_Mana, StatModifierRaid10MHeroic_Armor, StatModifierRaid10MHeroic_Damage, StatModifierRaid10MHeroic_CCDuration; +static float StatModifierRaid15M_Global, StatModifierRaid15M_Health, StatModifierRaid15M_Mana, StatModifierRaid15M_Armor, StatModifierRaid15M_Damage, StatModifierRaid15M_CCDuration; +static float StatModifierRaid20M_Global, StatModifierRaid20M_Health, StatModifierRaid20M_Mana, StatModifierRaid20M_Armor, StatModifierRaid20M_Damage, StatModifierRaid20M_CCDuration; +static float StatModifierRaid25M_Global, StatModifierRaid25M_Health, StatModifierRaid25M_Mana, StatModifierRaid25M_Armor, StatModifierRaid25M_Damage, StatModifierRaid25M_CCDuration; +static float StatModifierRaid25MHeroic_Global, StatModifierRaid25MHeroic_Health, StatModifierRaid25MHeroic_Mana, StatModifierRaid25MHeroic_Armor, StatModifierRaid25MHeroic_Damage, StatModifierRaid25MHeroic_CCDuration; +static float StatModifierRaid40M_Global, StatModifierRaid40M_Health, StatModifierRaid40M_Mana, StatModifierRaid40M_Armor, StatModifierRaid40M_Damage, StatModifierRaid40M_CCDuration; + +// StatModifier* (Boss) +static float StatModifier_Boss_Global, StatModifier_Boss_Health, StatModifier_Boss_Mana, StatModifier_Boss_Armor, StatModifier_Boss_Damage, StatModifier_Boss_CCDuration; +static float StatModifierHeroic_Boss_Global, StatModifierHeroic_Boss_Health, StatModifierHeroic_Boss_Mana, StatModifierHeroic_Boss_Armor, StatModifierHeroic_Boss_Damage, StatModifierHeroic_Boss_CCDuration; +static float StatModifierRaid_Boss_Global, StatModifierRaid_Boss_Health, StatModifierRaid_Boss_Mana, StatModifierRaid_Boss_Armor, StatModifierRaid_Boss_Damage, StatModifierRaid_Boss_CCDuration; +static float StatModifierRaidHeroic_Boss_Global, StatModifierRaidHeroic_Boss_Health, StatModifierRaidHeroic_Boss_Mana, StatModifierRaidHeroic_Boss_Armor, StatModifierRaidHeroic_Boss_Damage, StatModifierRaidHeroic_Boss_CCDuration; + +static float StatModifierRaid10M_Boss_Global, StatModifierRaid10M_Boss_Health, StatModifierRaid10M_Boss_Mana, StatModifierRaid10M_Boss_Armor, StatModifierRaid10M_Boss_Damage, StatModifierRaid10M_Boss_CCDuration; +static float StatModifierRaid10MHeroic_Boss_Global, StatModifierRaid10MHeroic_Boss_Health, StatModifierRaid10MHeroic_Boss_Mana, StatModifierRaid10MHeroic_Boss_Armor, StatModifierRaid10MHeroic_Boss_Damage, StatModifierRaid10MHeroic_Boss_CCDuration; +static float StatModifierRaid15M_Boss_Global, StatModifierRaid15M_Boss_Health, StatModifierRaid15M_Boss_Mana, StatModifierRaid15M_Boss_Armor, StatModifierRaid15M_Boss_Damage, StatModifierRaid15M_Boss_CCDuration; +static float StatModifierRaid20M_Boss_Global, StatModifierRaid20M_Boss_Health, StatModifierRaid20M_Boss_Mana, StatModifierRaid20M_Boss_Armor, StatModifierRaid20M_Boss_Damage, StatModifierRaid20M_Boss_CCDuration; +static float StatModifierRaid25M_Boss_Global, StatModifierRaid25M_Boss_Health, StatModifierRaid25M_Boss_Mana, StatModifierRaid25M_Boss_Armor, StatModifierRaid25M_Boss_Damage, StatModifierRaid25M_Boss_CCDuration; +static float StatModifierRaid25MHeroic_Boss_Global, StatModifierRaid25MHeroic_Boss_Health, StatModifierRaid25MHeroic_Boss_Mana, StatModifierRaid25MHeroic_Boss_Armor, StatModifierRaid25MHeroic_Boss_Damage, StatModifierRaid25MHeroic_Boss_CCDuration; +static float StatModifierRaid40M_Boss_Global, StatModifierRaid40M_Boss_Health, StatModifierRaid40M_Boss_Mana, StatModifierRaid40M_Boss_Armor, StatModifierRaid40M_Boss_Damage, StatModifierRaid40M_Boss_CCDuration; + +void LoadEnabledDungeons(std::string dungeonIdString) // Used for reading the string from the configuration file for selecting dungeons to scale +{ + std::string delimitedValue; + std::stringstream dungeonIdStream; + + dungeonIdStream.str(dungeonIdString); + while (std::getline(dungeonIdStream, delimitedValue, ',')) // Process each dungeon ID in the string, delimited by the comma - "," and then space " " + { + std::string pairOne, pairTwo; + std::stringstream dungeonPairStream(delimitedValue); + dungeonPairStream>>pairOne>>pairTwo; + auto dungeonMapId = atoi(pairOne.c_str()); + auto minPlayers = atoi(pairTwo.c_str()); + enabledDungeonIds[dungeonMapId] = minPlayers; + } +} + +std::map LoadInflectionPointOverrides(std::string dungeonIdString) // Used for reading the string from the configuration file for selecting dungeons to override +{ + std::string delimitedValue; + std::stringstream dungeonIdStream; + std::map overrideMap; + + dungeonIdStream.str(dungeonIdString); + while (std::getline(dungeonIdStream, delimitedValue, ',')) // Process each dungeon ID in the string, delimited by the comma - "," and then space " " + { + std::string val1, val2, val3, val4; + std::stringstream dungeonPairStream(delimitedValue); + dungeonPairStream >> val1 >> val2 >> val3 >> val4; + + auto dungeonMapId = atoi(val1.c_str()); + + // Replace any missing values with -1 + if (val2.empty()) { val2 = "-1"; } + if (val3.empty()) { val3 = "-1"; } + if (val4.empty()) { val4 = "-1"; } + + AutoBalanceInflectionPointSettings ipSettings = AutoBalanceInflectionPointSettings( + atof(val2.c_str()), + atof(val3.c_str()), + atof(val4.c_str()) + ); + + overrideMap[dungeonMapId] = ipSettings; + } + + return overrideMap; +} + +std::map LoadStatModifierOverrides(std::string dungeonIdString) // Used for reading the string from the configuration file for per-dungeon stat modifiers +{ + std::string delimitedValue; + std::stringstream dungeonIdStream; + std::map overrideMap; + + dungeonIdStream.str(dungeonIdString); + while (std::getline(dungeonIdStream, delimitedValue, ',')) // Process each dungeon ID in the string, delimited by the comma - "," and then space " " + { + std::string val1, val2, val3, val4, val5, val6, val7; + std::stringstream dungeonStream(delimitedValue); + dungeonStream >> val1 >> val2 >> val3 >> val4 >> val5 >> val6 >> val7; + + auto dungeonMapId = atoi(val1.c_str()); + + // Replace any missing values with -1 + if (val2.empty()) { val2 = "-1"; } + if (val3.empty()) { val3 = "-1"; } + if (val4.empty()) { val4 = "-1"; } + if (val5.empty()) { val5 = "-1"; } + if (val6.empty()) { val6 = "-1"; } + if (val7.empty()) { val7 = "-1"; } + + AutoBalanceStatModifiers statSettings = AutoBalanceStatModifiers( + atof(val2.c_str()), + atof(val3.c_str()), + atof(val4.c_str()), + atof(val5.c_str()), + atof(val6.c_str()), + atof(val7.c_str()) + ); + + overrideMap[dungeonMapId] = statSettings; + } + + return overrideMap; +} + +std::map LoadDynamicLevelOverrides(std::string dungeonIdString) // Used for reading the string from the configuration file for per-dungeon dynamic level overrides +{ + std::string delimitedValue; + std::stringstream dungeonIdStream; + std::map overrideMap; + + dungeonIdStream.str(dungeonIdString); + while (std::getline(dungeonIdStream, delimitedValue, ',')) // Process each dungeon ID in the string, delimited by the comma - "," and then space " " + { + std::string val1, val2, val3, val4, val5; + std::stringstream dungeonStream(delimitedValue); + dungeonStream >> val1 >> val2 >> val3 >> val4 >> val5; + + auto dungeonMapId = atoi(val1.c_str()); + + // Replace any missing values with -1 + if (val2.empty()) { val2 = "-1"; } + if (val3.empty()) { val3 = "-1"; } + if (val4.empty()) { val3 = "-1"; } + if (val5.empty()) { val3 = "-1"; } + + AutoBalanceLevelScalingDynamicLevelSettings dynamicLevelSettings = AutoBalanceLevelScalingDynamicLevelSettings( + atoi(val2.c_str()), + atoi(val3.c_str()), + atoi(val4.c_str()), + atoi(val5.c_str()) + ); + + overrideMap[dungeonMapId] = dynamicLevelSettings; + } + + return overrideMap; +} + +std::map LoadDistanceCheckOverrides(std::string dungeonIdString) +{ + std::string delimitedValue; + std::stringstream dungeonIdStream; + std::map overrideMap; + + dungeonIdStream.str(dungeonIdString); + while (std::getline(dungeonIdStream, delimitedValue, ',')) // Process each dungeon ID in the string, delimited by the comma - "," and then space " " + { + std::string val1, val2; + std::stringstream dungeonStream(delimitedValue); + dungeonStream >> val1 >> val2; + + auto dungeonMapId = atoi(val1.c_str()); + overrideMap[dungeonMapId] = atoi(val2.c_str()); + } + + return overrideMap; +} + + +bool isEnabledDungeon(uint32 dungeonId) +{ + return (enabledDungeonIds.find(dungeonId) != enabledDungeonIds.end()); +} + +bool perDungeonScalingEnabled() +{ + return (!enabledDungeonIds.empty()); +} + +bool hasDungeonOverride(uint32 dungeonId) +{ + return (dungeonOverrides.find(dungeonId) != dungeonOverrides.end()); +} + +bool hasBossOverride(uint32 dungeonId) +{ + return (bossOverrides.find(dungeonId) != bossOverrides.end()); +} + +bool hasStatModifierOverride(uint32 dungeonId) +{ + return (statModifierOverrides.find(dungeonId) != statModifierOverrides.end()); +} + +bool hasStatModifierBossOverride(uint32 dungeonId) +{ + return (statModifierBossOverrides.find(dungeonId) != statModifierBossOverrides.end()); +} + +bool hasStatModifierCreatureOverride(uint32 creatureId) +{ + return (statModifierCreatureOverrides.find(creatureId) != statModifierCreatureOverrides.end()); +} + +bool hasDynamicLevelOverride(uint32 dungeonId) +{ + return (levelScalingDynamicLevelOverrides.find(dungeonId) != levelScalingDynamicLevelOverrides.end()); +} + +bool hasLevelScalingDistanceCheckOverride(uint32 dungeonId) +{ + return (levelScalingDistanceCheckOverrides.find(dungeonId) != levelScalingDistanceCheckOverrides.end()); +} + +bool ShouldMapBeEnabled(Map* map) +{ + if (map->IsDungeon() || map->IsRaid()) + { + // get the current instance map + auto instanceMap = ((InstanceMap*)sMapMgr->FindMap(map->GetId(), map->GetInstanceId())); + + // if there wasn't one, then we're not in an instance + if (!instanceMap) + { + return false; + } + + // get the max player count for the instance + auto maxPlayerCount = instanceMap->GetMaxPlayers(); + + // if the player count is less than 1, then we're not in an instance + if (maxPlayerCount < 1) + { + return false; + } + + // use the configuration variables to determine if this instance type/size should have scaling enabled + if (instanceMap->IsHeroic()) + { + switch (maxPlayerCount) + { + case 5: + return Enable5MHeroic; + case 10: + return Enable10MHeroic; + case 25: + return Enable25MHeroic; + default: + return EnableOtherHeroic; + } + } + else + { + switch (maxPlayerCount) + { + case 5: + return Enable5M; + case 10: + return Enable10M; + case 15: + return Enable15M; + case 20: + return Enable20M; + case 25: + return Enable25M; + case 40: + return Enable40M; + default: + return EnableOtherNormal; + } + } + } + else + { + // we're not in a dungeon or a raid, we never scale + return false; + } +} + +void LoadMapSettings(Map* map) +{ + // Load (or create) the map's info + AutoBalanceMapInfo *mapABInfo=map->CustomData.GetDefault("AutoBalanceMapInfo"); + + // create an InstanceMap object + InstanceMap* instanceMap = map->ToInstanceMap(); + + //check for null pointer + if (!map) + { + return; + } + + if (!map->IsDungeon() && !map->IsRaid()) + { + return; + } + + // should the map be enabled at all? + mapABInfo->enabled = ShouldMapBeEnabled(map); + + // + // Dynamic Level Scaling Floor and Ceiling + // + + // 5-player normal dungeons + if (instanceMap->GetMaxPlayers() <= 5 && !instanceMap->IsHeroic()) + { + mapABInfo->levelScalingDynamicCeiling = LevelScalingDynamicLevelCeilingDungeons; + mapABInfo->levelScalingDynamicFloor = LevelScalingDynamicLevelFloorDungeons; + + } + // 5-player heroic dungeons + else if (instanceMap->GetMaxPlayers() <= 5 && instanceMap->IsHeroic()) + { + mapABInfo->levelScalingDynamicCeiling = LevelScalingDynamicLevelCeilingHeroicDungeons; + mapABInfo->levelScalingDynamicFloor = LevelScalingDynamicLevelFloorHeroicDungeons; + } + // Normal raids + else if (instanceMap->GetMaxPlayers() > 5 && !instanceMap->IsHeroic()) + { + mapABInfo->levelScalingDynamicCeiling = LevelScalingDynamicLevelCeilingRaids; + mapABInfo->levelScalingDynamicFloor = LevelScalingDynamicLevelFloorRaids; + } + // Heroic raids + else if (instanceMap->GetMaxPlayers() > 5 && instanceMap->IsHeroic()) + { + mapABInfo->levelScalingDynamicCeiling = LevelScalingDynamicLevelCeilingHeroicRaids; + mapABInfo->levelScalingDynamicFloor = LevelScalingDynamicLevelFloorHeroicRaids; + } + // something went wrong + else + { + LOG_ERROR("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Unable to determine dynamic scaling floor and ceiling for instance {}.", instanceMap->GetMapName()); + mapABInfo->levelScalingDynamicCeiling = 3; + mapABInfo->levelScalingDynamicFloor = 5; + } + + // + // Level Scaling Skip Levels + // + + // Load the global settings into the map + mapABInfo->levelScalingSkipHigherLevels = LevelScalingSkipHigherLevels; + mapABInfo->levelScalingSkipLowerLevels = LevelScalingSkipLowerLevels; + + // + // Per-instance overrides, if applicable + // + if (hasDynamicLevelOverride(map->GetId())) + { + AutoBalanceLevelScalingDynamicLevelSettings* myDynamicLevelSettings = &levelScalingDynamicLevelOverrides[map->GetId()]; + + // LevelScaling.SkipHigherLevels + if (myDynamicLevelSettings->skipHigher != -1) + mapABInfo->levelScalingSkipHigherLevels = myDynamicLevelSettings->skipHigher; + + // LevelScaling.SkipLowerLevels + if (myDynamicLevelSettings->skipLower != -1) + mapABInfo->levelScalingSkipLowerLevels = myDynamicLevelSettings->skipLower; + + // LevelScaling.DynamicLevelCeiling + if (myDynamicLevelSettings->ceiling != -1) + mapABInfo->levelScalingDynamicCeiling = myDynamicLevelSettings->ceiling; + + // LevelScaling.DynamicLevelFloor + if (myDynamicLevelSettings->floor != -1) + mapABInfo->levelScalingDynamicFloor = myDynamicLevelSettings->floor; + } +} + +void AddCreatureToMapData(Creature* creature, bool addToCreatureList = true, Player* playerToExcludeFromChecks = nullptr, bool forceRecalculation = false) +{ + // make sure we have a creature and that it's assigned to a map + if (!creature || !creature->GetMap()) + return; + + // if this isn't a dungeon or a battleground, skip + if (!(creature->GetMap()->IsDungeon() || creature->GetMap()->IsRaid())) + return; + + // get AutoBalance data + InstanceMap* instanceMap = ((InstanceMap*)sMapMgr->FindMap(creature->GetMapId(), creature->GetInstanceId())); + AutoBalanceMapInfo *mapABInfo=instanceMap->CustomData.GetDefault("AutoBalanceMapInfo"); + AutoBalanceCreatureInfo *creatureABInfo=creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); + + // store the creature's original level if this is the first time seeing it + if (creatureABInfo->UnmodifiedLevel == 0) + { + // handle summoned creatures + if (creature->IsSummon()) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): Creature {} ({}->\?\?) is a summon.", creature->GetName(), creature->GetLevel()); + if (creature->ToTempSummon() && + creature->ToTempSummon()->GetSummoner() && + creature->ToTempSummon()->GetSummoner()->ToCreature()) + { + Creature* summoner = creature->ToTempSummon()->GetSummoner()->ToCreature(); + if (!summoner) + { + creatureABInfo->UnmodifiedLevel = mapABInfo->avgCreatureLevel; + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): Summoned creature {} ({}) is not owned by a summoner.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + } + else + { + Creature* summonerCreature = summoner->ToCreature(); + AutoBalanceCreatureInfo *summonerABInfo=summonerCreature->CustomData.GetDefault("AutoBalanceCreatureInfo"); + + if (summonerABInfo->UnmodifiedLevel > 0) + { + creatureABInfo->UnmodifiedLevel = summonerABInfo->UnmodifiedLevel; + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): Summoned creature {} ({}) owned by {} ({}->{})", creature->GetName(), creatureABInfo->UnmodifiedLevel, summonerCreature->GetName(), summonerABInfo->UnmodifiedLevel, summonerCreature->GetLevel()); + } + else + { + creatureABInfo->UnmodifiedLevel = summonerCreature->GetLevel(); + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): Summoned creature {} ({}) owned by {} ({})", creature->GetName(), creatureABInfo->UnmodifiedLevel, summonerCreature->GetName(), summonerCreature->GetLevel()); + } + } + } + else + { + creatureABInfo->UnmodifiedLevel = mapABInfo->avgCreatureLevel; + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): Summoned creature {} ({}) does not have a summoner.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + } + + // if this is a summon, we shouldn't track it in any list and it does not contribute to the average level + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): Summoned creature {} ({}) will not affect the map's stats.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + return; + + } + // creature isn't a summon, just store their unmodified level + else + { + creatureABInfo->UnmodifiedLevel = creature->GetLevel(); + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({})", creature->GetName(), creatureABInfo->UnmodifiedLevel); + } + } + + // if this is a creature controlled by the player, skip + if (((creature->IsHunterPet() || creature->IsPet() || creature->IsSummon()) && creature->IsControlledByPlayer())) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is controlled by the player - skip.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + return; + } + + // if this is a non-relevant creature, skip + if (creature->IsCritter() || creature->IsTotem() || creature->IsTrigger()) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is a critter, totem, or trigger - skip.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + return; + } + + // if the creature level is below 85% of the minimum LFG level, assume it's a flavor creature and shouldn't be tracked or modified + if (creatureABInfo->UnmodifiedLevel < ((float)mapABInfo->lfgMinLevel * .85f)) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is below 85% of the LFG min level of {} and is NOT tracked.", creature->GetName(), creatureABInfo->UnmodifiedLevel, mapABInfo->lfgMinLevel); + return; + } + + // if the creature level is above 125% of the maximum LFG level, assume it's a flavor creature or holiday boss and shouldn't be tracked or modified + if (creatureABInfo->UnmodifiedLevel > ((float)mapABInfo->lfgMaxLevel * 1.15f)) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is above 115% of the LFG max level of {} and is NOT tracked.", creature->GetName(), creatureABInfo->UnmodifiedLevel, mapABInfo->lfgMaxLevel); + return; + } + + // is this creature already in the map's creature list? + bool isCreatureAlreadyInCreatureList = creatureABInfo->isInCreatureList; + + // add the creature to the map's creature list if configured to do so + if (addToCreatureList && !isCreatureAlreadyInCreatureList) + { + mapABInfo->allMapCreatures.push_back(creature); + creatureABInfo->isInCreatureList = true; + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is creature #{} in the creature list.", creature->GetName(), creatureABInfo->UnmodifiedLevel, mapABInfo->allMapCreatures.size()); + } + + // alter stats for the map if needed + bool isIncludedInMapStats = true; + + // if this creature was already in the creature list, don't consider it for map stats (again) + // exception for if forceRecalculation is true (used on player enter/exit to recalculate map stats) + if (isCreatureAlreadyInCreatureList && !forceRecalculation) + { + isIncludedInMapStats = false; + } + + Map::PlayerList const &playerList = creature->GetMap()->GetPlayers(); + if (!playerList.IsEmpty()) + { + // only do these additional checks if we still think they need to be applied to the map stats + if (isIncludedInMapStats) + { + // if the creature is vendor, trainer, or has gossip, don't use it to update map stats + if ((creature->IsVendor() || + creature->HasNpcFlag(UNIT_NPC_FLAG_GOSSIP) || + creature->HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER) || + creature->HasNpcFlag(UNIT_NPC_FLAG_TRAINER) || + creature->HasNpcFlag(UNIT_NPC_FLAG_TRAINER_PROFESSION) || + creature->HasNpcFlag(UNIT_NPC_FLAG_REPAIR) || + creature->HasUnitFlag(UNIT_FLAG_IMMUNE_TO_PC) || + creature->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE)) && + (!creature->IsDungeonBoss()) + ) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is a a vendor, trainer, or is otherwise not attackable - do not include in map stats.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + isIncludedInMapStats = false; + } + else + { + // if the creature is friendly to a player, don't use it to update map stats + for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) + { + Player* playerHandle = playerIteration->GetSource(); + + // if this player matches the player we're supposed to skip, skip + if (playerHandle == playerToExcludeFromChecks) + { + continue; + } + + // if the creature is friendly and not a boss + if (creature->IsFriendlyTo(playerHandle) && !creature->IsDungeonBoss()) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is friendly to {} - do not include in map stats.", creature->GetName(), creatureABInfo->UnmodifiedLevel, playerHandle->GetName()); + isIncludedInMapStats = false; + break; + } + } + + // perform the distance check if an override is configured for this map + if (hasLevelScalingDistanceCheckOverride(instanceMap->GetId())) + { + uint32 distance = levelScalingDistanceCheckOverrides[instanceMap->GetId()]; + bool isPlayerWithinDistance = false; + + for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) + { + Player* playerHandle = playerIteration->GetSource(); + + // if this player matches the player we're supposed to skip, skip + if (playerHandle == playerToExcludeFromChecks) + { + continue; + } + + if (playerHandle->IsWithinDist(creature, 500)) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is in range ({} world units) of player {} and is considered active.", creature->GetName(), creatureABInfo->UnmodifiedLevel, distance, playerHandle->GetName()); + isPlayerWithinDistance = true; + break; + } + else + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is NOT in range ({} world units) of any player and is NOT considered active.", creature->GetName(), creature->GetLevel(), distance); + } + } + + // if no players were within the distance, don't include this creature in the map stats + if (!isPlayerWithinDistance) + isIncludedInMapStats = false; + } + } + } + + if (isIncludedInMapStats) + { + // mark this creature as being considered in the map stats + creatureABInfo->isActive = true; + + // update the highest and lowest creature levels + if (creatureABInfo->UnmodifiedLevel > mapABInfo->highestCreatureLevel || mapABInfo->highestCreatureLevel == 0) + mapABInfo->highestCreatureLevel = creatureABInfo->UnmodifiedLevel; + if (creatureABInfo->UnmodifiedLevel < mapABInfo->lowestCreatureLevel || mapABInfo->lowestCreatureLevel == 0) + mapABInfo->lowestCreatureLevel = creatureABInfo->UnmodifiedLevel; + + // calculate the new average creature level + float creatureCount = mapABInfo->activeCreatureCount; + float newAvgCreatureLevel = (((float)mapABInfo->avgCreatureLevel * creatureCount) + (float)creatureABInfo->UnmodifiedLevel) / (creatureCount + 1.0f); + mapABInfo->avgCreatureLevel = newAvgCreatureLevel; + + // increment the active creature counter + mapABInfo->activeCreatureCount++; + + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is included in map stats, adjusting avgCreatureLevel to {}", creature->GetName(), creatureABInfo->UnmodifiedLevel, newAvgCreatureLevel); + + // reset the last config time so that the map data will get updated + lastConfigTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): lastConfigTime reset to {}", lastConfigTime); + } + else if (isCreatureAlreadyInCreatureList) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is already included in map stats.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + } + else + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): {} ({}) is NOT included in map stats.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + } + + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::AddCreatureToMapData(): There are {} active creatures.", mapABInfo->activeCreatureCount); + } +} + +void RemoveCreatureFromMapData(Creature* creature) +{ + // get map data + AutoBalanceMapInfo *mapABInfo=creature->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); + + // if the creature is in the all creature list, remove it + if (mapABInfo->allMapCreatures.size() > 0) + { + for (std::vector::iterator creatureIteration = mapABInfo->allMapCreatures.begin(); creatureIteration != mapABInfo->allMapCreatures.end(); ++creatureIteration) + { + if (*creatureIteration == creature) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreature::RemoveCreatureFromMapData(): {} ({}) is in the creature list and will be removed. There are {} creatures left.", creature->GetName(), creature->GetLevel(), mapABInfo->allMapCreatures.size() - 1); + mapABInfo->allMapCreatures.erase(creatureIteration); + + // mark this creature as removed + AutoBalanceCreatureInfo *creatureABInfo=creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); + creatureABInfo->isInCreatureList = false; + break; + } + } + } +} + +void UpdateMapLevelIfNeeded(Map* map) +{ + // get map data + AutoBalanceMapInfo *mapABInfo=map->CustomData.GetDefault("AutoBalanceMapInfo"); + + // if map needs update + if (mapABInfo->configTime != lastConfigTime) + { + LOG_DEBUG("module.AutoBalance", "UpdateMapLevelIfNeeded(): Map {} config is out of date ({} != {}) and will be updated.", + map->GetMapName(), + mapABInfo->configTime, + lastConfigTime); + + // load the map's settings + LoadMapSettings(map); + + // if LevelScaling is disabled OR if the average creature level is inside the skip range, + // set the map level to the average creature level, rounded to the nearest integer + if (!LevelScaling || + ((mapABInfo->avgCreatureLevel <= mapABInfo->highestPlayerLevel + mapABInfo->levelScalingSkipHigherLevels && mapABInfo->levelScalingSkipHigherLevels != 0) && + (mapABInfo->avgCreatureLevel >= mapABInfo->highestPlayerLevel - mapABInfo->levelScalingSkipLowerLevels && mapABInfo->levelScalingSkipLowerLevels != 0)) + ) + { + mapABInfo->mapLevel = (uint8)(mapABInfo->avgCreatureLevel + 0.5f); + mapABInfo->isLevelScalingEnabled = false; + } + // If the average creature level is lower than the highest player level, + // set the map level to the average creature level, rounded to the nearest integer + else if (mapABInfo->avgCreatureLevel <= mapABInfo->highestPlayerLevel) + { + mapABInfo->mapLevel = (uint8)(mapABInfo->avgCreatureLevel + 0.5f); + mapABInfo->isLevelScalingEnabled = true; + } + // caps at the highest player level + else + { + mapABInfo->mapLevel = mapABInfo->highestPlayerLevel; + mapABInfo->isLevelScalingEnabled = true; + } + + LOG_DEBUG("module.AutoBalance", "UpdateMapLevelIfNeeded(): Map {} level is now {}.", map->GetMapName(), mapABInfo->mapLevel); + + // mark the config updated + mapABInfo->configTime = lastConfigTime; + } +} + +void UpdateMapPlayerStats(Map* map) +{ + // get the map's info + AutoBalanceMapInfo *mapABInfo=map->CustomData.GetDefault("AutoBalanceMapInfo"); + + // get the map's player list + Map::PlayerList const &playerList = map->GetPlayers(); + + // if there are players on the map + if (!playerList.IsEmpty()) + { + uint8 highestPlayerLevel = 0; + uint8 lowestPlayerLevel = 0; + + // iterate through the players and update the highest and lowest player levels + for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) + { + Player* playerHandle = playerIteration->GetSource(); + if (playerHandle && !playerHandle->IsGameMaster()) + { + if (playerHandle->getLevel() > highestPlayerLevel || highestPlayerLevel == 0) + highestPlayerLevel = playerHandle->getLevel(); + + if (playerHandle->getLevel() < lowestPlayerLevel || lowestPlayerLevel == 0) + lowestPlayerLevel = playerHandle->getLevel(); + } + mapABInfo->highestPlayerLevel = highestPlayerLevel; + mapABInfo->lowestPlayerLevel = lowestPlayerLevel; + } + + LOG_DEBUG("module.AutoBalance", "UpdateMapPlayerStats(): Map {} player level range: {} - {}.", map->GetMapName(), mapABInfo->lowestPlayerLevel, mapABInfo->highestPlayerLevel); + } + + // update the player count + mapABInfo->playerCount = map->GetPlayersCountExceptGMs(); +} + +void LoadForcedCreatureIdsFromString(std::string creatureIds, int forcedPlayerCount) // Used for reading the string from the configuration file to for those creatures who need to be scaled for XX number of players. +{ + std::string delimitedValue; + std::stringstream creatureIdsStream; + + creatureIdsStream.str(creatureIds); + while (std::getline(creatureIdsStream, delimitedValue, ',')) // Process each Creature ID in the string, delimited by the comma - "," + { + int creatureId = atoi(delimitedValue.c_str()); + if (creatureId >= 0) + { + forcedCreatureIds[creatureId] = forcedPlayerCount; + } + } +} + +int GetForcedNumPlayers(int creatureId) +{ + if (forcedCreatureIds.find(creatureId) == forcedCreatureIds.end()) // Don't want the forcedCreatureIds map to blowup to a massive empty array + { + return -1; + } + return forcedCreatureIds[creatureId]; +} + +class AutoBalance_WorldScript : public WorldScript +{ + public: + AutoBalance_WorldScript() + : WorldScript("AutoBalance_WorldScript") + { + } + + void OnBeforeConfigLoad(bool /*reload*/) override + { + SetInitialWorldSettings(); + lastConfigTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + void OnStartup() override + { + } + + void SetInitialWorldSettings() + { + forcedCreatureIds.clear(); + enabledDungeonIds.clear(); + dungeonOverrides.clear(); + bossOverrides.clear(); + statModifierOverrides.clear(); + statModifierBossOverrides.clear(); + statModifierCreatureOverrides.clear(); + levelScalingDynamicLevelOverrides.clear(); + levelScalingDistanceCheckOverrides.clear(); + LoadForcedCreatureIdsFromString(sConfigMgr->GetOption("AutoBalance.ForcedID40", ""), 40); + LoadForcedCreatureIdsFromString(sConfigMgr->GetOption("AutoBalance.ForcedID25", ""), 25); + LoadForcedCreatureIdsFromString(sConfigMgr->GetOption("AutoBalance.ForcedID10", ""), 10); + LoadForcedCreatureIdsFromString(sConfigMgr->GetOption("AutoBalance.ForcedID5", ""), 5); + LoadForcedCreatureIdsFromString(sConfigMgr->GetOption("AutoBalance.ForcedID2", ""), 2); + LoadForcedCreatureIdsFromString(sConfigMgr->GetOption("AutoBalance.DisabledID", ""), 0); + LoadEnabledDungeons(sConfigMgr->GetOption("AutoBalance.PerDungeonPlayerCounts", "")); + + // Overrides + if (sConfigMgr->GetOption("AutoBalance.PerDungeonScaling", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.PerDungeonScaling` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + dungeonOverrides = LoadInflectionPointOverrides( + sConfigMgr->GetOption("AutoBalance.InflectionPoint.PerInstance",sConfigMgr->GetOption("AutoBalance.PerDungeonScaling", "", false), false) + ); // `AutoBalance.PerDungeonScaling` for backwards compatibility + + if (sConfigMgr->GetOption("AutoBalance.PerDungeonBossScaling", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.PerDungeonBossScaling` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + bossOverrides = LoadInflectionPointOverrides( + sConfigMgr->GetOption("AutoBalance.InflectionPoint.Boss.PerInstance", sConfigMgr->GetOption("AutoBalance.PerDungeonBossScaling", "", false), false) + ); // `AutoBalance.PerDungeonBossScaling` for backwards compatibility + + statModifierOverrides = LoadStatModifierOverrides( + sConfigMgr->GetOption("AutoBalance.StatModifier.PerInstance", "", false) + ); + + statModifierBossOverrides = LoadStatModifierOverrides( + sConfigMgr->GetOption("AutoBalance.StatModifier.Boss.PerInstance", "", false) + ); + + statModifierCreatureOverrides = LoadStatModifierOverrides( + sConfigMgr->GetOption("AutoBalance.StatModifier.PerCreature", "", false) + ); + + levelScalingDynamicLevelOverrides = LoadDynamicLevelOverrides( + sConfigMgr->GetOption("AutoBalance.LevelScaling.DynamicLevel.PerInstance", "", false) + ); + + levelScalingDistanceCheckOverrides = LoadDistanceCheckOverrides( + sConfigMgr->GetOption("AutoBalance.LevelScaling.DynamicLevel.DistanceCheck.PerInstance", "", false) + ); + + // AutoBalance.Enable.* + // Deprecated setting warning + if (sConfigMgr->GetOption("AutoBalance.enable", -1, false) != -1) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.enable` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + + EnableGlobal = sConfigMgr->GetOption("AutoBalance.Enable.Global", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); // `AutoBalance.enable` for backwards compatibility + + Enable5M = sConfigMgr->GetOption("AutoBalance.Enable.5M", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); + Enable10M = sConfigMgr->GetOption("AutoBalance.Enable.10M", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); + Enable15M = sConfigMgr->GetOption("AutoBalance.Enable.15M", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); + Enable20M = sConfigMgr->GetOption("AutoBalance.Enable.20M", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); + Enable25M = sConfigMgr->GetOption("AutoBalance.Enable.25M", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); + Enable40M = sConfigMgr->GetOption("AutoBalance.Enable.40M", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); + EnableOtherNormal = sConfigMgr->GetOption("AutoBalance.Enable.OtherNormal", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); + + Enable5MHeroic = sConfigMgr->GetOption("AutoBalance.Enable.5MHeroic", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); + Enable10MHeroic = sConfigMgr->GetOption("AutoBalance.Enable.5MHeroic", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); + Enable25MHeroic = sConfigMgr->GetOption("AutoBalance.Enable.5MHeroic", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); + EnableOtherHeroic = sConfigMgr->GetOption("AutoBalance.Enable.5MHeroic", sConfigMgr->GetOption("AutoBalance.enable", 1, false)); + + // Deprecated setting warning + if (sConfigMgr->GetOption("AutoBalance.DungeonsOnly", -1, false) != -1) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.DungeonsOnly` defined in `AutoBalance.conf`. This variable has been removed and has no effect. Please see `AutoBalance.conf.dist` for more details."); + + if (sConfigMgr->GetOption("AutoBalance.levelUseDbValuesWhenExists", -1, false) != -1) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.levelUseDbValuesWhenExists` defined in `AutoBalance.conf`. This variable has been removed and has no effect. Please see `AutoBalance.conf.dist` for more details."); + + // Misc Settings + // TODO: Organize and standardize variable names + + PlayerChangeNotify = sConfigMgr->GetOption("AutoBalance.PlayerChangeNotify", 1); + + rewardEnabled = sConfigMgr->GetOption("AutoBalance.reward.enable", 1); + PlayerCountDifficultyOffset = sConfigMgr->GetOption("AutoBalance.playerCountDifficultyOffset", 0); + rewardRaid = sConfigMgr->GetOption("AutoBalance.reward.raidToken", 49426); + rewardDungeon = sConfigMgr->GetOption("AutoBalance.reward.dungeonToken", 47241); + MinPlayerReward = sConfigMgr->GetOption("AutoBalance.reward.MinPlayerReward", 1); + + // InflectionPoint* + // warn the console if deprecated values are detected + if (sConfigMgr->GetOption("AutoBalance.BossInflectionMult", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.BossInflectionMult` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + + InflectionPoint = sConfigMgr->GetOption("AutoBalance.InflectionPoint", 0.5f, false); + InflectionPointCurveFloor = sConfigMgr->GetOption("AutoBalance.InflectionPoint.CurveFloor", 0.0f, false); + InflectionPointCurveCeiling = sConfigMgr->GetOption("AutoBalance.InflectionPoint.CurveCeiling", 1.0f, false); + InflectionPointBoss = sConfigMgr->GetOption("AutoBalance.InflectionPoint.BossModifier", sConfigMgr->GetOption("AutoBalance.BossInflectionMult", 1.0f, false), false); // `AutoBalance.BossInflectionMult` for backwards compatibility + + InflectionPointHeroic = sConfigMgr->GetOption("AutoBalance.InflectionPointHeroic", 0.5f, false); + InflectionPointHeroicCurveFloor = sConfigMgr->GetOption("AutoBalance.InflectionPointHeroic.CurveFloor", 0.0f, false); + InflectionPointHeroicCurveCeiling = sConfigMgr->GetOption("AutoBalance.InflectionPointHeroic.CurveCeiling", 1.0f, false); + InflectionPointHeroicBoss = sConfigMgr->GetOption("AutoBalance.InflectionPointHeroic.BossModifier", sConfigMgr->GetOption("AutoBalance.BossInflectionMult", 1.0f, false), false); // `AutoBalance.BossInflectionMult` for backwards compatibility + + InflectionPointRaid = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid", 0.5f, false); + InflectionPointRaidCurveFloor = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid.CurveFloor", 0.0f, false); + InflectionPointRaidCurveCeiling = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid.CurveCeiling", 1.0f, false); + InflectionPointRaidBoss = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid.BossModifier", sConfigMgr->GetOption("AutoBalance.BossInflectionMult", 1.0f, false), false); // `AutoBalance.BossInflectionMult` for backwards compatibility + + InflectionPointRaidHeroic = sConfigMgr->GetOption("AutoBalance.InflectionPointRaidHeroic", 0.5f, false); + InflectionPointRaidHeroicCurveFloor = sConfigMgr->GetOption("AutoBalance.InflectionPointRaidHeroic.CurveFloor", 0.0f, false); + InflectionPointRaidHeroicCurveCeiling = sConfigMgr->GetOption("AutoBalance.InflectionPointRaidHeroic.CurveCeiling", 1.0f, false); + InflectionPointRaidHeroicBoss = sConfigMgr->GetOption("AutoBalance.InflectionPointRaidHeroic.BossModifier", sConfigMgr->GetOption("AutoBalance.BossInflectionMult", 1.0f, false), false); // `AutoBalance.BossInflectionMult` for backwards compatibility + + InflectionPointRaid10M = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid10M", InflectionPointRaid, false); + InflectionPointRaid10MCurveFloor = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid10M.CurveFloor", InflectionPointRaidCurveFloor, false); + InflectionPointRaid10MCurveCeiling = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid10M.CurveCeiling", InflectionPointRaidCurveCeiling, false); + InflectionPointRaid10MBoss = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid10M.BossModifier", InflectionPointRaidBoss, false); + + InflectionPointRaid10MHeroic = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid10MHeroic", InflectionPointRaidHeroic, false); + InflectionPointRaid10MHeroicCurveFloor = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid10MHeroic.CurveFloor", InflectionPointRaidHeroicCurveFloor, false); + InflectionPointRaid10MHeroicCurveCeiling = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid10MHeroic.CurveCeiling", InflectionPointRaidHeroicCurveCeiling, false); + InflectionPointRaid10MHeroicBoss = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid10MHeroic.BossModifier", InflectionPointRaidHeroicBoss, false); + + InflectionPointRaid15M = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid15M", InflectionPointRaid, false); + InflectionPointRaid15MCurveFloor = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid15M.CurveFloor", InflectionPointRaidCurveFloor, false); + InflectionPointRaid15MCurveCeiling = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid15M.CurveCeiling", InflectionPointRaidCurveCeiling, false); + InflectionPointRaid15MBoss = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid15M.BossModifier", InflectionPointRaidBoss, false); + + InflectionPointRaid20M = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid20M", InflectionPointRaid, false); + InflectionPointRaid20MCurveFloor = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid20M.CurveFloor", InflectionPointRaidCurveFloor, false); + InflectionPointRaid20MCurveCeiling = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid20M.CurveCeiling", InflectionPointRaidCurveCeiling, false); + InflectionPointRaid20MBoss = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid20M.BossModifier", InflectionPointRaidBoss, false); + + InflectionPointRaid25M = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid25M", InflectionPointRaid, false); + InflectionPointRaid25MCurveFloor = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid25M.CurveFloor", InflectionPointRaidCurveFloor, false); + InflectionPointRaid25MCurveCeiling = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid25M.CurveCeiling", InflectionPointRaidCurveCeiling, false); + InflectionPointRaid25MBoss = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid25M.BossModifier", InflectionPointRaidBoss, false); + + InflectionPointRaid25MHeroic = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid25MHeroic", InflectionPointRaidHeroic, false); + InflectionPointRaid25MHeroicCurveFloor = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid25MHeroic.CurveFloor", InflectionPointRaidHeroicCurveFloor, false); + InflectionPointRaid25MHeroicCurveCeiling = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid25MHeroic.CurveCeiling", InflectionPointRaidHeroicCurveCeiling, false); + InflectionPointRaid25MHeroicBoss = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid25MHeroic.BossModifier", InflectionPointRaidHeroicBoss, false); + + InflectionPointRaid40M = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid40M", InflectionPointRaid, false); + InflectionPointRaid40MCurveFloor = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid40M.CurveFloor", InflectionPointRaidCurveFloor, false); + InflectionPointRaid40MCurveCeiling = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid40M.CurveCeiling", InflectionPointRaidCurveCeiling, false); + InflectionPointRaid40MBoss = sConfigMgr->GetOption("AutoBalance.InflectionPointRaid40M.BossModifier", InflectionPointRaidBoss, false); + + // StatModifier* + // warn the console if deprecated values are detected + if (sConfigMgr->GetOption("AutoBalance.rate.global", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.rate.global` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + if (sConfigMgr->GetOption("AutoBalance.rate.health", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.rate.health` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + if (sConfigMgr->GetOption("AutoBalance.rate.mana", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.rate.mana` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + if (sConfigMgr->GetOption("AutoBalance.rate.armor", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.rate.armor` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + if (sConfigMgr->GetOption("AutoBalance.rate.damage", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.rate.damage` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + + // 5-player dungeons + StatModifier_Global = sConfigMgr->GetOption("AutoBalance.StatModifier.Global", sConfigMgr->GetOption("AutoBalance.rate.global", 1.0f, false), false); // `AutoBalance.rate.global` for backwards compatibility + StatModifier_Health = sConfigMgr->GetOption("AutoBalance.StatModifier.Health", sConfigMgr->GetOption("AutoBalance.rate.health", 1.0f, false), false); // `AutoBalance.rate.health` for backwards compatibility + StatModifier_Mana = sConfigMgr->GetOption("AutoBalance.StatModifier.Mana", sConfigMgr->GetOption("AutoBalance.rate.mana", 1.0f, false), false); // `AutoBalance.rate.mana` for backwards compatibility + StatModifier_Armor = sConfigMgr->GetOption("AutoBalance.StatModifier.Armor", sConfigMgr->GetOption("AutoBalance.rate.armor", 1.0f, false), false); // `AutoBalance.rate.armor` for backwards compatibility + StatModifier_Damage = sConfigMgr->GetOption("AutoBalance.StatModifier.Damage", sConfigMgr->GetOption("AutoBalance.rate.damage", 1.0f, false), false); // `AutoBalance.rate.damage` for backwards compatibility + StatModifier_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifier.CCDuration", -1.0f, false); + + StatModifier_Boss_Global = sConfigMgr->GetOption("AutoBalance.StatModifier.Boss.Global", sConfigMgr->GetOption("AutoBalance.rate.global", 1.0f, false), false); // `AutoBalance.rate.global` for backwards compatibility + StatModifier_Boss_Health = sConfigMgr->GetOption("AutoBalance.StatModifier.Boss.Health", sConfigMgr->GetOption("AutoBalance.rate.health", 1.0f, false), false); // `AutoBalance.rate.health` for backwards compatibility + StatModifier_Boss_Mana = sConfigMgr->GetOption("AutoBalance.StatModifier.Boss.Mana", sConfigMgr->GetOption("AutoBalance.rate.mana", 1.0f, false), false); // `AutoBalance.rate.mana` for backwards compatibility + StatModifier_Boss_Armor = sConfigMgr->GetOption("AutoBalance.StatModifier.Boss.Armor", sConfigMgr->GetOption("AutoBalance.rate.armor", 1.0f, false), false); // `AutoBalance.rate.armor` for backwards compatibility + StatModifier_Boss_Damage = sConfigMgr->GetOption("AutoBalance.StatModifier.Boss.Damage", sConfigMgr->GetOption("AutoBalance.rate.damage", 1.0f, false), false); // `AutoBalance.rate.damage` for backwards compatibility + StatModifier_Boss_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifier.Boss.CCDuration", -1.0f, false); + + // 5-player heroic dungeons + StatModifierHeroic_Global = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.Global", sConfigMgr->GetOption("AutoBalance.rate.global", 1.0f, false), false); // `AutoBalance.rate.global` for backwards compatibility + StatModifierHeroic_Health = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.Health", sConfigMgr->GetOption("AutoBalance.rate.health", 1.0f, false), false); // `AutoBalance.rate.health` for backwards compatibility + StatModifierHeroic_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.Mana", sConfigMgr->GetOption("AutoBalance.rate.mana", 1.0f, false), false); // `AutoBalance.rate.mana` for backwards compatibility + StatModifierHeroic_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.Armor", sConfigMgr->GetOption("AutoBalance.rate.armor", 1.0f, false), false); // `AutoBalance.rate.armor` for backwards compatibility + StatModifierHeroic_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.Damage", sConfigMgr->GetOption("AutoBalance.rate.damage", 1.0f, false), false); // `AutoBalance.rate.damage` for backwards compatibility + StatModifierHeroic_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.CCDuration", -1.0f, false); + + StatModifierHeroic_Boss_Global = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.Boss.Global", sConfigMgr->GetOption("AutoBalance.rate.global", 1.0f, false), false); // `AutoBalance.rate.global` for backwards compatibility + StatModifierHeroic_Boss_Health = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.Boss.Health", sConfigMgr->GetOption("AutoBalance.rate.health", 1.0f, false), false); // `AutoBalance.rate.health` for backwards compatibility + StatModifierHeroic_Boss_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.Boss.Mana", sConfigMgr->GetOption("AutoBalance.rate.mana", 1.0f, false), false); // `AutoBalance.rate.mana` for backwards compatibility + StatModifierHeroic_Boss_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.Boss.Armor", sConfigMgr->GetOption("AutoBalance.rate.armor", 1.0f, false), false); // `AutoBalance.rate.armor` for backwards compatibility + StatModifierHeroic_Boss_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.Boss.Damage", sConfigMgr->GetOption("AutoBalance.rate.damage", 1.0f, false), false); // `AutoBalance.rate.damage` for backwards compatibility + StatModifierHeroic_Boss_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierHeroic.Boss.CCDuration", -1.0f, false); + + // Default for all raids + StatModifierRaid_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.Global", sConfigMgr->GetOption("AutoBalance.rate.global", 1.0f, false), false); // `AutoBalance.rate.global` for backwards compatibility + StatModifierRaid_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.Health", sConfigMgr->GetOption("AutoBalance.rate.health", 1.0f, false), false); // `AutoBalance.rate.health` for backwards compatibility + StatModifierRaid_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.Mana", sConfigMgr->GetOption("AutoBalance.rate.mana", 1.0f, false), false); // `AutoBalance.rate.mana` for backwards compatibility + StatModifierRaid_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.Armor", sConfigMgr->GetOption("AutoBalance.rate.armor", 1.0f, false), false); // `AutoBalance.rate.armor` for backwards compatibility + StatModifierRaid_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.Damage", sConfigMgr->GetOption("AutoBalance.rate.damage", 1.0f, false), false); // `AutoBalance.rate.damage` for backwards compatibility + StatModifierRaid_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.CCDuration", -1.0f, false); + + StatModifierRaid_Boss_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.Boss.Global", sConfigMgr->GetOption("AutoBalance.rate.global", 1.0f, false), false); // `AutoBalance.rate.global` for backwards compatibility + StatModifierRaid_Boss_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.Boss.Health", sConfigMgr->GetOption("AutoBalance.rate.health", 1.0f, false), false); // `AutoBalance.rate.health` for backwards compatibility + StatModifierRaid_Boss_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.Boss.Mana", sConfigMgr->GetOption("AutoBalance.rate.mana", 1.0f, false), false); // `AutoBalance.rate.mana` for backwards compatibility + StatModifierRaid_Boss_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.Boss.Armor", sConfigMgr->GetOption("AutoBalance.rate.armor", 1.0f, false), false); // `AutoBalance.rate.armor` for backwards compatibility + StatModifierRaid_Boss_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.Boss.Damage", sConfigMgr->GetOption("AutoBalance.rate.damage", 1.0f, false), false); // `AutoBalance.rate.damage` for backwards compatibility + StatModifierRaid_Boss_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid.Boss.CCDuration", -1.0f, false); + + // Default for all heroic raids + StatModifierRaidHeroic_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.Global", sConfigMgr->GetOption("AutoBalance.rate.global", 1.0f, false), false); // `AutoBalance.rate.global` for backwards compatibility + StatModifierRaidHeroic_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.Health", sConfigMgr->GetOption("AutoBalance.rate.health", 1.0f, false), false); // `AutoBalance.rate.health` for backwards compatibility + StatModifierRaidHeroic_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.Mana", sConfigMgr->GetOption("AutoBalance.rate.mana", 1.0f, false), false); // `AutoBalance.rate.mana` for backwards compatibility + StatModifierRaidHeroic_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.Armor", sConfigMgr->GetOption("AutoBalance.rate.armor", 1.0f, false), false); // `AutoBalance.rate.armor` for backwards compatibility + StatModifierRaidHeroic_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.Damage", sConfigMgr->GetOption("AutoBalance.rate.damage", 1.0f, false), false); // `AutoBalance.rate.damage` for backwards compatibility + StatModifierRaidHeroic_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.CCDuration", -1.0f, false); + + StatModifierRaidHeroic_Boss_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.Boss.Global", sConfigMgr->GetOption("AutoBalance.rate.global", 1.0f, false), false); // `AutoBalance.rate.global` for backwards compatibility + StatModifierRaidHeroic_Boss_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.Boss.Health", sConfigMgr->GetOption("AutoBalance.rate.health", 1.0f, false), false); // `AutoBalance.rate.health` for backwards compatibility + StatModifierRaidHeroic_Boss_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.Boss.Mana", sConfigMgr->GetOption("AutoBalance.rate.mana", 1.0f, false), false); // `AutoBalance.rate.mana` for backwards compatibility + StatModifierRaidHeroic_Boss_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.Boss.Armor", sConfigMgr->GetOption("AutoBalance.rate.armor", 1.0f, false), false); // `AutoBalance.rate.armor` for backwards compatibility + StatModifierRaidHeroic_Boss_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.Boss.Damage", sConfigMgr->GetOption("AutoBalance.rate.damage", 1.0f, false), false); // `AutoBalance.rate.damage` for backwards compatibility + StatModifierRaidHeroic_Boss_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaidHeroic.Boss.CCDuration", -1.0f, false); + + // 10-player raids + StatModifierRaid10M_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.Global", StatModifierRaid_Global, false); + StatModifierRaid10M_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.Health", StatModifierRaid_Health, false); + StatModifierRaid10M_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.Mana", StatModifierRaid_Mana, false); + StatModifierRaid10M_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.Armor", StatModifierRaid_Armor, false); + StatModifierRaid10M_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.Damage", StatModifierRaid_Damage, false); + StatModifierRaid10M_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.CCDuration", StatModifierRaid_CCDuration, false); + + StatModifierRaid10M_Boss_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.Boss.Global", StatModifierRaid_Boss_Global, false); + StatModifierRaid10M_Boss_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.Boss.Health", StatModifierRaid_Boss_Health, false); + StatModifierRaid10M_Boss_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.Boss.Mana", StatModifierRaid_Boss_Mana, false); + StatModifierRaid10M_Boss_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.Boss.Armor", StatModifierRaid_Boss_Armor, false); + StatModifierRaid10M_Boss_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.Boss.Damage", StatModifierRaid_Boss_Damage, false); + StatModifierRaid10M_Boss_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10M.Boss.CCDuration", StatModifierRaid_Boss_CCDuration, false); + + // 10-player heroic raids + StatModifierRaid10MHeroic_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.Global", StatModifierRaidHeroic_Global, false); + StatModifierRaid10MHeroic_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.Health", StatModifierRaidHeroic_Health, false); + StatModifierRaid10MHeroic_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.Mana", StatModifierRaidHeroic_Mana, false); + StatModifierRaid10MHeroic_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.Armor", StatModifierRaidHeroic_Armor, false); + StatModifierRaid10MHeroic_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.Damage", StatModifierRaidHeroic_Damage, false); + StatModifierRaid10MHeroic_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.CCDuration", StatModifierRaidHeroic_CCDuration, false); + + StatModifierRaid10MHeroic_Boss_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.Boss.Global", StatModifierRaidHeroic_Boss_Global, false); + StatModifierRaid10MHeroic_Boss_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.Boss.Health", StatModifierRaidHeroic_Boss_Health, false); + StatModifierRaid10MHeroic_Boss_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.Boss.Mana", StatModifierRaidHeroic_Boss_Mana, false); + StatModifierRaid10MHeroic_Boss_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.Boss.Armor", StatModifierRaidHeroic_Boss_Armor, false); + StatModifierRaid10MHeroic_Boss_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.Boss.Damage", StatModifierRaidHeroic_Boss_Damage, false); + StatModifierRaid10MHeroic_Boss_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid10MHeroic.Boss.CCDuration", StatModifierRaidHeroic_Boss_CCDuration, false); + + // 15-player raids + StatModifierRaid15M_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.Global", StatModifierRaid_Global, false); + StatModifierRaid15M_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.Health", StatModifierRaid_Health, false); + StatModifierRaid15M_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.Mana", StatModifierRaid_Mana, false); + StatModifierRaid15M_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.Armor", StatModifierRaid_Armor, false); + StatModifierRaid15M_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.Damage", StatModifierRaid_Damage, false); + StatModifierRaid15M_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.CCDuration", StatModifierRaid_CCDuration, false); + + StatModifierRaid15M_Boss_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.Boss.Global", StatModifierRaid_Boss_Global, false); + StatModifierRaid15M_Boss_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.Boss.Health", StatModifierRaid_Boss_Health, false); + StatModifierRaid15M_Boss_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.Boss.Mana", StatModifierRaid_Boss_Mana, false); + StatModifierRaid15M_Boss_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.Boss.Armor", StatModifierRaid_Boss_Armor, false); + StatModifierRaid15M_Boss_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.Boss.Damage", StatModifierRaid_Boss_Damage, false); + StatModifierRaid15M_Boss_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid15M.Boss.CCDuration", StatModifierRaid_Boss_CCDuration, false); + + // 20-player raids + StatModifierRaid20M_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.Global", StatModifierRaid_Global, false); + StatModifierRaid20M_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.Health", StatModifierRaid_Health, false); + StatModifierRaid20M_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.Mana", StatModifierRaid_Mana, false); + StatModifierRaid20M_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.Armor", StatModifierRaid_Armor, false); + StatModifierRaid20M_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.Damage", StatModifierRaid_Damage, false); + StatModifierRaid20M_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.CCDuration", StatModifierRaid_CCDuration, false); + + StatModifierRaid20M_Boss_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.Boss.Global", StatModifierRaid_Boss_Global, false); + StatModifierRaid20M_Boss_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.Boss.Health", StatModifierRaid_Boss_Health, false); + StatModifierRaid20M_Boss_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.Boss.Mana", StatModifierRaid_Boss_Mana, false); + StatModifierRaid20M_Boss_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.Boss.Armor", StatModifierRaid_Boss_Armor, false); + StatModifierRaid20M_Boss_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.Boss.Damage", StatModifierRaid_Boss_Damage, false); + StatModifierRaid20M_Boss_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid20M.Boss.CCDuration", StatModifierRaid_Boss_CCDuration, false); + + // 25-player raids + StatModifierRaid25M_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.Global", StatModifierRaid_Global, false); + StatModifierRaid25M_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.Health", StatModifierRaid_Health, false); + StatModifierRaid25M_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.Mana", StatModifierRaid_Mana, false); + StatModifierRaid25M_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.Armor", StatModifierRaid_Armor, false); + StatModifierRaid25M_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.Damage", StatModifierRaid_Damage, false); + StatModifierRaid25M_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.CCDuration", StatModifierRaid_CCDuration, false); + + StatModifierRaid25M_Boss_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.Boss.Global", StatModifierRaid_Boss_Global, false); + StatModifierRaid25M_Boss_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.Boss.Health", StatModifierRaid_Boss_Health, false); + StatModifierRaid25M_Boss_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.Boss.Mana", StatModifierRaid_Boss_Mana, false); + StatModifierRaid25M_Boss_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.Boss.Armor", StatModifierRaid_Boss_Armor, false); + StatModifierRaid25M_Boss_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.Boss.Damage", StatModifierRaid_Boss_Damage, false); + StatModifierRaid25M_Boss_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25M.Boss.CCDuration", StatModifierRaid_Boss_CCDuration, false); + + // 25-player heroic raids + StatModifierRaid25MHeroic_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.Global", StatModifierRaidHeroic_Global, false); + StatModifierRaid25MHeroic_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.Health", StatModifierRaidHeroic_Health, false); + StatModifierRaid25MHeroic_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.Mana", StatModifierRaidHeroic_Mana, false); + StatModifierRaid25MHeroic_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.Armor", StatModifierRaidHeroic_Armor, false); + StatModifierRaid25MHeroic_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.Damage", StatModifierRaidHeroic_Damage, false); + StatModifierRaid25MHeroic_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.CCDuration", StatModifierRaidHeroic_CCDuration, false); + + StatModifierRaid25MHeroic_Boss_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.Boss.Global", StatModifierRaidHeroic_Boss_Global, false); + StatModifierRaid25MHeroic_Boss_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.Boss.Health", StatModifierRaidHeroic_Boss_Health, false); + StatModifierRaid25MHeroic_Boss_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.Boss.Mana", StatModifierRaidHeroic_Boss_Mana, false); + StatModifierRaid25MHeroic_Boss_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.Boss.Armor", StatModifierRaidHeroic_Boss_Armor, false); + StatModifierRaid25MHeroic_Boss_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.Boss.Damage", StatModifierRaidHeroic_Boss_Damage, false); + StatModifierRaid25MHeroic_Boss_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid25MHeroic.Boss.CCDuration", StatModifierRaidHeroic_Boss_CCDuration, false); + + // 40-player raids + StatModifierRaid40M_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.Global", StatModifierRaid_Global, false); + StatModifierRaid40M_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.Health", StatModifierRaid_Health, false); + StatModifierRaid40M_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.Mana", StatModifierRaid_Mana, false); + StatModifierRaid40M_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.Armor", StatModifierRaid_Armor, false); + StatModifierRaid40M_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.Damage", StatModifierRaid_Damage, false); + StatModifierRaid40M_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.CCDuration", StatModifierRaid_CCDuration, false); + + StatModifierRaid40M_Boss_Global = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.Boss.Global", StatModifierRaid_Boss_Global, false); + StatModifierRaid40M_Boss_Health = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.Boss.Health", StatModifierRaid_Boss_Health, false); + StatModifierRaid40M_Boss_Mana = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.Boss.Mana", StatModifierRaid_Boss_Mana, false); + StatModifierRaid40M_Boss_Armor = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.Boss.Armor", StatModifierRaid_Boss_Armor, false); + StatModifierRaid40M_Boss_Damage = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.Boss.Damage", StatModifierRaid_Boss_Damage, false); + StatModifierRaid40M_Boss_CCDuration = sConfigMgr->GetOption("AutoBalance.StatModifierRaid40M.Boss.CCDuration", StatModifierRaid_Boss_CCDuration, false); + + // Modifier Min/Max + MinHPModifier = sConfigMgr->GetOption("AutoBalance.MinHPModifier", 0.1f); + MinManaModifier = sConfigMgr->GetOption("AutoBalance.MinManaModifier", 0.01f); + MinDamageModifier = sConfigMgr->GetOption("AutoBalance.MinDamageModifier", 0.01f); + MinCCDurationModifier = sConfigMgr->GetOption("AutoBalance.MinCCDurationModifier", 0.25f); + MaxCCDurationModifier = sConfigMgr->GetOption("AutoBalance.MaxCCDurationModifier", 1.0f); + + // LevelScaling.* + LevelScaling = sConfigMgr->GetOption("AutoBalance.LevelScaling", true); + + std::string LevelScalingMethodString = sConfigMgr->GetOption("AutoBalance.LevelScaling.Method", "dynamic", false); + if (LevelScalingMethodString == "fixed") + { + LevelScalingMethod = AUTOBALANCE_SCALING_FIXED; + } + else if (LevelScalingMethodString == "dynamic") + { + LevelScalingMethod = AUTOBALANCE_SCALING_DYNAMIC; + } + else + { + LOG_ERROR("server.loading", "mod-autobalance: invalid value `{}` for `AutoBalance.LevelScaling.Method` defined in `AutoBalance.conf`. Defaulting to a value of `dynamic`.", LevelScalingMethodString); + LevelScalingMethod = AUTOBALANCE_SCALING_DYNAMIC; + } + + if (sConfigMgr->GetOption("AutoBalance.LevelHigherOffset", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.LevelHigherOffset` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + LevelScalingSkipHigherLevels = sConfigMgr->GetOption("AutoBalance.LevelScaling.SkipHigherLevels", sConfigMgr->GetOption("AutoBalance.LevelHigherOffset", 3, false), true); + if (sConfigMgr->GetOption("AutoBalance.LevelLowerOffset", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.LevelLowerOffset` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + LevelScalingSkipLowerLevels = sConfigMgr->GetOption("AutoBalance.LevelScaling.SkipLowerLevels", sConfigMgr->GetOption("AutoBalance.LevelLowerOffset", 5, false), true); + + LevelScalingDynamicLevelCeilingDungeons = sConfigMgr->GetOption("AutoBalance.LevelScaling.DynamicLevel.Ceiling.Dungeons", 1); + LevelScalingDynamicLevelFloorDungeons = sConfigMgr->GetOption("AutoBalance.LevelScaling.DynamicLevel.Floor.Dungeons", 5); + LevelScalingDynamicLevelCeilingHeroicDungeons = sConfigMgr->GetOption("AutoBalance.LevelScaling.DynamicLevel.Ceiling.HeroicDungeons", 2); + LevelScalingDynamicLevelFloorHeroicDungeons = sConfigMgr->GetOption("AutoBalance.LevelScaling.DynamicLevel.Floor.HeroicDungeons", 5); + LevelScalingDynamicLevelCeilingRaids = sConfigMgr->GetOption("AutoBalance.LevelScaling.DynamicLevel.Ceiling.Raids", 3); + LevelScalingDynamicLevelFloorRaids = sConfigMgr->GetOption("AutoBalance.LevelScaling.DynamicLevel.Floor.Raids", 5); + LevelScalingDynamicLevelCeilingHeroicRaids = sConfigMgr->GetOption("AutoBalance.LevelScaling.DynamicLevel.Ceiling.HeroicRaids", 3); + LevelScalingDynamicLevelFloorHeroicRaids = sConfigMgr->GetOption("AutoBalance.LevelScaling.DynamicLevel.Floor.HeroicRaids", 5); + + if (sConfigMgr->GetOption("AutoBalance.LevelEndGameBoost", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.LevelEndGameBoost` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + LevelScalingEndGameBoost = sConfigMgr->GetOption("AutoBalance.LevelScaling.EndGameBoost", sConfigMgr->GetOption("AutoBalance.LevelEndGameBoost", 1, false), true); + + // RewardScaling.* + // warn the console if deprecated values are detected + if (sConfigMgr->GetOption("AutoBalance.DungeonScaleDownXP", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.DungeonScaleDownXP` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + if (sConfigMgr->GetOption("AutoBalance.DungeonScaleDownMoney", false, false)) + LOG_WARN("server.loading", "mod-autobalance: deprecated value `AutoBalance.DungeonScaleDownMoney` defined in `AutoBalance.conf`. This variable will be removed in a future release. Please see `AutoBalance.conf.dist` for more details."); + + std::string RewardScalingMethodString = sConfigMgr->GetOption("AutoBalance.RewardScaling.Method", "dynamic", false); + if (RewardScalingMethodString == "fixed") + { + RewardScalingMethod = AUTOBALANCE_SCALING_FIXED; + } + else if (RewardScalingMethodString == "dynamic") + { + RewardScalingMethod = AUTOBALANCE_SCALING_DYNAMIC; + } + else + { + LOG_ERROR("server.loading", "mod-autobalance: invalid value `{}` for `AutoBalance.RewardScaling.Method` defined in `AutoBalance.conf`. Defaulting to a value of `dynamic`.", RewardScalingMethodString); + RewardScalingMethod = AUTOBALANCE_SCALING_DYNAMIC; + } + + RewardScalingXP = sConfigMgr->GetOption("AutoBalance.RewardScaling.XP", sConfigMgr->GetOption("AutoBalance.DungeonScaleDownXP", true, false)); + RewardScalingXPModifier = sConfigMgr->GetOption("AutoBalance.RewardScaling.XP.Modifier", 1.0f, false); + + RewardScalingMoney = sConfigMgr->GetOption("AutoBalance.RewardScaling.Money", sConfigMgr->GetOption("AutoBalance.DungeonScaleDownMoney", true, false)); + RewardScalingMoneyModifier = sConfigMgr->GetOption("AutoBalance.RewardScaling.Money.Modifier", 1.0f, false); + + // Announcement + Announcement = sConfigMgr->GetOption("AutoBalanceAnnounce.enable", true); + + } +}; + +class AutoBalance_PlayerScript : public PlayerScript +{ + public: + AutoBalance_PlayerScript() + : PlayerScript("AutoBalance_PlayerScript") + { + } + + void OnLogin(Player *Player) override + { + if (EnableGlobal && Announcement) { + ChatHandler(Player->GetSession()).SendSysMessage("This server is running the |cff4CFF00AutoBalance |rmodule."); + } + } + + virtual void OnLevelChanged(Player* player, uint8 oldlevel) override + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_PlayerScript::OnLevelChanged(): {} has leveled from {} to {}", player->GetName(), oldlevel, player->getLevel()); + if (!player || player->IsGameMaster()) + return; + + Map* map = player->GetMap(); + + if (!map || !map->IsDungeon()) + return; + + // first update the map's player stats + UpdateMapPlayerStats(map); + + // schedule all creatures for an update + lastConfigTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + } + + void OnGiveXP(Player* player, uint32& amount, Unit* victim, uint8 /*xpSource*/) override + { + Map* map = player->GetMap(); + + // If this isn't a dungeon, make no changes + if (!map->IsDungeon() || !victim) + return; + + AutoBalanceMapInfo *mapABInfo=map->CustomData.GetDefault("AutoBalanceMapInfo"); + + if (victim && RewardScalingXP && mapABInfo->enabled) + { + Map* map = player->GetMap(); + + AutoBalanceCreatureInfo *creatureABInfo=victim->CustomData.GetDefault("AutoBalanceCreatureInfo"); + + if (map->IsDungeon()) + { + if (RewardScalingMethod == AUTOBALANCE_SCALING_DYNAMIC) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_PlayerScript::OnGiveXP(): Distributing XP from '{}' to '{}' in dynamic mode - {}->{}", + victim->GetName(), player->GetName(), amount, uint32(amount * creatureABInfo->XPModifier)); + amount = uint32(amount * creatureABInfo->XPModifier); + } + else if (RewardScalingMethod == AUTOBALANCE_SCALING_FIXED) + { + // Ensure that the players always get the same XP, even when entering the dungeon alone + auto maxPlayerCount = ((InstanceMap*)sMapMgr->FindMap(map->GetId(), map->GetInstanceId()))->GetMaxPlayers(); + auto currentPlayerCount = map->GetPlayersCountExceptGMs(); + LOG_DEBUG("module.AutoBalance", "AutoBalance_PlayerScript::OnGiveXP(): Distributing XP from '{}' to '{}' in fixed mode - {}->{}", + victim->GetName(), player->GetName(), amount, uint32(amount * creatureABInfo->XPModifier * ((float)currentPlayerCount / maxPlayerCount))); + amount = uint32(amount * creatureABInfo->XPModifier * ((float)currentPlayerCount / maxPlayerCount)); + } + } + } + } + + + // void OnBeforeDropAddItem + void OnBeforeLootMoney(Player* player, Loot* loot) override + { + Map* map = player->GetMap(); + + // If this isn't a dungeon, make no changes + if (!map->IsDungeon()) + return; + + AutoBalanceMapInfo *mapABInfo=map->CustomData.GetDefault("AutoBalanceMapInfo"); + ObjectGuid sourceGuid = loot->sourceWorldObjectGUID; + + if (mapABInfo->enabled && RewardScalingMoney) + { + // if the loot source is a creature, honor the modifiers for that creature + if (sourceGuid.IsCreature()) + { + Creature* sourceCreature = ObjectAccessor::GetCreature(*player, sourceGuid); + AutoBalanceCreatureInfo *creatureABInfo=sourceCreature->CustomData.GetDefault("AutoBalanceCreatureInfo"); + + // Dynamic Mode + if (RewardScalingMethod == AUTOBALANCE_SCALING_DYNAMIC) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_PlayerScript::OnBeforeLootMoney(): Distributing money from '{}' in dynamic mode - {}->{}", + sourceCreature->GetName(), loot->gold, uint32(loot->gold * creatureABInfo->MoneyModifier)); + loot->gold = uint32(loot->gold * creatureABInfo->MoneyModifier); + } + // Fixed Mode + else if (RewardScalingMethod == AUTOBALANCE_SCALING_FIXED) + { + // Ensure that the players always get the same money, even when entering the dungeon alone + auto maxPlayerCount = ((InstanceMap*)sMapMgr->FindMap(map->GetId(), map->GetInstanceId()))->GetMaxPlayers(); + auto currentPlayerCount = map->GetPlayersCountExceptGMs(); + LOG_DEBUG("module.AutoBalance", "AutoBalance_PlayerScript::OnBeforeLootMoney(): Distributing money from '{}' in fixed mode - {}->{}", + sourceCreature->GetName(), loot->gold, uint32(loot->gold * creatureABInfo->MoneyModifier * ((float)currentPlayerCount / maxPlayerCount))); + loot->gold = uint32(loot->gold * creatureABInfo->MoneyModifier * ((float)currentPlayerCount / maxPlayerCount)); + } + } + // for all other loot sources, just distribute in Fixed mode as though the instance was full + else + { + auto maxPlayerCount = ((InstanceMap*)sMapMgr->FindMap(map->GetId(), map->GetInstanceId()))->GetMaxPlayers(); + auto currentPlayerCount = map->GetPlayersCountExceptGMs(); + LOG_DEBUG("module.AutoBalance", "AutoBalance_PlayerScript::OnBeforeLootMoney(): Distributing money from a non-creature in fixed mode - {}->{}", + loot->gold, uint32(loot->gold * ((float)currentPlayerCount / maxPlayerCount))); + loot->gold = uint32(loot->gold * ((float)currentPlayerCount / maxPlayerCount)); + } + } + } +}; + +class AutoBalance_UnitScript : public UnitScript +{ + public: + AutoBalance_UnitScript() + : UnitScript("AutoBalance_UnitScript", true) + { + } + + uint32 DealDamage(Unit* AttackerUnit, Unit *playerVictim, uint32 damage, DamageEffectType /*damagetype*/) override + { + return _Modifer_DealDamage(playerVictim, AttackerUnit, damage); + } + + void ModifyPeriodicDamageAurasTick(Unit* target, Unit* attacker, uint32& damage, SpellInfo const* /*spellInfo*/) override + { + damage = _Modifer_DealDamage(target, attacker, damage); + } + + void ModifySpellDamageTaken(Unit* target, Unit* attacker, int32& damage, SpellInfo const* /*spellInfo*/) override + { + damage = _Modifer_DealDamage(target, attacker, damage); + } + + void ModifyMeleeDamage(Unit* target, Unit* attacker, uint32& damage) override + { + damage = _Modifer_DealDamage(target, attacker, damage); + } + + void ModifyHealReceived(Unit* target, Unit* attacker, uint32& damage, SpellInfo const* /*spellInfo*/) override + { + damage = _Modifer_DealDamage(target, attacker, damage); + } + + void OnAuraApply(Unit* unit, Aura* aura) override { + // Only if this aura has a duration + if (aura->GetDuration() > 0 || aura->GetMaxDuration() > 0) + { + uint32 auraDuration = _Modifier_CCDuration(unit, aura->GetCaster(), aura); + + // only update if we decided to change it + if (auraDuration != (float)aura->GetDuration()) + { + aura->SetMaxDuration(auraDuration); + aura->SetDuration(auraDuration); + } + } + } + + uint32 _Modifer_DealDamage(Unit* target, Unit* attacker, uint32 damage) + { + // check that we're enabled globally, else return the original damage + if (!EnableGlobal) + return damage; + + // make sure we have an attacker, that its not a player, and that the attacker is in the world, else return the original damage + if (!attacker || attacker->GetTypeId() == TYPEID_PLAYER || !attacker->IsInWorld()) + return damage; + + // make sure we're in an instance, else return the original damage + if ( + !( + (target->GetMap()->IsDungeon() && attacker->GetMap()->IsDungeon()) || + (target->GetMap()->IsRaid() && attacker->GetMap()->IsRaid()) + ) + ) + return damage; + + // get the map's info to see if we're enabled + AutoBalanceMapInfo *targetMapInfo = target->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); + AutoBalanceMapInfo *attackerMapInfo = attacker->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); + + // if either the target or the attacker's maps are not enabled, return the original damage + if (!targetMapInfo->enabled || !attackerMapInfo->enabled) + return damage; + + // get the current creature's damage multiplier + float damageMultiplier = attacker->CustomData.GetDefault("AutoBalanceCreatureInfo")->DamageMultiplier; + + // if it's the default of 1.0, return the original damage + if (damageMultiplier == 1) + return damage; + + // if the attacker is under the control of the player, return the original damage + if ((attacker->IsHunterPet() || attacker->IsPet() || attacker->IsSummon()) && attacker->IsControlledByPlayer()) + return damage; + + // we are good to go, return the original damage times the multiplier + return damage * damageMultiplier; + } + + uint32 _Modifier_CCDuration(Unit* target, Unit* caster, Aura* aura) + { + // store the original duration of the aura + float originalDuration = (float)aura->GetDuration(); + + // check that we're enabled globally, else return the original duration + if (!EnableGlobal) + return originalDuration; + + // ensure that both the target and the caster are defined + if (!target || !caster) + return originalDuration; + + // if the aura wasn't cast just now, don't change it + if (aura->GetDuration() != aura->GetMaxDuration()) + return originalDuration; + + // if the target isn't a player or the caster is a player, return the original duration + if (!target->IsPlayer() || caster->IsPlayer()) + return originalDuration; + + // make sure we're in an instance, else return the original duration + if ( + !( + (target->GetMap()->IsDungeon() && caster->GetMap()->IsDungeon()) || + (target->GetMap()->IsRaid() && caster->GetMap()->IsRaid()) + ) + ) + return originalDuration; + + // get the current creature's CC duration multiplier + float ccDurationMultiplier = caster->CustomData.GetDefault("AutoBalanceCreatureInfo")->CCDurationMultiplier; + + // if it's the default of 1.0, return the original damage + if (ccDurationMultiplier == 1) + return originalDuration; + + // if the aura was cast by a pet or summon, return the original duration + if ((caster->IsHunterPet() || caster->IsPet() || caster->IsSummon()) && caster->IsControlledByPlayer()) + return originalDuration; + + // only if this aura is a CC + if ( + aura->HasEffectType(SPELL_AURA_MOD_CHARM) || + aura->HasEffectType(SPELL_AURA_MOD_CONFUSE) || + aura->HasEffectType(SPELL_AURA_MOD_DISARM) || + aura->HasEffectType(SPELL_AURA_MOD_FEAR) || + aura->HasEffectType(SPELL_AURA_MOD_PACIFY) || + aura->HasEffectType(SPELL_AURA_MOD_POSSESS) || + aura->HasEffectType(SPELL_AURA_MOD_SILENCE) || + aura->HasEffectType(SPELL_AURA_MOD_STUN) || + aura->HasEffectType(SPELL_AURA_MOD_SPEED_SLOW_ALL) + ) + { + return originalDuration * ccDurationMultiplier; + } + else + { + return originalDuration; + } + } +}; + + +class AutoBalance_AllMapScript : public AllMapScript +{ + public: + AutoBalance_AllMapScript() + : AllMapScript("AutoBalance_AllMapScript") + { + } + + void OnCreateMap(Map* map) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllMapScript::OnCreateMap(): {}", map->GetMapName()); + + if (!map->IsDungeon() && !map->IsRaid()) + return; + + // get the map's info + AutoBalanceMapInfo *mapABInfo=map->CustomData.GetDefault("AutoBalanceMapInfo"); + + // get the map's LFG stats + LFGDungeonEntry const* dungeon = GetLFGDungeon(map->GetId(), map->GetDifficulty()); + if (dungeon) { + mapABInfo->lfgMinLevel = dungeon->MinLevel; + mapABInfo->lfgMaxLevel = dungeon->MaxLevel; + mapABInfo->lfgTargetLevel = dungeon->TargetLevel; + } + + // load the map's settings + LoadMapSettings(map); + } + + void OnPlayerEnterAll(Map* map, Player* player) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllMapScript::OnPlayerEnterAll(): {}", map->GetMapName()); + if (!map->IsDungeon() && !map->IsRaid()) + return; + + if (player->IsGameMaster()) + return; + + // get the map's info + AutoBalanceMapInfo *mapABInfo=map->CustomData.GetDefault("AutoBalanceMapInfo"); + + // recalculate the zone's level stats + mapABInfo->highestCreatureLevel = 0; + mapABInfo->lowestCreatureLevel = 0; + mapABInfo->avgCreatureLevel = 0; + mapABInfo->activeCreatureCount = 0; + + // see which existing creatures are active + for (std::vector::iterator creatureIterator = mapABInfo->allMapCreatures.begin(); creatureIterator != mapABInfo->allMapCreatures.end(); ++creatureIterator) + { + AddCreatureToMapData(*creatureIterator, false, nullptr, true); + } + + // determine if the map should be enabled for scaling based on the current settings + mapABInfo->enabled = ShouldMapBeEnabled(map); + + // updates the player count, player levels for the map + UpdateMapPlayerStats(map); + + if (PlayerChangeNotify && EnableGlobal && mapABInfo->enabled) + { + if (map->GetEntry()->IsDungeon() && player) + { + Map::PlayerList const &playerList = map->GetPlayers(); + if (!playerList.IsEmpty()) + { + for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) + { + if (Player* playerHandle = playerIteration->GetSource()) + { + ChatHandler chatHandle = ChatHandler(playerHandle->GetSession()); + auto instanceMap = ((InstanceMap*)sMapMgr->FindMap(map->GetId(), map->GetInstanceId())); + + std::string instanceDifficulty; if (instanceMap->IsHeroic()) instanceDifficulty = "Heroic"; else instanceDifficulty = "Normal"; + + chatHandle.PSendSysMessage("|cffFF0000 [AutoBalance]|r|cffFF8000 %s enters %s (%u-player %s). Player count set to %u (Player Difficulty Offset = %u) |r", + player->GetName().c_str(), + map->GetMapName(), + instanceMap->GetMaxPlayers(), + instanceDifficulty, + mapABInfo->playerCount + PlayerCountDifficultyOffset, + PlayerCountDifficultyOffset + ); + } + } + } + } + } + } + + void OnPlayerLeaveAll(Map* map, Player* player) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllMapScript::OnPlayerLeaveAll(): {}", map->GetMapName()); + if (!EnableGlobal) + return; + + // get the map's info + AutoBalanceMapInfo *mapABInfo=map->CustomData.GetDefault("AutoBalanceMapInfo"); + + // recalculate the zone's level stats + mapABInfo->highestCreatureLevel = 0; + mapABInfo->lowestCreatureLevel = 0; + mapABInfo->avgCreatureLevel = 0; + mapABInfo->activeCreatureCount = 0; + + // see which existing creatures are active + for (std::vector::iterator creatureIterator = mapABInfo->allMapCreatures.begin(); creatureIterator != mapABInfo->allMapCreatures.end(); ++creatureIterator) + { + AddCreatureToMapData(*creatureIterator, false, player, true); + } + + // determine if the map should be enabled for scaling based on the current settings + mapABInfo->enabled = ShouldMapBeEnabled(map); + + bool areAnyPlayersInCombat = false; + + // updates the player count and levels for the map + if (map->GetEntry() && map->GetEntry()->IsDungeon()) + { + // determine if any players in the map are in combat + // if so, do not adjust the player count + Map::PlayerList const& mapPlayerList = map->GetPlayers(); + for (Map::PlayerList::const_iterator itr = mapPlayerList.begin(); itr != mapPlayerList.end(); ++itr) + { + if (Player* mapPlayer = itr->GetSource()) + { + if (mapPlayer->IsInCombat() && mapPlayer->GetMap() == map) + { + areAnyPlayersInCombat = true; + + // notify the player that they left the instance while combat was in progress + ChatHandler chatHandle = ChatHandler(player->GetSession()); + chatHandle.PSendSysMessage("|cffFF0000 [AutoBalance]|r|cffFF8000 You left the instance while combat was in progress. The instance player count is still %u.", mapABInfo->playerCount); + + break; + } + } + } + if (areAnyPlayersInCombat) + { + for (Map::PlayerList::const_iterator itr = mapPlayerList.begin(); itr != mapPlayerList.end(); ++itr) + { + if (Player* mapPlayer = itr->GetSource()) + { + // only for the players who are in the instance and did not leave + if (mapPlayer != player) + { + ChatHandler chatHandle = ChatHandler(mapPlayer->GetSession()); + chatHandle.PSendSysMessage("|cffFF0000 [AutoBalance]|r|cffFF8000 %s left the instance while combat was in progress. The instance player count is still %u.", player->GetName().c_str(), mapABInfo->playerCount); + } + } + } + } + else + { + mapABInfo->playerCount = map->GetPlayersCountExceptGMs() - 1; + } + } + + if (PlayerChangeNotify && !player->IsGameMaster() && !areAnyPlayersInCombat && EnableGlobal && mapABInfo->enabled) + { + if (map->GetEntry()->IsDungeon() && player) + { + Map::PlayerList const &playerList = map->GetPlayers(); + if (!playerList.IsEmpty()) + { + for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) + { + Player* mapPlayer = playerIteration->GetSource(); + if (mapPlayer && mapPlayer != player) + { + ChatHandler chatHandle = ChatHandler(mapPlayer->GetSession()); + chatHandle.PSendSysMessage("|cffFF0000 [AutoBalance]|r|cffFF8000 %s left the instance. Player count set to %u (Player Difficulty Offset = %u) |r", player->GetName().c_str(), mapABInfo->playerCount, PlayerCountDifficultyOffset); + } + } + } + } + } + } +}; + +class AutoBalance_AllCreatureScript : public AllCreatureScript +{ +public: + AutoBalance_AllCreatureScript() + : AllCreatureScript("AutoBalance_AllCreatureScript") + { + } + + void Creature_SelectLevel(const CreatureTemplate* /*creatureTemplate*/, Creature* creature) override + { + if (creature->GetMap()->IsDungeon()) + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::Creature_SelectLevel(): {} ({})", creature->GetName(), creature->GetLevel()); + + // add the creature to the map's tracking list + AddCreatureToMapData(creature); + + // do an initial modification of the creature + ModifyCreatureAttributes(creature); + + } + + void OnCreatureAddWorld(Creature* creature) override + { + if (creature->GetMap()->IsDungeon()) + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnCreatureAddWorld(): {} ({})", creature->GetName(), creature->GetLevel()); + } + + void OnCreatureRemoveWorld(Creature* creature) override + { + if (creature->GetMap()->IsDungeon()) + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnCreatureRemoveWorld(): {} ({})", creature->GetName(), creature->GetLevel()); + + // remove the creature from the map's tracking list, if present + RemoveCreatureFromMapData(creature); + } + + void OnAllCreatureUpdate(Creature* creature, uint32 /*diff*/) override + { + // If the config is out of date and the creature was reset, run modify against it + if (ResetCreatureIfNeeded(creature)) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::OnAllCreatureUpdate(): Creature {} ({}) is reset to its original stats.", creature->GetName(), creature->GetLevel()); + + // Update the map's level if it is out of date + UpdateMapLevelIfNeeded(creature->GetMap()); + + ModifyCreatureAttributes(creature); + } + } + + // Reset the passed creature to stock if the config has changed + bool ResetCreatureIfNeeded(Creature* creature) + { + // make sure we have a creature and that it's assigned to a map + if (!creature || !creature->GetMap()) + return false; + + + // if this isn't a dungeon or a battleground, make no changes + if (!(creature->GetMap()->IsDungeon() || creature->GetMap()->IsRaid())) + return false; + + // if this is a pet or summon controlled by the player, make no changes + if ((creature->IsHunterPet() || creature->IsPet() || creature->IsSummon()) && creature->IsControlledByPlayer()) + return false; + + // if this is a non-relevant creature, skip + if (creature->IsCritter() || creature->IsTotem() || creature->IsTrigger()) + return false; + + // get (or create) the creature and map's info + AutoBalanceCreatureInfo *creatureABInfo=creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); + AutoBalanceMapInfo *mapABInfo=creature->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); + + // if this creature is below 85% of the minimum level for the map, make no changes + if (creatureABInfo->UnmodifiedLevel < (float)mapABInfo->lfgMinLevel * .85f) + { + if (creatureABInfo->configTime == 0) + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ResetCreatureIfNeeded(): {} ({}) is below 85% of the LFG min level for the map, do not reset or modify.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + + creatureABInfo->configTime = lastConfigTime; + return false; + } + + // if this creature is above 115% of the maximum level for the map, make no changes + if (creatureABInfo->UnmodifiedLevel > (float)mapABInfo->lfgMaxLevel * 1.15f) + { + if (creatureABInfo->configTime == 0) + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ResetCreatureIfNeeded(): {} ({}) is above 115% of the LFG max level for the map, do not reset or modify.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + + creatureABInfo->configTime = lastConfigTime; + return false; + } + + // if creature is dead and configTime is 0, skip + if (creature->isDead() && creatureABInfo->configTime == 0) + { + return false; + } + // if the creature is dead but configTime is NOT 0, we set it to 0 so that it will be recalculated if revived + // also remember that this creature was once alive but is now dead + else if (creature->isDead()) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ResetCreatureIfNeeded(): {} ({}) is dead and configTime is not 0 - prime for reset if revived.", creature->GetName(), creature->GetLevel()); + creatureABInfo->configTime = 0; + creatureABInfo->wasAliveNowDead = true; + return false; + } + + // if the config is outdated, reset the creature + if (creatureABInfo->configTime != lastConfigTime) + { + // before updating the creature, we should update the map level if needed + UpdateMapLevelIfNeeded(creature->GetMap()); + + // retain some values + uint8 unmodifiedLevel = creatureABInfo->UnmodifiedLevel; + bool isActive = creatureABInfo->isActive; + bool wasAliveNowDead = creatureABInfo->wasAliveNowDead; + bool isInCreatureList = creatureABInfo->isInCreatureList; + + // reset AutoBalance modifiers + creature->CustomData.Erase("AutoBalanceCreatureInfo"); + AutoBalanceCreatureInfo *creatureABInfo=creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); + + // grab the creature's template and the original creature's stats + CreatureTemplate const* creatureTemplate = creature->GetCreatureTemplate(); + + // set the creature's level + creature->SetLevel(unmodifiedLevel); + creatureABInfo->UnmodifiedLevel = unmodifiedLevel; + + // get the creature's base stats + CreatureBaseStats const* origCreatureStats = sObjectMgr->GetCreatureBaseStats(unmodifiedLevel, creatureTemplate->unit_class); + + // health + float currentHealthPercent = (float)creature->GetHealth() / (float)creature->GetMaxHealth(); + creature->SetMaxHealth(origCreatureStats->GenerateHealth(creatureTemplate)); + creature->SetHealth((float)origCreatureStats->GenerateHealth(creatureTemplate) * currentHealthPercent); + + // mana + if (creature->getPowerType() == POWER_MANA && creature->GetPower(POWER_MANA) >= 0 && creature->GetMaxPower(POWER_MANA) > 0) + { + float currentManaPercent = creature->GetPower(POWER_MANA) / creature->GetMaxPower(POWER_MANA); + creature->SetMaxPower(POWER_MANA, origCreatureStats->GenerateMana(creatureTemplate)); + creature->SetPower(POWER_MANA, creature->GetMaxPower(POWER_MANA) * currentManaPercent); + } + + // armor + creature->SetArmor(origCreatureStats->GenerateArmor(creatureTemplate)); + + // restore the saved data + creatureABInfo->isActive = isActive; + creatureABInfo->wasAliveNowDead = wasAliveNowDead; + creatureABInfo->isInCreatureList = isInCreatureList; + + // damage and ccduration are handled using AutoBalanceCreatureInfo data only + + // return true to indicate that the creature was reset + return true; + } + + // creature was not reset, return false + return false; + + } + + void ModifyCreatureAttributes(Creature* creature) + { + // make sure we have a creature and that it's assigned to a map + if (!creature || !creature->GetMap()) + return; + + // if this isn't a dungeon or a battleground, make no changes + if (!(creature->GetMap()->IsDungeon() || creature->GetMap()->IsRaid())) + return; + + // if this is a pet or summon controlled by the player, make no changes + if (((creature->IsHunterPet() || creature->IsPet() || creature->IsSummon()) && creature->IsControlledByPlayer())) + return; + + // if this is a non-relevant creature, make no changes + if (creature->IsCritter() || creature->IsTotem() || creature->IsTrigger()) + return; + + // grab creature and map data + AutoBalanceCreatureInfo *creatureABInfo=creature->CustomData.GetDefault("AutoBalanceCreatureInfo"); + AutoBalanceMapInfo *mapABInfo=creature->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); + + // mark the creature as updated using the current settings if needed + if (creatureABInfo->configTime != lastConfigTime) + creatureABInfo->configTime = lastConfigTime; + + // check to make sure that the creature's map is enabled for scaling + if (!mapABInfo->enabled || !EnableGlobal) + return; + + // if this creature is below 85% of the minimum LFG level for the map, make no changes + if (creatureABInfo->UnmodifiedLevel < (float)mapABInfo->lfgMinLevel * .85f) + return; + + // if this creature is above 115% of the maximum LFG level for the map, make no changes + if (creatureABInfo->UnmodifiedLevel > (float)mapABInfo->lfgMaxLevel * 1.15f) + return; + + // if the creature was dead (but this function is being called because they are being revived), reset it and allow modifications + if (creatureABInfo->wasAliveNowDead) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes(): {} ({}) was dead but appears to be alive now, reset wasAliveNowDead flag.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + // if the creature was dead, reset it + creatureABInfo->wasAliveNowDead = false; + } + // if the creature is dead and wasn't marked as dead by this script, simply skip + else if (creature->isDead()) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes(): {} ({}) is dead, do not modify.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + return; + } + + CreatureTemplate const *creatureTemplate = creature->GetCreatureTemplate(); + + InstanceMap* instanceMap = ((InstanceMap*)sMapMgr->FindMap(creature->GetMapId(), creature->GetInstanceId())); + uint32 mapId = instanceMap->GetEntry()->MapID; + if (perDungeonScalingEnabled() && !isEnabledDungeon(mapId)) + { + return; + } + uint32 maxNumberOfPlayers = instanceMap->GetMaxPlayers(); + int forcedNumPlayers = GetForcedNumPlayers(creatureTemplate->Entry); + + if (forcedNumPlayers > 0) + maxNumberOfPlayers = forcedNumPlayers; // Force maxNumberOfPlayers to be changed to match the Configuration entries ForcedID2, ForcedID5, ForcedID10, ForcedID20, ForcedID25, ForcedID40 + else if (forcedNumPlayers == 0) + return; // forcedNumPlayers 0 means that the creature is contained in DisabledID -> no scaling + + uint32 curCount=mapABInfo->playerCount + PlayerCountDifficultyOffset; + if (perDungeonScalingEnabled()) + { + curCount = adjustCurCount(curCount, mapId); + } + creatureABInfo->instancePlayerCount = curCount; + + if (!creatureABInfo->instancePlayerCount) // no players in map, do not modify attributes + return; + + if (!sABScriptMgr->OnBeforeModifyAttributes(creature, creatureABInfo->instancePlayerCount)) + return; + + // only scale levels if level scaling is enabled and the instance's average creature level is not within the skip range + if (LevelScaling && + ((mapABInfo->avgCreatureLevel > mapABInfo->highestPlayerLevel + mapABInfo->levelScalingSkipHigherLevels || mapABInfo->levelScalingSkipHigherLevels == 0) || + (mapABInfo->avgCreatureLevel < mapABInfo->highestPlayerLevel - mapABInfo->levelScalingSkipLowerLevels || mapABInfo->levelScalingSkipLowerLevels == 0)) + ) + { + uint8 selectedLevel; + + // if we're using dynamic scaling, calculate the creature's level based relative to the highest player level in the map + if (LevelScalingMethod == AUTOBALANCE_SCALING_DYNAMIC) + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) dynamic scaling floor: {}, ceiling: {}.", creature->GetName(), creatureABInfo->UnmodifiedLevel, mapABInfo->levelScalingDynamicFloor, mapABInfo->levelScalingDynamicCeiling); + + // calculate the creature's new level + selectedLevel = (mapABInfo->highestPlayerLevel + mapABInfo->levelScalingDynamicCeiling) - (mapABInfo->highestCreatureLevel - creatureABInfo->UnmodifiedLevel); + + // check to be sure that the creature's new level is at least the dynamic scaling floor + if (selectedLevel < (mapABInfo->highestPlayerLevel - mapABInfo->levelScalingDynamicFloor)) + { + selectedLevel = mapABInfo->highestPlayerLevel - mapABInfo->levelScalingDynamicFloor; + } + + // check to be sure that the creature's new level is no higher than the dynamic scaling ceiling + if (selectedLevel > (mapABInfo->highestPlayerLevel + mapABInfo->levelScalingDynamicCeiling)) + { + selectedLevel = mapABInfo->highestPlayerLevel + mapABInfo->levelScalingDynamicCeiling; + } + + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) scaled to {} via dynamic scaling.", creature->GetName(), creatureABInfo->UnmodifiedLevel, selectedLevel); + } + // otherwise we're using "fixed" scaling and should use the highest player level in the map + else + { + selectedLevel = mapABInfo->highestPlayerLevel; + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) scaled to {} via fixed scaling.", creature->GetName(), creatureABInfo->UnmodifiedLevel, selectedLevel); + } + + creatureABInfo->selectedLevel = selectedLevel; + creature->SetLevel(creatureABInfo->selectedLevel); + } + else + { + LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) not level scaled due to level scaling being disabled or the instance's average creature level being outside the skip range.", creature->GetName(), creatureABInfo->UnmodifiedLevel); + creatureABInfo->selectedLevel = creatureABInfo->UnmodifiedLevel; + } + + creatureABInfo->entry = creature->GetEntry(); + + CreatureBaseStats const* origCreatureStats = sObjectMgr->GetCreatureBaseStats(creatureABInfo->UnmodifiedLevel, creatureTemplate->unit_class); + CreatureBaseStats const* creatureStats = sObjectMgr->GetCreatureBaseStats(creatureABInfo->selectedLevel, creatureTemplate->unit_class); + + uint32 baseMana = origCreatureStats->GenerateMana(creatureTemplate); + uint32 scaledHealth = 0; + uint32 scaledMana = 0; + + // Note: InflectionPoint handle the number of players required to get 50% health. + // you'd adjust this to raise or lower the hp modifier for per additional player in a non-whole group. + // + // diff modify the rate of percentage increase between + // number of players. Generally the closer to the value of 1 you have this + // the less gradual the rate will be. For example in a 5 man it would take 3 + // total players to face a mob at full health. + // + // The +1 and /2 values raise the TanH function to a positive range and make + // sure the modifier never goes above the value or 1.0 or below 0. + // + // curveFloor and curveCeiling squishes the curve by adjusting the curve start and end points. + // This allows for better control over high and low player count scaling. + + float defaultMultiplier; + float curveFloor; + float curveCeiling; + + // + // Inflection Point + // + float inflectionValue = (float)maxNumberOfPlayers; + + if (instanceMap->IsHeroic()) + { + switch (maxNumberOfPlayers) + { + case 1: + case 2: + case 3: + case 4: + case 5: + inflectionValue *= InflectionPointHeroic; + curveFloor = InflectionPointHeroicCurveFloor; + curveCeiling = InflectionPointHeroicCurveCeiling; + break; + case 10: + inflectionValue *= InflectionPointRaid10MHeroic; + curveFloor = InflectionPointRaid10MHeroicCurveFloor; + curveCeiling = InflectionPointRaid10MHeroicCurveCeiling; + break; + case 25: + inflectionValue *= InflectionPointRaid25MHeroic; + curveFloor = InflectionPointRaid25MHeroicCurveFloor; + curveCeiling = InflectionPointRaid25MHeroicCurveCeiling; + break; + default: + inflectionValue *= InflectionPointRaidHeroic; + curveFloor = InflectionPointRaidHeroicCurveFloor; + curveCeiling = InflectionPointRaidHeroicCurveCeiling; + } + } + else + { + switch (maxNumberOfPlayers) + { + case 1: + case 2: + case 3: + case 4: + case 5: + inflectionValue *= InflectionPoint; + curveFloor = InflectionPointCurveFloor; + curveCeiling = InflectionPointCurveCeiling; + break; + case 10: + inflectionValue *= InflectionPointRaid10M; + curveFloor = InflectionPointRaid10MCurveFloor; + curveCeiling = InflectionPointRaid10MCurveCeiling; + break; + case 15: + inflectionValue *= InflectionPointRaid15M; + curveFloor = InflectionPointRaid15MCurveFloor; + curveCeiling = InflectionPointRaid15MCurveCeiling; + break; + case 20: + inflectionValue *= InflectionPointRaid20M; + curveFloor = InflectionPointRaid20MCurveFloor; + curveCeiling = InflectionPointRaid20MCurveCeiling; + break; + case 25: + inflectionValue *= InflectionPointRaid25M; + curveFloor = InflectionPointRaid25MCurveFloor; + curveCeiling = InflectionPointRaid25MCurveCeiling; + break; + case 40: + inflectionValue *= InflectionPointRaid40M; + curveFloor = InflectionPointRaid40MCurveFloor; + curveCeiling = InflectionPointRaid40MCurveCeiling; + break; + default: + inflectionValue *= InflectionPointRaid; + curveFloor = InflectionPointRaidCurveFloor; + curveCeiling = InflectionPointRaidCurveCeiling; + } + } + + // Per map ID overrides alter the above settings, if set + if (hasDungeonOverride(mapId)) + { + AutoBalanceInflectionPointSettings* myInflectionPointOverrides = &dungeonOverrides[mapId]; + + // Alter the inflectionValue according to the override, if set + if (myInflectionPointOverrides->value != -1) + { + inflectionValue = (float)maxNumberOfPlayers; // Starting over + inflectionValue *= myInflectionPointOverrides->value; + } + + if (myInflectionPointOverrides->curveFloor != -1) { curveFloor = myInflectionPointOverrides->curveFloor; } + if (myInflectionPointOverrides->curveCeiling != -1) { curveCeiling = myInflectionPointOverrides->curveCeiling; } + } + + // + // Boss Inflection Point + // + if (creature->IsDungeonBoss()) { + + float bossInflectionPointMultiplier; + + // Determine the correct boss inflection multiplier + if (instanceMap->IsHeroic()) + { + switch (maxNumberOfPlayers) + { + case 1: + case 2: + case 3: + case 4: + case 5: + bossInflectionPointMultiplier = InflectionPointHeroicBoss; + break; + case 10: + bossInflectionPointMultiplier = InflectionPointRaid10MHeroicBoss; + break; + case 25: + bossInflectionPointMultiplier = InflectionPointRaid25MHeroicBoss; + break; + default: + bossInflectionPointMultiplier = InflectionPointRaidHeroicBoss; + } + } + else + { + switch (maxNumberOfPlayers) + { + case 1: + case 2: + case 3: + case 4: + case 5: + bossInflectionPointMultiplier = InflectionPointBoss; + break; + case 10: + bossInflectionPointMultiplier = InflectionPointRaid10MBoss; + break; + case 15: + bossInflectionPointMultiplier = InflectionPointRaid15MBoss; + break; + case 20: + bossInflectionPointMultiplier = InflectionPointRaid20MBoss; + break; + case 25: + bossInflectionPointMultiplier = InflectionPointRaid25MBoss; + break; + case 40: + bossInflectionPointMultiplier = InflectionPointRaid40MBoss; + break; + default: + bossInflectionPointMultiplier = InflectionPointRaidBoss; + } + } + + // Per map ID overrides alter the above settings, if set + if (hasBossOverride(mapId)) + { + AutoBalanceInflectionPointSettings* myBossOverrides = &bossOverrides[mapId]; + + // If set, alter the inflectionValue according to the override + if (myBossOverrides->value != -1) + { + inflectionValue *= myBossOverrides->value; + } + // Otherwise, calculate using the value determined by instance type + else + { + inflectionValue *= bossInflectionPointMultiplier; + } + } + // No override, use the value determined by the instance type + else + { + inflectionValue *= bossInflectionPointMultiplier; + } + } + + // + // Stat Modifiers + // + + // Calculate stat modifiers + float statMod_global, statMod_health, statMod_mana, statMod_armor, statMod_damage, statMod_ccDuration; + float statMod_boss_global, statMod_boss_health, statMod_boss_mana, statMod_boss_armor, statMod_boss_damage, statMod_boss_ccDuration; + + // Apply the per-instance-type modifiers first + if (instanceMap->IsHeroic()) + { + switch (maxNumberOfPlayers) + { + case 1: + case 2: + case 3: + case 4: + case 5: + statMod_global = StatModifierHeroic_Global; + statMod_health = StatModifierHeroic_Health; + statMod_mana = StatModifierHeroic_Mana; + statMod_armor = StatModifierHeroic_Armor; + statMod_damage = StatModifierHeroic_Damage; + statMod_ccDuration = StatModifierHeroic_CCDuration; + + statMod_boss_global = StatModifierHeroic_Boss_Global; + statMod_boss_health = StatModifierHeroic_Boss_Health; + statMod_boss_mana = StatModifierHeroic_Boss_Mana; + statMod_boss_armor = StatModifierHeroic_Boss_Armor; + statMod_boss_damage = StatModifierHeroic_Boss_Damage; + statMod_boss_ccDuration = StatModifierHeroic_Boss_CCDuration; + break; + case 10: + statMod_global = StatModifierRaid10MHeroic_Global; + statMod_health = StatModifierRaid10MHeroic_Health; + statMod_mana = StatModifierRaid10MHeroic_Mana; + statMod_armor = StatModifierRaid10MHeroic_Armor; + statMod_damage = StatModifierRaid10MHeroic_Damage; + statMod_ccDuration = StatModifierRaid10MHeroic_CCDuration; + + statMod_boss_global = StatModifierRaid10MHeroic_Boss_Global; + statMod_boss_health = StatModifierRaid10MHeroic_Boss_Health; + statMod_boss_mana = StatModifierRaid10MHeroic_Boss_Mana; + statMod_boss_armor = StatModifierRaid10MHeroic_Boss_Armor; + statMod_boss_damage = StatModifierRaid10MHeroic_Boss_Damage; + statMod_boss_ccDuration = StatModifierRaid10MHeroic_Boss_CCDuration; + break; + case 25: + statMod_global = StatModifierRaid25MHeroic_Global; + statMod_health = StatModifierRaid25MHeroic_Health; + statMod_mana = StatModifierRaid25MHeroic_Mana; + statMod_armor = StatModifierRaid25MHeroic_Armor; + statMod_damage = StatModifierRaid25MHeroic_Damage; + statMod_ccDuration = StatModifierRaid25MHeroic_CCDuration; + + statMod_boss_global = StatModifierRaid25MHeroic_Boss_Global; + statMod_boss_health = StatModifierRaid25MHeroic_Boss_Health; + statMod_boss_mana = StatModifierRaid25MHeroic_Boss_Mana; + statMod_boss_armor = StatModifierRaid25MHeroic_Boss_Armor; + statMod_boss_damage = StatModifierRaid25MHeroic_Boss_Damage; + statMod_boss_ccDuration = StatModifierRaid25MHeroic_Boss_CCDuration; + break; + default: + statMod_global = StatModifierRaidHeroic_Global; + statMod_health = StatModifierRaidHeroic_Health; + statMod_mana = StatModifierRaidHeroic_Mana; + statMod_armor = StatModifierRaidHeroic_Armor; + statMod_damage = StatModifierRaidHeroic_Damage; + statMod_ccDuration = StatModifierRaidHeroic_CCDuration; + + statMod_boss_global = StatModifierRaidHeroic_Global; + statMod_boss_health = StatModifierRaidHeroic_Health; + statMod_boss_mana = StatModifierRaidHeroic_Mana; + statMod_boss_armor = StatModifierRaidHeroic_Armor; + statMod_boss_damage = StatModifierRaidHeroic_Damage; + statMod_boss_ccDuration = StatModifierRaidHeroic_Boss_CCDuration; + } + } + else + { + switch (maxNumberOfPlayers) + { + case 1: + case 2: + case 3: + case 4: + case 5: + statMod_global = StatModifier_Global; + statMod_health = StatModifier_Health; + statMod_mana = StatModifier_Mana; + statMod_armor = StatModifier_Armor; + statMod_damage = StatModifier_Damage; + statMod_ccDuration = StatModifier_CCDuration; + + statMod_boss_global = StatModifier_Boss_Global; + statMod_boss_health = StatModifier_Boss_Health; + statMod_boss_mana = StatModifier_Boss_Mana; + statMod_boss_armor = StatModifier_Boss_Armor; + statMod_boss_damage = StatModifier_Boss_Damage; + statMod_boss_ccDuration = StatModifier_Boss_CCDuration; + break; + case 10: + statMod_global = StatModifierRaid10M_Global; + statMod_health = StatModifierRaid10M_Health; + statMod_mana = StatModifierRaid10M_Mana; + statMod_armor = StatModifierRaid10M_Armor; + statMod_damage = StatModifierRaid10M_Damage; + statMod_ccDuration = StatModifierRaid10M_CCDuration; + + statMod_boss_global = StatModifierRaid10M_Boss_Global; + statMod_boss_health = StatModifierRaid10M_Boss_Health; + statMod_boss_mana = StatModifierRaid10M_Boss_Mana; + statMod_boss_armor = StatModifierRaid10M_Boss_Armor; + statMod_boss_damage = StatModifierRaid10M_Boss_Damage; + statMod_boss_ccDuration = StatModifierRaid10M_Boss_CCDuration; + break; + case 15: + statMod_global = StatModifierRaid15M_Global; + statMod_health = StatModifierRaid15M_Health; + statMod_mana = StatModifierRaid15M_Mana; + statMod_armor = StatModifierRaid15M_Armor; + statMod_damage = StatModifierRaid15M_Damage; + statMod_ccDuration = StatModifierRaid15M_CCDuration; + + statMod_boss_global = StatModifierRaid15M_Boss_Global; + statMod_boss_health = StatModifierRaid15M_Boss_Health; + statMod_boss_mana = StatModifierRaid15M_Boss_Mana; + statMod_boss_armor = StatModifierRaid15M_Boss_Armor; + statMod_boss_damage = StatModifierRaid15M_Boss_Damage; + statMod_boss_ccDuration = StatModifierRaid15M_Boss_CCDuration; + break; + case 20: + statMod_global = StatModifierRaid20M_Global; + statMod_health = StatModifierRaid20M_Health; + statMod_mana = StatModifierRaid20M_Mana; + statMod_armor = StatModifierRaid20M_Armor; + statMod_damage = StatModifierRaid20M_Damage; + statMod_ccDuration = StatModifierRaid20M_CCDuration; + + statMod_boss_global = StatModifierRaid20M_Boss_Global; + statMod_boss_health = StatModifierRaid20M_Boss_Health; + statMod_boss_mana = StatModifierRaid20M_Boss_Mana; + statMod_boss_armor = StatModifierRaid20M_Boss_Armor; + statMod_boss_damage = StatModifierRaid20M_Boss_Damage; + statMod_boss_ccDuration = StatModifierRaid20M_Boss_CCDuration; + break; + case 25: + statMod_global = StatModifierRaid25M_Global; + statMod_health = StatModifierRaid25M_Health; + statMod_mana = StatModifierRaid25M_Mana; + statMod_armor = StatModifierRaid25M_Armor; + statMod_damage = StatModifierRaid25M_Damage; + statMod_ccDuration = StatModifierRaid25M_CCDuration; + + statMod_boss_global = StatModifierRaid25M_Boss_Global; + statMod_boss_health = StatModifierRaid25M_Boss_Health; + statMod_boss_mana = StatModifierRaid25M_Boss_Mana; + statMod_boss_armor = StatModifierRaid25M_Boss_Armor; + statMod_boss_damage = StatModifierRaid25M_Boss_Damage; + statMod_boss_ccDuration = StatModifierRaid25M_Boss_CCDuration; + break; + case 40: + statMod_global = StatModifierRaid40M_Global; + statMod_health = StatModifierRaid40M_Health; + statMod_mana = StatModifierRaid40M_Mana; + statMod_armor = StatModifierRaid40M_Armor; + statMod_damage = StatModifierRaid40M_Damage; + statMod_ccDuration = StatModifierRaid40M_CCDuration; + + statMod_boss_global = StatModifierRaid40M_Boss_Global; + statMod_boss_health = StatModifierRaid40M_Boss_Health; + statMod_boss_mana = StatModifierRaid40M_Boss_Mana; + statMod_boss_armor = StatModifierRaid40M_Boss_Armor; + statMod_boss_damage = StatModifierRaid40M_Boss_Damage; + statMod_boss_ccDuration = StatModifierRaid40M_Boss_CCDuration; + break; + default: + statMod_global = StatModifierRaid_Global; + statMod_health = StatModifierRaid_Health; + statMod_mana = StatModifierRaid_Mana; + statMod_armor = StatModifierRaid_Armor; + statMod_damage = StatModifierRaid_Damage; + statMod_ccDuration = StatModifierRaid_CCDuration; + + statMod_boss_global = StatModifierRaid_Boss_Global; + statMod_boss_health = StatModifierRaid_Boss_Health; + statMod_boss_mana = StatModifierRaid_Boss_Mana; + statMod_boss_armor = StatModifierRaid_Boss_Armor; + statMod_boss_damage = StatModifierRaid_Boss_Damage; + statMod_boss_ccDuration = StatModifierRaid_Boss_CCDuration; + } + } + + // Boss modifiers + if (creature->IsDungeonBoss()) + { + // Start with the settings determined above + // AutoBalance.StatModifier*.Boss. + if (creature->IsDungeonBoss()) + { + statMod_global = statMod_boss_global; + statMod_health = statMod_boss_health; + statMod_mana = statMod_boss_mana; + statMod_armor = statMod_boss_armor; + statMod_damage = statMod_boss_damage; + statMod_ccDuration = statMod_boss_ccDuration; + } + + // Per-instance boss overrides + // AutoBalance.StatModifier.Boss.PerInstance + if (creature->IsDungeonBoss() && hasStatModifierBossOverride(mapId)) + { + AutoBalanceStatModifiers* myStatModifierBossOverrides = &statModifierBossOverrides[mapId]; + + if (myStatModifierBossOverrides->global != -1) { statMod_global = myStatModifierBossOverrides->global; } + if (myStatModifierBossOverrides->health != -1) { statMod_health = myStatModifierBossOverrides->health; } + if (myStatModifierBossOverrides->mana != -1) { statMod_mana = myStatModifierBossOverrides->mana; } + if (myStatModifierBossOverrides->armor != -1) { statMod_armor = myStatModifierBossOverrides->armor; } + if (myStatModifierBossOverrides->damage != -1) { statMod_damage = myStatModifierBossOverrides->damage; } + if (myStatModifierBossOverrides->ccduration != -1) { statMod_ccDuration = myStatModifierBossOverrides->ccduration; } + } + } + // Non-boss modifiers + else + { + // Per-instance non-boss overrides + // AutoBalance.StatModifier.PerInstance + if (hasStatModifierOverride(mapId)) + { + AutoBalanceStatModifiers* myStatModifierOverrides = &statModifierOverrides[mapId]; + + if (myStatModifierOverrides->global != -1) { statMod_global = myStatModifierOverrides->global; } + if (myStatModifierOverrides->health != -1) { statMod_health = myStatModifierOverrides->health; } + if (myStatModifierOverrides->mana != -1) { statMod_mana = myStatModifierOverrides->mana; } + if (myStatModifierOverrides->armor != -1) { statMod_armor = myStatModifierOverrides->armor; } + if (myStatModifierOverrides->damage != -1) { statMod_damage = myStatModifierOverrides->damage; } + if (myStatModifierOverrides->ccduration != -1) { statMod_ccDuration = myStatModifierOverrides->ccduration; } + } + } + + // Per-creature modifiers applied last + // AutoBalance.StatModifier.PerCreature + if (hasStatModifierCreatureOverride(creatureTemplate->Entry)) + { + AutoBalanceStatModifiers* myCreatureOverrides = &statModifierCreatureOverrides[creatureTemplate->Entry]; + + if (myCreatureOverrides->global != -1) { statMod_global = myCreatureOverrides->global; } + if (myCreatureOverrides->health != -1) { statMod_health = myCreatureOverrides->health; } + if (myCreatureOverrides->mana != -1) { statMod_mana = myCreatureOverrides->mana; } + if (myCreatureOverrides->armor != -1) { statMod_armor = myCreatureOverrides->armor; } + if (myCreatureOverrides->damage != -1) { statMod_damage = myCreatureOverrides->damage; } + if (myCreatureOverrides->ccduration != -1) { statMod_ccDuration = myCreatureOverrides->ccduration; } + } + + // #maththings + float diff = ((float)maxNumberOfPlayers/5)*1.5f; + + // For math reasons that I do not understand, curveCeiling needs to be adjusted to bring the actual multiplier + // closer to the curveCeiling setting. Create an adjustment based on how much the ceiling should be changed at + // the max players multiplier. + float curveCeilingAdjustment = curveCeiling / (((tanh(((float)maxNumberOfPlayers - inflectionValue) / diff) + 1.0f) / 2.0f) * (curveCeiling - curveFloor) + curveFloor); + + // Adjust the multiplier based on the configured floor and ceiling values, plus the ceiling adjustment we just calculated + defaultMultiplier = ((tanh(((float)creatureABInfo->instancePlayerCount - inflectionValue) / diff) + 1.0f) / 2.0f) * (curveCeiling * curveCeilingAdjustment - curveFloor) + curveFloor; + + if (!sABScriptMgr->OnAfterDefaultMultiplier(creature, defaultMultiplier)) + return; + + // + // Health Scaling + // + + float healthMultiplier = defaultMultiplier * statMod_global * statMod_health; + + if (healthMultiplier <= MinHPModifier) + healthMultiplier = MinHPModifier; + + float hpStatsRate = 1.0f; + float originalHealth = origCreatureStats->GenerateHealth(creatureTemplate); + + float newBaseHealth; + + // The database holds multiple values for base health, one for each expansion + // This code will smooth transition between the different expansions based on the highest player level in the instance + // Only do this if level scaling is enabled + + if (LevelScaling) + { + float vanillaHealth = creatureStats->BaseHealth[0]; + float bcHealth = creatureStats->BaseHealth[1]; + float wotlkHealth = creatureStats->BaseHealth[2]; + + // vanilla health + if (mapABInfo->highestPlayerLevel <= 60) + { + newBaseHealth = vanillaHealth; + } + // transition from vanilla to BC health + else if (mapABInfo->highestPlayerLevel < 63) + { + float vanillaMultiplier = (63 - mapABInfo->highestPlayerLevel) / 3.0f; + float bcMultiplier = 1.0f - vanillaMultiplier; + + newBaseHealth = (vanillaHealth * vanillaMultiplier) + (bcHealth * bcMultiplier); + } + // BC health + else if (mapABInfo->highestPlayerLevel <= 70) + { + newBaseHealth = bcHealth; + } + // transition from BC to WotLK health + else if (mapABInfo->highestPlayerLevel < 73) + { + float bcMultiplier = (73 - mapABInfo->highestPlayerLevel) / 3.0f; + float wotlkMultiplier = 1.0f - bcMultiplier; + + newBaseHealth = (bcHealth * bcMultiplier) + (wotlkHealth * wotlkMultiplier); + } + // WotLK health + else + { + newBaseHealth = wotlkHealth; + + // special increase for end-game content + if (LevelScalingEndGameBoost) + if (mapABInfo->highestPlayerLevel >= 75 && creatureABInfo->UnmodifiedLevel < 75) + { + newBaseHealth *= (float)(mapABInfo->highestPlayerLevel-70) * 0.3f; + } + } + + float newHealth = newBaseHealth * creatureTemplate->ModHealth; + hpStatsRate = newHealth / originalHealth; + + healthMultiplier *= hpStatsRate; + } + + creatureABInfo->HealthMultiplier = healthMultiplier; + scaledHealth = round(originalHealth * creatureABInfo->HealthMultiplier); + + // + // Mana Scaling + // + float manaStatsRate = 1.0f; + float newMana = creatureStats->GenerateMana(creatureTemplate); + manaStatsRate = newMana/float(baseMana); + + // check to be sure that manaStatsRate is not nan + if (manaStatsRate != manaStatsRate) + { + creatureABInfo->ManaMultiplier = 0.0f; + } + else + { + creatureABInfo->ManaMultiplier = defaultMultiplier * manaStatsRate * statMod_global * statMod_mana; + + if (creatureABInfo->ManaMultiplier <= MinManaModifier) + { + creatureABInfo->ManaMultiplier = MinManaModifier; + } + } + + scaledMana = round(baseMana * creatureABInfo->ManaMultiplier); + + // + // Armor Scaling + // + creatureABInfo->ArmorMultiplier = defaultMultiplier * statMod_global * statMod_armor; + uint32 newBaseArmor = round(creatureABInfo->ArmorMultiplier * (LevelScaling ? creatureStats->GenerateArmor(creatureTemplate) : origCreatureStats->GenerateArmor(creatureTemplate))); + + // + // Damage Scaling + // + float damageMul = defaultMultiplier * statMod_global * statMod_damage; + + // Can not be less than MinDamageModifier + if (damageMul <= MinDamageModifier) + { + damageMul = MinDamageModifier; + } + + // Calculate the new base damage + float origDmgBase = origCreatureStats->GenerateBaseDamage(creatureTemplate); + float newDmgBase = 0; + + float vanillaDamage = creatureStats->BaseDamage[0]; + float bcDamage = creatureStats->BaseDamage[1]; + float wotlkDamage = creatureStats->BaseDamage[2]; + + // The database holds multiple values for base damage, one for each expansion + // This code will smooth transition between the different expansions based on the highest player level in the instance + // Only do this if level scaling is enabled + + if (LevelScaling) + { + // vanilla damage + if (mapABInfo->highestPlayerLevel <= 60) + { + newDmgBase=vanillaDamage; + } + // transition from vanilla to BC damage + else if (mapABInfo->highestPlayerLevel < 63) + { + float vanillaMultiplier = (63 - mapABInfo->highestPlayerLevel) / 3.0; + float bcMultiplier = 1.0f - vanillaMultiplier; + + newDmgBase=(vanillaDamage * vanillaMultiplier) + (bcDamage * bcMultiplier); + } + // BC damage + else if (mapABInfo->highestPlayerLevel <= 70) + { + newDmgBase=bcDamage; + } + // transition from BC to WotLK damage + else if (mapABInfo->highestPlayerLevel < 73) + { + float bcMultiplier = (73 - mapABInfo->highestPlayerLevel) / 3.0; + float wotlkMultiplier = 1.0f - bcMultiplier; + + newDmgBase=(bcDamage * bcMultiplier) + (wotlkDamage * wotlkMultiplier); + } + // WotLK damage + else + { + newDmgBase=wotlkDamage; + + // special increase for end-game content + if (LevelScalingEndGameBoost && maxNumberOfPlayers <= 5) { + if (mapABInfo->highestPlayerLevel >= 75 && creatureABInfo->UnmodifiedLevel < 75) + newDmgBase *= float(mapABInfo->highestPlayerLevel-70) * 0.3f; + } + } + + damageMul *= newDmgBase/origDmgBase; + } + + // + // Crowd Control Debuff Duration Scaling + // + float ccDurationMul; + if (statMod_ccDuration != -1.0f) + { + ccDurationMul = defaultMultiplier * statMod_ccDuration; + + // Min/Max checking + if (ccDurationMul < MinCCDurationModifier) + { + ccDurationMul = MinCCDurationModifier; + } + else if (ccDurationMul > MaxCCDurationModifier) + { + ccDurationMul = MaxCCDurationModifier; + } + } + else + { + ccDurationMul = 1.0f; + } + + // + // Apply New Values + // + if (!sABScriptMgr->OnBeforeUpdateStats(creature, scaledHealth, scaledMana, damageMul, newBaseArmor)) + return; + + uint32 prevMaxHealth = creature->GetMaxHealth(); + uint32 prevMaxPower = creature->GetMaxPower(POWER_MANA); + uint32 prevHealth = creature->GetHealth(); + uint32 prevPower = creature->GetPower(POWER_MANA); + + Powers pType= creature->getPowerType(); + + creature->SetArmor(newBaseArmor); + creature->SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, (float)newBaseArmor); + creature->SetCreateHealth(scaledHealth); + creature->SetMaxHealth(scaledHealth); + creature->ResetPlayerDamageReq(); + creature->SetCreateMana(scaledMana); + creature->SetMaxPower(POWER_MANA, scaledMana); + creature->SetModifierValue(UNIT_MOD_ENERGY, BASE_VALUE, (float)100.0f); + creature->SetModifierValue(UNIT_MOD_RAGE, BASE_VALUE, (float)100.0f); + creature->SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, (float)scaledHealth); + creature->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)scaledMana); + creatureABInfo->DamageMultiplier = damageMul; + creatureABInfo->CCDurationMultiplier = ccDurationMul; + + uint32 scaledCurHealth=prevHealth && prevMaxHealth ? float(scaledHealth)/float(prevMaxHealth)*float(prevHealth) : 0; + uint32 scaledCurPower=prevPower && prevMaxPower ? float(scaledMana)/float(prevMaxPower)*float(prevPower) : 0; + + creature->SetHealth(scaledCurHealth); + if (pType == POWER_MANA) + creature->SetPower(POWER_MANA, scaledCurPower); + else + creature->setPowerType(pType); // fix creatures with different power types + + // + // Reward Scaling + // + + // calculate the average multiplier after level scaling is applied + float averageMultiplierAfterLevelScaling; + // use health and damage to calculate the average multiplier + averageMultiplierAfterLevelScaling = (creatureABInfo->HealthMultiplier + creatureABInfo->DamageMultiplier) / 2.0f; + + // XP Scaling + if (RewardScalingXP) + { + if (RewardScalingMethod == AUTOBALANCE_SCALING_FIXED) + { + creatureABInfo->XPModifier = RewardScalingXPModifier; + } + else if (RewardScalingMethod == AUTOBALANCE_SCALING_DYNAMIC) + { + creatureABInfo->XPModifier = averageMultiplierAfterLevelScaling * RewardScalingXPModifier; + } + } + + // Money Scaling + if (RewardScalingMoney) + { + //LOG_DEBUG("module.AutoBalance", "AutoBalance_AllCreatureScript::ModifyCreatureAttributes: Creature {} ({}) has an average post-level-scaling modifier of {}.", creature->GetName(), creature->GetLevel(), averageMultiplierAfterLevelScaling); + + if (RewardScalingMethod == AUTOBALANCE_SCALING_FIXED) + { + creatureABInfo->MoneyModifier = RewardScalingMoneyModifier; + } + else if (RewardScalingMethod == AUTOBALANCE_SCALING_DYNAMIC) + { + creatureABInfo->MoneyModifier = averageMultiplierAfterLevelScaling * RewardScalingMoneyModifier; + } + } + + creature->UpdateAllStats(); + } + +private: + uint32 adjustCurCount(uint32 inputCount, uint32 dungeonId) + { + uint8 minPlayers = enabledDungeonIds[dungeonId]; + return inputCount < minPlayers ? minPlayers : inputCount; + } +}; +class AutoBalance_CommandScript : public CommandScript +{ +public: + AutoBalance_CommandScript() : CommandScript("AutoBalance_CommandScript") { } + + std::vector GetCommands() const + { + static std::vector ABCommandTable = + { + { "setoffset", SEC_GAMEMASTER, true, &HandleABSetOffsetCommand, "Sets the global Player Difficulty Offset for instances. Example: (You + offset(1) = 2 player difficulty)." }, + { "getoffset", SEC_PLAYER, true, &HandleABGetOffsetCommand, "Shows current global player offset value." }, + { "checkmap", SEC_GAMEMASTER, true, &HandleABCheckMapCommand, "Run a check for current map/instance, it can help in case you're testing autobalance with GM." }, + { "mapstat", SEC_PLAYER, true, &HandleABMapStatsCommand, "Shows current autobalance information for this map" }, + { "creaturestat", SEC_PLAYER, true, &HandleABCreatureStatsCommand, "Shows current autobalance information for selected creature." }, + { "mythic", SEC_PLAYER, true, &HandleABMythicCommand, "Sets the group difficulty to Mythic" }, + { "legendary", SEC_PLAYER, true, &HandleABLegendaryCommand, "Sets the group difficulty to Legendary" }, + { "ascendant", SEC_PLAYER, true, &HandleABAscendantCommand, "Sets the group difficulty to Ascendant" }, + { "getdifficulty", SEC_PLAYER, true, &HandleABGetDifficultyCommand, "Shows the current group difficulty" }, + + }; + + static std::vector commandTable = + { + { "autobalance", SEC_PLAYER, false, NULL, "", ABCommandTable }, + { "ab", SEC_PLAYER, false, NULL, "", ABCommandTable }, + }; + return commandTable; + } + + static bool HandleABSetOffsetCommand(ChatHandler* handler, const char* args) + { + if (!*args) + { + handler->PSendSysMessage(".autobalance setoffset #"); + handler->PSendSysMessage("Sets the Player Difficulty Offset for instances. Example: (You + offset(1) = 2 player difficulty)."); + return false; + } + char* offset = strtok((char*)args, " "); + int32 offseti = -1; + + if (offset) + { + offseti = (uint32)atoi(offset); + handler->PSendSysMessage("Changing Player Difficulty Offset to %i.", offseti); + PlayerCountDifficultyOffset = offseti; + lastConfigTime = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + return true; + } + else + handler->PSendSysMessage("Error changing Player Difficulty Offset! Please try again."); + return false; + } + + static bool HandleABGetOffsetCommand(ChatHandler* handler, const char* /*args*/) + { + handler->PSendSysMessage("Current Player Difficulty Offset = %i", PlayerCountDifficultyOffset); + return true; + } + + static bool HandleABCheckMapCommand(ChatHandler* handler, const char* args) + { + Player *pl = handler->getSelectedPlayer(); + + if (!pl) + { + handler->SendSysMessage(LANG_SELECT_PLAYER_OR_PET); + handler->SetSentErrorMessage(true); + return false; + } + + AutoBalanceMapInfo *mapABInfo=pl->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); + + mapABInfo->playerCount = pl->GetMap()->GetPlayersCountExceptGMs(); + + Map::PlayerList const &playerList = pl->GetMap()->GetPlayers(); + uint8 level = 0; + if (!playerList.IsEmpty()) + { + for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration) + { + if (Player* playerHandle = playerIteration->GetSource()) + { + if (playerHandle->getLevel() > level) + mapABInfo->mapLevel = level = playerHandle->getLevel(); + } + } + } + + HandleABMapStatsCommand(handler, args); + + return true; + } + + static bool HandleABMapStatsCommand(ChatHandler* handler, const char* /*args*/) + { + Player *player; + player = handler->getSelectedPlayer() ? handler->getSelectedPlayer() : handler->GetPlayer(); + + AutoBalanceMapInfo *mapABInfo=player->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); + + if (player->GetMap()->IsDungeon() || player->GetMap()->IsRaid()) + { + handler->PSendSysMessage("---"); + handler->PSendSysMessage("Map: %s (ID: %u)", player->GetMap()->GetMapName(), player->GetMapId()); + handler->PSendSysMessage("Players on map: %u (Lvl %u - %u)", + mapABInfo->playerCount, + mapABInfo->lowestPlayerLevel, + mapABInfo->highestPlayerLevel + ); + handler->PSendSysMessage("Map Level: %u%s", (uint8)(mapABInfo->avgCreatureLevel+0.5f), + mapABInfo->isLevelScalingEnabled ? std::string("->") + std::to_string(mapABInfo->highestPlayerLevel) + std::string(" (Level Scaling Enabled)") : std::string(" (Level Scaling Disabled)") + ); + handler->PSendSysMessage("LFG Range: Lvl %u - %u (Target: Lvl %u)", mapABInfo->lfgMinLevel, mapABInfo->lfgMaxLevel, mapABInfo->lfgTargetLevel); + handler->PSendSysMessage("Active Creatures in map: %u (Lvl %u - %u | Avg Lvl %.2f)", + mapABInfo->activeCreatureCount, + mapABInfo->lowestCreatureLevel, + mapABInfo->highestCreatureLevel, + mapABInfo->avgCreatureLevel + ); + handler->PSendSysMessage("Total Creatures in map: %u", + mapABInfo->allMapCreatures.size() + ); + + return true; + } + else + { + handler->PSendSysMessage("The target is not in a dungeon or battleground."); + return true; + } + } + + static bool HandleABCreatureStatsCommand(ChatHandler* handler, const char* /*args*/) + { + Creature* target = handler->getSelectedCreature(); + + if (!target) + { + handler->SendSysMessage(LANG_SELECT_CREATURE); + handler->SetSentErrorMessage(true); + return false; + } + + AutoBalanceCreatureInfo *creatureABInfo=target->CustomData.GetDefault("AutoBalanceCreatureInfo"); + AutoBalanceMapInfo *mapABInfo=target->GetMap()->CustomData.GetDefault("AutoBalanceMapInfo"); + + handler->PSendSysMessage("---"); + handler->PSendSysMessage("%s (%u%s%s), %s", + target->GetName(), + creatureABInfo->UnmodifiedLevel, + mapABInfo->isLevelScalingEnabled ? std::string("->") + std::to_string(creatureABInfo->selectedLevel) : "", + target->IsDungeonBoss() ? " | Boss" : "", + creatureABInfo->isActive ? "Active for Map Stats" : "Ignored for Map Stats"); + handler->PSendSysMessage("Health multiplier: %.3f", creatureABInfo->HealthMultiplier); + handler->PSendSysMessage("Mana multiplier: %.3f", creatureABInfo->ManaMultiplier); + handler->PSendSysMessage("Armor multiplier: %.3f", creatureABInfo->ArmorMultiplier); + handler->PSendSysMessage("Damage multiplier: %.3f", creatureABInfo->DamageMultiplier); + handler->PSendSysMessage("CC Duration multiplier: %.3f", creatureABInfo->CCDurationMultiplier); + handler->PSendSysMessage("XP multiplier: %.3f Money multiplier: %.3f", creatureABInfo->XPModifier, creatureABInfo->MoneyModifier); + + return true; + + } + + static bool HandleABMythicCommand(ChatHandler* handler, const char* /*args*/) + { + Player* player = handler->GetPlayer(); + if(!player) { + return false; + } + + Group* group = player->GetGroup(); + if (!group) + { + handler->PSendSysMessage("autobalance: You are not in a group."); + return false; + } + + if (group->GetLeader() != handler->GetSession()->GetPlayer()) + { + handler->PSendSysMessage("autobalance: You are not the group leader."); + return false; + } + + CharacterDatabase.DirectExecute("UPDATE groups SET difficulty = 2 WHERE guid = {}", group->GetGUID().GetEntry()); + handler->PSendSysMessage("autobalance: group difficulty set to Mythic. Rewards improvements x1. level(81-83) recommended."); + + return true; + } + + static bool HandleABLegendaryCommand(ChatHandler* handler, const char* /*args*/) + { + Player* player = handler->GetPlayer(); + if(!player) { + return false; + } + + Group* group = player->GetGroup(); + if (!group) + { + handler->PSendSysMessage("autobalance: You are not in a group."); + return false; + } + + if (group->GetLeader() != handler->GetSession()->GetPlayer()) + { + handler->PSendSysMessage("autobalance: You are not the group leader."); + return false; + } + + CharacterDatabase.DirectExecute("UPDATE groups SET difficulty = 3 WHERE guid = {}", group->GetGUID().GetEntry()); + handler->PSendSysMessage("autobalance: group difficulty set to Mythic. Reward improvements x2 level(85) recommended."); + + return true; + } + + static bool HandleABAscendantCommand(ChatHandler* handler, const char* /*args*/) + { + Player* player = handler->GetPlayer(); + if(!player) { + return false; + } + + Group* group = player->GetGroup(); + if (!group) + { + handler->PSendSysMessage("autobalance: You are not in a group."); + return false; + } + + if (group->GetLeader() != handler->GetSession()->GetPlayer()) + { + handler->PSendSysMessage("autobalance: You are not the group leader."); + return false; + } + + CharacterDatabase.DirectExecute("UPDATE groups SET difficulty = 2 WHERE guid = {}", group->GetGUID().GetEntry()); + handler->PSendSysMessage("autobalance: group difficulty set to Ascendant. Reward improvements x3 leve(85) required."); + + return true; + } + + static bool HandleABGetDifficultyCommand(ChatHandler* handler, const char*) { + Player* player = handler->GetPlayer(); + if(!player) { + return false; + } + + Group* group = player->GetGroup(); + if (!group) + { + handler->PSendSysMessage("autobalance: You are not in a group."); + return false; + } + + if (group->GetLeader() != handler->GetSession()->GetPlayer()) + { + handler->PSendSysMessage("autobalance: You are not the group leader."); + return false; + } + + QueryResult result = CharacterDatabase.Query("SELECT difficulty FROM groups WHERE guid = {}", group->GetGUID().GetEntry()); + if (!result) + { + handler->PSendSysMessage("autobalance: group difficulty not found."); + return false; + } + + Field* fields = result->Fetch(); + handler->PSendSysMessage("autobalance: group difficulty is set to %u.", fields[0].Get()); + return true; + } +}; + +class AutoBalance_GlobalScript : public GlobalScript { +public: + AutoBalance_GlobalScript() : GlobalScript("AutoBalance_GlobalScript") { } + + void OnAfterUpdateEncounterState(Map* map, EncounterCreditType type, uint32 /*creditEntry*/, Unit* /*source*/, Difficulty /*difficulty_fixed*/, DungeonEncounterList const* /*encounters*/, uint32 /*dungeonCompleted*/, bool updated) override { + //if (!dungeonCompleted) + // return; + + if (!rewardEnabled || !updated) + return; + + if (map->GetPlayersCountExceptGMs() < MinPlayerReward) + return; + + AutoBalanceMapInfo *mapABInfo=map->CustomData.GetDefault("AutoBalanceMapInfo"); + + // skip if it's not a pre-wotlk dungeon/raid and if it's not scaled + if (!LevelScaling || mapABInfo->mapLevel <= 70 || mapABInfo->lfgMinLevel <= 70 + // skip when not in dungeon or not kill credit + || type != ENCOUNTER_CREDIT_KILL_CREATURE || !map->IsDungeon()) + return; + + Map::PlayerList const &playerList = map->GetPlayers(); + + if (playerList.IsEmpty()) + return; + + uint32 reward = map->ToInstanceMap()->GetMaxPlayers() > 5 ? rewardRaid : rewardDungeon; + if (!reward) + return; + + //instanceStart=0, endTime; + uint8 difficulty = map->GetDifficulty(); + + for (Map::PlayerList::const_iterator itr = playerList.begin(); itr != playerList.end(); ++itr) + { + if (!itr->GetSource() || itr->GetSource()->IsGameMaster() || itr->GetSource()->getLevel() < DEFAULT_MAX_LEVEL) + continue; + + itr->GetSource()->AddItem(reward, 1 + difficulty); // difficulty boost + } + } + + void OnBeforeDropAddItem(Player const* player, Loot& loot, bool canRate, uint16 lootMode, LootStoreItem* LootStoreItem, LootStore const& store) override { + // just log out the item drops for now + + if(LootStoreItem->itemid == 0) { + return; + } + + ItemTemplate const* newItem = sObjectMgr->GetItemTemplate(LootStoreItem->itemid); + Map* map = player->GetMap(); + + LOG_INFO("server", "> OnBeforeDropAddItem: Current Loot Drop Item {}", newItem->Name1); + + // 3 things things need to happen + // 1. Is the instance scaled up to max level or beyond? + // 2. Is the loot quality rare or higher? + // 3. What is the difficulty of the instances? 2 - Mythic 3 - Legendary 4 - Ascendant + + // 1. Is the instance scaled up to max level or beyond? + AutoBalanceMapInfo *mapABInfo = map->CustomData.GetDefault("AutoBalanceMapInfo"); + uint8 creatureLevel = mapABInfo->highestCreatureLevel; + + // The items are deterministic based ont the id just need to add the correct id starting point + uint32 idStart = 0; + + // 1. Is the instance scaled up to max level or beyond? + if(creatureLevel <= 80) { + return; + } + + // 2. Is the loot quality rare or higher? + if (newItem->Quality < 3) { + return; + } + + // 3. What is the difficulty of the instances? 2 - Mythic 3 - Legendary 4 - Ascendant + switch(map->GetDifficulty()) { + case 2: + idStart = 20000000; + break; + case 3: + idStart = 21000000; + break; + case 4: + idStart = 22000000; + break; + default: + return; + } + + const Group* group = player->GetGroup(); + if (!group) + { + ChatHandler(player->GetSession()).PSendSysMessage("autobalance: You are not in a group."); + return; + } + + ChatHandler chandler = ChatHandler(player->GetSession()); + + QueryResult result = CharacterDatabase.Query("SELECT difficulty FROM groups WHERE guid = {}", group->GetGUID().GetEntry()); + if (!result) + { + chandler.PSendSysMessage("autobalance: group difficulty not found."); + return; + } + + Field* fields = result->Fetch(); + chandler.PSendSysMessage("autobalance: group difficulty is set to %u.", fields[0].Get()); + + + // ItemTemplate const* newItem = sObjectMgr->GetItemTemplate(200000000); + // if(!newItem) { + // LOG_INFO("server", "> OnBeforeDropAddItem: New Loot Item not found"); + // } else { + // LOG_INFO("server", "> OnBeforeDropAddItem: New ITEM ItemName {} Quality {} ItemLevel {}", newItem->Name1, newItem->Quality, newItem->ItemLevel); + // } + + // for (LootItem& item : loot.items) { + // LOG_INFO("server", "> OnBeforeDropAddItem: Items {} {}", player->GetName(), item.itemid); + // ItemTemplate const* itemProto = sObjectMgr->GetItemTemplate(item.itemid); + // if (itemProto) { + // LOG_INFO("server", "> OnBeforeDropAddItem: OLD ITEM ItemName {} Quality {} ItemLevel {}", itemProto->Name1, itemProto->Quality, itemProto->ItemLevel); + // } + // } + } +}; + +// this handles updating custom group difficulties used in auto balancing mobs and +// scripts that enable buffs on mobs randomly +class AutoBalance_GroupScript : public GroupScript { +public: + AutoBalance_GroupScript() : GroupScript("AutoBalance_GroupScript") { } + + void OnCreate(Group* group, Player* leader) override { + if (!group) + return; + + Player* leader = group->GetLeader(); + if (!leader) + return; + + // default difficulty is whatever the player currently has it set as + uint8 difficulty = leader->GetDifficulty(); + + CharacterDatabase.DirectExecute("INSERT INTO group_difficulty (group_id, difficulty) VALUES ({}, {}) ON DUPLICATE KEY UPDATE difficulty = {}", + group->GetGUID().GetEntry(), difficulty, difficulty); + } + + void OnDisband(Group* group) override { + if (!group) + return; + + CharacterDatabase.DirectExecute("DELETE FROM group_difficulty WHERE group_id = {}", group->GetGUID().GetEntry()); + } +} + +void AddAutoBalanceScripts() +{ + new AutoBalance_WorldScript(); + new AutoBalance_PlayerScript(); + new AutoBalance_UnitScript(); + new AutoBalance_AllCreatureScript(); + new AutoBalance_AllMapScript(); + new AutoBalance_CommandScript(); + new AutoBalance_GlobalScript(); + new AutoBalance_GroupScript(); +} diff --git a/src/AutoBalance.h b/src/AutoBalance.h new file mode 100755 index 0000000..41ae418 --- /dev/null +++ b/src/AutoBalance.h @@ -0,0 +1,43 @@ +#ifndef MOD_AUTOBALANCE_H +#define MOD_AUTOBALANCE_H + +#include "ScriptMgr.h" +#include "Creature.h" + +// Manages registration, loading, and execution of scripts. +class ABScriptMgr +{ + public: /* Initialization */ + + static ABScriptMgr* instance(); + // called at the start of ModifyCreatureAttributes method + // it can be used to add some condition to skip autobalancing system for example + bool OnBeforeModifyAttributes(Creature* creature, uint32 & instancePlayerCount); + // called right after default multiplier has been set, you can use it to change + // current scaling formula based on number of players or just skip modifications + bool OnAfterDefaultMultiplier(Creature* creature, float &defaultMultiplier); + // called before change creature values, to tune some values or skip modifications + bool OnBeforeUpdateStats(Creature* creature, uint32 &scaledHealth, uint32 &scaledMana, float &damageMultiplier, uint32 &newBaseArmor); +}; + +#define sABScriptMgr ABScriptMgr::instance() + +/* +* Dedicated hooks for Autobalance Module +* Can be used to extend/customize this system +*/ +class ABModuleScript : public ModuleScript +{ + protected: + + ABModuleScript(const char* name); + + public: + virtual bool OnBeforeModifyAttributes(Creature* /*creature*/, uint32 & /*instancePlayerCount*/) { return true; } + virtual bool OnAfterDefaultMultiplier(Creature* /*creature*/, float & /*defaultMultiplier*/) { return true; } + virtual bool OnBeforeUpdateStats(Creature* /*creature*/, uint32 &/*scaledHealth*/, uint32 &/*scaledMana*/, float &/*damageMultiplier*/, uint32 &/*newBaseArmor*/) { return true; } +}; + +template class ScriptRegistry; + +#endif