Exact source used by this build. SHA-256: fe3fa18c5bd3ae94bc7811ad94180784cc3e0ce10166e0abeeca07c83fce0c41.

1 import { z } from "zod";
2 import { createRng } from "../../rng";
3 import type { IntentResult } from "../../types";
4 import type { IntentContext } from "../registry";
5 import { addItem, getInventory, takeSlot } from "../inventory";
6 import { debitMoney } from "../shipping/money";
7 
8 const GeodesSchema = z.object({ geodes: z.array(z.object({
9   itemId: z.string(), crackPrice: z.number().int().positive(),
10   drops: z.array(z.object({ itemId: z.string(), min: z.number().int().positive(), max: z.number().int().positive(), weight: z.number().positive() })).min(1),
11 })) });
12 
13 /** Shop access belongs to shops; this service owns only the mining reward transaction. */
14 export function crackMineGeode({ state, player, playerId, rules }: IntentContext, slot?: number): IntentResult {
15   const data = GeodesSchema.safeParse(rules.content?.mines);
16   if (!data.success) return { state, events: [], error: "Geode content is unavailable" };
17   const slots = getInventory(player, rules).slots;
18   const selected = slot ?? slots.findIndex((stack) => stack && data.data.geodes.some((entry) => entry.itemId === stack.id));
19   const stack = slots[selected], geode = data.data.geodes.find((entry) => entry.itemId === stack?.id);
20   if (!geode) return { state, events: [], error: "Choose a geode in your backpack" };
21   const taken = takeSlot(player, selected, 1, rules);
22   if (!taken) return { state, events: [], error: "Geode is not in inventory" };
23   const paid = debitMoney(state, playerId, geode.crackPrice);
24   if (!paid) return { state, events: [], error: "Insufficient funds" };
25   const rng = createRng(state.rngState);
26   let roll = rng.next() * geode.drops.reduce((sum, drop) => sum + drop.weight, 0);
27   const reward = geode.drops.find((drop) => { roll -= drop.weight; return roll < 0; }) ?? geode.drops[geode.drops.length - 1]!;
28   const quantity = reward.min + rng.nextInt(reward.max - reward.min + 1);
29   const added = addItem(taken.player, { id: reward.itemId, quality: "regular", quantity }, rules);
30   if (added.remaining) return { state, events: [], error: "Make room for the geode contents" };
31   return {
32     state: { ...paid, revision: state.revision + 1, rngState: rng.state, players: { ...paid.players, [playerId]: added.player } },
33     events: [{ type: "geodeCracked", playerId, areaId: player.areaId, data: { geodeId: geode.itemId, itemId: reward.itemId, quantity, cost: geode.crackPrice } }],
34   };
35 }
36