commit 796078a5645c241a915328e8cfa57c7b0de65082 Author: Ben Carter Date: Mon Jul 8 00:03:08 2024 -0400 Autobalance updates from local 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 0000000..6547b4a Binary files /dev/null and b/icon.png differ diff --git a/include.sh b/include.sh new file mode 100755 index 0000000..e69de29 diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100755 index 0000000..21c9245 --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,25 @@ + + +## 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