Pull using the database's tier list.

main
Mari 3 years ago
parent 1d99caa4ea
commit 61b04327f0
  1. 131
      migrations/committed/000017-pull-command-gets-tiers.sql
  2. 89
      src/commands/game/PullCommand.ts

@ -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';

@ -1,10 +1,72 @@
import {CommandContext, CommandOptionType, SlashCommand, SlashCreator} from "slash-create"; import {CommandContext, CommandOptionType, SlashCommand, SlashCreator} from "slash-create";
import {Chance} from "chance";
import {Snowflake} from "discord-api-types"; import {Snowflake} from "discord-api-types";
import {Pool} from "pg"; import {Pool} from "pg";
import {sendErrorMessage} from "../../queries/ErrorCodes.js"; 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<PullResult, "summonedunittierid" | "summonedunittiername" | "summoncost" | "resultingcurrency">
type ExampleUnit = Omit<PullResult, keyof PartialResult>
const possibleResults: Record<string, ExampleUnit> = {
"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 { export class PullCommand extends SlashCommand {
readonly pool: Pool readonly pool: Pool
@ -38,21 +100,24 @@ export class PullCommand extends SlashCommand {
typeof ctx.options.count === "number" typeof ctx.options.count === "number"
&& ctx.options.count >= 1 && ctx.options.count >= 1
&& ctx.options.count <= 10 ? Math.floor(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 * text: `SELECT *
FROM Command_Pull($1, $2, $3, $4, $5, $6)`, FROM Command_Pull($1, $2, $3, $4, $5, $6)`,
values: [ctx.channelID, ctx.guildID, ctx.user.id, ctx.user.username, ctx.user.discriminator, count], values: [ctx.channelID, ctx.guildID, ctx.user.id, ctx.user.username, ctx.user.discriminator, count],
}) })).rows
const results: string[] = [] const textResult: string[] = dbResult.map((result) => ({
for (let x = 0; x < count; x += 1) { ...result,
results.push(rand.weighted(["**Nicole**: D tier Predator Podcaster", ...possibleResults[result.summonedunittierid] ?? {
"**Herja**: C tier Viking Warrior", summonedunitinstanceid: 999,
"**Sharla**: B tier Skark Girl", summonedunitid: 999,
"**Melpomene**: A tier Muse of Tragedy", summonedunitname: "Missingno",
"**Lady Bootstrap**: S tier Time Traveler"], [20, 15, 10, 5, 1])) summonedunitsubtitle: "Broken Code",
firsttimepull: true,
wasalreadysummoned: true,
} }
})).map(formatPullResult)
return ctx.send({ 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, ephemeral: false,
}) })
} catch (e) { } catch (e) {

Loading…
Cancel
Save