mythic changes in development coded new npc

This commit is contained in:
2025-05-06 19:13:38 -04:00
parent 5c1e3ef667
commit 325bf44fc9
9 changed files with 2359 additions and 1 deletions

View File

@@ -0,0 +1,35 @@
import { Logger } from "../../classes/logger";
const log = new Logger("AdvancementState");
export type AdvancementType = "Magic" | "Attack" | "Defense";
export class AdvancementState {
private advancement: string | null = null;
private advType: AdvancementType | null = null;
SetType(type: AdvancementType): void {
this.advType = type;
}
GetAdvType(): AdvancementType | null {
return this.advType;
}
SetAdvancement(icon: string): void {
this.advancement = icon;
}
GetAdvancement(): string | null {
return this.advancement;
}
ClearAdvancement() {
this.advancement = null;
}
ClearState() {
this.advancement = null;
this.advType = null;
}
}

View File

@@ -0,0 +1,591 @@
/** @ts-expect-error */
let aio: AIO = {};
const id = (name: string) => `MythicAdvUI_${name}`;
function GetComponent<T extends WoWAPI.Frame = WoWAPI.Frame>(name: string, type: string = "Frame"): T | null {
const component = _G[id(name)];
return component ? component as T : null;
}
import { colors } from "../../classes/ui-utils";
import { AdvancementState } from "./advancement_state";
/**
* Advancement Name and Spell Reference
*
* ('80000001','spell_mp_titans_strength_aura'),
* ('80000002','spell_mp_steel_forged_aura'),
* ('80000003','spell_mp_celestial_grace_aura'),
* ('80000004','spell_mp_forbidden_knowledge_aura'),
* ('80000005','spell_mp_spectral_reflexes_aura'),
* ('80000006','spell_mp_eldritch_barrier_aura'),
* ('80000007','spell_mp_hellfire_shielding_aura'),
* ('80000008','spell_mp_primal_endurance_aura'),
* ('80000009','spell_mp_lichbane_aura'),
* ('80000010','spell_mp_glacial_fortress_aura');
*/
if (!aio.AddAddon()) {
const upgradeUIHandlers = aio.AddHandlers("MythicAdvUI", {});
const UpgradeUIFrames: Map<string, WoWAPI.Frame> = new Map();
// const itemSlots: Map<number, string> = new Map();
// let selectedDice: WoWAPI.Button | null = null;
// let rolling: boolean = false;
const mainWidth = 768;
const mainHeight = 512;
const centerOffset = mainWidth / 2;
// Load the advancement state based on the requested type for now just do magic type
const advancementState = new AdvancementState();
advancementState.SetType("Magic");
const customTextures = {
// frame background elements
"bgFrame": "Interface\\AddOns\\MythicPlusData\\Textures\\mythic-adv-frame",
"bgFrameHq": "Interface\\AddOns\\MythicPlusData\\Textures\\mythic-adv-frame-hq",
"darkBg": "Interface\\AddOns\\MythicPlusData\\Textures\\DialogBox-Background-Dark",
"diceFrame": "Interface\\AddOns\\MythicPlusData\\Textures\\advancement_frame",
// advancement icons buttons
"str": "Interface\\AddOns\\MythicPlusData\\Textures\\str_adv.tga",
"agi": "Interface\\AddOns\\MythicPlusData\\Textures\\agi_adv.tga",
"int": "Interface\\AddOns\\MythicPlusData\\Textures\\int_adv.tga",
"spr": "Interface\\AddOns\\MythicPlusData\\Textures\\spr_adv.tga",
"sta": "Interface\\AddOns\\MythicPlusData\\Textures\\sta_adv.tga",
"icon_overlay": "Interface\\AddOns\\MythicPlusData\\Textures\\icon_overlay.tga",
"int_selected": "Interface\\AddOns\\MythicPlusData\\Textures\\int_adv_selected.tga",
"spr_selected": "Interface\\AddOns\\MythicPlusData\\Textures\\spr_adv_selected.tga",
// Dice buttons
"single_roll": "Interface\\AddOns\\MythicPlusData\\Textures\\single_roll.tga",
"double_roll": "Interface\\AddOns\\MythicPlusData\\Textures\\double_roll.tga",
"triple_roll": "Interface\\AddOns\\MythicPlusData\\Textures\\triple_roll.tga",
"single_roll_selected": "Interface\\AddOns\\MythicPlusData\\Textures\\single_roll_selected.tga",
"double_roll_selected": "Interface\\AddOns\\MythicPlusData\\Textures\\double_roll_selected.tga",
"triple_roll_selected": "Interface\\AddOns\\MythicPlusData\\Textures\\triple_roll_selected.tga",
// Advancement Bars
"bars": "Interface\\AddOns\\MythicPlusData\\Textures\\adv_bars.tga",
// Roll Numbers
"numbers": "Interface\\AddOns\\MythicPlusData\\Textures\\big_numbers.tga",
}
interface ItemSlot extends WoWAPI.Button {
hasItem?: boolean;
itemLink?: string;
}
function GetTexture(name: string): string {
return customTextures[name] || "";
}
function UpdateDiceVisibility(show: boolean): void {
const buttons = ['single_roll', 'double_roll', 'triple_roll'];
buttons.forEach(name => {
const button = GetComponent<WoWAPI.Button>(`${name}_button`, "Button");
if (button) {
if (show) {
button.SetAlpha(0.8);
button.Enable();
} else {
button.SetAlpha(0.0);
button.Disable();
}
}
});
}
function SelectAdvancement(name: string, button: WoWAPI.Button): void {
// if the button click is itself, reset it
if (advancementState.GetAdvancement() === name) {
button.SetNormalTexture(GetTexture(name));
advancementState.SetAdvancement(null);
UpdateDiceVisibility(false);
} else {
// Set the new advancement
button.SetNormalTexture(GetTexture(`${name}_selected`));
// Handle opposing button
const opposingStats = {
'int': 'spr',
'spr': 'int',
'str': 'agi',
'agi': 'str'
};
const opposingStat = opposingStats[name];
if (opposingStat) {
const opposingButton = GetComponent<WoWAPI.Button>(`${opposingStat}_button`, "Button");
if (opposingButton) {
opposingButton.SetNormalTexture(GetTexture(opposingStat));
}
}
// Update advancement state and show dice buttons
advancementState.SetAdvancement(name);
UpdateDiceVisibility(true);
}
}
/**
* Add icons icons used for advancement upgrades.
* @param frame Main Frame
*/
function CreateAdvIcons(frame: WoWAPI.Frame): void {
const ICON_POS_LEFT = 38;
const ICON_POS_TOP = -150;
let topIcon: string = "";
let bottomIcon: string = "";
let topText: string = "";
let bottomText: string = "";
if (advancementState.GetAdvType() === "Magic") {
topIcon = "spr";
bottomIcon = "int";
topText = "Celestial Grace";
bottomText = "Forbidden Knowledge";
}
else if (advancementState.GetAdvType() === "Attack") {
topIcon = "str";
bottomIcon = "agi";
topText = "Titan's Strength";
bottomText = "Spectral Reflexes";
}
else if (advancementState.GetAdvType() === "Defense") {
topIcon = "sta";
topText = "Steel Forged"
}
const topButton = CreateFrame("Button", id(`${topIcon}_button`), frame);
topButton.SetSize(132, 132);
topButton.SetPoint("TOPLEFT", ICON_POS_LEFT, ICON_POS_TOP);
topButton.SetNormalTexture(GetTexture(topIcon));
topButton.SetHighlightTexture(GetTexture("icon_overlay"));
topButton.SetScript("OnClick", function(self: WoWAPI.Button) {
SelectAdvancement(topIcon, self);
});
topButton.Show();
const bottomButton = CreateFrame("Button", id(`${bottomIcon}_button`), frame);
bottomButton.SetSize(132, 132);
bottomButton.SetPoint("TOPLEFT", topButton, "BOTTOMLEFT", 0, -20);
bottomButton.SetNormalTexture(GetTexture(bottomIcon));
bottomButton.SetHighlightTexture(GetTexture("icon_overlay"));
bottomButton.SetScript("OnClick", function(self: WoWAPI.Button) {
SelectAdvancement(bottomIcon, self);
});
bottomButton.Show();
// Add advancement bars
const topbarTexture = frame.CreateTexture(id("topbar_gold"), "ARTWORK");
topbarTexture.SetPoint("LEFT", topButton, "RIGHT", -4, -10);
topbarTexture.SetSize(187, 37);
topbarTexture.SetTexture(GetTexture("bars"));
topbarTexture.SetTexCoord(36/256, 218/256, 0/256, 37/256);
const bottombarTexture = frame.CreateTexture(id("bottombar_gold"), "ARTWORK");
bottombarTexture.SetPoint("LEFT", bottomButton, "RIGHT", -4, -10);
bottombarTexture.SetSize(187, 37);
bottombarTexture.SetTexture(GetTexture("bars"));
bottombarTexture.SetTexCoord(36/256, 218/256, 0/256, 37/256);
const topbarText = frame.CreateFontString(id("topbar_text"), "OVERLAY");
topbarText.SetPoint("CENTER", topbarTexture, "CENTER", 0, 33);
topbarText.SetTextColor(1, 1, 1, 1) // White
topbarText.SetFont("Fonts\\FRIZQT__.TTF", 16)
topbarText.SetText(topText);
const bottombarText = frame.CreateFontString(id("bottombar_text"), "OVERLAY");
bottombarText.SetPoint("CENTER", bottombarTexture, "CENTER", 0, 33);
bottombarText.SetTextColor(1, 1, 1, 1) // White
bottombarText.SetFont("Fonts\\FRIZQT__.TTF", 16)
bottombarText.SetText(bottomText);
}
/**
*
*/
/**
* Creates the dice roll buttons that appear below the roll frame
* @param frame Main Frame
* @param rollFrame The frame containing the roll numbers
*/
function CreateDiceButtons(frame: WoWAPI.Frame, rollFrame: WoWAPI.Frame): void {
const buttonWidth = 72;
const buttonHeight = 72;
const spacing = 20;
const totalWidth = (buttonWidth * 3) + (spacing * 2);
const startX = -(totalWidth / 2) + (buttonWidth / 2);
const buttons = [
{ name: 'single_roll', rolls: 1 },
{ name: 'double_roll', rolls: 2 },
{ name: 'triple_roll', rolls: 3 }
];
let selectedButton = GetComponent<WoWAPI.Button>("selected_dice_button", "Button");
buttons.forEach((buttonInfo, index) => {
const buttonId = `${buttonInfo.name}_button`;
let button = GetComponent<WoWAPI.Button>(buttonId, "Button");
if (!button) {
button = CreateFrame("Button", id(buttonId), frame) as WoWAPI.Button;
button.SetSize(buttonWidth, buttonHeight);
button.SetPoint("TOP", rollFrame, "BOTTOM", startX + (index * (buttonWidth + spacing)), -75);
const normalTexture = button.CreateTexture(null, "BACKGROUND");
normalTexture.SetTexture(GetTexture(buttonInfo.name));
normalTexture.SetAllPoints(button);
button.SetNormalTexture(normalTexture);
const highlightTexture = button.CreateTexture(null, "HIGHLIGHT");
highlightTexture.SetTexture(GetTexture(`${buttonInfo.name}_selected`));
highlightTexture.SetAllPoints(button);
button.SetHighlightTexture(highlightTexture);
// Start hidden by default
button.SetAlpha(0.0);
button.SetScript("OnClick", function() {
if (selectedButton) {
const prevButtonInfo = buttons.find(b => b.name === selectedButton.GetName().split('_')[0]);
if (prevButtonInfo) {
selectedButton.SetNormalTexture(GetTexture(prevButtonInfo.name));
}
}
if (selectedButton === button) {
selectedButton = null;
} else {
selectedButton = button;
button.SetNormalTexture(GetTexture(`${buttonInfo.name}_selected`));
// Here you would typically trigger the roll with the number of dice
frame["rollNumbers"](buttonInfo.rolls, 25 * buttonInfo.rolls);
}
});
}
});
// Ensure buttons are hidden/shown based on current advancement state
UpdateDiceVisibility(advancementState.GetAdvancement() !== null);
}
/**
* Add title text to the frame.
* @param frame Main Frame
*/
function AddTitle(frame: WoWAPI.Frame): void {
// Add title text
const titleBar = frame.CreateFontString(id("TitleBar"), "OVERLAY");
titleBar.SetPoint("TOP", -150, -40);
titleBar.SetTextColor(1, 0.84, 0, 1) // Golden yellow
titleBar.SetFont("Interface/Modules/MythicPlus/Fonts/NOTO.TTF", 16)
titleBar.SetText(`Mythic Upgrades`);
}
/**
* This adds in the big numbers that will flash to simulate a dice rolls...
* even though the dice roll actually happens on the server in the module.
* @param frame
*/
function ShowRollNumbers(frame: WoWAPI.Frame): void {
let rollFrame = GetComponent("roll_frame");
if (!rollFrame) {
rollFrame = CreateFrame("Frame", id("roll_frame"), frame);
rollFrame.SetSize(120, 120);
rollFrame.SetPoint("CENTER", 200, -24);
rollFrame.SetBackdrop({
bgFile: GetTexture("darkBg"),
tile: false,
tileSize: 120,
insets: { left: 1, right: 1, top: 1, bottom: 1 },
});
rollFrame.SetBackdropColor(0, 0, 0, 0.3); // RGB + alpha
}
rollFrame.Show();
// create a texture that will change the graphic every few seconds between 1-25 using big_number tga
// for numbers greater than 9 it will need to add to smaller textures. Below are coordinates of the
// big_number tga [UL, UR, LL, LR]
const coords: {[key: number]: number[]} = {
0: [35, 96, 8, 81],
1: [174, 210, 8, 81],
2: [292, 345, 8, 81],
3: [424, 472, 8, 81],
4: [33, 95, 98, 163],
5: [167, 217, 98, 163],
6: [291, 346, 98, 163],
7: [425, 471, 98, 163],
8: [38, 89, 182, 247],
9: [165, 218, 182, 247],
}
let currentTextures: WoWAPI.Texture[] = [];
function clearTextures() {
currentTextures.forEach(texture => {
texture.Hide();
texture.ClearAllPoints();
});
currentTextures = [];
}
function createNumberTexture(digit: number, xOffset: number = 0) {
const rollTexture = rollFrame.CreateTexture(null, "ARTWORK");
rollTexture.SetSize(60, 60);
rollTexture.SetPoint("CENTER", rollFrame, "CENTER", xOffset, 0);
rollTexture.SetTexture(GetTexture("numbers"));
if (coords[digit]) {
rollTexture.SetTexCoord(
coords[digit][0] / 512,
coords[digit][1] / 512,
coords[digit][2] / 256,
coords[digit][3] / 256
);
}
currentTextures.push(rollTexture);
return rollTexture;
}
function showNumber(num: number) {
clearTextures();
if (num >= 10) {
const tens = Math.floor(num / 10);
const ones = num % 10;
createNumberTexture(tens, -30).Show();
createNumberTexture(ones, 30).Show();
} else {
createNumberTexture(num, 0).Show(); // Explicitly set offset to 0 for single digits
}
}
let rolling = false;
let lastUpdate = 0;
// Make rollNumbers accessible on the frame for external calls
frame["rollNumbers"] = function(min: number, max: number, duration: number = 2000) {
if (rolling) return;
clearTextures();
rolling = true;
let elapsed = 0;
frame.SetScript("OnUpdate", function(_, deltaTime) {
elapsed = elapsed + deltaTime * 1000;
lastUpdate += deltaTime * 1000;
// Update number every 100ms
if (lastUpdate >= 100) {
lastUpdate = 0;
const randomNum = Math.floor(Math.random() * (max - min + 1)) + min;
showNumber(randomNum);
}
if (elapsed >= duration) {
frame.SetScript("OnUpdate", null);
rolling = false;
}
});
}
}
/**
* Create the main upgrade window frame.
* @returns Main Frame
*/
function CreateUpgradeWindow(state: AdvancementState): WoWAPI.Frame {
// Main frame creation
const frame = CreateFrame("Frame", id("main"), UIParent);
frame.SetSize(mainWidth, mainHeight);
frame.SetPoint("CENTER");
frame.SetMovable(true);
frame.EnableMouse(true);
frame.RegisterForDrag("LeftButton");
frame.SetScript("OnDragStart", frame.StartMoving);
frame.SetScript("OnDragStop", frame.StopMovingOrSizing);
frame.SetFrameLevel(100); // Set high frame level to stay on top
// Background texture
const bgTexture = frame.CreateTexture(id("background"), "BACKGROUND");
bgTexture.SetPoint('TOPLEFT');
bgTexture.SetWidth(mainWidth);
bgTexture.SetHeight(mainHeight);
bgTexture.SetTexture(GetTexture("bgFrameHq"));
bgTexture.SetTexCoord(0, 768/1024, 0, 1); // Show 75% of width (768/1024) and full height
// Add components
CreateAdvIcons(frame);
AddTitle(frame);
ShowRollNumbers(frame);
const rollFrame = GetComponent("roll_frame");
if (rollFrame) {
CreateDiceButtons(frame, rollFrame);
}
return frame;
}
// Remove the template's close button
// const templateCloseButton = frame.GetChildren()[0] as WoWAPI.Frame;
// if (templateCloseButton) {
// templateCloseButton.Hide();
// }
// Add our custom close button
// const closeButton = CreateFrame("Button", id("CloseButton"), frame, "UIPanelCloseButton");
// closeButton.SetPoint("TOPRIGHT", -5, -5);
// closeButton.SetScript("OnClick", () => {
// frame.Hide();
// });
// Header Row: Skill Icons & Ranks
// for (let i = 0; i < 3; i++) {
// const skillFrame = CreateFrame("Frame", id(`Skill_${i}`), frame);
// skillFrame.SetSize(64, 64);
// skillFrame.SetPoint("TOP", -125 + i * 125, -55);
// const skillIcon = skillFrame.CreateTexture(id(`SkillIcon_${i}`), "ARTWORK");
// skillIcon.SetTexture("Interface\\Icons\\INV_Misc_QuestionMark");
// skillIcon.SetAllPoints();
// const skillName = skillFrame.CreateFontString(id(`SkillName_${i}`), "OVERLAY", "GameFontNormalLarge");
// skillName.SetPoint("TOP", skillFrame, "BOTTOM", 0, -5);
// skillName.SetText(`Skill ${i + 1}`);
// const skillRank = skillFrame.CreateFontString(id(`SkillRank_${i}`), "OVERLAY", "GameFontHighlightLarge");
// skillRank.SetPoint("TOP", skillName, "BOTTOM", 0, -2);
// skillRank.SetText("0 / 50");
// }
// Add a new texture in the middle of the frame that is 512x512 and the middle layer
// const centerTexture = frame.CreateTexture(id("DiceTextureBack"), "ARTWORK");
// centerTexture.SetDrawLayer("ARTWORK", 3);
// centerTexture.SetSize(128, 128);
// centerTexture.SetPoint("CENTER", frame, "CENTER", 0, -20);
// centerTexture.SetTexture("Interface\\MythicPlus\\adv-dice-light.blp");
// const circleTexture = frame.CreateTexture(id("DiceTextureBack"), "BACKGROUND");
// circleTexture.SetDrawLayer("ARTWORK", 5);
// circleTexture.SetSize(512, 512);
// circleTexture.SetAlpha(0.20);
// circleTexture.SetPoint("CENTER", frame, "CENTER", 0, -20);
// circleTexture.SetTexture("Interface\\MythicPlus\\gold-circle.blp");
// // Add a glowing effect behind the dice
// const glowTexture = frame.CreateTexture(id("DiceGlow"), "ARTWORK");
// glowTexture.SetDrawLayer("ARTWORK", -2);
// glowTexture.SetSize(300, 300);
// glowTexture.SetPoint("CENTER", centerTexture, "CENTER", 0, 0);
// glowTexture.SetTexture("Interface\\GLUES\\MODELS\\UI_MainMenu_Legion\\UI_MainMenu_Legion3");
// glowTexture.SetAlpha(0.3);
// Roll Display with better font handling
// const rollDisplay = frame.CreateFontString(id("RollDisplay"),"ARTWORK");
// rollDisplay.SetFont("Interface\\MythicPlus\\NOTO.TTF", 256, "THICKOUTLINE");
// rollDisplay.SetPoint("CENTER", frame, "CENTER", 0, -80);
// rollDisplay.SetTextColor(1, 0.84, 0, 1); // More golden yellow color
// rollDisplay.SetText("-");
// Dice Multipliers with Selection Logic
// const diceMultipliers = ["1x", "2x", "3x"];
// for (let k = 0; k < diceMultipliers.length; k++) {
// const diceButton = CreateFrame("Button", id(`Dice_${k}`), frame);
// diceButton.SetSize(24, 24);
// diceButton.SetPoint("TOPLEFT", 150 + (k * 30), -450);
// const diceText = diceButton.CreateFontString(id(`DiceText_${k}`), "OVERLAY", "GameFontHighlight");
// diceText.SetPoint("CENTER", diceButton, "CENTER", 0, 0);
// diceText.SetText(diceMultipliers[k]);
// diceButton.SetScript("OnClick", function() {
// if (selectedDice) {
// selectedDice.SetAlpha(1.0); // Reset previous selection
// }
// selectedDice = diceButton;
// selectedDice.SetAlpha(1.5); // Glow effect
// });
// }
// Roll Button with animation logic
// const rollButton = CreateFrame("Button", id("RollButton"), frame, "UIPanelButtonTemplate");
// rollButton.SetSize(100, 30);
// rollButton.SetPoint("BOTTOM", frame, "BOTTOM", 0, 50);
// rollButton.SetText("Roll");
// function rollDice(min: number, max: number, duration: number = 5000) {
// if (rolling) return;
// rolling = true;
// let elapsed = 0;
// let lastUpdate = 0;
// frame.SetScript("OnUpdate", function(_, deltaTime) {
// elapsed = elapsed + deltaTime * 5000;
// lastUpdate += deltaTime * 1000;
// // Only update every 200ms to slow down the rolling animation
// if (lastUpdate >= 100) {
// lastUpdate = 0;
// rollDisplay.SetText(`${Math.floor(Math.random() * (max - min + 1)) + min}`);
// }
// if (elapsed >= duration) {
// frame.SetScript("OnUpdate", null);
// rolling = false;
// // Set final roll
// rollDisplay.SetText(`${Math.floor(Math.random() * (max - min + 1)) + min}`);
// }
// });
// }
// rollButton.SetScript("OnClick", function() {
// rollDice(2, 23); // Default to 2-23 range for backward compatibility
// });
// Roll History
// const historyFrame = CreateFrame("Frame", id("HistoryFrame"), frame);
// historyFrame.SetSize(380, 80);
// historyFrame.SetPoint("BOTTOM", frame, "BOTTOM", 0, 10);
// const historyText = historyFrame.CreateFontString(id("HistoryText"), "OVERLAY", "GameFontHighlight");
// historyText.SetPoint("TOPLEFT", historyFrame, "TOPLEFT", 10, -10);
// historyText.SetText("Roll History:");
let frame: WoWAPI.Frame | undefined = undefined;
if(!UpgradeUIFrames.has(advancementState.GetAdvType())) {
// frame = CreateUpgradeWindow(advancementState);
} else {
// if we have the frame already show it
// frame = UpgradeUIFrames.get(advancementState.GetAdvType());
}
// For debugging purposes show the window
//frame.Show();
// Triggered from ths server to be set up later
upgradeUIHandlers.ShowUpgradeWindow = () => {
// if (!UpgradeUIFrames.has(1)) {
// CreateUpgradeWindow();
// }
// UpgradeUIFrames.get(1).Show();
};
}

View File

@@ -0,0 +1,223 @@
/** @ts-expect-error */
let aio: AIO = {};
const SCRIPT_NAME = 'UpgradeUI';
import { Logger } from "../../classes/logger";
const log = new Logger(SCRIPT_NAME);
// Flag to track if materials have been loaded
let materialsLoaded = false;
type ItemType = {
entry: number;
name: string;
}
type Materials = {
name: string;
items: Array<ItemType>
}
const upgradeMaterials: Record<number, Materials> = {};
/**
* Helper function to safely get items from a material
*/
const getItemsFromMaterial = (material: Materials): Array<ItemType> => {
if (!material) {
return [];
}
if (!material.items) {
material.items = [];
}
return material.items;
};
/**
* Handles the logic for showing the upgrade UI when a player types .advanceme
*/
const ShowUpgradeUI: player_event_on_command = (event: number, player: Player, command: string): boolean => {
if (command === "advanceme") {
log.info(`Showing Upgrade UI for player: ${player.GetName()}`);
aio.Handle(player, 'UpgradeUI', 'ShowUpgradeWindow');
return false;
}
return true;
};
/**
* Get a list of materials from the database that can be used for mythic plus advancement
* @param event World startup event
*/
// const GetMaterialsList: eluna_event_on_lua_state_open = (event: number) => {
// const query = WorldDBQuery(`select materialId, entry, name from mp_material_types`);
// const categoryMap: Record<number, string> = {
// 1: "Cloth",
// 2: "Rare Cloth",
// 3: "Plants",
// 4: "Rare Plants",
// 5: "Ore",
// 6: "Rare Ore",
// 7: "Leather",
// 8: "Rare Leather",
// 9: "Gems",
// 10: "Rare Gems",
// 11: "Enchanting",
// 12: "Rare Enchanting",
// 13: "Water Elements",
// 14: "Rare Water Elements",
// 15: "Fire Elements",
// 16: "Rare Fire Elements",
// 17: "Nature Elements",
// 18: "Rare Earth Elements",
// 19: "Shadow Elements",
// 20: "Rare Shadow Elements",
// 21: "Arcane Elements",
// 22: "Rare Arcane Elements",
// 23: "Veilstone",
// };
// if (query) {
// do {
// const materialId = query.GetUInt32(0);
// const entry = query.GetUInt32(1);
// const categoryName = categoryMap[materialId] || "Unknown";
// // Initialize the material group if it doesn't exist
// if (!upgradeMaterials[materialId]) {
// upgradeMaterials[materialId] = {
// name: categoryName,
// items: []
// };
// }
// // Add the item to the material group
// upgradeMaterials[materialId].items.push({
// entry: entry,
// name: query.GetString(2)
// });
// } while (query.NextRow());
// }
// log.info(`Loaded ${Object.keys(upgradeMaterials).length} material groups with items for mythic plus advancement`);
// materialsLoaded = true;
// };
// /**
// * This will show all the counts for a player by from the upgradeMaterials object
// */
// function GetMaterials(this:void, player: Player) {
// // Create a readable string representation of the materials and their counts
// let materialsString = 'Your Mythic+ Advancement Materials:\n';
// log.info("Showing material counts");
// // Track total materials by category
// const materialCounts: Record<number, number> = {};
// // Initialize counts for all categories
// const materialIds = Object.keys(upgradeMaterials);
// log.info(`Found ${materialIds.length} material groups`);
// // Initialize all material counts to 0
// for (let i = 0; i < materialIds.length; i++) {
// const materialId = materialIds[i];
// materialCounts[materialId] = 0;
// }
// // Process each material group
// for (let i = 0; i < materialIds.length; i++) {
// const materialId = materialIds[i];
// log.info(`Processing material ID: ${materialId}`);
// // Get the material object and verify it exists
// const material = upgradeMaterials[materialId];
// if (!material) {
// log.info(`Material with ID ${materialId} is undefined`);
// continue;
// }
// // Get the material name
// const materialName = material.name || "Unknown Material";
// materialsString = materialsString + '\n' + materialName + ':\n';
// // Get items using our helper function to ensure it's not nil
// const items = getItemsFromMaterial(material);
// log.info(`Material ${materialName} has ${items.length} items`);
// if (items.length === 0) {
// log.info(`No items found for material ${materialName}`);
// materialsString = materialsString + ' No items found in this category\n';
// continue;
// }
// // Process each item in the material category
// let categoryTotal = 0;
// for (let j = 0; j < items.length; j++) {
// // Get the item and verify it exists
// const item = items[j];
// if (!item) {
// log.info(`Item at index ${j} is undefined for material ${materialName}`);
// continue;
// }
// // Get the item entry and name
// const itemEntry = item.entry;
// const itemName = item.name || "Unknown Item";
// log.info(`Checking item ${itemName} (${itemEntry})`);
// try {
// // Get the item count
// const count = player.GetItemCount(itemEntry, true); // true = include bank
// log.info(`Player has ${count} of item ${itemName}`);
// // Add to category total
// categoryTotal = categoryTotal + count;
// // Only show items the player has
// if (count > 0) {
// materialsString = materialsString + ' - ' + itemName + ': ' + count + '\n';
// }
// } catch (error) {
// log.info(`Error getting count for item ${itemName}: ${error}`);
// }
// }
// // Store the category total
// materialCounts[materialId] = categoryTotal;
// // Add total for this category
// materialsString = materialsString + ' Total ' + materialName + ': ' + categoryTotal + '\n';
// }
// log.info("Initialized material counts");
// // Send the material counts to the player
// player.SendBroadcastMessage(materialsString);
// // Log the material counts for debugging
// log.info('Showed material counts for player: ' + player.GetName());
// return true;
// }
// const UpgradeHandlers = aio.AddHandlers("UpgradeUI", {
// ShowMaterialCount
// });
/**
* Register the command event to listen for ".advanceme"
*/
RegisterPlayerEvent(PlayerEvents.PLAYER_EVENT_ON_COMMAND, (...args) => ShowUpgradeUI(...args));
/**
* Get all the material types from the database
* RegisterServerEvent(ServerEvents.ELUNA_EVENT_ON_LUA_STATE_OPEN, (...args) => GetMaterialsList(...args));
*/

View File

@@ -0,0 +1,356 @@
/**
* Custom Spells that have been implemented for Mythic Plus components
*/
import { GetFusedItemFromItemEntry, IsVeilstone, IsMythicFusionItem, IsRareFusionItem, GetFusedItemMaterialId } from "./mythic_items";
import { Logger } from "../../classes/logger";
const log = new Logger("mythic_custom_spells");
const MAX_STACK_SIZE = 200;
/**
* Spells for combining old materials
* 150000 - Ore Fusion
* 150001 - Cloth Fusion
* 150002 - Leather Fusion
* 150003 - Alchemy Fusion
* 150004 - Gem Fusion
* 150005 - Essence Fusion
* 150006 - Cold Fusion
* 150007 - Flame Fusion
* 150008 - Arcane Fusion
* 150009 - Dark Fusion
* 150010 - Earth Fusion
*/
// Spells for combining old materials
export const SPELL_ORE_FUSION = 150000;
export const SPELL_CLOTH_FUSION = 150001;
export const SPELL_LEATHER_FUSION = 150002;
export const SPELL_ALCHEMY_FUSION = 150003;
export const SPELL_GEM_FUSION = 150004;
export const SPELL_ESSENCE_FUSION = 150005;
export const SPELL_COLD_FUSION = 150006;
export const SPELL_FLAME_FUSION = 150007;
export const SPELL_ARCANE_FUSION = 150008;
export const SPELL_DARK_FUSION = 150009;
export const SPELL_EARTH_FUSION = 150010;
export const SPELL_ORE_FUSION_RANK_2 = 150011;
export const SPELL_CLOTH_FUSION_RANK_2 = 150012;
export const SPELL_LEATHER_FUSION_RANK_2 = 150013;
export const SPELL_ALCHEMY_FUSION_RANK_2 = 150014;
export const SPELL_GEM_FUSION_RANK_2 = 150015;
export const SPELL_ESSENCE_FUSION_RANK_2 = 150016;
export const SPELL_COLD_FUSION_RANK_2 = 150017;
export const SPELL_FLAME_FUSION_RANK_2 = 150018;
export const SPELL_ARCANE_FUSION_RANK_2 = 150019;
export const SPELL_DARK_FUSION_RANK_2 = 150020;
export const SPELL_EARTH_FUSION_RANK_2 = 150021;
export const FUSION_SPELLS_RANK_1 = [150000, 150001, 150002, 150003, 150004, 150005, 150006, 150007, 150008, 150009, 150010];
export const FUSION_SPELLS_RANK_2 = [150011, 150012, 150013, 150014, 150015, 150016, 150017, 150018, 150019, 150020, 150021];
/**
* Stat Advancement Spells
* 1. 80000001 - Titans' Strength Aura
* 2. 80000002 - Steel Forged Aura
* 3. 80000003 - Celestial Grace Aura
* 4. 80000004 - Forbidden Knowledge Aura
* 5. 80000005 - Spectral Reflexes Aura
* 6. 80000006 - Eldritch Barrier Aura
* 7. 80000007 - Hellfire Shielding Aura
* 8. 80000008 - Primal Endurance Aura
* 9. 80000009 - Lichbane Aura
* 10. 80000010 - Glacial Fortress Aura
*/
// Stat Advancement Spells
export enum StatAdvancementSpell {
TITANS_STRENGTH_AURA = 80000001,
STEEL_FORGED_AURA = 80000002,
CELESTIAL_GRACE_AURA = 80000003,
FORBIDDEN_KNOWLEDGE_AURA = 80000004,
SPECTRAL_REFLEXES_AURA = 80000005,
ELDRITCH_BARRIER_AURA = 80000006,
HELLFIRE_SHIELDING_AURA = 80000007,
PRIMAL_ENDURANCE_AURA = 80000008,
LICHBANE_AURA = 80000009,
GLACIAL_FORTRESS_AURA = 80000010,
}
export const STAT_ADVANCEMENT_SPELLS = {
TITANS_STRENGTH_AURA: StatAdvancementSpell.TITANS_STRENGTH_AURA,
STEEL_FORGED_AURA: StatAdvancementSpell.STEEL_FORGED_AURA,
CELESTIAL_GRACE_AURA: StatAdvancementSpell.CELESTIAL_GRACE_AURA,
FORBIDDEN_KNOWLEDGE_AURA: StatAdvancementSpell.FORBIDDEN_KNOWLEDGE_AURA,
SPECTRAL_REFLEXES_AURA: StatAdvancementSpell.SPECTRAL_REFLEXES_AURA,
ELDRITCH_BARRIER_AURA: StatAdvancementSpell.ELDRITCH_BARRIER_AURA,
HELLFIRE_SHIELDING_AURA: StatAdvancementSpell.HELLFIRE_SHIELDING_AURA,
PRIMAL_ENDURANCE_AURA: StatAdvancementSpell.PRIMAL_ENDURANCE_AURA,
LICHBANE_AURA: StatAdvancementSpell.LICHBANE_AURA,
GLACIAL_FORTRESS_AURA: StatAdvancementSpell.GLACIAL_FORTRESS_AURA,
};
const RARE_ITEM_REQUIREMENT = 20;
const MYTHIC_ITEM_REQUIREMENT = 5;
// Get the total number of items in the stack for the given item
function GetTotalStackCount(item: Item): number {
if (!item) return 0;
return item.GetCount ? item.GetCount() : 0;
}
const playerAngerCount: Record<number, number> = {};
const MythicMaterialFusion: player_event_on_spell_cast = (event: number, player: Player, spell: Spell, skipCheck: boolean) => {
if(!FUSION_SPELLS_RANK_1.includes(spell.GetEntry()) && !FUSION_SPELLS_RANK_2.includes(spell.GetEntry())) {
return false;
}
const rank = FUSION_SPELLS_RANK_1.includes(spell.GetEntry()) ? 1 : 2;
const target = spell.GetTarget() as Item;
log.info(`Item Details ${target.GetName()}`);
const hasItem = player.HasItem(target.GetEntry());
if(!hasItem) {
log.info("Did not have an item target");
return false;
}
const fusedItemEntry = GetFusedItemFromItemEntry(target.GetEntry());
log.info(`Fused Item Entry ${fusedItemEntry}`);
if(fusedItemEntry == 0) {
player.SendBroadcastMessage("You can not cast fuse this material");
player.PlayDirectSound(847);
return false;
}
if(IsVeilstone(target.GetEntry())) {
PrintInfo(`Veilstone detected`);
const angerCount = (playerAngerCount[player.GetGUIDLow()] || 0) + 1;
playerAngerCount[player.GetGUIDLow()] = angerCount;
if(angerCount === 1) {
player.SendBroadcastMessage("You are attempting to do something that is forbidden, do no not do it again.");
player.PlayDirectSound(847);
return false;
}
if(angerCount == 2) {
player.SendBroadcastMessage("You are about to anger the gods, stop yourself before you cause a rift in the fabric of the world!!!");
player.PlayDirectSound(847);
return false
}
if(angerCount >= 3) {
player.Teleport(0, -11139.2, -1742.44, -29.7367, 0);
player.SendNotification("YOU HAVE ATTEMPTED TO CORRUPT THE VEIL AND HAVE ANGERED THE GODS. YOU HAVE BEEN SENTENCED TO PURGATORY!");
player.RemoveItem(target, 1);
return false;
}
}
// make sure the correct spell is mapping to the correct material type
// @see CategoryMapToFused
const materialId = GetFusedItemMaterialId(target.GetEntry());
if(materialId == 0) {
PrintInfo(`No material id found for fused item entry: ${fusedItemEntry}`);
return false;
}
if(materialId === 1 && spell.GetEntry() !== SPELL_CLOTH_FUSION && spell.GetEntry() !== SPELL_CLOTH_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 2 && spell.GetEntry()!== SPELL_CLOTH_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 3 && spell.GetEntry() !== SPELL_ALCHEMY_FUSION && spell.GetEntry() !== SPELL_ALCHEMY_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 4 && spell.GetEntry() !== SPELL_ALCHEMY_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 5 && spell.GetEntry() !== SPELL_ORE_FUSION && spell.GetEntry() !== SPELL_ORE_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 6 && spell.GetEntry() !== SPELL_ORE_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 7 && spell.GetEntry() !== SPELL_LEATHER_FUSION && spell.GetEntry() !== SPELL_LEATHER_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 8 && spell.GetEntry() !== SPELL_LEATHER_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 9 && spell.GetEntry() !== SPELL_GEM_FUSION && spell.GetEntry() !== SPELL_GEM_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 10 && spell.GetEntry() !== SPELL_GEM_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 11 && spell.GetEntry() !== SPELL_ESSENCE_FUSION && spell.GetEntry() !== SPELL_ESSENCE_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 12 && spell.GetEntry() !== SPELL_ESSENCE_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 13 && spell.GetEntry() !== SPELL_COLD_FUSION && spell.GetEntry() !== SPELL_COLD_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 14 && spell.GetEntry() !== SPELL_COLD_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 15 && spell.GetEntry() !== SPELL_FLAME_FUSION && spell.GetEntry() !== SPELL_FLAME_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 16 && spell.GetEntry() !== SPELL_FLAME_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 17 && spell.GetEntry() !== SPELL_ARCANE_FUSION && spell.GetEntry() !== SPELL_ARCANE_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 18 && spell.GetEntry() !== SPELL_ARCANE_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 19 && spell.GetEntry() !== SPELL_DARK_FUSION && spell.GetEntry() !== SPELL_DARK_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 20 && spell.GetEntry() !== SPELL_DARK_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 21 && spell.GetEntry() !== SPELL_EARTH_FUSION && spell.GetEntry() !== SPELL_EARTH_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
if(materialId === 22 && spell.GetEntry() !== SPELL_EARTH_FUSION_RANK_2) {
player.PlayDirectSound(847);
return false;
}
PrintInfo(`materialId: ${materialId} and spell: ${spell.GetEntry()}`);
if(IsMythicFusionItem(fusedItemEntry)) {
if(target.GetCount() < MYTHIC_ITEM_REQUIREMENT) {
player.SendBroadcastMessage("You do not have enough materials to apply fusion.");
player.PlayDirectSound(847);
return false;
}
log.info(`Fusing ${target.GetName()}`);
const random = Math.floor(Math.random() * 3) + 1;
player.PlayDirectSound(10720);
//player.AddItem(fusedItemEntry, random);
target.SetCount(target.GetCount() - MYTHIC_ITEM_REQUIREMENT);
return true;
}
if(IsRareFusionItem(fusedItemEntry)) {
if(target.GetCount() < RARE_ITEM_REQUIREMENT) {
player.SendBroadcastMessage("You do not have enough materials to apply fusion");
player.PlayDistanceSound(847);
return false;
}
// If the target is a max stack then apply it to the entire stack for QoL reasons
let totalFused = 0;
let removeCount = 0;
if(target.GetCount() === MAX_STACK_SIZE) {
for(let i = 0; i < 10; i++) {
let random = Math.floor(Math.random() * 3) + 1;
totalFused += random;
}
removeCount = MAX_STACK_SIZE;
player.RemoveItem(target, MAX_STACK_SIZE);
} else {
totalFused = Math.floor(Math.random() * 3) + 1;
removeCount = RARE_ITEM_REQUIREMENT;
target.SetCount(target.GetCount() - RARE_ITEM_REQUIREMENT);
player.SaveToDB();
player.RemoveItem(target, 1);
}
player.PlayDirectSound(12334);
player.AddItem(fusedItemEntry, totalFused);
}
return true;
};
// // Get the item
// const stackCount = GetTotalStackCount(item);
// // Determine if this item can be fused
// // const fusedItemEntry = GetFusedItemFromItemEntry(itemEntry);
// if (!fusedItemEntry || fusedItemEntry === 0) {
// player.SendBroadcastMessage("This item cannot be fused.");
// return false;
// }
// // Calculate number of batches (every 20 items)
// const batchSize = 20;
// const numBatches = Math.floor(stackCount / batchSize);
// if (numBatches === 0) {
// player.SendBroadcastMessage(`You need at least ${batchSize} items to fuse.`);
// return false;
// }
// // Remove items from player's inventory
// item.Remove(batchSize * numBatches);
// // For each batch, roll 1-3 and give that many fused items
// let totalFused = 0;
// for (let i = 0; i < numBatches; i++) {
// const amount = 1 + Math.floor(Math.random() * 3); // 1-3
// player.AddItem(fusedItemEntry, amount);
// totalFused += amount;
// }
// player.SendBroadcastMessage(
// `You fused ${numBatches * batchSize} items into ${totalFused} rare materials!`
// );
// Register
RegisterPlayerEvent(PlayerEvents.PLAYER_EVENT_ON_SPELL_CAST, (...args) => MythicMaterialFusion(...args));

View File

@@ -0,0 +1,172 @@
// custom items for advancement
export const ANCIENT_DICE = 911000;
export const ONYX_SPIKE_RELIC = 911001;
export const VEILSTONE = 911002;
export const FUSED_RARE_ORE = 911003;
export const FUSED_MYTHIC_ORE = 911004;
export const FUSED_RARE_CLOTH = 911005;
export const FUSED_MYTHIC_CLOTH = 911006;
export const FUSED_RARE_LEATHER = 911007;
export const FUSED_MYTHIC_LEATHER = 911008;
export const FUSED_RARE_ALCHEMY = 911009;
export const FUSED_MYTHIC_ALCHEMY = 911010;
export const FUSED_RARE_GEM = 911011;
export const FUSED_MYTHIC_GEM = 911012;
export const FUSED_RARE_ESSENCE = 911013;
export const FUSED_MYTHIC_ESSENCE = 911014;
export const FUSED_RARE_ICE_STONE = 911015;
export const FUSED_MYTHIC_ICE_STONE = 911016;
export const FUSED_RARE_INFERNAL_STONE = 911017;
export const FUSED_MYTHIC_INFERNAL_STONE = 911018;
export const FUSED_RARE_ARCANE_CRYSTAL = 911019;
export const FUSED_MYTHIC_ARCANE_CRYSTAL = 911020;
export const FUSED_RARE_DARK_CRYSTAL = 911021;
export const FUSED_MYTHIC_DARK_CRYSTAL = 911022;
export const FUSED_RARE_EARTH_STONE = 911023;
export const FUSED_MYTHIC_EARTH_STONE = 911024;
export const MYTHIC_MATERIALS = {
ANCIENT_DICE,
ONYX_SPIKE_RELIC,
VEILSTONE,
FUSED_RARE_ORE,
FUSED_MYTHIC_ORE,
FUSED_RARE_CLOTH,
FUSED_MYTHIC_CLOTH,
FUSED_RARE_LEATHER,
FUSED_MYTHIC_LEATHER,
FUSED_RARE_ALCHEMY,
FUSED_MYTHIC_ALCHEMY,
FUSED_RARE_GEM,
FUSED_MYTHIC_GEM,
FUSED_RARE_ESSENCE,
FUSED_MYTHIC_ESSENCE,
FUSED_RARE_ICE_STONE,
FUSED_MYTHIC_ICE_STONE,
FUSED_RARE_INFERNAL_STONE,
FUSED_MYTHIC_INFERNAL_STONE,
FUSED_RARE_ARCANE_CRYSTAL,
FUSED_MYTHIC_ARCANE_CRYSTAL,
FUSED_RARE_DARK_CRYSTAL,
FUSED_MYTHIC_DARK_CRYSTAL,
FUSED_RARE_EARTH_STONE,
FUSED_MYTHIC_EARTH_STONE
};
export const CategoryMapToFused: Record<number, number> = {
1: FUSED_RARE_CLOTH,
2: FUSED_MYTHIC_CLOTH,
3: FUSED_RARE_ALCHEMY,
4: FUSED_MYTHIC_ALCHEMY,
5: FUSED_RARE_ORE,
6: FUSED_MYTHIC_ORE,
7: FUSED_RARE_LEATHER,
8: FUSED_MYTHIC_LEATHER,
9: FUSED_RARE_GEM,
10: FUSED_MYTHIC_GEM,
11: FUSED_RARE_ESSENCE,
12: FUSED_MYTHIC_ESSENCE,
13: FUSED_RARE_ICE_STONE,
14: FUSED_MYTHIC_ICE_STONE,
15: FUSED_RARE_INFERNAL_STONE,
16: FUSED_MYTHIC_INFERNAL_STONE,
17: FUSED_RARE_ARCANE_CRYSTAL,
18: FUSED_MYTHIC_ARCANE_CRYSTAL,
19: FUSED_RARE_DARK_CRYSTAL,
20: FUSED_MYTHIC_DARK_CRYSTAL,
21: FUSED_RARE_EARTH_STONE,
22: FUSED_MYTHIC_EARTH_STONE
};
export const RARE_FUSED_ITEMS: number[] = [
FUSED_RARE_ORE,
FUSED_RARE_CLOTH,
FUSED_RARE_LEATHER,
FUSED_RARE_ALCHEMY,
FUSED_RARE_GEM,
FUSED_RARE_ESSENCE,
FUSED_RARE_ICE_STONE,
FUSED_RARE_INFERNAL_STONE,
FUSED_RARE_ARCANE_CRYSTAL,
FUSED_RARE_DARK_CRYSTAL,
FUSED_RARE_EARTH_STONE
];
export const MYTHIC_FUSED_ITEMS: number[] = [
FUSED_MYTHIC_ORE,
FUSED_MYTHIC_CLOTH,
FUSED_MYTHIC_LEATHER,
FUSED_MYTHIC_ALCHEMY,
FUSED_MYTHIC_GEM,
FUSED_MYTHIC_ESSENCE,
FUSED_MYTHIC_ICE_STONE,
FUSED_MYTHIC_INFERNAL_STONE,
FUSED_MYTHIC_ARCANE_CRYSTAL,
FUSED_MYTHIC_DARK_CRYSTAL,
FUSED_MYTHIC_EARTH_STONE
];
// Populated after load with items mapped to fused counterparts.
// key is the material id, value is the item entries that can be used in fusion
export const FusedToItemMap: Record<number, number[]> = {};
// create a lookup of item ids from the database to the fused items as the items in each category will
// produce that fused item when the fusion spell is cast on it.
const CreateItemToFusedMap: eluna_event_on_lua_state_open = (event: number) => {
const query = WorldDBQuery(`select materialId, entry, name from mp_material_types`);
if (query) {
do {
const materialId = query.GetUInt32(0);
const entry = query.GetUInt32(1);
// Initialize the material group if it doesn't exist
if (!FusedToItemMap[materialId]) {
FusedToItemMap[materialId] = [entry];
} else {
FusedToItemMap[materialId].push(entry);
}
} while (query.NextRow());
}
PrintInfo(`Loaded ${Object.keys(FusedToItemMap).length} material groups and mapped to fused items`);
}
RegisterServerEvent(ServerEvents.ELUNA_EVENT_ON_LUA_STATE_OPEN, (...args) => CreateItemToFusedMap(...args));
export function GetFusedItemFromItemEntry(itemEntry: number): number {
for (const [materialId, entries] of Object.entries(FusedToItemMap)) {
if (entries.includes(itemEntry)) {
return CategoryMapToFused[Number(materialId)];
}
}
return 0;
}
// Get the category of the fused item
export function GetFusedItemMaterialId(itemEntry: number): number {
for (const [materialId, entries] of Object.entries(FusedToItemMap)) {
if (entries.includes(itemEntry)) {
return Number(materialId);
}
}
return 0;
}
export function IsRareFusionItem(fusedItemEntry: number): boolean {
PrintInfo(`looking up ${fusedItemEntry}`);
return (RARE_FUSED_ITEMS.includes(fusedItemEntry)) ? true : false;
}
export function IsMythicFusionItem(fusedItemEntry: number): boolean {
return (MYTHIC_FUSED_ITEMS.includes(fusedItemEntry)) ? true : false;
}
export function IsVeilstone(itemEntry: number): boolean {
if(itemEntry == VEILSTONE) {
return true;
}
return false;
}

View File

@@ -0,0 +1,444 @@
/** @ts-expect-error */
let aio: AIO = {};
import { MapNames, MapIds, BossIDs } from "../../classes/mapzones";
import * as Spells from "./mythic_custom_spells";
/**
* This is the file for managing new NPC interactions
* - Mick Ashwild - 9500561 teaches leather/fire fusions
* - Thorin Firehand - 9500562 teaches ore/cold fusions
* - Elowyn Threadbinder - 9500563 teaches cloth/arcane fusions
* - Shivey - 9500564 teaches alchemy/nature fusions
* - ??? - 9500565 teaches gem/essence fusions
* - ??? - Old Witch teaches shadow fusion
*
*/
export enum NPCType {
MICK_ASHWILD = 'mick',
THORIN_FIREHAND = 'thorin',
ELOWYN_THREADBINDER = 'elowyn',
SHIVEY = 'shivey',
OLD_WITCH = 'old_witch'
}
export const NPCIds: Record<NPCType, number> = {
[NPCType.MICK_ASHWILD]: 9500561,
[NPCType.THORIN_FIREHAND]: 9500562,
[NPCType.ELOWYN_THREADBINDER]: 9500563,
[NPCType.SHIVEY]: 9500564,
[NPCType.OLD_WITCH]: 9500565
};
export enum GobjectType {
PORTAL = 'portal',
}
export const GobjectIds: Record<GobjectType, number> = {
[GobjectType.PORTAL]: 181508
}
const AUDIO_BASE_PATH = "Interface\\Modules\\MythicPlus\\Audio\\";
export const AudioPaths: Record<NPCType, string> = {
[NPCType.MICK_ASHWILD]: AUDIO_BASE_PATH + "Mick\\mick-",
[NPCType.THORIN_FIREHAND]: AUDIO_BASE_PATH + "Thorin\\thorin-",
[NPCType.ELOWYN_THREADBINDER]: AUDIO_BASE_PATH + "Elowyn\\elowyn-",
[NPCType.SHIVEY]: AUDIO_BASE_PATH + "Shivey\\shivey-",
[NPCType.OLD_WITCH]: AUDIO_BASE_PATH + "OldWitch\\old-witch-"
};
function getAudioFile(npcName: NPCType, file: string): string {
return AudioPaths[npcName] + file + ".mp3";
}
function isInMythicPlus(playerId: number, instanceId: number): boolean {
const result = CharDBQuery("select guid, instanceId, difficulty from mp_player_instance_data where guid = " + playerId + " and instanceId = " + instanceId);
if(result && result.GetUInt32(2) >= 3) {
return true;
}
return false;
}
/**
* the player will meet different characters at different times so the hello will check to see whch
* audio file and gossip menu needs to be shown. The list of locations an npc will show up is below
*
* Mick
* Rare Leather Fusion : Wailing Caverns Verdan the everliving
* Mythic Leather Fusion : Sartharion Obsidian Sanctum
* Rare Fire Fusion : Hellfire Ramparts Nazan + Vazruden
* Mythic Fire Fusion : Onyxia Lair Flamegor
* Casual Spawns : World ends Tavern Shatrath
*
* Thorin
* Rare Ore Fusion : BRD Magmus
* Mythic Ore Fusion : Ragnaros
* Casual Spawns : The Great Forge
*
*/
// Track NPC dialog animation state
interface ActiveNpcState {
time: number;
intro: boolean;
audioActive: boolean;
players?: string[]; // list of players nearby when the NPC spawns
lastEmote?: number;
outro?: string[]; // list of players that have heard the outro
}
interface PlayedAudioState {
playerName: string;
audioFile: string;
played: boolean;
}
// Track the ai update loop for each instance of a NPC which
let npcState: Record<string, ActiveNpcState> = {};
// Create emote maps for npcs while they are talking keys are seconds into audio and values are emote types
const emotesMap: Record<string, Record<number, EmoteType>> = {};
// Track the audio that has been played for each for this instance id.
// key: InstanceId value: playerId: audioFile
let playedAudio: Record<string, Record<string, string[]>> = {};
// Mick + WC
emotesMap[`${NPCIds[NPCType.MICK_ASHWILD]}-${MapIds[MapNames.WAILING_CAVERNS]}`] = {
1: EmoteType.STATE_TALK,
6: EmoteType.STATE_EXCLAIM,
8: EmoteType.STATE_TALK,
10: EmoteType.ONESHOT_NONE,
12: EmoteType.STATE_POINT,
14: EmoteType.ONESHOT_NONE,
15: EmoteType.STATE_TALK,
26: EmoteType.STATE_EXCLAIM,
28: EmoteType.STATE_TALK,
35: EmoteType.ONESHOT_EXCLAMATION,
37: EmoteType.STATE_TALK,
41: EmoteType.STATE_POINT,
43: EmoteType.ONESHOT_NONE
};
// Spell to spell MapId
const spellToMap: Record<number, number> = {
[MapIds[MapNames.WAILING_CAVERNS]]: Spells.SPELL_LEATHER_FUSION
};
// Does this player already know what the special NPC is offering.
function PlayerHasSpell(player: Player): boolean {
const mapId = player.GetMapId();
if(spellToMap[mapId]) {
return player.HasSpell(spellToMap[mapId]);
}
return false;
}
// This will prevent audio from being played more than once for the same player using a global map
function PlayAudioOnce(player: Player, audioFile: string): void {
const instanceId = player.GetInstanceId();
if(!playedAudio[instanceId]) {
playedAudio[instanceId] = {};
}
// If nothing has been played to the player create the audio entry
if(!playedAudio[instanceId][player.GetName()]) {
playedAudio[instanceId][player.GetName()] = [];
playedAudio[instanceId][player.GetName()].push(audioFile);
PrintDebug(`Playing audio ${audioFile} for player ${player.GetName()} in instance ${instanceId}`);
aio.Handle(player, 'AIOAudioPlayer', 'PlaySingleSound', audioFile);
return;
}
if(playedAudio[instanceId][player.GetName()].includes(audioFile)) {
return;
}
playedAudio[instanceId][player.GetName()].push(audioFile);
aio.Handle(player, 'AIOAudioPlayer', 'PlaySingleSound', audioFile);
}
// Get the instance state of the NPC for this group and instance.
function GetNPCState(creature: Creature): ActiveNpcState {
const instanceId = creature.GetInstanceId();
const creatureGuid = creature.GetGUIDLow();
let state = npcState[`${instanceId}-${creatureGuid}`];
if(!state) {
state = {
time: 0,
intro: false,
outro: [],
audioActive: false,
players: []
};
npcState[`${instanceId}-${creatureGuid}`] = state;
}
return state;
}
/**
* Handle AI Dialogs for Mick and others.
*/
const handleMickAIUpdates: creature_event_on_aiupdate = (event: number, creature: Creature, diff: number): boolean => {
const creatureGuid = creature.GetGUIDLow();
const instanceId = creature.GetInstanceId();
const state = npcState[`${instanceId}-${creatureGuid}`];
const previousTime = state.time || 0;
state.time = (previousTime + diff);
const seconds = Math.ceil(state.time / 1000);
const npcEmoteMap = emotesMap[`${creature.GetEntry()}-${creature.GetMapId()}`];
if(npcEmoteMap && npcEmoteMap[seconds] && npcEmoteMap[seconds] !== state.lastEmote) {
PrintDebug(`Mick player state: ${state.players.length}`);
creature.EmoteState(npcEmoteMap[seconds]);
state.lastEmote = npcEmoteMap[seconds];
}
// Handle the AI Updates for Wailing Caverns
if(MapIds[MapNames.WAILING_CAVERNS] === creature.GetMapId()) {
if(seconds >= 43) {
ClearUniqueCreatureEvents(creature.GetGUID(), instanceId);
state.audioActive = false;
state.intro = true;
for(let i=0; i < state.players.length; i++) {
const playerId = state.players[i];
if(!playerId) {
PrintError(`${creature.GetName()} gossip menu failed no valid player in state`);
continue;
}
const player = GetPlayerByName(playerId);
if(player) {
npcHello(player, creature, player.GetMapId());
}
}
}
}
return true;
}
/**
* the player will meet different characters at different times so the hello will check to see whch
* audio file and gossip menu needs to be shown. The list of locations an npc will show up is below
*
* Mick
* Rare Leather Fusion : Wailing Caverns Verdan the everliving
* Mythic Leather Fusion : Sartharion Obsidian Sanctum
* Rare Fire Fusion : Hellfire Ramparts Nazan + Vazruden
* Mythic Fire Fusion : Onyxia Lair Flamegor
* Casual Spawns : World ends Tavern Shatrath
*
* Thorin
* Rare Ore Fusion : BRD Magmus
* Mythic Ore Fusion : Ragnaros
* Casual Spawns : The Great Forge
*
*/
// When verdan dies Mike should show phase in from a portal
const verdanDied: creature_event_on_died = (event: number, creature: Creature, killer: Creature): boolean => {
// Spawn the portal and Mick
creature.SummonGameObject(GobjectIds[GobjectType.PORTAL], -79.273, 4.999, -30.962, 2.20);
const mick = creature.SpawnCreature(NPCIds[NPCType.MICK_ASHWILD], -79.273, 4.999, -30.962, 2.20, TempSummonType.TEMPSUMMON_TIMED_DESPAWN, 1200000);
mick.SetWalk(true);
mick.MoveTo(1, -83.252, 19.723, -31.076);
return false; // return false to continue normal action
};
function npcHello(player: Player, creature: Creature, mapId: number, known?: boolean): void {
player.GossipClearMenu();
PrintDebug("sending menu to player: " + player.GetName());
if(mapId === MapIds[MapNames.WAILING_CAVERNS]) {
if(known) {
player.GossipMenuAddItem(0, "Got nuthin' for ya friend.", 1, 999);
player.GossipSendMenu(1, creature, 90000);
return;
}
player.GossipMenuAddItem(3, "Learn Leather Fusion (requires grandmaster)",1, Spells.SPELL_LEATHER_FUSION);
player.GossipMenuAddItem(0, "Best to come another time", 1, 999);
}
player.GossipSendMenu(1, creature, 90000);
}
const mickSelect: gossip_event_on_select = (event: number, player: Player, creature: Creature, sender: number, selection: number): boolean => {
const state = GetNPCState(creature);
PrintDebug(`Player ${player.GetName()} selected ${selection}`);
// 999 is a signal nothing to do
if(selection === 999) {
// play the outro audio for the player if they are done.
if(!state.outro[player.GetName()]) {
aio.Handle(player, 'AIOAudioPlayer', 'PlaySingleSound', getAudioFile(NPCType.MICK_ASHWILD, "rare-goodbye"));
state.outro[player.GetName()] = true;
}
player.GossipClearMenu();
player.GossipComplete();
return true;
}
// Check the range of the spell we are trying to learn to make sure there is not a problem.
if(selection < Spells.SPELL_ORE_FUSION || selection > Spells.SPELL_EARTH_FUSION_RANK_2) {
PrintError(`The selection ${selection} is not in the range of learnable spells check coding!!`);
return true;
}
PrintDebug(`Learning spell ${selection} for player ${player.GetName()}`);
PrintDebug(`Player skill ${player.GetSkillValue(165)}`);
switch(player.GetMapId()) {
case MapIds[MapNames.WAILING_CAVERNS]:
if(player.GetSkillValue(165) == 450) { // if they are a grandmaster leather worker.
player.LearnSpell(selection);
PrintDebug(`Learning spell ${selection} for player ${player.GetName()}`);
PlayAudioOnce(player, getAudioFile(NPCType.MICK_ASHWILD, "teach-rare-yes"));
} else {
PrintDebug(`FAILING ${selection} for player ${player.GetName()}`);
PlayAudioOnce(player, getAudioFile(NPCType.MICK_ASHWILD, "teach-rare-no"));
}
break;
default:
break;
}
player.GossipComplete();
return true;
}
// Micks events
const mickHello: gossip_event_on_hello = (event: number, player: Player, creature: Creature): boolean => {
const zoneId = player.GetMapId();
const instanceId = player.GetInstanceId();
// Get the global creature state for special NPCs
const myState = GetNPCState(creature);
// if(isInMythicPlus(player.GetGUID(), instanceId)) {
if (MapIds[MapNames.WAILING_CAVERNS] === zoneId) {
creature.SetFacingToObject(player);
// Do play outro dialog if they already know the spell.
if(PlayerHasSpell(player)) {
npcHello(player, creature, zoneId, true);
if(!myState.outro[player.GetName()]) {
PlayAudioOnce(player, getAudioFile(NPCType.MICK_ASHWILD, "rare-goodbye"));
myState.outro[player.GetName()] = true;
}
return true;
}
// Audio should play for all players in range when the NPC spawns..
// however the menu will show up only to the player that clicked hello,
// or other players after the audio has played.
if (!myState.audioActive) {
const nearPlayers = creature.GetPlayersInRange(35);
for (let i = 0; i < nearPlayers.length; i++) {
myState.players.push(nearPlayers[i].GetName());
PlayAudioOnce(player, getAudioFile(NPCType.MICK_ASHWILD, "rare-hello"));
}
creature.EmoteState(EmoteType.ONESHOT_NONE);
myState.audioActive = true;
ClearUniqueCreatureEvents(creature.GetGUID(), instanceId, CreatureEvents.CREATURE_EVENT_ON_AIUPDATE);
RegisterUniqueCreatureEvent(creature.GetGUID(), instanceId, CreatureEvents.CREATURE_EVENT_ON_AIUPDATE, (...args) => handleMickAIUpdates(...args));
} else {
myState.players.push(player.GetName()); // player wants to interact with the NPC
if(myState.players.length === 1 && myState.intro) {
npcHello(player, creature, player.GetMapId());
}
}
}
return true;
};
/**** Boss Kill Handlers ****/
// Wailing Caverns - Verdan the Everliving is killed
RegisterCreatureEvent(5775, CreatureEvents.CREATURE_EVENT_ON_DIED, (...args) => verdanDied(...args));
/**** NPC Gossip Events Handlers ****/
// Mick Ashwild
RegisterCreatureGossipEvent(NPCIds[NPCType.MICK_ASHWILD], GossipEvents.GOSSIP_EVENT_ON_HELLO, (...args) => mickHello(...args));
RegisterCreatureGossipEvent(NPCIds[NPCType.MICK_ASHWILD], GossipEvents.GOSSIP_EVENT_ON_SELECT, (...args) => mickSelect(...args));
/**** NPC Events Handlers ****/
// Registers the creature state to our instance state map and sets default state values.
function commonCreatureRegister(creature: Creature) {
ClearUniqueCreatureEvents(creature.GetGUID(), creature.GetInstanceId());
npcState[`${creature.GetInstanceId()}-${creature.GetGUIDLow()}`] = {
time: 0,
intro: false,
outro: [],
audioActive: false,
players: []
};
}
// Micks Events
RegisterCreatureEvent(NPCIds[NPCType.MICK_ASHWILD], CreatureEvents.CREATURE_EVENT_ON_SPAWN, (event: number, creature: Creature): boolean => {
commonCreatureRegister(creature);
return false;
});
RegisterCreatureEvent(NPCIds[NPCType.MICK_ASHWILD], CreatureEvents.CREATURE_EVENT_ON_REMOVE, (event: number, creature: Creature): boolean => {
commonCreatureRegister(creature);
return false;
});
/**** Server events for state management around instances ****/
const cleanupAudio: map_event_on_destroy = (event: number, map: EMap) => {
playedAudio[map.GetInstanceId()] = {};
};
// Register Map Event on Destroy
RegisterServerEvent(ServerEvents.MAP_EVENT_ON_DESTROY, (...args) => cleanupAudio(...args));
const createPlayedAudio: map_event_on_destroy = (event: number, map: EMap) => {
playedAudio[map.GetInstanceId()] = {};
};
// Register Map Event on Destroy
RegisterServerEvent(ServerEvents.MAP_EVENT_ON_DESTROY, (...args) => createPlayedAudio(...args));
const resetPlayedAudio: eluna_event_on_lua_state_open = (event: number) => {
PrintDebug("resetting played audio");
playedAudio = {};
};
const resetPlayedAudioClose: eluna_event_on_lua_state_close = (event: number) => {
PrintDebug("resetting played audio");
playedAudio = {};
}
// Register Server Event on Lua State Open
RegisterServerEvent(ServerEvents.ELUNA_EVENT_ON_LUA_STATE_OPEN, (...args) => resetPlayedAudio(...args));
RegisterServerEvent(ServerEvents.ELUNA_EVENT_ON_LUA_STATE_CLOSE, (...args) => resetPlayedAudioClose(...args));

View File

@@ -1,4 +1,5 @@
// Purpose: Logger class to log messages to the console.
export class Logger {
public logname: string;

536
modules/classes/mapzones.ts Normal file
View File

@@ -0,0 +1,536 @@
import { Logger } from "./logger";
const logger = new Logger("MapZones");
export enum MapNames {
// Classic WoW dungeons
RAGEFIRE_CHASM = 'ragefire_chasm',
WAILING_CAVERNS = 'wailing_caverns',
DEADMINES = 'deadmines',
SHADOWFANG_KEEP = 'shadowfang_keep',
STOCKADE = 'stockade',
BLACKFATHOM_DEEPS = 'blackfathom_deeps',
GNOMEREGAN = 'gnomeregan',
RAZORFEN_KRAUL = 'razorfen_kraul',
SCARLET_MONASTERY = 'scarlet_monastery',
SCHOLOMANCE = 'scholomance',
SUNKEN_TEMPLE = 'sunken_temple',
RAZORFEN_DOWNS = 'razorfen_downs',
ULDAMAN = 'uldaman',
STRATHOLME = 'stratholme',
BLACKROCK_SPIRE_LOWER = 'blackrock_spire_lower',
BLACKROCK_SPIRE_UPPER = 'blackrock_spire_upper',
DIRE_MAUL = 'dire_maul',
ZUL_FARRAK = 'zul_farrak',
MARAUDON = 'maraudon',
TEMPLE_ATAL_HAKKAR = 'temple_atal_hakkar',
BLACKROCK_DEPTHS = 'blackrock_depths',
// Classic WoW raids
MOLTEN_CORE = 'molten_core',
BLACKWING_LAIR = 'blackwing_lair',
RUINS_OF_AHNQIRAJ = 'ruins_of_ahnqiraj',
TEMPLE_OF_AHNQIRAJ = 'temple_of_ahnqiraj',
ZUL_GURUB = 'zul_gurub',
ONYXIAS_LAIR = 'onyxias_lair',
EMERALD_DREAM = 'emerald_dream',
// The Burning Crusade dungeons
SHATTERED_HALLS = 'shattered_halls',
BLOOD_FURNACE = 'blood_furnace',
HELLFIRE_RAMPARTS = 'hellfire_ramparts',
STEAMVAULTS = 'steamvaults',
UNDERBOG = 'underbog',
SLAVE_PENS = 'slave_pens',
MANA_TOMBS = 'mana_tombs',
AUCHENAI_CRYPTS = 'auchenai_crypts',
SETHEKK_HALLS = 'sethekk_halls',
SHADOW_LABYRINTH = 'shadow_labyrinth',
OLD_HILLSBRAD = 'old_hillsbrad',
BOTANICA = 'botanica',
MECHANAR = 'mechanar',
ARCATRAZ = 'arcatraz',
MAGISTERS_TERRACE = 'magisters_terrace',
BLACK_MORASS = 'black_morass',
// The Burning Crusade raids
KARAZHAN = 'karazhan',
GRUULS_LAIR = 'gruuls_lair',
MAGTHERIDONS_LAIR = 'magtheridons_lair',
SERPENTSHRINE_CAVERN = 'serpentshrine_cavern',
TEMPEST_KEEP = 'tempest_keep',
MOUNT_HYJAL = 'mount_hyjal',
BLACK_TEMPLE = 'black_temple',
SUNWELL_PLATEAU = 'sunwell_plateau',
ZUL_AMAN = 'zul_aman',
// Wrath of the Lich King dungeons
UTGARDE_KEEP = 'utgarde_keep',
UTGARDE_PINNACLE = 'utgarde_pinnacle',
AHNKAHET = 'ahnkahet',
NEXUS = 'nexus',
CULLING_STRATHOLME = 'culling_stratholme',
DRAK_THARON = 'drak_tharon',
AZJOL_NERUB = 'azjol_nerub',
VIOLET_HOLD = 'violet_hold',
GUNDRAK = 'gundrak',
HALLS_OF_STONE = 'halls_of_stone',
HALLS_OF_LIGHTNING = 'halls_of_lightning',
OCULUS = 'oculus',
TRIAL_OF_CHAMPION = 'trial_of_champion',
FORGE_OF_SOULS = 'forge_of_souls',
PIT_OF_SARON = 'pit_of_saron',
HALLS_OF_REFLECTION = 'halls_of_reflection',
// Wrath of the Lich King raids
NAXXRAMAS = 'naxxramas',
OBSIDIAN_SANCTUM = 'obsidian_sanctum',
EYE_OF_ETERNITY = 'eye_of_eternity',
ULDUAR = 'ulduar',
TRIAL_OF_CRUSADER = 'trial_of_crusader',
VAULT_OF_ARCHAVON = 'vault_of_archavon',
ICECROWN_CITADEL = 'icecrown_citadel',
RUBY_SANCTUM = 'ruby_sanctum'
}
export const MapIds: Record<MapNames, number> = {
// Classic WoW dungeons
[MapNames.RAGEFIRE_CHASM]: 389,
[MapNames.WAILING_CAVERNS]: 43,
[MapNames.DEADMINES]: 36,
[MapNames.SHADOWFANG_KEEP]: 33,
[MapNames.STOCKADE]: 34,
[MapNames.BLACKFATHOM_DEEPS]: 48,
[MapNames.GNOMEREGAN]: 90,
[MapNames.RAZORFEN_KRAUL]: 47,
[MapNames.SCARLET_MONASTERY]: 189,
[MapNames.SCHOLOMANCE]: 289,
[MapNames.SUNKEN_TEMPLE]: 109,
[MapNames.RAZORFEN_DOWNS]: 129,
[MapNames.ULDAMAN]: 70,
[MapNames.STRATHOLME]: 329,
[MapNames.BLACKROCK_SPIRE_LOWER]: 229,
[MapNames.BLACKROCK_SPIRE_UPPER]: 230,
[MapNames.DIRE_MAUL]: 429,
[MapNames.ZUL_FARRAK]: 209,
[MapNames.MARAUDON]: 349,
[MapNames.TEMPLE_ATAL_HAKKAR]: 269,
[MapNames.BLACKROCK_DEPTHS]: 230,
// Classic WoW raids
[MapNames.MOLTEN_CORE]: 409,
[MapNames.BLACKWING_LAIR]: 469,
[MapNames.RUINS_OF_AHNQIRAJ]: 509,
[MapNames.TEMPLE_OF_AHNQIRAJ]: 531,
[MapNames.ZUL_GURUB]: 309,
[MapNames.ONYXIAS_LAIR]: 249,
[MapNames.EMERALD_DREAM]: 169,
// The Burning Crusade dungeons
[MapNames.SHATTERED_HALLS]: 540,
[MapNames.BLOOD_FURNACE]: 542,
[MapNames.HELLFIRE_RAMPARTS]: 543,
[MapNames.STEAMVAULTS]: 545,
[MapNames.UNDERBOG]: 546,
[MapNames.SLAVE_PENS]: 547,
[MapNames.MANA_TOMBS]: 557,
[MapNames.AUCHENAI_CRYPTS]: 558,
[MapNames.SETHEKK_HALLS]: 556,
[MapNames.SHADOW_LABYRINTH]: 555,
[MapNames.OLD_HILLSBRAD]: 560,
[MapNames.BOTANICA]: 553,
[MapNames.MECHANAR]: 554,
[MapNames.ARCATRAZ]: 552,
[MapNames.MAGISTERS_TERRACE]: 585,
[MapNames.BLACK_MORASS]: 269,
// The Burning Crusade raids
[MapNames.KARAZHAN]: 532,
[MapNames.GRUULS_LAIR]: 565,
[MapNames.MAGTHERIDONS_LAIR]: 544,
[MapNames.SERPENTSHRINE_CAVERN]: 548,
[MapNames.TEMPEST_KEEP]: 550,
[MapNames.MOUNT_HYJAL]: 534,
[MapNames.BLACK_TEMPLE]: 564,
[MapNames.SUNWELL_PLATEAU]: 580,
[MapNames.ZUL_AMAN]: 568,
// Wrath of the Lich King dungeons
[MapNames.UTGARDE_KEEP]: 574,
[MapNames.UTGARDE_PINNACLE]: 575,
[MapNames.AHNKAHET]: 619,
[MapNames.NEXUS]: 576,
[MapNames.CULLING_STRATHOLME]: 595,
[MapNames.DRAK_THARON]: 600,
[MapNames.AZJOL_NERUB]: 601,
[MapNames.VIOLET_HOLD]: 608,
[MapNames.GUNDRAK]: 604,
[MapNames.HALLS_OF_STONE]: 599,
[MapNames.HALLS_OF_LIGHTNING]: 602,
[MapNames.OCULUS]: 578,
[MapNames.TRIAL_OF_CHAMPION]: 650,
[MapNames.FORGE_OF_SOULS]: 632,
[MapNames.PIT_OF_SARON]: 658,
[MapNames.HALLS_OF_REFLECTION]: 668,
// Wrath of the Lich King raids
[MapNames.NAXXRAMAS]: 533,
[MapNames.OBSIDIAN_SANCTUM]: 615,
[MapNames.EYE_OF_ETERNITY]: 616,
[MapNames.ULDUAR]: 603,
[MapNames.TRIAL_OF_CRUSADER]: 649,
[MapNames.VAULT_OF_ARCHAVON]: 624,
[MapNames.ICECROWN_CITADEL]: 631,
[MapNames.RUBY_SANCTUM]: 724
};
export const DungeonLevels: Record<MapNames, number> = {
// Classic WoW dungeons
[MapNames.RAGEFIRE_CHASM]: 18,
[MapNames.WAILING_CAVERNS]: 25,
[MapNames.DEADMINES]: 23,
[MapNames.SHADOWFANG_KEEP]: 30,
[MapNames.STOCKADE]: 30,
[MapNames.BLACKFATHOM_DEEPS]: 32,
[MapNames.GNOMEREGAN]: 38,
[MapNames.RAZORFEN_KRAUL]: 40,
[MapNames.SCARLET_MONASTERY]: 45,
[MapNames.SCHOLOMANCE]: 60,
[MapNames.SUNKEN_TEMPLE]: 60,
[MapNames.RAZORFEN_DOWNS]: 33,
[MapNames.ULDAMAN]: 40,
[MapNames.STRATHOLME]: 60,
[MapNames.BLACKROCK_SPIRE_LOWER]: 60,
[MapNames.BLACKROCK_SPIRE_UPPER]: 60,
[MapNames.DIRE_MAUL]: 60,
[MapNames.ZUL_FARRAK]: 50,
[MapNames.MARAUDON]: 55,
[MapNames.TEMPLE_ATAL_HAKKAR]: 57,
[MapNames.BLACKROCK_DEPTHS]: 60,
// Classic WoW raids
[MapNames.MOLTEN_CORE]: 60,
[MapNames.BLACKWING_LAIR]: 60,
[MapNames.RUINS_OF_AHNQIRAJ]: 60,
[MapNames.TEMPLE_OF_AHNQIRAJ]: 60,
[MapNames.ZUL_GURUB]: 60,
[MapNames.ONYXIAS_LAIR]: 60,
[MapNames.EMERALD_DREAM]: 60,
// The Burning Crusade dungeons
[MapNames.SHATTERED_HALLS]: 70,
[MapNames.BLOOD_FURNACE]: 65,
[MapNames.HELLFIRE_RAMPARTS]: 62,
[MapNames.STEAMVAULTS]: 64,
[MapNames.UNDERBOG]: 65,
[MapNames.SLAVE_PENS]: 64,
[MapNames.MANA_TOMBS]: 66,
[MapNames.AUCHENAI_CRYPTS]: 67,
[MapNames.SETHEKK_HALLS]: 70,
[MapNames.SHADOW_LABYRINTH]: 70,
[MapNames.OLD_HILLSBRAD]: 68,
[MapNames.BOTANICA]: 70,
[MapNames.MECHANAR]: 70,
[MapNames.ARCATRAZ]: 70,
[MapNames.MAGISTERS_TERRACE]: 70,
[MapNames.BLACK_MORASS]: 70,
// The Burning Crusade raids
[MapNames.KARAZHAN]: 70,
[MapNames.GRUULS_LAIR]: 70,
[MapNames.MAGTHERIDONS_LAIR]: 70,
[MapNames.SERPENTSHRINE_CAVERN]: 70,
[MapNames.TEMPEST_KEEP]: 70,
[MapNames.MOUNT_HYJAL]: 70,
[MapNames.BLACK_TEMPLE]: 70,
[MapNames.SUNWELL_PLATEAU]: 70,
[MapNames.ZUL_AMAN]: 70,
// Wrath of the Lich King dungeons
[MapNames.UTGARDE_KEEP]: 72,
[MapNames.UTGARDE_PINNACLE]: 76,
[MapNames.AHNKAHET]: 75,
[MapNames.NEXUS]: 73,
[MapNames.CULLING_STRATHOLME]: 80,
[MapNames.DRAK_THARON]: 76,
[MapNames.AZJOL_NERUB]: 75,
[MapNames.VIOLET_HOLD]: 77,
[MapNames.GUNDRAK]: 78,
[MapNames.HALLS_OF_STONE]: 78,
[MapNames.HALLS_OF_LIGHTNING]: 80,
[MapNames.OCULUS]: 78,
[MapNames.TRIAL_OF_CHAMPION]: 80,
[MapNames.FORGE_OF_SOULS]: 80,
[MapNames.PIT_OF_SARON]: 80,
[MapNames.HALLS_OF_REFLECTION]: 80,
// Wrath of the Lich King raids
[MapNames.NAXXRAMAS]: 80,
[MapNames.OBSIDIAN_SANCTUM]: 80,
[MapNames.EYE_OF_ETERNITY]: 80,
[MapNames.ULDUAR]: 80,
[MapNames.TRIAL_OF_CRUSADER]: 80,
[MapNames.VAULT_OF_ARCHAVON]: 80,
[MapNames.ICECROWN_CITADEL]: 80,
[MapNames.RUBY_SANCTUM]: 80
};
// Boss info
export type Boss = {
entry: number;
name: string;
location: string;
isFinalBoss?: boolean;
}
export const Bosses: Boss[] = [
// Classic WoW dungeons
{ entry: 11520, name: "Taragaman the Hungerer", location: "Ragefire Chasm" },
{ entry: 3654, name: "Mutanus the Devourer", location: "Wailing Caverns", isFinalBoss: true },
{ entry: 639, name: "Edwin VanCleef", location: "The Deadmines", isFinalBoss: true },
{ entry: 4275, name: "Archmage Arugal", location: "Shadowfang Keep", isFinalBoss: true },
{ entry: 4829, name: "Aku'mai", location: "Blackfathom Deeps", isFinalBoss: true },
{ entry: 1716, name: "Bazil Thredd", location: "Stormwind Stockade", isFinalBoss: true },
{ entry: 7800, name: "Mekgineer Thermaplugg", location: "Gnomeregan", isFinalBoss: true },
{ entry: 4421, name: "Charlga Razorflank", location: "Razorfen Kraul", isFinalBoss: true },
{ entry: 4543, name: "Bloodmage Thalnos", location: "Scarlet Monastery Graveyard", isFinalBoss: true },
{ entry: 6487, name: "Arcanist Doan", location: "Scarlet Monastery Library", isFinalBoss: true },
{ entry: 3975, name: "Herod", location: "Scarlet Monastery Armory", isFinalBoss: true },
{ entry: 3977, name: "High Inquisitor Whitemane", location: "Scarlet Monastery Cathedral", isFinalBoss: true },
{ entry: 7358, name: "Amnennar the Coldbringer", location: "Razorfen Downs", isFinalBoss: true },
{ entry: 2748, name: "Archaedas", location: "Uldaman", isFinalBoss: true },
{ entry: 7267, name: "Chief Ukorz Sandscalp", location: "Zul'Farrak", isFinalBoss: true },
{ entry: 12201, name: "Princess Theradras", location: "Maraudon", isFinalBoss: true },
{ entry: 8443, name: "Avatar of Hakkar", location: "Sunken Temple", isFinalBoss: true },
{ entry: 9019, name: "Emperor Dagran Thaurissan", location: "Blackrock Depths", isFinalBoss: true },
{ entry: 9568, name: "Overlord Wyrmthalak", location: "Lower Blackrock Spire", isFinalBoss: true },
{ entry: 10363, name: "General Drakkisath", location: "Upper Blackrock Spire", isFinalBoss: true },
{ entry: 11492, name: "Alzzin the Wildshaper", location: "Dire Maul East", isFinalBoss: true },
{ entry: 11489, name: "Tendris Warpwood", location: "Dire Maul West", isFinalBoss: true },
{ entry: 11501, name: "King Gordok", location: "Dire Maul North", isFinalBoss: true },
{ entry: 10440, name: "Baron Rivendare", location: "Stratholme Undead Side", isFinalBoss: true },
{ entry: 10813, name: "Balnazzar", location: "Stratholme Live Side", isFinalBoss: true },
{ entry: 1853, name: "Darkmaster Gandling", location: "Scholomance", isFinalBoss: true },
// The Burning Crusade dungeons
{ entry: 17307, name: "Vazruden", location: "Hellfire Ramparts" },
{ entry: 17536, name: "Nazan", location: "Hellfire Ramparts", isFinalBoss: true },
{ entry: 17377, name: "Keli'dan the Breaker", location: "The Blood Furnace", isFinalBoss: true },
{ entry: 16808, name: "Warchief Kargath Bladefist", location: "The Shattered Halls", isFinalBoss: true },
{ entry: 17942, name: "Quagmirran", location: "The Slave Pens", isFinalBoss: true },
{ entry: 17826, name: "Swamplord Musel'ek", location: "The Underbog", isFinalBoss: true },
{ entry: 17798, name: "Warlord Kalithresh", location: "The Steamvault", isFinalBoss: true },
{ entry: 18344, name: "Nexus-Prince Shaffar", location: "Mana-Tombs", isFinalBoss: true },
{ entry: 18373, name: "Exarch Maladaar", location: "Auchenai Crypts", isFinalBoss: true },
{ entry: 18473, name: "Talon King Ikiss", location: "Sethekk Halls", isFinalBoss: true },
{ entry: 18708, name: "Murmur", location: "Shadow Labyrinth", isFinalBoss: true },
{ entry: 19220, name: "Pathaleon the Calculator", location: "The Mechanar", isFinalBoss: true },
{ entry: 17977, name: "Warp Splinter", location: "The Botanica", isFinalBoss: true },
{ entry: 20912, name: "Harbinger Skyriss", location: "The Arcatraz", isFinalBoss: true },
{ entry: 17881, name: "Aeonus", location: "The Black Morass", isFinalBoss: true },
{ entry: 18096, name: "Epoch Hunter", location: "Old Hillsbrad Foothills", isFinalBoss: true },
{ entry: 24664, name: "Kael'thas Sunstrider", location: "Magisters' Terrace", isFinalBoss: true },
// The Burning Crusade raids
{ entry: 22887, name: "Attumen the Huntsman", location: "Karazhan" },
{ entry: 22888, name: "Moroes", location: "Karazhan" },
{ entry: 22889, name: "Maiden of Virtue", location: "Karazhan" },
{ entry: 22890, name: "The Big Bad Wolf", location: "Karazhan" },
{ entry: 22891, name: "The Crone", location: "Karazhan" },
{ entry: 22892, name: "Romulo", location: "Karazhan" },
{ entry: 22893, name: "Julianne", location: "Karazhan" },
{ entry: 22894, name: "The Curator", location: "Karazhan" },
{ entry: 22895, name: "Terestian Illhoof", location: "Karazhan" },
{ entry: 22896, name: "Shade of Aran", location: "Karazhan" },
{ entry: 22897, name: "Netherspite", location: "Karazhan" },
{ entry: 22898, name: "Chess Event", location: "Karazhan" },
{ entry: 22899, name: "Prince Malchezaar", location: "Karazhan", isFinalBoss: true },
{ entry: 22900, name: "Nightbane", location: "Karazhan" },
{ entry: 22901, name: "Gruul the Dragonkiller", location: "Gruul's Lair", isFinalBoss: true },
{ entry: 22902, name: "Magtheridon", location: "Magtheridon's Lair", isFinalBoss: true },
{ entry: 22903, name: "Lady Vashj", location: "Serpentshrine Cavern", isFinalBoss: true },
{ entry: 22904, name: "Kael'thas Sunstrider", location: "The Eye", isFinalBoss: true },
{ entry: 22905, name: "Al'ar", location: "The Eye", isFinalBoss: true },
{ entry: 22906, name: "Solarian", location: "The Eye", isFinalBoss: true },
{ entry: 22907, name: "Void Reaver", location: "The Eye", isFinalBoss: true },
{ entry: 22908, name: "High Astromancer Solarian", location: "The Eye", isFinalBoss: true },
{ entry: 22909, name: "Kael'thas Sunstrider", location: "The Eye", isFinalBoss: true },
{ entry: 22910, name: "Rage Winterchill", location: "Mount Hyjal", isFinalBoss: true },
{ entry: 22911, name: "Anetheron", location: "Mount Hyjal", isFinalBoss: true },
{ entry: 22912, name: "Kaz'rogal", location: "Mount Hyjal", isFinalBoss: true },
{ entry: 22913, name: "Azgalor", location: "Mount Hyjal", isFinalBoss: true },
{ entry: 22914, name: "Archimonde", location: "Mount Hyjal", isFinalBoss: true },
{ entry: 22915, name: "Najentus", location: "Black Temple", isFinalBoss: true },
{ entry: 22916, name: "Supremus", location: "Black Temple", isFinalBoss: true },
{ entry: 22917, name: "Shade of Akama", location: "Black Temple", isFinalBoss: true },
{ entry: 22918, name: "Teron Gorefiend", location: "Black Temple", isFinalBoss: true },
{ entry: 22919, name: "Gurtogg Bloodboil", location: "Black Temple", isFinalBoss: true },
{ entry: 22920, name: "Reliquary of Souls", location: "Black Temple", isFinalBoss: true },
{ entry: 22921, name: "Mother Shahraz", location: "Black Temple", isFinalBoss: true },
{ entry: 22922, name: "The Illidari Council", location: "Black Temple", isFinalBoss: true },
{ entry: 22923, name: "Illidan Stormrage", location: "Black Temple", isFinalBoss: true },
{ entry: 22924, name: "Kalecgos", location: "Sunwell Plateau", isFinalBoss: true },
{ entry: 22925, name: "Brutallus", location: "Sunwell Plateau", isFinalBoss: true },
{ entry: 22926, name: "Felmyst", location: "Sunwell Plateau", isFinalBoss: true },
{ entry: 22927, name: "Eredar Twins", location: "Sunwell Plateau", isFinalBoss: true },
{ entry: 22928, name: "M'uru", location: "Sunwell Plateau", isFinalBoss: true },
{ entry: 22929, name: "Kil'jaeden", location: "Sunwell Plateau", isFinalBoss: true },
// Wrath of the Lich King dungeons
{ entry: 23954, name: "Ingvar the Plunderer", location: "Utgarde Keep", isFinalBoss: true },
{ entry: 26723, name: "Keristrasza", location: "The Nexus", isFinalBoss: true },
{ entry: 29120, name: "Anub'arak", location: "Azjol-Nerub", isFinalBoss: true },
{ entry: 29311, name: "Herald Volazj", location: "Ahn'kahet: The Old Kingdom", isFinalBoss: true },
{ entry: 26632, name: "The Prophet Tharon'ja", location: "Drak'Tharon Keep", isFinalBoss: true },
{ entry: 31134, name: "Cyanigosa", location: "Violet Hold", isFinalBoss: true },
{ entry: 29306, name: "Gal'darah", location: "Gundrak", isFinalBoss: true },
{ entry: 27978, name: "Sjonnir the Ironshaper", location: "Halls of Stone", isFinalBoss: true },
{ entry: 28923, name: "Loken", location: "Halls of Lightning", isFinalBoss: true },
{ entry: 27656, name: "Ley-Guardian Eregos", location: "The Oculus", isFinalBoss: true },
{ entry: 26533, name: "Mal'Ganis", location: "Culling of Stratholme", isFinalBoss: true },
{ entry: 26861, name: "King Ymiron", location: "Utgarde Pinnacle", isFinalBoss: true },
{ entry: 35451, name: "The Black Knight", location: "Trial of the Champion", isFinalBoss: true },
{ entry: 36502, name: "Devourer of Souls", location: "Forge of Souls", isFinalBoss: true },
{ entry: 36658, name: "Scourgelord Tyrannus", location: "Pit of Saron", isFinalBoss: true },
{ entry: 37226, name: "The Lich King", location: "Halls of Reflection", isFinalBoss: true },
// Wrath of the Lich King raids
{ entry: 29341, name: "Anub'Rekhan", location: "Naxxramas", isFinalBoss: true },
{ entry: 29342, name: "Grand Widow Faerlina", location: "Naxxramas", isFinalBoss: true },
{ entry: 29343, name: "Maexxna", location: "Naxxramas", isFinalBoss: true },
{ entry: 29344, name: "Noth the Plaguebringer", location: "Naxxramas", isFinalBoss: true },
{ entry: 29345, name: "Heigan the Unclean", location: "Naxxramas", isFinalBoss: true },
{ entry: 29346, name: "Loatheb", location: "Naxxramas", isFinalBoss: true },
{ entry: 29347, name: "Instructor Razuvious", location: "Naxxramas", isFinalBoss: true },
{ entry: 29348, name: "Gothik the Harvester", location: "Naxxramas", isFinalBoss: true },
{ entry: 29349, name: "The Four Horsemen", location: "Naxxramas", isFinalBoss: true },
{ entry: 29350, name: "Patchwerk", location: "Naxxramas", isFinalBoss: true },
{ entry: 29351, name: "Grobbulus", location: "Naxxramas", isFinalBoss: true },
{ entry: 29352, name: "Gluth", location: "Naxxramas", isFinalBoss: true },
{ entry: 29353, name: "Thaddius", location: "Naxxramas", isFinalBoss: true },
{ entry: 29354, name: "Sapphiron", location: "Naxxramas", isFinalBoss: true },
{ entry: 29355, name: "Kel'Thuzad", location: "Naxxramas", isFinalBoss: true },
{ entry: 29356, name: "Sartharion", location: "Obsidian Sanctum", isFinalBoss: true },
{ entry: 29357, name: "Malygos", location: "Eye of Eternity", isFinalBoss: true },
{ entry: 29358, name: "Ignis the Furnace Master", location: "Ulduar", isFinalBoss: true },
{ entry: 29359, name: "XT-002 Deconstructor", location: "Ulduar", isFinalBoss: true },
{ entry: 29360, name: "Kologarn", location: "Ulduar", isFinalBoss: true },
{ entry: 29361, name: "Auriaya", location: "Ulduar", isFinalBoss: true },
{ entry: 29362, name: "Hodir", location: "Ulduar", isFinalBoss: true },
{ entry: 29363, name: "Thorim", location: "Ulduar", isFinalBoss: true },
{ entry: 29364, name: "Freya", location: "Ulduar", isFinalBoss: true },
{ entry: 29365, name: "Mimiron", location: "Ulduar", isFinalBoss: true },
{ entry: 29366, name: "General Vezax", location: "Ulduar", isFinalBoss: true },
{ entry: 29367, name: "Yogg-Saron", location: "Ulduar", isFinalBoss: true },
{ entry: 29368, name: "The Northrend Beasts", location: "Trial of the Crusader", isFinalBoss: true },
{ entry: 29369, name: "Lord Jaraxxus", location: "Trial of the Crusader", isFinalBoss: true },
{ entry: 29370, name: "The Faction Champions", location: "Trial of the Crusader", isFinalBoss: true },
{ entry: 29371, name: "Twin Val'kyr", location: "Trial of the Crusader", isFinalBoss: true },
{ entry: 29372, name: "Anub'arak", location: "Trial of the Crusader", isFinalBoss: true },
{ entry: 29373, name: "Onyxia", location: "Onyxia's Lair", isFinalBoss: true },
{ entry: 29374, name: "Lord Marrowgar", location: "Icecrown Citadel", isFinalBoss: true },
{ entry: 29375, name: "Lady Deathwhisper", location: "Icecrown Citadel", isFinalBoss: true },
{ entry: 29376, name: "Gunship Battle", location: "Icecrown Citadel", isFinalBoss: true },
{ entry: 29377, name: "Deathbringer Saurfang", location: "Icecrown Citadel", isFinalBoss: true },
{ entry: 29378, name: "Festergut", location: "Icecrown Citadel", isFinalBoss: true },
{ entry: 29379, name: "Rotface", location: "Icecrown Citadel", isFinalBoss: true },
{ entry: 29380, name: "Professor Putricide", location: "Icecrown Citadel", isFinalBoss: true },
{ entry: 29381, name: "Blood-Queen Lana'thel", location: "Icecrown Citadel", isFinalBoss: true },
{ entry: 29382, name: "Valithria Dreamwalker", location: "Icecrown Citadel", isFinalBoss: true },
{ entry: 29383, name: "Sindragosa", location: "Icecrown Citadel", isFinalBoss: true },
{ entry: 29384, name: "The Lich King", location: "Icecrown Citadel", isFinalBoss: true },
{ entry: 29385, name: "Halion", location: "Ruby Sanctum", isFinalBoss: true },
// Molten Core
{ entry: 12118, name: "Lucifron", location: "Molten Core" },
{ entry: 11982, name: "Magmadar", location: "Molten Core" },
{ entry: 12259, name: "Gehennas", location: "Molten Core" },
{ entry: 12057, name: "Garr", location: "Molten Core" },
{ entry: 12264, name: "Shazzrah", location: "Molten Core" },
{ entry: 12056, name: "Baron Geddon", location: "Molten Core" },
{ entry: 12098, name: "Sulfuron Harbinger", location: "Molten Core" },
{ entry: 11988, name: "Golemagg the Incinerator", location: "Molten Core" },
{ entry: 12018, name: "Majordomo Executus", location: "Molten Core" },
{ entry: 11502, name: "Ragnaros", location: "Molten Core", isFinalBoss: true },
// Blackwing Lair
{ entry: 12435, name: "Razorgore the Untamed", location: "Blackwing Lair" },
{ entry: 13020, name: "Vaelastrasz the Corrupt", location: "Blackwing Lair" },
{ entry: 12017, name: "Broodlord Lashlayer", location: "Blackwing Lair" },
{ entry: 11983, name: "Firemaw", location: "Blackwing Lair" },
{ entry: 14601, name: "Ebonroc", location: "Blackwing Lair" },
{ entry: 11981, name: "Flamegor", location: "Blackwing Lair" },
{ entry: 14020, name: "Chromaggus", location: "Blackwing Lair" },
{ entry: 11583, name: "Nefarian", location: "Blackwing Lair", isFinalBoss: true },
// Ruins of Ahn'Qiraj
{ entry: 15348, name: "Kurinnaxx", location: "Ruins of Ahn'Qiraj" },
{ entry: 15341, name: "General Rajaxx", location: "Ruins of Ahn'Qiraj" },
{ entry: 15340, name: "Moam", location: "Ruins of Ahn'Qiraj" },
{ entry: 15370, name: "Buru the Gorger", location: "Ruins of Ahn'Qiraj" },
{ entry: 15369, name: "Ayamiss the Hunter", location: "Ruins of Ahn'Qiraj" },
{ entry: 15339, name: "Ossirian the Unscarred", location: "Ruins of Ahn'Qiraj", isFinalBoss: true },
// Temple of Ahn'Qiraj
{ entry: 15263, name: "The Prophet Skeram", location: "Temple of Ahn'Qiraj" },
{ entry: 15516, name: "Battleguard Sartura", location: "Temple of Ahn'Qiraj" },
{ entry: 15510, name: "Fankriss the Unyielding", location: "Temple of Ahn'Qiraj" },
{ entry: 15509, name: "Princess Huhuran", location: "Temple of Ahn'Qiraj" },
{ entry: 15275, name: "Emperor Vek'lor", location: "Temple of Ahn'Qiraj" },
{ entry: 15276, name: "Emperor Vek'nilash", location: "Temple of Ahn'Qiraj" },
{ entry: 15727, name: "C'Thun", location: "Temple of Ahn'Qiraj", isFinalBoss: true },
// Zul'Gurub
{ entry: 14517, name: "High Priestess Jeklik", location: "Zul'Gurub" },
{ entry: 14507, name: "High Priest Venoxis", location: "Zul'Gurub" },
{ entry: 14510, name: "High Priestess Mar'li", location: "Zul'Gurub" },
{ entry: 14509, name: "High Priest Thekal", location: "Zul'Gurub" },
{ entry: 14515, name: "High Priestess Arlokk", location: "Zul'Gurub" },
{ entry: 14834, name: "Hakkar the Soulflayer", location: "Zul'Gurub", isFinalBoss: true },
{ entry: 11382, name: "Bloodlord Mandokir", location: "Zul'Gurub" },
{ entry: 11380, name: "Jin'do the Hexxer", location: "Zul'Gurub" },
{ entry: 15114, name: "Gahz'ranka", location: "Zul'Gurub" },
{ entry: 15082, name: "Renataki", location: "Zul'Gurub" },
{ entry: 15083, name: "Grilek", location: "Zul'Gurub" },
{ entry: 15084, name: "Hazza'rah", location: "Zul'Gurub" },
{ entry: 15085, name: "Wushoolay", location: "Zul'Gurub" },
// Karazhan
{ entry: 16152, name: "Attumen the Huntsman", location: "Karazhan" },
{ entry: 15687, name: "Moroes", location: "Karazhan" },
{ entry: 16457, name: "Maiden of Virtue", location: "Karazhan" },
{ entry: 17521, name: "The Big Bad Wolf", location: "Karazhan" },
{ entry: 18168, name: "The Crone", location: "Karazhan" },
{ entry: 17533, name: "Romulo", location: "Karazhan" },
{ entry: 17534, name: "Julianne", location: "Karazhan" },
{ entry: 15691, name: "The Curator", location: "Karazhan" },
{ entry: 15688, name: "Terestian Illhoof", location: "Karazhan" },
{ entry: 16524, name: "Shade of Aran", location: "Karazhan" },
{ entry: 15689, name: "Netherspite", location: "Karazhan" },
{ entry: 16816, name: "Chess Event", location: "Karazhan" },
{ entry: 15690, name: "Prince Malchezaar", location: "Karazhan", isFinalBoss: true },
{ entry: 17225, name: "Nightbane", location: "Karazhan" }
];
// Quickly check if a creature is a final boss
export const FinalBossIDs = new Set(Bosses.filter(b => b.isFinalBoss).map(b => b.entry));
// Example usage:
export function isFinalBoss(entry: number): boolean {
return FinalBossIDs.has(entry);
}
// Look up by name
export const BossesByName = Object.fromEntries(
Bosses.map(b => [b.name.toLowerCase(), b])
);
// Example usage:
export function getBossByName(name: string): Boss | undefined {
return BossesByName[name.toLowerCase()];
}
// For backward compatibility
export const BossIDs: Record<number, boolean> = Object.fromEntries(
Bosses.map(b => [b.entry, true])
);

View File

@@ -147,7 +147,7 @@ const GossipSelect: gossip_event_on_select = (event: number, player: Player, cre
1
);
player.RemoveItem(PlayerItem, PlayerItem.GetEntry(), 1);
player.RemoveItem(PlayerItem, 1);
player.GossipClearMenu();
player.GossipComplete();