Pull units from the database rather than the internal list

main
Mari 2 years ago
parent 1e93bf7ed9
commit 826210461d
  1. 2
      .idea/dataSources.xml
  2. 133
      migrations/committed/000019-pull-command-gets-units.sql
  3. 57
      src/commands/game/PullCommand.ts
  4. 8
      src/queries/ErrorCodes.ts

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="Local Development Database" read-only="true" uuid="f45c1a42-afba-45e3-a87c-3a0e1ff84111">
<data-source source="LOCAL" name="Local Development Database" uuid="f45c1a42-afba-45e3-a87c-3a0e1ff84111">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<schema-control>AUTOMATIC</schema-control>

@ -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 <t:%s:R> 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';

@ -16,51 +16,7 @@ interface PullResult {
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,
},
}
type PartialResult = Omit<PullResult, "summonedunitinstanceid" | "firsttimepull" | "wasalreadysummoned">
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 ")}`,

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

Loading…
Cancel
Save