import {getShopEquipment, listShopItems } from './shopEquipment';
import {EquipmentSlot} from '../shared/inventory';
import { clearTravelPlan, completeTravel, getAllPaths, getAllServices, getCityDetails, getService, getTravelPlan, stepForward, travel } from './map';
-import { signup, login, authEndpoint, AuthRequest } from './auth';
+import { signup, login, authEndpoint } from './auth';
import {db} from './lib/db';
import { getPlayerSkills} from './skills';
+import { handleChatCommands } from './chat-commands';
import { fightRound, blockPlayerInFight } from './fight';
import { renderChatMessage } from './views/chat';
// TEMP!
-import { createMonsters } from '../../seeds/monsters';
-import { createAllCitiesAndLocations } from '../../seeds/cities';
-import { createShopItems, createShopEquipment } from '../../seeds/shop_items';
import { Item, PlayerItem, ShopItem } from 'shared/items';
import { equip, unequip } from './equipment';
import { HealthPotionSmall } from '../shared/items/health_potion';
cache.set(`socket:${player.id}`, socket.id);
// ref to get the player object
cache.set(`token:${player.id}`, player);
+ cache.set(`socket-lookup:${socket.id}`, {
+ id: player.id,
+ username: player.username
+ });
socket.emit('authToken', player.id);
socket.emit('chat', renderChatMessage(broadcastMessage('server', `${player.username} just logged in`)));
}
+function uniqueConnectedUsers(): Set<string> {
+ const users = new Set<string>();
+
+ io.sockets.sockets.forEach((socket) => {
+ users.add(cache.get(`socket-lookup:${socket.id}`).username);
+ });
+
+ return users;
+}
+
io.on('connection', async socket => {
logger.log(`socket ${socket.id} connected, authToken: ${socket.handshake.headers['x-authtoken']}`);
socket.on('disconnect', () => {
console.log(`Player ${player.username} left`);
- io.emit('status', `${io.sockets.sockets.size} Online (v${version})`);
+ cache.delete(`socket-lookup:${socket.id}`);
+
+ io.emit('status', `${uniqueConnectedUsers().size} Online (v${version})`);
});
- io.emit('status', `${io.sockets.sockets.size} Online (v${version})`);
+ io.emit('status', `${uniqueConnectedUsers().size} Online (v${version})`);
// this is a special event to let the client know it can start
// requesting data
socket.emit('ready');
app.use(repairRouter);
-app.get('/chat/history', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/chat/history', authEndpoint, async (req: Request, res: Response) => {
let html = chatHistory.map(renderChatMessage);
res.send(html.join("\n"));
});
-app.post('/chat', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/chat', authEndpoint, async (req: Request, res: Response) => {
const msg = req.body.message.trim();
if(!msg || !msg.length) {
return;
}
- let message: Message;
- if(msg.startsWith('/server lmnop')) {
- if(msg === '/server lmnop refresh-monsters') {
- await createMonsters();
- message = broadcastMessage('server', 'Monster refresh!');
- }
- else if(msg === '/server lmnop refresh-cities') {
- await createAllCitiesAndLocations();
- message = broadcastMessage('server', 'Cities, Locations, and Paths refreshed!');
- }
- else if(msg === '/server lmnop refresh-shops') {
- await createShopItems();
- await createShopEquipment();
- message = broadcastMessage('server', 'Refresh shop items');
- }
- else {
- const str = msg.split('/server lmnop ')[1];
- if(str) {
- message = broadcastMessage('server', str);
+ if(msg.startsWith('/server') && req.player.permissions.includes('admin')) {
+ try {
+ const output = await handleChatCommands(msg, req.player, io);
+ if(output) {
+ io.to(cache.get(`socket:${req.player.id}`)).emit('chat', renderChatMessage(output));
}
}
+ catch(e) {
+ io.to(cache.get(`socket:${req.player.id}`)).emit('chat', renderChatMessage(broadcastMessage('server', e.message)));
+ }
+ }
+ else if(msg === '/online') {
+ const users = Array.from(uniqueConnectedUsers().values());
+ // send to specific user
+ const message = broadcastMessage('server', `Online Players: [${users.join(", ")}]`);
+ io.to(cache.get(`socket:${req.player.id}`)).emit('chat', renderChatMessage(message));
+ res.sendStatus(204);
}
else {
- message = broadcastMessage(req.player.username, xss(msg, {
+ const message = broadcastMessage(req.player.username, xss(msg, {
whiteList: {}
}));
chatHistory.push(message);
chatHistory.slice(-10);
- }
-
- if(message) {
io.emit('chat', renderChatMessage(message));
- res.sendStatus(204);
}
+
+ res.sendStatus(204);
});
-app.get('/player', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/player', authEndpoint, async (req: Request, res: Response) => {
const equipment = await getEquippedItems(req.player.id);
res.send(renderPlayerBar(req.player) + renderProfilePage(req.player, equipment));
});
-app.post('/player/stat/:stat', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/player/stat/:stat', authEndpoint, async (req: Request, res: Response) => {
const equipment = await getEquippedItems(req.player.id);
const stat = req.params.stat;
if(!['strength', 'constitution', 'dexterity', 'intelligence'].includes(stat)) {
res.send(renderPlayerBar(req.player) + renderProfilePage(req.player, equipment));
});
-app.get('/player/skills', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/player/skills', authEndpoint, async (req: Request, res: Response) => {
const skills = await getPlayerSkills(req.player.id);
res.send(renderSkills(skills));
});
-app.get('/player/inventory', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/player/inventory', authEndpoint, async (req: Request, res: Response) => {
const [inventory, items] = await Promise.all([
getInventory(req.player.id),
getPlayersItems(req.player.id)
res.send(renderInventoryPage(inventory, items));
});
-app.post('/player/equip/:item_id/:slot', authEndpoint, blockPlayerInFight, async (req: AuthRequest, res: Response) => {
+app.post('/player/equip/:item_id/:slot', authEndpoint, blockPlayerInFight, async (req: Request, res: Response) => {
const inventoryItem = await getInventoryItem(req.player.id, req.params.item_id);
const equippedItems = await getEquippedItems(req.player.id);
const requestedSlot = req.params.slot;
res.send(renderInventoryPage(inventory, items, inventoryItem.type) + renderPlayerBar(req.player));
});
-app.post('/player/unequip/:item_id', authEndpoint, blockPlayerInFight, async (req: AuthRequest, res: Response) => {
+app.post('/player/unequip/:item_id', authEndpoint, blockPlayerInFight, async (req: Request, res: Response) => {
const [item, ] = await Promise.all([
getInventoryItem(req.player.id, req.params.item_id),
unequip(req.player.id, req.params.item_id)
res.send(renderInventoryPage(inventory, items, item.type) + renderPlayerBar(req.player));
});
-app.get('/player/explore', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/player/explore', authEndpoint, async (req: Request, res: Response) => {
const fight = await loadMonsterFromFight(req.player.id);
const travelPlan = await getTravelPlan(req.player.id);
let closestTown = req.player.city_id;
}
if(fight) {
- const data: MonsterForFight = {
- id: fight.id,
- hp: fight.hp,
- maxHp: fight.maxHp,
- name: fight.name,
- level: fight.level,
- fight_trigger: fight.fight_trigger
- };
const location = await getMonsterLocation(fight.ref_id);
-
- res.send(renderPlayerBar(req.player) + renderFightPreRound(data, true, location, closestTown));
+ res.send(renderPlayerBar(req.player) + renderFightPreRound(fight, true, location, closestTown));
}
else {
if(travelPlan) {
});
// used to purchase equipment from a particular shop
-app.put('/location/:location_id/equipment/:item_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.put('/location/:location_id/equipment/:item_id', authEndpoint, async (req: Request, res: Response) => {
const item = await getShopEquipment(parseInt(req.params.item_id), parseInt(req.params.location_id));
if(!item) {
});
// used to purchase items from a particular shop
-app.put('/location/:location_id/items/:item_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.put('/location/:location_id/items/:item_id', authEndpoint, async (req: Request, res: Response) => {
const item: (ShopItem & Item) = await getItemFromShop(parseInt(req.params.item_id), parseInt(req.params.location_id));
if(!item) {
// used to display equipment modals in a store, validates that
// the equipment is actually in this store before displaying
// the modal
-app.get('/location/:location_id/equipment/:item_id/overview', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/location/:location_id/equipment/:item_id/overview', authEndpoint, async (req: Request, res: Response) => {
const equipment = await getShopEquipment(parseInt(req.params.item_id), parseInt(req.params.location_id));
if(!equipment) {
// used to display item modals in a store, validates that
// the item is actually in this store before displaying
// the modal
-app.get('/location/:location_id/items/:item_id/overview', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/location/:location_id/items/:item_id/overview', authEndpoint, async (req: Request, res: Response) => {
const item: (ShopItem & Item) = await getItemFromShop(parseInt(req.params.item_id), parseInt(req.params.location_id));
if(!item) {
res.send(html);
});
-app.put('/item/:item_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.put('/item/:item_id', authEndpoint, async (req: Request, res: Response) => {
const item: PlayerItem = await getItemFromPlayer(req.player.id, parseInt(req.params.item_id));
if(!item) {
});
-app.get('/modal/items/:item_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/modal/items/:item_id', authEndpoint, async (req: Request, res: Response) => {
const item: PlayerItem = await getItemFromPlayer(req.player.id, parseInt(req.params.item_id));
if(!item) {
res.send(html);
});
-app.get('/city/stores/city:stores/:location_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/city/stores/city:stores/:location_id', authEndpoint, async (req: Request, res: Response) => {
const location = await getService(parseInt(req.params.location_id));
if(!location || location.city_id !== req.player.city_id) {
res.send(html);
});
-app.get('/city/explore/city:explore/:location_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/city/explore/city:explore/:location_id', authEndpoint, async (req: Request, res: Response) => {
const location = await getService(parseInt(req.params.location_id));
if(!location || location.city_id !== req.player.city_id) {
res.send(renderOnlyMonsterSelector(monsters, 0, location));
});
-app.post('/travel', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/travel', authEndpoint, async (req: Request, res: Response) => {
const destination_id = parseInt(req.body.destination_id);
if(!destination_id || isNaN(destination_id)) {
res.json(travelPlan);
});
-app.post('/fight/turn', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/fight/turn', authEndpoint, async (req: Request, res: Response) => {
const fightBlockKey = `fightturn:${req.player.id}`;
if(cache[fightBlockKey] && cache[fightBlockKey] > Date.now()) {
}
cache[fightBlockKey] = Date.now() + CONSTANT.FIGHT_ATTACK_DELAY;
- const monster = await loadMonsterWithFaction(req.player.id);
+ const monster = await loadMonsterFromFight(req.player.id);
if(!monster) {
res.send(Alert.ErrorAlert('Not in a fight'));
}
const fightData = await fightRound(req.player, monster, {
- action: req.body.action,
- target: req.body.fightTarget
+ action: req.body.action
});
res.send(html + travelSection + playerBar);
});
-app.post('/fight', fightRateLimiter, authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/fight', fightRateLimiter, authEndpoint, async (req: Request, res: Response) => {
if(req.player.hp <= 0) {
logger.log(`Player didn\'t have enough hp`);
return res.sendStatus(400);
const fight = await createFight(req.player.id, monster, fightTrigger);
const location = await getService(monster.location_id);
-
- const data: MonsterForFight = {
- id: fight.id,
- hp: fight.hp,
- maxHp: fight.maxHp,
- name: fight.name,
- level: fight.level,
- fight_trigger: fight.fight_trigger
- };
-
- res.send(renderFightPreRound(data, true, location, location.city_id));
+ res.send(renderFightPreRound(fight, true, location, location.city_id));
});
-app.post('/travel/step', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/travel/step', authEndpoint, async (req: Request, res: Response) => {
const stepTimerKey = `step:${req.player.id}`;
const travelPlan = await getTravelPlan(req.player.id);
}
});
-app.post('/travel/return-to-source', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/travel/return-to-source', authEndpoint, async (req: Request, res: Response) => {
// puts the player back in their starting town
// doesn't matter if they don't have one
// redirect them!
const fight = await loadMonsterFromFight(req.player.id);
if(fight) {
// go to the fight screen
- const data: MonsterForFight = {
- id: fight.id,
- hp: fight.hp,
- maxHp: fight.maxHp,
- name: fight.name,
- level: fight.level,
- fight_trigger: fight.fight_trigger
- };
const location = await getMonsterLocation(fight.ref_id);
- res.send(renderPlayerBar(req.player) + renderFightPreRound(data, true, location, req.player.city_id));
+ res.send(renderPlayerBar(req.player) + renderFightPreRound(fight, true, location, req.player.city_id));
}
else {
const [city, locations, paths] = await Promise.all([
});
-app.post('/travel/:destination_id', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/travel/:destination_id', authEndpoint, async (req: Request, res: Response) => {
if(req.player.hp <= 0) {
logger.log(`Player didn\'t have enough hp`);
res.send(Alert.ErrorAlert('Sorry, you need some HP to start travelling.'));
}));
});
-app.get('/settings', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.get('/settings', authEndpoint, async (req: Request, res: Response) => {
let warning = '';
let html = '';
if(req.player.account_type === 'session') {
res.send(warning + html);
});
-app.post('/logout', authEndpoint, async (req: AuthRequest, res: Response) => {
+app.post('/logout', authEndpoint, async (req: Request, res: Response) => {
// ref to get the socket id for a particular player
cache.delete(`socket:${req.player.id}`);
// ref to get the player object