40 Commits

Author SHA1 Message Date
c73dba69d7 Added new melee scaling 2024-10-04 20:41:52 -04:00
b9825b47e0 Working on spell scaling individually per dungeon to make sur eI account for everything and many different spells 2024-10-03 01:13:29 -04:00
cbde9bbfe2 Updated EligibleDamageTarget additional safeguard on owner check. 2024-10-02 23:27:33 -04:00
af9e7b3f3f Added pets generator script and fixed segfaults in eligible methods 2024-10-02 00:05:32 -04:00
5b6805dcc7 Added studio code props / settins 2024-10-01 22:56:41 -04:00
78648bd511 Added necessary sql files 2024-10-01 22:56:29 -04:00
Ben Carter
fd4c9d2b51 Merge pull request #2 from araxiaonline/feat/add-mythic-v1
Build: Feat/add mythic v1
2024-10-01 22:45:13 -04:00
2fda1c1a93 Scaling updates for Attack Power 2024-10-01 19:50:07 -04:00
ca1dda4979 Added debug command to CommandScript 2024-10-01 00:10:34 -04:00
04dc95f2e3 Addressed compile error not being defined in MythicPlus.h 2024-09-30 21:12:15 -04:00
d7e1f7a026 Adding base increases to normalize damange similar to heroic dungeons from normal 2024-09-30 21:03:28 -04:00
b6e21b5cf7 Adding scaling factors to how damage is scaled 2024-09-30 00:23:56 -04:00
e4a234b567 more work on unit script to increase damage 2024-09-29 02:27:54 -04:00
c4be5d44be Adding Spell scaling into base class 2024-09-29 02:04:02 -04:00
ff5eb09faa Refactored unit damage to be cleaner 2024-09-28 18:35:42 -04:00
6356d975d8 alpha seems to be working 2024-09-27 00:48:08 -04:00
5815d20c1c Added logging to test enemy weapon damage out and adding different setting for weapon base damage for enemies. 2024-09-26 23:34:25 -04:00
abee7bcc41 Added . eligible target checks and attempting to debug damage scalin for non-player supporters 2024-09-26 01:13:53 -04:00
014773379e Added changes to scaling and some debugging logic around the unit scripts 2024-09-25 22:32:38 -04:00
d74e9f96e1 Added more umph to multiplier on base damage 2024-09-25 01:28:45 -04:00
519d17a272 Added Unit damage code and removed chatty logging 2024-09-25 01:19:16 -04:00
7b79bf0316 Added Creature Map tracking and improved performance on storage 2024-09-20 23:28:58 -04:00
3b7c03ce0f Scaling tests are working for base stats 2024-09-20 01:28:33 -04:00
e7d1b85074 Added Scaling to boss and figured out event race conditions 2024-09-20 01:17:39 -04:00
7a73671b0f Added level ups for creatures and better logging around creature events 2024-09-19 00:59:40 -04:00
02f27a32f1 Added in leveling for enemies 2024-09-05 23:52:05 -04:00
a626114043 fixed typo in configuration settings and switched to afterconfig load 2024-09-03 23:42:14 -04:00
f98ec7ad57 Added in logic for when player enters an instance to store details and updated config settings 2024-09-03 23:01:31 -04:00
d98a0345d7 fixed compile issues for first compile and updated conf to remove duplicates 2024-09-02 00:37:11 -04:00
ef206b6a0d Merge branch 'main' into feat/add-mythic-v1 2024-09-01 23:54:11 -04:00
Ben Carter
3428bcc605 Merge pull request #1 from araxiaonline/feat/update-loot
Added scaled stats and mythicplus table up to 100
2024-09-01 23:53:35 -04:00
84543dc17a Added Legendary and Ascendant static shortcut commands 2024-09-01 23:50:31 -04:00
9a2b880350 Finished up commands 2024-09-01 23:49:35 -04:00
f7e2bf2450 Adding scripts that add creature higher level stats 2024-09-01 01:35:31 -04:00
3383cb1323 Added methods for saving group data to memory and the database
Also added some new commands
2024-09-01 01:34:35 -04:00
2c8b67ef07 Changed MythicPlus and MpDataStore to pure singletons and updated current
calling scripts to not get the instance just call via defined macro
2024-08-31 23:17:52 -04:00
87d748cef6 Added initial pass at configuration variables for mythic plus 2024-08-31 22:21:59 -04:00
a5dd94fe54 Adding data storage and commands 2024-08-17 20:51:27 -04:00
7446c676f2 Building out data stores. Moved logging to standalone class. 2024-08-05 22:19:46 -04:00
0415c5ef41 Starting to build out the base setup for the module 2024-08-03 23:06:45 -04:00
29 changed files with 41741 additions and 186 deletions

22
.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,22 @@
{
"configurations": [
{
"name": "AzerothCore",
"includePath": [
"${workspaceFolder}/src/common/Logging/**",
"${workspaceFolder}/src",
"${workspaceFolder}/deps",
"${workspaceFolder}/**",
"/opt/homebrew/include"
],
"defines": [
"AZEROTHCORE"
],
"compilerPath": "/usr/bin/clang++", // macOS typically uses clang as the default compiler
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "macos-clang-x64"
}
],
"version": 4
}

View File

@@ -0,0 +1,114 @@
[worldserver]
##########################################################
#
# Logging Configuration
#
##########################################################
Appender.MythicPlusLog=2,5,0,mod-mythic-plus.log,w
Appender.MythicPlusConsole=1,5,0,"1 9 3 6 5 8"
Logger.module.MythicPlus=5,MythicPlusLog MythicPlusConsole
##########################################################
#
# Mythic+ Global Settings
# - These settings are used to enable or disable the Mythic+ system and set the global settings for the system.
#
# Enabled: 1 = Enabled, 0 = Disabled - Enables the Mythic+ system.
# EnableItemRewards: 1 = Enabled, 0 = Disabled - Enables the reward system for Mythic+ dungeons.
# EnableDeathLimits: 1 = Enabled, 0 = Disabled - Enables the death limit system for Mythic+ dungeons.
# EnabledDifficulties: A comma separated list of difficulties that are enabled for Mythic+ dungeons.
# DisabledDungeons: A comma separated list of dungeons that are disabled for Mythic+ dungeons.
#
##########################################################
MythicPlus.Enabled = 1
MythicPlus.EnableItemRewards = 1
MythicPlus.EnableDeathLimits = 1
##########################################################
#
# Enabled difficulty settings
#
##########################################################
MythicPlus.EnabledDifficulties = mythic,legendary,ascendant
MythicPlus.DisabledDungeons =
##########################################################
#
# Mythic+ Stat Modifiers By Difficuty
# - These values are used to adjust the difficulty of enemies and bosses base
# stats based on the difficulty of the dungeon.
# - Bosses will only receive their multiplier not the Dungeon multiplier to prevent multiplicative scaling.
#
##########################################################
MythicPlus.Mythic.DungeonHealth = 1.25
MythicPlus.Mythic.DungeonMelee = 1.25
MythicPlus.Mythic.DungeonBaseDamage = 1.50
MythicPlus.Mythic.DungeonSpell = 1.15
MythicPlus.Mythic.DungeonArmor = 1.95
MythicPlus.Mythic.DungeonAvgLevel = 83
MythicPlus.Mythic.DungeonBossHealth = 1.50
MythicPlus.Mythic.DungeonBossMelee = 1.35
MythicPlus.Mythic.DungeonBossBaseDamage = 2.00
MythicPlus.Mythic.DungeonBossSpell = 1.25
MythicPlus.Mythic.DungeonBossArmor = 1.35
MythicPlus.Mythic.DungeonBossLevel = 85
MythicPlus.Legendary.DungeonHealth = 2.25
MythicPlus.Legendary.DungeonMelee = 2.25
MythicPlus.Legendary.DungeonBaseDamage = 2.75
MythicPlus.Legendary.DungeonSpell = 2.25
MythicPlus.Legendary.DungeonArmor = 2.25
MythicPlus.Legendary.DungeonAvgLevel = 85
MythicPlus.Legendary.DungeonBossHealth = 2.25
MythicPlus.Legendary.DungeonBossMelee = 2.25
MythicPlus.Legendary.DungeonBossSpell = 2.25
MythicPlus.Legendary.DungeonBossBaseDamage = 4.00
MythicPlus.Legendary.DungeonBossArmor = 3.25
MythicPlus.Legendary.DungeonBossLevel = 87
MythicPlus.Ascendant.DungeonHealth = 3.25
MythicPlus.Ascendant.DungeonMelee = 3.25
MythicPlus.Ascendant.DungeonBaseDamage = 4.75
MythicPlus.Ascendant.DungeonSpell = 3.25
MythicPlus.Ascendant.DungeonArmor = 3.25
MythicPlus.Ascendant.DungeonAvgLevel = 87
MythicPlus.Ascendant.DungeonBossHealth = 3.25
MythicPlus.Ascendant.DungeonBossMelee = 3.25
MythicPlus.Ascendant.DungeonBossSpell = 3.25
MythicPlus.Ascendant.DungeonBossBaseDamage = 6.00
MythicPlus.Ascendant.DungeonBossArmor = 3.25
MythicPlus.Ascendant.DungeonBossLevel = 90
##########################################################
#
# Mythic+ Gameplay Settings
# - If Death limit mode is enabled the below numbers set the max number of total deaths before players are kicked out and the
# that instance of the dungeon is failed.
#
##########################################################
MythicPlus.Mythic.DeathAllowance = 100
MythicPlus.Legendary.DeathAllowance = 30
MythicPlus.Ascendant.DeathAllowance = 15
##########################################################
#
# Itemization Settings
# - These values are used to set the offset to where items per difficulty will map to.
# - Item drops in this mode use the original item entry + on offset to determine the new item entry reward.
# - This enables a fast lookup at loot drop time as it is just an addition operation.
# - If using the base geneated sql the settings below will map to it. If the offset is incorrect,
# the item will not be found and the original low level item will be rewarded.
#
##########################################################
MythicPlus.Mythic.ItemOffset = 20000000
MythicPlus.Legendary.ItemOffset = 21000000
MythicPlus.Ascendant.ItemOffset = 22000000

View File

@@ -1,25 +1,4 @@
/**
* This creates the scaled up stats based on polynomial regression of the base stat growth
*/
CREATE TABLE IF NOT EXISTS `mythicplus_classlevelstats` (
`level` tinyint unsigned NOT NULL,
`class` tinyint unsigned NOT NULL,
`basehp0` int unsigned NOT NULL DEFAULT '1',
`basehp1` int unsigned NOT NULL DEFAULT '1',
`basehp2` int unsigned NOT NULL DEFAULT '1',
`basemana` int unsigned NOT NULL DEFAULT '0',
`basearmor` int unsigned NOT NULL DEFAULT '1',
`attackpower` int unsigned NOT NULL DEFAULT '0',
`rangedattackpower` int unsigned NOT NULL DEFAULT '0',
`damage_base` float NOT NULL DEFAULT '0',
`damage_exp1` float NOT NULL DEFAULT '0',
`damage_exp2` float NOT NULL DEFAULT '0',
`comment` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
PRIMARY KEY (`level`,`class`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
;
INSERT IGNORE INTO mythicplus_classlevelstats (class, `level`, basehp0, basehp1, basehp2,
REPLACE creature_classlevelstats (class, `level`, basehp0, basehp1, basehp2,
basemana, basearmor, attackpower, rangedattackpower, damage_base, damage_exp1, damage_exp2) VALUES
(1, 81, 5489.19, 9449.55, 12640.32, 0.00, 10033.36, 714.66, 111.97, 47.9043, 131.5509, 166.3098),
(1, 82, 5641.53, 9700.79, 12855.00, 0.00, 10337.65, 790.54, 121.09, 48.5708, 131.9685, 169.2029),

View File

@@ -0,0 +1,91 @@
-- Last Update: 2021/08/15
-- Description: Scale factors for Mythic+ dungeons used to normalize dungeon difficulty across expansions.
DROP TABLE IF EXISTS mythic_plus_scale_factors;
CREATE TABLE IF NOT EXISTS mythic_plus_scale_factors (
mapId SMALLINT PRIMARY KEY,
dmg_bonus TINYINT,
spell_bonus TINYINT,
hp_bonus TINYINT,
difficulty TINYINT,
max TINYINT
);
-- 1. Pre 60 level dungeons (13 dmg_bonus, 2 hp_bonus, max 23, difficulty 3)
INSERT INTO mythic_plus_scale_factors (mapId, dmg_bonus, spell_bonus, hp_bonus, difficulty, max)
VALUES
(389, 22, 19,2, 3, 23), -- Ragefire Chasm
(43, 19, 18,2, 3, 23), -- Wailing Caverns
(36, 19, 19,2, 3, 23), -- The Deadmines
(33, 19, 19,2, 3, 23), -- Shadowfang Keep
(34, 19, 19,2, 3, 23), -- The Stockade
(48, 19, 19,2, 3, 23), -- Blackfathom Deeps
(90, 19, 19,2, 3, 23), -- Gnomeregan
(47, 19, 19,2, 3, 23), -- Razorfen Kraul
(189, 19, 19,2, 3, 23), -- Scarlet Monastery (Graveyard)
(129, 19, 19,2, 3, 23), -- Razorfen Downs
(70, 19, 19,2, 3, 23), -- Uldaman
(209, 19, 19,2, 3, 23), -- Zul'Farrak
(349, 19, 19,2, 3, 23) -- Maraudon
ON DUPLICATE KEY UPDATE mapId = mapId;
-- 2. Level 60 dungeons for classic (15 dmg_bonus, 3 hp_bonus, max 25, difficulty 3)
INSERT INTO mythic_plus_scale_factors (mapId, dmg_bonus, spell_bonus, hp_bonus, difficulty, max)
VALUES
(289, 17, 20,3, 3, 25), -- Scholomance
(109, 17, 20,3, 3, 25), -- Sunken Temple
(329, 17, 20,3, 3, 25), -- Stratholme
(229, 17, 20,3, 3, 25), -- Blackrock Spire (Lower)
(230, 17, 20,3, 3, 25), -- Blackrock Spire (Upper)
(429, 17, 20,3, 3, 25), -- Dire Maul
(269, 17, 20,3, 3, 25) -- Temple of Atal'Hakkar
ON DUPLICATE KEY UPDATE mapId = mapId;
-- 3. Pre 70 dungeons in Burning Crusade (15 dmg_bonus, 4 hp_bonus, max 26, difficulty 3)
INSERT INTO mythic_plus_scale_factors (mapId, dmg_bonus, spell_bonus, hp_bonus, difficulty, max)
VALUES
(542, 16, 14,4, 3, 26), -- Hellfire The Blood Furnace
(543, 16, 14,4, 3, 26), -- Hellfire Ramparts
(545, 16, 14,4, 3, 26), -- Coilfang Steamvaults
(546, 16, 14,4, 3, 26), -- Coilfang Reservoir: The Underbog
(547, 16, 14,4, 3, 26), -- Coilfang Reservoir: The Underbog
(557, 16, 14,4, 3, 26), -- Auchindoun: Mana-Tombs
(558, 16, 14,4, 3, 26), -- Auchindoun: Auchenai Crypts
(560, 16, 14,4, 3, 26) -- Caverns of Time: Old Hillsbrad Foothills
ON DUPLICATE KEY UPDATE mapId = mapId;
-- 4. Level 70 dungeons in Burning Crusade (14 dmg_bonus, 4 hp_bonus, max 29, difficulty 3)
INSERT INTO mythic_plus_scale_factors (mapId, dmg_bonus, spell_bonus, hp_bonus, difficulty, max)
VALUES
(540, 14, 13,4, 3, 29), -- Shattered Halls
(556, 14, 13,4, 3, 29), -- Auchindoun: Sethekk Halls
(555, 14, 13,4, 3, 29), -- Auchindoun: Shadow Labyrinth
(553, 14, 13,4, 3, 29), -- Tempest Keep: The Botanica
(554, 14, 13,4, 3, 29), -- Tempest Keep: The Mechanar
(552, 14, 13,4, 3, 29), -- Tempest Keep: The Arcatraz
(585, 14, 13,4, 3, 29) -- Magisters' Terrace
ON DUPLICATE KEY UPDATE mapId = mapId;
-- 5. Pre 80 dungeons in Wrath of the Lich King (17 dmg_bonus, 3 hp_bonus, max 30, difficulty 3)
INSERT INTO mythic_plus_scale_factors (mapId, dmg_bonus, spell_bonus, hp_bonus, difficulty, max)
VALUES
(574, 19, 12,3, 3, 30), -- Utgarde Keep
(619, 19, 12,3, 3, 30), -- Ahn'kahet: The Old Kingdom
(576, 19, 12,3, 3, 30), -- The Nexus
(600, 19, 12,3, 3, 30), -- Drak'Tharon Keep
(601, 19, 12,3, 3, 30), -- Azjol-Nerub
(608, 19, 12,3, 3, 30) -- The Violet Hold
ON DUPLICATE KEY UPDATE mapId = mapId;
-- 6. Level 80 dungeons in Wrath of the Lich King (19 dmg_bonus, 4 hp_bonus, max 33, difficulty 3)
INSERT INTO mythic_plus_scale_factors (mapId, dmg_bonus, spell_bonus, hp_bonus, difficulty, max)
VALUES
(595, 19, 13,5, 3, 33), -- The Culling of Stratholme
(604, 19, 13,5, 3, 33), -- Gundrak
(599, 19, 13,5, 3, 33), -- Halls of Stone
(602, 19, 13,5, 3, 33), -- Halls of Lightning
(578, 19, 13,5, 3, 33), -- The Oculus
(650, 19, 13,5, 3, 33), -- Trial of the Champion
(632, 19, 13,5, 3, 33), -- The Forge of Souls
(658, 19, 13,5, 3, 33), -- Pit of Saron
(668, 19, 13,5, 3, 33) -- Halls of Reflection
ON DUPLICATE KEY UPDATE mapId = mapId;

View File

@@ -0,0 +1,51 @@
/** Attempts to insert all post-80 player class stats into the database. */
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (1, 81, 9485, 0, 209, 138, 193, 45, 71);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (1, 82, 10434, 0, 211, 139, 195, 45, 72);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (1, 83, 11477, 0, 213, 140, 197, 45, 73);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (1, 84, 12594, 0, 215, 142, 199, 46, 74);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (1, 85, 13793, 0, 218, 143, 201, 46, 75);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (2, 81, 7449, 4564, 166, 100, 158, 109, 112);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (2, 82, 8078, 4693, 169, 101, 161, 111, 113);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (2, 83, 8737, 4822, 172, 103, 164, 112, 115);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (2, 84, 9427, 4951, 175, 104, 166, 114, 116);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (2, 85, 10151, 5080, 178, 106, 169, 116, 118);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (3, 81, 7482, 4916, 78, 189, 134, 94, 102);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (3, 82, 8070, 5084, 79, 192, 136, 95, 103);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (3, 83, 8681, 5252, 80, 195, 138, 97, 105);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (3, 84, 9315, 5419, 81, 198, 140, 98, 107);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (3, 85, 9973, 5587, 82, 201, 142, 100, 108);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (4, 81, 7246, 0, 121, 200, 113, 47, 71);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (4, 82, 7814, 0, 123, 203, 114, 48, 72);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (4, 83, 8407, 0, 124, 206, 116, 48, 73);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (4, 84, 9025, 0, 126, 209, 117, 49, 74);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (4, 85, 9670, 0, 128, 212, 119, 50, 75);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (5, 81, 7395, 4153, 49, 57, 74, 197, 205);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (5, 82, 7975, 4280, 49, 58, 75, 201, 208);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (5, 83, 8581, 4407, 50, 58, 76, 204, 211);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (5, 84, 9215, 4534, 50, 59, 77, 207, 215);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (5, 85, 9876, 4661, 51, 60, 78, 210, 218);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (6, 81, 8118, 0, 176, 114, 166, 37, 58);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (6, 82, 8740, 0, 179, 116, 169, 37, 59);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (6, 83, 9390, 0, 181, 117, 172, 37, 60);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (6, 84, 10070, 0, 184, 119, 175, 38, 61);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (6, 85, 10802, 0, 187, 121, 178, 38, 61);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (7, 81, 7382, 4562, 123, 77, 139, 131, 145);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (7, 82, 7981, 4692, 123, 78, 141, 134, 147);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (7, 83, 8607, 4823, 125, 79, 143, 137, 149);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (7, 84, 9259, 4953, 126, 80, 145, 139, 151);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (7, 85, 9939, 5083, 128, 81, 147, 142, 153);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (8, 81, 7382, 3446, 39, 47, 61, 207, 197);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (8, 82, 7981, 3546, 39, 47, 62, 211, 201);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (8, 83, 8607, 3647, 40, 48, 63, 214, 204);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (8, 84, 9259, 3748, 40, 48, 63, 218, 208);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (8, 85, 9939, 3849, 40, 49, 64, 221, 211);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (9, 81, 7153, 3690, 61, 69, 92, 158, 165);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (9, 82, 7700, 3817, 62, 70, 93, 161, 168);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (9, 83, 8266, 3944, 63, 71, 94, 164, 171);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (9, 84, 8852, 4072, 63, 72, 95, 166, 175);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (9, 85, 9460, 4199, 64, 73, 96, 169, 178);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (11, 81, 5694, 3227, 99, 90, 106, 163, 180);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (11, 82, 6132, 3342, 100, 91, 108, 166, 183);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (11, 83, 6596, 3458, 101, 92, 109, 168, 186);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (11, 84, 7086, 3573, 102, 94, 111, 171, 190);
insert ignore into acore_world.player_class_stats (Class, Level, BaseHP, BaseMana, Strength, Agility, Stamina, Intellect, Spirit) values (11, 85, 7601, 3688, 104, 95, 113, 174, 193);

22
scripts/go.mod Normal file
View File

@@ -0,0 +1,22 @@
module github.com/araxiaonline/mod-mythic-plus
go 1.22.4
require (
git.sr.ht/~sbinet/gg v0.5.0 // indirect
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect
github.com/campoy/embedmd v1.0.0 // indirect
github.com/go-fonts/liberation v0.3.2 // indirect
github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea // indirect
github.com/go-pdf/fpdf v0.9.0 // indirect
github.com/goccmack/gocc v0.0.0-20230228185258-2292f9e40198 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/image v0.14.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.15.0 // indirect
gonum.org/v1/gonum v0.15.0 // indirect
gonum.org/v1/plot v0.14.0 // indirect
)

59
scripts/go.sum Normal file
View File

@@ -0,0 +1,59 @@
git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8=
git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY=
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
github.com/go-fonts/liberation v0.3.2 h1:XuwG0vGHFBPRRI8Qwbi5tIvR3cku9LUfZGq/Ar16wlQ=
github.com/go-fonts/liberation v0.3.2/go.mod h1:N0QsDLVUQPy3UYg9XAc3Uh3UDMp2Z7M1o4+X98dXkmI=
github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea h1:DfZQkvEbdmOe+JK2TMtBM+0I9GSdzE2y/L1/AmD8xKc=
github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea/go.mod h1:Y7Vld91/HRbTBm7JwoI7HejdDB0u+e9AUBO9MB7yuZk=
github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw=
github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y=
github.com/goccmack/gocc v0.0.0-20230228185258-2292f9e40198 h1:FSii2UQeSLngl3jFoR4tUKZLprO7qUlh/TKKticc0BM=
github.com/goccmack/gocc v0.0.0-20230228185258-2292f9e40198/go.mod h1:DTh/Y2+NbnOVVoypCCQrovMPDKUGp4yZpSbWg5D0XIM=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE=
gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU=
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=

209
scripts/main.go Normal file
View File

@@ -0,0 +1,209 @@
package main
import (
"fmt"
"log"
"math"
"gonum.org/v1/gonum/mat"
)
// Data structure to hold the input data
type Data struct {
Level float64
Class int
BaseHP0 float64
BaseHP1 float64
BaseHP2 float64
BaseMana float64
BaseArmor float64
AttackPower float64
RangedAttackPower float64
DamageBase float64
DamageExp1 float64
DamageExp2 float64
Comment *string
}
func main() {
// Input data (levels 68 to 80 for each class)
data := []Data{
{68, 1, 3834, 6542, 6986, 0, 6126, 292, 41, 39.2381, 94.4934, 104.527, nil},
{68, 2, 3067, 5233, 6986, 2991, 6116, 276, 31, 36.3244, 87.2677, 104.527, nil},
{68, 4, 3834, 6542, 6986, 0, 5527, 292, 41, 39.2381, 94.4934, 104.527, nil},
{68, 8, 2684, 4580, 5588, 6882, 4928, 130, 27, 33.3048, 80.1061, 96.6868, nil},
{69, 1, 3942, 6761, 7984, 0, 6423, 298, 43, 39.9047, 99.5328, 114.153, nil},
{69, 2, 3153, 5409, 7984, 3080, 6412, 282, 32, 36.974, 91.8916, 114.153, nil},
{69, 4, 3942, 6761, 7984, 0, 5795, 298, 43, 39.9047, 99.5328, 114.153, nil},
{69, 8, 2759, 4733, 6387, 7031, 5167, 133, 28, 33.8695, 84.2722, 105.591, nil},
{70, 1, 4050, 6986, 8982, 0, 6719, 304, 44, 40.5714, 104.527, 123.779, nil},
{70, 2, 3240, 5589, 8982, 3155, 6708, 286, 33, 37.6361, 96.7364, 123.779, nil},
{70, 4, 4050, 6986, 8982, 0, 6062, 304, 44, 40.5714, 104.527, 123.779, nil},
{70, 8, 2835, 4890, 7185, 7196, 5404, 135, 28, 34.4369, 88.3402, 114.496, nil},
{71, 1, 4163, 7181, 9291, 0, 7018, 308, 48, 41.2381, 106.357, 127.382, nil},
{71, 2, 3330, 5744, 9291, 3231, 7007, 290, 37, 38.2899, 98.3977, 127.383, nil},
{71, 4, 4163, 7181, 9291, 0, 6332, 308, 48, 41.2381, 106.357, 127.382, nil},
{71, 8, 2914, 5027, 7432, 7332, 5645, 137, 31, 35.0025, 92.4034, 117.829, nil},
{72, 1, 4278, 7380, 9610, 0, 7318, 314, 53, 41.9047, 108.071, 131.091, nil},
{72, 2, 3422, 5903, 9610, 3309, 7305, 296, 40, 38.9492, 99.8571, 131.092, nil},
{72, 4, 4278, 7380, 9610, 0, 6602, 314, 53, 41.9047, 108.071, 131.091, nil},
{72, 8, 2995, 5166, 7688, 7500, 5886, 140, 34, 35.5693, 96.5068, 121.259, nil},
{73, 1, 4399, 7588, 9940, 0, 7618, 320, 58, 42.5714, 118.643, 134.908, nil},
{73, 2, 3519, 6070, 9940, 3387, 7604, 302, 44, 39.6048, 101.451, 134.908, nil},
{73, 4, 4399, 7580, 9940, 0, 6872, 320, 58, 42.5714, 118.643, 134.908, nil},
{73, 8, 3098, 5311, 7952, 7654, 6126, 143, 37, 36.1353, 100.617, 124.79, nil},
{74, 1, 4524, 7804, 10282, 0, 7918, 354, 63, 43.2381, 120.434, 138.836, nil},
{74, 2, 3619, 6243, 10282, 3466, 7903, 334, 48, 40.2629, 102.955, 138.836, nil},
{74, 4, 4524, 1, 10282, 0, 7143, 354, 63, 43.2381, 120.434, 138.836, nil},
{74, 8, 3186, 1, 8225, 7809, 6368, 158, 41, 36.7018, 104.723, 128.423, nil},
{75, 1, 4652, 8025, 10635, 0, 8219, 392, 68, 43.9047, 122.226, 142.878, nil},
{75, 2, 3722, 6420, 10635, 3561, 8204, 370, 53, 40.9193, 104.52, 142.878, nil},
{75, 4, 4652, 1, 10635, 0, 7415, 392, 68, 43.9047, 122.226, 142.878, nil},
{75, 8, 3256, 5617, 8508, 7981, 6610, 175, 45, 37.268, 108.832, 132.162, nil},
{76, 1, 4781, 8247, 11001, 0, 8520, 432, 74, 44.5713, 124.018, 147.038, nil},
{76, 2, 3825, 6602, 11001, 3643, 8503, 408, 57, 41.5757, 106.085, 147.038, nil},
{76, 4, 4781, 1, 11001, 0, 7686, 432, 74, 44.5713, 124.018, 147.038, nil},
{76, 8, 3367, 1, 8800, 8139, 6851, 193, 49, 37.8342, 112.941, 136.01, nil},
{77, 1, 4916, 8480, 11379, 0, 8822, 478, 81, 45.2379, 125.81, 151.319, nil},
{77, 2, 3933, 6784, 11379, 3725, 8803, 452, 62, 42.2321, 107.65, 151.319, nil},
{77, 4, 4916, 1, 11379, 0, 7958, 478, 81, 45.2379, 125.81, 151.319, nil},
{77, 8, 3462, 1, 9103, 8313, 7094, 214, 54, 38.4004, 117.05, 139.97, nil},
{78, 1, 5052, 8715, 11770, 0, 9124, 528, 88, 45.9045, 127.602, 155.724, nil},
{78, 2, 4042, 6972, 11770, 3809, 9104, 500, 68, 42.8885, 109.215, 155.724, nil},
{78, 4, 5052, 1, 11770, 0, 8230, 528, 88, 45.9045, 127.602, 155.724, nil},
{78, 8, 3558, 1, 9416, 8459, 7335, 236, 58, 38.9666, 121.159, 144.045, nil},
{79, 1, 5194, 8960, 12175, 0, 9426, 582, 95, 46.5711, 129.394, 160.258, nil},
{79, 2, 4155, 7167, 12175, 3893, 9405, 550, 74, 43.5449, 110.78, 160.258, nil},
{79, 4, 5194, 1, 12175, 0, 8503, 582, 95, 46.5711, 129.394, 160.258, nil},
{79, 8, 3658, 1, 9740, 8636, 7579, 260, 64, 39.5328, 125.268, 148.239, nil},
{80, 1, 5342, 9215, 12600, 0, 9729, 642, 103, 47.2377, 131.186, 164.924, nil},
{80, 2, 4274, 7373, 12600, 3994, 9706, 608, 80, 44.2013, 112.345, 164.924, nil},
{80, 4, 5342, 1, 12600, 0, 8776, 642, 103, 47.2377, 131.186, 164.924, nil},
{80, 8, 3739, 1, 10080, 8814, 7822, 287, 69, 40.099, 129.377, 152.555, nil},
}
// Classes to process
classes := []int{1, 2, 4, 8}
// Columns to process
columns := []string{"BaseHP0", "BaseHP1", "BaseHP2", "BaseMana", "BaseArmor", "AttackPower", "RangedAttackPower", "DamageBase", "DamageExp1", "DamageExp2"}
// Fit polynomials for each class and each attribute
classCoefficients := make(map[int]map[string][]float64)
for _, class := range classes {
classData := filterDataByClass(data, class)
// Store coefficients for each column for this class
coefficients := make(map[string][]float64)
for _, column := range columns {
levels := make([]float64, 0)
values := make([]float64, 0)
for _, d := range classData {
var value float64
switch column {
case "BaseHP0":
value = d.BaseHP0
case "BaseHP1":
value = d.BaseHP1
case "BaseHP2":
value = d.BaseHP2
case "BaseMana":
value = d.BaseMana
case "BaseArmor":
value = d.BaseArmor
case "AttackPower":
value = d.AttackPower
case "RangedAttackPower":
value = d.RangedAttackPower
case "DamageBase":
value = d.DamageBase
case "DamageExp1":
value = d.DamageExp1
case "DamageExp2":
value = d.DamageExp2
}
if value > 1 {
levels = append(levels, d.Level)
values = append(values, value)
}
}
if len(levels) == 0 || len(values) == 0 {
continue // skip if no valid data
}
// Perform polynomial regression (degree 2)
coeffs, err := polyFit(levels, values, 2)
if err != nil {
log.Fatalf("Failed to calculate polynomial fit for class %d, %s: %v", class, column, err)
}
coefficients[column] = coeffs
}
classCoefficients[class] = coefficients
}
// Generate SQL insert statements for levels 81 to 100
fmt.Println("INSERT INTO mythicplus_classlevelstats (class, level, basehp0, basehp1, basehp2, basemana, basearmor, attackpower, rangedattackpower, damage_base, damage_exp1, damage_exp2) VALUES")
for _, class := range classes {
for level := 81.0; level <= 100; level++ {
predictedValues := calculateScaledValues(level, classCoefficients[class])
fmt.Printf("(%d, %.0f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.4f, %.4f, %.4f),\n",
class, level,
predictedValues["BaseHP0"], predictedValues["BaseHP1"], predictedValues["BaseHP2"], predictedValues["BaseMana"],
predictedValues["BaseArmor"], predictedValues["AttackPower"], predictedValues["RangedAttackPower"],
predictedValues["DamageBase"], predictedValues["DamageExp1"], predictedValues["DamageExp2"])
}
}
}
// filterDataByClass filters the dataset by class
func filterDataByClass(data []Data, class int) []Data {
var filtered []Data
for _, d := range data {
if d.Class == class {
filtered = append(filtered, d)
}
}
return filtered
}
// polyFit performs a polynomial fit of the given degree on the data.
func polyFit(x, y []float64, degree int) ([]float64, error) {
// Create the Vandermonde matrix
n := len(x)
vander := mat.NewDense(n, degree+1, nil)
for i := range x {
for j := 0; j <= degree; j++ {
vander.Set(i, j, math.Pow(x[i], float64(j)))
}
}
// Create a vector for the output values
yVec := mat.NewVecDense(len(y), y)
// Solve the least squares problem
var coeffs mat.VecDense
err := coeffs.SolveVec(vander, yVec)
if err != nil {
return nil, err
}
return coeffs.RawVector().Data, nil
}
// calculateScaledValues takes a level and coefficient map to return scaled values
func calculateScaledValues(level float64, coeffs map[string][]float64) map[string]float64 {
values := make(map[string]float64)
for column, c := range coeffs {
// Use the polynomial formula: ax^2 + bx + c
values[column] = c[2]*math.Pow(level, 2) + c[1]*level + c[0]
}
return values
}

39202
scripts/pets/creatures.json Normal file

File diff suppressed because it is too large Load Diff

153
scripts/pets/main.go Normal file
View File

@@ -0,0 +1,153 @@
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"math"
"os"
)
type Creature struct {
CreatureEntry int `json:"creature_entry"`
Level int `json:"level"`
HP int `json:"hp"`
Mana int `json:"mana"`
Armor int `json:"armor"`
Str int `json:"str"`
Agi int `json:"agi"`
Sta int `json:"sta"`
Int int `json:"inte"`
Spi int `json:"spi"`
MinDmg int `json:"min_dmg"`
MaxDmg int `json:"max_dmg"`
}
// RegressionResult stores the slope and intercept of a linear regression
type RegressionResult struct {
Slope float64
Intercept float64
}
// LinearRegression performs a simple linear regression on the given data points
func LinearRegression(levels []float64, stats []float64) RegressionResult {
n := len(levels)
if n != len(stats) {
panic("levels and stats arrays must have the same length")
}
// Calculate averages
var sumX, sumY, sumXY, sumXX float64
for i := 0; i < n; i++ {
sumX += levels[i]
sumY += stats[i]
sumXY += levels[i] * stats[i]
sumXX += levels[i] * levels[i]
}
avgX := sumX / float64(n)
avgY := sumY / float64(n)
// Calculate slope and intercept
slope := (sumXY - float64(n)*avgX*avgY) / (sumXX - float64(n)*avgX*avgX)
intercept := avgY - slope*avgX
return RegressionResult{
Slope: slope,
Intercept: intercept,
}
}
// PredictStat returns the predicted stat value for a given level based on a linear regression model
func PredictStat(reg RegressionResult, level int) float64 {
return reg.Intercept + reg.Slope*float64(level)
}
// ScaleCreatures generates stats for levels 81 to 87 based on linear regression and outputs SQL statements
func ScaleCreatures(creatures []Creature) {
creatureMap := make(map[int][]Creature)
// Group creatures by their creature_entry
for _, creature := range creatures {
creatureMap[creature.CreatureEntry] = append(creatureMap[creature.CreatureEntry], creature)
}
// Process each creature entry independently
for creatureEntry, creatureList := range creatureMap {
var levels []float64
var hp, mana, armor, str, agi, sta, inte, spi, minDmg, maxDmg []float64
// Collect data for linear regression from levels 1 to 80
for _, creature := range creatureList {
if creature.Level <= 80 {
levels = append(levels, float64(creature.Level))
hp = append(hp, float64(creature.HP))
mana = append(mana, float64(creature.Mana))
armor = append(armor, float64(creature.Armor))
str = append(str, float64(creature.Str))
agi = append(agi, float64(creature.Agi))
sta = append(sta, float64(creature.Sta))
inte = append(inte, float64(creature.Int))
spi = append(spi, float64(creature.Spi))
minDmg = append(minDmg, float64(creature.MinDmg))
maxDmg = append(maxDmg, float64(creature.MaxDmg))
}
}
// Perform linear regression on each stat
hpReg := LinearRegression(levels, hp)
manaReg := LinearRegression(levels, mana)
armorReg := LinearRegression(levels, armor)
strReg := LinearRegression(levels, str)
agiReg := LinearRegression(levels, agi)
staReg := LinearRegression(levels, sta)
inteReg := LinearRegression(levels, inte)
spiReg := LinearRegression(levels, spi)
minDmgReg := LinearRegression(levels, minDmg)
maxDmgReg := LinearRegression(levels, maxDmg)
// Generate SQL insert statements for levels 81 to 87 for each creature_entry
for level := 81; level <= 87; level++ {
newCreature := Creature{
CreatureEntry: creatureEntry, // Process per creature_entry
Level: level,
HP: int(math.Round(math.Max(PredictStat(hpReg, level), 1))), // Round and ensure positive
Mana: int(math.Round(math.Max(PredictStat(manaReg, level), 1))),
Armor: int(math.Round(math.Max(PredictStat(armorReg, level), 1))), // Armor scaling
Str: int(math.Round(math.Max(PredictStat(strReg, level), 1))),
Agi: int(math.Round(math.Max(PredictStat(agiReg, level), 1))),
Sta: int(math.Round(math.Max(PredictStat(staReg, level), 1))),
Int: int(math.Round(math.Max(PredictStat(inteReg, level), 1))),
Spi: int(math.Round(math.Max(PredictStat(spiReg, level), 1))),
MinDmg: int(math.Round(math.Max(PredictStat(minDmgReg, level), 1))),
MaxDmg: int(math.Round(math.Max(PredictStat(maxDmgReg, level), 1))),
}
// Output SQL INSERT statement for each creature_entry and level
fmt.Printf("INSERT INTO pet_levelstats (creature_entry, level, hp, mana, armor, str, agi, sta, inte, spi, min_dmg, max_dmg) "+
"VALUES (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d);\n",
newCreature.CreatureEntry, newCreature.Level, newCreature.HP, newCreature.Mana, newCreature.Armor,
newCreature.Str, newCreature.Agi, newCreature.Sta, newCreature.Int, newCreature.Spi,
newCreature.MinDmg, newCreature.MaxDmg)
}
}
}
func main() {
// Read the JSON file
file, err := os.Open("creatures.json")
if err != nil {
fmt.Println("Error reading file:", err)
return
}
defer file.Close()
byteValue, _ := ioutil.ReadAll(file)
// Parse the JSON data
var creatures []Creature
json.Unmarshal(byteValue, &creatures)
// Scale creatures and generate SQL for levels 81 to 87
ScaleCreatures(creatures)
}

View File

@@ -1,60 +1,52 @@
#include "ScriptMgr.h"
#include "MpDataStore.h"
#include "MpLogger.h"
#include "MythicPlus.h"
#include "MapMgr.h"
#include "ScriptMgr.h"
class MythicPlus_AllCreatureScript : public AllCreatureScript
{
public:
MythicPlus_AllCreatureScript() : AllCreatureScript("MythicPlus_AllCreatureScript")
{
MythicPlus_AllCreatureScript() : AllCreatureScript("MythicPlus_AllCreatureScript") {}
}
// void OnBeforeCreatureSelectLevel(const CreatureTemplate* /*creatureTemplate*/, Creature* creature, uint8& level) override
// {
// }
// void OnAllCreatureUpdate(Creature* creature, uint32 diff) override
// {
// }
// When a new creature is added into a mythic+ map add it to the list of creatures to scale later.
void OnCreatureAddWorld(Creature* creature) override
{
// if (creature->GetMap()->IsDungeon() || creature->GetMap()->IsRaid())
// LOG_DEBUG("module.MythicPlus",
// "MythicPlus_AllCreatureScript::OnCreatureAddWorld(): {} ({})",
// creature->GetName(),
// creature->GetLevel()
// );
Map* map = creature->GetMap();
if (!sMythicPlus->IsMapEligible(map)) {
return;
}
if (!sMythicPlus->IsCreatureEligible(creature)) {
return;
}
// if we have instance data about zone then just scale the creature otherwise add to be scaled once we do.
MpInstanceData* instanceData = sMpDataStore->GetInstanceData(map->GetId(), map->GetInstanceId());
if(instanceData) {
sMythicPlus->AddScaledCreature(creature, instanceData);
} else {
sMythicPlus->AddCreatureForScaling(creature);
}
}
// Cleanup the creature from custom data used for mythic+ mod
void OnCreatureRemoveWorld(Creature* creature) override
{
// if (creature->GetMap()->IsDungeon() || creature->GetMap()->IsRaid())
// LOG_DEBUG("module.MythicPlus",
// "MythicPlus_AllCreatureScript::OnCreatureRemoveWorld(): {} ({})",
// creature->GetName(),
// creature->GetLevel()
// );
// // remove the creature from the map's tracking list, if present
// sMythicPlus->RemoveCreatureFromMapData(creature);
sMythicPlus->RemoveCreature(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.MythicPlus",
// "MythicPlus_AllCreatureScript::OnAllCreatureUpdate(): Creature {} ({}) is reset to its original stats.",
// creature->GetName(),
// creature->GetLevel()
// );
// // Update the map's level if it is out of date
// sMythicPlus->UpdateMapLevelIfNeeded(creature->GetMap());
// ModifyCreatureAttributes(creature);
// }
}
};
void AddAllCreatureScripts()
void Add_MP_AllCreatureScripts()
{
MpLogger::debug("Add_MP_AllCreatureScripts");
new MythicPlus_AllCreatureScript();
}

View File

@@ -1,9 +1,11 @@
#include "ScriptMgr.h"
#include "Log.h"
#include "Player.h"
#include "MythicPlus.h"
#include "Chat.h"
#include "MapMgr.h"
#include "MpLogger.h"
#include "Map.h"
#include "MpDataStore.h"
#include "MythicPlus.h"
#include "Player.h"
#include "ScriptMgr.h"
class MythicPlus_AllMapScript : public AllMapScript
{
@@ -12,36 +14,109 @@ public:
{
}
void OnCreateMap(Map* map)
{
// LOG_DEBUG("module.MythicPlus", "MythicPlus_AllMapScript::OnCreateMap(): {}", map->GetMapName());
// if (!map->IsDungeon() && !map->IsRaid())
// return;
}
void OnCreateMap(Map* map) { }
/**
* When a player enters the map check it needs to set up the instance data
*/
void OnPlayerEnterAll(Map* map, Player* player)
{
// LOG_DEBUG("module.MythicPlus", "MythicPlus_AllMapScript::OnPlayerEnterAll(): {}", map->GetMapName());
if (!sMythicPlus->IsMapEligible(map)) {
return;
}
// if (!map->IsDungeon() && !map->IsRaid())
// return;
if(!sMythicPlus->IsDifficultySet(player)) {
return;
}
// if (player->IsGameMaster())
// return;
Group* group = player->GetGroup();
if (group) {
MpLogger::debug("Player {} entered map {} in groupLeader {}", player->GetName(), map->GetMapName(), group->GetLeaderName());
} else {
return;
}
// if there is not any group data for this group then just bail
const MpGroupData* groupData = sMpDataStore->GetGroupData(group->GetGUID());
if (!groupData) {
return;
}
// Check if we already have mythic instance data set for this map and group
MpInstanceData* existingData = sMpDataStore->GetInstanceData(map->GetId(), map->GetInstanceId());
if (existingData) {
if(player->GetName() == group->GetLeaderName()) {
MpLogger::debug("Instance data already set for Map: {} InstanceId: {} for GroupLeader: {} ",
map->GetMapName(),
map->GetInstanceId(),
group->GetLeaderName()
);
}
return;
}
MpInstanceData instanceData;
switch(groupData->difficulty) {
case MP_DIFFICULTY_MYTHIC:
instanceData.boss = sMythicPlus->mythicBossModifiers;
instanceData.creature = sMythicPlus->mythicDungeonModifiers;
instanceData.itemRewards = sMythicPlus->EnableItemRewards;
instanceData.deathLimits = sMythicPlus->mythicDeathAllowance;
instanceData.itemOffset = sMythicPlus->mythicItemOffset;
break;
case MP_DIFFICULTY_LEGENDARY:
instanceData.boss = sMythicPlus->legendaryBossModifiers;
instanceData.creature = sMythicPlus->legendaryDungeonModifiers;
instanceData.itemRewards = sMythicPlus->EnableItemRewards;
instanceData.deathLimits = sMythicPlus->legendaryDeathAllowance;
instanceData.itemOffset = sMythicPlus->legendaryItemOffset;
break;
case MP_DIFFICULTY_ASCENDANT:
instanceData.boss = sMythicPlus->ascendantBossModifiers;
instanceData.creature = sMythicPlus->ascendantDungeonModifiers;
instanceData.itemRewards = sMythicPlus->EnableItemRewards;
instanceData.deathLimits = sMythicPlus->ascendantDeathAllowance;
instanceData.itemOffset = sMythicPlus->ascendantItemOffset;
break;
default:
MpLogger::debug("No difficulty set for group {}", group->GetGUID().GetCounter());
return;
}
instanceData.difficulty = groupData->difficulty;
// Attempt to cast map to InstanceMap, making sure it is not null
instanceData.instance = dynamic_cast<InstanceMap*>(sMapMgr->FindMap(map->GetId(), map->GetInstanceId()));
if (!instanceData.instance)
{
MpLogger::error("Failed to find InstanceMap for map ID {} and instance ID {}.", map->GetId(), map->GetInstanceId());
return;
}
MpLogger::debug("Setting up instance data for group {} for map {} instance {} data {}",
group->GetGUID().GetCounter(),
map->GetMapName(),
map->GetInstanceId(),
instanceData.ToString()
);
sMpDataStore->AddInstanceData(map->GetId(), map->GetInstanceId(), instanceData);
// Once we have instance data set we can scale the remaining characters in our instance
sMythicPlus->ScaleRemaining(player, &instanceData);
}
void OnPlayerLeaveAll(Map* map, Player* player)
// When an instance is destroyed remove the instance data from the data store
virtual void OnDestroyInstance(MapInstanced* /*mapInstanced*/, Map* map)
{
// LOG_DEBUG("module.MythicPlus", "MythicPlus_AllMapScript::OnPlayerLeaveAll(): {}", map->GetMapName());
// if (!sMythicPlus->EnableGlobal)
// return;
}
if (!sMythicPlus->IsMapEligible(map)) {
return;
}
sMpDataStore->RemoveInstanceData(map->GetId(), map->GetInstanceId());
}
};
void AddAllMapScripts()
void Add_MP_AllMapScripts()
{
MpLogger::debug("Add_MP_AllMapScripts()");
new MythicPlus_AllMapScript();
}
}

View File

@@ -1,16 +1,214 @@
#include "Chat.h"
#include "MpDataStore.h"
#include "MythicPlus.h"
#include "MpDataStore.h"
#include "MpLogger.h"
#include "Player.h"
#include "ScriptMgr.h"
using namespace Acore::ChatCommands;
// make sure this is the new way to do this, i think it's the old busted shit
class MythicPlus_CommandScript : public CommandScript
{
public:
MythicPlus_CommandScript() : CommandScript("MythicPlus_CommandScript")
{
}
ChatCommandTable GetCommands() const override
{
static ChatCommandTable commandTableMain =
{
{"", HandleHelp, SEC_PLAYER, Console::No},
{"status", HandleStatus, SEC_PLAYER, Console::No},
{"showstats", HandleDebug, SEC_PLAYER, Console::No},
// {"mythic",HandleMythic, SEC_PLAYER, Console::No},
// {"legendary",HandleLegendary, SEC_PLAYER, Console::No},
// {"ascendant",HandleAscendant, SEC_PLAYER, Console::No},
{"set", HandleSetDifficulty, SEC_PLAYER, Console::No},
{"disable", HandleDisable, SEC_ADMINISTRATOR, Console::Yes},
{"enable", HandleEnable, SEC_ADMINISTRATOR, Console::Yes}
};
static ChatCommandTable commandTable =
{
{"mp", commandTableMain},
{"mythicplus", commandTableMain},
{"mp debug", HandleDebug, SEC_PLAYER, Console::No}
};
return commandTable;
}
static bool HandleHelp(ChatHandler* handler, const std::vector<std::string>& /*args*/)
{
std::string helpText = "Mythic+ Commands:\n"
" .mp status - show current global settings of Mythic+ mod\n"
" .mp set [mythic,legendary,ascendant] - Set Mythic+ difficulty in current beta only supports mythic.\n"
" .mp [enable,disable] - enable or disable this mod\n"
" .mp - Show this help message\n";
handler->PSendSysMessage(helpText);
return true;
}
static bool HandleDebug(ChatHandler* handler, const std::vector<std::string>& args)
{
Creature* target = handler->getSelectedCreature();
if(!target) {
handler->PSendSysMessage("You must select a creature to debug.");
return true;
}
handler->PSendSysMessage(LANG_NPCINFO_LEVEL, target->GetLevel());
handler->PSendSysMessage(LANG_NPCINFO_HEALTH, target->GetCreateHealth(), target->GetMaxHealth(), target->GetHealth());
handler->PSendSysMessage("WeaponDmg Main {} - {}",
target->GetWeaponDamageRange(BASE_ATTACK, MINDAMAGE),
target->GetWeaponDamageRange(BASE_ATTACK, MAXDAMAGE)
);
handler->PSendSysMessage("WeaponDmg Range {} - {}",
target->GetWeaponDamageRange(RANGED_ATTACK, MINDAMAGE),
target->GetWeaponDamageRange(RANGED_ATTACK, MAXDAMAGE)
);
handler->PSendSysMessage("WeaponDmg Offhand {} - {}",
target->GetWeaponDamageRange(OFF_ATTACK, MINDAMAGE),
target->GetWeaponDamageRange(OFF_ATTACK, MAXDAMAGE)
);
handler->PSendSysMessage("Attack Power Main {}", target->GetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE));
handler->PSendSysMessage("Attack Power Ranged {}", target->GetModifierValue(UNIT_MOD_ATTACK_POWER_RANGED, BASE_VALUE));
handler->PSendSysMessage(LANG_NPCINFO_ARMOR, target->GetArmor());
handler->PSendSysMessage("Damage Modifier {}", target->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, BASE_VALUE));
return true;
}
// sets the difficluty for the group
static bool HandleSetDifficulty(ChatHandler* handler, const std::vector<std::string>& args)
{
Player* player = handler->GetSession()->GetPlayer();
Group* group = player->GetGroup();
if (!group) {
MpLogger::debug("HandleSetMythic() No Group for player: {}", player->GetName());
handler->PSendSysMessage("|cFFFF0000 You must be in a group to be able to set a Mythic+ difficulty.");
return true;
}
if (args.empty()) {
handler->PSendSysMessage("|cFFFF0000 You must specify a difficulty level. Expected values are 'mythic', 'legendary', or 'ascendant'.");
return true;
}
std::string difficulty = args[0];
// if(!sMythicPlus->IsDifficultyEnabled(difficulty)) {
// handler->PSendSysMessage("|cFFFF0000 The difficulty level you have selected is not enabled.");
// return true;
// }
if (!group->IsLeader(player->GetGUID())) {
handler->PSendSysMessage("|cFFFF0000 You must be the group leader to set a Mythic+ difficulty.");
return true;
}
if (player->GetMap()->IsDungeon()) {
player->ResetInstances(player->GetGUID(), INSTANCE_RESET_CHANGE_DIFFICULTY, false);
player->SendResetInstanceSuccess(player->GetMap()->GetId());
return true;
}
if (difficulty == "mythic") {
sMpDataStore->AddGroupData(group, MpGroupData(group, MP_DIFFICULTY_MYTHIC, 0));
}
else if (difficulty == "legendary") {
sMpDataStore->AddGroupData(group,MpGroupData(group, MP_DIFFICULTY_LEGENDARY, 0));
}
else if (difficulty == "ascendant") {
sMpDataStore->AddGroupData(group, MpGroupData(group, MP_DIFFICULTY_ASCENDANT, 0));
}
else {
handler->PSendSysMessage("|cFFFF0000 Invalid difficulty level. Expected values are 'mythic', 'legendary', or 'ascendant'.");
return true;
}
handler->PSendSysMessage("Mythic+ difficulty set to: " + difficulty);
MpLogger::debug("HandleSetMythic() Set difficulty player: {} {}", player->GetName(), difficulty);
return true;
}
static bool HandleMythic(ChatHandler* handler, const std::vector<std::string>& /*args*/)
{
return HandleSetDifficulty(handler, {"mythic"});
}
static bool HandleLegendary(ChatHandler* handler, const std::vector<std::string>& /*args*/)
{
return HandleSetDifficulty(handler, {"legendary"});
}
static bool HandleAscendant(ChatHandler* handler, const std::vector<std::string>& /*args*/)
{
return HandleSetDifficulty(handler, {"ascendant"});
}
static bool HandleStatus(ChatHandler* handler)
{
Player* player = handler->GetPlayer();
std::string status = Acore::StringFormat(
"Mythic+ Status:\n"
" Mythic+ Enabled: %s\n"
" Mythic+ Item Rewards: %s\n"
" Mythic+ DeathLimits: %s\n",
sMythicPlus->Enabled ? "Yes" : "No",
sMythicPlus->EnableItemRewards ? "Yes" : "No",
sMythicPlus->EnableDeathLimits ? "Yes" : "No");
if (player->GetGroup()) {
auto groupData = sMpDataStore->GetGroupData(player->GetGroup()->GetGUID());
if (groupData) {
status += Acore::StringFormat(
" Group Difficulty: %u\n"
" Group Deaths: %u\n",
groupData->difficulty,
groupData->deaths);
} else {
status += " Group Difficulty: Not Set\n";
}
}
handler->PSendSysMessage(status);
return true;
}
static bool HandleDisable(ChatHandler* handler)
{
MpLogger::debug("HandleDisable()");
sMythicPlus->Enabled = false;
handler->SendSysMessage("Mythic+ mod has been disabled.");
return true;
}
static bool HandleEnable(ChatHandler* handler)
{
MpLogger::debug("HandleEnable()");
sMythicPlus->Enabled = false;
handler->SendSysMessage("Mythic+ mod has been enabled.");
return true;
}
};
void AddCommandScripts()
void Add_MP_CommandScripts()
{
MpLogger::debug("Add_MP_CommandScripts()");
new MythicPlus_CommandScript();
}

View File

@@ -1,5 +1,8 @@
#include "MpLogger.h"
#include "MythicPlus.h"
#include "ScriptMgr.h"
#include "Player.h"
#include "Map.h"
class MythicPlus_GlobalScript : public GlobalScript
{
@@ -7,25 +10,65 @@ public:
MythicPlus_GlobalScript() : GlobalScript("MythicPlus_GlobalScript") { }
void OnAfterUpdateEncounterState(Map* map, EncounterCreditType type, uint32 /*creditEntry*/, Unit* /*source*/, Difficulty /*difficulty_fixed*/, DungeonEncounterList const* /*encounters*/, uint32 /*dungeonCompleted*/, bool updated) override
{
// This adds the mythic+ item scaling to the loot table for enemies
void OnBeforeDropAddItem(Player const* player, Loot& loot, bool /*canRate*/, uint16 /*lootMode*/, LootStoreItem* LootStoreItem, LootStore const& store) override {
}
if(LootStoreItem->itemid == 0) {
return;
}
/**
* This is used to provide better rewards to players when they complete a dungeon.
* The rewards are scaled up versions of the original drop.
* @brief Called after a player has looted an item from a creature.
*/
void OnBeforeDropAddItem(Player const* player, Loot& loot, bool canRate, uint16 lootMode, LootStoreItem* LootStoreItem, LootStore const& store) override
{
// Not playing on mythic+ difficulty skip doing loot
if(!sMythicPlus->IsDifficultySet(player)) {
return;
}
// Not on an eligible map skip doing loot
Map* map = player->GetMap();
if (!sMythicPlus->IsMapEligible(map)) {
return;
}
MpInstanceData* mythicSettings = sMpDataStore->GetInstanceData(map->GetId(), map->GetInstanceId());
// if there are not mythic settings set for this group and map skip
if (!mythicSettings) {
MpLogger::warn("No mythic settings found for map {} instance {}", map->GetMapName(), map->GetInstanceId());
return;
}
// if the item rewards are disabled skip
if (mythicSettings->itemRewards == false ) {
return;
}
// get the item to scale up
ItemTemplate const* origItem = sObjectMgr->GetItemTemplate(LootStoreItem->itemid);
if (!origItem) {
MpLogger::warn("Item not found for itemid {} in OnBeforeDropAddItem()", LootStoreItem->itemid);
return;
}
uint32 newItemId = origItem->ItemId + mythicSettings->itemOffset;
ItemTemplate const* newItemTempl = sObjectMgr->GetItemTemplate(newItemId);
if(!newItemTempl) {
MpLogger::warn("New Loot Item not found for itemid {} original item: {} ({})", newItemId, origItem->Name1, origItem->ItemId);
return;
}
LootStoreItem->itemid = newItemId;
// Revalidate the LootStoreItem to ensure consistency
if (!LootStoreItem->IsValid(store, newItemId)) {
MpLogger::info("LootStoreItem is not valid after updating itemid to {} in OnBeforeDropAddItem()", newItemId);
return;
}
}
};
void AddGlobalScripts()
void Add_MP_GlobalScripts()
{
MpLogger::debug("Add_MP_GlobalScripts()");
new MythicPlus_GlobalScript();
}

View File

@@ -1,6 +1,8 @@
#include "MpDataStore.h"
#include "MpLogger.h"
#include "ScriptMgr.h"
#include "MythicPlus.h"
#include "Group.h"
// this handles updating custom group difficulties used in auto balancing mobs and
// scripts that enable buffs on mobs randomly
@@ -18,17 +20,16 @@ class MythicPlus_GroupScript : public GroupScript
return;
}
// sMpDataStore->AddGroupData(group->GetGUID(), gd);
}
void OnDisband(Group* group) override {
if (!group) {
return;
}
sMpDataStore->RemoveGroupData(group);
}
};
void AddGroupScripts()
void Add_MP_GroupScripts()
{
MpLogger::debug("Add_MP_GroupScripts()");
new MythicPlus_GroupScript();
}
}

194
src/MpDataStore.cpp Normal file
View File

@@ -0,0 +1,194 @@
#include "CharacterDatabase.h"
#include "MpDataStore.h"
#include "MpLogger.h"
// Adds an entry for the group difficult to memory and updats database
void MpDataStore::AddGroupData(Group *group, MpGroupData groupData) {
ObjectGuid guid = group->GetGUID();
if (!guid) {
MpLogger::error("AddGroupData called with invalid group GUID");
return;
}
if (!groupData.difficulty) {
MpLogger::error("AddGroupData called with invalid difficulty");
return;
}
// if we already have data override it
if (_groupData->contains(guid)) {
_groupData->at(guid) = groupData;
} else {
_groupData->emplace(guid, groupData);
}
MpLogger::debug("Add Group difficulty for group {} to {}", guid.GetCounter());
CharacterDatabase.Execute("REPLACE INTO group_difficulty (guid, difficulty) VALUES ({},{}) ",
guid.GetCounter(),
groupData.difficulty
);
}
MpGroupData* MpDataStore::GetGroupData(Group* group) {
if (auto it = _groupData->find(group->GetGUID()); it != _groupData->end()) {
return &(it->second);
}
return nullptr;
}
/**
* This keeps track of instance keys (mapId, instanceId) for a group this can be used to
* reset instance settings in the instanceData store when a difficulty is changed by the group leader.
*/
void MpDataStore::PushGroupInstanceKey(Group *group, uint32 mapId, uint32 instanceId) {
ObjectGuid guid = group->GetGUID();
if (!guid) {
MpLogger::error("PushGroupInstanceKey called with invalid group GUID");
return;
}
if (!mapId || !instanceId) {
MpLogger::error("PushGroupInstanceKey called with invalid mapId or instanceId");
return;
}
if (!_groupData->contains(guid)) {
MpLogger::error("PushGroupInstanceKey called with invalid group GUID");
return;
}
_groupData->at(guid).instanceDataKeys.push_back(GetInstanceDataKey(mapId, instanceId));
}
// This clears out any group data from memory and the database
void MpDataStore::RemoveGroupData(Group *group) {
MpLogger::debug("RemoveGroupData for group {}", group->GetGUID().GetCounter());
_groupData->erase(group->GetGUID());
CharacterDatabase.Execute("DELETE FROM group_difficulty WHERE guid = {}) ", group->GetGUID().GetCounter());
}
void MpDataStore::AddPlayerData(ObjectGuid guid, MpPlayerData pd) {
MpLogger::debug("AddPlayerData for player {}", guid.GetCounter());
_playerData->emplace(guid, pd);
}
void MpDataStore::RemovePlayerData(ObjectGuid guid) {
MpLogger::debug("RemovePlayerData for player {}", guid.GetCounter());
_playerData->erase(guid);
}
void MpDataStore::AddInstanceData(uint32 mapId, uint32 instanceId, MpInstanceData instanceSettings) {
_instanceData->emplace(GetInstanceDataKey(mapId, instanceId), instanceSettings);
}
MpInstanceData* MpDataStore::GetInstanceData(uint32 mapId, uint32 instanceId) {
if (!_instanceData->contains(GetInstanceDataKey(mapId, instanceId))) {
return nullptr;
}
return &_instanceData->at(GetInstanceDataKey(mapId, instanceId));
}
void MpDataStore::RemoveInstanceData(uint32 mapId, uint32 instanceId) {
_instanceData->erase(GetInstanceDataKey(mapId, instanceId));
}
void MpDataStore::AddCreatureData(ObjectGuid guid, MpCreatureData creatureData) {
MpLogger::debug("AddInstanceCreatureData for creature {}", guid.GetCounter());
_instanceCreatureData->emplace(guid, creatureData);
}
MpCreatureData* MpDataStore::GetCreatureData(ObjectGuid guid) {
if (!_instanceCreatureData->contains(guid)) {
return nullptr;
}
return &_instanceCreatureData->at(guid);
}
std::vector<MpCreatureData*> MpDataStore::GetUnscaledCreatures(uint32 mapId, uint32 instanceId) {
std::vector<MpCreatureData*> creatures;
for (auto& [guid, creatureData] : *_instanceCreatureData) {
Creature* creature = creatureData.creature;
if (creature->GetMapId() == mapId && creature->GetInstanceId() == instanceId && !creatureData.IsScaled()) {
creatures.push_back(&creatureData);
}
}
return creatures;
}
void MpDataStore::RemoveCreatureData(ObjectGuid guid) {
// MpLogger::debug("RemoveInstanceCreatureData data for creature {}", guid.GetCounter());
_instanceCreatureData->erase(guid);
}
MpScaleFactor MpDataStore::GetScaleFactor(int32 mapId, int32 difficulty) const {
auto key = GetScaleFactorKey(mapId, difficulty);
if (_scaleFactors->contains(key)) {
return _scaleFactors->at(key);
}
return MpScaleFactor{
.dmgBonus = 3,
.healthBonus = 2,
.maxDamageBonus = 30
};
}
int32 MpDataStore::GetHealthScaleFactor(int32 mapId, int32 difficulty) const {
return GetScaleFactor(mapId, difficulty).healthBonus;
}
int32 MpDataStore::GetDamageScaleFactor(int32 mapId, int32 difficulty) const {
return GetScaleFactor(mapId, difficulty).dmgBonus;
}
int32 MpDataStore::GetSpellScaleFactor(int32 mapId, int32 difficulty) const {
return GetScaleFactor(mapId, difficulty).spellBonus;
}
int32 MpDataStore::GetMaxDamageScaleFactor(int32 mapId, int32 difficulty) const {
return GetScaleFactor(mapId, difficulty).maxDamageBonus;
}
int32 MpDataStore::LoadScaleFactors() {
// 0 1 2 3 4 5
QueryResult result = WorldDatabase.Query("SELECT mapId, dmg_bonus, spell_bonus, hp_bonus, difficulty, max FROM mythic_plus_scale_factors");
if (!result) {
MpLogger::error("Failed to load mythic scale factors from database");
return 0;
}
do {
Field* fields = result->Fetch();
uint32 mapId = fields[0].Get<uint32>();
int32 damageBonus = fields[1].Get<int32>();
int32 spellBonus = fields[2].Get<int32>();
int32 healthBonus = fields[3].Get<int32>();
int32 difficulty = fields[4].Get<int32>();
int32 maxDamageBonus = fields[5].Get<int32>();
MpScaleFactor scaleFactor = {
.dmgBonus = damageBonus,
.healthBonus = healthBonus,
.spellBonus = spellBonus,
.maxDamageBonus = maxDamageBonus
};
_mutableScaleFactors->emplace(GetScaleFactorKey(mapId, difficulty), scaleFactor);
} while (result->NextRow());
// move to const map one loaded so can not be changed after
_scaleFactors = std::move(_mutableScaleFactors);
return int32(_scaleFactors->size());
}

264
src/MpDataStore.h Normal file
View File

@@ -0,0 +1,264 @@
#ifndef MYTHICPLUS_DATASTORE_H
#define MYTHICPLUS_DATASTORE_H
#include "Creature.h"
#include "Group.h"
#include "MapMgr.h"
#include "Player.h"
#include "ObjectGuid.h"
#include <unordered_map>
#include <map>
#include <string>
#include <vector>
#include <memory>
enum MpDifficulty {
MP_DIFFICULTY_NORMAL = 0,
MP_DIFFICULTY_HEROIC = 1,
MP_DIFFICULTY_EPIC = 2,
MP_DIFFICULTY_MYTHIC = 3,
MP_DIFFICULTY_LEGENDARY = 4,
MP_DIFFICULTY_ASCENDANT = 5
};
struct MpGroupData
{
Group* group;
MpDifficulty difficulty;
uint32 deaths;
std::vector<std::pair<uint32,uint32>> instanceDataKeys;
MpGroupData() : group(nullptr), difficulty(MpDifficulty{}), deaths(0) {
instanceDataKeys.reserve(32);
}
MpGroupData(Group* group, MpDifficulty difficulty, uint32 deaths)
: group(group), difficulty(difficulty), deaths(deaths) {
instanceDataKeys.reserve(32);
}
std::string ToString() const {
return "MpGroupData: { group: " + std::to_string(group->GetGUID().GetCounter()) +
", difficulty: " + std::to_string(difficulty) +
", deaths: " + std::to_string(deaths) + " }";
}
};
struct MpPlayerData
{
Player* player;
uint8 difficulty;
uint32 deaths;
};
struct MpScaleFactor
{
int32 dmgBonus;
int32 healthBonus;
int32 spellBonus;
int32 maxDamageBonus;
std::string ToString() const {
return "MpScaleFactor: { dmgBonus: " + std::to_string(dmgBonus) +
", healthBonus: " + std::to_string(healthBonus) +
", healthBonus: " + std::to_string(healthBonus) +
", maxDamageBonus: " + std::to_string(maxDamageBonus) + " }";
}
};
struct MpMultipliers
{
float health;
float melee;
float baseDamage;
float spell;
float armor;
uint8 avgLevel;
std::string ToString() const {
return "MpMultipliers: { health: " + std::to_string(health) +
", melee: " + std::to_string(melee) +
", melee: " + std::to_string(baseDamage) +
", spell: " + std::to_string(spell) +
", armor: " + std::to_string(armor) +
", avgLevel: " + std::to_string(avgLevel) + " }";
}
};
struct MpInstanceData
{
InstanceMap* instance;
MpDifficulty difficulty;
// Enemy data
MpMultipliers boss;
MpMultipliers creature;
// Instance Settings
bool itemRewards;
uint32 deathLimits;
uint32 itemOffset;
std::string ToString() const {
return "MpInstanceData: { " +
std::string("instance: ") + (instance ? "valid instance" : "nullptr") +
", boss: " + boss.ToString() +
", creature: " + creature.ToString() +
", itemRewards: " + (itemRewards ? "true" : "false") +
", deathLimits: " + std::to_string(deathLimits) +
", itemOffset: " + std::to_string(itemOffset) + " }";
}
};
/**
* Simple struct for managing information about creatures that
* are in a mythic+ instance.
*/
struct MpCreatureData
{
Creature* creature;
bool scaled;
// Original information about the creature that was altered.
uint8 originalLevel;
CreatureBaseStats const* originalStats;
// Custom difficulty modifiers to creatures at higher difficulties.
std::vector<uint32> auras;
std::vector<std::string> affixes;
MpCreatureData(Creature* creature)
: creature(creature), scaled(false)
{
if(creature) {
originalLevel = creature->GetLevel();
originalStats = sObjectMgr->GetCreatureBaseStats(
originalLevel,
creature->GetCreatureTemplate()->unit_class
);
}
auras.reserve(3);
affixes.reserve(3);
}
void SetScaled(bool scaled) {
this->scaled = scaled;
}
bool IsScaled() {
return scaled;
}
/**@todo Add Affixes and Aura Spell methods */
};
class MpDataStore {
private:
MpDataStore()
: _playerData(std::make_unique<std::unordered_map<ObjectGuid, MpPlayerData>>()),
_instanceData(std::make_unique<std::map<std::pair<uint32, uint32>, MpInstanceData>>()),
_groupData(std::make_unique<std::unordered_map<ObjectGuid, MpGroupData>>()),
_instanceCreatureData(std::make_unique<std::unordered_map<ObjectGuid, MpCreatureData>>()),
_mutableScaleFactors(std::make_unique<std::map<std::pair<int32, int32>,MpScaleFactor>>())
{
_playerData->reserve(32);
_groupData->reserve(32);
_instanceCreatureData->reserve(500);
};
inline ~MpDataStore() {}
std::unique_ptr<std::unordered_map<ObjectGuid, MpPlayerData>> _playerData;
// Instance data containes information about how to scale creatures
std::unique_ptr<std::map<std::pair<uint32,uint32>,MpInstanceData>> _instanceData; // {mapId,instanceId}
// Group data stored current group difficulty setting, and stats of group
std::unique_ptr<std::unordered_map<ObjectGuid, MpGroupData>> _groupData;
// This is all creatures that have been scaled used for determining what has been scaled
std::unique_ptr<std::unordered_map<ObjectGuid, MpCreatureData>> _instanceCreatureData;
// use to mimic pattern normals scale to heroic (loaded at server start)
std::unique_ptr<std::map<std::pair<int32,int32>,MpScaleFactor>> _mutableScaleFactors; // {mapId,difficulty}
std::unique_ptr<const std::map<std::pair<int32,int32>,MpScaleFactor>> _scaleFactors; // {mapId,difficulty}
public:
// ensure we only ever have one instance of this class
MpDataStore(const MpDataStore&) = delete;
MpDataStore& operator=(const MpDataStore&) = delete;
const MpPlayerData* GetPlayerData(ObjectGuid guid) const {
try {
return &_playerData->at(guid);
} catch (const std::out_of_range& oor) {
return nullptr;
}
}
const MpGroupData* GetGroupData(ObjectGuid guid) const {
if (_groupData->contains(guid)) {
return &_groupData->at(guid);
} else {
return nullptr;
}
}
const MpGroupData* GetGroupData(Player *player) const {
return GetGroupData(player->GetGroup()->GetGUID());
};
// Set and remove settigs for a group options (difficulty, deaths, stats, etc)
void AddGroupData(Group *group, MpGroupData groupData);
void RemoveGroupData(Group *group);
MpGroupData* GetGroupData(Group *group);
void PushGroupInstanceKey(Group *group, uint32 mapId, uint32 instanceId);
void AddPlayerData(ObjectGuid guid, MpPlayerData pd);
void RemovePlayerData(ObjectGuid guid);
// Each Map/Instance is a unique key that contains scaling information based on difficulty
void AddInstanceData(uint32 mapId, uint32 instanceId, MpInstanceData );
MpInstanceData* GetInstanceData(uint32 mapId, uint32 instanceId);
void RemoveInstanceData(uint32 mapId, uint32 instanceId);
// Methods for interacting with the map of creatures in a mythic instances
void AddCreatureData(ObjectGuid guid, MpCreatureData creatureData);
MpCreatureData* GetCreatureData(ObjectGuid guid);
void RemoveCreatureData(ObjectGuid guid);
std::vector<MpCreatureData*> GetUnscaledCreatures(uint32 mapId, uint32 instanceId);
// Scale factors are used to determine a base bonus for enemies base on the instance difficulty
int32 GetHealthScaleFactor(int32 mapId, int32 difficulty) const;
int32 GetDamageScaleFactor(int32 mapId, int32 difficulty) const;
int32 GetMaxDamageScaleFactor(int32 mapId, int32 difficulty) const;
int32 GetSpellScaleFactor(int32 mapId, int32 difficulty) const;
MpScaleFactor GetScaleFactor(int32 mapId, int32 difficulty) const;
auto GetInstanceDataKey(uint32 mapId, uint32 instanceId) const {
return std::make_pair(mapId, instanceId);
}
auto GetScaleFactorKey(int32 mapId, int32 difficulty) const {
return std::make_pair(mapId, difficulty);
}
// Used at initial server load
int32 LoadScaleFactors();
static MpDataStore* instance() {
static MpDataStore instance;
return &instance;
}
};
#define sMpDataStore MpDataStore::instance()
#endif // MpDataStore_DATASTORE_H

36
src/MpLogger.h Normal file
View File

@@ -0,0 +1,36 @@
#ifndef MP_LOGGER_H
#define MP_LOGGER_H
#include "Log.h"
class MpLogger
{
public:
template<typename... Args>
static void debug(const char* fmt, Args&&... args) {
LOG_DEBUG("module.MythicPlus", "[MythicPlus] DEBUG " + std::string(fmt), std::forward<Args>(args)...);
}
template<typename... Args>
static void error(const char* fmt, Args&&... args) {
LOG_ERROR("module.MythicPlus", "[MythicPlus] ERROR " + std::string(fmt), std::forward<Args>(args)...);
}
template<typename... Args>
static void info(const char* fmt, Args&&... args) {
LOG_INFO("module.MythicPlus", "[MythicPlus] INFO " + std::string(fmt), std::forward<Args>(args)...);
}
template<typename... Args>
static void warn(const char* fmt, Args&&... args) {
LOG_WARN("module.MythicPlus", "[MythicPlus] WARN " + std::string(fmt), std::forward<Args>(args)...);
}
template<typename... Args>
static void trace(const char* fmt, Args&&... args) {
LOG_TRACE("module.MythicPlus", "[MythicPlus] TRACE " + std::string(fmt), std::forward<Args>(args)...);
}
};
#endif // MP_LOGGER_H

View File

@@ -1,11 +1,495 @@
#include "MythicPlus.h"
#include "MpLogger.h"
#include "ObjectMgr.h"
#include "MapMgr.h"
#include "ScriptMgr.h"
#include "Group.h"
#include "Unit.h"
MythicPlus::MythicPlus()
#include <algorithm>
#include <cmath>
bool MythicPlus::IsMapEligible(Map* map)
{
if (!Enabled) {
return false;
}
if (map->IsDungeon()) {
return true;
}
return false;
}
MythicPlus::~MythicPlus()
bool MythicPlus::IsDifficultySet(Player const* player)
{
Group const* group = player->GetGroup();
if (!group) {
return false;
}
MpGroupData const* groupData = sMpDataStore->GetGroupData(group->GetGUID());
if (!groupData) {
return false;
}
return true;
}
bool MythicPlus::IsDifficultyEnabled(std::string difficulty)
{
return std::find(enabledDifficulties.begin(), enabledDifficulties.end(), difficulty) != enabledDifficulties.end();
}
bool MythicPlus::IsDungeonDisabled(uint32 dungeon)
{
return std::find(disabledDungeons.begin(), disabledDungeons.end(), dungeon) != disabledDungeons.end();
}
bool MythicPlus::EligibleHealTarget(Unit* target)
{
if (!target) {
return false;
}
if (target->GetTypeId() == TYPEID_CORPSE || target->GetTypeId() == TYPEID_GAMEOBJECT) {
return false;
}
#if defined(MOD_PRESENT_NPCBOTS)
if (target->IsNPCBot()) {
return false;
}
if ((target->IsPet() || target->IsSummon() || target->IsHunterPet()) && target->GetOwner()->IsNPCBot()) {
return false;
}
#endif
if(sMythicPlus->IsCreatureEligible(target->ToCreature())) {
return true;
}
return false;
}
bool MythicPlus::EligibleDamageTarget(Unit* target)
{
if (!target) {
return false;
}
if (target->GetTypeId() == TYPEID_PLAYER) {
MpLogger::debug("Target {} is a player", target->GetName());
return true;
}
#if defined(MOD_PRESENT_NPCBOTS)
if (target->IsNPCBot()) {
return true;
}
if ((target->IsPet() || target->IsSummon() || target->IsHunterPet()) && target->GetOwner() && target->GetOwner()->IsNPCBot()) {
return true;
}
#endif
Creature* creature = target->ToCreature();
if (creature && (creature->IsPet() || creature->IsSummon() || creature->IsHunterPet()) && creature->GetOwner() && creature->IsControlledByPlayer()) {
return true;
}
return false;
}
bool MythicPlus::IsCreatureEligible(Creature* creature)
{
if (!creature) {
return false;
}
if (creature->IsDungeonBoss()) {
return true;
}
// Check if the creature is a pet or summon controlled by a player
if ((creature->IsHunterPet() || creature->IsPet() || creature->IsSummon()) && creature->IsControlledByPlayer()) {
return false;
}
// Skip critters, totems, and triggers
if (creature->IsCritter() || creature->IsTotem() || creature->IsTrigger()) {
return false;
}
#if defined(MOD_PRESENT_NPCBOTS)
// Safely check if the creature is an NPC Bot
if (creature->IsNPCBot()) {
MpLogger::debug("Creature {} is an NPC Bot, do not scale", creature->GetName());
return false;
}
#endif
// Check for NPC-related flags (vendor, gossip, quest giver, trainer, etc.)
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()))
{
return false;
}
return true;
}
void MythicPlus::AddCreatureForScaling(Creature* creature)
{
if (!IsCreatureEligible(creature)) {
return;
}
sMpDataStore->AddCreatureData(creature->GetGUID(), MpCreatureData(creature));
MpLogger::debug("Added creature {} to instance data for instance {}",
creature->GetName(),
creature->GetMap()->GetMapName()
);
}
void MythicPlus::AddScaledCreature(Creature* creature, MpInstanceData* instanceData)
{
MpCreatureData creatureData = MpCreatureData(creature);
// allow small variance in level for non-boss creatures
uint8 level = uint8(urand(instanceData->creature.avgLevel - 1, instanceData->creature.avgLevel + 1));
if(creature->IsDungeonBoss()) {
ScaleCreature(instanceData->boss.avgLevel, creature, &instanceData->boss, instanceData->difficulty);
} else {
ScaleCreature(level, creature, &instanceData->creature, instanceData->difficulty);
}
creatureData.SetScaled(true);
sMpDataStore->AddCreatureData(creature->GetGUID(), creatureData);
// MpLogger::debug("Scaled Creature {} Entry {} Id {} level from {} to {}",
// creature->GetName(),
// creature->GetEntry(),
// creature->GetGUID().GetCounter(),
// creature->GetLevel(),
// level
// );
}
void MythicPlus::ScaleRemaining(Player* player, MpInstanceData* instanceData)
{
std::vector<MpCreatureData*> creatures = sMpDataStore->GetUnscaledCreatures(player->GetMapId(), player->GetInstanceId());
for (MpCreatureData* creatureData : creatures) {
AddScaledCreature(creatureData->creature, instanceData);
}
}
// Perform any memory cleanup when the creature is removed from the world and no longer needed.
void MythicPlus::RemoveCreature(Creature* creature)
{
MpCreatureData* creatureData = sMpDataStore->GetCreatureData(creature->GetGUID());
if (!creatureData) {
return;
}
sMpDataStore->RemoveCreatureData(creature->GetGUID());
}
void MythicPlus::ScaleCreature(uint8 level, Creature* creature, MpMultipliers* multipliers, MpDifficulty difficulty)
{
uint32 origLevel = creature->GetLevel();
CreatureTemplate const* cInfo = creature->GetCreatureTemplate();
uint32 mapId = creature->GetMapId();
creature->SetLevel(level);
CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(
level,
cInfo->unit_class
);
uint32 basehp = stats->BaseHealth[EXPANSION_WRATH_OF_THE_LICH_KING];
uint32 health = CalculateNewHealth(cInfo, mapId, difficulty, basehp, multipliers->health);
MpLogger::debug("Creature {} base health scaled from {} to {}",
creature->GetName(),
basehp,
health
);
creature->SetCreateHealth(health);
creature->SetMaxHealth(health);
creature->SetHealth(health);
creature->ResetPlayerDamageReq();
creature->SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, (float)health);
/**
* @TODO: Figure out mana later for unit_types 2 and 8
*/
uint32 mana = uint32(std::ceil(stats->BaseMana * cInfo->ModMana));
creature->SetCreateMana(mana);
creature->SetMaxPower(POWER_MANA, mana);
creature->SetPower(POWER_MANA, mana);
if(cInfo->unit_class == UNIT_CLASS_MAGE) {
creature->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)mana * 10.0f);
}
if(cInfo->unit_class == UNIT_CLASS_PALADIN) {
creature->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)mana * 3.0f);
}
float oldAp = stats->AttackPower;
float oldRangeAp = stats->RangedAttackPower;
uint32 rangeAp = irand(215, 357);
float ap; // = ((85 - origLevel) * APratio); // * 100;
int32 damageBonus = sMpDataStore->GetDamageScaleFactor(mapId, difficulty);
float dmgMod = cInfo->DamageModifier + damageBonus;
ap = dmgMod * 80 + oldAp;
if (creature->GetLevel() >= 60) {
ap = ap * 1.25f;
rangeAp = rangeAp * 1.25f;
}
creature->SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, ap);
creature->SetModifierValue(UNIT_MOD_ATTACK_POWER_RANGED, BASE_VALUE, rangeAp);
// MpLogger::debug("Creature {} base attack power {} new ap {}",
// creature->GetName(),
// oldAp,
// ap
// );
// This works out a bonus damage to apply to the mob using the database and original mod settings.
// the thought behind this is some mobs in dungeons are intended to hit harder than others
// so applying a flat bonus keeps the ratios the same but increases the overall difficulty.
// Of course within reason.
int32 maxBonus = sMpDataStore->GetMaxDamageScaleFactor(mapId, difficulty);
// Allow bosses to scale as high as they want but limit non-bosses to a max bonus
if(!creature->IsDungeonBoss() && damageBonus > maxBonus) {
dmgMod = maxBonus;
}
float oldDmgModifier = creature->GetModifierValue(UNIT_MOD_DAMAGE_MAINHAND, BASE_VALUE);
creature->SetModifierValue(UNIT_MOD_DAMAGE_MAINHAND,BASE_PCT, dmgMod);
creature->SetModifierValue(UNIT_MOD_DAMAGE_OFFHAND,BASE_PCT, dmgMod*0.85f);
creature->SetModifierValue(UNIT_MOD_DAMAGE_RANGED,BASE_PCT, dmgMod);
creature->UpdateAllStats();
// Scale up the armor with some variance also to make some tougher enemies in the mix
uint32 armor = uint32(std::ceil(stats->BaseArmor * multipliers->armor * cInfo->ModArmor));
creature->SetArmor(armor);
// ap = pow(float((creature->GetLevel() - origLevel) / 5), 1.8f) * 1000
}
int32 MythicPlus::ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, MpCreatureData* creatureData, Creature* creature, Unit* target, float damageMultiplier)
{
if (!spellInfo) {
MpLogger::error("Invalid spell info ScaleDamageSpell()");
return damage;
}
if(!creatureData) {
// this is probably a summoned object or totem so going to cheat here and just use the flat modifer
// Handle totems that do some nasty things to us Slave Pens anyone
if(creature->IsTotem()) {
Unit* owner = creature->GetOwner();
if(owner) {
float lvlDmgBonus = float(85 - owner->GetLevel() / 5.0f);
return int32(damage * lvlDmgBonus * damageMultiplier);
} else {
return damage * damageMultiplier;
}
}
MpLogger::error("Invalid creature data ScaleDamageSpell()");
return damage * damageMultiplier;
}
if(!creature) {
MpLogger::error("Invalid creature ScaleDamageSpell()");
return damage * damageMultiplier;
}
int32 originalLevel = creatureData->originalLevel;
MpInstanceData *instanceData = sMpDataStore->GetInstanceData(creature->GetMapId(), creature->GetInstanceId());
int32 spellBonus = sMpDataStore->GetSpellScaleFactor(creature->GetMapId(), instanceData->difficulty);
if(creature->IsDungeonBoss()) {
spellBonus *= 1.25;
}
// since we are using logrithmic operation divide the level by the original level
float scalingFactor = pow(float((creature->GetLevel() - originalLevel) / 10.0f ), float(spellBonus) / 5.0f);
MpLogger::debug("Creature {} original level: {} New Level{} and Scaling level {}", creature->GetName(), originalLevel, creature->GetLevel(), scalingFactor);
int32 totalDamage = 0;
auto effects = spellInfo->GetEffects();
for (uint8 i = 0; i < effects.size(); ++i)
{
SpellEffectInfo effect = effects[i];
if(effect.IsAura()) {
switch(spellInfo->Effects[i].ApplyAuraName) {
case SPELL_AURA_PERIODIC_DAMAGE:
case SPELL_AURA_PERIODIC_DAMAGE_PERCENT:
case SPELL_AURA_POWER_BURN:
case SPELL_AURA_PERIODIC_LEECH:
case SPELL_AURA_PERIODIC_TRIGGER_SPELL:
case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE:
case SPELL_AURA_PERIODIC_DUMMY:
case SPELL_AURA_DUMMY:
totalDamage += effect.CalcValue(creature, nullptr, target);
break;
default:
break;
}
} else {
switch(effect.Effect)
{
case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE:
case SPELL_EFFECT_WEAPON_DAMAGE:
case SPELL_EFFECT_NORMALIZED_WEAPON_DMG:
return damage;
case SPELL_EFFECT_SCHOOL_DAMAGE:
case SPELL_EFFECT_ENVIRONMENTAL_DAMAGE:
case SPELL_EFFECT_POWER_BURN:
case SPELL_EFFECT_HEALTH_LEECH:
case SPELL_EFFECT_TRIGGER_SPELL:
case SPELL_EFFECT_TRIGGER_SPELL_WITH_VALUE:
case SPELL_EFFECT_DUMMY:
totalDamage += effect.CalcValue(creature, nullptr, target);
break;
default:
break;
}
}
}
if(totalDamage == 0) {
return damage;
}
// Apply scaling factor and the set multiplier from the instance data
totalDamage = int32(totalDamage * scalingFactor * damageMultiplier);
MpLogger::debug("Spell {} damage scaled from for spell New Damage: {} using: scaling Factor: {} and damage Multi: {}",spellInfo->SpellName[0], totalDamage, scalingFactor, damageMultiplier);
return totalDamage;
}
int32 MythicPlus::ScaleHealSpell(SpellInfo const * spellInfo, MpCreatureData* creatureData, Creature* creature, Creature* target, float healMultiplier)
{
if (!spellInfo) {
MpLogger::error("Invalid spell info ScaleHealSpell()");
return 0;
}
if(!creatureData) {
MpLogger::error("Invalid creature data ScaleHealSpell()");
return 0;
}
if(!creature) {
MpLogger::error("Invalid creature ScaleHealSpell()");
return 0;
}
if(!target) {
MpLogger::error("Invalid target ScaleHealSpell()");
return 0;
}
int32 originalHp = creatureData->originalStats->BaseHealth[EXPANSION_WRATH_OF_THE_LICH_KING];
int32 currentHealth = creature->GetHealth();
int32 totalHeal = 0;
auto effects = spellInfo->GetEffects();
// Loop through all spell effects to scale their base healing
for (uint8 i = 0; i < effects.size(); ++i)
{
SpellEffectInfo effect = effects[i];
totalHeal += effect.CalcValue(creature, nullptr, target);
}
// Apply scaling factor and the set multiplier from the instance data
MpLogger::debug("Spell healing scaled from for spell New Damage: {}", totalHeal);
return pow((totalHeal / originalHp) * currentHealth, 0.8f) * healMultiplier;
}
/**
* Function is copied because was not accessible in core creature class
*/
float GetTypeHealthModifier(int32 Rank)
{
switch (Rank)
{
case CREATURE_ELITE_NORMAL:
return sWorld->getRate(RATE_CREATURE_NORMAL_HP);
case CREATURE_ELITE_ELITE:
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_HP);
case CREATURE_ELITE_RAREELITE:
return sWorld->getRate(RATE_CREATURE_ELITE_RAREELITE_HP);
case CREATURE_ELITE_WORLDBOSS:
return sWorld->getRate(RATE_CREATURE_ELITE_WORLDBOSS_HP);
case CREATURE_ELITE_RARE:
return sWorld->getRate(RATE_CREATURE_ELITE_RARE_HP);
default:
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_HP);
}
}
// This takes the orignal health and scales flat based on the factor then applies the configuration modifier from the conf file
uint32 CalculateNewHealth(CreatureTemplate const* cInfo, uint32 mapId, MpDifficulty difficulty, uint32 origHealth, float confHPMod)
{
int32 rank = 0;
if(cInfo && cInfo->rank > 0) {
rank = cInfo->rank;
}
// Add some variance to the healthpool so enemies are not all the same
float healthVariation = frand(0.85f, 1.15f);
float unitTypeMod = GetTypeHealthModifier(rank);
uint32 basehp = uint32(std::ceil(origHealth * unitTypeMod * healthVariation));
int32 hpScaleFactor = sMpDataStore->GetHealthScaleFactor(mapId, difficulty);
if(cInfo->ModHealth > 0.0f) {
return uint32(basehp * (cInfo->ModHealth + hpScaleFactor) * confHPMod);
} else {
return uint32(basehp * (hpScaleFactor) * confHPMod);
}
}
float GetTypeDamageModifier(int32 Rank)
{
switch (Rank)
{
case CREATURE_ELITE_NORMAL:
return sWorld->getRate(RATE_CREATURE_NORMAL_DAMAGE);
case CREATURE_ELITE_ELITE:
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_DAMAGE);
case CREATURE_ELITE_RAREELITE:
return sWorld->getRate(RATE_CREATURE_ELITE_RAREELITE_DAMAGE);
case CREATURE_ELITE_WORLDBOSS:
return sWorld->getRate(RATE_CREATURE_ELITE_WORLDBOSS_DAMAGE);
case CREATURE_ELITE_RARE:
return sWorld->getRate(RATE_CREATURE_ELITE_RARE_DAMAGE);
default:
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_DAMAGE);
}
}

View File

@@ -1,19 +1,127 @@
#ifndef MYTHICPLUS_H
#define MYTHICPLUS_H
class MythicPlus {
private:
MythicPlus();
~MythicPlus();
#include "Creature.h"
#include "Define.h"
#include "Map.h"
#include "MpDataStore.h"
#include "Player.h"
#include "SpellInfo.h"
#include "Unit.h"
#include <map>
#include <string>
#include <vector>
#include <unordered_map>
class MythicPlus
{
public:
static MythicPlus * getInstance() {
// accessor for this singleton
static MythicPlus* instance()
{
static MythicPlus instance;
return &instance;
}
// ensure we only ever have one instance of this class
MythicPlus(const MythicPlus&) = delete;
MythicPlus& operator=(const MythicPlus&) = delete;
// Global Settings
bool Enabled;
bool EnableItemRewards;
bool EnableDeathLimits;
// Turn off specific features of mythic+
std::vector<std::string> enabledDifficulties;
std::vector<uint32> disabledDungeons;
// Difficulty Modifiers
MpMultipliers mythicDungeonModifiers;
MpMultipliers mythicBossModifiers;
MpMultipliers legendaryDungeonModifiers;
MpMultipliers legendaryBossModifiers;
MpMultipliers ascendantDungeonModifiers;
MpMultipliers ascendantBossModifiers;
// Death Allowances
uint32 mythicDeathAllowance;
uint32 legendaryDeathAllowance;
uint32 ascendantDeathAllowance;
// Itemization Offsets
uint32 mythicItemOffset;
uint32 legendaryItemOffset;
uint32 ascendantItemOffset;
enum MP_UNIT_EVENT_TYPE
{
UNIT_EVENT_MELEE,
UNIT_EVENT_HEAL,
UNIT_EVENT_DOT,
UNIT_EVENT_SPELL,
UNIT_EVENT_HOT
};
// Map is eligible for mythic+ scaling
bool IsMapEligible(Map* map);
// If a player difficulty is set that is eligible for mythic+ scaling
bool IsDifficultySet(Player const* player);
// Check is difficulty is enabled in the configuration
bool IsDifficultyEnabled(std::string difficulty);
// if configuration has disabled the specific dungeon return false
bool IsDungeonDisabled(uint32 dungeonId);
// Is it a scaled creature that is being healed
bool EligibleHealTarget(Unit* target);
// Validates if the target of an attack should receive mythic+ damage/heal/dot scaling
bool EligibleDamageTarget(Unit* target);
// The creature should be given Mythic+ scaling and powers check for pets, npcs, etc
bool IsCreatureEligible(Creature* creature);
// Adds the creature if eligible to be scaled
void AddCreatureForScaling(Creature* creature);
// Removes the creature from the scaling list and cleans up memory
void RemoveCreature(Creature* creature);
/**
* Creatures are added to an instance before a player enter event is fired
* therefore it is necessary to scan the instance creature information and
* and scale any creatures that were loaded before the first player using
* the instance data from the group settings.
*/
void ScaleRemaining(Player* player, MpInstanceData* instanceData);
// This will attempt to scale a creature using instancedata
void AddScaledCreature(Creature* creature, MpInstanceData* instanceData);
// Scales the creature based on the level and the creature base stats
void ScaleCreature(uint8 level, Creature* creature, MpMultipliers* multipliers, MpDifficulty difficulty);
// Scales a damage spell up based on the level increase
int32 ScaleDamageSpell(SpellInfo const * spellInfo, uint32 damage, MpCreatureData* creatureData, Creature* creature, Unit* target, float damageMultiplier);
// This scales a heal spell up based on the how much % the original heal spell was
int32 ScaleHealSpell(SpellInfo const * spellInfo, MpCreatureData* creatureData, Creature* creature, Creature* target, float healMultiplier);
private:
MythicPlus() { }
~MythicPlus() { }
};
#define sMythicPlus MythicPlus::getInstance()
float GetTypeHealthModifier(int32 rank);
float GetTypeDamageModifier(int32 rank);
uint32 CalculateNewHealth(CreatureTemplate const* cInfo, uint32 mapId, MpDifficulty difficulty, uint32 origHealth, float confHPMod);
float CalculateNewBaseDamage(CreatureTemplate const* cInfo, uint32 mapId, MpDifficulty difficulty, float origDamage);
#endif
#define sMythicPlus MythicPlus::instance()
#endif // MYTHICPLUS_H

View File

@@ -1,22 +1,22 @@
void Addmod_mythic_plusScripts();
void AddAllCreatureScripts();
void AddAllMapScripts();
void AddCommandScripts();
void AddGlobalScripts();
void AddGroupScripts();
void AddPlayerScripts();
void AddUnitScripts();
void AddWorldScripts();
void Add_MP_AllCreatureScripts();
void Add_MP_AllMapScripts();
void Add_MP_CommandScripts();
void Add_MP_GlobalScripts();
void Add_MP_GroupScripts();
void Add_MP_PlayerScripts();
void Add_MP_UnitScripts();
void Add_MP_WorldScripts();
void Addmod_mythic_plusScripts()
{
AddAllCreatureScripts();
AddAllMapScripts();
AddCommandScripts();
AddGlobalScripts();
AddGroupScripts();
AddPlayerScripts();
AddUnitScripts();
AddWorldScripts();
Add_MP_AllCreatureScripts();
Add_MP_AllMapScripts();
Add_MP_CommandScripts();
Add_MP_GlobalScripts();
// Add_MP_GroupScripts();
// Add_MP_PlayerScripts();
Add_MP_UnitScripts();
Add_MP_WorldScripts();
}

View File

@@ -1,56 +1,39 @@
#include "ScriptMgr.h"
#include "MpLogger.h"
#include "MpDataStore.h"
#include "MythicPlus.h"
#include "Player.h"
#include "ScriptMgr.h"
class MythicPlus_PlayerScript : public PlayerScript
{
public:
MythicPlus_PlayerScript() : PlayerScript("MythicPlus_PlayerScript") { }
void OnLogin(Player *Player) override
void OnPlayerJustDied(Player* player, Unit* killer)
{
// if (sMythicPlus->EnableGlobal && sMythicPlus->Announcement) {
// ChatHandler(Player->GetSession()).SendSysMessage("This server is running the |cff4CFF00MythicPlus |rmodule.");
// }
}
Map* map = player->GetMap();
if(!sMythicPlus->IsMapEligible(map)) {
return;
}
virtual void OnLevelChanged(Player* player, uint8 oldlevel) override
{
// Map* map = player->GetMap();
if (!player) {
return;
}
// if (!map || !map->IsDungeon())
// return;
if (!killer) {
return;
}
// first update the map's player stats
// sMythicPlus->UpdateMapPlayerStats(map);
// // schedule all creatures for an update
// sMythicPlus->lastConfigTime =
// std::chrono::duration_cast<std::chrono::microseconds>(
// std::chrono::system_clock::now().time_since_epoch()
// ).count();
}
void OnGiveXP(Player* player, uint32& amount, Unit* victim, uint8 /*xpSource*/) override
{
// Map* map = player->GetMap();
}
// 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;
MpGroupData *data = sMpDataStore->GetGroupData(player->GetGroup());
if (player->GetMap()->IsDungeon()) {
MpLogger::debug("Player {} just died in dungeon {} by {}", player->GetName(), player->GetMap()->GetMapName(), killer->GetName());
}
}
};
void AddPlayerScripts()
void Add_MP_PlayerScripts()
{
MpLogger::debug("Add_MP_PlayerScripts()");
new MythicPlus_PlayerScript();
}

View File

@@ -1,20 +1,224 @@
#include "ScriptMgr.h"
#include "MythicPlus.h"
#include "MpLogger.h"
#include "Player.h"
#include "MythicPlus.h"
#include "ScriptMgr.h"
class MythicPlus_UnitScript : public UnitScript
{
public:
MythicPlus_UnitScript() : UnitScript("MythicPlus_UnitScript", true) { }
void ModifyPeriodicDamageAurasTick(Unit* target, Unit* attacker, uint32& damage, SpellInfo const* spellInfo) override {
if (!target && !attacker) {
return;
}
void OnAuraApply(Unit* unit, Aura* aura) override {
Map *map = target->GetMap();
if(!sMythicPlus->IsMapEligible(map)) {
return;
}
auto effects = spellInfo->Effects;
bool isHot = false;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) {
switch(effects[i].Effect) {
case SPELL_EFFECT_HEAL:
case SPELL_EFFECT_HEAL_MAX_HEALTH:
case SPELL_EFFECT_HEAL_MECHANICAL:
case SPELL_EFFECT_HEAL_PCT:
case SPELL_EFFECT_SPIRIT_HEAL:
isHot = true;
break;
}
}
if(isHot) {
damage = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_HOT, target, attacker, damage, spellInfo);
} else {
damage = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_DOT, target, attacker, damage, spellInfo);
}
}
void ModifySpellDamageTaken(Unit* target, Unit* attacker, int32& damage, SpellInfo const* spellInfo) override {
if (!target && !attacker) {
return;
}
Map *map = target->GetMap();
if(!sMythicPlus->IsMapEligible(map)) {
return;
}
if(sMythicPlus->EligibleDamageTarget(target)) {
MpLogger::debug("ModifySpellDamageTaken: {} hits {} with spell: {}", attacker->GetName(), target->GetName(), spellInfo->SpellName[0]);
}
damage = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_SPELL, target, attacker, damage, spellInfo);
}
/**
* Directly Modify the melee damage characters and allied creatures will
* receive from mythic+ scaled enemies.
*/
void ModifyMeleeDamage(Unit* target, Unit* attacker, uint32& damage) override {
if (!target && !attacker) {
return;
}
Map *map = target->GetMap();
if(!sMythicPlus->IsMapEligible(map)) {
return;
}
damage = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_MELEE, target, attacker, damage);
}
// When a healing spell hits a mythic+ enemy modify based on the modifiers for the difficulty
void ModifyHealReceived(Unit* target, Unit* healer, uint32& healing, SpellInfo const* spellInfo) override {
if (!target && !healer) {
return;
}
Map *map = target->GetMap();
if(!sMythicPlus->IsMapEligible(map)) {
return;
}
healing = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_HEAL, target, healer, healing, spellInfo);
}
uint32 modifyIncomingDmgHeal(MythicPlus::MP_UNIT_EVENT_TYPE eventType,Unit* target, Unit* attacker, uint32 damageOrHeal, SpellInfo const* spellInfo = nullptr) {
if (!target && !attacker) {
return damageOrHeal;
}
int32 alteredDmgHeal = 0;
Map *map = target->GetMap();
if(!sMythicPlus->IsMapEligible(map)) {
return damageOrHeal;
}
if(attacker && attacker->IsPlayer()) {
return damageOrHeal;
}
#if defined(MOD_PRESENT_NPCBOTS)
if (attacker && attacker->IsNPCBot()) {
return damageOrHeal;
}
#endif
Creature* creature = attacker ? attacker->ToCreature() : nullptr;
if (!creature) {
MpLogger::debug("Attacker was considered not a creature");
return damageOrHeal;
}
MpInstanceData* instanceData = sMpDataStore->GetInstanceData(map->GetId(), map->GetInstanceId());
if(!instanceData) {
return damageOrHeal;
}
std::string eventName = "";
switch (eventType) {
case MythicPlus::UNIT_EVENT_MELEE:
eventName = "Melee";
break;
case MythicPlus::UNIT_EVENT_HEAL:
eventName = "Heal";
break;
case MythicPlus::UNIT_EVENT_DOT:
eventName = "DOT";
break;
case MythicPlus::UNIT_EVENT_SPELL:
eventName = "Spell";
break;
case MythicPlus::UNIT_EVENT_HOT:
eventName = "HOT";
break;
}
if(eventType != MythicPlus::UNIT_EVENT_MELEE) {
MpLogger::debug("Incoming Event Type ({}): {} hits {} before mod: {} spell: ", eventName, attacker->GetName(), target->GetName(), damageOrHeal, spellInfo ? spellInfo->SpellName[0] : "none");
if(creature->IsDungeonBoss()) {
alteredDmgHeal = damageOrHeal * instanceData->boss.melee;
} else {
alteredDmgHeal = damageOrHeal * instanceData->creature.melee;
}
}
// If the target is the enemy then increase the amount of healing by the instance data modifier for spell output.
if(sMythicPlus->EligibleDamageTarget(target)) {
/**
* @TODO: Allow more granular control over the scaling of DOT, HOT, and other spell effects
* in the future if needed
*/
switch (eventType) {
case MythicPlus::UNIT_EVENT_MELEE:
if(creature->IsDungeonBoss()) {
alteredDmgHeal = damageOrHeal * instanceData->boss.melee;
} else {
alteredDmgHeal = damageOrHeal * instanceData->creature.melee;
}
MpLogger::debug("Incoming Melee New Damage: {}({}) {} hits {}", alteredDmgHeal, damageOrHeal, attacker->GetName(), target->GetName());
break;
case MythicPlus::UNIT_EVENT_DOT:
case MythicPlus::UNIT_EVENT_SPELL:
if(creature->IsDungeonBoss()) {
if(spellInfo) {
alteredDmgHeal = sMythicPlus->ScaleDamageSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, target, instanceData->boss.spell);
} else {
alteredDmgHeal = damageOrHeal * instanceData->boss.spell;
}
} else {
if(spellInfo) {
alteredDmgHeal = sMythicPlus->ScaleDamageSpell(spellInfo, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, target, instanceData->creature.spell);
} else {
alteredDmgHeal = damageOrHeal * instanceData->creature.spell;
}
}
if(spellInfo) {
MpLogger::debug("Incoming spell New Damage: {}({}) {} hits {} spell: {} ID: {}", alteredDmgHeal, damageOrHeal, attacker->GetName(), target->GetName(), spellInfo->SpellName[0], spellInfo->Id);
} else {
MpLogger::debug("Incoming spell New Damage: {}({}) {} hits {}", alteredDmgHeal, damageOrHeal, attacker->GetName(), target->GetName());
}
break;
case MythicPlus::UNIT_EVENT_HOT:
case MythicPlus::UNIT_EVENT_HEAL:
break;
}
}
/**
* @TODO: Add more granular control over the scaling of healing spells
*/
if(sMythicPlus->EligibleHealTarget(target) && (eventType == MythicPlus::UNIT_EVENT_HEAL || eventType == MythicPlus::UNIT_EVENT_HOT)) {
bool isHeal = true;
if(creature->IsDungeonBoss()) {
if(spellInfo) {
alteredDmgHeal = sMythicPlus->ScaleHealSpell(spellInfo, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, attacker->ToCreature(), instanceData->boss.spell);
} else {
alteredDmgHeal = damageOrHeal * instanceData->boss.spell;
}
} else {
if(spellInfo) {
alteredDmgHeal = sMythicPlus->ScaleHealSpell(spellInfo, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, attacker->ToCreature(), instanceData->creature.spell);
} else {
alteredDmgHeal = damageOrHeal * instanceData->creature.spell;
}
}
MpLogger::debug("Incoming heal: {}({}) {} hits {}", alteredDmgHeal, damageOrHeal, attacker->GetName(), target->GetName());
}
return alteredDmgHeal > 0 ? alteredDmgHeal : damageOrHeal;
}
};
void AddUnitScripts()
void Add_MP_UnitScripts()
{
new MythicPlus_UnitScript();
}

View File

@@ -1,32 +1,103 @@
#include "ScriptMgr.h"
#include "MythicPlus.h"
#include "Config.h"
#include "MythicPlus.h"
#include "MpDataStore.h"
#include "MpLogger.h"
#include "Player.h"
#include "ScriptMgr.h"
class MythicPlus_WorldScript : public WorldScript
{
public:
MythicPlus_WorldScript() : WorldScript("MythicPlus_WorldScript") { }
void OnBeforeConfigLoad(bool /*reload*/) override
void OnAfterConfigLoad(bool /*reload*/) override
{
SetInitialWorldSettings();
// sMythicPlus->lastConfigTime = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
// Global Settings
sMythicPlus->Enabled = sConfigMgr->GetOption<bool>("MythicPlus.Enabled", 1);
sMythicPlus->EnableItemRewards = sConfigMgr->GetOption<bool>("MythicPlus.EnableItemRewards", 1);
sMythicPlus->EnableDeathLimits = sConfigMgr->GetOption<bool>("MythicPlus.EnableDeathLimits", 1);
/** @todo Add these back in once I write the parsers for handling the different configuration values. */
// sMythicPlus->enabledDifficulties = sConfigMgr->GetOption<std::vector<std::string>>("MythicPlus.EnabledDifficulties", {"mythic", "legendary", "ascendant"});
// sMythicPlus->disabledDungeons = sConfigMgr->GetOption<std::vector<uint32>>("MythicPlus.DisabledDungeons", {});
// Mythic Difficulty Modifiers
sMythicPlus->mythicDungeonModifiers = {
.health = sConfigMgr->GetOption<float>("MythicPlus.Mythic.DungeonHealth", 1.25f),
.melee = sConfigMgr->GetOption<float>("MythicPlus.Mythic.DungeonMelee", 1.25f),
.baseDamage = sConfigMgr->GetOption<float>("MythicPlus.Mythic.DungeonBaseDamage", 1.5f),
.spell = sConfigMgr->GetOption<float>("MythicPlus.Mythic.DungeonSpell", 1.15f),
.armor = sConfigMgr->GetOption<float>("MythicPlus.Mythic.DungeonArmor", 1.25f),
.avgLevel = sConfigMgr->GetOption<uint8>("MythicPlus.Mythic.DungeonAvgLevel", 83)
};
sMythicPlus->mythicBossModifiers = {
.health = sConfigMgr->GetOption<float>("MythicPlus.Mythic.DungeonBossHealth", 1.50f),
.melee = sConfigMgr->GetOption<float>("MythicPlus.Mythic.DungeonBossMelee", 1.35f),
.baseDamage = sConfigMgr->GetOption<float>("MythicPlus.Mythic.DungeonBossBaseDamage", 2.0f),
.spell = sConfigMgr->GetOption<float>("MythicPlus.Mythic.DungeonBossSpell", 1.25f),
.armor = sConfigMgr->GetOption<float>("MythicPlus.Mythic.DungeonBossArmor", 1.35f),
.avgLevel = sConfigMgr->GetOption<uint8>("MythicPlus.Mythic.DungeonBossLevel", 85)
};
// Legendary Difficulty Modifiers
sMythicPlus->legendaryDungeonModifiers = {
.health = sConfigMgr->GetOption<float>("MythicPlus.Legendary.DungeonHealth", 2.25f),
.melee = sConfigMgr->GetOption<float>("MythicPlus.Legendary.DungeonMelee", 2.25f),
.baseDamage = sConfigMgr->GetOption<float>("MythicPlus.Legendary.DungeonBaseDamage", 2.75f),
.spell = sConfigMgr->GetOption<float>("MythicPlus.Legendary.DungeonSpell", 2.25f),
.armor = sConfigMgr->GetOption<float>("MythicPlus.Legendary.DungeonArmor", 2.25f),
.avgLevel = sConfigMgr->GetOption<uint8>("MythicPlus.Legendary.DungeonAvgLevel", 85)
};
sMythicPlus->legendaryBossModifiers = {
.health = sConfigMgr->GetOption<float>("MythicPlus.Legendary.DungeonBossHealth", 2.25f),
.melee = sConfigMgr->GetOption<float>("MythicPlus.Legendary.DungeonBossMelee", 2.25f),
.baseDamage = sConfigMgr->GetOption<float>("MythicPlus.Legendary.DungeonBossBaseDamage", 4.0f),
.spell = sConfigMgr->GetOption<float>("MythicPlus.Legendary.DungeonBossSpell", 2.25f),
.armor = sConfigMgr->GetOption<float>("MythicPlus.Legendary.DungeonBossArmor", 3.25f),
.avgLevel = sConfigMgr->GetOption<uint8>("MythicPlus.Legendary.DungeonBossLevel", 87)
};
sMythicPlus->ascendantDungeonModifiers = {
.health = sConfigMgr->GetOption<float>("MythicPlus.Ascendant.DungeonHealth", 3.25f),
.melee = sConfigMgr->GetOption<float>("MythicPlus.Ascendant.DungeonMelee", 3.25f),
.baseDamage = sConfigMgr->GetOption<float>("MythicPlus.Ascendant.DungeonBaseDamage", 4.75f),
.spell = sConfigMgr->GetOption<float>("MythicPlus.Ascendant.DungeonSpell", 3.25f),
.armor = sConfigMgr->GetOption<float>("MythicPlus.Ascendant.DungeonArmor", 3.25f),
.avgLevel = sConfigMgr->GetOption<uint8>("MythicPlus.Ascendant.DungeonAvgLevel", 87)
};
sMythicPlus->ascendantBossModifiers = {
.health = sConfigMgr->GetOption<float>("MythicPlus.Ascendant.DungeonBossHealth", 3.25f),
.melee = sConfigMgr->GetOption<float>("MythicPlus.Ascendant.DungeonBossMelee", 3.25f),
.baseDamage = sConfigMgr->GetOption<float>("MythicPlus.Ascendant.DungeonBossBaseDamage", 6.0f),
.spell = sConfigMgr->GetOption<float>("MythicPlus.Ascendant.DungeonBossSpell", 3.25f),
.armor = sConfigMgr->GetOption<float>("MythicPlus.Ascendant.DungeonBossArmor", 3.25f),
.avgLevel = sConfigMgr->GetOption<uint8>("MythicPlus.Ascendant.DungeonBossLevel", 90)
};
// Death Allowances
sMythicPlus->mythicDeathAllowance = sConfigMgr->GetOption<uint32>("MythicPlus.Mythic.DeathAllowance", 1000);
sMythicPlus->legendaryDeathAllowance = sConfigMgr->GetOption<uint32>("MythicPlus.Legendary.DeathAllowance", 30);
sMythicPlus->ascendantDeathAllowance = sConfigMgr->GetOption<uint32>("MythicPlus.Ascendant.DeathAllowance", 15);
// Itemization Offsets
sMythicPlus->mythicItemOffset = sConfigMgr->GetOption<uint32>("MythicPlus.Mythic.ItemOffset", 20000000);
sMythicPlus->legendaryItemOffset = sConfigMgr->GetOption<uint32>("MythicPlus.Legendary.ItemOffset", 21000000);
sMythicPlus->ascendantItemOffset = sConfigMgr->GetOption<uint32>("MythicPlus.Ascendant.ItemOffset", 22000000);
}
void OnStartup() override
{
}
void SetInitialWorldSettings()
{
// sMythicPlus->configValue = sConfigMgr->GetOption<float>("MythicPlus.ConfigValue", 1.0f, false);
int32 size = sMpDataStore->LoadScaleFactors();
MpLogger::info("Loaded {} Mythic+ Scaling Factors from database...", size);
}
};
void AddWorldScripts()
void Add_MP_WorldScripts()
{
MpLogger::debug("Add_MP_WorldScripts()");
new MythicPlus_WorldScript();
}