chore(release): 0.4.0
[risinglegends.git] / src / server / dungeon.ts
1 import { Fight } from "shared/monsters";
2 import { Dungeon, DungeonRoom, DungeonPlayer, DungeonState, DungeonStateSummaryVists, DungeonStateSummaryFights } from "../shared/dungeon";
3 import { db } from './lib/db';
4 import { Request, Response } from 'express';
5 import { ErrorAlert } from "./views/alert";
6 import { addEvent } from './events';
7
8 export async function blockPlayerInDungeon(req: Request, res: Response, next: any) {
9   const state = await getActiveDungeon(req.player.id);
10   if(!state) {
11     next();
12   }
13   else {
14     res.send(ErrorAlert('You are currently exploring a dungeon'));
15   }
16 }
17
18 export async function getActiveDungeon(player_id: string): Promise<DungeonPlayer> {
19   return db.select('*').from<DungeonPlayer>('dungeon_players').where({
20     player_id
21   }).first();
22 }
23
24 export async function loadDungeon(dungeon_id: string): Promise<Dungeon> {
25   return db.select('*').from<Dungeon>('dungeons').where({id: dungeon_id}).first();
26 }
27
28 export async function getDungeonState(player_id: string, dungeon_id: string) {
29   return db.select('*').from<DungeonState>('dungeon_state').where({
30     player_id, 
31     dungeon_id
32   });
33 }
34
35 export async function addNewState(player_id: string, dungeon_id: string, event_name: string, props: Record<string, any> = {}) {
36   return db('dungeon_state').insert({
37     player_id,
38     dungeon_id,
39     event_name,
40     event_props: props
41   });
42 }
43 // in a dungeon fight, we dont care what room the player is in, 
44 // but we care what room they are going to
45 export async function completeDungeonFight(player_id: string, monster: Fight): Promise<DungeonPlayer> {
46   const activeDungeon = await getActiveDungeon(player_id);
47
48   await addNewState(player_id, activeDungeon.dungeon_id, 'FIGHT_COMPLETE', {
49     monster_id: monster.ref_id,
50     target_room: activeDungeon.target_room_id,
51     source_room: activeDungeon.current_room_id
52   });
53
54   await movePlayerToRoomInDungeon(player_id, activeDungeon.dungeon_id, activeDungeon.target_room_id);
55
56   return getActiveDungeon(player_id);
57 }
58
59 export async function movePlayerToRoomInDungeon(player_id: string, dungeon_id: string, room_id: string) {
60   await updatePlayerDungeonState(player_id, dungeon_id, { 
61     current_room_id: room_id,
62     target_room_id: room_id
63   });
64
65   await addNewState(player_id, dungeon_id, 'ROOM_VISIT', {
66     room_id
67   });
68 }
69
70 export async function getRoomVists(player_id: string, dungeon_id: string): Promise<DungeonStateSummaryVists> {
71
72   const res = await db.raw(`select 
73 count(id) as visits, 
74 event_props->>'room_id' as room_id
75 from dungeon_state
76 group by event_props->>'room_id',player_id,dungeon_id,event_name
77 having player_id = ? and dungeon_id = ? and event_name = ?
78 `, [
79     player_id,
80     dungeon_id,
81     'ROOM_VISIT'
82   ]);
83
84   let data = {};
85   res.rows.forEach((row: {room_id: string, visits: string}) => {
86     data[row.room_id] = parseInt(row.visits);
87   });
88
89   return data;
90 }
91
92 export async function getUniqueFights(player_id: string, dungeon_id: string): Promise<DungeonStateSummaryFights> {
93   // fights always happen between movement source->target. So when we record
94   // the state of a fight, we record the source/target room props.
95   // however, when we are checking if a fight occurrent in a room.. we really 
96   // only care about the TARGET room. 
97   const res = await db.raw(`select 
98 count(id) as fights,
99 event_props->>'monster_id' as monster_id,
100 event_props->>'target_room' as room_id
101 from dungeon_state 
102 group by event_props->>'monster_id',event_props->>'target_room',
103 player_id,dungeon_id,event_name 
104 having player_id = ? and dungeon_id = ? and event_name = ?
105 `, [
106     player_id,
107     dungeon_id,
108     'FIGHT_COMPLETE'
109   ]);
110
111   let data = {};
112   res.rows.forEach((row: {fights: string, monster_id: string, room_id: string}) => {
113     if(!data[row.room_id]) {
114       data[row.room_id] = {};
115     }
116
117     if(!data[row.room_id][row.monster_id]) {
118       data[row.room_id][row.monster_id] = row.fights;
119     }
120   });
121
122   return data;
123 }
124
125 export async function putPlayerInDungeon(player_id: string, dungeon_id: string): Promise<DungeonPlayer> {
126   const res: {rows: DungeonPlayer[] }= await db.raw(`
127     insert into dungeon_players 
128 (player_id, dungeon_id, current_room_id) 
129 values 
130 (?, ?, (select starting_room from dungeons where id = ?)) returning *`, [player_id, dungeon_id, dungeon_id])
131
132   const data = res.rows.pop();
133
134   await addNewState(player_id, dungeon_id, 'ROOM_VISIT', {
135     room_id: data.current_room_id
136   });
137
138   return data;
139 }
140
141
142 export async function updatePlayerDungeonState(player_id: string, dungeon_id: string, fields: any) {
143   const res = await db('dungeon_players').where({
144     player_id,
145     dungeon_id
146   }).update(fields).returning('*');
147
148   return res.pop();
149 }
150
151 export async function loadRoom(room_id: string): Promise<DungeonRoom | undefined> {
152   return db.select('*')
153             .from<DungeonRoom>('dungeon_rooms')
154             .where({id: room_id})
155             .first();
156 }
157
158 export async function completeDungeon(player_id: string) {
159   const dungeonPlayer = await getActiveDungeon(player_id);
160   const dungeonState = await getDungeonState(player_id, dungeonPlayer.dungeon_id);
161
162   await db('dungeon_players').where({player_id}).delete();
163   await db('dungeon_state').where({player_id, dungeon_id: dungeonPlayer.dungeon_id}).delete();
164
165   let startTime = Number.MAX_VALUE;
166   let endTime = 0;
167   dungeonState.forEach(s => {
168     startTime = Math.min(startTime, s.create_date);
169     endTime = Math.max(endTime, s.create_date);
170   });
171
172   addEvent('DUNGEON_COMPLETE', player_id, {
173     dungeon_id: dungeonPlayer.dungeon_id,
174     start: startTime,
175     end: endTime,
176     duration: endTime - startTime
177   });
178 }