Files

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(" • ")}`;
}