35 Commits

Author SHA1 Message Date
ad39d53c58 fixed issues for newest version 2025-01-24 17:13:52 -05:00
b4013b7024 commented ideas on how the perma stat bonuses will work with custom auras 2025-01-18 11:34:08 -05:00
7d1a9018df Many changes to bring in the Advancement system for upgrading player stats via dice 2025-01-18 11:29:21 -05:00
d4fa53d435 Updates to bring in event handlers for communication and register on server load 2025-01-18 11:28:07 -05:00
2aeafa689a New client event dispatchers to commumicate to WoW Client using formatted event structure 2025-01-18 11:27:30 -05:00
f42cba3099 removed the CreatureHooks calls until later implemented 2024-12-17 17:48:16 -05:00
aa58b493ba move data channel to event processor since it makes more sense with that class 2024-12-17 17:47:28 -05:00
5387960965 Added EventProcessor for client and server communication 2024-12-17 17:43:13 -05:00
4be3fae368 Fixed warnings and updated comments 2024-12-13 19:19:29 -05:00
d410220bd0 Added methods for upgrading an advancement 2024-12-13 19:19:10 -05:00
4df7800afa Fixed all Warnings and added in advancement methods + material types loader 2024-12-13 19:18:44 -05:00
173d19bcd3 Fixed all Warnings and added in advancement methods + material types loader 2024-12-13 19:18:33 -05:00
738194fbfa added many methods related to increasing a players rank 2024-12-12 23:42:16 -05:00
81b7a87ced Added a new constant for MAX Advancement Ranks 2024-12-12 23:41:41 -05:00
8e0809cb1c Added constants for items 2024-12-12 18:53:08 -05:00
cb48af9e9d Added historical record for player advancmenets 2024-12-12 18:52:52 -05:00
acf11d25f2 Added information about the data channel used for client to server communication. 2024-12-11 15:41:36 -05:00
41fd4876cf renamed function 2024-12-11 15:40:44 -05:00
ef62c10df3 Updated schema definition 2024-12-11 15:40:24 -05:00
5fbb0eac74 Added in methods for loading in data for players 2024-12-11 15:40:00 -05:00
0982e24f06 Added Loading of advancments from database on server start and player login. 2024-12-11 15:39:45 -05:00
fa7389f24a Reorganized and fixed issues with material / types and ranks 2024-12-11 15:39:12 -05:00
8436460350 Added scheduling and fixed for death counting 2024-12-02 23:57:21 -05:00
3bd1f3cdc6 Added more handlers for players 2024-12-02 23:57:03 -05:00
6115459150 Added new sql changes for advancement 2024-12-02 23:56:22 -05:00
3093ab3280 Added new Advancement Manager classes for allowing upgrades 2024-12-02 23:56:01 -05:00
fa6f3f3eab Refactored scripts and Creature data 2024-11-29 15:25:38 -05:00
ccf222ec4f fixed issue with misaligned group deaths 2024-11-26 00:15:04 -05:00
91d3f91dfc Group data and player data objects were not referencing the same object corrected using right pointers 2024-11-25 23:55:50 -05:00
9612974b01 Added functions to store player data on bind 2024-11-24 23:53:14 -05:00
fd5e186032 Added boot on deaths limits hit 2024-11-24 21:51:13 -05:00
baf0d8e6e7 Added difficulty to death stats 2024-11-22 19:12:08 -05:00
f3230a6559 Merge branch 'main' of https://github.com/araxiaonline/mod-mythic-plus 2024-11-22 14:14:08 -05:00
86d5ba83f1 merged in branch and handled conflicts 2024-11-22 14:08:50 -05:00
Ben Carter
318244f0fd Merge pull request #9 from araxiaonline/feat/add-data-saves
Feature: Adds in data saves and upgrade system
2024-11-22 13:46:40 -05:00
43 changed files with 2622 additions and 569 deletions

View File

@@ -0,0 +1,100 @@
-- Used for tracking group instance data for mythic runs
DROP TABLE IF EXISTS mp_group_data;
CREATE TABLE mp_group_data (
groupId INT UNSIGNED NOT NULL DEFAULT '0',
difficulty INT UNSIGNED,
mapId INT UNSIGNED,
instanceId INT UNSIGNED,
instanceTimer INT UNSIGNED,
deaths INT UNSIGNED,
PRIMARY KEY (groupId)
);
-- Used for tracking current instance data for players
DROP TABLE IF EXISTS mp_player_instance_data;
CREATE TABLE mp_player_instance_data (
guid INT UNSIGNED NOT NULL DEFAULT '0',
difficulty INT UNSIGNED NOT NULL DEFAULT '3',
mapId INT UNSIGNED NOT NULL,
instanceId INT UNSIGNED,
deaths INT UNSIGNED NOT NULL,
PRIMARY KEY (guid, mapId, instanceId)
);
-- Used for tracking player deaths to specific creatures in mythic runs
DROP TABLE IF EXISTS mp_player_death_stats;
CREATE TABLE mp_player_death_stats (
guid INT UNSIGNED NOT NULL DEFAULT '0',
creatureEntry INT UNSIGNED NOT NULL,
difficulty TINYINT UNSIGNED NOT NULL DEFAULT '0',
numDeaths INT UNSIGNED NOT NULL DEFAULT '0',
lastUpdated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (guid, creatureEntry, difficulty)
);
-- Used for tracking player runs in mythic dungeons
DROP TABLE IF EXISTS mp_player_runs;
CREATE TABLE mp_player_runs (
runId INT UNSIGNED AUTO_INCREMENT,
guid INT UNSIGNED NOT NULL DEFAULT '0',
difficulty INT UNSIGNED NOT NULL DEFAULT '3',
mapId INT UNSIGNED NOT NULL DEFAULT '0',
groupDeaths INT UNSIGNED NOT NULL DEFAULT '0',
personalDeaths INT UNSIGNED NOT NULL DEFAULT '0',
startTime INT UNSIGNED,
completeTime TIMESTAMP,
botCount TINYINT UNSIGNED DEFAULT '0',
PRIMARY KEY (runId),
INDEX idx_guid (guid),
INDEX idx_mapId (mapId)
);
-- Used for tracking player stats in mythic dungeons
DROP TABLE IF EXISTS mp_player_stats;
CREATE TABLE mp_player_stats (
guid INT UNSIGNED NOT NULL DEFAULT '0',
mapId INT UNSIGNED NOT NULL DEFAULT '0',
difficulty TINYINT UNSIGNED NOT NULL DEFAULT '0',
deaths INT UNSIGNED DEFAULT '0',
runs INT UNSIGNED DEFAULT '0',
completions INT UNSIGNED DEFAULT '0',
totalTime INT UNSIGNED DEFAULT '0',
bestTime INT UNSIGNED DEFAULT '0',
PRIMARY KEY (guid, mapId, difficulty)
);
-- Used to enable custom stat upgrades from materials and drops in mythic dungeons
DROP TABLE IF EXISTS mp_player_advancements;
CREATE TABLE mp_player_advancements (
guid INT UNSIGNED NOT NULL,
advancementId INT UNSIGNED NOT NULL,
bonus FLOAT NOT NULL,
upgradeRank INT UNSIGNED NOT NULL,
diceSpent INT UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (guid, advancementId)
);
-- Used to show historic player roll data by advancement.
DROP TABLE IF EXISTS mp_player_advancement_history;
CREATE TABLE mp_player_advancement_history (
guid INT UNSIGNED NOT NULL,
advancementId INT UNSIGNED NOT NULL,
bonus FLOAT NOT NULL,
upgradeRank INT UNSIGNED NOT NULL,
diceSpent INT UNSIGNED NOT NULL DEFAULT '0',
materialId1 INT UNSIGNED NOT NULL,
materialId2 INT UNSIGNED NOT NULL,
materialId3 INT UNSIGNED NOT NULL,
materialCost1 INT UNSIGNED NOT NULL,
materialCost2 INT UNSIGNED NOT NULL,
materialCost3 INT UNSIGNED NOT NULL,
added TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (guid, advancementId)
);

View File

@@ -1,80 +0,0 @@
-- Used for tracking group instance data for mythic runs
DROP TABLE IF EXISTS mp_group_data;
CREATE TABLE mp_group_data (
groupId INT UNSIGNED NOT NULL DEFAULT '0',
difficulty INT UNSIGNED,
mapID INT UNSIGNED,
instanceId INT UNSIGNED,
instanceTimer INT UNSIGNED,
deaths INT UNSIGNED,
PRIMARY KEY (groupId)
);
-- Used for tracking current instance data for players
DROP TABLE IF EXISTS mp_player_instance_data;
CREATE TABLE mp_player_instance_data(
guid INT UNSIGNED NOT NULL DEFAULT '0',
difficulty INT UNSIGNED NOT NULL DEFAULT '3',
mapId INT UNSIGNED NOT NULL,
instanceId INT UNSIGNED,
deaths INT UNSIGNED NOT NULL,
PRIMARY KEY (guid, mapId, instanceId)
);
-- Used for tracking player deaths to specific creatures in mythic runs
DROP TABLE IF EXISTS mp_player_death_stats;
CREATE TABLE mp_player_death_stats(
guid INT UNSIGNED NOT NULL DEFAULT '0',
creatureEntry INT UNSIGNED NOT NULL,
numDeaths INT UNSIGNED NOT NULL DEFAULT '0',
lastUpdated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (guid),
INDEX idx_creature (creatureEntry)
);
--- Used for tracking player runs in mythic dungeons
DROP TABLE IF EXISTS mp_player_runs;
CREATE TABLE mp_player_runs(
runId INT UNSIGNED AUTO_INCREMENT,
guid INT UNSIGNED NOT NULL DEFAULT '0',
difficulty INT UNSIGNED NOT NULL DEFAULT '3',
mapId INT UNSIGNED,
groupDeaths INT UNSIGNED,
personalDeaths INT UNSIGNED,
completeTime INT UNSIGNED,
botCount TINYINT UNSIGNED DEFAULT '0',
PRIMARY KEY (runId),
INDEX idx_guid (guid),
INDEX idx_mapId (mapId)
);
--- Used for tracking player stats in mythic dungeons
DROP TABLE IF EXISTS mp_player_stats;
CREATE TABLE mp_player_stats (
guid INT UNSIGNED NOT NULL DEFAULT '0',
mapId INT UNSIGNED NOT NULL DEFAULT '0',
difficulty TINYINT UNSIGNED NOT NULL DEFAULT '0',
deaths INT UNSIGNED DEFAULT '0',
runs INT UNSIGNED DEFAULT '0',
completions INT UNSIGNED DEFAULT '0',
totalTime INT UNSIGNED DEFAULT '0',
bestTime INT UNSIGNED DEFAULT '0',
PRIMARY KEY (guid, mapId, difficulty)
);
--- Used to enable custom stat upgrads from materials and drops in mythic dungeons
DROP TABLE IF EXISTS mp_player_stat_upgrades;
CREATE TABLE mp_player_stat_upgrades
(
guid INT UNSIGNED NOT NULL,
statTypeId INT UNSIGNED NOT NULL,
bonus Float NOT NULL,
upgradeRank INT UNSIGNED NOT NULL,
materialSpent INT UNSIGNED NOT NULL DEFAULT '0',
diceSpent INT UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (guid, statTypeId)
);

View File

@@ -0,0 +1,48 @@
-- Used to track upgrade ranks for stat improvements and min/max values
DROP TABLE IF EXISTS mp_stat_upgrade_ranks;
DROP TABLE IF EXISTS mp_upgrade_ranks;
CREATE TABLE mp_upgrade_ranks (
upgradeRank INT UNSIGNED NOT NULL,
advancementId INT UNSIGNED NOT NULL,
materialId1 INT UNSIGNED NOT NULL,
materialId2 INT UNSIGNED NOT NULL,
materialId3 INT UNSIGNED NOT NULL,
materialCost1 INT UNSIGNED NOT NULL,
materialCost2 INT UNSIGNED NOT NULL,
materialCost3 INT UNSIGNED NOT NULL,
minIncrease1 INT UNSIGNED NOT NULL,
maxIncrease1 INT UNSIGNED NOT NULL,
minIncrease2 INT UNSIGNED NOT NULL,
maxIncrease2 INT UNSIGNED NOT NULL,
minIncrease3 INT UNSIGNED NOT NULL,
maxIncrease3 INT UNSIGNED NOT NULL,
chanceCost1 INT UNSIGNED NOT NULL,
chanceCost2 INT UNSIGNED NOT NULL,
chanceCost3 INT UNSIGNED NOT NULL,
PRIMARY KEY (upgradeRank, advancementId)
);
-- Used to allocate trade materials based on slot upgrades
DROP TABLE IF EXISTS mp_material_types;
CREATE TABLE mp_material_types (
materialId INT UNSIGNED NOT NULL,
entry INT UNSIGNED NOT NULL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY (materialId, entry)
);
-- Description: Scale factors for Mythic+ dungeons used to normalize dungeon difficulty across expansions.
DROP TABLE IF EXISTS mp_scale_factors;
CREATE TABLE mp_scale_factors (
mapId SMALLINT NOT NULL,
dmg_bonus INT DEFAULT '100',
spell_bonus INT DEFAULT '100',
hp_bonus INT DEFAULT '100',
difficulty INT DEFAULT '100',
max INT DEFAULT '100',
PRIMARY KEY (mapId)
);

View File

@@ -0,0 +1,537 @@
TRUNCATE TABLE mp_material_types;
-- MaterialID for all droppable cloth items
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT DISTINCT
1 AS materialId, -- Assign the same materialId to all rows
it.entry,
it.name
FROM item_template it
LEFT JOIN creature_loot_template clt ON (it.entry = clt.Item)
WHERE it.name LIKE '%Cloth'
AND it.class = 7
AND it.subclass = 5
AND it.name NOT LIKE 'Bolt%'
AND it.VerifiedBuild = 12340
AND clt.Entry IS NOT NULL;
-- Crafted cloth items that are not dropped by enemies
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
2 AS materialId, -- Assign the same materialId to all rows
it.entry,
it.name
FROM item_template it
LEFT JOIN creature_loot_template clt ON (it.entry = clt.Item)
WHERE it.name LIKE '%Cloth'
AND it.class = 7
AND it.subclass = 5
AND it.name NOT LIKE 'Bolt%'
AND it.VerifiedBuild = 12340
AND clt.Entry IS NULL;
-- Common Herbs available to herbalists
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
3 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE it.class = 7
AND it.subclass = 9
AND it.VerifiedBuild = 12340
AND it.RequiredSkillRank != 0
AND it.name NOT IN (
'Black Lotus',
'Ghost Mushroom',
'Dreamfoil',
'Bloodvine',
'Fel Lotus',
'Netherbloom',
'Nightmare Vine',
'Frost Lotus',
"Adder's Tongue",
'Fire Leaf',
'Lichbloom',
'Icethorn',
"Talandra's Rose"
);
-- Rare Herbs that are harder to find or drop in raids
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
4 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE it.name IN (
'Black Lotus', -- Classic: Rare spawn in Dire Maul and outdoor zones, extremely rare in raids.
'Ghost Mushroom', -- Classic: Found in Maraudon (Dungeon-only herb).
'Bloodvine', -- Classic: Drops from enemies in Zul'Gurub.
'Fel Lotus', -- TBC: Extremely rare herb drop while gathering Felweed, Dreaming Glory, etc., in Outland.
'Netherbloom', -- TBC: Rare spawn in Netherstorm, also dropped by some elites.
'Nightmare Vine', -- TBC: Found in Shadowmoon Valley and drops in Black Temple from specific mobs.
'Frost Lotus', -- Wrath: Extremely rare spawn from herb nodes or dungeons like Utgarde Pinnacle.
"Adder's Tongue", -- Wrath: Rare spawn from dungeon nodes in Drak'Tharon Keep and Gundrak.
'Fire Leaf', -- Wrath: Rare herb found in Wintergrasp, sometimes in dungeon PvP areas.
'Icethorn', -- Wrath: Harvested from specific dungeon nodes in higher-level Northrend zones.
"Talandra's Rose" -- Wrath: Found sparsely in Zul'Drak dungeons like Gundrak.
);
-- Common ores that are easy to find in the world
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT DISTINCT
5 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE it.class = 7
AND it.subclass = 7
AND it.VerifiedBuild = 12340
AND it.name NOT LIKE '%Bar'
AND it.name NOT LIKE '%Ingot'
AND it.name NOT LIKE '%Stone'
AND it.name NOT LIKE '%Shard'
AND it.name NOT IN ('Coal', 'Elemental Flux', 'Hardened Khorium')
AND it.name NOT IN ('Elementium Ore', 'Dark Iron Ore', 'Primal Nether', 'Runed Orb', 'Crusader Orb', 'Blood of the Mountain')
ORDER BY it.name;
-- Rare ores that are harder to find or drop in raids
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT DISTINCT
6 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE
it.name Like '%Obsidian Shard%'
OR it.name IN ('Elementium Ore', 'Dark Iron Ore', 'Primal Nether', 'Runed Orb', 'Crusader Orb', 'Blood of the Mountain')
and entry <= 2000000
ORDER BY it.name;
-- Common skins that are easy to find in the world
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
7 AS materialId, -- Unique ID for commonly skinnable materials
it.entry,
it.name
FROM item_template it
WHERE it.name IN (
'Borean Leather',
'Heavy Leather',
'Heavy Hide',
'Light Leather',
'Light Hide',
'Medium Leather',
'Medium Hide',
'Rugged Leather',
'Rugged Hide',
'Thick Leather',
'Thick Hide',
'Knothide Leather',
'Icy Dragonscale',
'Jormungar Scale',
'Nerubian Chitin',
'Turtle Scale'
);
-- Rare skinning material found in the world or dropped by enemies
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
8 AS materialId, -- Unique ID for rare skinnable and non-skinnable materials
it.entry,
it.name
FROM item_template it
WHERE it.name IN (
'Black Dragonscale',
'Blue Dragonscale',
'Green Dragonscale',
'Red Dragonscale',
'Scale of Onyxia',
'Nether Dragonscales',
'Wind Scales',
'Thick Clefthoof Leather',
'Cobra Scales',
'Devilsaur Leather',
'Green Whelp Scale',
'Black Whelp Scale',
'Perfect Deviate Scale',
'Core Leather',
'Dreamscale',
'Primal Bat Leather',
'Primal Tiger Leather'
);
-- Common uncut gems that are easy to find in the world
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
9 AS materialId, -- Unique ID for common uncut gems
it.entry,
it.name
FROM item_template it
WHERE
it.VerifiedBuild = 12340 -- Wrath of the Lich King build
AND it.name NOT LIKE '%Cut%' -- Exclude pre-cut gems
AND it.name IN (
'Malachite',
'Tigerseye',
'Moss Agate',
'Shadowgem',
'Jade',
'Lesser Moonstone',
'Citrine',
'Aquamarine',
'Star Ruby',
'Azerothian Diamond',
'Huge Emerald',
'Large Opal',
'Blue Sapphire',
'Arcane Crystal',
'Bloodstone',
'Sun Crystal',
'Chalcedony',
'Dark Jade',
'Shadow Crystal',
'Huge Citrine',
'Monarch Topaz',
'Forest Emerald',
'Sky Sapphire',
"Autumn's Glow",
'Twilight Opal',
'Scarlet Ruby'
);
-- Rare gems that are harder to find or drop in raids
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
10 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- TBC Epic Gems
'Crimson Spinel',
'Empyrean Sapphire',
'Lionseye',
'Shadowsong Amethyst',
'Pyrestone',
'Seaspray Emerald',
-- WotLK Epic Gems
'Cardinal Ruby',
'Ametrine',
"King's Amber",
'Eye of Zul',
'Majestic Zircon',
'Dreadstone'
);
-- common enchantment materials that are easy to find in the world
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
11 AS materialId, -- Example materialId for enchantment items
it.entry,
it.name
FROM item_template it
WHERE
it.class = 7
AND it.subclass = 12
AND it.VerifiedBuild = 12340
AND it.name NOT LIKE '%Rod%'
AND it.name NOT IN (
-- Classic Rare Enchantment Items
'Righteous Orb', -- Drops in Stratholme.
'Lava Core', -- Rare drop in Molten Core.
'Black Diamond', -- Rare drop in Blackrock Depths and related areas.
'Essence of the Red', -- Reward from Vaelastrasz in Blackwing Lair.
'Onyxia Scale', -- Dropped by Onyxia, used in fire resistance recipes.
-- TBC Rare Enchantment Items
'Primal Might', -- Crafted using all Primals, very rare due to material costs.
'Heart of Darkness', -- Rare drop in Black Temple.
'Sunmote', -- Drops in Sunwell Plateau.
'Nether Vortex', -- Raid-exclusive crafting material in TBC.
-- WotLK Rare Enchantment Items
'Eternal Might', -- Custom material replacing Eternal combinations, rare in high-level content.
'Frozen Orb', -- Drops in WotLK heroic dungeons.
'Runed Orb', -- Drops in Ulduar.
'Crusader Orb' -- Drops in Trial of the Crusader raid.
);
-- Rare enchantment materials that are harder to find or drop in raids
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
12 AS materialId, -- Example materialId for enchantment items
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic Rare Enchantment Items
'Righteous Orb', -- Drops in Stratholme.
'Lava Core', -- Rare drop in Molten Core.
'Black Diamond', -- Rare drop in Blackrock Depths and related areas.
'Essence of the Red', -- Reward from Vaelastrasz in Blackwing Lair.
'Onyxia Scale', -- Dropped by Onyxia, used in fire resistance recipes.
-- TBC Rare Enchantment Items
'Primal Might', -- Crafted using all Primals, very rare due to material costs.
'Heart of Darkness', -- Rare drop in Black Temple.
'Sunmote', -- Drops in Sunwell Plateau.
'Nether Vortex', -- Raid-exclusive crafting material in TBC.
-- WotLK Rare Enchantment Items
'Eternal Might', -- Custom material replacing Eternal combinations, rare in high-level content.
'Frozen Orb', -- Drops in WotLK heroic dungeons.
'Runed Orb', -- Drops in Ulduar.
'Crusader Orb' -- Drops in Trial of the Crusader raid.
);
-- Common materials that related to water / frost
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
13 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Elemental Water',
'Essence of Water',
'Glacial Fragments',
-- TBC
'Arctic Fur',
'Thick Dawnstone',
-- WotLK
'Frost Lotus',
'Frostweave Cloth'
);
-- Rare materials that related to water / frost
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
14 AS materialId, -- Unique ID for rare frost resistance materials
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Frozen Rune',
'Core of Earth',
-- TBC
'Frost-Infused Shard',
'Nether Residuum',
'Primal Water',
-- WotLK
'Primordial Saronite',
'Icy Dragonscale',
'Eternal Water'
);
-- Common materials that relate to fire
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
15 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Elemental Fire',
'Essence of Fire',
'Heart of Fire',
'Small Flame Sac',
'Core of Flame',
-- TBC
'Mote of Fire',
'Flame Spessarite', -- Common fire-themed gem for jewelcrafting.
'Smoldering Core', -- Fire-themed drop from elementals.
-- WotLK
'Fire Leaf',
'Crystallized Fire'
);
-- Rare materials that relate to fire resistance gear or fire-themed weapons
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
16 AS materialId, -- Unique ID for rare fire resistance and fire weapon crafting materials
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Fiery Core', -- Key material for crafting fire resistance gear in Molten Core.
'Lava Core', -- Used in crafting fire resistance gear and recipes.
'Sulfuron Ingot', -- Rare drop from Molten Core, used for crafting legendary fire weapons.
-- TBC
'Primal Fire', -- Core material for fire crafting in TBC.
'Hardened Adamantite Bar', -- Material for crafting fire-resistant shields and gear.
-- WotLK
'Primordial Saronite', -- Used for crafting high-level resistance and utility gear.
'Eternal Fire', -- Central material for fire-themed crafting.
'Runed Orb', -- Used in crafting fire-related weapons and gear.
'Smouldering Crystals', -- High-value fire crafting material from fire-themed mobs.
'Burning Embers' -- Rare crafting material for WotLK fire-themed items.
);
-- Common materials that relate to nature
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
17 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Elemental Earth',
'Essence of Earth',
'Living Essence',
'Small Venom Sac',
"Un'Goro Soil",
'Ichor of Undeath', -- Used in nature-themed recipes.
-- TBC
'Mote of Earth', -- Combines into Primal Earth.
'Mote of Life', -- Combines into Primal Life.
'Terocone', -- Nature-themed herb used in alchemy.
-- WotLK
'Crystallized Earth', -- Combines into Eternal Earth.
'Crystallized Life', -- Combines into Eternal Life.
'Icethorn', -- Herb with thematic ties to nature resistance potions.
'Ancient Moss'
);
-- Rare Nature resistance materials
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
18 AS materialId, -- Unique ID for rare nature resistance and nature weapon crafting materials
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Heart of the Wild', -- Rare material for crafting nature resistance gear.
'Living Essence', -- Rare crafting material dropped by nature elementals.
"Gahz'rilla's Electrified Scale",-- Rare item from Zul'Farrak for nature resistance gear.
-- TBC
'Primal Earth', -- Central crafting material for resistance and nature-themed items.
'Primal Life', -- Rare material for nature-themed crafting.
-- WotLK
'Eternal Earth', -- Central material for nature-themed crafting.
'Eternal Life', -- Rare material for nature-themed crafting.
'Runed Orb', -- Rare crafting material for nature-themed gear.
"Adder's Tongue" -- Herb used in nature resistance recipes.
);
-- Common materials that relate to shadow
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
19 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Shadowgem', -- Common low-level gem with shadow theme.
'Elemental Shadow', -- Basic crafting reagent from shadow elementals.
'Essence of Shadow', -- Dropped by shadow-themed elementals in high-level zones.
-- TBC
'Mote of Shadow', -- Combines into Primal Shadow, used in crafting.
'Dark Rune', -- Dropped in Scholomance and used for shadow resistance crafting.
-- WotLK
'Crystallized Shadow', -- Combines into Eternal Shadow.
'Deadnettle', -- Herb with thematic ties to shadow crafting.
'Shadowy Essence' -- Rare Northrend material for shadow crafting.
);
-- Rare materials that relate to shadow resistance gear or shadow-themed weapons
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
20 AS materialId, -- Unique ID for rare shadow resistance and shadow weapon crafting materials
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Heart of Darkness', -- Rare drop used for shadow resistance gear in TBC and Classic.
'Shadow Oil', -- Crafted alchemical reagent for shadow resistance.
'Black Lotus', -- Rare herb used in high-level shadow potions.
-- TBC
'Primal Shadow', -- Central crafting material for shadow resistance.
'Nightmare Seed', -- Rare herb used in shadow resistance recipes.
'Felcloth', -- Cloth with shadow resistance used in tailoring.
-- WotLK
'Eternal Shadow', -- Core material for shadow resistance crafting.
'Runed Orb' -- Rare material for shadow-themed gear and weapons.
);
-- Common materials that relate to arcane resistance gear or arcane-themed weapons
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
21 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Arcane Crystal', -- Rare mining material used in Arcanite Bars.
'Essence of Magic', -- Dropped by arcane elementals.
'Lesser Magic Essence', -- Early enchanting material.
'Greater Magic Essence', -- Enchanting material with arcane ties.
-- TBC
'Mote of Mana', -- Combines into Primal Mana.
-- WotLK
'Crystallized Mana' -- Combines into Eternal Mana.
);
-- Rare materials that relate to arcane resistance gear or arcane-themed weapons
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
22 AS materialId, -- Unique ID for rare arcane resistance and arcane weapon crafting materials
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Golden Pearl', -- Rare material used in crafting arcane resistance gear.
'Arcanite Bar', -- Transmuted from Arcane Crystals, used in crafting high-end gear.
-- TBC
'Primal Mana', -- Essential for arcane resistance crafting.
'Nether Vortex', -- Raid-exclusive crafting material.
'Void Sphere', -- Rare arcane-themed gem obtained through badge purchases or raids.
-- WotLK
'Eternal Mana', -- Central crafting material for arcane resistance gear.
'Saronite Animus' -- Rare drop from arcane-themed enemies in Icecrown Citadel.
);

View File

@@ -1,17 +1,7 @@
-- 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 INT,
spell_bonus INT,
hp_bonus INT,
difficulty INT,
max INT
);
-- 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)
INSERT INTO mp_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
@@ -29,7 +19,7 @@ VALUES
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)
INSERT INTO mp_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
@@ -41,7 +31,7 @@ VALUES
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)
INSERT INTO mp_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
@@ -54,7 +44,7 @@ VALUES
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)
INSERT INTO mp_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
@@ -66,7 +56,7 @@ VALUES
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)
INSERT INTO mp_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
@@ -77,7 +67,7 @@ VALUES
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)
INSERT INTO mp_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

View File

@@ -0,0 +1,253 @@
-- SQL Script to Insert 50 Ranks for Each Stat
REPLACE INTO mp_upgrade_ranks (upgradeRank, advancementId, materialId1, materialCost1, materialId2, materialCost2, materialId3, materialCost3, minIncrease1, maxIncrease1, minIncrease2, maxIncrease2, minIncrease3, maxIncrease3, chanceCost1, chanceCost2, chanceCost3) VALUES
(1, 3, 7, 100, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 20, 50, 75),
(2, 3, 7, 150, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 23, 53, 78),
(3, 3, 7, 200, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 26, 56, 81),
(4, 3, 7, 250, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 29, 59, 84),
(5, 3, 7, 300, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 32, 62, 87),
(6, 3, 7, 350, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 35, 65, 90),
(7, 3, 7, 400, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 38, 68, 93),
(8, 3, 7, 450, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 41, 71, 96),
(9, 3, 7, 500, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 44, 74, 99),
(10, 3, 7, 550, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 47, 77, 102),
(11, 3, 7, 500, 8, 0, 0, 0, 3, 12, 7, 12, 12, 15, 50, 80, 105),
(12, 3, 7, 525, 8, 10, 0, 0, 3, 12, 7, 12, 12, 15, 53, 83, 108),
(13, 3, 7, 550, 8, 20, 0, 0, 3, 12, 7, 12, 12, 15, 56, 86, 111),
(14, 3, 7, 575, 8, 30, 0, 0, 3, 12, 7, 12, 12, 15, 59, 89, 114),
(15, 3, 7, 600, 8, 40, 0, 0, 3, 12, 7, 12, 12, 15, 62, 92, 117),
(16, 3, 7, 625, 8, 50, 0, 0, 3, 12, 7, 12, 12, 15, 65, 95, 120),
(17, 3, 7, 650, 8, 60, 0, 0, 3, 12, 7, 12, 12, 15, 68, 98, 123),
(18, 3, 7, 675, 8, 70, 0, 0, 3, 12, 7, 12, 12, 15, 71, 101, 126),
(19, 3, 7, 700, 8, 80, 0, 0, 3, 12, 7, 12, 12, 15, 74, 104, 129),
(20, 3, 7, 725, 8, 90, 0, 0, 3, 12, 7, 12, 12, 15, 77, 107, 132),
(21, 3, 7, 750, 8, 100, 0, 0, 5, 14, 9, 14, 16, 19, 80, 110, 135),
(22, 3, 7, 775, 8, 110, 0, 0, 5, 14, 9, 14, 16, 19, 83, 113, 138),
(23, 3, 7, 800, 8, 120, 0, 0, 5, 14, 9, 14, 16, 19, 86, 116, 141),
(24, 3, 7, 825, 8, 130, 0, 0, 5, 14, 9, 14, 16, 19, 89, 119, 144),
(25, 3, 7, 850, 8, 140, 0, 0, 5, 14, 9, 14, 16, 19, 92, 122, 147),
(26, 3, 7, 875, 8, 150, 0, 0, 5, 14, 9, 14, 16, 19, 95, 125, 150),
(27, 3, 7, 900, 8, 160, 0, 0, 5, 14, 9, 14, 16, 19, 98, 128, 153),
(28, 3, 7, 925, 8, 170, 0, 0, 5, 14, 9, 14, 16, 19, 101, 131, 156),
(29, 3, 7, 950, 8, 180, 0, 0, 5, 14, 9, 14, 16, 19, 104, 134, 159),
(30, 3, 7, 1000, 8, 190, 20, 3, 5, 14, 9, 14, 16, 19, 107, 137, 162),
(31, 3, 7, 1018, 8, 200, 20, 6, 7, 16, 11, 16, 20, 23, 110, 140, 165),
(32, 3, 7, 1036, 8, 210, 20, 9, 7, 16, 11, 16, 20, 23, 113, 143, 168),
(33, 3, 7, 1054, 8, 220, 20, 12, 7, 16, 11, 16, 20, 23, 116, 146, 171),
(34, 3, 7, 1072, 8, 230, 20, 15, 7, 16, 11, 16, 20, 23, 119, 149, 174),
(35, 3, 7, 1090, 8, 240, 20, 18, 7, 16, 11, 16, 20, 23, 122, 152, 177),
(36, 3, 7, 1108, 8, 250, 20, 21, 7, 16, 11, 16, 20, 23, 125, 155, 180),
(37, 3, 7, 1126, 8, 260, 20, 24, 7, 16, 11, 16, 20, 23, 128, 158, 183),
(38, 3, 7, 1144, 8, 270, 20, 27, 7, 16, 11, 16, 20, 23, 131, 161, 186),
(39, 3, 7, 1162, 8, 280, 20, 30, 7, 16, 11, 16, 20, 23, 134, 164, 189),
(40, 3, 7, 1180, 8, 290, 20, 33, 7, 16, 11, 16, 20, 23, 137, 167, 192),
(41, 3, 7, 1198, 8, 300, 20, 36, 9, 18, 13, 18, 24, 27, 140, 170, 195),
(42, 3, 7, 1216, 8, 310, 20, 39, 9, 18, 13, 18, 24, 27, 143, 173, 198),
(43, 3, 7, 1234, 8, 320, 20, 42, 9, 18, 13, 18, 24, 27, 146, 176, 201),
(44, 3, 7, 1252, 8, 330, 20, 45, 9, 18, 13, 18, 24, 27, 149, 179, 204),
(45, 3, 7, 1270, 8, 340, 20, 48, 9, 18, 13, 18, 24, 27, 152, 182, 207),
(46, 3, 7, 1288, 8, 350, 20, 51, 9, 18, 13, 18, 24, 27, 155, 185, 210),
(47, 3, 7, 1306, 8, 360, 20, 54, 9, 18, 13, 18, 24, 27, 158, 188, 213),
(48, 3, 7, 1324, 8, 370, 20, 57, 9, 18, 13, 18, 24, 27, 161, 191, 216),
(49, 3, 7, 1342, 8, 380, 20, 60, 9, 18, 13, 18, 24, 27, 164, 194, 219),
(50, 3, 7, 1360, 8, 390, 20, 63, 9, 18, 13, 18, 24, 27, 167, 197, 222),
(1, 4, 9, 100, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 20, 50, 75),
(2, 4, 9, 150, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 23, 53, 78),
(3, 4, 9, 200, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 26, 56, 81),
(4, 4, 9, 250, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 29, 59, 84),
(5, 4, 9, 300, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 32, 62, 87),
(6, 4, 9, 350, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 35, 65, 90),
(7, 4, 9, 400, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 38, 68, 93),
(8, 4, 9, 450, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 41, 71, 96),
(9, 4, 9, 500, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 44, 74, 99),
(10, 4, 9, 550, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 47, 77, 102),
(11, 4, 9, 500, 10, 0, 0, 0, 3, 12, 7, 12, 12, 15, 50, 80, 105),
(12, 4, 9, 525, 10, 10, 0, 0, 3, 12, 7, 12, 12, 15, 53, 83, 108),
(13, 4, 9, 550, 10, 20, 0, 0, 3, 12, 7, 12, 12, 15, 56, 86, 111),
(14, 4, 9, 575, 10, 30, 0, 0, 3, 12, 7, 12, 12, 15, 59, 89, 114),
(15, 4, 9, 600, 10, 40, 0, 0, 3, 12, 7, 12, 12, 15, 62, 92, 117),
(16, 4, 9, 625, 10, 50, 0, 0, 3, 12, 7, 12, 12, 15, 65, 95, 120),
(17, 4, 9, 650, 10, 60, 0, 0, 3, 12, 7, 12, 12, 15, 68, 98, 123),
(18, 4, 9, 675, 10, 70, 0, 0, 3, 12, 7, 12, 12, 15, 71, 101, 126),
(19, 4, 9, 700, 10, 80, 0, 0, 3, 12, 7, 12, 12, 15, 74, 104, 129),
(20, 4, 9, 725, 10, 90, 0, 0, 3, 12, 7, 12, 12, 15, 77, 107, 132),
(21, 4, 9, 750, 10, 100, 0, 0, 5, 14, 9, 14, 16, 19, 80, 110, 135),
(22, 4, 9, 775, 10, 110, 0, 0, 5, 14, 9, 14, 16, 19, 83, 113, 138),
(23, 4, 9, 800, 10, 120, 0, 0, 5, 14, 9, 14, 16, 19, 86, 116, 141),
(24, 4, 9, 825, 10, 130, 0, 0, 5, 14, 9, 14, 16, 19, 89, 119, 144),
(25, 4, 9, 850, 10, 140, 0, 0, 5, 14, 9, 14, 16, 19, 92, 122, 147),
(26, 4, 9, 875, 10, 150, 0, 0, 5, 14, 9, 14, 16, 19, 95, 125, 150),
(27, 4, 9, 900, 10, 160, 0, 0, 5, 14, 9, 14, 16, 19, 98, 128, 153),
(28, 4, 9, 925, 10, 170, 0, 0, 5, 14, 9, 14, 16, 19, 101, 131, 156),
(29, 4, 9, 950, 10, 180, 0, 0, 5, 14, 9, 14, 16, 19, 104, 134, 159),
(30, 4, 9, 1000, 10, 190, 20, 3, 5, 14, 9, 14, 16, 19, 107, 137, 162),
(31, 4, 9, 1018, 10, 200, 20, 6, 7, 16, 11, 16, 20, 23, 110, 140, 165),
(32, 4, 9, 1036, 10, 210, 20, 9, 7, 16, 11, 16, 20, 23, 113, 143, 168),
(33, 4, 9, 1054, 10, 220, 20, 12, 7, 16, 11, 16, 20, 23, 116, 146, 171),
(34, 4, 9, 1072, 10, 230, 20, 15, 7, 16, 11, 16, 20, 23, 119, 149, 174),
(35, 4, 9, 1090, 10, 240, 20, 18, 7, 16, 11, 16, 20, 23, 122, 152, 177),
(36, 4, 9, 1108, 10, 250, 20, 21, 7, 16, 11, 16, 20, 23, 125, 155, 180),
(37, 4, 9, 1126, 10, 260, 20, 24, 7, 16, 11, 16, 20, 23, 128, 158, 183),
(38, 4, 9, 1144, 10, 270, 20, 27, 7, 16, 11, 16, 20, 23, 131, 161, 186),
(39, 4, 9, 1162, 10, 280, 20, 30, 7, 16, 11, 16, 20, 23, 134, 164, 189),
(40, 4, 9, 1180, 10, 290, 20, 33, 7, 16, 11, 16, 20, 23, 137, 167, 192),
(41, 4, 9, 1198, 10, 300, 20, 36, 9, 18, 13, 18, 24, 27, 140, 170, 195),
(42, 4, 9, 1216, 10, 310, 20, 39, 9, 18, 13, 18, 24, 27, 143, 173, 198),
(43, 4, 9, 1234, 10, 320, 20, 42, 9, 18, 13, 18, 24, 27, 146, 176, 201),
(44, 4, 9, 1252, 10, 330, 20, 45, 9, 18, 13, 18, 24, 27, 149, 179, 204),
(45, 4, 9, 1270, 10, 340, 20, 48, 9, 18, 13, 18, 24, 27, 152, 182, 207),
(46, 4, 9, 1288, 10, 350, 20, 51, 9, 18, 13, 18, 24, 27, 155, 185, 210),
(47, 4, 9, 1306, 10, 360, 20, 54, 9, 18, 13, 18, 24, 27, 158, 188, 213),
(48, 4, 9, 1324, 10, 370, 20, 57, 9, 18, 13, 18, 24, 27, 161, 191, 216),
(49, 4, 9, 1342, 10, 380, 20, 60, 9, 18, 13, 18, 24, 27, 164, 194, 219),
(50, 4, 9, 1360, 10, 390, 20, 63, 9, 18, 13, 18, 24, 27, 167, 197, 222),
(1, 0, 1, 100, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 20, 50, 75),
(2, 0, 1, 150, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 23, 53, 78),
(3, 0, 1, 200, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 26, 56, 81),
(4, 0, 1, 250, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 29, 59, 84),
(5, 0, 1, 300, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 32, 62, 87),
(6, 0, 1, 350, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 35, 65, 90),
(7, 0, 1, 400, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 38, 68, 93),
(8, 0, 1, 450, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 41, 71, 96),
(9, 0, 1, 500, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 44, 74, 99),
(10, 0, 1, 550, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 47, 77, 102),
(11, 0, 1, 500, 2, 0, 0, 0, 3, 12, 7, 12, 12, 15, 50, 80, 105),
(12, 0, 1, 525, 2, 10, 0, 0, 3, 12, 7, 12, 12, 15, 53, 83, 108),
(13, 0, 1, 550, 2, 20, 0, 0, 3, 12, 7, 12, 12, 15, 56, 86, 111),
(14, 0, 1, 575, 2, 30, 0, 0, 3, 12, 7, 12, 12, 15, 59, 89, 114),
(15, 0, 1, 600, 2, 40, 0, 0, 3, 12, 7, 12, 12, 15, 62, 92, 117),
(16, 0, 1, 625, 2, 50, 0, 0, 3, 12, 7, 12, 12, 15, 65, 95, 120),
(17, 0, 1, 650, 2, 60, 0, 0, 3, 12, 7, 12, 12, 15, 68, 98, 123),
(18, 0, 1, 675, 2, 70, 0, 0, 3, 12, 7, 12, 12, 15, 71, 101, 126),
(19, 0, 1, 700, 2, 80, 0, 0, 3, 12, 7, 12, 12, 15, 74, 104, 129),
(20, 0, 1, 725, 2, 90, 0, 0, 3, 12, 7, 12, 12, 15, 77, 107, 132),
(21, 0, 1, 750, 2, 100, 0, 0, 5, 14, 9, 14, 16, 19, 80, 110, 135),
(22, 0, 1, 775, 2, 110, 0, 0, 5, 14, 9, 14, 16, 19, 83, 113, 138),
(23, 0, 1, 800, 2, 120, 0, 0, 5, 14, 9, 14, 16, 19, 86, 116, 141),
(24, 0, 1, 825, 2, 130, 0, 0, 5, 14, 9, 14, 16, 19, 89, 119, 144),
(25, 0, 1, 850, 2, 140, 0, 0, 5, 14, 9, 14, 16, 19, 92, 122, 147),
(26, 0, 1, 875, 2, 150, 0, 0, 5, 14, 9, 14, 16, 19, 95, 125, 150),
(27, 0, 1, 900, 2, 160, 0, 0, 5, 14, 9, 14, 16, 19, 98, 128, 153),
(28, 0, 1, 925, 2, 170, 0, 0, 5, 14, 9, 14, 16, 19, 101, 131, 156),
(29, 0, 1, 950, 2, 180, 0, 0, 5, 14, 9, 14, 16, 19, 104, 134, 159),
(30, 0, 1, 1000, 2, 190, 20, 3, 5, 14, 9, 14, 16, 19, 107, 137, 162),
(31, 0, 1, 1018, 2, 200, 20, 6, 7, 16, 11, 16, 20, 23, 110, 140, 165),
(32, 0, 1, 1036, 2, 210, 20, 9, 7, 16, 11, 16, 20, 23, 113, 143, 168),
(33, 0, 1, 1054, 2, 220, 20, 12, 7, 16, 11, 16, 20, 23, 116, 146, 171),
(34, 0, 1, 1072, 2, 230, 20, 15, 7, 16, 11, 16, 20, 23, 119, 149, 174),
(35, 0, 1, 1090, 2, 240, 20, 18, 7, 16, 11, 16, 20, 23, 122, 152, 177),
(36, 0, 1, 1108, 2, 250, 20, 21, 7, 16, 11, 16, 20, 23, 125, 155, 180),
(37, 0, 1, 1126, 2, 260, 20, 24, 7, 16, 11, 16, 20, 23, 128, 158, 183),
(38, 0, 1, 1144, 2, 270, 20, 27, 7, 16, 11, 16, 20, 23, 131, 161, 186),
(39, 0, 1, 1162, 2, 280, 20, 30, 7, 16, 11, 16, 20, 23, 134, 164, 189),
(40, 0, 1, 1180, 2, 290, 20, 33, 7, 16, 11, 16, 20, 23, 137, 167, 192),
(41, 0, 1, 1198, 2, 300, 20, 36, 9, 18, 13, 18, 24, 27, 140, 170, 195),
(42, 0, 1, 1216, 2, 310, 20, 39, 9, 18, 13, 18, 24, 27, 143, 173, 198),
(43, 0, 1, 1234, 2, 320, 20, 42, 9, 18, 13, 18, 24, 27, 146, 176, 201),
(44, 0, 1, 1252, 2, 330, 20, 45, 9, 18, 13, 18, 24, 27, 149, 179, 204),
(45, 0, 1, 1270, 2, 340, 20, 48, 9, 18, 13, 18, 24, 27, 152, 182, 207),
(46, 0, 1, 1288, 2, 350, 20, 51, 9, 18, 13, 18, 24, 27, 155, 185, 210),
(47, 0, 1, 1306, 2, 360, 20, 54, 9, 18, 13, 18, 24, 27, 158, 188, 213),
(48, 0, 1, 1324, 2, 370, 20, 57, 9, 18, 13, 18, 24, 27, 161, 191, 216),
(49, 0, 1, 1342, 2, 380, 20, 60, 9, 18, 13, 18, 24, 27, 164, 194, 219),
(50, 0, 1, 1360, 2, 390, 20, 63, 9, 18, 13, 18, 24, 27, 167, 197, 222),
(1, 1, 3, 100, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 20, 50, 75),
(2, 1, 3, 150, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 23, 53, 78),
(3, 1, 3, 200, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 26, 56, 81),
(4, 1, 3, 250, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 29, 59, 84),
(5, 1, 3, 300, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 32, 62, 87),
(6, 1, 3, 350, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 35, 65, 90),
(7, 1, 3, 400, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 38, 68, 93),
(8, 1, 3, 450, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 41, 71, 96),
(9, 1, 3, 500, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 44, 74, 99),
(10, 1, 3, 550, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 47, 77, 102),
(11, 1, 3, 500, 4, 0, 0, 0, 3, 12, 7, 12, 12, 15, 50, 80, 105),
(12, 1, 3, 525, 4, 10, 0, 0, 3, 12, 7, 12, 12, 15, 53, 83, 108),
(13, 1, 3, 550, 4, 20, 0, 0, 3, 12, 7, 12, 12, 15, 56, 86, 111),
(14, 1, 3, 575, 4, 30, 0, 0, 3, 12, 7, 12, 12, 15, 59, 89, 114),
(15, 1, 3, 600, 4, 40, 0, 0, 3, 12, 7, 12, 12, 15, 62, 92, 117),
(16, 1, 3, 625, 4, 50, 0, 0, 3, 12, 7, 12, 12, 15, 65, 95, 120),
(17, 1, 3, 650, 4, 60, 0, 0, 3, 12, 7, 12, 12, 15, 68, 98, 123),
(18, 1, 3, 675, 4, 70, 0, 0, 3, 12, 7, 12, 12, 15, 71, 101, 126),
(19, 1, 3, 700, 4, 80, 0, 0, 3, 12, 7, 12, 12, 15, 74, 104, 129),
(20, 1, 3, 725, 4, 90, 0, 0, 3, 12, 7, 12, 12, 15, 77, 107, 132),
(21, 1, 3, 750, 4, 100, 0, 0, 5, 14, 9, 14, 16, 19, 80, 110, 135),
(22, 1, 3, 775, 4, 110, 0, 0, 5, 14, 9, 14, 16, 19, 83, 113, 138),
(23, 1, 3, 800, 4, 120, 0, 0, 5, 14, 9, 14, 16, 19, 86, 116, 141),
(24, 1, 3, 825, 4, 130, 0, 0, 5, 14, 9, 14, 16, 19, 89, 119, 144),
(25, 1, 3, 850, 4, 140, 0, 0, 5, 14, 9, 14, 16, 19, 92, 122, 147),
(26, 1, 3, 875, 4, 150, 0, 0, 5, 14, 9, 14, 16, 19, 95, 125, 150),
(27, 1, 3, 900, 4, 160, 0, 0, 5, 14, 9, 14, 16, 19, 98, 128, 153),
(28, 1, 3, 925, 4, 170, 0, 0, 5, 14, 9, 14, 16, 19, 101, 131, 156),
(29, 1, 3, 950, 4, 180, 0, 0, 5, 14, 9, 14, 16, 19, 104, 134, 159),
(30, 1, 3, 1000, 4, 190, 20, 3, 5, 14, 9, 14, 16, 19, 107, 137, 162),
(31, 1, 3, 1018, 4, 200, 20, 6, 7, 16, 11, 16, 20, 23, 110, 140, 165),
(32, 1, 3, 1036, 4, 210, 20, 9, 7, 16, 11, 16, 20, 23, 113, 143, 168),
(33, 1, 3, 1054, 4, 220, 20, 12, 7, 16, 11, 16, 20, 23, 116, 146, 171),
(34, 1, 3, 1072, 4, 230, 20, 15, 7, 16, 11, 16, 20, 23, 119, 149, 174),
(35, 1, 3, 1090, 4, 240, 20, 18, 7, 16, 11, 16, 20, 23, 122, 152, 177),
(36, 1, 3, 1108, 4, 250, 20, 21, 7, 16, 11, 16, 20, 23, 125, 155, 180),
(37, 1, 3, 1126, 4, 260, 20, 24, 7, 16, 11, 16, 20, 23, 128, 158, 183),
(38, 1, 3, 1144, 4, 270, 20, 27, 7, 16, 11, 16, 20, 23, 131, 161, 186),
(39, 1, 3, 1162, 4, 280, 20, 30, 7, 16, 11, 16, 20, 23, 134, 164, 189),
(40, 1, 3, 1180, 4, 290, 20, 33, 7, 16, 11, 16, 20, 23, 137, 167, 192),
(41, 1, 3, 1198, 4, 300, 20, 36, 9, 18, 13, 18, 24, 27, 140, 170, 195),
(42, 1, 3, 1216, 4, 310, 20, 39, 9, 18, 13, 18, 24, 27, 143, 173, 198),
(43, 1, 3, 1234, 4, 320, 20, 42, 9, 18, 13, 18, 24, 27, 146, 176, 201),
(44, 1, 3, 1252, 4, 330, 20, 45, 9, 18, 13, 18, 24, 27, 149, 179, 204),
(45, 1, 3, 1270, 4, 340, 20, 48, 9, 18, 13, 18, 24, 27, 152, 182, 207),
(46, 1, 3, 1288, 4, 350, 20, 51, 9, 18, 13, 18, 24, 27, 155, 185, 210),
(47, 1, 3, 1306, 4, 360, 20, 54, 9, 18, 13, 18, 24, 27, 158, 188, 213),
(48, 1, 3, 1324, 4, 370, 20, 57, 9, 18, 13, 18, 24, 27, 161, 191, 216),
(49, 1, 3, 1342, 4, 380, 20, 60, 9, 18, 13, 18, 24, 27, 164, 194, 219),
(50, 1, 3, 1360, 4, 390, 20, 63, 9, 18, 13, 18, 24, 27, 167, 197, 222),
(1, 2, 5, 100, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 20, 50, 75),
(2, 2, 5, 150, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 23, 53, 78),
(3, 2, 5, 200, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 26, 56, 81),
(4, 2, 5, 250, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 29, 59, 84),
(5, 2, 5, 300, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 32, 62, 87),
(6, 2, 5, 350, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 35, 65, 90),
(7, 2, 5, 400, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 38, 68, 93),
(8, 2, 5, 450, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 41, 71, 96),
(9, 2, 5, 500, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 44, 74, 99),
(10, 2, 5, 550, 0, 0, 0, 0, 1, 10, 5, 10, 8, 11, 47, 77, 102),
(11, 2, 5, 500, 6, 0, 0, 0, 3, 12, 7, 12, 12, 15, 50, 80, 105),
(12, 2, 5, 525, 6, 10, 0, 0, 3, 12, 7, 12, 12, 15, 53, 83, 108),
(13, 2, 5, 550, 6, 20, 0, 0, 3, 12, 7, 12, 12, 15, 56, 86, 111),
(14, 2, 5, 575, 6, 30, 0, 0, 3, 12, 7, 12, 12, 15, 59, 89, 114),
(15, 2, 5, 600, 6, 40, 0, 0, 3, 12, 7, 12, 12, 15, 62, 92, 117),
(16, 2, 5, 625, 6, 50, 0, 0, 3, 12, 7, 12, 12, 15, 65, 95, 120),
(17, 2, 5, 650, 6, 60, 0, 0, 3, 12, 7, 12, 12, 15, 68, 98, 123),
(18, 2, 5, 675, 6, 70, 0, 0, 3, 12, 7, 12, 12, 15, 71, 101, 126),
(19, 2, 5, 700, 6, 80, 0, 0, 3, 12, 7, 12, 12, 15, 74, 104, 129),
(20, 2, 5, 725, 6, 90, 0, 0, 3, 12, 7, 12, 12, 15, 77, 107, 132),
(21, 2, 5, 750, 6, 100, 0, 0, 5, 14, 9, 14, 16, 19, 80, 110, 135),
(22, 2, 5, 775, 6, 110, 0, 0, 5, 14, 9, 14, 16, 19, 83, 113, 138),
(23, 2, 5, 800, 6, 120, 0, 0, 5, 14, 9, 14, 16, 19, 86, 116, 141),
(24, 2, 5, 825, 6, 130, 0, 0, 5, 14, 9, 14, 16, 19, 89, 119, 144),
(25, 2, 5, 850, 6, 140, 0, 0, 5, 14, 9, 14, 16, 19, 92, 122, 147),
(26, 2, 5, 875, 6, 150, 0, 0, 5, 14, 9, 14, 16, 19, 95, 125, 150),
(27, 2, 5, 900, 6, 160, 0, 0, 5, 14, 9, 14, 16, 19, 98, 128, 153),
(28, 2, 5, 925, 6, 170, 0, 0, 5, 14, 9, 14, 16, 19, 101, 131, 156),
(29, 2, 5, 950, 6, 180, 0, 0, 5, 14, 9, 14, 16, 19, 104, 134, 159),
(30, 2, 5, 1000, 6, 190, 20, 3, 5, 14, 9, 14, 16, 19, 107, 137, 162),
(31, 2, 5, 1018, 6, 200, 20, 6, 7, 16, 11, 16, 20, 23, 110, 140, 165),
(32, 2, 5, 1036, 6, 210, 20, 9, 7, 16, 11, 16, 20, 23, 113, 143, 168),
(33, 2, 5, 1054, 6, 220, 20, 12, 7, 16, 11, 16, 20, 23, 116, 146, 171),
(34, 2, 5, 1072, 6, 230, 20, 15, 7, 16, 11, 16, 20, 23, 119, 149, 174),
(35, 2, 5, 1090, 6, 240, 20, 18, 7, 16, 11, 16, 20, 23, 122, 152, 177),
(36, 2, 5, 1108, 6, 250, 20, 21, 7, 16, 11, 16, 20, 23, 125, 155, 180),
(37, 2, 5, 1126, 6, 260, 20, 24, 7, 16, 11, 16, 20, 23, 128, 158, 183),
(38, 2, 5, 1144, 6, 270, 20, 27, 7, 16, 11, 16, 20, 23, 131, 161, 186),
(39, 2, 5, 1162, 6, 280, 20, 30, 7, 16, 11, 16, 20, 23, 134, 164, 189),
(40, 2, 5, 1180, 6, 290, 20, 33, 7, 16, 11, 16, 20, 23, 137, 167, 192),
(41, 2, 5, 1198, 6, 300, 20, 36, 9, 18, 13, 18, 24, 27, 140, 170, 195),
(42, 2, 5, 1216, 6, 310, 20, 39, 9, 18, 13, 18, 24, 27, 143, 173, 198),
(43, 2, 5, 1234, 6, 320, 20, 42, 9, 18, 13, 18, 24, 27, 146, 176, 201),
(44, 2, 5, 1252, 6, 330, 20, 45, 9, 18, 13, 18, 24, 27, 149, 179, 204),
(45, 2, 5, 1270, 6, 340, 20, 48, 9, 18, 13, 18, 24, 27, 152, 182, 207),
(46, 2, 5, 1288, 6, 350, 20, 51, 9, 18, 13, 18, 24, 27, 155, 185, 210),
(47, 2, 5, 1306, 6, 360, 20, 54, 9, 18, 13, 18, 24, 27, 158, 188, 213),
(48, 2, 5, 1324, 6, 370, 20, 57, 9, 18, 13, 18, 24, 27, 161, 191, 216),
(49, 2, 5, 1342, 6, 380, 20, 60, 9, 18, 13, 18, 24, 27, 164, 194, 219),
(50, 2, 5, 1360, 6, 390, 20, 63, 9, 18, 13, 18, 24, 27, 167, 197, 222)
;

View File

@@ -1,255 +0,0 @@
-- MaterialID for all droppable cloth items
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
1 AS materialId, -- Assign the same materialId to all rows
it.entry,
it.name
FROM item_template it
LEFT JOIN creature_loot_template clt ON (it.entry = clt.Item)
WHERE it.name LIKE '%Cloth'
AND it.class = 7
AND it.subclass = 5
AND it.name NOT LIKE 'Bolt%'
AND it.VerifiedBuild = 12340
AND clt.Entry IS NULL;
-- Crafted cloth items that are not dropped by enemies
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
2 AS materialId, -- Assign the same materialId to all rows
it.entry,
it.name
FROM item_template it
LEFT JOIN creature_loot_template clt ON (it.entry = clt.Item)
WHERE it.name LIKE '%Cloth'
AND it.class = 7
AND it.subclass = 5
AND it.name NOT LIKE 'Bolt%'
AND it.VerifiedBuild = 12340
AND clt.Entry IS NULL;
-- Common Herbs available to herbalists
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
3 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE it.class = 7
AND it.subclass = 9
AND it.VerifiedBuild = 12340
AND it.RequiredSkillRank != 0
OR name = 'Mageroyal'
ORDER BY it.RequiredSkillRank, it.name;
-- Rare Herbs that are harder to find or drop in raids
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
4 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE it.class = 7
AND it.subclass = 9
AND it.VerifiedBuild = 12340
AND it.RequiredSkillRank = 0
OR name != 'Mageroyal'
ORDER BY it.RequiredSkillRank, it.name;
-- Common ores that are easy to find in the world
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
5 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE it.class = 7
AND it.subclass = 7
AND it.VerifiedBuild = 12340
AND it.name NOT LIKE '%Bar'
AND it.name NOT LIKE '%Ingot'
AND it.name NOT LIKE '%Stone'
AND it.name NOT LIKE '%Shard'
AND it.name NOT IN ('Coal', 'Elemental Flux', 'Hardened Khorium')
AND it.name NOT IN ('Elementium Ore', 'Khorium Ore')
ORDER BY it.name;
-- Rare ores that are harder to find or drop in raids
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT DISTINCT
6 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE
it.name Like '%Obsidian Shard%'
OR it.name IN ('Elementium Ore', 'Dark Iron Ore', 'Primal Nether', 'Runed Orb', 'Crusader Orb', 'Blood of the Mountain')
and entry <= 2000000
ORDER BY it.name;
-- Common skins that are easy to find in the world
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
7 AS materialId, -- Unique ID for commonly skinnable materials
it.entry,
it.name
FROM item_template it
WHERE it.name IN (
'Borean Leather',
'Heavy Leather',
'Heavy Hide',
'Light Leather',
'Light Hide',
'Medium Leather',
'Medium Hide',
'Rugged Leather',
'Rugged Hide',
'Thick Leather',
'Thick Hide',
'Knothide Leather',
'Icy Dragonscale',
'Jormungar Scale',
'Nerubian Chitin',
'Turtle Scale'
);
-- Rare skinning material found in the world or dropped by enemies
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
8 AS materialId, -- Unique ID for rare skinnable and non-skinnable materials
it.entry,
it.name
FROM item_template it
WHERE it.name IN (
'Black Dragonscale',
'Blue Dragonscale',
'Green Dragonscale',
'Red Dragonscale',
'Scale of Onyxia',
'Nether Dragonscales',
'Wind Scales',
'Thick Clefthoof Leather',
'Cobra Scales',
'Devilsaur Leather',
'Green Whelp Scale',
'Black Whelp Scale',
'Perfect Deviate Scale',
'Core Leather',
'Dreamscale',
'Primal Bat Leather',
'Primal Tiger Leather',
);
-- Common uncut gems that are easy to find in the world
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
9 AS materialId, -- Unique ID for common uncut gems
it.entry,
it.name
FROM item_template it
WHERE
it.VerifiedBuild = 12340 -- Wrath of the Lich King build
AND it.name NOT LIKE '%Cut%' -- Exclude pre-cut gems
AND it.name IN (
'Malachite',
'Tigerseye',
'Moss Agate',
'Shadowgem',
'Jade',
'Lesser Moonstone',
'Citrine',
'Aquamarine',
'Star Ruby',
'Azerothian Diamond',
'Huge Emerald',
'Large Opal',
'Blue Sapphire',
'Arcane Crystal',
'Bloodstone',
'Sun Crystal',
'Chalcedony',
'Dark Jade',
'Shadow Crystal',
'Huge Citrine',
'Monarch Topaz',
'Forest Emerald',
'Sky Sapphire',
"Autumn's Glow",
'Twilight Opal',
'Scarlet Ruby'
);
-- Rare gems that are harder to find or drop in raids
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
10 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- TBC Epic Gems
'Crimson Spinel',
'Empyrean Sapphire',
'Lionseye',
'Shadowsong Amethyst',
'Pyrestone',
'Seaspray Emerald',
-- WotLK Epic Gems
'Cardinal Ruby',
'Ametrine',
"King\'s Amber",
'Eye of Zul',
'Majestic Zircon',
'Dreadstone'
);
--- Common materials that related to water / frost
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
11 AS materialId,
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Elemental Water',
'Essence of Water',
'Glacial Fragments',
-- TBC
'Arctic Fur',
'Thick Dawnstone',
-- WotLK
'Frost Lotus',
'Frostweave Cloth'
)
--- Rare materials that related to water / frost
REPLACE INTO mp_material_types (materialId, entry, name)
SELECT
12 AS materialId, -- Unique ID for rare frost resistance materials
it.entry,
it.name
FROM item_template it
WHERE
it.name IN (
-- Classic
'Frozen Rune',
'Core of Earth',
-- TBC
'Frost-Infused Shard',
'Nether Residuum',
'Primal Water',
-- WotLK
'Primordial Saronite',
'Titanium Frostguard Ring',
'Icy Dragonscale',
'Eternal Water'
);

View File

@@ -1,45 +0,0 @@
--- Used to track upgrade ranks for stat improvements and min/max values
DROP TABLE IF EXISTS mp_stat_upgrade_ranks;
CREATE TABLE mp_stat_upgrade_ranks
(
upgradeRank INT UNSIGNED NOT NULL,
statTypeId INT UNSIGNED NOT NULL,
materialId1 INT UNSIGNED NOT NULL,
materialId2 INT UNSIGNED NOT NULL,
materialId3 INT UNSIGNED NOT NULL,
materialCost INT UNSIGNED NOT NULL,
materialCost2 INT UNSIGNED NOT NULL,
materialCost3 INT UNSIGNED NOT NULL,
minIncrease1 INT UNSIGNED NOT NULL,
maxIncrease1 INT UNSIGNED NOT NULL,
minIncrease2 INT UNSIGNED NOT NULL,
maxIncrease2 INT UNSIGNED NOT NULL,
minIncrease3 INT UNSIGNED NOT NULL,
maxIncrease3 INT UNSIGNED NOT NULL,
chanceCost1 INT UNSIGNED NOT NULL,
chanceCost2 INT UNSIGNED NOT NULL,
chanceCost3 INT UNSIGNED NOT NULL,
PRIMARY KEY (upgradeRank, statTypeId)
);
-- Used to allocate trade materials based on slot upgrades
DROP TABLE IF EXISTS mp_material_types;
CREATE TABLE mp_material_types
(
materialId INT UNSIGNED NOT NULL,
entry INT UNSIGNED NOT NULL,
name VARCHAR(255) NOT NULL,
PRIMARY KEY (materialId, entry)
);
-- Description: Scale factors for Mythic+ dungeons used to normalize dungeon difficulty across expansions.
DROP TABLE IF EXISTS mp_scale_factors;
CREATE TABLE IF NOT EXISTS mp_scale_factors (
mapId SMALLINT PRIMARY KEY,
dmg_bonus INT,
spell_bonus INT,
hp_bonus INT,
difficulty INT,
max INT
);

View File

@@ -7,11 +7,19 @@ import (
// Stats enum
const (
STAT_STRENGTH = iota
STAT_INTELLECT = iota
STAT_SPIRIT
STAT_STRENGTH
STAT_AGILITY
STAT_STAMINA
STAT_INTELLECT
STAT_SPIRIT
)
const (
RESIST_FROST = iota
RESIST_FIRE
RESIST_NATURE
RESIST_SHADOW
RESIST_ARCANE
)
func main() {
@@ -25,22 +33,31 @@ func main() {
// Stats names for clarity
stats := map[int]string{
STAT_STRENGTH: "STAT_STRENGTH",
STAT_AGILITY: "STAT_AGILITY",
STAT_STAMINA: "STAT_STAMINA",
STAT_INTELLECT: "STAT_INTELLECT",
STAT_SPIRIT: "STAT_SPIRIT",
STAT_INTELLECT: "Intellect",
STAT_SPIRIT: "Spirit",
STAT_STRENGTH: "Strength",
STAT_AGILITY: "Agility",
STAT_STAMINA: "Stamina",
}
// Start writing the SQL script
fmt.Fprintln(outputFile, "-- SQL Script to Insert 50 Ranks for Each Stat")
fmt.Fprintln(outputFile, "INSERT INTO mp_stat_upgrade_ranks (upgradeRank, statTypeId, materialId1, materialCost, materialId2, materialCost2, materialId3, materialCost3, minIncrease1, maxIncrease1, minIncrease2, maxIncrease2, minIncrease3, maxIncrease3, chanceCost1, chanceCost2, chanceCost3) VALUES")
fmt.Fprintln(outputFile, "INSERT INTO mp_upgrade_ranks (upgradeRank, advancementId, materialId1, materialCost1, materialId2, materialCost2, materialId3, materialCost3, minIncrease1, maxIncrease1, minIncrease2, maxIncrease2, minIncrease3, maxIncrease3, chanceCost1, chanceCost2, chanceCost3) VALUES")
// Iterate over stats
for statID := range stats {
for rank := 1; rank <= 50; rank++ {
// Material cost increases by 50 per rank
materialCost := 100 + (rank-1)*50
materialCost := 50
if rank < 11 {
materialCost = 100 + (rank-1)*50
}
if rank >= 11 && rank < 30 {
materialCost = 500 + (rank-11)*25
}
if rank >= 30 {
materialCost = 1000 + (rank-30)*18
}
// Stat growth
minIncrease1 := 1 + (rank-1)/10*2
@@ -56,10 +73,26 @@ func main() {
chanceCost2 := 50 + (rank-1)*3
chanceCost3 := 75 + (rank-1)*3
// use material ids from the mp_material_types table material1 should be common stuff.
materialId1 := statID*2 + 1
// material2 should be rare stuff only required after rank 10 at growth rate of 5 per rank
materialId2, materialCost2 := 0, 0
if rank > 10 {
materialId2 = statID*2 + 2
materialCost2 = (rank - 11) * 10
}
materialId3, materialCost3 := 0, 0
if rank >= 30 {
materialId3 = 20 // Group lot of raid only items
materialCost3 = (rank - 29) * 3
}
// Write SQL insert statement for this rank
sql := fmt.Sprintf(
"(%d, %d, 1001, %d, 0, 0, 0, 0, %d, %d, %d, %d, %d, %d, %d, %d, %d)",
rank, statID, materialCost,
"(%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)",
rank, statID, materialId1, materialCost, materialId2, materialCost2, materialId3, materialCost3,
minIncrease1, maxIncrease1, minIncrease2, maxIncrease1, minIncrease3, maxIncrease3,
chanceCost1, chanceCost2, chanceCost3,
)

415
src/AdvancementMgr.cpp Normal file
View File

@@ -0,0 +1,415 @@
#include "AdvancementMgr.h"
#include "CharacterDatabase.h"
#include "WorldDatabase.h"
#include "Player.h"
#include "MpLogger.h"
#include "MythicPlus.h"
#include "MpConstants.h"
#include <string_view>
#include <tuple>
#include <mutex>
/**
* Table schema for upgrade ranks populated by go script:
*
* upgradeRank INT UNSIGNED NOT NULL,
* advancementId INT UNSIGNED NOT NULL,
* materialId1 INT UNSIGNED NOT NULL,
* materialId2 INT UNSIGNED NOT NULL,
* materialId3 INT UNSIGNED NOT NULL,
* materialCost INT UNSIGNED NOT NULL,
* materialCost2 INT UNSIGNED NOT NULL,
* materialCost3 INT UNSIGNED NOT NULL,
* minIncrease1 INT UNSIGNED NOT NULL,
* maxIncrease1 INT UNSIGNED NOT NULL,
* minIncrease2 INT UNSIGNED NOT NULL,
* maxIncrease2 INT UNSIGNED NOT NULL,
* minIncrease3 INT UNSIGNED NOT NULL,
* maxIncrease3 INT UNSIGNED NOT NULL,
* chanceCost1 INT UNSIGNED NOT NULL,
* chanceCost2 INT UNSIGNED NOT NULL,
* chanceCost3 INT UNSIGNED NOT NULL,
*
* PRIMARY KEY (upgradeRank, statTypeId)
*
* This loads the ranks from the database and stores them into memory for access. This should only be
* called on server start-up as it is static data that should only change no new builds.
*/
int32 AdvancementMgr::LoadAdvancementRanks() {
_advancementRanks.clear();
//
// const char*
constexpr std::string_view query = R"(
SELECT
upgradeRank,
advancementId,
materialId1,
materialId2,
materialId3,
materialCost1,
materialCost2,
materialCost3,
minIncrease1,
maxIncrease1,
minIncrease2,
maxIncrease2,
minIncrease3,
maxIncrease3,
chanceCost1,
chanceCost2,
chanceCost3
FROM mp_upgrade_ranks
)";
QueryResult result = WorldDatabase.Query(query);
if (!result) {
MpLogger::error("Failed to load mythic scale factors from database");
return 0;
}
do {
Field* fields = result->Fetch();
uint32 upgradeRank = fields[0].Get<uint32>();
uint32 advancementId = fields[1].Get<uint32>();
uint32 materialId1 = fields[2].Get<uint32>();
uint32 materialId2 = fields[3].Get<uint32>();
uint32 materialId3 = fields[4].Get<uint32>();
uint32 materialCost1 = fields[5].Get<uint32>();
uint32 materialCost2 = fields[6].Get<uint32>();
uint32 materialCost3 = fields[7].Get<uint32>();
uint32 minIncrease1 = fields[8].Get<uint32>();
uint32 maxIncrease1 = fields[9].Get<uint32>();
uint32 minIncrease2 = fields[10].Get<uint32>();
uint32 maxIncrease2 = fields[11].Get<uint32>();
uint32 minIncrease3 = fields[12].Get<uint32>();
uint32 maxIncrease3 = fields[13].Get<uint32>();
uint32 chanceCost1 = fields[14].Get<uint32>();
uint32 chanceCost2 = fields[15].Get<uint32>();
uint32 chanceCost3 = fields[16].Get<uint32>();
// Should add validator... but let's do it without and trust in the o-DB-Wan-kenobe
MpAdvancements advancement = static_cast<MpAdvancements>(advancementId);
// List of all ranks keyed by rank, advancementId
MpAdvancementRank rank = {
.rank = upgradeRank,
.advancementId = advancement,
.materialCost = std::unordered_map<uint32, uint32>(),
.rollCost = {chanceCost1, chanceCost2, chanceCost3},
.lowRange = std::make_pair(minIncrease1, maxIncrease1),
.midRange = std::make_pair(minIncrease2, maxIncrease2),
.highRange = std::make_pair(minIncrease3, maxIncrease3)
};
rank.materialCost.emplace(materialId1, materialCost1);
rank.materialCost.emplace(materialId2, materialCost2);
rank.materialCost.emplace(materialId3, materialCost3);
_advancementRanks.try_emplace(std::make_pair(upgradeRank, advancement), rank);
} while (result->NextRow());
return _advancementRanks.size();
}
/**
* guid INT UNSIGNED NOT NULL,
* advancementId INT UNSIGNED NOT NULL,
* bonus FLOAT NOT NULL,
* upgradeRank INT UNSIGNED NOT NULL,
* diceSpent INT UNSIGNED NOT NULL DEFAULT '0',
*
* PRIMARY KEY (guid, advancementId)
*
* This loads the player advancements when a player logs in stores it into memory for access by spell scripts that
* are applied to the player on login to apply the bonuses.
*/
int32 AdvancementMgr::LoadPlayerAdvancements(Player* player) {
std::lock_guard<std::mutex> lock(_playerAdvancementMutex);
constexpr std::string_view query = R"(
SELECT
guid,
advancementId,
bonus,
upgradeRank,
diceSpent
FROM mp_player_advancements
WHERE guid = {}
)";
QueryResult result = CharacterDatabase.Query(query, player->GetGUID().GetCounter());
// If the player does not have any upgrades just return perfectly fine not a problem until they purchase one.
if(!result) {
return 0;
}
do {
Field* fields = result->Fetch();
uint32 guid = fields[0].Get<uint32>();
uint32 advancementId = fields[1].Get<uint32>();
float bonus = fields[2].Get<float>();
uint32 upgradeRank = fields[3].Get<uint32>();
uint32 diceSpent = fields[4].Get<uint32>();
MpAdvancements advancement = static_cast<MpAdvancements>(advancementId);
MpPlayerRank playerRank = MpPlayerRank();
playerRank.rank = upgradeRank;
playerRank.advancementId = advancement;
playerRank.diceSpent = diceSpent;
playerRank.bonus = bonus;
// List of all ranks keyed by rank, advancementId
_playerAdvancements[guid][advancement] = playerRank;
} while (result->NextRow());
return result->GetRowCount();
}
/**
* Load Material Types from the database into memory
*/
int32 AdvancementMgr::LoadMaterialTypes() {
constexpr std::string_view query = R"(
SELECT
materialId,
entry
FROM mp_material_types
)";
QueryResult result = WorldDatabase.Query(query);
do {
Field* fields = result->Fetch();
uint32 materialId = fields[0].Get<uint32>();
uint32 entry = fields[1].Get<uint32>();
if(!_materialTypes.contains(materialId)) {
_materialTypes.emplace(materialId,std::vector<uint32>());
}
_materialTypes.at(materialId).push_back(entry);
} while (result->NextRow());
return result->GetRowCount();
}
MpAdvancementRank* AdvancementMgr::GetAdvancementRank(uint32 rank, MpAdvancements advancement)
{
auto key = std::make_pair(rank, advancement);
if (_advancementRanks.contains(key))
{
return &_advancementRanks.at(key);
}
else
{
MpLogger::error("Advancement Id {} and rank {} could not be found", rank, advancement);
return nullptr;
}
}
MpPlayerRank* AdvancementMgr::GetPlayerAdvancementRank(Player* player, MpAdvancements advancement)
{
if(!player) {
MpLogger::error("Could not retrieve player advancement for null player {}", player->GetName());
return nullptr;
}
if (_playerAdvancements.contains(player->GetGUID().GetCounter()) && _playerAdvancements[player->GetGUID().GetCounter()].contains(advancement))
{
return &_playerAdvancements[player->GetGUID().GetCounter()][advancement];
}
return nullptr;
}
bool AdvancementMgr::UpgradeAdvancement(Player* player, MpAdvancements advancement, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3)
{
std::lock_guard<std::mutex> lock(_playerAdvancementMutex);
// Validators to make sure inputs are correct to perform the upgrade
if(!player) {
MpLogger::error("Could not upgrade advancement for player, player was nullpointer");
return false;
}
if(diceCostLevel < 1 || diceCostLevel > 3) {
MpLogger::error("Invalid dice cost level valid vales (1,2,3) received {} for player {}", diceCostLevel, player->GetName());
return false;
}
if(itemEntry1 == 0) {
MpLogger::error("Material1 can not be 0 can not perform advancement upgrade for player {} Advancement {}", player->GetName(), advancement);
return false;
}
MpPlayerRank* playerRank = GetPlayerAdvancementRank(player, advancement);
// IF there is not create the base struct and add to the player map for this advancement
if(!playerRank) {
MpPlayerRank newPlayerRank;
newPlayerRank.advancementId = advancement;
auto& playerAdvMap = _playerAdvancements[player->GetGUID().GetCounter()];
playerAdvMap.emplace(advancement, newPlayerRank);
playerRank = &playerAdvMap.at(advancement);
}
if(playerRank->rank == MP_MAX_ADVANCEMENT_RANK) {
MpLogger::error("Player {} has reached the maximum rank for advancement {}", player->GetName(), advancement);
return false;
}
uint32 newRank = playerRank->rank + 1;
MpAdvancementRank* advancementRank = GetAdvancementRank(newRank, advancement);
if(!advancementRank->IsValid()) {
MpLogger::error("Advancement {} rank {} could not be found", advancement, newRank);
return false;
}
// If the player has the items needed to upgrade this advancement, then remove the items from the player inventory and apply the upgrade
if(!_PlayerHasItems(player, advancementRank, diceCostLevel, itemEntry1, itemEntry2, itemEntry3)) {
MpLogger::info("Player {} does not have the required items to upgrade advancement {}", player->GetName(), advancement);
return false;
}
// Charge the player the cost of the upgrade
_ChargeItemCost(player, advancementRank, diceCostLevel, itemEntry1, itemEntry2, itemEntry3);
// Finally get the bonus to apply for the player
float roll = _RollAdvancement(advancementRank, diceCostLevel);
// Update the player advancement rank in memory and database
playerRank->rank = newRank;
playerRank->diceSpent += advancementRank->rollCost[diceCostLevel];
playerRank->bonus += roll;
return true;
}
bool AdvancementMgr::ResetPlayerAdvancements(Player* /*player*/)
{
std::lock_guard<std::mutex> lock(_playerAdvancementMutex);
return true;
}
void AdvancementMgr::_ResetPlayerAdvancement(Player* player, MpAdvancements advancement)
{
std::lock_guard<std::mutex> lock(_playerAdvancementMutex);
return;
}
// Roll them stats DnD style.
float AdvancementMgr::_RollAdvancement(MpAdvancementRank* advancementRank, uint32 diceCostLevel)
{
uint32 min, max;
switch (diceCostLevel)
{
case 1:
min = advancementRank->lowRange.first;
max = advancementRank->lowRange.second;
break;
case 2:
min = advancementRank->midRange.first;
max = advancementRank->midRange.second;
break;
case 3:
min = advancementRank->highRange.first;
max = advancementRank->highRange.second;
break;
default:
MpLogger::error("Invalid dice cost level valid vales (1,2,3) received {} for rank roll", diceCostLevel, advancementRank->rank);
break;
}
return frand(min, max);
}
// Checks the players inventory to validate they have the required items to perform an upgrade based on the set cost for the passed in level.
bool AdvancementMgr::_PlayerHasItems(Player* player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3)
{
// Check if player has the required dice to upgrade the advancement if not do nothing.
uint32 diceCost = advancementRank->materialCost.at(diceCostLevel);
if(!player->HasItemCount(MpConstants::ANCIENT_DICE, diceCost)) {
MpLogger::info("Player {} does not have enough dice to upgrade advancement {}", player->GetName(), advancementRank->advancementId);
return false;
}
// Validate the passed in item for materialId 1 is valid and the player has enough to purchase.
if(itemEntry1 > 0) {
if(!advancementRank->HasMaterial(itemEntry1)) {
MpLogger::error("Material1 {} is not a valid material for advancement {}", itemEntry1, advancementRank->advancementId);
return false;
}
if(!player->HasItemCount(itemEntry1, advancementRank->materialCost[itemEntry1])) {
MpLogger::info("Player {} does not have enough material {} to upgrade advancement {}", player->GetName(), itemEntry1, advancementRank->advancementId);
return false;
}
}
if(itemEntry2 > 0) {
if(!advancementRank->HasMaterial(itemEntry2)) {
MpLogger::error("Material1 {} is not a valid material for advancement {}", itemEntry2, advancementRank->advancementId);
return false;
}
if(!player->HasItemCount(itemEntry2, advancementRank->materialCost[itemEntry2])) {
MpLogger::info("Player {} does not have enough material {} to upgrade advancement {}", player->GetName(), itemEntry2, advancementRank->advancementId);
return false;
}
}
if(itemEntry3 > 0) {
if(!advancementRank->HasMaterial(itemEntry3)) {
MpLogger::error("Material1 {} is not a valid material for advancement {}", itemEntry3, advancementRank->advancementId);
return false;
}
if(!player->HasItemCount(itemEntry3, advancementRank->materialCost[itemEntry3])) {
MpLogger::info("Player {} does not have enough material {} to upgrade advancement {}", player->GetName(), itemEntry3, advancementRank->advancementId);
return false;
}
}
return true;
}
// Remove all items required for the upgrade.
void AdvancementMgr::_ChargeItemCost(Player *player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3)
{
uint32 diceCost = advancementRank->materialCost[diceCostLevel];
Item* item = player->GetItemByEntry(MpConstants::ANCIENT_DICE);
item->SetCount(item->GetCount() - diceCost);
item->SendUpdateToPlayer(player);
// Remove the material from the player inventory
if(itemEntry1 > 0) {
item = player->GetItemByEntry(itemEntry1);
item->SetCount(item->GetCount() - advancementRank->materialCost[itemEntry1]);
item->SendUpdateToPlayer(player); // Update the client with the new dice count
}
if(itemEntry2 > 0) {
item = player->GetItemByEntry(itemEntry2);
item->SetCount(item->GetCount() - advancementRank->materialCost[itemEntry2]);
item->SendUpdateToPlayer(player); // Update the client with the new dice count
}
if(itemEntry3 > 0) {
item = player->GetItemByEntry(itemEntry3);
item->SetCount(item->GetCount() - advancementRank->materialCost[itemEntry3]);
item->SendUpdateToPlayer(player); // Update the client with the new dice count
}
return;
}

144
src/AdvancementMgr.h Normal file
View File

@@ -0,0 +1,144 @@
#ifndef ADVANCEMENT_MGR_H
#define ADVANCEMENT_MGR_H
#include "SharedDefines.h"
#include "Player.h"
#include <memory>
#include <unordered_map>
#include <map>
#include <mutex>
enum MpAdvancements
{
MP_ADV_STRENGTH = 0,
MP_ADV_AGILITY = 1,
MP_ADV_STAMINA = 2,
MP_ADV_INTELLECT = 3,
MP_ADV_SPIRIT = 4,
MP_ADV_RESIST_FIRE = 5,
MP_ADV_RESIST_NATURE = 6,
MP_ADV_RESIST_FROST = 7,
MP_ADV_RESIST_SHADOW = 8,
MP_ADV_RESIST_ARCANE = 9,
MP_ADV_MAX = 10
};
/**
* Advancement Rank represents each level for a stat increase that has can be purchases.
* It includes materials, type, and range of successful roll.
*/
struct MpAdvancementRank
{
uint32 rank;
MpAdvancements advancementId;
std::unordered_map<uint32 /*item_entry*/,uint32 /*quantity*/> materialCost;
std::array<int, 3> rollCost; // 0 = low, 1 = mid, 2 = high
// Range of status based on bet dice roll.
std::pair<uint32 /*min*/, uint32 /*max*/> lowRange;
std::pair<uint32 /*min*/, uint32 /*max*/> midRange;
std::pair<uint32 /*min*/, uint32 /*max*/> highRange;
// Used to validate this struct is set correctly
bool IsValid() {
return (rank > 0 && advancementId >= 0 && advancementId < MP_ADV_MAX);
}
// Check if the map has an the item entry for the passed in material
bool HasMaterial(uint32 itemEntry) {
return materialCost.contains(itemEntry);
}
};
// Struct is used for tracking player advancement bonuses for improving stats
struct MpPlayerRank
{
uint32 rank;
MpAdvancements advancementId;
uint32 diceSpent;
float bonus;
MpPlayerRank() : rank(0), diceSpent(0), bonus(0.0f) {}
};
/**
* This singleton class is responsible for managing the advancement system
* used to improve player stats enough to challenge harder difficulties on existing dungeons.
*
* Advancements are purchased by players using based on the mp_material_type table with a "bet"
* on dice roll. This enables players to increase their stats in a random way that is not
* guaranteed to be successful. (Similar to how DND stats rolls work on character creation. )
*/
class AdvancementMgr
{
// Shared mutex for handling writes to shared player advancement data
std::mutex _playerAdvancementMutex;
// All advancement levels for each stat [rank][advancement_id] = AdvancementLevel
std::map<std::pair<uint32 /*rank*/, MpAdvancements>, MpAdvancementRank> _advancementRanks;
// Map of player advancements [player_guid][advancement_id] = rank
std::unordered_map<uint32 /*player_guid*/, std::unordered_map<MpAdvancements, MpPlayerRank>> _playerAdvancements;
// Map of different material types used fo advancing stats for players
std::unordered_map<uint32 /*material_id*/, std::vector<uint32> /* item entries */> _materialTypes;
public:
static AdvancementMgr* instance() {
static AdvancementMgr instance;
return &instance;
}
// Loads advancement information from the database into memory when players are logged in or server starts.
int32 LoadAdvancementRanks();
int32 LoadMaterialTypes();
int32 LoadPlayerAdvancements(Player* player);
// Methods for looking up advancement rank data
MpAdvancementRank* GetAdvancementRank(uint32 rank, MpAdvancements advancement);
// Methods for updating and setting data related to current player advancements
MpPlayerRank* GetPlayerAdvancementRank(Player* player, MpAdvancements advancement);
/**
* This upgrades a player Advancement on the server side, which will handle the following actions:
* 1. Validating player has enough dice and materials to upgrade the advancement
* 2. Rolling the dice to see what bonus is rewarded
* 3. Removing the dice and materials from the player inventory
* 4. Updating the player advancement rank in memory and database
*
* Since different materials can be used for each advancement, at the moment only support one material type from the list. supporting
* mixed materials is more complicated and the UI to support it is much more complex, while this is not as nice it is much simpler to implement.
* That means all materials have to be selected and passed in at the time of making this call.
*/
bool UpgradeAdvancement(Player* player, MpAdvancements advancement, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3);
// Used to reset all advancements for a specific player
bool ResetPlayerAdvancements(Player* player);
private:
AdvancementMgr() {}
~AdvancementMgr() {}
// Will reset all the player advancements and refund the spent dice and material with a penalty for the reset.
void _ResetPlayerAdvancement(Player* player, MpAdvancements advancement);
// Rolls the dice to see how much a bonus is given based on the dice spend level
float _RollAdvancement(MpAdvancementRank* advancementRank, uint32 diceCostLevel);
// Determines if a player has required items to upgrade
bool _PlayerHasItems(Player* player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3);
// Removes items from player inventory based on the required advancement rank.
void _ChargeItemCost(Player* player, MpAdvancementRank* advancementRank, uint32 diceCostLevel, uint32 itemEntry1, uint32 itemEntry2, uint32 itemEntry3);
// This will save the advancement purchase to the history database
void _DBSaveAdvancement(Player* player, MpAdvancementRank* advancementRank, MpPlayerRank* playerRank, uint32 diceCost, float roll);
};
#define sAdvancementMgr AdvancementMgr::instance()
#endif

View File

@@ -0,0 +1,32 @@
// #include "MpDataStore.h"
// #include "MpLogger.h"
// class CreatureOverride {
// public:
// uint32 entry;
// MpDifficulty difficulty;
// Creature* creature;
// CreatureOverride(uint32 entry): entry(entry), difficulty(difficulty) {
// sMpDataStore->AddCreatureOverride(this->entry, this);
// }
// virtual void Modify(Creature* creature) final {
// this->creature = creature;
// }
// virtual void HandleModify() = 0;
// void ModifyAttackPower(uint32 ap) {
// if(this)
// }
// void ModifyHealth(uint32 health) {}
// void ModifyArmor(uint32 armor) {}
// void ModifySpellPower(uint32 sp) {}
// ~CreatureOverride() {}
// };

View File

@@ -0,0 +1,45 @@
#include "MpClientDispatcher.h"
#include "MpEventProcessor.h"
#include "Player.h"
#include "MpLogger.h"
#include "Chat.h"
#include "WorldPacket.h"
#include <string_view>
#include <string>
/**
* In order to allow communication from client UIs without modifying mod-eluna directly to support
* This sends a packet directly using the AddOn message channel in the background.
*/
bool MpClientDispatcher::Dispatch(MpClientEvent event, Player* player, std::vector<std::string>& args)
{
if(!MpClientEventNames.contains(event)) {
MpLogger::warn("No event registered for event: {}", event);
return false;
}
// Build the message string in same format to send to the client.
std::string_view eventName = MpClientEventNames.at(event);
std::string message = std::to_string(player->GetGUID().GetCounter()) + "|" + eventName.data();
for(auto& arg : args) {
message += "|" + arg;
}
std::string prefix = std::string(MP_DATA_CHAT_CHANNEL);
std::string fullmsg = prefix + "\t" + message;
WorldPacket data(SMSG_MESSAGECHAT, 100);
data << uint8(CHAT_MSG_ADDON);
data << int32(LANG_ADDON);
data << player->GetGUID().GetCounter();
data << uint32(0);
data << player->GetGUID().GetCounter();
data << uint32(fullmsg.length() + 1);
data << fullmsg;
data << uint8(0);
player->GetSession()->SendPacket(&data);
return 0;
return true;
}

View File

@@ -0,0 +1,35 @@
#ifndef MYTHIC_PLUS_CLIENT_DISPATCHER_H
#define MYTHIC_PLUS_CLIENT_DISPATCHER_H
#include "MpEvent.h"
#include "Player.h"
#include <string>
#include <string_view>
#include <vector>
class MpClientDispatcher
{
public:
static MpClientDispatcher* instance() {
static MpClientDispatcher instance;
return &instance;
}
MpClientDispatcher(const MpClientDispatcher&) = delete;
MpClientDispatcher& operator=(const MpClientDispatcher&) = delete;
// encode and send a message to the client for an event in the map
bool Dispatch(MpClientEvent event, Player* player, std::vector<std::string>& args);
private:
MpClientDispatcher() {};
~MpClientDispatcher() {};
};
#define sMpClientDispatcher MpClientDispatcher::instance()
#endif

47
src/Event/MpEvent.h Normal file
View File

@@ -0,0 +1,47 @@
#include <array>
#include <string_view>
#include <string>
#include <unordered_map>
#ifndef MP_EVENTS_H
#define MP_EVENTS_H
// This defines list of incoming events typically from the client
enum class MpEvent
{
None, // Default catch all for no event
Invalid, // Used to capture identify error events
UpgradeAdvancement, // Upgrades a player advancement 1 level
ResetAdvancement, // Resets a player Advancement back to 0
ResetAllAdvancements, // Resets all player advancements
GetAdvancementRank // Get the details about the rank of a specific advancement (cost, bonus, etc)
};
// Client events that are communicated to the over an addon message to the player client
enum class MpClientEvent
{
Error,
UpgradeAdvancement,
ResetAdvancement,
ResetAllAdvancements,
GetAdvancementRank
};
// Mapping of Event Strings to EventNames for Event Callbacks from client
inline std::unordered_map<std::string_view, MpEvent> MpEventMap = {{
{"UpgradeAdvancement", MpEvent::UpgradeAdvancement},
{"ResetAdvancement", MpEvent::ResetAdvancement},
{"ResetAllAdvancements", MpEvent::ResetAllAdvancements},
{"GetAdvancementRank", MpEvent::ResetAllAdvancements}
}};
inline std::unordered_map<MpClientEvent, std::string_view> MpClientEventNames = {{
{MpClientEvent::Error, "Error"},
{MpClientEvent::UpgradeAdvancement, "UpgradeAdvancement"},
{MpClientEvent::ResetAdvancement, "ResetAdvancement"},
{MpClientEvent::ResetAllAdvancements, "ResetAllAdvancements"},
{MpClientEvent::GetAdvancementRank, "GetAdvancementRank"}
}};
#endif

View File

@@ -0,0 +1,139 @@
#include "MpEvent.h"
#include "MpLogger.h"
#include "AdvancementMgr.h"
#include "MpEventProcessor.h"
#include "MpClientDispatcher.h"
#include "Player.h"
#include <string>
/**
* All handlers for passed between client and server handlers are managed
* here.
*/
/**
* Handles Updates to a players advancement system
* Event format
* p|playerGuid|UpgradeAdvancement|advancementId|rank|diceSpent|bonus
*/
enum class MP_EVENT_CODE
{
SUCCESS = 0,
INVALID_EVENT = 100,
INVALID_ARGUMENT_SIZE = 201,
INVALID_ARGUMENT = 202,
INVALID_ARGUMENT_TYPE = 203,
};
// Send an error event to the client
bool SendEventError(Player* player, const std::string& method, MP_EVENT_CODE code, std::string message)
{
std::vector<std::string> clientError = { std::to_string(static_cast<int>(code)), message };
MpLogger::error("Event Processor) Sending client error: {} {}", code, message);
sMpClientDispatcher->Dispatch(MpClientEvent::Error, player, clientError);
return false;
}
class UpdateAdvancements : public MpEventInterface
{
public:
const std::string EventName() const override
{
return "UpgradeAdvancement";
}
bool Execute(Player* player, std::vector<std::string>& args)
{
// Store the event data to send back to the client for parsing
std::vector<std::string> eventData;
MpLogger::info("(EventProcessor) Executing {}}", EventName());
for(auto& arg : args) {
MpLogger::info("{} Arg: {}", EventName(), arg);
}
// Validate the message is int he right format
if(args.size() != 5) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT_SIZE, "Invalid number of arguments expected 5, found " + std::to_string(args.size()));
}
uint32 advancementId = std::stoi(args[0]);
if(advancementId >= MpAdvancements::MP_ADV_MAX) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid advancement id " + args[0] + " max is " + std::to_string(MpAdvancements::MP_ADV_MAX));
}
uint32 diceLevel = std::stoi(args[1]);
if(diceLevel < 1 || diceLevel > 3) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid dice level " + args[1] + " valid values are 1,2,3");
}
uint32 itemEntry1 = std::stoi(args[2]);
if(itemEntry1 == 0) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid item entry1 can not be empty " + args[2]);
}
uint32 itemEntry2 = std::stoi(args[3]);
uint32 itemEntry3 = std::stoi(args[4]);
// Upgrade the advancement for the player!
if(! sAdvancementMgr->UpgradeAdvancement(player, static_cast<MpAdvancements>(advancementId), diceLevel, itemEntry1, itemEntry2, itemEntry3)) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Failed to upgrade advancement for player " + player->GetName());
}
eventData = {"0", "success"};
// Send response back to the client
sMpClientDispatcher->Dispatch(MpClientEvent::UpgradeAdvancement, player, eventData);
return true;
}
};
class GetAdvancementRank : public MpEventInterface
{
public:
const std::string EventName() const override
{
return "GetAdvancementRank";
}
bool Execute(Player* player, std::vector<std::string>& args)
{
// Store the event data to send back to the client for parsing
std::vector<std::string> eventData;
MpLogger::info("(EventProcessor) Executing {}}", EventName());
for(auto& arg : args) {
MpLogger::info("{} Arg: {}", EventName(), arg);
}
// Validate the message is int he right format
if(args.size() != 1) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT_SIZE, "Invalid number of arguments expected 1, found " + std::to_string(args.size()));
}
uint32 advancementId = std::stoi(args[0]);
if(advancementId >= MpAdvancements::MP_ADV_MAX) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Invalid advancement id " + args[0] + " max is " + std::to_string(MpAdvancements::MP_ADV_MAX));
}
MpAdvancementRank* rank = sAdvancementMgr->GetAdvancementRank(1, static_cast<MpAdvancements>(advancementId));
if(!rank) {
return SendEventError(player, EventName(),MP_EVENT_CODE::INVALID_ARGUMENT, "Failed to get advancement rank for player " + player->GetName());
}
eventData = {std::to_string(rank->rank), std::to_string(rank->advancementId)};
// Send response back to the client
sMpClientDispatcher->Dispatch(MpClientEvent::GetAdvancementRank, player, eventData);
return true;
}
};
void MP_Register_EventHandlers()
{
auto updateAdvancements = std::make_shared<UpdateAdvancements>();
sMpEventProcessor->RegisterHandler(MpEvent::UpgradeAdvancement, updateAdvancements);
}

View File

@@ -0,0 +1,131 @@
#include "MpEventProcessor.h"
#include "MythicPlus.h"
#include "MpLogger.h"
#include "Player.h"
#include <boost/algorithm/string/replace.hpp>
#include <string_view>
#include <string>
#include <vector>
bool MpEventProcessor::ProcessMessage(Player* player, const std::string& msg) {
if(!player) {
MpLogger::error("Null player passed to processMessage");
return false;
}
// check prefix of message channel is formatted correctly
if(! msg.starts_with(MP_DATA_CHAT_CHANNEL)) {
MpLogger::error("Invalid message format received from player {} message: {}", player->GetName(), msg);
return false;
}
std::string message = msg;
// shift the message identifier off the front including first '|' character
message.erase(0, MP_DATA_CHAT_CHANNEL.size()+1);
// clean up the message before passing it to the parser
boost::replace_all(message, "||", "|");
EventParseRslt result = _parsePlayerMessage(player, message);
MpEvent event = std::get<0>(result);
uint32 guid = std::get<1>(result);
std::vector<std::string> args = std::get<2>(result);
MpLogger::info("MpEvent Processor - event: {} guid: {} args: {}", event, guid, args.size());
// If th message was not able to be parsed it is a failure
if(event == MpEvent::Invalid) {
MpLogger::warn("Invalid event, could not be parsed for player {} message: {}", player->GetName(), message);
return false;
}
// if the message is not from the same player who called it ignore it as it is attempt to hack the system
if(player->GetGUID().GetCounter() != guid) {
MpLogger::warn("Player {} sent a message {} for eventId: {} player guid does not match", player->GetName(), message, event);
return false;
}
// If the event is not registered ignore it
if(!_eventHandlers.contains(event)) {
MpLogger::info("No handler registered for event: {}", event);
return false;
}
return Dispatch(event, player, args);
}
void MpEventProcessor::RegisterHandler(MpEvent event, std::shared_ptr<MpEventInterface> handler) {
_eventHandlers[event] = handler;
}
// This fires the execution to the actual event.
bool MpEventProcessor::Dispatch(MpEvent event, Player* player, std::vector<std::string>& args) {
if(!_eventHandlers.contains(event)) {
MpLogger::warn("No handler registered for event: {}", event);
return false;
}
return _eventHandlers[event]->Execute(player, args);
}
// Find our eventId using the string name
MpEvent MpEventProcessor::_getEventByName(std::string_view eventName)
{
auto it = MpEventMap.find(eventName);
return (it != MpEventMap.end()) ? it->second : MpEvent::None;
}
/**
* Parse the incoming message into id, event, and handler arguments
*/
EventParseRslt MpEventProcessor::_parsePlayerMessage(Player* player, const std::string& msg)
{
if(msg[0] != 'p') {
MpLogger::warn("Invalid player message format received from player {} message: {}", player->GetName(), msg);
return EventParseRslt{MpEvent::Invalid, 0, {}};
}
std::vector<std::string> handlerArgs;
char delimiter = '|';
// split the protocol into valid parts
std::vector<std::string> parts = _splitString(msg, delimiter);
if (parts.size() < 3) {
MpLogger::warn("Malformed player message received from player {}: {}", player->GetName(), msg);
return EventParseRslt{MpEvent::Invalid, 0, {}};
}
MpEvent event = _getEventByName(parts[2]);
handlerArgs.assign(parts.begin() + 3, parts.end());
MpLogger::info("Player {} sent a client event message {} for event: {} eventId: {} ", player->GetName(), msg, MP_DATA_CHAT_CHANNEL, parts[2], event);
return EventParseRslt{event, player->GetGUID().GetCounter(), std::move(handlerArgs)};
}
// Split the string passed in by delimiters
std::vector<std::string> MpEventProcessor::_splitString(const std::string& s, char delimiter) {
std::vector<std::string> tokens;
size_t start = 0;
size_t end = s.find(delimiter);
while (end != std::string::npos) {
if (end != start) {
tokens.emplace_back(s.substr(start, end - start));
}
start = end + 1;
end = s.find(delimiter, start);
}
// Add the last token if it's not empty
if (start < s.length()) {
tokens.emplace_back(s.substr(start));
}
return tokens;
}

View File

@@ -0,0 +1,86 @@
#ifndef MYTHIC_PLUS_EVENT_PROCESSOR_H
#define MYTHIC_PLUS_EVENT_PROCESSOR_H
#include "MpEvent.h"
#include "Player.h"
#include <string>
#include <string_view>
#include <vector>
/**
* In order to allow communication from client UIs without modifying mod-eluna directly to support
* this mods functionality the following custom chat channel below is for all MP UI Client interactions.
*
* p|playerGuid|action|input1|input2|input3...
*/
inline const std::string_view MP_DATA_CHAT_CHANNEL = "MPUi";
/**
* Interface for all Mythic+ Event communication between client and server
*/
class MpEventInterface
{
public:
MpEventInterface() = default;
virtual ~MpEventInterface() = default;
[[nodiscard]] virtual bool Execute(Player* player, std::vector<std::string>& args) = 0;
[[nodiscard]] virtual const std::string EventName() const = 0;
};
using EventParseRslt = std::tuple<MpEvent, uint32_t, std::vector<std::string>>;
/**
* This class is responsible for processing events that are send from the client. Events
* are processed FIFO and executed. This is specifically to allow AddOn or AIO
* communication to this module without requiring additional changes to mod-eluna or the core.
*
* Message Body Format
* Client Player Events
* - p|playerGuid|action|input1|input2|input3...
* i.e) p|5793|UpgradeAdvancement|0|10|2
*/
class MpEventProcessor
{
public:
static MpEventProcessor* instance() {
static MpEventProcessor instance;
return &instance;
}
MpEventProcessor(const MpEventProcessor&) = delete;
MpEventProcessor& operator=(const MpEventProcessor&) = delete;
// Process a message from a specific player
bool ProcessMessage(Player* player, const std::string& msg);
// Registers a handler for a valid MpEvent specified in the MpEvent enum
// In this design Event:Handler is 1:1
void RegisterHandler(MpEvent event, std::shared_ptr<MpEventInterface> handler);
// Dispatch will execute the event handler
bool Dispatch(MpEvent event, Player* player, std::vector<std::string>& args);
private:
MpEventProcessor() {}
~MpEventProcessor() {}
// List of registered handlers for event management
std::unordered_map<MpEvent, std::shared_ptr<MpEventInterface>> _eventHandlers;
// Get the correct Event associated to handlers by the message strong
MpEvent _getEventByName(std::string_view eventName);
// Parse a message from the player
EventParseRslt _parsePlayerMessage(Player* player, const std::string& msg);
// Helper to break up a string into pieces used in parsing the message
std::vector<std::string> _splitString(const std::string& s, char delimiter = '|');
};
#define sMpEventProcessor MpEventProcessor::instance()
#endif // MYTHIC_PLUS_EVENT_PROCESSOR_H

11
src/MpConstants.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef MP_CONSTANTS_H
#define MP_CONSTANTS_H
namespace MpConstants
{
constexpr int ANCIENT_DICE = 911000;
constexpr int DARK_SPIKE = 911001;
}
#endif

View File

@@ -1,5 +1,6 @@
#include "CharacterDatabase.h"
#include "MpDataStore.h"
#include "Chat.h"
#include "Group.h"
#include "MpLogger.h"
@@ -54,8 +55,9 @@ void MpDataStore::AddGroupData(Group *group, MpGroupData groupData) {
if(!player) {
MpLogger::error("AddGroupData called with null player in instance");
}
player->GetSession()->SendNotification("The group leader has changed the difficulty setting. You have been removed from the instance.");
ChatHandler(player->GetSession()).SendSysMessage("The group leader has changed the difficulty setting. You have been removed from the instance.");
//("The group leader has changed the difficulty setting. You have been removed from the instance.");
// player->GetSession()->SendNotification("The group leader has changed the difficulty setting. You have been removed from the instance.");
}
}
@@ -77,7 +79,10 @@ void MpDataStore::AddGroupData(Group *group, MpGroupData groupData) {
MpLogger::error("AddGroupData called with null player in instance");
}
player->GetSession()->SendNotification("The group leader has changed the difficulty setting. You have been removed from the instance.");
if(player) {
ChatHandler(player->GetSession()).SendSysMessage("The group leader has changed the difficulty setting. You have been removed from the instance.");
// player->GetSession()->SendNotification("The group leader has changed the difficulty setting. You have been removed from the instance."); -- previous implementation for older core users
}
}
}
@@ -136,16 +141,10 @@ void MpDataStore::RemoveGroupData(Group *group) {
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());
// Adds PlayerData related to MythicRun Status to map
void MpDataStore::AddPlayerData(ObjectGuid guid, MpPlayerData* pd) {
_playerData->emplace(guid, pd);
// get the player
Player* player = ObjectAccessor::FindPlayer(guid);
}
void MpDataStore::UpdatePlayerData(ObjectGuid guid, MpPlayerData /*pd*/) {
// _playerData->at(guid) = pd;
}
void MpDataStore::RemovePlayerData(ObjectGuid guid) {
@@ -273,7 +272,7 @@ int32 MpDataStore::LoadScaleFactors() {
_scaleFactors->clear();
// 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");
QueryResult result = WorldDatabase.Query("SELECT mapId, dmg_bonus, spell_bonus, hp_bonus, difficulty, max FROM mp_scale_factors");
if (!result) {
MpLogger::error("Failed to load mythic scale factors from database");
return 0;
@@ -339,7 +338,6 @@ void MpDataStore::DBAddPlayerDeath(Player* player) {
MpLogger::error("DBAddPlayerDeath called with invalid player");
return;
}
CharacterDatabase.Execute("UPDATE mp_player_instance_data SET deaths = deaths + 1 WHERE guid = {} and mapId = {} and instanceId = {}",
player->GetGUID().GetCounter(),
player->GetMapId(),
@@ -348,7 +346,7 @@ void MpDataStore::DBAddPlayerDeath(Player* player) {
}
// Logs death for player that occurs by a creature directly.
void MpDataStore::DBAddPlayerDeath(Player* player, Creature* creature) {
void MpDataStore::DBAddPlayerDeath(Player* player, Creature* creature, MpDifficulty difficulty) {
if (!player) {
MpLogger::error("DBAddPlayerDeath called with invalid player");
return;
@@ -360,13 +358,14 @@ void MpDataStore::DBAddPlayerDeath(Player* player, Creature* creature) {
player->GetInstanceId()
);
CharacterDatabase.Execute("REPLACE INTO mp_player_death_stats (guid, mapId, instanceId, creatureId) VALUES ({},{},{},{}) ",
CharacterDatabase.Execute(
"INSERT INTO mp_player_death_stats (guid, creatureEntry, difficulty, numDeaths, lastUpdated) "
"VALUES ({}, {}, {}, 1, CURRENT_TIMESTAMP) "
"ON DUPLICATE KEY UPDATE numDeaths = numDeaths + 1, lastUpdated = CURRENT_TIMESTAMP",
player->GetGUID().GetCounter(),
player->GetMapId(),
player->GetInstanceId(),
creature->GetEntry()
creature->GetEntry(),
difficulty
);
}
void MpDataStore::DBUpdateGroupData(ObjectGuid groupGuid, MpDifficulty difficulty, uint32 mapId, uint32 instanceId, uint32 deaths) {
@@ -374,8 +373,7 @@ void MpDataStore::DBUpdateGroupData(ObjectGuid groupGuid, MpDifficulty difficult
MpLogger::error("DBUpdateGroupData called with invalid groupGuid");
return;
}
CharacterDatabase.Execute("REPLACE INTO mp_group_data (guid, difficulty, mapId, instanceId, deaths) VALUES ({},{},{},{},{}) ",
CharacterDatabase.Execute("REPLACE INTO mp_group_data (groupId, difficulty, mapId, instanceId, deaths) VALUES ({},{},{},{},{}) ",
groupGuid.GetCounter(),
difficulty,
mapId,
@@ -400,7 +398,7 @@ void MpDataStore::DBAddGroupDeath(Group* group, uint32 mapId, uint32 instanceId,
return;
}
CharacterDatabase.Execute("UPDATE mp_group_data SET deaths = deaths + 1 WHERE guid = {} and mapId = {} and instanceId = {} and difficulty = {}",
CharacterDatabase.Execute("UPDATE mp_group_data SET deaths = deaths + 1 WHERE groupId = {} and mapId = {} and instanceId = {} and difficulty = {}",
group->GetGUID().GetCounter(),
mapId,
instanceId,
@@ -417,6 +415,15 @@ void MpDataStore::DBRemovePlayerData(ObjectGuid playerGuid) {
CharacterDatabase.Execute("DELETE FROM mp_player_instance_data WHERE guid = {} ", playerGuid.GetCounter());
}
void MpDataStore::DBRemovePlayerInstanceData(uint32 instanceId) {
if (!instanceId) {
MpLogger::error("DBRemovePlayerInstanceData: missing instanceId to remove player instance ");
return;
}
CharacterDatabase.Execute("DELETE FROM mp_player_instance_data WHERE instanceId = {} ", instanceId);
}
void MpDataStore::DBUpdateGroupTimerDeaths(ObjectGuid groupGuid, uint32 mapId, uint32 instanceId, uint32 timer, uint32 deaths) {
if (!groupGuid) {
@@ -424,7 +431,7 @@ void MpDataStore::DBUpdateGroupTimerDeaths(ObjectGuid groupGuid, uint32 mapId, u
return;
}
CharacterDatabase.Execute("REPLACE INTO mp_group_data (guid, mapId, instanceId, instanceTimer, deaths) VALUES ({},{},{},{},{}) ",
CharacterDatabase.Execute("REPLACE INTO mp_group_data (groupId, mapId, instanceId, instanceTimer, deaths) VALUES ({},{},{},{},{}) ",
groupGuid.GetCounter(),
mapId,
instanceId,
@@ -439,5 +446,16 @@ void MpDataStore::DBRemoveGroupData(ObjectGuid groupGuid) {
return;
}
CharacterDatabase.Execute("DELETE FROM mp_group_data WHERE guid = {} ", groupGuid.GetCounter());
CharacterDatabase.Execute("DELETE FROM mp_group_data WHERE groupId = {} ", groupGuid.GetCounter());
}
// Remove instance data using the instanceId
void MpDataStore::DBRemoveGroupInstanceData(uint32 instanceId) {
if (!instanceId) {
MpLogger::error("DBRemoveGroupData called with invalid groupGuid");
return;
}
CharacterDatabase.Execute("DELETE FROM mp_group_data WHERE instanceId = {} ", instanceId);
}

View File

@@ -6,6 +6,7 @@
#include "Group.h"
#include "MapMgr.h"
#include "Player.h"
#include "MpLogger.h"
#include "ObjectGuid.h"
#include <unordered_map>
@@ -14,7 +15,8 @@
#include <vector>
#include <memory>
enum MpDifficulty {
enum MpDifficulty
{
MP_DIFFICULTY_NORMAL = 0,
MP_DIFFICULTY_HEROIC = 1,
MP_DIFFICULTY_EPIC = 2,
@@ -25,8 +27,9 @@ enum MpDifficulty {
class MpDataStore;
struct MpPlayerInstanceData {
uint32 deaths;
struct MpPlayerInstanceData
{
uint32 deaths = 0;
};
// struct used to track player performance in an instance.
@@ -45,12 +48,14 @@ struct MpPlayerData
void AddDeath(uint32 mapId, uint32 instanceId) {
auto key = std::make_pair(mapId, instanceId);
if(instanceData.contains(key)) {
instanceData[key].deaths++;
} else {
instanceData[key] = MpPlayerInstanceData{.deaths = 1};
}
MpLogger::info("========= Player {} added death to instance data {}", player->GetName(), instanceData[key].deaths);
}
uint32 GetDeaths(uint32 mapId, uint32 instanceId) const {
@@ -110,6 +115,16 @@ struct MpGroupData
}
void AddPlayerData(MpPlayerData* playerData) {
// Check if the playerData is already in the vector
if (std::any_of(players.begin(), players.end(), [playerData](MpPlayerData* existingData) {
return existingData->player == playerData->player;
})) {
MpLogger::warn("PlayerData for player {} is already in the players vector", playerData->player->GetName());
return;
}
// Add playerData to the vector
players.push_back(playerData);
}
@@ -138,8 +153,7 @@ struct MpScaleFactor
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) + " }";
", spellBonus: " + std::to_string(spellBonus) + "}";
}
};
@@ -272,10 +286,11 @@ struct MpCreatureData
/**@todo Add Affixes and Aura Spell methods */
};
class MpDataStore {
class MpDataStore
{
private:
MpDataStore()
: _playerData(std::make_unique<std::unordered_map<ObjectGuid, MpPlayerData>>()),
: _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>>()),
@@ -286,9 +301,15 @@ private:
_instanceCreatureData->reserve(500);
};
inline ~MpDataStore() {}
inline ~MpDataStore() {
for (auto& [guid, playerData] : *_playerData) {
delete playerData;
}
std::unique_ptr<std::unordered_map<ObjectGuid, MpPlayerData>> _playerData;
_playerData->clear();
}
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}
@@ -313,7 +334,7 @@ public:
MpPlayerData* GetPlayerData(ObjectGuid guid) {
try {
return &_playerData->at(guid);
return _playerData->at(guid);
} catch (const std::out_of_range& oor) {
return nullptr;
}
@@ -339,8 +360,7 @@ public:
void PushGroupInstanceKey(Group *group, uint32 mapId, uint32 instanceId);
// Data related to player settings and stags
void AddPlayerData(ObjectGuid guid, MpPlayerData pd);
void UpdatePlayerData(ObjectGuid guid, MpPlayerData pd);
void AddPlayerData(ObjectGuid guid, MpPlayerData* pd);
void RemovePlayerData(ObjectGuid guid);
void ResetPlayerData(ObjectGuid guid);
@@ -385,14 +405,17 @@ public:
void DBUpdatePlayerInstanceData(ObjectGuid playerGuid, MpDifficulty difficulty, uint32 mapId = 0, uint32 instanceId = 0, uint32 deaths = 0);
void DBResetPlayerDeaths(Player* player);
void DBAddPlayerDeath(Player* player, Creature* killer);
void DBAddPlayerDeath(Player* player, Creature* killer, MpDifficulty difficulty);
void DBAddPlayerDeath(Player* player);
void DBRemovePlayerData(ObjectGuid playerGuid);
void DBRemovePlayerInstanceData(uint32 instanceId);
void DBRemoveGroupInstanceData(uint32 instanceId);
void DBUpdateGroupData(ObjectGuid groupGuid, MpDifficulty difficulty, uint32 mapId, uint32 instanceId, uint32 deaths);
void DBUpdateGroupTimerDeaths(ObjectGuid groupGuid, uint32 mapId, uint32 instanceId, uint32 timer, uint32 deaths);
void DBRemoveGroupData(ObjectGuid groupGuid);
void DBAddGroupDeath(Group* group, uint32 mapId, uint32 instanceId, MpDifficulty difficulty);
//SavePlayerDungeonStats(Group* group, MpGroupData const* groupData);
static MpDataStore* instance() {

61
src/MpScheduler.h Normal file
View File

@@ -0,0 +1,61 @@
#ifndef MYTHICPLUS_SCHEDULER_H
#define MYTHICPLUS_SCHEDULER_H
#include "MpLogger.h"
#include "ScriptMgr.h"
#include "TaskScheduler.h"
#include <chrono>
enum MP_SCHEDULE_GROUP
{
MP_WORLD_TASK_GROUP = 100
};
/**
* Global scheduler specifically for handling events fired inside of mythic
* scripts
*
* Example usage:
* void ScheduleWorldTask(std::chrono::nanoseconds const& time, std::function<void (TaskContext)> task) {
* sMpScheduler.Schedule(time, MP_WORLD_TASK_GROUP, [](TaskContext ctx) {
* // do the things you want for Mp Task Group
* return;
* });
* }
*/
class MpScheduler
{
public:
static MpScheduler* instance () {
static MpScheduler instance;
return &instance;
}
MpScheduler(const MpScheduler&) = delete;
MpScheduler& operator=(const MpScheduler&) = delete;
TaskScheduler& GetWorldScheduler() {
return _worldScheduler;
}
private:
MpScheduler() {}
~MpScheduler() {}
TaskScheduler _worldScheduler;
};
#define sMpScheduler MpScheduler::instance()
#endif // MYTHICPLUS_SCHEDULER_H
// Attach the world scheduler to listen to world events
class MpScheduler_WorldScript : public WorldScript
{
public:
MpScheduler_WorldScript() : WorldScript("MpScheduler_GlobalScript") { }
void OnUpdate(uint32 diff) override {
sMpScheduler->GetWorldScheduler().Update(diff);
}
};

View File

@@ -1,6 +1,6 @@
#include "Creature.h"
#include "CreatureAI.h"
#include "CreatureHooks.h"
// #include "CreatureHooks.h"
#include "MpLogger.h"
#include "MythicPlus.h"
#include "ScriptMgr.h"
@@ -27,12 +27,12 @@ public:
}
void JustDied(Unit* killer) override {
sCreatureHooks->JustDied(me->ToCreature(), killer);
// sCreatureHooks->JustDied(me->ToCreature(), killer);
BaseAI::JustDied(killer);
}
void Reset() override {
sCreatureHooks->JustSpawned(me->ToCreature());
// sCreatureHooks->JustSpawned(me->ToCreature());
BaseAI::Reset();
}

View File

@@ -113,8 +113,6 @@ bool MythicPlus::EligibleDamageTarget(Unit* target)
bool MythicPlus::IsCreatureEligible(Creature* creature)
{
CreatureTemplate const * cInfo = creature->GetCreatureTemplate();
if (!creature) {
return false;
}
@@ -198,7 +196,7 @@ void MythicPlus::AddScaledCreature(Creature* creature, MpInstanceData* instanceD
// creature->SetAI(ai);
// We know the creature is scaled and in the instance to fire the event.
sCreatureHooks->AddToInstance(creature);
// sCreatureHooks->AddToInstance(creature);
std::string name = creature->GetName();
// Assign random affix for now.
@@ -461,7 +459,6 @@ int32 MythicPlus::ScaleHealSpell(SpellInfo const * spellInfo, uint32 heal, MpCre
auto effects = spellInfo->GetEffects();
for (uint8 i = 0; i < effects.size(); ++i)
{
SpellEffectInfo effect = effects[i];
MpLogger::debug(" >>> Spell {} effect {} value: {} by creature {}", spellInfo->SpellName[i], effects[i].Effect, heal, creature->GetName());
}
@@ -476,6 +473,11 @@ int32 MythicPlus::ScaleHealSpell(SpellInfo const * spellInfo, uint32 heal, MpCre
return int32(heal * scalingFactor * healMultiplier);
}
void MythicPlus::GroupReset(Group* /*group*/, Map* /* map */) {
// Stubbed out for later implementation
}
bool MythicPlus::IsFinalBoss(Creature* creature) {
std::array<uint32, 128> finalBosses = {
// --- WoW Classic Dungeons ---

View File

@@ -8,12 +8,25 @@
#include "Player.h"
#include "SpellInfo.h"
#include "Unit.h"
#include "TaskScheduler.h"
#include <map>
#include <string>
#include <vector>
#include <unordered_map>
// Used to limit the total advancment rank and allow for changing the max rank in one place.
inline const uint8 MP_MAX_ADVANCEMENT_RANK = 50;
/**
* Main Class for the mod responsible for controls related to scaling instances,
* handling logic related to setting up instances for MythicPlus to work.
*
* MpDataStore is heavily used as well for storing data in memory and interactions with
* database storage.
*
* This is a singleton instance that can be accessed through sMythicPlus.
*/
class MythicPlus
{
public:
@@ -60,7 +73,6 @@ public:
uint32 meleeAttackPowerDampener;
uint32 meleeAttackPowerStart;
enum MP_UNIT_EVENT_TYPE
{
UNIT_EVENT_MELEE,
@@ -121,6 +133,7 @@ public:
int32 ScaleHealSpell(SpellInfo const * spellInfo, uint32 heal, MpCreatureData* creatureData, Creature* creature, Creature* target, float healMultiplier);
static bool IsFinalBoss(Creature* creature);
static void GroupReset(Group* group, Map* map);
private:
MythicPlus() { }

View File

@@ -1,10 +1,17 @@
#include "Instances/Ragefire/boss_bazzalan.cpp"
#include "MpScheduler.h"
#include "MpLogger.h"
// Creature Overrides
enum {
RAGEFIRE_BAZZALAN = 11519
};
// This adds schedulers for use across scripts scoped to MythicPlus
void Add_MP_Schedulers() {
MpLogger::debug("Add_MP_Schedulers()");
new MpScheduler_WorldScript();
}
void Addmod_mythic_plusScripts();
void Add_MP_AllCreatureScripts();
void Add_MP_AllMapScripts();
@@ -14,12 +21,9 @@ void Add_MP_GroupScripts();
void Add_MP_PlayerScripts();
void Add_MP_UnitScripts();
void Add_MP_WorldScripts();
void Add_MP_PlayerMessageEvents();
// Mythic custom encounters for mythic+ dungeons
void Addmod_mythic_plusScripts()
{
void Addmod_mythic_plusScripts() {
Add_MP_AllCreatureScripts();
Add_MP_AllMapScripts();
Add_MP_CommandScripts();
@@ -28,6 +32,8 @@ void Addmod_mythic_plusScripts()
Add_MP_PlayerScripts();
Add_MP_UnitScripts();
Add_MP_WorldScripts();
Add_MP_Schedulers();
Add_MP_PlayerMessageEvents();
// new Ragefire_Bazzalan_Mythic();

View File

@@ -1,104 +0,0 @@
#include "MpLogger.h"
#include "MpDataStore.h"
#include "MythicPlus.h"
#include "Player.h"
#include "Group.h"
#include "ScriptMgr.h"
class MythicPlus_PlayerScript : public PlayerScript
{
public:
MythicPlus_PlayerScript() : PlayerScript("MythicPlus_PlayerScript") { }
void OnPlayerKilledByCreature(Player* player, Unit* killer)
{
MpLogger::debug("OnPlayerJustDied: %s", player->GetName());
Map* map = player->GetMap();
if(!sMythicPlus->IsMapEligible(map)) {
return;
}
if (!player) {
return;
}
MpGroupData *data = sMpDataStore->GetGroupData(player->GetGroup());
if (!data) {
MpLogger::error("OnPlayerJustDied: No group data found for %s", player->GetName());
return;
}
MpPlayerData *playerData = sMpDataStore->GetPlayerData(player->GetGUID());
if (!playerData) {
MpLogger::error("OnPlayerJustDied: No player data found for %s", player->GetName());
return;
}
// Update in memory store
playerData->AddDeath(map->GetId(), map->GetInstanceId());
// Track deaths and add to mp_player_death_stats
Creature* creature = killer->ToCreature();
if(creature) {
sMpDataStore->DBAddPlayerDeath(player, creature);
} else {
sMpDataStore->DBAddPlayerDeath(player);
}
// update that group data in the database
sMpDataStore->DBAddGroupDeath(data->group, map->GetId(), map->GetInstanceId(), data->difficulty);
}
void OnSave(Player* player) override { }
// When a player is bound to an instance need to make sure they are saved in the data soure to retrieve later.
void OnBindToInstance(Player* player, Difficulty /*difficulty*/, uint32 mapId, bool /*permanent*/) override
{
if(!player) {
return;
}
Group* group = player->GetGroup();
// If they are not in a group do nothing.
if(!group) {
return;
}
MpGroupData* data = sMpDataStore->GetGroupData(group);
// If there is not any mythic+ data set for this group do nothing.
if(!data) {
return;
}
Map* map = player->GetMap();
if(!map) {
MpLogger::warn("Player {} is not in a map", player->GetName());
return;
}
MpPlayerData* playerData = sMpDataStore->GetPlayerData(player->GetGUID());
if(!playerData) {
MpLogger::warn("PlayerData not found for player {} perhaps not in mythic+ group, bad player state?", player->GetName());
}
auto mapKey = sMpDataStore->GetInstanceDataKey(mapId, player->GetInstanceId());
playerData->instanceData.emplace(mapKey, MpPlayerInstanceData{
.deaths = 0,
});
sMpDataStore->DBUpdatePlayerInstanceData(player->GetGUID(), data->difficulty, map->GetId(), player->GetInstanceId(), 0);
if(group->GetLeaderGUID() == player->GetGUID()) {
sMpDataStore->DBUpdateGroupData(group->GetGUID(), data->difficulty, map->GetId(), player->GetInstanceId(), 0);
}
}
};
void Add_MP_PlayerScripts()
{
MpLogger::debug("Add_MP_PlayerScripts()");
new MythicPlus_PlayerScript();
}

View File

@@ -116,11 +116,13 @@ public:
if (!sMythicPlus->IsMapEligible(map)) {
return;
}
// Removes currenct GroupData Instance Data and removes from database storage
sMpDataStore->RemoveInstanceData(map->GetId(), map->GetInstanceId());
// If there is player data for this map reset it to default values
// remove group instance and group instance data from database during a reset
sMpDataStore->DBRemovePlayerInstanceData(map->GetInstanceId());
sMpDataStore->DBRemoveGroupInstanceData(map->GetInstanceId());
}
};

View File

@@ -28,8 +28,7 @@ class MythicPlus_GroupScript : public GroupScript
MpDifficulty difficulty = GetPlayerDifficulty(player);
MpPlayerData playerData = MpPlayerData(player, difficulty, group->GetGUID().GetCounter());
pd = &playerData;
sMpDataStore->AddPlayerData(guid, playerData);
sMpDataStore->AddPlayerData(guid, &playerData);
} else {
// If the player is joining a new group then reset the death counters otherwise let them ride

View File

@@ -0,0 +1,91 @@
#include "MpLogger.h"
#include "MythicPlus.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "AdvancementMgr.h"
#include "Chat.h"
#include "Channel.h"
#include "ChannelMgr.h"
#include "MpEventProcessor.h"
#include <boost/algorithm/string/predicate.hpp> // For starts_with
#include <vector>
#include <string>
#include <ranges>
/**
* This script file is a special event handler attached to the chat channel for MythicPlus to intercept
* message events from the client hidden chat channel and process them, as well as return events back to
* the client. It's a very simplified version of how Eluna / AIO manage messages from UI to C++ mods.
*
* All Messages come into a chat channel from a specific user on a hidden channel with details in the MythicPlus.h
* class for the protocol data definition.
*/
class MythicPlus_PlayerMessageEvents : public PlayerScript
{
public:
MythicPlus_PlayerMessageEvents() : PlayerScript("MythicPlus_PlayerMessageEvents") {}
/**
* Listens to AddOn Chat channel for Mythic+ communication between UI and server mythic+ functionality
*/
void OnChat(Player* player, uint32 type, uint32 lang, std::string& msg, Player* receiver) override
{
// All communication from the client should be a whisper to themselves over tha addon channel
if(!player || !receiver) {
return;
}
if(lang == LANG_ADDON) {
if(msg.empty()) {
MpLogger::info("Empty AddOn message received from player: {}", player->GetName());
return;
}
// if the message begins with our prefix for our data channel then process the event
if(boost::starts_with(msg, MP_DATA_CHAT_CHANNEL)) {
sMpEventProcessor->ProcessMessage(player, msg);
}
}
}
/**
* When a player logs in add them to the data channel specifically for Mythic+ communication
* between UI and server module.
*
* Load advancement data for the player at load time used to apply buffs.
*/
// void OnLogin(Player* player) override
// {
// if(!player) {
// return;
// }
// // Create a channel called MpEx if it does not exist
// MpLogger::info("Player {} logged in on team {}", player->GetName(), player->GetTeamId());
// ChannelMgr* cmg = ChannelMgr::forTeam(player->GetTeamId());
// if(!cmg) {
// MpLogger::error("Failed to get channel manager for team {}", player->GetTeamId());
// return;
// }
// Channel* channel = cmg->GetChannel(static_cast<std::string>(MP_DATA_CHAT_CHANNEL), player);
// if(!channel) {
// MpLogger::error("Failed to get mythic data channel for player {}", player->GetName());
// Channel* nchan = new Channel(static_cast<std::string>(MP_DATA_CHAT_CHANNEL), 17, 0, player->GetTeamId());
// if(!nchan) {
// MpLogger::error("Failed to create mythic data channel for player {}", player->GetName());
// return;
// }
// }
// }
};
void Add_MP_PlayerMessageEvents()
{
MpLogger::debug("Add_MP_PlayerEventMessages");
new MythicPlus_PlayerMessageEvents();
}

View File

@@ -0,0 +1,194 @@
#include "MpLogger.h"
#include "MpDataStore.h"
#include "MpScheduler.h"
#include "MythicPlus.h"
#include "Player.h"
#include "Group.h"
#include "ScriptMgr.h"
#include "TaskScheduler.h"
#include "AdvancementMgr.h"
class MythicPlus_PlayerScript : public PlayerScript
{
public:
MythicPlus_PlayerScript() : PlayerScript("MythicPlus_PlayerScript") {}
/**
* Mythic+ special actions when a player dies:
* - Track the death for the group
* - Update the player death stats
* - Determine whether or not the Group failed the instance due to death count setting.
*/
void OnPlayerKilledByCreature(Creature* killer, Player* player) override
{
Map* map = player->GetMap();
if(!sMythicPlus->IsMapEligible(map)) {
return;
}
if (!player) {
return;
}
Group* group = player->GetGroup();
if(!group) {
MpLogger::warn("Missing group data for player {}", player->GetName());
return;
}
MpGroupData *data = sMpDataStore->GetGroupData(player->GetGroup());
if (!data) {
MpLogger::warn("Missin group data for player {}", player->GetName());
return;
}
MpPlayerData *playerData = sMpDataStore->GetPlayerData(player->GetGUID());
if (!playerData) {
MpLogger::warn("Missin player data for player {}", player->GetName());
return;
}
playerData->AddDeath(map->GetId(), map->GetInstanceId());
if(killer) {
sMpDataStore->DBAddPlayerDeath(player, killer, data->difficulty);
} else {
sMpDataStore->DBAddPlayerDeath(player);
}
sMpDataStore->DBAddGroupDeath(group, player->GetMapId(), player->GetInstanceId(), data->difficulty);
uint32 totalDeaths = data->GetDeaths(player->GetMapId(), player->GetInstanceId());
MpLogger::info("Total Deaths: {}", totalDeaths);
if(totalDeaths > 1) {
MpLogger::debug(" :::: Player Deaths for Group too high! ::::::");
TaskScheduler& wScheduler = sMpScheduler->GetWorldScheduler();
wScheduler.Schedule(10s, MP_WORLD_TASK_GROUP, [player, map](TaskContext /*ctx*/) {
Group* group = player->GetGroup();
if(!group) {
return;
}
MythicPlus::GroupReset(group, map);
});
}
// if(totalDeaths > 1) {
// Map* map = player->GetMap();
// if(!map) {
// return;
// }
// Group* group = player->GetGroup();
// if(!group) {
// MpLogger::warn("Player {} is not in a group.", player->GetName());
// return;
// }
// // map->RemoveAllPlayers();
// MpLogger::info("Starting scheduled failure notification");
// // auto testlambda = [](TaskContext ctx) { return; };
// sMpScheduler->ScheduleWorldTask(1s, [](TaskContext ctx) {
// MpLogger::info("<<<<<<<<<<< Player Death Scheduler fire >>>>>>>>>>>>>");
// });
// sMpScheduler->GetWorldScheduler().Schedule(1s, [playerName = player->GetName()](TaskContext ctx) {
// MpLogger::info("<<<<<<<<<<< Player Death Scheduler fire {} >>>>>>>>>>>>>", playerName);
// return;
// });
// std::vector<Player*> players = GetGroupMembers(player);
// MpLogger::info("Failed mythic+ instance run notification fired. ");
// WorldPacket data;
// for(Player* player : players)
// {
// MpLogger::info("Seding notification of failure to player: {}", player->GetName());
// player->GetSession()->SendShowBank(player->GetGUID());
// // player->GetSession()->SendNotification("Your group has died too many time to continue.");
// // ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID_BOSS_EMOTE, LANG_UNIVERSAL, nullptr, player, message);
// // player->GetSession()->SendPacket(&data);
// }
// map->ToInstanceMap()->Reset(0);
// );
// }
}
void OnLogin(Player* player) override
{
// Load the player advancement data for the player when they login
int32 size = sAdvancementMgr->LoadPlayerAdvancements(player);
MpLogger::info("Loaded {} player advancements for player {}", size, player->GetName());
}
// When a player is bound to an instance need to make sure they are saved in the data soure to retrieve later.
void OnBindToInstance(Player* player, Difficulty /*difficulty*/, uint32 mapId, bool /*permanent*/) override
{
if(!player) {
return;
}
Group* group = player->GetGroup();
// If they are not in a group do nothing.
if(!group) {
return;
}
MpGroupData* data = sMpDataStore->GetGroupData(group);
// If there is not any mythic+ data set for this group do nothing.
if(!data) {
return;
}
Map* map = player->GetMap();
if(!map) {
MpLogger::warn("Player {} is not in a map", player->GetName());
return;
}
// get the player data or set it up
MpPlayerData* playerData = sMpDataStore->GetPlayerData(player->GetGUID());
if(!playerData) {
playerData = new MpPlayerData(player, data->difficulty, group->GetGUID().GetCounter());
sMpDataStore->AddPlayerData(player->GetGUID(), playerData);
}
// Add this players data to the group data
data->AddPlayerData(playerData);
auto mapKey = sMpDataStore->GetInstanceDataKey(mapId, player->GetInstanceId());
playerData->instanceData.emplace(mapKey, MpPlayerInstanceData{
.deaths = 0,
});
sMpDataStore->DBUpdatePlayerInstanceData(player->GetGUID(), data->difficulty, map->GetId(), map->GetInstanceId());
sMpDataStore->DBUpdateGroupData(group->GetGUID(), data->difficulty, map->GetId(), map->GetInstanceId(), 0);
}
std::vector<Player*> GetGroupMembers(Player* currentPlayer)
{
std::vector<Player*> groupPlayers;
Group* group = currentPlayer->GetGroup();
if (!group)
{
MpLogger::warn("Player is not in a group.");
return groupPlayers;
}
group->DoForAllMembers([&](Player* member) {
groupPlayers.push_back(member);
});
return groupPlayers;
}
};
void Add_MP_PlayerScripts()
{
MpLogger::debug("Add_MP_PlayerScripts()");
new MythicPlus_PlayerScript();
}

View File

@@ -32,6 +32,7 @@ public:
}
}
if(isHot) {
damage = modifyIncomingDmgHeal(MythicPlus::UNIT_EVENT_HOT, target, attacker, damage, spellInfo);
} else {
@@ -186,7 +187,6 @@ public:
* @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, damageOrHeal, sMpDataStore->GetCreatureData(attacker->GetGUID()), creature, attacker->ToCreature(), instanceData->boss.spell);

View File

@@ -1,9 +1,11 @@
#include "Config.h"
#include "MythicPlus.h"
#include "MpDataStore.h"
#include "AdvancementMgr.h"
#include "MpLogger.h"
#include "Player.h"
#include "ScriptMgr.h"
#include "MpEventHandlers.cpp"
class MythicPlus_WorldScript : public WorldScript
{
@@ -95,6 +97,16 @@ public:
{
int32 size = sMpDataStore->LoadScaleFactors();
MpLogger::info("Loaded {} Mythic+ Scaling Factors from database...", size);
size = sAdvancementMgr->LoadAdvancementRanks();
MpLogger::info("Loaded {} advancement ranks...", size);
size = sAdvancementMgr->LoadMaterialTypes();
MpLogger::info("Loaded {} material types...", size);
// Registering event handlers for the Mythic+ events from client
MP_Register_EventHandlers();
MpLogger::info("Registered Mythic+ Event Handlers...");
}
};

70
src/Spells/Toughness.cpp Normal file
View File

@@ -0,0 +1,70 @@
// #include "CreatureScript.h"
// #include "PetDefines.h"
// #include "Player.h"
// #include "MpLogger.h"
// #include "SpellAuraEffects.h"
// #include "SpellInfo.h"
// #include "SpellMgr.h"
// #include "SpellScript.h"
// #include "SpellScriptLoader.h"
// #include "UnitAI.h"
// #include "World.h"
// class spell_mp_toughness_aura : public AuraScript
// {
// PrepareAuraScript(spell_mp_toughness_aura);
// void HandleEffectApply(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
// {
// Player* player = GetTarget()->ToPlayer();
// if (!player)
// return;
// MpLogger::info("Applying Advancement Toughness to Player {}", player->GetName());
// if (Unit* caster = GetCaster())
// {
// if (caster->IsPlayer())
// {
// Player* player = caster->ToPlayer();
// // Add 500 Strength
// player->HandleStatModifier(UNIT_MOD_STAT_STRENGTH, TOTAL_VALUE, 500, true);
// // Apply red glow visual effect
// caster->SendPlaySpellVisual(11674); // Visual ID 11674 for red glow
// }
// }
// }
// void HandleEffectRemove(AuraEffect const* /*aurEff*/, AuraEffectHandleModes /*mode*/)
// {
// LOG_INFO("server.worldserver", "spell_custom_red_glow_strength_aura::HandleEffectRemove");
// if (Unit* caster = GetCaster())
// {
// if (caster->IsPlayer())
// {
// Player* player = caster->ToPlayer();
// // Remove 500 Strength
// player->HandleStatModifier(UNIT_MOD_STAT_STRENGTH, TOTAL_VALUE, 500, false);
// }
// }
// }
// void Register() override
// {
// LOG_INFO("server.worldserver", "spell_custom_red_glow_strength_aura::Register");
// OnEffectApply += AuraEffectApplyFn(spell_custom_red_glow_strength_aura::HandleEffectApply, EFFECT_0, SPELL_AURA_MOD_STAT, AURA_EFFECT_HANDLE_REAL);
// OnEffectRemove += AuraEffectRemoveFn(spell_custom_red_glow_strength_aura::HandleEffectRemove, EFFECT_0, SPELL_AURA_MOD_STAT, AURA_EFFECT_HANDLE_REAL);
// }
// };
// void AddSC_spell_custom_red_glow_strength_aura()
// {
// // LOG_INFO("server.loading", "Registering spell custom_red_glow_strength_aura");
// RegisterSpellScript(spell_custom_red_glow_strength_aura);
// }