- Deleted scripts/hud.js, scripts/event-translation.js, styles/hud.css, templates/hud.html. - Removed mod.api.getHud/openHud/closeHud/buildHudUpdatePayload exports. - Removed hudPosition setting (moved to combat-hud-hub). - Added hub integration: at ready, if combat-hud-hub is installed, register a 'pinned-achievements' section. The chatBubble listener now also pushes entries into that section via hub.api.pushFeedEntry. - Soft-dep on combat-hud-hub (optional); popover still works without the hub. - Tests: 57/57 passing covering sections A-G (rule engine, catalog, awarding, hooks wiring, hub integration, graceful degradation).
It's Achievable (its-achievable)
Foundry VTT module: achievements engine, custom rules, rewards,
achievement wall, and combat HUD. Consumes the generic Foundry hook
facade from foundry-hooks-lib and the encounter state from battle-focus.
Status
v0.1.0 — first release as a standalone repo. Sourced from
battle-focus/scripts/{achievements.js, achievement-wall.js, custom-achievements-app.js, hud.js, achievement-rules.js} at
battle-focus v0.5.0-alpha.12 (99cf757 on Gitea).
Stage 2 of the Foundry module split. The achievement code is now an independent module with its own:
- Settings namespace (
its-achievable.*) - Module id (
its-achievable, lowercase kebab) - Achievements catalog and rule engine
- HUD subscription to hooks-lib's v0.2.0 envelope stream
- Chat-bar popover and form application
battle-focus retains its own copies of these files until Stage 3 of
the split ships (which will delete the copies and add its-achievable
as a soft dependency of battle-focus).
Dependencies
foundry-hooks-lib(≥0.2.0) — provides the Foundry event stream via the generic{ts, hook, args}envelope facade. Seehooks-lib/docs/HOOK_CONTRACT.md.battle-focus(soft) — provides the encounter singleton viagame.modules.get("battle-focus").api.getActiveEncounter().
If either is missing, its-achievable logs a warning and degrades gracefully (achievements inactive).
Public API (on game.modules.get("its-achievable").api)
const api = game.modules.get("its-achievable").api;
// Catalog
api.getAchievementCatalog();
api.getActorAchievements(actorKey);
// Rule engine
api.evaluateRulesForEvent(event, encounter, actor, targetActor);
api.evaluateRulesForEncounterEnd(encounter);
api.evaluateRulesForCareerUpdate(career);
// Awarding
api.processEventForAchievements(event, encounter);
api.evaluateCombatAchievements(stats);
api.evaluateCareerAchievements(pcName, career, encounterId);
api.awardAchievement(actorKey, achievementId, encounterId);
// Wall + HUD
api.renderAchievementWall(actorId, actorName, opts);
api.getAchievementWallProgress(actorId, actorName);
api.renderAchievementPopover(unlocks, viewerName);
api.buildHudUpdatePayload(encounter, event);
api.openCustomAchievementsApp();
Settings namespace
Old (battle-focus.*) |
New (its-achievable.*) |
|---|---|
achievementsByActor |
achievementsByActor |
customAchievementRules |
customAchievementRules |
enableRewards |
enableRewards |
No automatic migration. Per the Stage 2 decision, users with existing worlds will need to re-create their custom rules and re-trigger any past achievement awards. Documented in CHANGELOG.
Tests
npm test # smoke test, no Foundry needed
tests/PLAN.md describes what we test and what we don't.
Real-Foundry integration testing happens when battle-focus migrates
in Stage 3 and the existing E2E suite exercises the moved code.
Architecture notes
- Hooks-lib first. its-achievable subscribes to
foundry-hooks-lib's envelope stream. Combat lifecycle, document CRUD, dnd5e rolls — all viahooksLib.api.subscribeMany({...}). No directHooks.on(...)calls. - Encounter via battle-focus's API. its-achievable does not import
battle-focus's
encounter.jsdirectly; it callsbattle-focus.api.getActiveEncounter()to resolve the singleton. - HUD derives its payload locally.
buildHudUpdatePayloadlives in its-achievable (moved from battle-focus). It derives the HUD state from hooks-lib envelopes, not from a battle-focus broadcast.