jillo-bot/src/lib/rpg/behaviors.ts

91 lines
3.4 KiB
TypeScript

import { giveItem, type Item, isDefaultItem, formatItems } from './items';
import { Either, Left, Right } from '../util';
import { ItemBehavior, db } from '../db';
import { BLOOD_ITEM, dealDamage } from './pvp';
export interface Behavior {
name: string,
description: string,
itemType: 'plain' | 'weapon' | 'consumable',
// make it look fancy
format?: (value?: number) => string,
// triggers upon use
// for 'weapons', this is on attack
// for 'consumable' and `plain`, this is on use
// returns Left upon success with an optional message, the reason for failure otherwise (Right)
onUse?: (value: number | undefined, item: Item, user: string) => Promise<Either<string | null, string>>,
// triggers upon `weapons` attack
// returns the new damage value upon success (Left), the reason for failure otherwise (Right)
onAttack?: (value: number | undefined, item: Item, user: string, target: string, damage: number) => Promise<Either<number, string>>,
}
const defaultFormat = (behavior: Behavior, value?: number) => `${behavior.name}${value ? ' ' + value.toString() : ''}`;
export const behaviors: Behavior[] = [
{
name: 'heal',
description: 'Heals the user by `value`',
itemType: 'consumable',
async onUse(value, item, user) {
if (!value) return new Right('A value is required for this behavior');
await dealDamage(user, -Math.floor(value));
return new Left(`You were healed by ${formatItems(BLOOD_ITEM, value)}!`);
},
},
{
name: 'damage',
description: 'Damages the user by `value',
itemType: 'consumable',
async onUse(value, item, user) {
if (!value) return new Right('A value is required for this behavior');
await dealDamage(user, Math.floor(value));
return new Left(`You were damaged by ${formatItems(BLOOD_ITEM, value)}!`);
},
},
{
name: 'random_up',
description: 'Randomizes the attack value up by a maximum of `value`',
itemType: 'weapon',
format: (value) => `random +${value}`,
async onAttack(value, item, user, target, damage) {
if (!value) return new Right('A value is required for this behavior');
return new Left(damage + Math.round(Math.random() * value));
},
},
{
name: 'random_down',
description: 'Randomizes the attack value down by a maximum of `value`',
itemType: 'weapon',
format: (value) => `random -${value}`,
async onAttack(value, item, user, target, damage) {
if (!value) return new Right('A value is required for this behavior');
return new Left(damage - Math.round(Math.random() * value));
},
},
{
name: 'lifesteal',
description: 'Gain blood by stabbing your foes, scaled by `value`x',
itemType: 'weapon',
format: (value) => `lifesteal: ${value}x`,
async onAttack(value, item, user, target, damage) {
if (!value) return new Right('A value is required for this behavior');
giveItem(user, BLOOD_ITEM, Math.floor(damage * value));
return new Left(damage);
},
}
];
export async function getBehaviors(item: Item) {
if (isDefaultItem(item)) {
return item.behaviors || [];
} else {
return await db<ItemBehavior>('itemBehaviors')
.where('item', item.id);
}
}
export function getBehavior(behavior: string) {
return behaviors.find(b => b.name === behavior);
}
export function formatBehavior(behavior: Behavior, value: number | undefined) {
return behavior.format ? behavior.format(value) : defaultFormat(behavior, value);
}