mirror of
https://github.com/araxiaonline/mod-mythic-plus.git
synced 2026-06-13 03:02:24 -04:00
Compare commits
35 Commits
feat/add-d
...
refactor-s
| Author | SHA1 | Date | |
|---|---|---|---|
| ad39d53c58 | |||
| b4013b7024 | |||
| 7d1a9018df | |||
| d4fa53d435 | |||
| 2aeafa689a | |||
| f42cba3099 | |||
| aa58b493ba | |||
| 5387960965 | |||
| 4be3fae368 | |||
| d410220bd0 | |||
| 4df7800afa | |||
| 173d19bcd3 | |||
| 738194fbfa | |||
| 81b7a87ced | |||
| 8e0809cb1c | |||
| cb48af9e9d | |||
| acf11d25f2 | |||
| 41fd4876cf | |||
| ef62c10df3 | |||
| 5fbb0eac74 | |||
| 0982e24f06 | |||
| fa7389f24a | |||
| 8436460350 | |||
| 3bd1f3cdc6 | |||
| 6115459150 | |||
| 3093ab3280 | |||
| fa6f3f3eab | |||
| ccf222ec4f | |||
| 91d3f91dfc | |||
| 9612974b01 | |||
| fd5e186032 | |||
| baf0d8e6e7 | |||
| f3230a6559 | |||
| 86d5ba83f1 | |||
|
|
318244f0fd |
100
data/sql/db-characters/base/01_mp_schema.sql
Normal file
100
data/sql/db-characters/base/01_mp_schema.sql
Normal 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)
|
||||
);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
48
data/sql/db-world/base/01_mp_world_schema.sql
Normal file
48
data/sql/db-world/base/01_mp_world_schema.sql
Normal 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)
|
||||
);
|
||||
537
data/sql/db-world/base/02_mp_material_types.sql
Normal file
537
data/sql/db-world/base/02_mp_material_types.sql
Normal 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.
|
||||
);
|
||||
@@ -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
|
||||
253
data/sql/db-world/base/07_mp_upgrade_ranks.sql
Normal file
253
data/sql/db-world/base/07_mp_upgrade_ranks.sql
Normal 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)
|
||||
;
|
||||
@@ -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'
|
||||
);
|
||||
@@ -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
|
||||
);
|
||||
@@ -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
415
src/AdvancementMgr.cpp
Normal 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
144
src/AdvancementMgr.h
Normal 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
|
||||
32
src/Creature/CreatureOverride.h
Normal file
32
src/Creature/CreatureOverride.h
Normal 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() {}
|
||||
// };
|
||||
45
src/Event/MpClientDispatch.cpp
Normal file
45
src/Event/MpClientDispatch.cpp
Normal 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;
|
||||
}
|
||||
35
src/Event/MpClientDispatcher.h
Normal file
35
src/Event/MpClientDispatcher.h
Normal 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
47
src/Event/MpEvent.h
Normal 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
|
||||
139
src/Event/MpEventHandlers.cpp
Normal file
139
src/Event/MpEventHandlers.cpp
Normal 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);
|
||||
}
|
||||
131
src/Event/MpEventProcessor.cpp
Normal file
131
src/Event/MpEventProcessor.cpp
Normal 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;
|
||||
}
|
||||
86
src/Event/MpEventProcessor.h
Normal file
86
src/Event/MpEventProcessor.h
Normal 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
11
src/MpConstants.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
61
src/MpScheduler.h
Normal 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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
@@ -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() { }
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
91
src/Scripts/PlayerMessageEvents.cpp
Normal file
91
src/Scripts/PlayerMessageEvents.cpp
Normal 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();
|
||||
}
|
||||
194
src/Scripts/PlayerScript.cpp
Normal file
194
src/Scripts/PlayerScript.cpp
Normal 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();
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
70
src/Spells/Toughness.cpp
Normal 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);
|
||||
// }
|
||||
Reference in New Issue
Block a user