diff --git a/src/app/(frontend)/eod/custom/actions.ts b/src/app/(frontend)/eod/custom/actions.ts index 9ae3630..ed206ba 100644 --- a/src/app/(frontend)/eod/custom/actions.ts +++ b/src/app/(frontend)/eod/custom/actions.ts @@ -5,7 +5,11 @@ import { getPayload } from "payload"; import { emitGameEvent } from "@/utils/event-log/emit"; import { EventTypes } from "@/utils/event-log/eventTypes"; import { hasPermission } from "@/utils/access-control/hasPermission"; +import { broadcastToUser } from "@/lib/realtime/bus"; +import { creditEodXp } from "@/lib/custom-minigames/payouts"; import { + applyCreatorBank, + customPlayerXp, isEodUnlocked, minefieldFlagsError, validateGridSize, @@ -120,6 +124,7 @@ export async function createMinefield(input: { rows: number; cols: number; mines: unknown; + exposeEdgeMines?: boolean; }): Promise> { try { const { payload, user } = await authenticate(); @@ -149,6 +154,7 @@ export async function createMinefield(input: { mines: mineCheck.positions, mineCount: mineCheck.positions.length, published: true, + exposeEdgeMines: input.exposeEdgeMines ?? true, playCount: 0, }, depth: 0, @@ -177,6 +183,7 @@ export async function updateMinefield(input: { name?: string; mines?: unknown; published?: boolean; + exposeEdgeMines?: boolean; }): Promise> { try { const { payload, user } = await authenticate(); @@ -212,6 +219,9 @@ export async function updateMinefield(input: { if (input.published !== undefined) { data.published = Boolean(input.published); } + if (input.exposeEdgeMines !== undefined) { + data.exposeEdgeMines = Boolean(input.exposeEdgeMines); + } if (input.mines !== undefined) { const mineCheck = validateMinePositions(input.mines, existing.rows, existing.cols); if (mineCheck.error) { @@ -306,7 +316,7 @@ export async function submitCustomMinefieldScore(input: { seconds: number; flags: number; won: boolean; -}): Promise> { +}): Promise> { try { const { payload, user } = await authenticate(); @@ -337,6 +347,45 @@ export async function submitCustomMinefieldScore(input: { return { success: false, error: flagError }; } + // Payouts: wins pay 10% of the equivalent standard run, the field's author + // banks 5% of each payout, and authors earn nothing on their own fields. + const isOwnPlay = minefield.author === (user.id as number); + let playerXp = 0; + let creatorXpCredited = 0; + let creatorXpBank = minefield.creatorXpBank ?? 0; + if (input.won && !isOwnPlay) { + playerXp = customPlayerXp({ + mineCount: minefield.mineCount, + seconds: input.seconds, + won: true, + }); + if (playerXp > 0) { + const credited = await creditEodXp(payload, user.id as number, playerXp); + if (credited) { + broadcastToUser("profile-update", Number(user.id), { + reason: "xp", + xpGained: playerXp, + profileId: credited.profileId, + }); + } + + const prevBank = creatorXpBank; + const applied = applyCreatorBank(prevBank, playerXp); + const authorId = typeof minefield.author === "number" ? minefield.author : null; + const authorCredit = + applied.wholeXpToCredit > 0 && authorId !== null + ? await creditEodXp(payload, authorId, applied.wholeXpToCredit) + : null; + if (authorCredit) { + creatorXpCredited = applied.wholeXpToCredit; + creatorXpBank = applied.bank; + } else { + // No profile (or nothing whole due): keep the full banked fraction. + creatorXpBank = prevBank + applyCreatorBank(0, playerXp).bank; + } + } + } + await payload.create({ collection: "custom-minefield-scores", data: { @@ -352,7 +401,10 @@ export async function submitCustomMinefieldScore(input: { await payload.update({ collection: "custom-minefields", id: minefield.id, - data: { playCount: (minefield.playCount ?? 0) + 1 }, + data: { + playCount: (minefield.playCount ?? 0) + 1, + ...(playerXp > 0 ? { creatorXpBank } : {}), + }, overrideAccess: true, depth: 0, }); @@ -360,7 +412,9 @@ export async function submitCustomMinefieldScore(input: { await emitGameEvent(payload, { type: EventTypes.MinigameCustomEodPlay, message: input.won - ? `Cleared custom minefield "${minefield.name}" in ${input.seconds}s` + ? `Cleared custom minefield "${minefield.name}" in ${input.seconds}s${ + playerXp > 0 ? ` (+${playerXp} XP)` : "" + }` : `Died on custom minefield "${minefield.name}" after ${input.seconds}s`, actor: user.id as number, targetCollection: "custom-minefields", @@ -369,10 +423,12 @@ export async function submitCustomMinefieldScore(input: { seconds: input.seconds, flags: input.flags, won: Boolean(input.won), + xpEarned: playerXp, + creatorXpCredited, }, }); - return { success: true }; + return { success: true, data: { xpEarned: playerXp } }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; if (message === "Unauthorized") {