proc-gen equipment drops from monsters can be picked up
[sketchy-heroes.git] / src / routes / inventory / pick.ts
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';
10
11 type rarityDropRates = {
12   rarity: Rarity,
13   weight: number
14 };
15
16 type StatModifierStat = {
17   name: string;
18   min: number;
19   max: number;
20 }
21 type StatModifiers = {
22   stats: StatModifierStat[],
23   required: string[]
24 };
25
26 const WorldDropInput = Type.Object({
27   params: Type.Object({
28     playerId: Type.String(),
29     dropId: Type.String()
30   })
31 });
32
33 type InputType = Static<typeof WorldDropInput>;
34
35 export type pickWorldDropOutput = {
36   item: Inventory
37 }
38
39 export const pickWorldDrop = server.post<InputType, pickWorldDropOutput>('/v1/accounts/:playerId/pick/:dropId', {
40   schema: WorldDropInput,
41   authenicated: true
42 }, async req => {
43   const player = await prisma.player.findUnique({
44     where: {
45       id: req.params.playerId
46     }
47   });
48
49   if(!player) {
50     throw new ForbiddenError();
51   }
52   const drop = await prisma.worldDrop.findUnique({
53     where: {
54       id: req.params.dropId
55     },
56     include: {
57       baseItem: true
58     }
59   });
60
61   if(!drop) {
62     throw new NotFoundError(`Drop ${req.params.dropId} is not valid`);
63   }
64
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);
68
69   if(selectedRarity === undefined)  {
70     selectedRarity = {
71       rarity: Rarity.COMMON,
72       weight: 1
73     };
74   }
75
76   console.log(selectedRarity);
77
78   // 2. figure out durability
79   //We're just going to bump durability by ~25% for each rarity type
80   const rarityDurabilityMap = {
81     [Rarity.COMMON]: 1,
82     [Rarity.UNCOMMON]: 1.15,
83     [Rarity.RARE]: 1.26
84   }
85   const durability = Math.ceil(drop.baseItem.durability * rarityDurabilityMap[selectedRarity.rarity]);
86
87   console.log('durability', durability, drop.baseItem, rarityDurabilityMap, selectedRarity.rarity);
88
89   // 3. figure out stat modifiers 
90   const stats: Record<string, number> = {
91     pow: 0,
92     zest: 0,
93     woosh: 0,
94     luck: 0,
95     aha: 0,
96     wow: 0,
97     stamina: 0,
98     hp: 0
99   };
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);
105       selectedStats++;
106     });
107   });
108
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);
114     if(stat) {
115       selectedStats++;
116       stats[stat.name] += random(stat.min, stat.max);
117     }
118   }
119
120   if(selectedRarity.rarity === Rarity.RARE) {
121     for(let i = 0; i < 2; ++i) {
122       const stat = sample<StatModifierStat>(mods.stats);
123       if(stat) {
124         selectedStats++;
125         stats[stat.name] += random(stat.min, stat.max);
126       }
127     }
128   }
129
130   // 4. Calculate uses until discovery
131   // each stat assigned to an item increases its uses 
132   // by 20. 
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);
136
137
138   // 5. Give the player the new item, but set it to undiscovered
139   const inventoryItem = await prisma.inventory.create({
140     data: {
141       playerId: player.id,
142       itemId: drop.itemId,
143       discovered: false,
144       usesUntilDiscovery: discover,
145       maxDurability: durability,
146       currentDurability: durability,
147       rarity: selectedRarity.rarity,
148       type: drop.baseItem.type,
149       statModifiers: stats,
150       stackable: false
151     }
152   });
153
154   return {item: inventoryItem};
155 });