@jjlmoya/utils-tabletop 1.25.0 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -0
  3. package/src/entries.ts +2 -0
  4. package/src/tests/locale_completeness.test.ts +1 -1
  5. package/src/tests/tool_validation.test.ts +1 -1
  6. package/src/tool/encounter-difficulty-calculator/bibliography.astro +6 -0
  7. package/src/tool/encounter-difficulty-calculator/bibliography.ts +12 -0
  8. package/src/tool/encounter-difficulty-calculator/component.astro +53 -0
  9. package/src/tool/encounter-difficulty-calculator/controller.ts +133 -0
  10. package/src/tool/encounter-difficulty-calculator/dnd-5e-encounter-difficulty-calculator.css +389 -0
  11. package/src/tool/encounter-difficulty-calculator/dom-views.ts +54 -0
  12. package/src/tool/encounter-difficulty-calculator/entry.ts +27 -0
  13. package/src/tool/encounter-difficulty-calculator/evaluator.ts +12 -0
  14. package/src/tool/encounter-difficulty-calculator/i18n/de.ts +155 -0
  15. package/src/tool/encounter-difficulty-calculator/i18n/en.ts +155 -0
  16. package/src/tool/encounter-difficulty-calculator/i18n/es.ts +155 -0
  17. package/src/tool/encounter-difficulty-calculator/i18n/fr.ts +155 -0
  18. package/src/tool/encounter-difficulty-calculator/i18n/id.ts +155 -0
  19. package/src/tool/encounter-difficulty-calculator/i18n/it.ts +155 -0
  20. package/src/tool/encounter-difficulty-calculator/i18n/ja.ts +155 -0
  21. package/src/tool/encounter-difficulty-calculator/i18n/ko.ts +155 -0
  22. package/src/tool/encounter-difficulty-calculator/i18n/nl.ts +155 -0
  23. package/src/tool/encounter-difficulty-calculator/i18n/pl.ts +155 -0
  24. package/src/tool/encounter-difficulty-calculator/i18n/pt.ts +155 -0
  25. package/src/tool/encounter-difficulty-calculator/i18n/ru.ts +155 -0
  26. package/src/tool/encounter-difficulty-calculator/i18n/sv.ts +155 -0
  27. package/src/tool/encounter-difficulty-calculator/i18n/tr.ts +155 -0
  28. package/src/tool/encounter-difficulty-calculator/i18n/zh.ts +155 -0
  29. package/src/tool/encounter-difficulty-calculator/index.ts +11 -0
  30. package/src/tool/encounter-difficulty-calculator/logic.test.ts +37 -0
  31. package/src/tool/encounter-difficulty-calculator/logic.ts +174 -0
  32. package/src/tool/encounter-difficulty-calculator/seo.astro +16 -0
  33. package/src/tool/encounter-difficulty-calculator/storage.ts +26 -0
  34. package/src/tool/encounter-difficulty-calculator/ui.ts +45 -0
  35. package/src/tools.ts +2 -0
@@ -0,0 +1,54 @@
1
+ import type { EncounterDifficulty, EncounterResult } from './logic';
2
+ import { evaluateEncounter } from './evaluator';
3
+
4
+ type Labels = Record<string, string>;
5
+
6
+ function formatNumber(value: number): string {
7
+ return new Intl.NumberFormat('en-US').format(value);
8
+ }
9
+
10
+ function labelForDifficulty(key: EncounterDifficulty, labels: Labels): string {
11
+ return labels[key] ?? key;
12
+ }
13
+
14
+ function renderMarkers(count: number, className: string, element: string): string {
15
+ return Array.from({ length: count }, () => '<' + element + ' class="' + className + '"></' + element + '>').join('');
16
+ }
17
+
18
+ function renderHint(result: EncounterResult, labels: Labels): string {
19
+ return labels[result.difficulty + 'Hint'] ?? '';
20
+ }
21
+
22
+ function renderWarnings(result: EncounterResult, labels: Labels): string {
23
+ const warningText = result.warnings.map((key) => labels[key] ?? '').filter(Boolean);
24
+ if (warningText.length === 0) return '';
25
+ return '<div class="encounter-warning"><span>' + labels.warning + '</span><p>' + warningText.join(' ') + '</p></div>';
26
+ }
27
+
28
+ function renderThresholds(result: EncounterResult, labels: Labels): string {
29
+ const bands: EncounterDifficulty[] = ['belowEasy', 'easy', 'medium', 'hard', 'deadly'];
30
+ return bands.map((key) => {
31
+ const value = key === 'belowEasy' ? 0 : result.thresholds[key];
32
+ const active = key === result.difficulty ? ' is-active' : '';
33
+ const amount = value ? formatNumber(value) + ' XP' : '<' + formatNumber(result.thresholds.easy) + ' XP';
34
+ return '<div class="threshold-band ' + key + active + '"><span>' + labelForDifficulty(key, labels) + '</span><strong>' + amount + '</strong></div>';
35
+ }).join('');
36
+ }
37
+
38
+ export function renderResult(result: EncounterResult, labels: Labels): string {
39
+ const evaluation = evaluateEncounter(result);
40
+ const title = labelForDifficulty(evaluation.titleKey, labels);
41
+ const scale = Math.min(1.2, Math.max(0.7, 0.7 + evaluation.ratio * 0.5));
42
+ const opacity = Math.min(1, 0.8 + evaluation.ratio * 0.2);
43
+ const style = '--threat-scale:' + scale.toFixed(2) + ';--threat-opacity:' + opacity.toFixed(2);
44
+ return '<div class="result-orbit" data-difficulty="' + result.difficulty + '">'
45
+ + '<div class="orbit-labels"><span>' + labels.partyMarker + '</span><span>' + labels.threatMarker + '</span></div>'
46
+ + '<div class="orbit-scene" style="' + style + '">'
47
+ + '<div class="party-orbit">' + renderMarkers(result.settings.partySize, 'party-marker', 'i') + '</div>'
48
+ + '<div class="threat-orbit">' + renderMarkers(result.settings.monsterCount, 'threat-marker', 'b') + '</div><div class="orbit-line"></div></div>'
49
+ + '<div class="result-head"><span class="result-kicker">' + labels.resultSection + '</span><strong>' + title + '</strong><small>' + formatNumber(result.adjustedXp) + ' ' + labels.xpUnit + '</small></div>'
50
+ + '<p class="result-hint">' + renderHint(result, labels) + '</p>'
51
+ + '<div class="threshold-track">' + renderThresholds(result, labels) + '</div>'
52
+ + '<div class="result-stats"><span><small>' + labels.baseXp + '</small><strong>' + formatNumber(result.baseXp) + ' ' + labels.xpUnit + '</strong></span><span><small>' + labels.multiplier + '</small><strong>×' + result.multiplier + '</strong></span><span><small>' + labels.partyThreshold + '</small><strong>' + formatNumber(result.thresholds.medium) + ' ' + labels.xpUnit + '</strong></span></div>'
53
+ + renderWarnings(result, labels) + '</div>';
54
+ }
@@ -0,0 +1,27 @@
1
+ import type { TabletopToolEntry, ToolLocaleContent } from '../../types';
2
+ import type { EncounterDifficultyUI } from './ui';
3
+
4
+ export type { EncounterDifficultyUI } from './ui';
5
+ export type EncounterDifficultyLocaleContent = ToolLocaleContent<EncounterDifficultyUI>;
6
+
7
+ export const encounterDifficultyCalculator: TabletopToolEntry<EncounterDifficultyUI> = {
8
+ id: 'encounter-difficulty-calculator',
9
+ icons: { bg: 'mdi:sword-cross', fg: 'mdi:scale-balance' },
10
+ i18n: {
11
+ de: () => import('./i18n/de').then((module) => module.content),
12
+ en: () => import('./i18n/en').then((module) => module.content),
13
+ es: () => import('./i18n/es').then((module) => module.content),
14
+ fr: () => import('./i18n/fr').then((module) => module.content),
15
+ id: () => import('./i18n/id').then((module) => module.content),
16
+ it: () => import('./i18n/it').then((module) => module.content),
17
+ ja: () => import('./i18n/ja').then((module) => module.content),
18
+ ko: () => import('./i18n/ko').then((module) => module.content),
19
+ nl: () => import('./i18n/nl').then((module) => module.content),
20
+ pl: () => import('./i18n/pl').then((module) => module.content),
21
+ pt: () => import('./i18n/pt').then((module) => module.content),
22
+ ru: () => import('./i18n/ru').then((module) => module.content),
23
+ sv: () => import('./i18n/sv').then((module) => module.content),
24
+ tr: () => import('./i18n/tr').then((module) => module.content),
25
+ zh: () => import('./i18n/zh').then((module) => module.content),
26
+ },
27
+ };
@@ -0,0 +1,12 @@
1
+ import type { EncounterDifficulty, EncounterResult } from './logic';
2
+
3
+ export interface EncounterEvaluation {
4
+ titleKey: EncounterDifficulty;
5
+ ratio: number;
6
+ warningKeys: string[];
7
+ }
8
+
9
+ export function evaluateEncounter(result: EncounterResult): EncounterEvaluation {
10
+ const ratio = result.thresholds.deadly === 0 ? 1 : result.adjustedXp / result.thresholds.deadly;
11
+ return { titleKey: result.difficulty, ratio: Math.min(ratio, 1.35), warningKeys: result.warnings };
12
+ }
@@ -0,0 +1,155 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { EncounterDifficultyLocaleContent, EncounterDifficultyUI } from '../entry';
4
+
5
+ const ui: EncounterDifficultyUI = {
6
+ intro: 'Stellen Sie Gruppe und Bedrohung ein. Der Rechner gleicht die Begegnung mit den D&D 5e 2014 Schwellenwerten ab.',
7
+ partySection: 'Die Gruppe',
8
+ partyLevel: 'Charakterstufe',
9
+ partyLevelHint: 'Verwenden Sie eine Stufe für Gruppen mit ähnlicher Charakterstufe.',
10
+ partySize: 'Charaktere',
11
+ partySizeHint: 'Die Regeln passen den Monster-Multiplikator für sehr kleine oder große Gruppen an.',
12
+ threatSection: 'Die Bedrohung',
13
+ monsterCr: 'Herausforderungsgrad (CR)',
14
+ monsterCrHint: 'Wählen Sie den CR eines Monsters. Gemischte Gruppen erfordern eine eigene Berechnung.',
15
+ moreCr: 'Höhere CR anzeigen',
16
+ lessCr: 'Weniger CR anzeigen',
17
+ monsterCount: 'Anzahl der Monster',
18
+ monsterCountHint: 'Mehr Kreaturen erhöhen den Aktionsdruck selbst bei moderater Basis-XP.',
19
+ presets: 'Mit einer Szene starten',
20
+ presetClassic: 'Klassische Patrouille',
21
+ presetBoss: 'Boss alleine',
22
+ presetSwarm: 'Horde von Dienern',
23
+ resultSection: 'Begegnungsdruck',
24
+ belowEasyHint: 'Eine leichte Szene zur Ressourcenschonung für spätere Herausforderungen.',
25
+ easyHint: 'Eine überschaubare Szene mit geringem Druck auf die Gruppe.',
26
+ mediumHint: 'Eine spürbare Prüfung, die Trefferpunkte oder Ressourcen kosten kann.',
27
+ hardHint: 'Eine gefährliche Szene, in der Taktik und Ressourcenwahl entscheiden.',
28
+ deadlyHint: 'Signal für tödlichen Druck. Prüfen Sie Fluchtwege, Gelände und Fehlerkosten.',
29
+ adjustedXp: 'Angepasste XP',
30
+ baseXp: 'Basis-XP',
31
+ multiplier: 'Gruppen-Multiplikator',
32
+ partyThreshold: 'Mittlere Schwelle',
33
+ belowEasy: 'Unter Leicht',
34
+ easy: 'Leicht',
35
+ medium: 'Mittel',
36
+ hard: 'Schwer',
37
+ deadly: 'Tödlich',
38
+ warning: 'Hinweis',
39
+ partyAdjustment: 'Der Multiplikator wurde angepasst, da die Gruppe weniger als drei oder mehr als fünf Charaktere umfasst.',
40
+ highCr: 'Ein Monster mit höherem CR als die Gruppenstufe kann Charaktere schnell zu Boden werfen.',
41
+ manyMonsters: 'Elf oder mehr Monster erschweren die Kampfverwaltung und erzeugen starke Aktionsschwankungen.',
42
+ rulesNote: 'Schätzung nach D&D 5e 2014 Regeln. Gelände, Taktik, Zauber und magische Gegenstände werden nicht berücksichtigt.',
43
+ rulesLinkLabel: 'Quellregeln lesen',
44
+ reset: 'Auf Beispiel zurücksetzen',
45
+ xpUnit: 'XP',
46
+ sceneLabel: 'Grafische Begegnungsdruck-Anzeige',
47
+ partyMarker: 'Gruppe',
48
+ threatMarker: 'Bedrohung',
49
+ };
50
+
51
+ const faq = [
52
+ {
53
+ question: 'Welche Regeln nutzt dieser Begegnungsrechner?',
54
+ answer: 'Er nutzt die D&D 5e 2014 Regeln aus den Grundregeln. Er addiert die Gruppenschwellen für leicht, mittel, schwer und tödlich und vergleicht sie mit den angepassten XP.',
55
+ },
56
+ {
57
+ question: 'Warum unterscheidet sich die angepasste XP von der Belohnungs-XP?',
58
+ answer: 'Die Regeln multiplizieren die Gesamt-XP der Monster, um die Gefahr mehrerer agierender Kreaturen abzubilden. Angepasste XP dienen nur dem Schwierigkeitsvergleich.',
59
+ },
60
+ {
61
+ question: 'Kann ich gemischte Monstergruppen berechnen?',
62
+ answer: 'Nutzen Sie das Tool als schnelle Schätzung für gleiche Monster. Für gemischte Gruppen addieren Sie die XP aller Kreaturen und wenden den Multiplikator an.',
63
+ },
64
+ {
65
+ question: 'Bedeutet ein tödliches Ergebnis den sicheren Tod?',
66
+ answer: 'Nein. Tödlich bedeutet, dass die angepasste XP die tödliche Schwelle erreicht. Taktik, Gelände, Zauber und Entscheidungen verändern das reale Ergebnis.',
67
+ },
68
+ {
69
+ question: 'Warum ändert die Gruppengröße den Multiplikator?',
70
+ answer: 'Die Grundregeln empfehlen eine Erhöhung des Multiplikators für Gruppen unter drei Charakteren und eine Verringerung für Gruppen ab sechs Charakteren.',
71
+ },
72
+ ];
73
+
74
+ const howTo = [
75
+ {
76
+ name: 'Gruppenstufe festlegen',
77
+ text: 'Wählen Sie die durchschnittliche Stufe der Charaktere.',
78
+ },
79
+ {
80
+ name: 'Gruppengröße wählen',
81
+ text: 'Geben Sie die Anzahl der teilnehmenden Charaktere ein.',
82
+ },
83
+ {
84
+ name: 'Bedrohung beschreiben',
85
+ text: 'Wählen Sie den Herausforderungsgrad (CR) und die Anzahl der Monster.',
86
+ },
87
+ {
88
+ name: 'Druck ablesen',
89
+ text: 'Vergleichen Sie die angepassten XP mit den Schwellenwerten.',
90
+ },
91
+ ];
92
+
93
+ const faqSchema: WithContext<FAQPage> = {
94
+ '@context': 'https://schema.org',
95
+ '@type': 'FAQPage',
96
+ mainEntity: faq.map((item) => ({
97
+ '@type': 'Question',
98
+ name: item.question,
99
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
100
+ })),
101
+ };
102
+
103
+ const appSchema: WithContext<SoftwareApplication> = {
104
+ '@context': 'https://schema.org',
105
+ '@type': 'SoftwareApplication',
106
+ name: 'D&D 5e Begegnungs-Schwierigkeitsrechner',
107
+ operatingSystem: 'All',
108
+ applicationCategory: 'GameApplication',
109
+ description: 'Berechnen Sie die Begegnungsschwierigkeit für D&D 5e 2014 basierend auf Stufe, Gruppengröße, Monster-CR, Anzahl und offiziellen Schwellenwerten.',
110
+ };
111
+
112
+ const howToSchema: WithContext<HowTo> = {
113
+ '@context': 'https://schema.org',
114
+ '@type': 'HowTo',
115
+ name: 'Wie man die D&D 5e Begegnungsschwierigkeit berechnet',
116
+ step: howTo.map((item) => ({
117
+ '@type': 'HowToStep',
118
+ name: item.name,
119
+ text: item.text,
120
+ })),
121
+ };
122
+
123
+ export const content: EncounterDifficultyLocaleContent = {
124
+ slug: 'dnd-5e-begegnungs-schwierigkeitsrechner',
125
+ title: 'D&D 5e Begegnungs Schwierigkeitsrechner',
126
+ description: 'Schätzen Sie den Begegnungsdruck für D&D 5e 2014 mit Stufe, Gruppengröße, Monster-CR, Anzahl, angepassten XP und Schwellenwerten.',
127
+ ui,
128
+ seo: [
129
+ { type: 'title', text: 'Begegnungsschwierigkeit vor der Initiative einschätzen', level: 2 },
130
+ { type: 'paragraph', html: 'Ein D&D-Kampf ist mehr als nur die gedruckte Zahl eines Monsters. Dieser Rechner wandelt Gruppengröße, Stufe, CR und Monsteranzahl in die angepassten XP nach den D&D 5e 2014 Regeln um. Das visuelle Ergebnis zeigt übersichtlich, wo die geplante Szene im Vergleich zu den Schwellenwerten für leicht, mittel, schwer und tödlich liegt.' },
131
+ { type: 'title', text: 'Wie die D&D 5e Begegnungsformel funktioniert', level: 2 },
132
+ { type: 'paragraph', html: 'Die Methode addiert die XP-Schwellenwerte jedes Charakters für jede Schwierigkeitsstufe, summiert die Monster-XP und wendet den Gruppenmultiplikator an. Gruppen mit weniger als drei Charakteren nutzen den nächsthöheren Multiplikator, während Gruppen ab sechs Charakteren den nächstniedrigeren Multiplikator verwenden.' },
133
+ {
134
+ type: 'table',
135
+ headers: ['Signal', 'Bedeutung am Spieltisch'],
136
+ rows: [
137
+ ['Unter Leicht', 'Die Szene dient als Aufwärmung oder zum leichten Ressourcenverbrauch.'],
138
+ ['Leicht', 'Die Gruppe gewinnt in der Regel ohne großen Ressourceneinsatz.'],
139
+ ['Mittel', 'Erwarten Sie spürbaren Druck und Entscheidungen über Ressourceneinsatz.'],
140
+ ['Schwer', 'Planen Sie den Verlust von Trefferpunkten und Zauberslots ein.'],
141
+ ['Tödlich', 'Prüfen Sie Taktik, Gelände und die Folgen schlechter Würfe.'],
142
+ ],
143
+ },
144
+ { type: 'title', text: 'Warum die Monsteranzahl entscheidend ist', level: 2 },
145
+ { type: 'paragraph', html: 'Mehrere Monster erzeugen durch mehr Aktionen und Reaktionen höhere Gefahr als ein einzelnes Monster mit gleichen Gesamt-XP. Deshalb nutzt ein Paar von Kreaturen einen höheren Multiplikator als eine einzelne Kreatur mit derselben kombinierten XP-Zahl.' },
146
+ { type: 'tip', title: 'Hohen CR als spezifisches Warnsignal betrachten', html: 'Ein Monster mit höherem CR als die Gruppenstufe kann Charaktere mit einem Angriff ausschalten. Prüfen Sie Schaden und Effekte genau.' },
147
+ { type: 'title', text: 'Das Ergebnis als Vorbereitungshilfe nutzen', level: 2 },
148
+ { type: 'paragraph', html: 'Prüfen Sie vor schweren oder tödlichen Begegnungen Raumgröße, Deckung und Zauberoptionen der Charaktere. Enge Räume, Überraschung, Umgebungsschaden und Erschöpfung können die tatsächliche Gefahr am Spieltisch erheblich verändern.' },
149
+ { type: 'tip', title: 'Gemischte Gruppen manuell anpassen', html: 'Addieren Sie für gemischte Gruppen die Basis-XP aller Kreaturen und wenden Sie den Multiplikator auf die Gesamtzahl an.' },
150
+ ],
151
+ faq,
152
+ bibliography,
153
+ howTo,
154
+ schemas: [faqSchema, appSchema, howToSchema] as unknown as Record<string, unknown>[],
155
+ };
@@ -0,0 +1,155 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { EncounterDifficultyLocaleContent, EncounterDifficultyUI } from '../entry';
4
+
5
+ const ui: EncounterDifficultyUI = {
6
+ intro: 'Set the party and the threat. The calculator weighs the encounter against the D&D 5e 2014 thresholds so you can spot the pressure before initiative starts.',
7
+ partySection: 'The party',
8
+ partyLevel: 'Character level',
9
+ partyLevelHint: 'Use one level for a party with characters of similar level.',
10
+ partySize: 'Characters',
11
+ partySizeHint: 'The rules adjust the monster multiplier for very small or large groups.',
12
+ threatSection: 'The threat',
13
+ monsterCr: 'Monster challenge rating',
14
+ monsterCrHint: 'Choose the CR of one repeated monster. Mixed groups need a separate calculation.',
15
+ moreCr: 'Show higher CR',
16
+ lessCr: 'Show fewer CR values',
17
+ monsterCount: 'Number of monsters',
18
+ monsterCountHint: 'More creatures increase the action pressure even when their total XP is modest.',
19
+ presets: 'Start with a scene',
20
+ presetClassic: 'Classic patrol',
21
+ presetBoss: 'Boss alone',
22
+ presetSwarm: 'Crowd of minions',
23
+ resultSection: 'Encounter pressure',
24
+ belowEasyHint: 'A light scene that may preserve resources for what comes next.',
25
+ easyHint: 'A manageable scene with limited pressure on the party.',
26
+ mediumHint: 'A meaningful test that may cost hit points or useful resources.',
27
+ hardHint: 'A dangerous scene where tactics and resource choices matter.',
28
+ deadlyHint: 'A lethal pressure signal. Check escape routes, terrain, and the cost of one bad round.',
29
+ adjustedXp: 'Adjusted XP',
30
+ baseXp: 'Base XP',
31
+ multiplier: 'Group multiplier',
32
+ partyThreshold: 'Medium threshold',
33
+ belowEasy: 'Below easy',
34
+ easy: 'Easy',
35
+ medium: 'Medium',
36
+ hard: 'Hard',
37
+ deadly: 'Deadly',
38
+ warning: 'Read this',
39
+ partyAdjustment: 'The monster multiplier has been adjusted because the party has fewer than three or more than five characters.',
40
+ highCr: 'A monster with a CR above the party level can drop a character quickly. Treat this label as a danger signal, not a prediction.',
41
+ manyMonsters: 'Eleven or more monsters can make a fight much harder to run and can create swingy action economy.',
42
+ rulesNote: 'This is an estimate for the D&D 5e 2014 encounter method. It does not account for terrain, tactics, spells, magic items, or player experience.',
43
+ rulesLinkLabel: 'Read the source rules',
44
+ reset: 'Reset to the sample',
45
+ xpUnit: 'XP',
46
+ sceneLabel: 'Encounter pressure visual',
47
+ partyMarker: 'Party',
48
+ threatMarker: 'Threat',
49
+ };
50
+
51
+ const faq = [
52
+ {
53
+ question: 'What rules does this encounter difficulty calculator use?',
54
+ answer: 'It uses the D&D 5e 2014 method from the Basic Rules. It adds the party thresholds for easy, medium, hard, and deadly encounters, then compares them with the adjusted XP of the monsters.',
55
+ },
56
+ {
57
+ question: 'Why is adjusted XP different from the XP awarded by a monster?',
58
+ answer: 'The rules multiply the total monster XP to reflect the danger of several creatures acting in the same round. Adjusted XP is a comparison value for encounter difficulty, not the XP characters receive.',
59
+ },
60
+ {
61
+ question: 'Can I use this for a mixed group of monsters?',
62
+ answer: 'Use it as a quick estimate for repeated monsters. For a mixed group, add the XP of each creature and apply the multiplier to the total number of meaningful monsters. The calculator keeps one CR visible so the input stays fast and easy to read.',
63
+ },
64
+ {
65
+ question: 'Does a deadly result mean the party will die?',
66
+ answer: 'No. Deadly means the adjusted XP reaches the deadly threshold in this rules method. Terrain, tactics, rests, spells, magic items, monster abilities, and player decisions can change the actual result.',
67
+ },
68
+ {
69
+ question: 'Why does party size change the multiplier?',
70
+ answer: 'The Basic Rules recommend increasing the multiplier for parties of fewer than three characters and decreasing it for parties of six or more. This accounts for the different action economy of unusually small or large groups.',
71
+ },
72
+ ];
73
+
74
+ const howTo = [
75
+ {
76
+ name: 'Set the party level',
77
+ text: 'Choose the level shared by most characters in the party. If levels vary widely, treat the result as a starting point and check the lowest level separately.',
78
+ },
79
+ {
80
+ name: 'Set the group size',
81
+ text: 'Choose the number of characters who will enter the encounter. Small and large parties receive a multiplier adjustment.',
82
+ },
83
+ {
84
+ name: 'Describe the threat',
85
+ text: 'Choose one monster challenge rating and the number of meaningful monsters. Use the scene presets when you want a fast baseline.',
86
+ },
87
+ {
88
+ name: 'Read the pressure',
89
+ text: 'Compare adjusted XP with the threshold bands. Use warnings as prompts to inspect high CR, action economy, terrain, and the resources available before the fight.',
90
+ },
91
+ ];
92
+
93
+ const faqSchema: WithContext<FAQPage> = {
94
+ '@context': 'https://schema.org',
95
+ '@type': 'FAQPage',
96
+ mainEntity: faq.map((item) => ({
97
+ '@type': 'Question',
98
+ name: item.question,
99
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
100
+ })),
101
+ };
102
+
103
+ const appSchema: WithContext<SoftwareApplication> = {
104
+ '@context': 'https://schema.org',
105
+ '@type': 'SoftwareApplication',
106
+ name: 'D&D 5e Encounter Difficulty Calculator',
107
+ operatingSystem: 'All',
108
+ applicationCategory: 'GameApplication',
109
+ description: 'Estimate encounter difficulty for a D&D 5e 2014 party using character level, party size, monster challenge rating, monster count, XP, and official threshold bands.',
110
+ };
111
+
112
+ const howToSchema: WithContext<HowTo> = {
113
+ '@context': 'https://schema.org',
114
+ '@type': 'HowTo',
115
+ name: 'How to estimate D&D 5e encounter difficulty',
116
+ step: howTo.map((item) => ({
117
+ '@type': 'HowToStep',
118
+ name: item.name,
119
+ text: item.text,
120
+ })),
121
+ };
122
+
123
+ export const content: EncounterDifficultyLocaleContent = {
124
+ slug: 'dnd-5e-encounter-difficulty-calculator',
125
+ title: 'D&D 5e Encounter Difficulty Calculator',
126
+ description: 'Estimate encounter pressure for D&D 5e 2014 using party level, group size, monster CR, monster count, adjusted XP, and official difficulty thresholds.',
127
+ ui,
128
+ seo: [
129
+ { type: 'title', text: 'Read Encounter Difficulty Before Initiative Starts', level: 2 },
130
+ { type: 'paragraph', html: 'A D&D encounter is more than the number printed beside a monster. This calculator turns party size, character level, challenge rating, and monster count into the adjusted XP value used by the D&D 5e 2014 encounter method. The visual result shows where the scene sits against the easy, medium, hard, and deadly thresholds.' },
131
+ { type: 'title', text: 'How the D&D 5e Encounter Formula Works', level: 2 },
132
+ { type: 'paragraph', html: 'The method first adds one XP threshold for every character at each difficulty band. It then adds the XP values of the monsters and applies a multiplier based on the number of meaningful creatures. Parties with fewer than three characters use the next higher multiplier, while parties of six or more use the next lower multiplier.' },
133
+ {
134
+ type: 'table',
135
+ headers: ['Signal', 'What to inspect at the table'],
136
+ rows: [
137
+ ['Below easy', 'The encounter may be a warm up, a travel beat, or a resource saving scene.'],
138
+ ['Easy', 'The party should usually win without spending many resources.'],
139
+ ['Medium', 'Expect pressure and at least one meaningful choice about resources.'],
140
+ ['Hard', 'Plan for characters to lose hit points, spell slots, or position.'],
141
+ ['Deadly', 'Check tactics, terrain, escape routes, and the cost of one bad round.'],
142
+ ],
143
+ },
144
+ { type: 'title', text: 'Why Monster Count Matters', level: 2 },
145
+ { type: 'paragraph', html: 'Several monsters can create more danger than their base XP suggests because they bring more attacks, reactions, positions, and chances to focus fire. That is why a pair of creatures uses a higher multiplier than one creature with the same combined XP. The result is a warning light for action economy, not a promise about how the encounter will feel.' },
146
+ { type: 'tip', title: 'Treat High CR as a Specific Danger Signal', html: 'A monster whose CR is above the party level may be able to remove a character with one strong action. Inspect its damage, control effects, saving throw difficulty, and mobility instead of relying on the final difficulty word alone.' },
147
+ { type: 'title', text: 'Use the Result as a Preparation Prompt', level: 2 },
148
+ { type: 'paragraph', html: 'Before using a hard or deadly encounter, look at the full situation. A cramped room, cover, surprise, environmental damage, concentration spells, legendary actions, and an exhausted party can all move the real danger away from the table value. A lower result can still become memorable when the objective, terrain, or time pressure creates a meaningful choice.' },
149
+ { type: 'tip', title: 'Mixed Monster Groups Need a Wider Check', html: 'This quick calculator keeps one CR visible so the interface stays fast. For a mixed group, total the XP of every meaningful monster, apply the multiplier to that total, and compare the adjusted XP with the same party thresholds.' },
150
+ ],
151
+ faq,
152
+ bibliography,
153
+ howTo,
154
+ schemas: [faqSchema, appSchema, howToSchema] as unknown as Record<string, unknown>[],
155
+ };
@@ -0,0 +1,155 @@
1
+ import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
2
+ import { bibliography } from '../bibliography';
3
+ import type { EncounterDifficultyLocaleContent, EncounterDifficultyUI } from '../entry';
4
+
5
+ const ui: EncounterDifficultyUI = {
6
+ intro: 'Ajusta el grupo y la amenaza. La calculadora mide el encuentro con los umbrales de D&D 5e 2014 para detectar la presión antes de la iniciativa.',
7
+ partySection: 'El grupo',
8
+ partyLevel: 'Nivel del personaje',
9
+ partyLevelHint: 'Usa un nivel uniforme para un grupo con personajes de nivel similar.',
10
+ partySize: 'Personajes',
11
+ partySizeHint: 'Las reglas ajustan el multiplicador de monstruos para grupos muy pequeños o grandes.',
12
+ threatSection: 'La amenaza',
13
+ monsterCr: 'Desafío del monstruo (CR)',
14
+ monsterCrHint: 'Elige el CR de un monstruo repetido. Los grupos mixtos requieren un cálculo independiente.',
15
+ moreCr: 'Mostrar más CR',
16
+ lessCr: 'Mostrar menos CR',
17
+ monsterCount: 'Cantidad de monstruos',
18
+ monsterCountHint: 'Más criaturas aumentan la presión de acciones aunque su experiencia base sea modesta.',
19
+ presets: 'Empezar con una escena',
20
+ presetClassic: 'Patrulla clásica',
21
+ presetBoss: 'Jefe en solitario',
22
+ presetSwarm: 'Horda de esbirros',
23
+ resultSection: 'Presión del encuentro',
24
+ belowEasyHint: 'Una escena ligera que permite conservar recursos para los siguientes retos.',
25
+ easyHint: 'Una escena asequible con poca presión sobre el grupo.',
26
+ mediumHint: 'Una prueba significativa que puede costar puntos de golpe o recursos.',
27
+ hardHint: 'Una escena peligrosa donde la táctica y la gestión de recursos importan.',
28
+ deadlyHint: 'Señal de presión mortal. Revisa rutas de escape, terreno y el coste de un mal asalto.',
29
+ adjustedXp: 'XP Ajustada',
30
+ baseXp: 'XP Base',
31
+ multiplier: 'Multiplicador de grupo',
32
+ partyThreshold: 'Umbral Medio',
33
+ belowEasy: 'Inferior a fácil',
34
+ easy: 'Fácil',
35
+ medium: 'Medio',
36
+ hard: 'Difícil',
37
+ deadly: 'Mortal',
38
+ warning: 'Ten en cuenta',
39
+ partyAdjustment: 'Se ha ajustado el multiplicador de monstruos porque el grupo tiene menos de tres o más de cinco personajes.',
40
+ highCr: 'Un monstruo con CR superior al nivel del grupo puede derribar a un personaje rápidamente. Trata esta etiqueta como una alerta.',
41
+ manyMonsters: 'Once o más monstruos pueden dificultar la gestión del combate y crear gran volatilidad en la economía de acciones.',
42
+ rulesNote: 'Esta estimación se basa en las reglas de D&D 5e 2014. No contempla terreno, táctica, conjuros, objetos mágicos o experiencia del jugador.',
43
+ rulesLinkLabel: 'Leer las reglas de origen',
44
+ reset: 'Restablecer ejemplo',
45
+ xpUnit: 'XP',
46
+ sceneLabel: 'Gráfico visual de presión',
47
+ partyMarker: 'Grupo',
48
+ threatMarker: 'Amenaza',
49
+ };
50
+
51
+ const faq = [
52
+ {
53
+ question: '¿Qué reglas utiliza esta calculadora de dificultad de encuentros?',
54
+ answer: 'Utiliza el método oficial de D&D 5e 2014 de las Reglas Básicas. Suma los umbrales del grupo para encuentros fáciles, medios, difíciles y mortales, y los compara con la XP ajustada de los monstruos.',
55
+ },
56
+ {
57
+ question: '¿Por qué la XP ajustada es diferente de la XP que otorga un monstruo?',
58
+ answer: 'Las reglas multiplican la XP total de los monstruos para reflejar el peligro de varias criaturas actuando en el mismo asalto. La XP ajustada es un valor comparativo de dificultad, no la XP que reciben los personajes.',
59
+ },
60
+ {
61
+ question: '¿Puedo usar esto para un grupo mixto de monstruos?',
62
+ answer: 'Úsala como una estimación rápida para monstruos idénticos. Para grupos mixtos, suma la XP de cada criatura y aplica el multiplicador al número total de monstruos significativos.',
63
+ },
64
+ {
65
+ question: '¿Un resultado mortal significa que el grupo morirá?',
66
+ answer: 'No. Mortal significa que la XP ajustada alcanza el umbral mortal en las reglas. El terreno, las tácticas, los descansos, conjuros, objetos mágicos y decisiones pueden alterar el resultado real.',
67
+ },
68
+ {
69
+ question: '¿Por qué el tamaño del grupo cambia el multiplicador?',
70
+ answer: 'Las Reglas Básicas recomiendan aumentar el multiplicador para grupos de menos de tres personajes y reducirlo para grupos de seis o más, equilibrando la economía de acciones.',
71
+ },
72
+ ];
73
+
74
+ const howTo = [
75
+ {
76
+ name: 'Establece el nivel del grupo',
77
+ text: 'Elige el nivel predominante de los personajes. Si los niveles varían mucho, toma el resultado como orientación y evalúa el nivel más bajo por separado.',
78
+ },
79
+ {
80
+ name: 'Indica el tamaño del grupo',
81
+ text: 'Introduce el número de personajes en el encuentro. Los grupos pequeños y grandes reciben un ajuste en el multiplicador.',
82
+ },
83
+ {
84
+ name: 'Describe la amenaza',
85
+ text: 'Selecciona el desafío (CR) y la cantidad de monstruos. Usa las plantillas predefinidas para una comprobación rápida.',
86
+ },
87
+ {
88
+ name: 'Interpreta la presión',
89
+ text: 'Compara la XP ajustada con las franjas de umbral y revisa las advertencias sobre economía de acciones, terreno y recursos antes del combate.',
90
+ },
91
+ ];
92
+
93
+ const faqSchema: WithContext<FAQPage> = {
94
+ '@context': 'https://schema.org',
95
+ '@type': 'FAQPage',
96
+ mainEntity: faq.map((item) => ({
97
+ '@type': 'Question',
98
+ name: item.question,
99
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
100
+ })),
101
+ };
102
+
103
+ const appSchema: WithContext<SoftwareApplication> = {
104
+ '@context': 'https://schema.org',
105
+ '@type': 'SoftwareApplication',
106
+ name: 'Calculadora de Dificultad de Encuentros D&D 5e',
107
+ operatingSystem: 'All',
108
+ applicationCategory: 'GameApplication',
109
+ description: 'Calcula la dificultad de encuentros para grupos de D&D 5e 2014 basándote en nivel, tamaño de grupo, desafío de monstruos (CR), cantidad, XP y umbrales oficiales.',
110
+ };
111
+
112
+ const howToSchema: WithContext<HowTo> = {
113
+ '@context': 'https://schema.org',
114
+ '@type': 'HowTo',
115
+ name: 'Cómo calcular la dificultad de un encuentro en D&D 5e',
116
+ step: howTo.map((item) => ({
117
+ '@type': 'HowToStep',
118
+ name: item.name,
119
+ text: item.text,
120
+ })),
121
+ };
122
+
123
+ export const content: EncounterDifficultyLocaleContent = {
124
+ slug: 'calculadora-dificultad-encuentros-dnd-5e',
125
+ title: 'Calculadora de Dificultad de Encuentros D&D 5e',
126
+ description: 'Estima la presión de encuentros en D&D 5e 2014 mediante nivel del grupo, tamaño, CR de monstruos, cantidad, XP ajustada y umbrales oficiales.',
127
+ ui,
128
+ seo: [
129
+ { type: 'title', text: 'Analiza la dificultad del combate antes de tirar iniciativa', level: 2 },
130
+ { type: 'paragraph', html: 'Un encuentro de D&D es más que el número impreso junto a un monstruo. Esta calculadora convierte el tamaño del grupo, nivel, desafío (CR) y número de monstruos en el valor de XP ajustada utilizado por el sistema oficial de D&D 5e 2014.' },
131
+ { type: 'title', text: 'Cómo funciona la fórmula de encuentros de D&D 5e', level: 2 },
132
+ { type: 'paragraph', html: 'El método suma el umbral de XP de cada personaje para cada nivel de dificultad. Luego suma la XP base de los monstruos y aplica un multiplicador según el número de criaturas. Los grupos de menos de tres personajes usan un multiplicador superior y los de seis o más usan uno inferior.' },
133
+ {
134
+ type: 'table',
135
+ headers: ['Señal', 'Qué evaluar en la mesa'],
136
+ rows: [
137
+ ['Inferior a fácil', 'El encuentro puede ser un calentamiento, un evento de viaje o una escena de desgaste menor.'],
138
+ ['Fácil', 'El grupo debería ganar sin gastar apenas recursos.'],
139
+ ['Medio', 'Espera cierta presión y al menos una decisión importante sobre el uso de recursos.'],
140
+ ['Difícil', 'Planifica que los personajes pierdan puntos de golpe, espacio de conjuros o posición.'],
141
+ ['Mortal', 'Revisa tácticas, terreno, vías de escape y el impacto de un mal turno.'],
142
+ ],
143
+ },
144
+ { type: 'title', text: 'Por qué importa la cantidad de monstruos', level: 2 },
145
+ { type: 'paragraph', html: 'Varios monstruos crean más peligro del que indica su XP base porque aportan más ataques, reacciones y capacidad de concentrar el daño sobre un personaje. Por eso dos criaturas aplican un multiplicador mayor que una sola con la misma XP acumulada.' },
146
+ { type: 'tip', title: 'Considera un CR alto como una alerta específica', html: 'Un monstruo cuyo CR supere el nivel del grupo puede dejar KO a un personaje en una sola acción potente. Revisa su daño, efectos de control y movilidad en lugar de confiar solo en la palabra de dificultad final.' },
147
+ { type: 'title', text: 'Utiliza el resultado como guía de preparación', level: 2 },
148
+ { type: 'paragraph', html: 'Antes de plantear un encuentro difícil o mortal, evalúa el contexto general. Habitaciones estrechas, coberturas, sorpresa, trampas ambientales y conjuros de concentración pueden modificar sustancialmente el peligro real.' },
149
+ { type: 'tip', title: 'Los grupos mixtos requieren un ajuste manual', html: 'Esta calculadora mantiene un único CR para agilizar la entrada de datos. Si el combate incluye criaturas distintas, suma la XP base de cada una, aplica el multiplicador al total de monstruos y compáralo con los umbrales del grupo.' },
150
+ ],
151
+ faq,
152
+ bibliography,
153
+ howTo,
154
+ schemas: [faqSchema, appSchema, howToSchema] as unknown as Record<string, unknown>[],
155
+ };