Initial Commit
This commit is contained in:
98
packages/eve-bot/src/commands/search/_old_ItemLookup.ts
Normal file
98
packages/eve-bot/src/commands/search/_old_ItemLookup.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { ButtonStyle, ChatInputCommandInteraction, CommandInteraction, MessageFlags } from 'discord.js';
|
||||
import { typeSearch } from './search';
|
||||
import { createActionRow, useNavigation, type ResumeableInteraction } from '@lib/discord';
|
||||
import { getTypeBlueprints, getTypeSchematics, getTypeSkills, getTypeVariants, typeHasAttributes, type Type } from 'star-kitten-lib/eve';
|
||||
import { mainPage, attributesPage, fittingPage, skillsPage, industryPage } from './pages';
|
||||
|
||||
export enum PageKey {
|
||||
MAIN = 'main',
|
||||
ATTRIBUTES = 'attributes',
|
||||
FITTING = 'fitting',
|
||||
SKILLS = 'skills',
|
||||
INDUSTRY = 'industry',
|
||||
}
|
||||
|
||||
export interface TypeContext {
|
||||
type: Type;
|
||||
interaction: CommandInteraction;
|
||||
disabled?: boolean;
|
||||
buildButtonRow: (key: string, context: TypeContext) => any[];
|
||||
}
|
||||
|
||||
export interface ItemLookupOptions {
|
||||
ephemeral: boolean;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export async function itemLookup(interaction: ChatInputCommandInteraction, options: ItemLookupOptions, saveResume: (messageId: string) => void) {
|
||||
const deferred = await interaction.deferReply({ flags: options.ephemeral ? MessageFlags.Ephemeral : undefined });
|
||||
const name = interaction.options.getString('name') ?? '';
|
||||
|
||||
await lookup(deferred.interaction as any, options, name, interaction.guild?.preferredLocale, saveResume);
|
||||
}
|
||||
|
||||
export async function resumeItemLookup(interaction: ResumeableInteraction, options: ItemLookupOptions, name: string, saveResume: (messageId: string) => void) {
|
||||
await lookup(interaction, options, name, interaction.guild?.preferredLocale, saveResume);
|
||||
}
|
||||
|
||||
|
||||
async function lookup(messageOrInteraction: ResumeableInteraction | ChatInputCommandInteraction, options: ItemLookupOptions, name: string, locale: string, saveResume: (messageId: string) => void) {
|
||||
const type = await typeSearch(name);
|
||||
if (!type) {
|
||||
if (messageOrInteraction instanceof ChatInputCommandInteraction) {
|
||||
messageOrInteraction.editReply({ content: `${options.type} ${name} not found` });
|
||||
} else {
|
||||
messageOrInteraction.message.edit({ content: `${options.type} ${name} not found` });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const updateContext = async (key: string, context: TypeContext) => {
|
||||
return Promise.resolve(key);
|
||||
};
|
||||
|
||||
const buildButtonRow = (key: string, context: TypeContext) => {
|
||||
return createActionRow(
|
||||
{ customId: PageKey.MAIN, label: 'Main', style: ButtonStyle.Primary, disabled: key === PageKey.MAIN },
|
||||
typeHasAttributes(context.type) && {
|
||||
customId: PageKey.ATTRIBUTES,
|
||||
label: 'Attributes',
|
||||
style: ButtonStyle.Primary,
|
||||
// disabled: key === PageKey.ATTRIBUTES,
|
||||
},
|
||||
typeHasAttributes(context.type) && {
|
||||
customId: PageKey.FITTING,
|
||||
label: `Fitting${getTypeVariants(context.type).length > 0 ? ' | Variants' : ''}`,
|
||||
style: ButtonStyle.Primary,
|
||||
// disabled: key === PageKey.FITTING,
|
||||
},
|
||||
getTypeSkills(context.type)?.length > 0 && {
|
||||
customId: PageKey.SKILLS,
|
||||
label: 'Skills',
|
||||
style: ButtonStyle.Primary,
|
||||
// disabled: key === PageKey.SKILLS,
|
||||
},
|
||||
(getTypeBlueprints(context.type)?.length > 0 || getTypeSchematics(context.type)?.length > 0) && {
|
||||
customId: PageKey.INDUSTRY,
|
||||
label: 'Industry',
|
||||
style: ButtonStyle.Primary,
|
||||
// disabled: key === PageKey.INDUSTRY,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
useNavigation({
|
||||
interaction: messageOrInteraction,
|
||||
key: 'main',
|
||||
pages: [
|
||||
mainPage(PageKey.MAIN, locale),
|
||||
attributesPage(PageKey.ATTRIBUTES, locale),
|
||||
fittingPage(PageKey.FITTING, locale),
|
||||
skillsPage(PageKey.SKILLS, locale),
|
||||
industryPage(PageKey.INDUSTRY, locale),
|
||||
],
|
||||
context: { type, buildButtonRow, interaction: messageOrInteraction },
|
||||
updateContext,
|
||||
saveResume,
|
||||
});
|
||||
}
|
||||
189
packages/eve-bot/src/commands/search/pages/_old_attributes.ts
Normal file
189
packages/eve-bot/src/commands/search/pages/_old_attributes.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import { renderThreeColumns, type Page } from '@lib/discord';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import {
|
||||
attributeOrdering,
|
||||
type Type,
|
||||
CommonAttribute,
|
||||
eveRefLink,
|
||||
getTypeIconUrl,
|
||||
typeHasAnyAttribute,
|
||||
getGroup,
|
||||
typeGetAttribute,
|
||||
getUnit,
|
||||
renderUnit,
|
||||
} from 'star-kitten-lib/eve';
|
||||
import type { PageKey, TypeContext } from '../_old_ItemLookup';
|
||||
|
||||
export function attributesPage(key: PageKey.ATTRIBUTES, locale: string = 'en'): Page<TypeContext> {
|
||||
return {
|
||||
key,
|
||||
content: async (context: TypeContext) => {
|
||||
const type = context.type;
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(type.name[locale] ?? type.name.en)
|
||||
.setThumbnail(getTypeIconUrl(type))
|
||||
.setURL(eveRefLink(type.type_id))
|
||||
.setFooter({ text: `id: ${type.type_id}` })
|
||||
.setColor('Green');
|
||||
|
||||
const embeds = [embed];
|
||||
const fields = [];
|
||||
|
||||
if (type.dogma_attributes) {
|
||||
const useOrders =
|
||||
getGroup(type.group_id).category_id === 11
|
||||
? attributeOrdering['11']
|
||||
: getGroup(type.group_id).category_id === 87
|
||||
? attributeOrdering['87']
|
||||
: attributeOrdering.default;
|
||||
|
||||
Object.entries(useOrders).map((pair) => {
|
||||
const [attributePath, attrs] = pair;
|
||||
const combined = attrs['groupedAttributes']
|
||||
? attrs.normalAttributes.concat(...(attrs['groupedAttributes']?.map(([name, id]) => id) ?? []))
|
||||
: attrs.normalAttributes;
|
||||
if (!typeHasAnyAttribute(type, combined)) return;
|
||||
const split = attributePath.split('/');
|
||||
const name = split[split.length - 1];
|
||||
fields.push(
|
||||
...renderThreeColumns(
|
||||
name,
|
||||
getAttributeNames(type, combined, locale),
|
||||
[],
|
||||
getAttributeValues(type, combined, locale),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// for (const [name, attrs] of Object.entries(attrMap)) {
|
||||
// if (!type.hasAnyAttribute(attrs)) continue;
|
||||
// if (name === 'Cargo | Drones' && type.group.category.category_id === CommonCategory.MODULE) continue;
|
||||
// fields.push(...renderThreeColumns(
|
||||
// name,
|
||||
// getAttributeNames(type, attrs, locale),
|
||||
// [],
|
||||
// getAttributeValues(type, attrs, locale)
|
||||
// ));
|
||||
// }
|
||||
|
||||
// there is a max number of 24 fields per embed
|
||||
embed.addFields(fields.splice(0, 24));
|
||||
while (fields.length > 0) {
|
||||
embeds.push(new EmbedBuilder().addFields(fields.splice(0, 24)));
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'page',
|
||||
embeds,
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const structureAttrs = [
|
||||
CommonAttribute.StructureHitpoints,
|
||||
CommonAttribute.Mass,
|
||||
CommonAttribute.Volume,
|
||||
CommonAttribute.InertiaModifier,
|
||||
CommonAttribute.StructureEMResistance,
|
||||
CommonAttribute.StructureThermalResistance,
|
||||
CommonAttribute.StructureKineticResistance,
|
||||
CommonAttribute.StructureExplosiveResistance,
|
||||
];
|
||||
|
||||
const droneAttrs = [CommonAttribute.CargoCapacity, CommonAttribute.DroneBandwidth, CommonAttribute.DroneCapacity];
|
||||
|
||||
const armorAttrs = [
|
||||
CommonAttribute.ArmorHitpoints,
|
||||
CommonAttribute.ArmorEMResistance,
|
||||
CommonAttribute.ArmorThermalResistance,
|
||||
CommonAttribute.ArmorKineticResistance,
|
||||
CommonAttribute.ArmorExplosiveResistance,
|
||||
];
|
||||
|
||||
const shieldAttrs = [
|
||||
CommonAttribute.ShieldCapacity,
|
||||
CommonAttribute.ShieldRechargeTime,
|
||||
CommonAttribute.ShieldEMResistance,
|
||||
CommonAttribute.ShieldThermalResistance,
|
||||
CommonAttribute.ShieldKineticResistance,
|
||||
CommonAttribute.ShieldExplosiveResistance,
|
||||
];
|
||||
|
||||
const elResAttrs = [
|
||||
CommonAttribute.CapacitorWarfareResistance,
|
||||
CommonAttribute.StasisWebifierResistance,
|
||||
CommonAttribute.WeaponDisruptionResistance,
|
||||
];
|
||||
|
||||
const capAttrs = [CommonAttribute.CapacitorCapacity, CommonAttribute.CapacitorRechargeTime];
|
||||
|
||||
const targetAttrs = [
|
||||
CommonAttribute.MaxTargetRange,
|
||||
CommonAttribute.MaxLockedTargets,
|
||||
CommonAttribute.SignatureRadius,
|
||||
CommonAttribute.ScanResolution,
|
||||
CommonAttribute.RadarSensorStrength,
|
||||
CommonAttribute.MagnetometricSensorStrength,
|
||||
CommonAttribute.GravimetricSensorStrength,
|
||||
CommonAttribute.LadarSensorStrength,
|
||||
];
|
||||
|
||||
const jumpAttrs = [
|
||||
CommonAttribute.JumpDriveCapacitorNeed,
|
||||
CommonAttribute.MaxJumpRange,
|
||||
CommonAttribute.JumpDriveFuelNeed,
|
||||
CommonAttribute.JumpDriveConsumptionAmount,
|
||||
CommonAttribute.FuelBayCapacity,
|
||||
CommonAttribute.ConduitJumpConsumptionAmount,
|
||||
CommonAttribute.COnduitJumpPassengerCapacity,
|
||||
];
|
||||
|
||||
const propAttrs = [CommonAttribute.MaxVelocity, CommonAttribute.WarpSpeed];
|
||||
|
||||
const weaponAttrs = [
|
||||
CommonAttribute.DamageMultiplier,
|
||||
CommonAttribute.AccuracyFalloff,
|
||||
CommonAttribute.OptimalRange,
|
||||
CommonAttribute.RateOfFire,
|
||||
CommonAttribute.TrackingSpeed,
|
||||
CommonAttribute.ReloadTime,
|
||||
CommonAttribute.ActivationTime,
|
||||
CommonAttribute.ChargeSize,
|
||||
CommonAttribute.UsedWithCharge1,
|
||||
CommonAttribute.UsedWithCharge2,
|
||||
];
|
||||
|
||||
const eWarAttrs = [CommonAttribute.MaxVelocityBonus];
|
||||
|
||||
const attrMap = {
|
||||
Weapon: weaponAttrs,
|
||||
Structure: structureAttrs,
|
||||
Armor: armorAttrs,
|
||||
Shield: shieldAttrs,
|
||||
'Cargo | Drones': droneAttrs,
|
||||
'Electronic Resistances': elResAttrs,
|
||||
Capacitor: capAttrs,
|
||||
Targeting: targetAttrs,
|
||||
'Jump Drive Systems': jumpAttrs,
|
||||
Propulsion: propAttrs,
|
||||
'Electronic Warfare': eWarAttrs,
|
||||
};
|
||||
|
||||
export function getAttributeNames(type: Type, ids: number[], locale: string = 'en') {
|
||||
return ids
|
||||
.map((id) => typeGetAttribute(type, id))
|
||||
.filter((attr) => !!attr)
|
||||
.map((attr) => `> ${attr.attribute.display_name[locale] ?? attr.attribute.display_name.en}`);
|
||||
}
|
||||
|
||||
export function getAttributeValues(type: Type, ids: number[], locale: string = 'en') {
|
||||
return ids
|
||||
.map((id) => typeGetAttribute(type, id))
|
||||
.filter((attr) => !!attr)
|
||||
.map(
|
||||
(attr) => `**${attr.attribute.unit_id ? renderUnit(getUnit(attr.attribute.unit_id), attr.value) : attr.value}**`,
|
||||
);
|
||||
}
|
||||
86
packages/eve-bot/src/commands/search/pages/_old_fitting.ts
Normal file
86
packages/eve-bot/src/commands/search/pages/_old_fitting.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import { renderThreeColumns, type Page } from '@lib/discord';
|
||||
import { getAttributeNames, getAttributeValues } from './_old_attributes';
|
||||
import { PageKey, type TypeContext } from '../_old_ItemLookup';
|
||||
import {
|
||||
eveRefLink,
|
||||
getTypeIconUrl,
|
||||
getTypeVariants,
|
||||
typeHasAnyAttribute,
|
||||
CommonAttribute,
|
||||
renderTypeEveRefLink,
|
||||
} from 'star-kitten-lib/eve';
|
||||
|
||||
export function fittingPage(key: string = PageKey.FITTING, locale: string = 'en'): Page<TypeContext> {
|
||||
return {
|
||||
key,
|
||||
content: async (context: TypeContext) => {
|
||||
const type = context.type;
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(type.name[locale] ?? type.name.en)
|
||||
.setThumbnail(getTypeIconUrl(type))
|
||||
.setURL(eveRefLink(type.type_id))
|
||||
.setFooter({ text: `id: ${type.type_id}` })
|
||||
.setColor('Green');
|
||||
|
||||
const fields = [];
|
||||
|
||||
for (const [name, attrs] of Object.entries(attrMap)) {
|
||||
if (!typeHasAnyAttribute(type, attrs)) continue;
|
||||
fields.push(
|
||||
...renderThreeColumns(
|
||||
name,
|
||||
getAttributeNames(type, attrs, locale),
|
||||
[],
|
||||
getAttributeValues(type, attrs, locale),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// get variants
|
||||
{
|
||||
if (getTypeVariants(type).length > 0) {
|
||||
getTypeVariants(type).map((v) => {
|
||||
fields.push({
|
||||
name: `${v.metaGroup.name[locale] ?? v.metaGroup.name.en} variants`,
|
||||
value: v.types.map((t) => renderTypeEveRefLink(t, locale)).join('\n'),
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
return {
|
||||
type: 'page',
|
||||
embeds: [embed.setDescription('This item does not have any fitting attributes.')],
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
}
|
||||
|
||||
embed.addFields(fields);
|
||||
return {
|
||||
type: 'page',
|
||||
embeds: [embed],
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const shipOutputAttrs = [CommonAttribute.PowergridOutput, CommonAttribute.CPUOutput];
|
||||
|
||||
const hardpointAttrs = [CommonAttribute.TurretHardpoints, CommonAttribute.LauncherHardpoints];
|
||||
|
||||
const moduleAttrs = [CommonAttribute.HighSlots, CommonAttribute.MediumSlots, CommonAttribute.LowSlots];
|
||||
|
||||
const rigAttrs = [CommonAttribute.RigSlots, CommonAttribute.RigSize, CommonAttribute.Calibration];
|
||||
|
||||
const moduleFittingAttrs = [CommonAttribute.CPUUsage, CommonAttribute.PowergridUsage, CommonAttribute.ActivationCost];
|
||||
|
||||
const attrMap = {
|
||||
'Ship Output': shipOutputAttrs,
|
||||
Hardpoints: hardpointAttrs,
|
||||
Modules: moduleAttrs,
|
||||
Rigs: rigAttrs,
|
||||
Fitting: moduleFittingAttrs,
|
||||
};
|
||||
115
packages/eve-bot/src/commands/search/pages/_old_industry.ts
Normal file
115
packages/eve-bot/src/commands/search/pages/_old_industry.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { renderThreeColumns, type Page } from '@lib/discord';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import {
|
||||
getBlueprint,
|
||||
type ManufacturingActivity,
|
||||
eveRefLink,
|
||||
getType,
|
||||
getSchematic,
|
||||
getTypeIconUrl,
|
||||
getTypeBlueprints,
|
||||
getTypeSchematics,
|
||||
} from 'star-kitten-lib/eve';
|
||||
import type { PageKey, TypeContext } from '../_old_ItemLookup';
|
||||
|
||||
export function industryPage(key: PageKey.INDUSTRY, locale: string = 'en'): Page<TypeContext> {
|
||||
return {
|
||||
key,
|
||||
content: async (context: TypeContext) => {
|
||||
const type = context.type;
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(type.name[locale] ?? type.name.en)
|
||||
.setThumbnail(getTypeIconUrl(type))
|
||||
.setURL(eveRefLink(type.type_id))
|
||||
.setFooter({ text: `id: ${type.type_id}` })
|
||||
.setColor('Green');
|
||||
|
||||
let description = '';
|
||||
|
||||
const fields = [];
|
||||
const bps = getTypeBlueprints(type);
|
||||
if (bps.length > 0) {
|
||||
bps.map((bp) => {
|
||||
const type = bp.blueprint;
|
||||
const blueprint = getBlueprint(bp.blueprint.type_id);
|
||||
const activity = blueprint.activities[bp.activity];
|
||||
|
||||
description += `### Blueprint\n`;
|
||||
description += `[${type.name[locale] ?? type.name.en}](${eveRefLink(type.type_id)})\n`;
|
||||
// fields.push({
|
||||
// name: 'Blueprints',
|
||||
// value: bps.map(bp => {
|
||||
// const type = bp.blueprint;
|
||||
// return `[${type.name[locale] ?? type.name.en}](${type.eveRefLink})`;
|
||||
// })
|
||||
// });
|
||||
|
||||
if (activity['materials']) {
|
||||
const manufacturing = activity as ManufacturingActivity;
|
||||
if (manufacturing.materials) {
|
||||
description += '### Materials\n```';
|
||||
description += Object.values(manufacturing.materials)
|
||||
.map((m) => {
|
||||
const t = getType(m.type_id);
|
||||
return `${t.name[locale] ?? t.name.en} ${m.quantity}`;
|
||||
})
|
||||
.join('\n');
|
||||
description += '```';
|
||||
|
||||
// fields.push(...renderThreeColumns(
|
||||
// 'Materials',
|
||||
// Object.values(manufacturing.materials).map(m => {
|
||||
// const t = getType(m.type_id);
|
||||
// return `[${t.name[locale] ?? t.name.en}](${t.eveRefLink})`;
|
||||
// }),
|
||||
// [],
|
||||
// Object.values(manufacturing.materials).map(m => {
|
||||
// const t = getType(m.type_id);
|
||||
// return `x**${m.quantity}**`;
|
||||
// }),
|
||||
// ));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const schematics = getTypeSchematics(type);
|
||||
if (schematics.length > 0) {
|
||||
schematics.map((type) => {
|
||||
const schematic = getSchematic(type.type_id);
|
||||
|
||||
fields.push({
|
||||
name: 'Schematic',
|
||||
value: `[${type.name[locale] ?? type.name.en}](${eveRefLink(type.type_id)})`,
|
||||
});
|
||||
|
||||
fields.push(
|
||||
...renderThreeColumns(
|
||||
'Materials',
|
||||
Object.values(schematic.materials).map((m) => {
|
||||
const t = getType(m.type_id);
|
||||
return `[${t.name[locale] ?? t.name.en}](${eveRefLink(t.type_id)})`;
|
||||
}),
|
||||
[],
|
||||
Object.values(schematic.materials).map((m) => {
|
||||
return `x**${m.quantity}**`;
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (description === '') {
|
||||
description = 'No blueprints or schematics found';
|
||||
}
|
||||
|
||||
embed.addFields(fields);
|
||||
embed.setDescription(description);
|
||||
return {
|
||||
type: 'page',
|
||||
embeds: [embed],
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
134
packages/eve-bot/src/commands/search/pages/_old_skills.ts
Normal file
134
packages/eve-bot/src/commands/search/pages/_old_skills.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
import { coloredText, renderThreeColumns, WHITE_SPACE, type Page } from '@lib/discord';
|
||||
import { EmbedBuilder } from 'discord.js';
|
||||
import type { Type } from 'star-kitten-lib/eve';
|
||||
import { eveRefLink, getCharacterSkills, getGroup, getTypeIconUrl, getTypeSkills } from 'star-kitten-lib/eve';
|
||||
import { CommonCategory } from 'star-kitten-lib/eve';
|
||||
import type { PageKey, TypeContext } from '../_old_ItemLookup';
|
||||
import { CharacterHelper, UserHelper } from 'star-kitten-lib/db';
|
||||
|
||||
function canUseText(type: Type) {
|
||||
const category = getGroup(type.group_id).category_id;
|
||||
switch (category) {
|
||||
case CommonCategory.SHIP:
|
||||
return 'fly this ship';
|
||||
case CommonCategory.DRONE:
|
||||
return 'use this drone';
|
||||
case CommonCategory.MODULE:
|
||||
return 'use this module';
|
||||
default:
|
||||
return 'use this item';
|
||||
}
|
||||
}
|
||||
|
||||
export function skillsPage(key: PageKey.SKILLS, locale: string = 'en'): Page<TypeContext> {
|
||||
return {
|
||||
key: 'skills',
|
||||
content: async (context: TypeContext) => {
|
||||
const type = context.type;
|
||||
|
||||
if (!type.required_skills || type.required_skills.length === 0) {
|
||||
return {
|
||||
type: 'page',
|
||||
embeds: [
|
||||
new EmbedBuilder()
|
||||
.setTitle(type.name[locale] ?? type.name.en)
|
||||
.setDescription('This item does not require any skills to use.')
|
||||
.setThumbnail(getTypeIconUrl(type))
|
||||
.setURL(eveRefLink(type.type_id))
|
||||
.setFooter({ text: `id: ${type.type_id}` })
|
||||
.setColor('Green'),
|
||||
],
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
}
|
||||
|
||||
const user = UserHelper.findByDiscordId(context.interaction.user.id);
|
||||
const main = CharacterHelper.find(user.mainCharacterID);
|
||||
const skills = main && (await getCharacterSkills(main));
|
||||
const characterSkills: { [key: number]: number } =
|
||||
skills && skills?.skills.reduce((acc, skill) => ({ ...acc, [skill.skill_id]: skill.trained_skill_level }), {});
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(type.name[locale] ?? type.name.en)
|
||||
.setThumbnail(getTypeIconUrl(type))
|
||||
.setURL(eveRefLink(type.type_id))
|
||||
.setFooter({ text: `id: ${type.type_id} -- ◼ = trained | ☒ = required but not trained` });
|
||||
|
||||
let description = '';
|
||||
|
||||
description += '### Required Skills\n```\n';
|
||||
description += getTypeSkills(type)
|
||||
.map((skillLevel) => `${skillLevel.skill.name[locale] ?? skillLevel.skill.name.en} ${skillLevel.level}`)
|
||||
.join('\n');
|
||||
description += '```';
|
||||
|
||||
let canFly = true;
|
||||
if (characterSkills) {
|
||||
if (getTypeSkills(type).every((skillLevel) => characterSkills[skillLevel.skill.type_id] >= skillLevel.level)) {
|
||||
description += coloredText(`${main.name} can ${canUseText(type)}`, 'green');
|
||||
canFly = true;
|
||||
} else {
|
||||
description += coloredText(`${main.name} cannot ${canUseText(type)}`, 'red');
|
||||
canFly = false;
|
||||
}
|
||||
}
|
||||
embed.setDescription(description);
|
||||
embed.addFields(
|
||||
renderThreeColumns('', getSkillNames(type, locale), [], getSkillLevels(type, characterSkills).map(renderLevel)),
|
||||
);
|
||||
embed.setColor(canFly ? 'Green' : 'Red');
|
||||
return {
|
||||
type: 'page',
|
||||
embeds: [embed],
|
||||
components: [context.buildButtonRow(key, context)],
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getSkillNames(type: Type, locale: string, depth: number = 0) {
|
||||
let spacing = '';
|
||||
for (let i = 0; i < depth; ++i) {
|
||||
spacing += WHITE_SPACE;
|
||||
}
|
||||
let names: string[] = [];
|
||||
getTypeSkills(type).forEach((skillLevel) => {
|
||||
names.push(
|
||||
`${spacing}[${skillLevel.skill.name[locale] ?? skillLevel.skill.name.en}](${skillLevel.skill.eveRefLink})`,
|
||||
);
|
||||
if (skillLevel.skill.skills.length > 0) {
|
||||
names.push(...getSkillNames(skillLevel.skill, locale, depth + 1));
|
||||
}
|
||||
});
|
||||
return names;
|
||||
}
|
||||
|
||||
interface RequiredLevel {
|
||||
required: number;
|
||||
have: number;
|
||||
}
|
||||
|
||||
// skills is a map of skill_id to trained_skill_level
|
||||
function getSkillLevels(type: Type, skills?: { [key: number]: number }): RequiredLevel[] {
|
||||
let levels: RequiredLevel[] = [];
|
||||
getTypeSkills(type).forEach((skillLevel) => {
|
||||
levels.push({
|
||||
required: skillLevel.level,
|
||||
have: skills ? skills[skillLevel.skill.type_id] || 0 : 0,
|
||||
});
|
||||
if (skillLevel.skill.skills.length > 0) {
|
||||
levels.push(...getSkillLevels(skillLevel.skill, skills));
|
||||
}
|
||||
});
|
||||
return levels;
|
||||
}
|
||||
|
||||
function renderLevel(level: RequiredLevel) {
|
||||
let str = '';
|
||||
for (let i = 1; i <= 5; ++i) {
|
||||
str += i <= level.required ? (level.have >= i ? '◼' : '☒') : level.have >= i ? '◼' : '▢';
|
||||
// shapes to test with:
|
||||
// '■' '▰' '▱' '▨' '▧' '◼' '▦' '▩' '▥' '▤' '▣' '▢' '◪' '◫' '◩' '◨' '◧'
|
||||
}
|
||||
return str + `${WHITE_SPACE}${level.have}/${level.required}`;
|
||||
}
|
||||
140
packages/eve-bot/src/commands/search/pages/attributes.ts
Normal file
140
packages/eve-bot/src/commands/search/pages/attributes.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { renderSubroutes, type Page } from '@star-kitten/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import {
|
||||
ButtonStyle,
|
||||
createContainer,
|
||||
createSection,
|
||||
createSeparator,
|
||||
createTextDisplay,
|
||||
createThumbnail,
|
||||
Padding,
|
||||
} from '@star-kitten/discord/components';
|
||||
import {
|
||||
getGroup,
|
||||
getType,
|
||||
getUnit,
|
||||
renderUnit,
|
||||
typeGetAttribute,
|
||||
typeHasAnyAttribute,
|
||||
type Type,
|
||||
} from '@star-kitten/eve/models';
|
||||
import { attributeOrdering } from '@star-kitten/eve';
|
||||
import { searchActionRow } from './helpers';
|
||||
import { toTitleCase } from '@star-kitten/util/text.js';
|
||||
|
||||
enum Images {
|
||||
ATTRIBUTES = 'https://iili.io/KTbaMR2.md.webp',
|
||||
DEFENSES = 'https://iili.io/KTbSVoX.md.webp',
|
||||
FITTING = 'https://iili.io/KufiFYG.md.webp',
|
||||
FACILITIES = 'https://iili.io/KufikGt.md.webp',
|
||||
}
|
||||
|
||||
const attributeCategoryMap = {
|
||||
structure: 'UI/Fitting/Structure',
|
||||
armor: 'UI/Common/Armor',
|
||||
shield: 'UI/Common/Shield',
|
||||
ewar: 'UI/Common/EWarResistances',
|
||||
capacitor: 'UI/Fitting/FittingWindow/Capacitor',
|
||||
targeting: 'UI/Fitting/FittingWindow/Targeting',
|
||||
facilities: 'UI/InfoWindow/SharedFacilities',
|
||||
fighters: 'UI/InfoWindow/FighterFacilities',
|
||||
on_death: 'UI/InfoWindow/OnDeath',
|
||||
jump_drive: 'UI/InfoWindow/JumpDriveSystems',
|
||||
propulsion: 'UI/Compare/Propulsion',
|
||||
};
|
||||
|
||||
const groupedCategories = [
|
||||
// defenses
|
||||
['shield', 'armor', 'structure', 'ewar'],
|
||||
// fittings
|
||||
['capacitor', 'targeting', 'propulsion'],
|
||||
// facilities
|
||||
['facilities', 'fighters', 'on_death', 'jump_drive'],
|
||||
];
|
||||
|
||||
function getAttributeOrdering(type: Type) {
|
||||
const group = getGroup(type.group_id);
|
||||
switch (group.category_id) {
|
||||
case 11:
|
||||
return attributeOrdering['11'];
|
||||
case 87:
|
||||
return attributeOrdering['87'];
|
||||
default:
|
||||
return attributeOrdering.default;
|
||||
}
|
||||
}
|
||||
|
||||
const bannerMap = {
|
||||
shield: Images.DEFENSES,
|
||||
armor: Images.DEFENSES,
|
||||
structure: Images.DEFENSES,
|
||||
ewar: Images.DEFENSES,
|
||||
|
||||
capacitor: Images.FITTING,
|
||||
targeting: Images.FITTING,
|
||||
propulsion: Images.FITTING,
|
||||
|
||||
facilities: Images.FACILITIES,
|
||||
fighters: Images.FACILITIES,
|
||||
on_death: Images.FACILITIES,
|
||||
jump_drive: Images.FACILITIES,
|
||||
};
|
||||
|
||||
const page: Page<SearchState> = {
|
||||
key: 'attributes',
|
||||
render: (context) => {
|
||||
const type = getType(context.state.data.type_id);
|
||||
const ordering = getAttributeOrdering(type);
|
||||
|
||||
return {
|
||||
components: [
|
||||
createContainer(
|
||||
{},
|
||||
createSection(
|
||||
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
createTextDisplay(`# [${type.name.en}](https://everef.net/types/${type.type_id})\n## Attributes`),
|
||||
),
|
||||
...renderSubroutes(
|
||||
context,
|
||||
'attributes',
|
||||
groupedCategories.map((group) =>
|
||||
group.map((cat) => {
|
||||
const attrCat = ordering[attributeCategoryMap[cat]];
|
||||
const attrs = attrCat.groupedCategories
|
||||
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
|
||||
: attrCat.normalAttributes;
|
||||
if (!typeHasAnyAttribute(type, attrs)) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
label: toTitleCase(cat.replace('_', ' ')),
|
||||
value: cat,
|
||||
banner: bannerMap[cat],
|
||||
};
|
||||
}),
|
||||
),
|
||||
(currentRoute) => {
|
||||
const lines: string[] = [];
|
||||
const attrCat = ordering[attributeCategoryMap[currentRoute]];
|
||||
const attrs = attrCat.groupedCategories
|
||||
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
|
||||
: attrCat.normalAttributes;
|
||||
attrs.map((attrId) => {
|
||||
const attr = typeGetAttribute(type, attrId);
|
||||
if (!attr) return;
|
||||
const unit = attr.attribute.unit_id ? renderUnit(getUnit(attr.attribute.unit_id), attr.value) : '';
|
||||
lines.push(`${attr.attribute.display_name.en.padEnd(24)} ${unit}`);
|
||||
});
|
||||
return createTextDisplay('```\n' + lines.join('\n') + '\n```');
|
||||
},
|
||||
{ style: ButtonStyle.SECONDARY },
|
||||
),
|
||||
createSeparator(Padding.LARGE),
|
||||
searchActionRow('attributes'),
|
||||
),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default page;
|
||||
11
packages/eve-bot/src/commands/search/pages/helpers.ts
Normal file
11
packages/eve-bot/src/commands/search/pages/helpers.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createActionRow, createButton } from '@star-kitten/discord/components';
|
||||
|
||||
export function searchActionRow(pageKey: string) {
|
||||
return createActionRow(
|
||||
createButton('Main', 'main', { disabled: pageKey === 'main' }),
|
||||
createButton('Attributes', 'attributes', { disabled: pageKey === 'attributes' }),
|
||||
createButton('Fittings', 'fittings', { disabled: pageKey === 'fittings' }),
|
||||
createButton('Skills', 'skills', { disabled: pageKey === 'skills' }),
|
||||
createButton('Industry', 'industry', { disabled: pageKey === 'industry' }),
|
||||
);
|
||||
}
|
||||
3
packages/eve-bot/src/commands/search/pages/index.ts
Normal file
3
packages/eve-bot/src/commands/search/pages/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './_old_industry';
|
||||
export * from './main';
|
||||
export * from './attributes';
|
||||
89
packages/eve-bot/src/commands/search/pages/main.ts
Normal file
89
packages/eve-bot/src/commands/search/pages/main.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { Page } from '@star-kitten/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import {
|
||||
createContainer,
|
||||
createMediaGallery,
|
||||
createSection,
|
||||
createTextDisplay,
|
||||
createThumbnail,
|
||||
createURLButton,
|
||||
} from '@star-kitten/discord/components';
|
||||
import { getRoleBonuses, getSkillBonuses, getType } from '@star-kitten/eve/models/type.js';
|
||||
import { cleanText } from '@star-kitten/eve/utils/markdown.js';
|
||||
import { typeSearch } from '@star-kitten/eve/utils/typeSearch.js';
|
||||
import { isApplicationCommand } from '@star-kitten/discord';
|
||||
import { fetchPrice } from '@star-kitten/eve/third-party/evetycoon.js';
|
||||
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
|
||||
import { searchActionRow } from './helpers';
|
||||
|
||||
const page: Page<SearchState> = {
|
||||
key: 'main',
|
||||
render: async (context) => {
|
||||
if (!context.state.data.type_id && isApplicationCommand(context.interaction)) {
|
||||
const typeName = context.interaction.data.options?.find((opt) => opt.name === 'name')?.value;
|
||||
const found = await typeSearch(typeName as string);
|
||||
|
||||
if (!found) {
|
||||
return {
|
||||
components: [createTextDisplay(`No item found for: ${typeName}`)],
|
||||
};
|
||||
}
|
||||
|
||||
context.state.data.type_id = found.type_id;
|
||||
}
|
||||
|
||||
const type = getType(context.state.data.type_id);
|
||||
|
||||
const skillBonuses = getSkillBonuses(type);
|
||||
const roleBonuses = getRoleBonuses(type);
|
||||
const price = await fetchPrice(type.type_id);
|
||||
|
||||
return {
|
||||
components: [
|
||||
createContainer(
|
||||
{},
|
||||
createSection(
|
||||
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
createTextDisplay(`
|
||||
# [${type.name.en}](https://everef.net/types/${type.type_id})
|
||||
|
||||
${skillBonuses
|
||||
.map((bonus) => {
|
||||
return `## Bonus per level of ${bonus.skill.name.en}
|
||||
${bonus.bonuses
|
||||
.sort((a, b) => a.importance - b.importance)
|
||||
.map((b) => `${b.bonus}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
|
||||
.join('\n')}`;
|
||||
})
|
||||
.join('\n')}
|
||||
${
|
||||
roleBonuses.length > 0
|
||||
? `\n## Role Bonuses
|
||||
${roleBonuses
|
||||
.sort((a, b) => a.importance - b.importance)
|
||||
.map((b) => `${b.bonus ?? ''}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
|
||||
.join('\n')}`
|
||||
: ''
|
||||
}
|
||||
`),
|
||||
),
|
||||
createMediaGallery({
|
||||
url: 'https://iili.io/KTPCFRt.md.webp',
|
||||
}),
|
||||
// createSeparator(Padding.LARGE),
|
||||
createSection(
|
||||
createURLButton('View on EVE Tycoon', `https://evetycoon.com/market/${type.type_id}`),
|
||||
createTextDisplay(
|
||||
`## Buy: ${price ? formatNumberToShortForm(price.buyAvgFivePercent) : '--'} ISK
|
||||
## Sell: ${price ? formatNumberToShortForm(price.sellAvgFivePercent) : '--'} ISK`,
|
||||
),
|
||||
),
|
||||
createTextDisplay(`-# Type Id: ${type.type_id}`),
|
||||
searchActionRow('main'),
|
||||
),
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export default page;
|
||||
69
packages/eve-bot/src/commands/search/search.command.ts
Normal file
69
packages/eve-bot/src/commands/search/search.command.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
createChatCommand,
|
||||
isAutocomplete,
|
||||
stringOption,
|
||||
type CommandContext,
|
||||
type ExecutableInteraction,
|
||||
} from '@star-kitten/discord';
|
||||
import { usePages } from '@star-kitten/discord/pages';
|
||||
import { initializeTypeSearch, typeSearchAutoComplete } from '@star-kitten/eve/utils/typeSearch.js';
|
||||
|
||||
import main from './pages/main';
|
||||
import attributes from './pages/attributes';
|
||||
|
||||
let now = Date.now();
|
||||
console.debug('Initializing type search...');
|
||||
await initializeTypeSearch().catch((e) => {
|
||||
console.error('Failed to initialize type search', e);
|
||||
process.exit(1);
|
||||
});
|
||||
console.debug(`Type search initialized. Took ${Date.now() - now}ms`);
|
||||
|
||||
export interface SearchState {
|
||||
type_id: number;
|
||||
}
|
||||
|
||||
export default createChatCommand(
|
||||
{
|
||||
name: 'search',
|
||||
description: 'Search for a type',
|
||||
options: [
|
||||
stringOption({
|
||||
name: 'name',
|
||||
description: 'The type name to search for',
|
||||
autocomplete: true,
|
||||
required: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
execute,
|
||||
);
|
||||
|
||||
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
|
||||
if (isAutocomplete(interaction)) {
|
||||
const focusedOption = interaction.data.options?.find((opt) => opt.focused);
|
||||
if (focusedOption?.name === 'name') {
|
||||
const value = focusedOption.value as string;
|
||||
const results = await typeSearchAutoComplete(value);
|
||||
if (results) {
|
||||
await interaction.result(results);
|
||||
} else {
|
||||
await interaction.result([]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
usePages<SearchState>(
|
||||
{
|
||||
pages: {
|
||||
main,
|
||||
attributes,
|
||||
},
|
||||
initialPage: 'main',
|
||||
ephemeral: false,
|
||||
},
|
||||
interaction,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user