hypixel-auction-notifier/src/helperFunctions.ts

242 lines
6.2 KiB
TypeScript
Raw Normal View History

2024-12-27 21:17:59 +01:00
// utils.ts
import nbt from 'prismarine-nbt';
import { Item } from './Item';
import { loadConfig } from './configLoader';
const config = loadConfig();
// If you have a defined structure for your config file, feel free to refine this type.
interface ConfigType {
data: {
minPriceForRawcraft: number;
[key: string]: any;
};
filters: {
EnchantThreshold: Record<string, number>;
EnchantThresholdConditionalBypass: Record<string, Record<string, number>>;
[key: string]: any;
};
}
interface NbtData {
[key: string]: any;
}
// Keep track of current intervals by name
let currentAsyncIntervals: Record<string, boolean> = {};
type ProfitItem = {
RCProfit: number;
RCPP: number;
snipeProfit: number;
snipePP: number;
};
function numberWithCommas(x: number | string): string {
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
function addNotation(type: string, value: number): string {
let returnVal: string = value.toString();
let notList: string[] = [];
if (type === 'shortScale') {
notList = [' Thousand', ' Million', ' Billion', ' Trillion', ' Quadrillion', ' Quintillion'];
} else if (type === 'oneLetters') {
notList = ['K', 'M', 'B', 'T'];
}
let checkNum = 1000;
if (type !== 'none' && type !== 'commas') {
let notValue = notList[notList.length - 1] ?? '';
for (let u = notList.length; u >= 1; u--) {
notValue = notList.shift() ?? '';
for (let o = 3; o >= 1; o--) {
if (value >= checkNum) {
// Use a fixed decimal approach
returnVal = (
+(Math.floor(value / (checkNum / 100)) / Math.pow(10, o)) * 10
).toFixed(o - 1) + notValue;
}
checkNum *= 10;
}
}
} else {
returnVal = numberWithCommas(value.toFixed(0));
}
return returnVal;
}
async function getParsed(encoded: string): Promise<any> {
return new Promise((resolve, reject) => {
const buf = Buffer.from(encoded, 'base64');
// The callback signature must match the prismarine-nbt types:
nbt.parse(buf, (err: Error | null, data) => {
if (err) return reject(err);
resolve(nbt.simplify(data));
});
});
}
function getProfit(price: number, rcCost: number, lbin: number): ProfitItem {
const profitItem: ProfitItem = {
RCProfit: 0,
RCPP: 0,
snipeProfit: 0,
snipePP: 0,
};
if (price >= 1_000_000) {
profitItem.RCProfit = lbin + rcCost - price - (lbin + rcCost) * 0.02;
profitItem.RCPP = parseFloat(((profitItem.RCProfit * 100) / lbin).toFixed(1));
profitItem.snipeProfit = lbin - price - lbin * 0.02;
profitItem.snipePP = parseFloat(((profitItem.snipeProfit * 100) / lbin).toFixed(1));
} else {
profitItem.RCProfit = lbin + rcCost - price - (lbin + rcCost) * 0.01;
profitItem.RCPP = parseFloat(((profitItem.RCProfit * 100) / lbin).toFixed(1));
profitItem.snipeProfit = lbin - price - lbin * 0.01;
profitItem.snipePP = parseFloat(((profitItem.snipeProfit * 100) / lbin).toFixed(1));
}
return profitItem;
}
function splitNumber(num: number = 1, parts: number = 1): number[] {
const n: number = Math.floor(num / parts);
const arr: number[] = [];
for (let i = 0; i < parts; i++) {
arr.push(n);
}
if (arr.reduce((a, b) => a + b, 0) === num) {
return arr;
}
for (let i = 0; i < parts; i++) {
arr[i]++;
if (arr.reduce((a, b) => a + b, 0) === num) {
return arr;
}
}
// If we still havent balanced it out, itll just return this array
return arr;
}
interface ItemType {
itemData: {
id: string;
enchants?: Record<string, number>;
aow?: boolean;
recomb?: boolean;
hpbs?: number;
fpbs?: number;
};
auctionData: {
lbin: number;
category?: string;
};
[key: string]: any;
}
interface LbinsType {
[key: string]: {
lbin: number;
};
}
interface BazaarPriceType {
[key: string]: number;
}
function getRawCraft(
item: Item,
bazaarPrice: BazaarPriceType,
lbins: LbinsType
): number {
let price = 0;
const typedConfig = config as ConfigType;
const ignoreMatch: string =
Object.keys(typedConfig.filters.EnchantThresholdConditionalBypass ?? {}).find((key) => {
return item.itemData.id.includes(key);
}) ?? '';
if (item.auctionData.lbin < typedConfig.data.minPriceForRawcraft) return 0;
const isInIgnore = !!ignoreMatch;
if (item.itemData.enchants && !item.itemData.id.includes(';')) {
for (const enchant of Object.keys(item.itemData.enchants)) {
const degree = item.itemData.enchants[enchant];
const badEnchant =
typeof typedConfig.filters.EnchantThreshold[enchant] === 'number'
? degree >= typedConfig.filters.EnchantThreshold[enchant]
: false;
if (isInIgnore) {
const enchantMinValue =
typedConfig.filters.EnchantThresholdConditionalBypass[ignoreMatch]?.[enchant];
if (enchantMinValue && degree <= enchantMinValue) continue;
}
if (badEnchant) {
price += lbins[`${enchant.toUpperCase()};${degree.toString()}`]
? lbins[`${enchant.toUpperCase()};${degree.toString()}`].lbin * 0.5
: 0;
}
}
}
if (item.itemData.aow) {
price += lbins['THE_ART_OF_WAR'].lbin * 0.3;
}
if (
item.itemData.recomb &&
['weapon', 'armor', 'accessories'].includes(item.auctionData.category ?? '')
) {
price += bazaarPrice['RECOMBOBULATOR_3000'] * 0.5;
}
price += (item.itemData.hpbs ?? 0) * bazaarPrice['HOT_POTATO_BOOK'] * 0.05;
price += (item.itemData.fpbs ?? 0) * bazaarPrice['FUMING_POTATO_BOOK'] * 0.1;
return price;
}
// Schedules a recurring async task.
async function asyncInterval(
asyncTask: () => Promise<void>,
intervalName: string,
timeout: number
): Promise<void> {
currentAsyncIntervals[intervalName] = true;
setTimeout(async function () {
if (!currentAsyncIntervals[intervalName]) return;
await asyncTask();
await asyncInterval(asyncTask, intervalName, timeout);
}, timeout);
}
function stopAsyncInterval(intervalName: string): void {
currentAsyncIntervals[intervalName] = false;
}
function currentIntervals(): Record<string, boolean> {
return currentAsyncIntervals;
}
// Exports
export {
addNotation,
getParsed,
getProfit,
splitNumber,
getRawCraft,
asyncInterval,
stopAsyncInterval,
currentIntervals,
};