1 import { server } from '@lib/server';
2 import { Static, Type } from '@sinclair/typebox';
3 import { prisma } from '@lib/db';
4 import { ForbiddenError, NotFoundError } from '@lib/http-errors';
5 import { logger } from '@lib/logger';
6 import {weighted} from '@lib/weighted';
7 import {Inventory, Rarity} from '@prisma/client';
8 import Decimal from 'decimal.js';
9 import { random, sample } from 'lodash';
11 type rarityDropRates = {
16 type StatModifierStat = {
21 type StatModifiers = {
22 stats: StatModifierStat[],
26 const WorldDropInput = Type.Object({
28 playerId: Type.String(),
33 type InputType = Static<typeof WorldDropInput>;
35 export type pickWorldDropOutput = {
39 export const pickWorldDrop = server.post<InputType, pickWorldDropOutput>('/v1/accounts/:playerId/pick/:dropId', {
40 schema: WorldDropInput,
43 const player = await prisma.player.findUnique({
45 id: req.params.playerId
50 throw new ForbiddenError();
52 const drop = await prisma.worldDrop.findUnique({
62 throw new NotFoundError(`Drop ${req.params.dropId} is not valid`);
65 // figure out the item stats and award to the player
66 // 1. get rarity - this functions as a modifier for everything else
67 let selectedRarity = weighted<rarityDropRates>(drop.baseItem.rarityDropRates as rarityDropRates[], i => i.weight);
69 if(selectedRarity === undefined) {
71 rarity: Rarity.COMMON,
76 console.log(selectedRarity);
78 // 2. figure out durability
79 //We're just going to bump durability by ~25% for each rarity type
80 const rarityDurabilityMap = {
82 [Rarity.UNCOMMON]: 1.15,
85 const durability = Math.ceil(drop.baseItem.durability * rarityDurabilityMap[selectedRarity.rarity]);
87 console.log('durability', durability, drop.baseItem, rarityDurabilityMap, selectedRarity.rarity);
89 // 3. figure out stat modifiers
90 const stats: Record<string, number> = {
100 let selectedStats = 0;
101 const mods = drop.baseItem.statModifiers as StatModifiers;
102 mods.required.forEach(name => {
103 mods.stats.filter(s => s.name === name).forEach(stat => {
104 stats[stat.name] += random(stat.min, stat.max);
109 // the item now has the "Common" number of stat modifiers.
110 // uncommon will have 1 extra stat modifier
111 // rare will have 2 extra stat modifiers
112 if(selectedRarity.rarity === Rarity.UNCOMMON) {
113 const stat = sample<StatModifierStat>(mods.stats);
116 stats[stat.name] += random(stat.min, stat.max);
120 if(selectedRarity.rarity === Rarity.RARE) {
121 for(let i = 0; i < 2; ++i) {
122 const stat = sample<StatModifierStat>(mods.stats);
125 stats[stat.name] += random(stat.min, stat.max);
130 // 4. Calculate uses until discovery
131 // each stat assigned to an item increases its uses
133 // Each rarity level adds another 20
134 const rarityDiscovery = [Rarity.COMMON, Rarity.UNCOMMON, Rarity.RARE];
135 const discover = selectedStats * 20 + (rarityDiscovery.indexOf(selectedRarity.rarity) > 0 ? rarityDiscovery.indexOf(selectedRarity.rarity) * 20 : 0);
138 // 5. Give the player the new item, but set it to undiscovered
139 const inventoryItem = await prisma.inventory.create({
144 usesUntilDiscovery: discover,
145 maxDurability: durability,
146 currentDurability: durability,
147 rarity: selectedRarity.rarity,
148 type: drop.baseItem.type,
149 statModifiers: stats,
154 return {item: inventoryItem};