Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loot Probability Command #483

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/commands/loot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import {
type ChatInputCommandInteraction,
type CommandInteraction,
SlashCommandBuilder,
SlashCommandSubcommandBuilder,
} from "discord.js";

import type { BotContext } from "@/context.js";
import type { ApplicationCommand } from "@/commands/command.js";
import { ensureChatInputCommand } from "@/utils/interactionUtils.js";

import * as lootDataService from "@/service/lootData.js";
import { adjustLootWithBuffsFromUser } from "@/service/lootDrop.js";

export default class LootCommand implements ApplicationCommand {
name = "loot";
description = "Geht's um Loot?";

applicationCommand = new SlashCommandBuilder()
.setName(this.name)
.setDescription(this.description)
.addSubcommand(
new SlashCommandSubcommandBuilder()
.setName("wahrscheinlichkeiten")
.setDescription("Zeige die Wahrscheinlichkeiten für Loot Gegenstände an"),
);

async handleInteraction(interaction: CommandInteraction, context: BotContext) {
const command = ensureChatInputCommand(interaction);
const subCommand = command.options.getSubcommand();
switch (subCommand) {
case "wahrscheinlichkeiten":
await this.#showLootProbability(interaction, context);
break;
default:
throw new Error(`Unknown subcommand: "${subCommand}"`);
}
}

async #showLootProbability(interaction: CommandInteraction, context: BotContext) {
if (!interaction.isChatInputCommand()) {
throw new Error("Interaction is not a chat input command");
}
if (!interaction.guild || !interaction.channel) {
return;
}

// TODO: Lowperformer solution. A diagram with graphviz or something would be cooler
const loot = (
await adjustLootWithBuffsFromUser(interaction.user, lootDataService.lootTemplates)
).loot.filter(l => l.weight > 0);
const totalWeight = loot.reduce((acc, curr) => acc + curr.weight, 0);
const lootWithProbabilitiy = loot
.map(l => ({
...l, // Oh no, please optimize for webscale. No need to copy the whole data :cry:
probability: Number(l.weight / totalWeight),
}))
.sort((a, b) => b.probability - a.probability);

const textRepresentation = lootWithProbabilitiy
.map(
l =>
`${l.displayName}: ${l.probability.toLocaleString(undefined, { style: "percent", maximumFractionDigits: 2 })}`,
)
.join("\n");

await interaction.reply(
`Deine persönlichen Loot Wahrscheinlichkeiten:\n\n${textRepresentation}`,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mach mal ephemeral.

);
}
}
30 changes: 19 additions & 11 deletions src/service/lootDrop.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as fs from "node:fs/promises";

import {
ActionRowBuilder,
ButtonBuilder,
ButtonStyle,
ChannelType,
Expand All @@ -28,6 +27,7 @@ import {
} from "@/service/lootData.js";

import log from "@log";
import type { LootTemplate } from "@/storage/loot.js";

const lootTimeoutMs = 60 * 1000;

Expand Down Expand Up @@ -152,13 +152,15 @@ export async function postLootDrop(
return;
}

const defaultWeights = lootTemplates.map(t => t.weight);
const { messages, weights } = await getDropWeightAdjustments(interaction.user, defaultWeights);
const { messages, loot } = await adjustLootWithBuffsFromUser(interaction.user, lootTemplates);

const rarityWeights = lootAttributeTemplates.map(a => a.initialDropWeight ?? 0);
const initialAttribute = randomEntryWeighted(lootAttributeTemplates, rarityWeights);

const template = randomEntryWeighted(lootTemplates, weights);
const template = randomEntryWeighted(
lootTemplates,
loot.map(l => l.weight),
);
const claimedLoot = await lootService.createLoot(
template,
interaction.user,
Expand Down Expand Up @@ -228,12 +230,12 @@ export async function postLootDrop(

type AdjustmentResult = {
messages: string[];
weights: number[];
loot: LootTemplate[];
};

async function getDropWeightAdjustments(
export async function adjustLootWithBuffsFromUser(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Der Purpose der Funktion sind generell Adjustments in der Situation des Klickens. Das können Buffs, aber auch Debufffs durch aktuelle Items sein. Später war gedacht, die aktulle Uhrzeit des Klickens oder den schenkenden User ("Schenker X hat Item Y, deshalb ist Warhscheinlichkeit Z höher/geringer") etc.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mit "Buffs" meinte ich tatsächlich auch beides. Vielleicht git's da eine bessere Bezeichnung

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weight Adjustments

user: User,
weights: readonly number[],
loot: readonly LootTemplate[],
): Promise<AdjustmentResult> {
const waste = await lootService.getUserLootCountById(user.id, LootKindId.RADIOACTIVE_WASTE);
const messages = [];
Expand All @@ -254,13 +256,19 @@ async function getDropWeightAdjustments(
messages.push("Da du privat versichert bist, hast du die doppelte Chance auf eine AU.");
}

const newWeights = [...weights];
newWeights[LootKindId.NICHTS] = Math.ceil(weights[LootKindId.NICHTS] * wasteFactor) | 0;
newWeights[LootKindId.KRANKSCHREIBUNG] = (weights[LootKindId.KRANKSCHREIBUNG] * pkvFactor) | 0;
const weights = loot.map(t => t.weight);
const newLoot = [...loot];
const updateEntry = (id: LootKindId, newValue: number) => {
const idx = newLoot.findIndex(l => l.id === id);
console.assert(idx !== -1);
newLoot[idx].weight = newValue;
};
updateEntry(LootKindId.NICHTS, Math.ceil(weights[LootKindId.NICHTS] * wasteFactor) | 0);
updateEntry(LootKindId.KRANKSCHREIBUNG, (weights[LootKindId.KRANKSCHREIBUNG] * pkvFactor) | 0);

return {
messages,
weights: newWeights,
loot: newLoot,
Comment on lines -263 to +274
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Früher war das so und es hatte irgendeinen Grund, warum ich das umgebaut habe. Kann dir nicht genau sagen, welchen

};
}

Expand Down