Add Daily command and fix daily time problems.

main
Mari 2 years ago
parent 690b9db45b
commit 89215ef8e9
  1. 137
      migrations/committed/000015-daily-and-vgnyj-error-functions.sql
  2. 5
      src/GameServer.ts
  3. 47
      src/commands/game/DailyCommand.ts
  4. 2
      src/queries/ErrorCodes.ts

@ -0,0 +1,137 @@
--! Previous: sha1:2a39b3b006a8d5ec5d30ac1e5c2158d282b99acb
--! Hash: sha1:1390238abcc8e013a87b670fbcb3ae127a51ece8
--! Message: daily and VGNYJ error functions
--- Calculates the date at which /daily can be used again after the given timestamp.
CREATE OR REPLACE FUNCTION NextDailyCommand(IN lastDaily timestamp with time zone)
RETURNS timestamp with time zone
CALLED ON NULL INPUT
IMMUTABLE
AS
$$
SELECT (COALESCE(lastDaily, TIMESTAMP '-infinity' AT TIME ZONE 'UTC')::date + 1)::timestamp AT TIME ZONE 'UTC'
$$ LANGUAGE 'sql';
--- Raises the VGNYJ error.
CREATE OR REPLACE PROCEDURE Error_NotYetJoined() AS
$$
BEGIN
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;
$$ LANGUAGE 'plpgsql';
--- Runs the full /daily 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.
--- VGDLY: Already used today.
CREATE OR REPLACE FUNCTION Command_Daily(
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,
OUT newLastDaily Player.lastDaily%TYPE,
OUT newNextDaily Player.lastDaily%TYPE,
OUT newCurrency Player.currency%TYPE,
OUT bonus Player.currency%TYPE)
STRICT
VOLATILE
AS
$$
DECLARE
playerId Player.id%TYPE;
playerLastDaily Player.lastDaily%TYPE;
playerNextDaily Player.lastDaily%TYPE;
BEGIN
CALL CheckGameCommandIn(requestedChannel, requestedGuild);
SELECT InvokingPlayer.id, InvokingPlayer.lastDaily, NextDailyCommand(InvokingPlayer.lastdaily)
INTO playerId, playerLastDaily, playerNextDaily
FROM GetInvokingPlayer(forId, newUsername, newDiscriminator) AS InvokingPlayer;
IF playerId IS NULL THEN
CALL Error_NotYetJoined();
END IF;
bonus = 100;
IF playerNextDaily > NOW() THEN
RAISE EXCEPTION USING
ERRCODE = 'VGDLY',
MESSAGE = 'Already used today',
DETAIL = format('You last used your daily command at <t:%s>. You can use it again at <t:%s>.',
extract(EPOCH FROM playerLastDaily)::INT,
extract(EPOCH FROM playerNextDaily)::INT),
HINT = format('Wait <t:%s:R> and you can use the /daily command again to get some more currency!',
extract(EPOCH FROM playerNextDaily)::INT);
ELSE
UPDATE Player
SET currency = currency + bonus,
lastDaily = NOW()
WHERE id = playerId
RETURNING lastDaily, currency INTO newLastDaily, newCurrency;
newNextDaily = NextDailyCommand(newLastDaily);
END IF;
END;
$$ LANGUAGE 'plpgsql';
--- 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,
summonedUnitTierName UnitTier.name%TYPE,
firstTimePull BOOLEAN,
wasAlreadySummoned BOOLEAN
)
STRICT
VOLATILE
ROWS 10
AS
$$
DECLARE
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
CALL Error_NotYetJoined();
END IF;
cost = 10 * 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 NextDailyCommand(playerLastDaily) > NOW()
WHEN FALSE THEN 'You can use the /daily command to get some more currency for today!'
ELSE format('You can use the /daily command again <t:%s:R> to get some more currency!',
extract(EPOCH FROM NextDailyCommand(playerLastDaily))::INT)
END;
END IF;
END;
$$ LANGUAGE 'plpgsql';

@ -7,6 +7,7 @@ import {PullCommand} from "./commands/game/PullCommand.js";
import {JoinCommand} from "./commands/game/JoinCommand.js";
import {singleColumnQueryResult} from "./queries/QueryHelpers.js";
import {UnjoinCommand} from "./commands/debug/UnjoinCommand.js";
import {DailyCommand} from "./commands/game/DailyCommand.js";
export class GameServer extends BaseServer {
@ -33,6 +34,10 @@ export class GameServer extends BaseServer {
pool: this.pool,
gameGuildIds: singleColumnQueryResult(await promisedGameGuildIds),
}))
this.slashcmd.registerCommand(new DailyCommand(this.slashcmd, {
pool: this.pool,
gameGuildIds: singleColumnQueryResult(await promisedGameGuildIds),
}))
this.server.get("/game/started", async (req, res) => {
const token = await generateXSRFCookie(res)
res.code(200)

@ -0,0 +1,47 @@
import {CommandContext, SlashCommand, SlashCreator} from "slash-create";
import {Snowflake} from "discord-api-types";
import {Pool} from "pg";
import {sendErrorMessage} from "../../queries/ErrorCodes.js";
import {singleRowQueryResult} from "../../queries/QueryHelpers.js";
export class DailyCommand extends SlashCommand {
readonly pool: Pool
constructor(creator: SlashCreator, {pool, gameGuildIds}: {
pool: Pool,
gameGuildIds: Snowflake[]
}) {
super(creator, {
name: "daily",
guildIDs: gameGuildIds,
description: "Gathers your daily currency once per day, resetting at midnight UTC.",
options: []
});
this.pool = pool
}
async run(ctx: CommandContext): Promise<any> {
try {
const result = singleRowQueryResult(await this.pool.query<{ newlastdaily: Date, newnextdaily: Date, newcurrency: number, bonus: number }>({
text: `SELECT *
FROM Command_Daily($1, $2, $3, $4, $5)`,
values: [ctx.channelID, ctx.guildID, ctx.user.id, ctx.user.username, ctx.user.discriminator],
}))
if (result === undefined) {
await ctx.send({
content: "Unexpectedly got no results!!",
ephemeral: true,
})
console.log("Unexpectedly empty Command_Join result!")
return;
}
return ctx.send({
content: `Nice! You reap ${result.bonus} currency from the void, leaving you with a total of ${result.newcurrency}!\n\nYou collected your currency now at <t:${Math.floor(result.newlastdaily.getTime() / 1000)}> and can gather it again in <t:${Math.floor(result.newnextdaily.getTime() / 1000)}:R> at <t:${Math.floor(result.newnextdaily.getTime() / 1000)}>.`,
ephemeral: true,
})
} catch (e) {
await sendErrorMessage(ctx, e)
}
}
}

@ -8,6 +8,7 @@ export enum ErrorCodes {
BAD_GUILD_GAME = "VGBGG",
NOT_YET_JOINED = "VGNYJ",
NOT_ENOUGH_CURRENCY = "VGNEC",
DAILY_ALREADY_USED = "VGDLY",
}
/** Checks if the error is a database error. */
@ -26,6 +27,7 @@ export async function sendErrorMessage(ctx: CommandContext, err: unknown): Promi
case ErrorCodes.BAD_GUILD_GAME:
case ErrorCodes.NOT_YET_JOINED:
case ErrorCodes.NOT_ENOUGH_CURRENCY:
case ErrorCodes.DAILY_ALREADY_USED:
await ctx.send({
content: `**${err.message}**\n${err.detail}\n\n**Tip**: ${err.hint}`,
ephemeral: true,

Loading…
Cancel
Save