diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index be5bec9..be79ed8 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,7 +1,7 @@ - + postgresql true AUTOMATIC diff --git a/migrations/committed/000019-pull-command-gets-units.sql b/migrations/committed/000019-pull-command-gets-units.sql new file mode 100644 index 0000000..ba3f1e3 --- /dev/null +++ b/migrations/committed/000019-pull-command-gets-units.sql @@ -0,0 +1,133 @@ +--! Previous: sha1:f03c81c8d0ca3976d4b6fe4792ee7558d1c2c2f1 +--! Hash: sha1:c8b959244b460096124e37cb9014d1a1b3faf7d3 +--! Message: pull command gets units + +--- 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. +--- VGNUT: No units in tier. The randomly selected tier has no units and it's impossible to summon a unit. +--- VGUNF: Unit not found. The randomly selected unit was not found. +--- VGTNF: Tier not found. The randomly selected tier was not found. +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; + tierRandom FLOAT; + validUnits INTEGER[] := ARRAY []::INTEGER[]; + unitRandom FLOAT; +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 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 to get some more currency!', + extract(EPOCH FROM NextDailyCommand(playerLastDaily))::INT) + END; + END IF; + FOR summonIdx IN 1..count + LOOP + tierRandom = random(); + unitRandom = random(); + summonedUnitTierId = id + FROM UnitTier + WHERE (UnitTier.pullWeightLower <= tierRandom AND + UnitTier.pullWeightUpper > tierRandom); + validUnits = ARRAY(SELECT id FROM Unit WHERE (Unit.tierId = summonedUnitTierId)); + IF validUnits = ARRAY []::INTEGER[] THEN + RAISE EXCEPTION USING + ERRCODE = 'VGNUT', + MESSAGE = 'No units of tier', + DETAIL = format( + 'The dice selected the tier with ID ''%s'', but there are no units of that tier!', + summonedUnitTierId), + HINT = 'This indicates a problem in the database. ' + || 'Contact an admin to fix this problem and get your justly deserved pull.'; + END IF; + summonedUnitId = validUnits[ + floor(unitRandom::DOUBLE PRECISION * array_length(validUnits, 1)::DOUBLE PRECISION)::INTEGER + 1]; + SELECT Unit.id, Unit.name, Unit.subtitle, Unit.tierId + INTO summonedUnitId, summonedUnitName, summonedUnitSubtitle, summonedUnitTierId + FROM Unit + WHERE Unit.id = summonedUnitId; + IF NOT FOUND THEN + RAISE EXCEPTION USING + ERRCODE = 'VGUNF', + MESSAGE = 'Unit not found', + DETAIL = format( + 'The dice selected the unit with ID ''%s'' of tier ID ''%s'', but the unit wasn''t found!', + summonedUnitId, summonedUnitTierId), + HINT = 'This indicates a problem in the database.' + || ' Contact an admin to fix this problem and get your justly deserved pull.'; + END IF; + SELECT UnitTier.id, UnitTier.name + INTO summonedUnitTierId, summonedUnitTierName + FROM UnitTier + WHERE UnitTier.id = summonedUnitTierId; + IF NOT FOUND THEN + RAISE EXCEPTION USING + ERRCODE = 'VGTNF', + MESSAGE = 'Tier not found', + DETAIL = format( + 'The dice selected the unit with ID ''%s'' of tier ID ''%s'', but the tier wasn''t found!', + summonedUnitId, summonedUnitTierId), + HINT = 'This indicates a problem in the database.' + || ' Contact an admin to fix this problem and get your justly deserved pull.'; + END IF; + summonedUnitInstanceId = NULL::INTEGER; + summonCost = costPerSummon; + resultingCurrency = oldCurrency - (costPerSummon * summonIdx); + firstTimePull = NULL::BOOLEAN; + wasAlreadySummoned = NULL::BOOLEAN; + RETURN NEXT; + END LOOP; +END; + +$$ LANGUAGE 'plpgsql'; diff --git a/src/commands/game/PullCommand.ts b/src/commands/game/PullCommand.ts index 156b703..3a4cfa7 100644 --- a/src/commands/game/PullCommand.ts +++ b/src/commands/game/PullCommand.ts @@ -16,51 +16,7 @@ interface PullResult { 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, - }, -} +type PartialResult = Omit function formatPullResult(r: PullResult): string { return `${r.wasalreadysummoned ? "deepened your bond with" : @@ -107,14 +63,9 @@ export class PullCommand extends SlashCommand { })).rows const textResult: string[] = dbResult.map((result) => ({ ...result, - ...possibleResults[result.summonedunittierid] ?? { - summonedunitinstanceid: 999, - summonedunitid: 999, - summonedunitname: "Missingno", - summonedunitsubtitle: "Broken Code", - firsttimepull: true, - wasalreadysummoned: true, - } + summonedunitinstanceid: 0, + firsttimepull: false, + wasalreadysummoned: false })).map(formatPullResult) return ctx.send({ content: `_${ctx.user.mention}_, you ${textResult.join("\n And you also ")}`, diff --git a/src/queries/ErrorCodes.ts b/src/queries/ErrorCodes.ts index 974e756..c49ab2c 100644 --- a/src/queries/ErrorCodes.ts +++ b/src/queries/ErrorCodes.ts @@ -9,6 +9,9 @@ export enum ErrorCodes { NOT_YET_JOINED = "VGNYJ", NOT_ENOUGH_CURRENCY = "VGNEC", DAILY_ALREADY_USED = "VGDLY", + NO_UNITS_IN_TIER = "VGNUT", + UNIT_NOT_FOUND = "VGUNF", + TIER_NOT_FOUND = "VGTNF", } /** Checks if the error is a database error. */ @@ -28,6 +31,9 @@ export async function sendErrorMessage(ctx: CommandContext, err: unknown): Promi case ErrorCodes.NOT_YET_JOINED: case ErrorCodes.NOT_ENOUGH_CURRENCY: case ErrorCodes.DAILY_ALREADY_USED: + case ErrorCodes.NO_UNITS_IN_TIER: + case ErrorCodes.UNIT_NOT_FOUND: + case ErrorCodes.TIER_NOT_FOUND: await ctx.send({ content: `**${err.message}**\n${err.detail}\n\n**Tip**: ${err.hint}`, ephemeral: true, @@ -35,7 +41,7 @@ export async function sendErrorMessage(ctx: CommandContext, err: unknown): Promi return default: await ctx.send({ - content: `**Unexpected Error (${err.code})**: ${err.message}\n${err.detail}\n\n**Tip**: ${err.hint}`, + content: `**Unexpected Error (${err.code})**${err.message ? ": " : ""}${err.message}${err.detail ? "\n" : ""}${err.detail}${err.hint ? "\n\n**Tip**: " : ""}${err.hint}`, ephemeral: true, }) return