1 import { server } from '../lib/server';
2 import { Static, Type } from '@sinclair/typebox';
3 import { prisma } from '../lib/db';
4 import { ForbiddenError } from '../lib/http-errors';
5 import { random, sample } from 'lodash';
6 import {Biome, Player, Fight} from '@prisma/client';
7 import {now} from '../lib/time';
8 import {generateMonster} from '../services/generate-monster';
10 type RawMonsterSelection = {
13 time: {min: number, max:number}[]
16 const MoveInput = Type.Object({
18 playerId: Type.String()
22 type MoveInputType = Static<typeof MoveInput>;
24 export type MoveOutputType = {
31 generatedMonster: Fight | null
34 const moveStatements = [
36 "You take a few steps",
37 "You pause for a second before hurrying along",
38 "You stumble forward..."
41 export const move = server.post<MoveInputType, MoveOutputType>('/v1/accounts/:playerId/move', {
46 let generatedMonster: Fight | null = null;
48 const player = await prisma.player.findUnique({
50 id: req.params.playerId
58 throw new ForbiddenError();
61 if(player.stamina <= 0) {
62 throw new ForbiddenError('Insufficient Stamina');
69 if(player.steps > player.zoneBiome.maxSteps) {
70 // ok, move them to a new zone!
71 const chosenBiome = sample(Object.values(Biome)) as Biome;
72 const biome = await prisma.zoneBiomes.create({
75 maxSteps: random(500, 1500)
79 await prisma.zoneBiomes.delete({
81 id: player.zoneBiomeId
85 player.zoneBiome = biome;
90 await prisma.player.update({
96 stamina: player.stamina
101 // now that you have moved, you have 40% chance of fighting a monster
102 if(random(0, 100) < 20) {
103 // ok it's battle time!
104 // retrieve all monsters from the database that are in this biome
105 // that are within the timeframe specified!
106 const monsters = await prisma.$queryRaw`
107 WITH p AS ( -- probability
109 weight::NUMERIC / sum(weight) OVER () AS probability
111 WHERE biome = ${player.zoneBiome.biome}
113 cp AS ( -- cumulative probability
115 sum(p.probability) OVER (
116 ORDER BY probability DESC
117 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
124 cp.cumprobability - cp.probability AS startprobability,
125 cp.cumprobability AS endprobability
129 SELECT "monsterId", biome, time FROM fp
130 WHERE random() BETWEEN startprobability AND endprobability;
131 ` as RawMonsterSelection[];
133 // now lets filter out the timezones!
134 const moment = now();
135 const availableMonsters = monsters.filter(monster => {
136 return monster.time.filter(time => {
137 return time.min <= moment && time.max >= moment;
141 if(availableMonsters) {
142 const selectedMonster = sample(availableMonsters);
143 if(selectedMonster) {
144 const monster = await prisma.monster.findUnique({
146 id: selectedMonster.monsterId
151 generatedMonster = await prisma.fight.create({
152 data: generateMonster(player, monster)
164 id: player.zoneBiomeId,
165 biome: player.zoneBiome.biome
168 displayText: sample(moveStatements) || moveStatements[0]