mirror of
https://github.com/araxiaonline/mod-mythic-plus.git
synced 2026-06-13 03:02:24 -04:00
Compare commits
40 Commits
feat/updat
...
test-v2
| Author | SHA1 | Date | |
|---|---|---|---|
| c73dba69d7 | |||
| b9825b47e0 | |||
| cbde9bbfe2 | |||
| af9e7b3f3f | |||
| 5b6805dcc7 | |||
| 78648bd511 | |||
|
|
fd4c9d2b51 | ||
| 2fda1c1a93 | |||
| ca1dda4979 | |||
| 04dc95f2e3 | |||
| d7e1f7a026 | |||
| b6e21b5cf7 | |||
| e4a234b567 | |||
| c4be5d44be | |||
| ff5eb09faa | |||
| 6356d975d8 | |||
| 5815d20c1c | |||
| abee7bcc41 | |||
| 014773379e | |||
| d74e9f96e1 | |||
| 519d17a272 | |||
| 7b79bf0316 | |||
| 3b7c03ce0f | |||
| e7d1b85074 | |||
| 7a73671b0f | |||
| 02f27a32f1 | |||
| a626114043 | |||
| f98ec7ad57 | |||
| d98a0345d7 | |||
| ef206b6a0d | |||
|
|
3428bcc605 | ||
| 84543dc17a | |||
| 9a2b880350 | |||
| f7e2bf2450 | |||
| 3383cb1323 | |||
| 2c8b67ef07 | |||
| 87d748cef6 | |||
| a5dd94fe54 | |||
| 7446c676f2 | |||
| 0415c5ef41 |
22
.vscode/c_cpp_properties.json
vendored
Normal file
22
.vscode/c_cpp_properties.json
vendored
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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),
|
||||
91
data/sql/db-world/base/mythic_plus_scale_factors.sql
Normal file
91
data/sql/db-world/base/mythic_plus_scale_factors.sql
Normal 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;
|
||||
51
data/sql/db-world/base/player_class_stats.sql
Normal file
51
data/sql/db-world/base/player_class_stats.sql
Normal 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
22
scripts/go.mod
Normal 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
59
scripts/go.sum
Normal 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
209
scripts/main.go
Normal 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
39202
scripts/pets/creatures.json
Normal file
File diff suppressed because it is too large
Load Diff
153
scripts/pets/main.go
Normal file
153
scripts/pets/main.go
Normal 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)
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
194
src/MpDataStore.cpp
Normal 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
264
src/MpDataStore.h
Normal 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
36
src/MpLogger.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
122
src/MythicPlus.h
122
src/MythicPlus.h
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user