feat: introduce weekly bonus for daily rewards, updating calculations, configuration, and command UI.
This commit is contained in:
@@ -15,9 +15,10 @@ export const daily = createCommand({
|
|||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle("💰 Daily Reward Claimed!")
|
.setTitle("💰 Daily Reward Claimed!")
|
||||||
.setDescription(`You claimed ** ${result.amount}** Astral Units!`)
|
.setDescription(`You claimed ** ${result.amount}** Astral Units!${result.isWeekly ? `\n🎉 **Weekly Bonus!** +${result.weeklyBonus} extra!` : ''}`)
|
||||||
.addFields(
|
.addFields(
|
||||||
{ name: "Streak", value: `🔥 ${result.streak} days`, inline: true },
|
{ name: "Streak", value: `🔥 ${result.streak} days`, inline: true },
|
||||||
|
{ name: "Weekly Progress", value: `${"🟩".repeat(result.streak % 7 || 7)}${"⬜".repeat(7 - (result.streak % 7 || 7))} (${result.streak % 7 || 7}/7)`, inline: true },
|
||||||
{ name: "Next Reward", value: `< t:${Math.floor(result.nextReadyAt.getTime() / 1000)}: R > `, inline: true }
|
{ name: "Next Reward", value: `< t:${Math.floor(result.nextReadyAt.getTime() / 1000)}: R > `, inline: true }
|
||||||
)
|
)
|
||||||
.setColor("Gold")
|
.setColor("Gold")
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export interface GameConfigType {
|
|||||||
daily: {
|
daily: {
|
||||||
amount: bigint;
|
amount: bigint;
|
||||||
streakBonus: bigint;
|
streakBonus: bigint;
|
||||||
|
weeklyBonus: bigint;
|
||||||
cooldownMs: number;
|
cooldownMs: number;
|
||||||
},
|
},
|
||||||
transfers: {
|
transfers: {
|
||||||
@@ -79,6 +80,7 @@ const configSchema = z.object({
|
|||||||
daily: z.object({
|
daily: z.object({
|
||||||
amount: bigIntSchema,
|
amount: bigIntSchema,
|
||||||
streakBonus: bigIntSchema,
|
streakBonus: bigIntSchema,
|
||||||
|
weeklyBonus: bigIntSchema.default(50n),
|
||||||
cooldownMs: z.number(),
|
cooldownMs: z.number(),
|
||||||
}),
|
}),
|
||||||
transfers: z.object({
|
transfers: z.object({
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ mock.module("@/lib/config", () => ({
|
|||||||
daily: {
|
daily: {
|
||||||
amount: 100n,
|
amount: 100n,
|
||||||
streakBonus: 10n,
|
streakBonus: 10n,
|
||||||
|
weeklyBonus: 50n,
|
||||||
cooldownMs: 86400000, // 24 hours
|
cooldownMs: 86400000, // 24 hours
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,6 +140,7 @@ describe("economyService", () => {
|
|||||||
expect(result.streak).toBe(6);
|
expect(result.streak).toBe(6);
|
||||||
// Base 100 + (6-1)*10 = 150
|
// Base 100 + (6-1)*10 = 150
|
||||||
expect(result.amount).toBe(150n);
|
expect(result.amount).toBe(150n);
|
||||||
|
expect(result.isWeekly).toBe(false);
|
||||||
|
|
||||||
// Check updates
|
// Check updates
|
||||||
expect(mockUpdate).toHaveBeenCalledWith(users);
|
expect(mockUpdate).toHaveBeenCalledWith(users);
|
||||||
@@ -146,6 +148,28 @@ describe("economyService", () => {
|
|||||||
expect(mockInsert).toHaveBeenCalledWith(transactions);
|
expect(mockInsert).toHaveBeenCalledWith(transactions);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should claim weekly bonus correctly on 7th day", async () => {
|
||||||
|
const recentPast = new Date("2023-01-01T11:00:00Z");
|
||||||
|
|
||||||
|
mockFindFirst
|
||||||
|
.mockResolvedValueOnce({ expiresAt: recentPast })
|
||||||
|
.mockResolvedValueOnce({ id: 1n, dailyStreak: 6, balance: 1000n }); // User currently at 6 days
|
||||||
|
|
||||||
|
const result = await economyService.claimDaily("1");
|
||||||
|
|
||||||
|
expect(result.claimed).toBe(true);
|
||||||
|
// Streak should increase: 6 + 1 = 7
|
||||||
|
expect(result.streak).toBe(7);
|
||||||
|
|
||||||
|
// Base: 100
|
||||||
|
// Streak Bonus: (7-1)*10 = 60
|
||||||
|
// Weekly Bonus: 50
|
||||||
|
// Total: 210
|
||||||
|
expect(result.amount).toBe(210n);
|
||||||
|
expect(result.isWeekly).toBe(true);
|
||||||
|
expect(result.weeklyBonus).toBe(50n);
|
||||||
|
});
|
||||||
|
|
||||||
it("should throw if cooldown is active", async () => {
|
it("should throw if cooldown is active", async () => {
|
||||||
const future = new Date("2023-01-02T12:00:00Z"); // +24h
|
const future = new Date("2023-01-02T12:00:00Z"); // +24h
|
||||||
mockFindFirst.mockResolvedValue({ expiresAt: future });
|
mockFindFirst.mockResolvedValue({ expiresAt: future });
|
||||||
|
|||||||
@@ -106,7 +106,11 @@ export const economyService = {
|
|||||||
|
|
||||||
const bonus = (BigInt(streak) - 1n) * config.economy.daily.streakBonus;
|
const bonus = (BigInt(streak) - 1n) * config.economy.daily.streakBonus;
|
||||||
|
|
||||||
const totalReward = config.economy.daily.amount + bonus;
|
// Weekly bonus check
|
||||||
|
const isWeeklyCurrent = streak > 0 && streak % 7 === 0;
|
||||||
|
const weeklyBonusAmount = isWeeklyCurrent ? config.economy.daily.weeklyBonus : 0n;
|
||||||
|
|
||||||
|
const totalReward = config.economy.daily.amount + bonus + weeklyBonusAmount;
|
||||||
await txFn.update(users)
|
await txFn.update(users)
|
||||||
.set({
|
.set({
|
||||||
balance: sql`${users.balance} + ${totalReward}`,
|
balance: sql`${users.balance} + ${totalReward}`,
|
||||||
@@ -138,7 +142,7 @@ export const economyService = {
|
|||||||
description: `Daily reward (Streak: ${streak})`,
|
description: `Daily reward (Streak: ${streak})`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { claimed: true, amount: totalReward, streak, nextReadyAt };
|
return { claimed: true, amount: totalReward, streak, nextReadyAt, isWeekly: isWeeklyCurrent, weeklyBonus: weeklyBonusAmount };
|
||||||
}, tx);
|
}, tx);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user