check_cover
Calculate cover between attacker and target positions to determine Armor Class and Dexterity save bonuses for combat scenarios.
Instructions
Check cover between attacker and target, returning AC and Dex save bonuses
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| attacker | Yes | ||
| target | Yes | ||
| obstacles | No | ||
| creatures | No | ||
| creaturesProvideCover | No |
Implementation Reference
- src/modules/spatial.ts:1170-1292 (handler)Main handler function for check_cover tool. Computes the highest level of cover between attacker and target positions by checking obstacles and creatures on the line of sight. Returns ASCII-formatted output with AC and Dex save bonuses based on D&D 5e rules (none: +0, half: +2, three-quarters: +5, total: blocked).export function checkCover(input: CheckCoverInput): string { const content: string[] = []; const attackerPos: Position = { x: input.attacker.x, y: input.attacker.y, z: input.attacker.z ?? 0, }; const targetPos: Position = { x: input.target.x, y: input.target.y, z: input.target.z ?? 0, }; // Calculate distance const dx = targetPos.x - attackerPos.x; const dy = targetPos.y - attackerPos.y; const dz = (targetPos.z ?? 0) - (attackerPos.z ?? 0); const distance = Math.sqrt(dx * dx + dy * dy + dz * dz) * 5; // Convert to feet // Build header content.push(centerText('COVER CHECK', COVER_DISPLAY_WIDTH)); content.push(''); content.push(`Attacker: (${attackerPos.x}, ${attackerPos.y}, ${attackerPos.z ?? 0})`); content.push(`Target: (${targetPos.x}, ${targetPos.y}, ${targetPos.z ?? 0})`); content.push(`Distance: ${Math.round(distance)} feet`); content.push(''); content.push(BOX.LIGHT.H.repeat(COVER_DISPLAY_WIDTH)); content.push(''); // Find highest cover level from all obstacles let coverLevel: CoverLevel = 'none'; const coverSources: Array<{ type: string; position: string }> = []; // Check static obstacles const obstacles = input.obstacles || []; for (const obstacle of obstacles) { // Skip obstacles at the attacker's position if ( obstacle.x === attackerPos.x && obstacle.y === attackerPos.y && (obstacle.z ?? 0) === (attackerPos.z ?? 0) ) { continue; } const obstacleCover = calculateCoverFromObstacle(obstacle, attackerPos, targetPos); if (obstacleCover !== null) { coverLevel = determineCoverLevel(coverLevel, obstacleCover); if (obstacleCover !== 'none') { coverSources.push({ type: obstacle.type, position: `(${obstacle.x}, ${obstacle.y}, ${obstacle.z ?? 0})`, }); } } } // Check creature cover if (input.creaturesProvideCover && input.creatures) { for (const creature of input.creatures) { if (isOnLine(attackerPos, targetPos, creature)) { const creatureCover = getCreatureCover(creature.size); coverLevel = determineCoverLevel(coverLevel, creatureCover); if (creatureCover !== 'none') { coverSources.push({ type: `${creature.size} creature`, position: `(${creature.x}, ${creature.y}, ${creature.z ?? 0})`, }); } } } } // Display cover sources if any if (coverSources.length > 0) { content.push(centerText('COVER SOURCES', COVER_DISPLAY_WIDTH)); content.push(''); for (const source of coverSources) { content.push(` ${source.type} at ${source.position}`); } content.push(''); content.push(BOX.LIGHT.H.repeat(COVER_DISPLAY_WIDTH)); content.push(''); } // Display result content.push(centerText('RESULT', COVER_DISPLAY_WIDTH)); content.push(''); switch (coverLevel) { case 'total': content.push('TOTAL COVER'); content.push(''); content.push('Target cannot be directly targeted.'); content.push('No line of effect for attacks or spells.'); break; case 'three_quarters': content.push('THREE-QUARTERS COVER'); content.push(''); content.push(`AC Bonus: +${COVER_BONUSES.three_quarters}`); content.push(`Dex Save Bonus: +${COVER_BONUSES.three_quarters}`); break; case 'half': content.push('HALF COVER'); content.push(''); content.push(`AC Bonus: +${COVER_BONUSES.half}`); content.push(`Dex Save Bonus: +${COVER_BONUSES.half}`); break; default: content.push('NO COVER'); content.push(''); content.push('AC Bonus: +0'); content.push('Dex Save Bonus: +0'); break; } return createBox('COVER CHECK', content); }
- src/modules/spatial.ts:1148-1154 (schema)Zod input schema for validating check_cover tool arguments, defining attacker/target positions and optional obstacles/creatures.export const checkCoverSchema = z.object({ attacker: CoverPositionSchema, target: CoverPositionSchema, obstacles: z.array(ObstacleSchema).optional(), creatures: z.array(CreatureBlockSchema).optional(), creaturesProvideCover: z.boolean().default(false), });
- src/registry.ts:675-692 (registration)Registration of check_cover tool in the MCP registry, including name, description, input schema conversion, and wrapper handler that validates input and calls the spatial checkCover function.check_cover: { name: 'check_cover', description: 'Check cover between attacker and target, returning AC and Dex save bonuses', inputSchema: toJsonSchema(checkCoverSchema), handler: async (args) => { try { const validated = checkCoverSchema.parse(args); const result = checkCover(validated); return success(result); } catch (err) { if (err instanceof z.ZodError) { const messages = err.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); return error(`Validation failed: ${messages}`); } const message = err instanceof Error ? err.message : String(err); return error(message); } },