106 lines
3.6 KiB
TypeScript
106 lines
3.6 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';
|
|
|
|
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,
|
|
type: '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<Either<string | null, string>>,
|
|
// 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<Either<{damage?: number, message?: string}, string>>,
|
|
}
|
|
|
|
const defaultFormat = (behavior: Behavior, value?: number) => `${behavior.name}${value ? ' ' + value.toString() : ''}`;
|
|
|
|
export const behaviors: Behavior[] = [
|
|
{
|
|
name: 'heal',
|
|
description: 'Heals the user by `value`',
|
|
type: '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',
|
|
type: '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: 'random_up',
|
|
description: 'Randomizes the attack value up by a maximum of `value`',
|
|
type: '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`',
|
|
type: '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',
|
|
type: '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<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);
|
|
} |