557 lines
15 KiB
JavaScript
557 lines
15 KiB
JavaScript
const {
|
|
SlashCommandBuilder,
|
|
EmbedBuilder,
|
|
AttachmentBuilder
|
|
} = require("discord.js");
|
|
|
|
const { mClient } = require("../..");
|
|
const path = require("node:path");
|
|
const jobIconDirectory = path.resolve(
|
|
__dirname,
|
|
"../../assets/jobs"
|
|
);
|
|
const expansions = [
|
|
{ name: "A Realm Reborn", value: 2 },
|
|
{ name: "Heavensward", value: 3 },
|
|
{ name: "Stormblood", value: 4 },
|
|
{ name: "Shadowbringers", value: 5 },
|
|
{ name: "Endwalker", value: 6 },
|
|
{ name: "Dawntrail", value: 7 }
|
|
];
|
|
|
|
const dutyTypes = [
|
|
{ name: "Dungeon", value: "Dungeon" },
|
|
{ name: "Trial", value: "Trial" },
|
|
{ name: "Raid", value: "Raid" },
|
|
{ name: "Alliance Raid", value: "AllianceRaid" },
|
|
{ name: "Guildhest", value: "Guildhest" },
|
|
{ name: "Variant Dungeon", value: "VariantDungeon" },
|
|
{ name: "Criterion", value: "Criterion" }
|
|
];
|
|
|
|
const dutyDifficulties = [
|
|
{ name: "Normal", value: "Normal" },
|
|
{ name: "Hard", value: "Hard" },
|
|
{ name: "Extreme", value: "Extreme" },
|
|
{ name: "Unreal", value: "Unreal" },
|
|
{ name: "Savage", value: "Savage" },
|
|
{ name: "Ultimate", value: "Ultimate" },
|
|
{ name: "Chaotic", value: "Chaotic" }
|
|
];
|
|
|
|
const roulettes = [
|
|
{ name: "Leveling", value: "Leveling" },
|
|
{ name: "High-level Dungeons", value: "High-level Dungeons" },
|
|
{ name: "Level Cap Dungeons", value: "Level Cap Dungeons" },
|
|
{ name: "Trials", value: "Trials" },
|
|
{ name: "Normal Raids", value: "Normal Raids" },
|
|
{ name: "Alliance Raids", value: "Alliance Raids" },
|
|
{ name: "Main Scenario", value: "Main Scenario" },
|
|
{ name: "Guildhests", value: "Guildhests" },
|
|
{ name: "Mentor", value: "Mentor" }
|
|
];
|
|
|
|
const roles = [
|
|
{ name: "Tank", value: "Tank" },
|
|
{ name: "Healer", value: "Healer" },
|
|
{ name: "DPS", value: "DPS" }
|
|
];
|
|
|
|
const roleTypes = [
|
|
{ name: "Tank", value: "Tank" },
|
|
{ name: "Pure Healer", value: "Pure" },
|
|
{ name: "Shield Healer", value: "Shield" },
|
|
{ name: "Melee DPS", value: "Melee" },
|
|
{ name: "Physical Ranged DPS", value: "PhysicalRanged" },
|
|
{ name: "Magical Ranged DPS", value: "MagicalRanged" }
|
|
];
|
|
|
|
const damageTypes = [
|
|
{ name: "Physical", value: "Physical" },
|
|
{ name: "Magical", value: "Magical" }
|
|
];
|
|
|
|
function getJobIcon(job) {
|
|
const fileName = `${job.Name.replace(/\s+/g, "")}.png`;
|
|
|
|
return path.join(
|
|
jobIconDirectory,
|
|
fileName
|
|
);
|
|
}
|
|
|
|
const dutyIcons = {
|
|
Guildhest:
|
|
"https://ffxiv.consolegameswiki.com/mediawiki/images/6/61/Guildhest.png",
|
|
|
|
Dungeon:
|
|
"https://ffxiv.consolegameswiki.com/mediawiki/images/a/ab/Dungeon.png",
|
|
|
|
Trial:
|
|
"https://ffxiv.consolegameswiki.com/mediawiki/images/0/0d/Trial.png",
|
|
|
|
Raid:
|
|
"https://ffxiv.consolegameswiki.com/mediawiki/images/d/d6/Raid.png",
|
|
|
|
AllianceRaid:
|
|
"https://ffxiv.consolegameswiki.com/mediawiki/images/d/d6/Raid.png",
|
|
|
|
VariantDungeon:
|
|
"https://ffxiv.consolegameswiki.com/mediawiki/images/5/5e/Variant_and_criterion_dungeons_icon1.png",
|
|
|
|
Criterion:
|
|
"https://ffxiv.consolegameswiki.com/mediawiki/images/5/5e/Variant_and_criterion_dungeons_icon1.png"
|
|
};
|
|
|
|
const dutyDifficultyIcons = {
|
|
Ultimate:
|
|
"https://ffxiv.consolegameswiki.com/mediawiki/images/8/8a/Ultimate_Raid.png",
|
|
|
|
Chaotic:
|
|
"https://ffxiv.consolegameswiki.com/mediawiki/images/a/a9/Chaotic_Alliance_Raid.png"
|
|
};
|
|
|
|
|
|
module.exports = {
|
|
data: new SlashCommandBuilder()
|
|
.setName("random")
|
|
.setDescription("Randomly select FFXIV content.")
|
|
|
|
.addSubcommand(subcommand =>
|
|
subcommand
|
|
.setName("duty")
|
|
.setDescription("Select a random duty to complete.")
|
|
|
|
.addIntegerOption(option =>
|
|
option
|
|
.setName("level")
|
|
.setDescription("Your current level.")
|
|
.setRequired(true)
|
|
.setMinValue(10)
|
|
.setMaxValue(100)
|
|
)
|
|
|
|
.addStringOption(option =>
|
|
option
|
|
.setName("type")
|
|
.setDescription("Only select a specific duty type.")
|
|
.addChoices(...dutyTypes)
|
|
)
|
|
|
|
.addStringOption(option =>
|
|
option
|
|
.setName("difficulty")
|
|
.setDescription("Only select a specific difficulty.")
|
|
.addChoices(...dutyDifficulties)
|
|
)
|
|
|
|
.addIntegerOption(option =>
|
|
option
|
|
.setName("expansion")
|
|
.setDescription("Only select duties from an expansion.")
|
|
.addChoices(...expansions)
|
|
)
|
|
|
|
.addStringOption(option =>
|
|
option
|
|
.setName("roulette")
|
|
.setDescription("Only select duties belonging to a roulette.")
|
|
.addChoices(...roulettes)
|
|
)
|
|
|
|
.addIntegerOption(option =>
|
|
option
|
|
.setName("party-size")
|
|
.setDescription("Only select duties with this party size.")
|
|
.addChoices(
|
|
{ name: "4 Players", value: 4 },
|
|
{ name: "8 Players", value: 8 },
|
|
{ name: "24 Players", value: 24 }
|
|
)
|
|
)
|
|
|
|
.addBooleanOption(option =>
|
|
option
|
|
.setName("duty-support")
|
|
.setDescription("Filter by Duty Support availability.")
|
|
)
|
|
|
|
.addBooleanOption(option =>
|
|
option
|
|
.setName("trusts")
|
|
.setDescription("Filter by Trust availability.")
|
|
)
|
|
)
|
|
|
|
.addSubcommand(subcommand =>
|
|
subcommand
|
|
.setName("class")
|
|
.setDescription("Select a random class or job.")
|
|
|
|
.addStringOption(option =>
|
|
option
|
|
.setName("role")
|
|
.setDescription("Only select a specific role.")
|
|
.addChoices(...roles)
|
|
)
|
|
|
|
.addStringOption(option =>
|
|
option
|
|
.setName("role-type")
|
|
.setDescription("Only select a specific role subtype.")
|
|
.addChoices(...roleTypes)
|
|
)
|
|
|
|
.addStringOption(option =>
|
|
option
|
|
.setName("damage-type")
|
|
.setDescription("Filter by physical or magical damage.")
|
|
.addChoices(...damageTypes)
|
|
)
|
|
|
|
.addIntegerOption(option =>
|
|
option
|
|
.setName("expansion")
|
|
.setDescription("Only select classes/jobs from an expansion.")
|
|
.addChoices(...expansions)
|
|
)
|
|
|
|
.addBooleanOption(option =>
|
|
option
|
|
.setName("jobstone")
|
|
.setDescription("True for jobs, false for base classes.")
|
|
)
|
|
|
|
.addBooleanOption(option =>
|
|
option
|
|
.setName("limited")
|
|
.setDescription("Filter by limited-job status.")
|
|
)
|
|
|
|
.addIntegerOption(option =>
|
|
option
|
|
.setName("max-starting-level")
|
|
.setDescription("Maximum starting level of the class/job.")
|
|
.setMinValue(1)
|
|
.setMaxValue(100)
|
|
)
|
|
),
|
|
|
|
async execute(interaction) {
|
|
switch (interaction.options.getSubcommand()) {
|
|
case "duty":
|
|
return randomDuty(interaction);
|
|
|
|
case "class":
|
|
return randomClass(interaction);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
async function randomDuty(interaction) {
|
|
const collection = mClient
|
|
.db("neo-db")
|
|
.collection("items_duties");
|
|
|
|
const level = interaction.options.getInteger("level");
|
|
const type = interaction.options.getString("type");
|
|
const difficulty = interaction.options.getString("difficulty");
|
|
const expansion = interaction.options.getInteger("expansion");
|
|
const roulette = interaction.options.getString("roulette");
|
|
const partySize = interaction.options.getInteger("party-size");
|
|
const dutySupport = interaction.options.getBoolean("duty-support");
|
|
const trusts = interaction.options.getBoolean("trusts");
|
|
|
|
const query = {
|
|
MinimumLevel: {
|
|
$lte: level
|
|
}
|
|
};
|
|
|
|
if (type !== null)
|
|
query.Type = type;
|
|
|
|
if (difficulty !== null)
|
|
query.Difficulty = difficulty;
|
|
|
|
if (expansion !== null)
|
|
query.Expansion = expansion;
|
|
|
|
if (roulette !== null)
|
|
query.Roulettes = roulette;
|
|
|
|
if (partySize !== null)
|
|
query["Party.Size"] = partySize;
|
|
|
|
if (dutySupport !== null)
|
|
query["Party.CanUseDutySupport"] = dutySupport;
|
|
|
|
if (trusts !== null)
|
|
query["Party.CanUseTrusts"] = trusts;
|
|
|
|
|
|
const [duty] = await collection.aggregate([
|
|
{
|
|
$match: query
|
|
},
|
|
{
|
|
$sample: {
|
|
size: 1
|
|
}
|
|
}
|
|
]).toArray();
|
|
|
|
|
|
if (!duty) {
|
|
return interaction.reply({
|
|
content: "No duties matched those filters.",
|
|
ephemeral: true
|
|
});
|
|
}
|
|
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setTitle(duty.Name)
|
|
.setThumbnail(getDutyIcon(duty))
|
|
.setDescription(
|
|
[
|
|
`**${formatDutyType(duty.Type)}**`,
|
|
`${duty.Difficulty} Difficulty`,
|
|
`${getExpansionName(duty.Expansion)} • Patch ${formatPatch(duty)}`
|
|
].join("\n")
|
|
)
|
|
.addFields(
|
|
{
|
|
name: "Level",
|
|
value: `${duty.MinimumLevel}`,
|
|
inline: true
|
|
},
|
|
{
|
|
name: "Level Sync",
|
|
value: `${duty.LevelSync}`,
|
|
inline: true
|
|
},
|
|
{
|
|
name: "Minimum Item Level",
|
|
value: duty.MinimumItemLevel
|
|
? `${duty.MinimumItemLevel}`
|
|
: "None",
|
|
inline: true
|
|
},
|
|
{
|
|
name: "Party",
|
|
value: formatParty(duty.Party)
|
|
},
|
|
{
|
|
name: "Roulettes",
|
|
value: duty.Roulettes?.length
|
|
? duty.Roulettes.join(", ")
|
|
: "None"
|
|
},
|
|
{
|
|
name: "NPC Support",
|
|
value: [
|
|
`Duty Support: ${duty.Party.CanUseDutySupport ? "Yes" : "No"}`,
|
|
`Trusts: ${duty.Party.CanUseTrusts ? "Yes" : "No"}`
|
|
].join("\n")
|
|
}
|
|
);
|
|
|
|
|
|
return interaction.reply({
|
|
embeds: [embed]
|
|
});
|
|
}
|
|
|
|
|
|
async function randomClass(interaction) {
|
|
const collection = mClient
|
|
.db("neo-db")
|
|
.collection("items_classes");
|
|
|
|
const role = interaction.options.getString("role");
|
|
const roleType = interaction.options.getString("role-type");
|
|
const damageType = interaction.options.getString("damage-type");
|
|
const expansion = interaction.options.getInteger("expansion");
|
|
const hasJobStone = interaction.options.getBoolean("jobstone");
|
|
const limited = interaction.options.getBoolean("limited");
|
|
const maxStartingLevel =
|
|
interaction.options.getInteger("max-starting-level");
|
|
|
|
const query = {};
|
|
|
|
if (role !== null)
|
|
query.Role = role;
|
|
|
|
if (roleType !== null)
|
|
query.RoleType = roleType;
|
|
|
|
if (damageType !== null)
|
|
query.DamageType = damageType;
|
|
|
|
if (expansion !== null)
|
|
query.Expansion = expansion;
|
|
|
|
if (hasJobStone !== null)
|
|
query.HasJobStone = hasJobStone;
|
|
|
|
if (limited !== null)
|
|
query.Limited = limited;
|
|
|
|
if (maxStartingLevel !== null) {
|
|
query.StartingLevel = {
|
|
$lte: maxStartingLevel
|
|
};
|
|
}
|
|
|
|
|
|
const [job] = await collection.aggregate([
|
|
{
|
|
$match: query
|
|
},
|
|
{
|
|
$sample: {
|
|
size: 1
|
|
}
|
|
}
|
|
]).toArray();
|
|
|
|
|
|
if (!job) {
|
|
return interaction.reply({
|
|
content: "No classes or jobs matched those filters.",
|
|
ephemeral: true
|
|
});
|
|
}
|
|
|
|
const icon = new AttachmentBuilder(
|
|
getJobIcon(job),
|
|
{
|
|
name: `${job.Abbreviation}.png`
|
|
}
|
|
);
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setTitle(`${job.Name} (${job.Abbreviation})`)
|
|
.setThumbnail(`attachment://${job.Abbreviation}.png`)
|
|
.setDescription(
|
|
[
|
|
`**${job.Role} • ${formatRoleType(job.RoleType)}**`,
|
|
`${job.DamageType} Damage`,
|
|
`${getExpansionName(job.Expansion)} • Patch ${formatPatch(job)}`
|
|
].join("\n")
|
|
)
|
|
.addFields(
|
|
{
|
|
name: "Starting Level",
|
|
value: `${job.StartingLevel}`,
|
|
inline: true
|
|
},
|
|
{
|
|
name: "Job Stone",
|
|
value: job.HasJobStone ? "Yes" : "No",
|
|
inline: true
|
|
},
|
|
{
|
|
name: "Limited Job",
|
|
value: job.Limited ? "Yes" : "No",
|
|
inline: true
|
|
},
|
|
{
|
|
name: "Base Class",
|
|
value: job.Class ?? "None",
|
|
inline: true
|
|
}
|
|
);
|
|
|
|
|
|
return interaction.reply({
|
|
embeds: [embed],
|
|
files: [icon]
|
|
});
|
|
}
|
|
|
|
|
|
function getDutyIcon(duty) {
|
|
return dutyDifficultyIcons[duty.Difficulty]
|
|
?? dutyIcons[duty.Type]
|
|
?? null;
|
|
}
|
|
|
|
|
|
function getExpansionName(expansion) {
|
|
return expansions.find(entry =>
|
|
entry.value === expansion
|
|
)?.name ?? `Expansion ${expansion}`;
|
|
}
|
|
|
|
|
|
function formatPatch(item) {
|
|
if (item.MinorPatch) {
|
|
return `${item.Expansion}.${item.MajorPatch}${item.MinorPatch}`;
|
|
}
|
|
|
|
return `${item.Expansion}.${item.MajorPatch}`;
|
|
}
|
|
|
|
|
|
function formatDutyType(type) {
|
|
switch (type) {
|
|
case "AllianceRaid":
|
|
return "Alliance Raid";
|
|
|
|
case "VariantDungeon":
|
|
return "Variant Dungeon";
|
|
|
|
default:
|
|
return type;
|
|
}
|
|
}
|
|
|
|
|
|
function formatRoleType(roleType) {
|
|
switch (roleType) {
|
|
case "PhysicalRanged":
|
|
return "Physical Ranged";
|
|
|
|
case "MagicalRanged":
|
|
return "Magical Ranged";
|
|
|
|
case "Pure":
|
|
return "Pure Healer";
|
|
|
|
case "Shield":
|
|
return "Shield Healer";
|
|
|
|
default:
|
|
return roleType;
|
|
}
|
|
}
|
|
|
|
|
|
function formatParty(party) {
|
|
const roles = [];
|
|
|
|
if (party.Tanks > 0) {
|
|
roles.push(
|
|
`${party.Tanks} Tank${party.Tanks !== 1 ? "s" : ""}`
|
|
);
|
|
}
|
|
|
|
if (party.Healers > 0) {
|
|
roles.push(
|
|
`${party.Healers} Healer${party.Healers !== 1 ? "s" : ""}`
|
|
);
|
|
}
|
|
|
|
if (party.DPS > 0) {
|
|
roles.push(`${party.DPS} DPS`);
|
|
}
|
|
|
|
if (!roles.length) {
|
|
return `${party.Size} Player${party.Size !== 1 ? "s" : ""}`;
|
|
}
|
|
|
|
return `${party.Size} Players\n${roles.join(" • ")}`;
|
|
} |