Skip to main content
Glama
scroll.ts5.95 kB
import { z } from 'zod'; import { SpellcastingClassSchema } from './spell.js'; /** * Scroll Properties Schema * Extended properties for scroll items that contain spells */ export const ScrollPropertiesSchema = z.object({ spellName: z.string().min(1, 'Spell name is required'), spellLevel: z.number().int().min(0).max(9).describe('Spell level (0 for cantrips, 1-9 for leveled spells)'), scrollDC: z.number().int().min(10).optional().describe('Default spell save DC if caster has no spellcasting ability'), scrollAttackBonus: z.number().int().optional().describe('Default spell attack bonus if caster has no spellcasting ability'), requiresCheck: z.boolean().default(false).describe('Whether scroll use requires an Arcana check'), checkDC: z.number().int().min(10).optional().describe('DC for Arcana check to use scroll'), spellClass: SpellcastingClassSchema.optional().describe('Class list the spell is on (for determining if check is required)') }); export type ScrollProperties = z.infer<typeof ScrollPropertiesSchema>; /** * Scroll Usage Validation Request */ export const ScrollUsageValidationSchema = z.object({ characterId: z.string().describe('Character attempting to use the scroll'), itemId: z.string().describe('ID of the scroll item'), spellName: z.string().describe('Name of the spell on the scroll'), spellLevel: z.number().int().min(0).max(9).describe('Level of the spell'), spellClass: SpellcastingClassSchema.optional().describe('Class list the spell is on'), targetId: z.string().optional().describe('Target for the spell'), targetPoint: z.object({ x: z.number(), y: z.number() }).optional().describe('Target point for area spells') }); export type ScrollUsageValidation = z.infer<typeof ScrollUsageValidationSchema>; /** * Scroll Usage Result */ export const ScrollUsageResultSchema = z.object({ success: z.boolean().describe('Whether the scroll was used successfully'), consumed: z.boolean().describe('Whether the scroll was consumed (even on failure)'), requiresCheck: z.boolean().describe('Whether an Arcana check was required'), checkRoll: z.number().int().optional().describe('The d20 roll for the Arcana check'), checkTotal: z.number().int().optional().describe('Total Arcana check result'), checkDC: z.number().int().optional().describe('DC for the Arcana check'), checkPassed: z.boolean().optional().describe('Whether the check passed'), reason: z.enum([ 'auto_success', // Spell on class list and can cast that level 'check_passed', // Required check and passed 'check_failed', // Required check and failed 'not_in_inventory', // Character doesn't have the scroll 'spell_cast', // Spell successfully cast 'invalid_scroll' // Scroll data is invalid ]).describe('Reason for the result'), spellCastResult: z.record(z.any()).optional().describe('Result of the spell cast if successful'), message: z.string().describe('Human-readable message about the result') }); export type ScrollUsageResult = z.infer<typeof ScrollUsageResultSchema>; /** * Scroll Creation Request */ export const CreateScrollRequestSchema = z.object({ spellName: z.string().min(1, 'Spell name is required'), spellLevel: z.number().int().min(0).max(9), scrollDC: z.number().int().min(10).optional() .describe('Default DC (usually 13 + spell level for crafted scrolls)'), scrollAttackBonus: z.number().int().optional() .describe('Default attack bonus (usually +5 + spell level)'), spellClass: SpellcastingClassSchema.optional() .describe('Class list the spell is on'), value: z.number().int().min(0).optional() .describe('Gold value of the scroll'), description: z.string().optional() .describe('Custom description for the scroll') }); export type CreateScrollRequest = z.infer<typeof CreateScrollRequestSchema>; /** * Scroll rarity based on spell level */ export enum ScrollRarity { COMMON = 'common', // Cantrips UNCOMMON = 'uncommon', // 1st-3rd level RARE = 'rare', // 4th-5th level VERY_RARE = 'very_rare', // 6th-8th level LEGENDARY = 'legendary' // 9th level } /** * Get scroll rarity based on spell level */ export function getScrollRarity(spellLevel: number): ScrollRarity { if (spellLevel === 0) return ScrollRarity.COMMON; if (spellLevel <= 3) return ScrollRarity.UNCOMMON; if (spellLevel <= 5) return ScrollRarity.RARE; if (spellLevel <= 8) return ScrollRarity.VERY_RARE; return ScrollRarity.LEGENDARY; } /** * Calculate default scroll DC based on spell level * Formula: 13 + spell level (standard for crafted scrolls) */ export function calculateScrollDC(spellLevel: number): number { return 13 + spellLevel; } /** * Calculate default scroll attack bonus based on spell level * Formula: 5 + spell level (standard for crafted scrolls) */ export function calculateScrollAttackBonus(spellLevel: number): number { return 5 + spellLevel; } /** * Calculate scroll value based on spell level and rarity * Based on DMG scroll prices */ export function calculateScrollValue(spellLevel: number): number { const valueLookup: Record<number, number> = { 0: 25, // Cantrip - Common (25-50gp) 1: 75, // 1st level - Uncommon (50-100gp) 2: 150, // 2nd level - Uncommon (100-200gp) 3: 300, // 3rd level - Uncommon (200-400gp) 4: 750, // 4th level - Rare (500-1000gp) 5: 1500, // 5th level - Rare (1000-2000gp) 6: 5000, // 6th level - Very Rare (2500-7500gp) 7: 10000, // 7th level - Very Rare (5000-15000gp) 8: 20000, // 8th level - Very Rare (10000-30000gp) 9: 50000 // 9th level - Legendary (25000-100000gp) }; return valueLookup[spellLevel] || 100; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Mnehmos/rpg-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server