All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
+### [0.3.6](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.3.6;hp=v0.3.5;ds=sidebyside) (2023-09-06)
+
+
+### Features
+
+* chat command to set player level 9ced477
+* rbac support with admin permission 940079d
+
### [0.3.5](https://git.xangelo.ca/?p=risinglegends.git;a=commitdiff;h=v0.3.5;hp=v0.3.4;ds=sidebyside) (2023-09-06)
--- /dev/null
+import { Knex } from "knex";
+
+
+export async function up(knex: Knex): Promise<void> {
+ return knex.schema.createTable('permissions', function(table){
+ table.string('name').primary();
+ }).createTable('player_permissions', function(table) {
+ table.string('permission');
+ table.uuid('player_id');
+ table.primary(['permission', 'player_id']);
+ });
+}
+
+
+export async function down(knex: Knex): Promise<void> {
+ return knex.schema.dropTable('permissions').dropTable('player_permissions');
+}
+
{
"name": "rising-legends",
- "version": "0.3.5",
+ "version": "0.3.6",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "rising-legends",
- "version": "0.3.5",
+ "version": "0.3.6",
"dependencies": {
"@honeycombio/opentelemetry-node": "^0.4.0",
"@opentelemetry/auto-instrumentations-node": "^0.37.0",
{
"name": "rising-legends",
"private": true,
- "version": "0.3.5",
+ "version": "0.3.6",
"scripts": {
"up": "npx prisma migrate dev --name \"init\"",
"start": "pm2 start dist/server/api.js",
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';
return;
}
- let message: Message;
- if(msg.startsWith('/server lmnop')) {
+ if(msg.startsWith('/server') && req.player.permissions.includes('admin')) {
try {
- 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);
- }
+ const output = await handleChatCommands(msg, req.player, io);
+ if(output) {
+ io.to(cache.get(`socket:${req.player.id}`)).emit('chat', renderChatMessage(output));
}
}
catch(e) {
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: Request, res: Response) => {
--- /dev/null
+import { Server } from 'socket.io';
+import { maxHp, maxVigor, Player } from '../shared/player';
+import { createMonsters } from '../../seeds/monsters';
+import { createAllCitiesAndLocations } from '../../seeds/cities';
+import { createShopItems, createShopEquipment } from '../../seeds/shop_items';
+import { broadcastMessage, Message } from '../shared/message';
+import { updatePlayer } from './player';
+
+export async function handleChatCommands(msg: string, player: Player, io: Server): Promise<Message> {
+ let message: Message;
+ if(msg === '/server refresh-monsters') {
+ await createMonsters();
+ message = broadcastMessage('server', 'Monster refresh!');
+ }
+ else if(msg === '/server refresh-cities') {
+ await createAllCitiesAndLocations();
+ message = broadcastMessage('server', 'Cities, Locations, and Paths refreshed!');
+ }
+ else if(msg === '/server refresh-shops') {
+ await createShopItems();
+ await createShopEquipment();
+ message = broadcastMessage('server', 'Refresh shop items');
+ }
+ else if(msg.startsWith('/server set-level')) {
+ const level = parseInt(msg.split(' ').pop());
+ if(level < 1) {
+ message = broadcastMessage('server', 'Needs to be at least level 1');
+ }
+ else {
+ message = broadcastMessage('server', `Set player level: ${level}`);
+
+ player.level = level;
+ player.strength = 4;
+ player.constitution = 4;
+ player.dexterity = 4;
+ player.intelligence = 4;
+ player.hp = maxHp(player.constitution, player.level);
+ player.vigor = maxVigor(player.constitution, player.level);
+ player.stat_points = level-1;
+
+ await updatePlayer(player);
+ }
+ }
+ else {
+ const str = msg.split('/server ')[1];
+ if(str) {
+ message = broadcastMessage('server', str);
+ }
+ }
+
+ return message;
+}
export async function loadPlayer(authToken: string): Promise<Player> {
const res = await db.first()
- .select('players.*', 'profession_levels.level', 'profession_levels.exp')
+ .select(
+ 'players.*',
+ 'profession_levels.level',
+ 'profession_levels.exp',
+ db.raw(`coalesce(pp.permissions, '[]'::json) as permissions`)
+ )
.from<Player>('players')
.join('profession_levels', function() {
this.on(function() {
this.andOn('profession_levels.profession', '=', 'players.profession')
})
})
+ .leftJoin(
+ db.raw(`(select json_agg(pp.permission) as permissions, pp.player_id from player_permissions pp group by pp.player_id) pp`),
+ 'pp.player_id','=', 'players.id'
+ )
.where({
'players.id': authToken
});
- return res;
+ return res;
}
export async function createPlayer(): Promise<Player> {
import { SkillDefinition, Skill } from './skills';
import { EquippedItemDetails } from './equipped';
+export type Permission = 'admin' | 'moderator';
+
export type Player = {
id: string,
account_type: 'session' | 'auth',
city_id: number;
stat_points: number;
vigor: number;
+ permissions: Permission[]
}
export type PlayerWithSkills = Player & {