From 61b04327f0bbdb121a53abd9f47fa8ded120a268 Mon Sep 17 00:00:00 2001 From: Mari Date: Wed, 12 Jan 2022 13:44:59 -0500 Subject: [PATCH] Pull using the database's tier list. --- .../000017-pull-command-gets-tiers.sql | 131 ++++++++++++++++++ src/commands/game/PullCommand.ts | 91 ++++++++++-- 2 files changed, 209 insertions(+), 13 deletions(-) create mode 100644 migrations/committed/000017-pull-command-gets-tiers.sql diff --git a/migrations/committed/000017-pull-command-gets-tiers.sql b/migrations/committed/000017-pull-command-gets-tiers.sql new file mode 100644 index 0000000..678517f --- /dev/null +++ b/migrations/committed/000017-pull-command-gets-tiers.sql @@ -0,0 +1,131 @@ +--! Previous: sha1:c36f619890ff43d233e58abb4c5b9d5a4e556ca3 +--! Hash: sha1:3a9ed9f26777390a1dccadc516bade27ffddf684 +--! Message: pull command gets tiers + +ALTER TABLE UnitTier + ADD COLUMN IF NOT EXISTS pullWeightLower FLOAT NOT NULL DEFAULT 0, + ADD COLUMN IF NOT EXISTS pullWeightUpper FLOAT NOT NULL DEFAULT 0; + +--- Updates the UnitTier pull weight bounds to span from 0.0 to 1.0 and match the weights given. +CREATE OR REPLACE PROCEDURE UnitTier_UpdatePullWeightBounds() AS +$$ +WITH weights AS ( + SELECT UnitTier.id AS id, + (sum(UnitTier.pullWeight) OVER (ORDER BY UnitTier.sortOrder) - UnitTier.pullWeight)::FLOAT / + (sum(UnitTier.pullWeight) OVER ())::FLOAT AS newPullWeightLower, + (sum(pullWeight) OVER (ORDER BY sortOrder))::FLOAT / + (sum(pullWeight) OVER ())::FLOAT AS newPullWeightUpper + FROM UnitTier +) +UPDATE UnitTier +SET pullWeightLower = weights.newPullWeightLower, + pullWeightUpper = weights.newPullWeightUpper +FROM weights +WHERE weights.id = UnitTier.id; +$$ LANGUAGE 'sql'; + +CALL UnitTier_UpdatePullWeightBounds(); + +DROP TRIGGER IF EXISTS UnitTier_UpdatePullWeightBounds_Trigger ON UnitTier; +CREATE OR REPLACE FUNCTION UnitTier_UpdatePullWeightBounds_TriggerFunc() RETURNS TRIGGER AS +$$ +BEGIN + CALL UnitTier_UpdatePullWeightBounds(); +END; +$$ + LANGUAGE 'plpgsql'; +--- Automatically updates the pull weight bounds whenever the table changes. +CREATE TRIGGER UnitTier_UpdatePullWeightBounds_Trigger + AFTER INSERT OR UPDATE OF pullWeight OR DELETE + ON UnitTier + FOR EACH STATEMENT +EXECUTE PROCEDURE UnitTier_UpdatePullWeightBounds_TriggerFunc(); + +DROP FUNCTION IF EXISTS Command_Pull( + IN requestedChannel DiscordChannel.discordId%TYPE, + IN requestedGuild DiscordChannel.guildId%TYPE, + IN forId DiscordUser.discordId%TYPE, + IN newUsername DiscordUser.username%TYPE, + IN newDiscriminator DiscordUser.discriminator%TYPE, + IN count INT +); +--- Runs the full /pull command. +--- Error codes: +--- VGBCG: Bad channel (game). This is not a valid channel to send game commands in. +--- VGBGG: Bad guild (game). This is not a valid guild to send game commands in. +--- VGNYJ: Not yet joined. The Discord user using has not joined the game yet. +--- VGNEC: Not enough currency. +CREATE OR REPLACE FUNCTION Command_Pull( + IN requestedChannel DiscordChannel.discordId%TYPE, + IN requestedGuild DiscordChannel.guildId%TYPE, + IN forId DiscordUser.discordId%TYPE, + IN newUsername DiscordUser.username%TYPE, + IN newDiscriminator DiscordUser.discriminator%TYPE, + IN count INT +) + RETURNS TABLE + ( + summonedUnitInstanceId SummonedUnit.instanceId%TYPE, + summonedUnitId Unit.id%TYPE, + summonedUnitName Unit.name%TYPE, + summonedUnitSubtitle Unit.subtitle%TYPE, + summonedUnitTierId UnitTier.id%TYPE, + summonedUnitTierName UnitTier.name%TYPE, + summonCost Player.currency%TYPE, + resultingCurrency Player.currency%TYPE, + firstTimePull BOOLEAN, + wasAlreadySummoned BOOLEAN + ) + STRICT + VOLATILE + ROWS 10 +AS +$$ +DECLARE + costPerSummon CONSTANT INT := 10; + playerId Player.id%TYPE; + cost Player.currency%TYPE; + oldCurrency Player.currency%TYPE; + playerLastDaily Player.lastDaily%TYPE; +BEGIN + CALL CheckGameCommandIn(requestedChannel, requestedGuild); + SELECT InvokingPlayer.id, InvokingPlayer.currency, InvokingPlayer.lastDaily + INTO playerId, oldCurrency, playerLastDaily + FROM GetInvokingPlayer(forId, newUsername, newDiscriminator) AS InvokingPlayer; + IF playerId IS NULL THEN + RAISE EXCEPTION USING + ERRCODE = 'VGNYJ', + MESSAGE = 'Not yet joined', + DETAIL = 'You haven''t joined the game yet, and can''t use this command until you do.', + HINT = 'Use the /join command to join the game!'; + END IF; + cost = costPerSummon * count; + UPDATE Player SET currency = currency - cost WHERE id = playerId AND currency >= cost; + IF NOT FOUND THEN + RAISE EXCEPTION USING + ERRCODE = 'VGNEC', + MESSAGE = 'Not enough currency', + DETAIL = format('Pulling %s heroines would cost %s currency, but you only have %s currency.', + count, cost, oldCurrency), + HINT = CASE playerLastDaily IS NULL OR playerLastDaily < NOW() - '1 day'::interval + WHEN TRUE THEN 'Try using the /daily command to get some more currency for today!' + ELSE format('Wait %s and you can use the /daily command to get some more currency!', + (playerLastDaily + '1 day'::interval) - NOW()) + END; + END IF; + RETURN QUERY + WITH rng AS (SELECT summonId, random() AS tierRandom FROM generate_series(1, count) AS series(summonId)) + SELECT NULL::INTEGER AS summonedUnitInstanceId, + NULL::INTEGER AS summonedUnitId, + NULL::VARCHAR AS summonedUnitName, + NULL::VARCHAR AS summonedUnitSubtitle, + t.id AS summonedUnitTierId, + t.name AS summonedUnitTierName, + costPerSummon AS summonCost, + oldCurrency - (costPerSummon * r.summonId) AS resultingCurrency, + NULL::BOOLEAN AS firstTimePull, + NULL::BOOLEAN AS wasAlreadySummoned + FROM rng AS r + INNER JOIN UnitTier AS t ON (t.pullWeightLower <= r.tierRandom AND t.pullWeightUpper > r.tierRandom); +END; +$$ LANGUAGE 'plpgsql'; diff --git a/src/commands/game/PullCommand.ts b/src/commands/game/PullCommand.ts index ae96c3c..156b703 100644 --- a/src/commands/game/PullCommand.ts +++ b/src/commands/game/PullCommand.ts @@ -1,10 +1,72 @@ import {CommandContext, CommandOptionType, SlashCommand, SlashCreator} from "slash-create"; -import {Chance} from "chance"; import {Snowflake} from "discord-api-types"; import {Pool} from "pg"; import {sendErrorMessage} from "../../queries/ErrorCodes.js"; -const rand = Chance() +interface PullResult { + summonedunitinstanceid: number, + summonedunitid: number, + summonedunitname: string, + summonedunitsubtitle: string, + summonedunittierid: string, + summonedunittiername: string, + summoncost: number, + resultingcurrency: number, + firsttimepull: boolean, + wasalreadysummoned: boolean, +} + +type PartialResult = Pick +type ExampleUnit = Omit + +const possibleResults: Record = { + "s": { + summonedunitinstanceid: 0, + summonedunitid: 0, + summonedunitname: "Lady Bootstrap", + summonedunitsubtitle: "Time Traveler", + firsttimepull: true, + wasalreadysummoned: false, + }, + "a": { + summonedunitinstanceid: 1, + summonedunitid: 1, + summonedunitname: "Melpomene", + summonedunitsubtitle: "Muse of Tragedy", + firsttimepull: false, + wasalreadysummoned: false, + }, + "b": { + summonedunitinstanceid: 2, + summonedunitid: 2, + summonedunitname: "Sharla", + summonedunitsubtitle: "Skark Girl", + firsttimepull: true, + wasalreadysummoned: false, + }, + "c": { + summonedunitinstanceid: 3, + summonedunitid: 3, + summonedunitname: "Herja", + summonedunitsubtitle: "Viking Warrior", + firsttimepull: false, + wasalreadysummoned: false, + }, + "d": { + summonedunitinstanceid: 4, + summonedunitid: 4, + summonedunitname: "Nicole", + summonedunitsubtitle: "Predator Podcaster", + firsttimepull: false, + wasalreadysummoned: true, + }, +} + +function formatPullResult(r: PullResult): string { + return `${r.wasalreadysummoned ? "deepened your bond with" : + r.firsttimepull ? "forged a **new bond** with" : "summoned forth"} ***${r.summonedunitname}***, ` + + `**${r.summonedunitsubtitle}**! _(${r.summonedunittiername})_` +} export class PullCommand extends SlashCommand { readonly pool: Pool @@ -38,21 +100,24 @@ export class PullCommand extends SlashCommand { typeof ctx.options.count === "number" && ctx.options.count >= 1 && ctx.options.count <= 10 ? Math.floor(ctx.options.count) : 1 - await this.pool.query({ + const dbResult: PartialResult[] = (await this.pool.query({ text: `SELECT * FROM Command_Pull($1, $2, $3, $4, $5, $6)`, values: [ctx.channelID, ctx.guildID, ctx.user.id, ctx.user.username, ctx.user.discriminator, count], - }) - const results: string[] = [] - for (let x = 0; x < count; x += 1) { - results.push(rand.weighted(["**Nicole**: D tier Predator Podcaster", - "**Herja**: C tier Viking Warrior", - "**Sharla**: B tier Skark Girl", - "**Melpomene**: A tier Muse of Tragedy", - "**Lady Bootstrap**: S tier Time Traveler"], [20, 15, 10, 5, 1])) - } + })).rows + const textResult: string[] = dbResult.map((result) => ({ + ...result, + ...possibleResults[result.summonedunittierid] ?? { + summonedunitinstanceid: 999, + summonedunitid: 999, + summonedunitname: "Missingno", + summonedunitsubtitle: "Broken Code", + firsttimepull: true, + wasalreadysummoned: true, + } + })).map(formatPullResult) return ctx.send({ - content: `_${ctx.user.mention}_, you pulled...\n \\* ${results.join("\n \\* ")}`, + content: `_${ctx.user.mention}_, you ${textResult.join("\n And you also ")}`, ephemeral: false, }) } catch (e) {