1 import {Player} from '@prisma/client';
2 import axios from 'axios';
3 import { Events } from './events';
4 import { LoginOutputType, AccountCreateType, MoveOutputType, FightStartType, FightRoundOutput } from 'src/routes';
5 import {actionLog} from './components';
6 import {disableFightButton, disablePickItemButton, enableFightButton, enablePickItemButton} from './dom';
7 import {pickWorldDropOutput} from 'src/routes/inventory';
9 type ApiResponse<T> = {
10 status: 'ok' | 'error',
15 processingTime: number
20 export class Api extends Events {
22 headers: Record<string, string>;
23 player: Player | null;
25 constructor(root: string) {
29 // we use the headers for auth data
34 setPlayer(player: Player) {
36 this.emit('player', player);
40 async get<T>(endpoint: string, params: Record<string, string> = {}): Promise<ApiResponse<T>> {
41 const res = await axios({
49 if(res.data.status !== 'ok') {
50 throw new Error(res.data.payload.toString());
56 async post<T>(endpoint: string, data: any = {}): Promise<ApiResponse<T>> {
57 const res = await axios({
65 if(res.data.status !== 'ok') {
66 throw new Error(res.data.payload.toString());
72 async signup(username: string, password: string, confirmation: string): Promise<AccountCreateType> {
73 const res = await this.post<AccountCreateType>('/v1/accounts', {
76 confirmation: confirmation
82 async setGameTime(gameTime: number) {
83 const str = gameTime < 10 ? `0${gameTime}` : gameTime.toString();
85 $('body').removeClass().addClass(`sky-gradient-${str}`);
88 async syncPlayerInfo() {
90 throw new Error('Not authenticated');
92 const res = await this.get<Player>(`/v1/accounts/${this.player.id}`);
94 this.setPlayer(res.payload);
96 // this also sets the background based on the time!
97 this.setGameTime(res.meta.gameTime);
99 setTimeout(this.syncPlayerInfo.bind(this), 10000);
102 async login(username: string, password: string): Promise<LoginOutputType> {
103 const res = await this.post<LoginOutputType>('/v1/accounts/auth', {
108 this.headers['x-auth-token'] = res.payload.token;
110 this.setPlayer(res.payload.player);
112 this.setGameTime(res.meta.gameTime);
115 this.syncPlayerInfo();
120 async move(): Promise<MoveOutputType> {
121 if(this.player === null) {
122 throw new Error('Not authenicated');
124 const res = await this.post<MoveOutputType>(`/v1/accounts/${this.player.id}/move`);
126 this.setPlayer(res.payload.player);
128 if(res.payload.generatedMonster !== null) {
129 actionLog(`You see a ${res.payload.generatedMonster.name}`);
130 if(this.player.hp > 0 && this.player.stamina > 0) {
131 enableFightButton({fightId: res.payload.generatedMonster.id});
135 actionLog(res.payload.displayText, true);
136 disableFightButton();
142 async increaseStat(stat: string): Promise<Player> {
143 if(this.player === null) {
144 throw new Error('Not authenticated');
146 const res = await this.post<Player>(`/v1/accounts/${this.player.id}/stat`, {
150 this.setPlayer(res.payload);
154 async startFight(fightId: string): Promise<FightStartType> {
155 if(this.player === null) {
156 throw new Error('Not authenticated');
159 const res = await this.post<FightStartType>(`/v1/accounts/${this.player.id}/fight/${fightId}`);
161 if(!res.payload.id) {
162 throw new Error('Invalid fight!');
168 async pickItem(dropId: string): Promise<void> {
169 if(this.player === null) {
170 throw new Error('Not authenticated');
173 disablePickItemButton();
175 const res = await this.post<pickWorldDropOutput>(`/v1/accounts/${this.player.id}/pick/${dropId}`);
177 actionLog(`You picked up the item!`, false);
180 async fight(fightId: string): Promise<FightRoundOutput> {
181 if(this.player === null) {
182 throw new Error('Not authenticated');
185 await this.startFight(fightId);
187 const res = await this.post<FightRoundOutput>(`/v1/accounts/${this.player.id}/fight/${fightId}/round`, {
191 const output = res.payload;
193 this.setPlayer(output.player);
197 const participants = {
198 [output.player.id]: output.player.username,
199 [output.monster.id]: output.monster.name
202 output.roundData.map(round => {
203 const p1 = participants[round.attacker];
204 const p2 = participants[round.defender];
206 const css = round.attacker === output.player.id ? 'text-secondary' : 'text-info';
207 let str = `<span class="${css}">${p1}</span> dealt <span class="text-danger">${round.damage} damage</span> to ${p2}`;
210 }).forEach(a => actionLog(a, false));
212 if(output.winner === 'player') {
213 actionLog(`<span class="text-success">You defeated the ${output.monster.name}</span>`, false);
216 actionLog(`<span class="text-danger">You were defeated by the ${output.monster.name}</span>`, false);
219 if(output.reward.exp) {
220 actionLog(`You gained <span class="text-info">${output.reward.exp}</span> Exp`, false);
223 if(output.reward.currency) {
224 actionLog(`You gained <span class="text-warning">${output.reward.currency}</span> Steel`, false);
227 if(output.reward.worldDrop) {
228 enablePickItemButton({itemId: output.reward.worldDrop.id}, output.reward.worldDrop.name);
231 disableFightButton();