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'; interface BehaviorContext { value: number | undefined, } type ItemContext = BehaviorContext & { item: Item, user: string, } type AttackContext = ItemContext & { target: string, damage: number, } export interface Behavior { name: string, description: string, types: ('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?: (ctx: ItemContext) => Promise>, // triggers upon `weapons` attack // returns the new damage value if applicable upon success and an optional message (Left), the reason for failure otherwise (Right) onAttack?: (ctx: AttackContext) => Promise>, } const defaultFormat = (behavior: Behavior, value?: number) => `${behavior.name}${value ? ' ' + value.toString() : ''}`; export const behaviors: Behavior[] = [ { name: 'heal', description: 'Heals the user by `value`', types: ['plain', 'consumable'], async onUse(ctx) { if (!ctx.value) return new Right('A value is required for this behavior'); await dealDamage(ctx.user, -Math.floor(ctx.value)); return new Left(`You were healed by ${formatItems(BLOOD_ITEM, ctx.value)}!`); }, }, { name: 'damage', description: 'Damages the user by `value', types: ['plain', 'consumable'], async onUse(ctx) { if (!ctx.value) return new Right('A value is required for this behavior'); await dealDamage(ctx.user, Math.floor(ctx.value)); return new Left(`You were damaged by ${formatItems(BLOOD_ITEM, ctx.value)}!`); }, }, { name: 'give', description: 'Give an item of ID `value` to the user', types: ['plain', 'consumable'], }, { name: 'random_up', description: 'Randomizes the attack value up by a maximum of `value`', types: ['weapon'], format: (value) => `random +${value}`, async onAttack(ctx) { if (!ctx.value) return new Right('A value is required for this behavior'); return new Left({ damage: ctx.damage + Math.round(Math.random() * ctx.value) }); }, }, { name: 'random_down', description: 'Randomizes the attack value down by a maximum of `value`', types: ['weapon'], format: (value) => `random -${value}`, async onAttack(ctx) { if (!ctx.value) return new Right('A value is required for this behavior'); return new Left({ damage: ctx.damage - Math.round(Math.random() * ctx.value) }); }, }, { name: 'lifesteal', description: 'Gain blood by stabbing your foes, scaled by `value`x', types: ['weapon'], format: (value) => `lifesteal ${value}x`, async onAttack(ctx) { if (!ctx.value) return new Right('A value is required for this behavior'); const amt = Math.floor(ctx.damage * ctx.value); giveItem(ctx.user, BLOOD_ITEM, amt); return new Left({ message: `You gained ${formatItems(BLOOD_ITEM, amt)} from your target!` }); }, } ]; export async function getBehaviors(item: Item) { if (isDefaultItem(item)) { return item.behaviors || []; } else { return await db('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); }