chore(release): 0.3.3
[risinglegends.git] / src / server / monster.ts
1 import { db } from './lib/db';
2 import { Fight, Monster, MonsterWithFaction, MonsterForList, FightTrigger, MonsterVariant, MonsterVariants } from '../shared/monsters';
3 import { TimePeriod, TimeManager } from '../shared/time';
4 import { LocationWithCity } from '../shared/map';
5 import { random, sample } from 'lodash';
6 import { CHANCE_TO_FIGHT_SPECIAL } from '../shared/constants';
7
8 const time = new TimeManager();
9
10 /**
11  * return a list of monsters that
12  * - are at the current location
13  * - in the current time period
14  * - in any time period
15  */
16 export async function getMonsterList(location_id: number, timePeriod: TimePeriod[] = []): Promise<Monster[]> {
17   if(timePeriod.length === 0) {
18     timePeriod.push('any');
19     timePeriod.push(time.getTimePeriod());
20   }
21
22   const res: Monster[] = await db.select('*')
23                       .where({ location_id })
24                       .whereIn('time_period', timePeriod)
25                       .from<Monster>('monsters')
26                       .orderBy('minLevel');
27
28   return res;
29 }
30
31 export async function loadMonster(id: number): Promise<Monster> {
32   return db.select('*').from<Monster>('monsters').where({
33     id
34   }).first();
35 }
36
37 export async function loadMonsterFromFight(player_id: string): Promise<Fight> {
38   return await db.first().select('*').from<Fight>('fight').where({
39     player_id,
40   });
41 }
42
43 export async function loadMonsterWithFaction(player_id: string): Promise<MonsterWithFaction> {
44   const res = await db.raw(`
45                       select 
46                         f.*, fa.id as faction_id, fa.name as faction_name,
47                         m.minLevel, m.maxLevel
48                       from fight f
49                       join monsters m on f.ref_id = m.id
50                       left outer join factions fa on m.faction_id = fa.id
51                       where f.player_id = ?
52                         limit 1
53                       `, [player_id]);
54
55   return res.rows[0];
56 }
57
58 export async function saveFightState(authToken: string, monster: Fight) {
59   return db('fight').where({
60     player_id: authToken,
61     id: monster.id
62   }).update<Fight>({
63     hp: monster.hp
64   });
65 }
66
67 export async function createFight(playerId: string, monster: Monster, fightTrigger: FightTrigger): Promise<Fight> {
68   const chosenLevel = random(monster.minLevel, monster.maxLevel);
69   // 30% boost per level difference
70   const modifier = Math.pow(Math.E, (chosenLevel - monster.minLevel)/monster.maxLevel);
71   let variant: MonsterVariant = {
72     name: '',
73     display: '{{name}}',
74     strength: 1,
75     constitution: 1,
76     dexterity: 1,
77     intelligence: 1,
78     exp: 1,
79     gold: 1,
80     maxHp: 1,
81     defence: 1
82   };
83
84   if(monster.maxLevel >= 5 && random(0,100) <= CHANCE_TO_FIGHT_SPECIAL) {
85     variant = sample(MonsterVariants);
86   }
87
88   const monsterData: Omit<Fight, 'id'> = {
89     player_id: playerId,
90     variant: variant.name,
91     name: variant.display.replace("{{name}}", monster.name),
92     strength: Math.floor(monster.strength * modifier * variant.strength),
93     constitution: Math.floor(monster.constitution * modifier * variant.constitution),
94     dexterity: Math.floor(monster.dexterity * modifier * variant.dexterity),
95     intelligence: Math.floor(monster.intelligence * modifier * variant.intelligence),
96     exp: Math.floor(monster.exp * modifier * variant.exp),
97     level: chosenLevel,
98     gold: Math.floor(monster.gold * modifier * variant.exp),
99     hp: Math.floor(monster.hp * modifier * variant.maxHp),
100     defence: Math.floor(monster.defence * modifier * variant.defence),
101     maxHp: Math.floor(monster.maxHp * modifier * variant.maxHp),
102     ref_id: monster.id,
103     fight_trigger: fightTrigger
104   }
105
106   const res = await db('fight').insert(monsterData)
107                     .returning<Fight[]>('*');
108
109   return res.pop();
110 }
111
112 export async function getMonsterLocation(monsterId: number): Promise<LocationWithCity> {
113 return db.select(['locations.*', 'cities.name as city_name'])
114           .from<Monster>('monsters')
115           .join('locations', 'monsters.location_id', '=', 'locations.id')
116           .join('cities', 'cities.id', '=', 'locations.city_id')
117           .where({
118             'monsters.id': monsterId
119           }).first();
120 }
121
122 /**
123  * Given a list of cities, it will return a monster that 
124  * exists in any of the exploration zones with every monster 
125  * having an equal probability of appearing
126  */
127 export async function getRandomMonster(city_id: number[]): Promise<MonsterForList> {
128   const res = await db.raw('select id,name,level from monsters where location_id in (select id from locations where city_id in (?)) order by random() limit 1', [city_id.join(',')])
129
130   return res.rows[0] as MonsterForList;
131 }
132
133 export async function clearFight(authToken: string) {
134   return db('fight').where({
135     player_id: authToken
136   }).delete();
137 }