@lippelt/srd-dnd5e-2024 0.1.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.
- package/README.md +34 -0
- package/dist/index.cjs +238 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +31 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +207 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# @lippelt/srd-dnd5e-2024
|
|
2
|
+
|
|
3
|
+
Módulo D&D 5e (2024 / "One D&D") para [`@lippelt/srd-core`](../core).
|
|
4
|
+
|
|
5
|
+
Baseado no [System Reference Document 5.2](https://dnd.wizards.com/resources/systems-reference-document) da Wizards of the Coast, sob **Creative Commons Attribution 4.0 International**.
|
|
6
|
+
|
|
7
|
+
## Diferenças vs `@lippelt/srd-dnd5e-2014`
|
|
8
|
+
|
|
9
|
+
- **Exhaustion**: agora uma escala única 1..10 com **−2 cumulativo** em d20 tests por nível; nível 10 = morte (vs 6 níveis discretos do 5.1).
|
|
10
|
+
- O parâmetro `exhaustion` no `roll('d20')` / `roll('attack')` / `roll('save')` aplica o penalty automaticamente.
|
|
11
|
+
- O tracker tem o campo `exhaustion` (0..10) pra registrar.
|
|
12
|
+
- **Conditions**: 14 + Exhaustion como entrada única (em vez de `exhaustion-1` até `exhaustion-6`).
|
|
13
|
+
- **Resto da matemática**: idêntica à 2014 — advantage/disadvantage, crit em 20 natural, save DC = 8 + prof + mod, etc.
|
|
14
|
+
|
|
15
|
+
## Bundle
|
|
16
|
+
|
|
17
|
+
- **9 dice presets** — d20 (+ adv/desv), d4-d12, d100
|
|
18
|
+
- **15 conditions** — Blinded, Charmed, Deafened, Exhaustion (level), Frightened, Grappled, Incapacitated, Invisible, Paralyzed, Petrified, Poisoned, Prone, Restrained, Stunned, Unconscious
|
|
19
|
+
- **4 tracker fields** — CA, Exh, Mortes ✓, Mortes ✗
|
|
20
|
+
- **Rules:** `check`, `attack`, `save`, `damage`, `applyDamage` (resistance/vulnerability/immunity)
|
|
21
|
+
- **Helpers:** `abilityMod`, `spellSaveDC`, `spellAttackBonus`, `exhaustionPenalty`
|
|
22
|
+
|
|
23
|
+
## Uso
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { register } from '@lippelt/srd-core'
|
|
27
|
+
import { dnd5e2024 } from '@lippelt/srd-dnd5e-2024'
|
|
28
|
+
|
|
29
|
+
register(dnd5e2024)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Licença
|
|
33
|
+
|
|
34
|
+
[MIT](LICENSE). Mecânica deriva do SRD 5.2 (CC-BY 4.0, Wizards of the Coast).
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
abilityMod: () => abilityMod,
|
|
24
|
+
dnd5e2024: () => dnd5e2024,
|
|
25
|
+
exhaustionPenalty: () => exhaustionPenalty,
|
|
26
|
+
resetRoller: () => resetRoller,
|
|
27
|
+
setRoller: () => setRoller,
|
|
28
|
+
spellAttackBonus: () => spellAttackBonus,
|
|
29
|
+
spellSaveDC: () => spellSaveDC
|
|
30
|
+
});
|
|
31
|
+
module.exports = __toCommonJS(index_exports);
|
|
32
|
+
var roller = (sides) => Math.floor(Math.random() * sides) + 1;
|
|
33
|
+
function setRoller(fn) {
|
|
34
|
+
roller = fn;
|
|
35
|
+
}
|
|
36
|
+
function resetRoller() {
|
|
37
|
+
roller = (sides) => Math.floor(Math.random() * sides) + 1;
|
|
38
|
+
}
|
|
39
|
+
function roll(sides, count = 1) {
|
|
40
|
+
const out = [];
|
|
41
|
+
for (let i = 0; i < count; i++) out.push(roller(sides));
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
var DICE_PRESETS = [
|
|
45
|
+
{ id: "d20", label: "d20", notation: "1d20", category: "check" },
|
|
46
|
+
{ id: "d20-adv", label: "d20 (adv.)", notation: "advantage", category: "check", description: "Rola 2d20 e pega o maior." },
|
|
47
|
+
{ id: "d20-dis", label: "d20 (desv.)", notation: "disadvantage", category: "check", description: "Rola 2d20 e pega o menor." },
|
|
48
|
+
{ id: "d4", label: "d4", notation: "1d4", category: "damage" },
|
|
49
|
+
{ id: "d6", label: "d6", notation: "1d6", category: "damage" },
|
|
50
|
+
{ id: "d8", label: "d8", notation: "1d8", category: "damage" },
|
|
51
|
+
{ id: "d10", label: "d10", notation: "1d10", category: "damage" },
|
|
52
|
+
{ id: "d12", label: "d12", notation: "1d12", category: "damage" },
|
|
53
|
+
{ id: "d100", label: "d100", notation: "1d100", category: "special" }
|
|
54
|
+
];
|
|
55
|
+
var CONDITIONS = [
|
|
56
|
+
{ id: "blinded", label: "Blinded", summary: "Cego: falha em testes que exigem vis\xE3o; atacantes t\xEAm vantagem; o cego tem desvantagem." },
|
|
57
|
+
{ id: "charmed", label: "Charmed", summary: "Enfeiti\xE7ado: n\xE3o pode atacar o encantador nem usar habilidades nocivas contra ele." },
|
|
58
|
+
{ id: "deafened", label: "Deafened", summary: "Surdo: falha em testes que dependem de audi\xE7\xE3o." },
|
|
59
|
+
{ id: "exhaustion", label: "Exhaustion (level)", summary: "Exaust\xE3o (1..10): \u22122 acumulativo em todos os testes de d20 por n\xEDvel; n\xEDvel 10 = morte." },
|
|
60
|
+
{ id: "frightened", label: "Frightened", summary: "Amedrontado: desvantagem se a fonte do medo estiver \xE0 vista; n\xE3o pode se mover para perto dela." },
|
|
61
|
+
{ id: "grappled", label: "Grappled", summary: "Agarrado: velocidade 0; termina se o agarrador ficar incapacitado ou afastado." },
|
|
62
|
+
{ id: "incapacitated", label: "Incapacitated", summary: "Incapacitado: n\xE3o pode tomar a\xE7\xF5es, rea\xE7\xF5es nem mover-se." },
|
|
63
|
+
{ id: "invisible", label: "Invisible", summary: "Invis\xEDvel: vantagem em ataques; atacantes t\xEAm desvantagem." },
|
|
64
|
+
{ id: "paralyzed", label: "Paralyzed", summary: "Paralisado: incapacitado, n\xE3o pode mover/falar; ataques a at\xE9 1,5m cr\xEDtico autom\xE1tico." },
|
|
65
|
+
{ id: "petrified", label: "Petrified", summary: "Petrificado: incapacitado, transformado em pedra; resist\xEAncia a todo dano; imune a venenos e doen\xE7as." },
|
|
66
|
+
{ id: "poisoned", label: "Poisoned", summary: "Envenenado: desvantagem em ataques e testes de habilidade." },
|
|
67
|
+
{ id: "prone", label: "Prone", summary: "Ca\xEDdo: desvantagem em ataques; corpo-a-corpo contra o alvo t\xEAm vantagem; \xE0 dist\xE2ncia t\xEAm desvantagem." },
|
|
68
|
+
{ id: "restrained", label: "Restrained", summary: "Contido: velocidade 0; desvantagem em ataques e em Dex saves; atacantes t\xEAm vantagem." },
|
|
69
|
+
{ id: "stunned", label: "Stunned", summary: "Atordoado: incapacitado; falha em Str/Dex saves; atacantes t\xEAm vantagem." },
|
|
70
|
+
{ id: "unconscious", label: "Unconscious", summary: "Inconsciente: incapacitado, ca\xEDdo; ataques a at\xE9 1,5m cr\xEDtico autom\xE1tico." }
|
|
71
|
+
];
|
|
72
|
+
var TRACKER_FIELDS = [
|
|
73
|
+
{
|
|
74
|
+
key: "ac",
|
|
75
|
+
label: "CA",
|
|
76
|
+
kind: "integer",
|
|
77
|
+
min: 1,
|
|
78
|
+
max: 30,
|
|
79
|
+
default: 10,
|
|
80
|
+
description: "Classe de Armadura \u2014 DC para ser atingido por ataques."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
key: "exhaustion",
|
|
84
|
+
label: "Exh",
|
|
85
|
+
kind: "integer",
|
|
86
|
+
min: 0,
|
|
87
|
+
max: 10,
|
|
88
|
+
default: 0,
|
|
89
|
+
description: "N\xEDvel de Exhaustion (0..10). Cada ponto d\xE1 \u22122 cumulativo em d20 tests; 10 = morte."
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
key: "deathSuccesses",
|
|
93
|
+
label: "M\u2713",
|
|
94
|
+
kind: "integer",
|
|
95
|
+
min: 0,
|
|
96
|
+
max: 3,
|
|
97
|
+
default: 0,
|
|
98
|
+
description: "Sucessos em testes de resist\xEAncia \xE0 morte (3 = estabilizado)."
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
key: "deathFailures",
|
|
102
|
+
label: "M\u2717",
|
|
103
|
+
kind: "integer",
|
|
104
|
+
min: 0,
|
|
105
|
+
max: 3,
|
|
106
|
+
default: 0,
|
|
107
|
+
description: "Falhas em testes de resist\xEAncia \xE0 morte (3 = morte)."
|
|
108
|
+
}
|
|
109
|
+
];
|
|
110
|
+
function abilityMod(score) {
|
|
111
|
+
return Math.floor((score - 10) / 2);
|
|
112
|
+
}
|
|
113
|
+
function spellSaveDC(proficiency, casterMod) {
|
|
114
|
+
return 8 + proficiency + casterMod;
|
|
115
|
+
}
|
|
116
|
+
function spellAttackBonus(proficiency, casterMod) {
|
|
117
|
+
return proficiency + casterMod;
|
|
118
|
+
}
|
|
119
|
+
function exhaustionPenalty(level) {
|
|
120
|
+
const clamped = Math.max(0, Math.min(10, Math.trunc(level)));
|
|
121
|
+
return clamped === 0 ? 0 : -2 * clamped;
|
|
122
|
+
}
|
|
123
|
+
function rollD20({
|
|
124
|
+
modifier = 0,
|
|
125
|
+
advantage = false,
|
|
126
|
+
disadvantage = false,
|
|
127
|
+
exhaustion = 0
|
|
128
|
+
}) {
|
|
129
|
+
const cancelled = advantage && disadvantage;
|
|
130
|
+
const useAdv = advantage && !cancelled;
|
|
131
|
+
const useDis = disadvantage && !cancelled;
|
|
132
|
+
const count = useAdv || useDis ? 2 : 1;
|
|
133
|
+
const dice = roll(20, count);
|
|
134
|
+
const picked = useAdv ? Math.max(...dice) : useDis ? Math.min(...dice) : dice[0];
|
|
135
|
+
const exhPenalty = exhaustionPenalty(exhaustion);
|
|
136
|
+
const effectiveMod = modifier + exhPenalty;
|
|
137
|
+
const total = picked + effectiveMod;
|
|
138
|
+
const notes = [];
|
|
139
|
+
if (cancelled) notes.push("vantagem/desvantagem se cancelaram");
|
|
140
|
+
else if (useAdv) notes.push("vantagem");
|
|
141
|
+
else if (useDis) notes.push("desvantagem");
|
|
142
|
+
if (exhPenalty !== 0) notes.push(`exaust\xE3o ${exhaustion} (${exhPenalty})`);
|
|
143
|
+
if (picked === 20) notes.push("cr\xEDtico natural");
|
|
144
|
+
if (picked === 1) notes.push("falha cr\xEDtica");
|
|
145
|
+
const modStr = effectiveMod === 0 ? "" : effectiveMod > 0 ? `+${effectiveMod}` : `${effectiveMod}`;
|
|
146
|
+
const dStr = count === 2 ? `2d20${useAdv ? "kh1" : "kl1"}` : "1d20";
|
|
147
|
+
return { rolls: dice, modifier: effectiveMod, total, notation: `${dStr}${modStr}`, notes };
|
|
148
|
+
}
|
|
149
|
+
function rollAttack(params) {
|
|
150
|
+
const result = rollD20(params);
|
|
151
|
+
const adv = params.advantage && !params.disadvantage;
|
|
152
|
+
const d20 = result.rolls.length === 2 ? adv ? result.rolls[0] >= result.rolls[1] ? result.rolls[0] : result.rolls[1] : result.rolls[0] <= result.rolls[1] ? result.rolls[0] : result.rolls[1] : result.rolls[0];
|
|
153
|
+
const notes = [...result.notes ?? []];
|
|
154
|
+
if (params.targetAC !== void 0) {
|
|
155
|
+
const natural20 = d20 === 20;
|
|
156
|
+
const natural1 = d20 === 1;
|
|
157
|
+
const hit = natural20 || !natural1 && result.total >= params.targetAC;
|
|
158
|
+
notes.push(
|
|
159
|
+
hit ? natural20 ? "acerto cr\xEDtico" : "acertou" : natural1 ? "erro cr\xEDtico" : "errou"
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
return { ...result, notes };
|
|
163
|
+
}
|
|
164
|
+
function rollSave(params) {
|
|
165
|
+
const result = rollD20(params);
|
|
166
|
+
const notes = [...result.notes ?? []];
|
|
167
|
+
notes.push(result.total >= params.dc ? "sucesso" : "falha");
|
|
168
|
+
return { ...result, notes: notes.concat([`DC ${params.dc}`]) };
|
|
169
|
+
}
|
|
170
|
+
function rollDamage({ count, sides, modifier = 0, critical = false }) {
|
|
171
|
+
const totalDice = critical ? count * 2 : count;
|
|
172
|
+
const dice = roll(sides, totalDice);
|
|
173
|
+
const sum = dice.reduce((a, b) => a + b, 0);
|
|
174
|
+
const total = Math.max(0, sum + modifier);
|
|
175
|
+
const modStr = modifier === 0 ? "" : modifier > 0 ? `+${modifier}` : `${modifier}`;
|
|
176
|
+
return {
|
|
177
|
+
rolls: dice,
|
|
178
|
+
modifier,
|
|
179
|
+
total,
|
|
180
|
+
notation: `${totalDice}d${sides}${modStr}${critical ? " (crit)" : ""}`,
|
|
181
|
+
notes: critical ? ["dano cr\xEDtico \u2014 dados dobrados"] : []
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function applyDndDamage(incoming, ctx) {
|
|
185
|
+
const t = ctx.target;
|
|
186
|
+
const type = ctx.type;
|
|
187
|
+
if (!type || !t) return { final: Math.max(0, incoming), notes: [] };
|
|
188
|
+
if (t.immunity?.includes(type)) return { final: 0, notes: [`imune a ${type}`] };
|
|
189
|
+
if (t.resistance?.includes(type)) {
|
|
190
|
+
return { final: Math.floor(incoming / 2), notes: [`resist\xEAncia a ${type} (metade)`] };
|
|
191
|
+
}
|
|
192
|
+
if (t.vulnerability?.includes(type)) {
|
|
193
|
+
return { final: incoming * 2, notes: [`vulnerabilidade a ${type} (dobro)`] };
|
|
194
|
+
}
|
|
195
|
+
return { final: Math.max(0, incoming), notes: [] };
|
|
196
|
+
}
|
|
197
|
+
var RULES = {
|
|
198
|
+
roll(kind, params) {
|
|
199
|
+
switch (kind) {
|
|
200
|
+
case "d20":
|
|
201
|
+
case "check":
|
|
202
|
+
case "ability":
|
|
203
|
+
return rollD20(params);
|
|
204
|
+
case "attack":
|
|
205
|
+
return rollAttack(params);
|
|
206
|
+
case "save":
|
|
207
|
+
return rollSave(params);
|
|
208
|
+
case "damage":
|
|
209
|
+
return rollDamage(params);
|
|
210
|
+
default:
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
applyDamage(incoming, target) {
|
|
215
|
+
return applyDndDamage(incoming, target);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
var dnd5e2024 = {
|
|
219
|
+
id: "dnd5e-2024",
|
|
220
|
+
name: "Dungeons & Dragons 5e (2024)",
|
|
221
|
+
ruleVersion: "SRD 5.2",
|
|
222
|
+
attribution: "Contains material from the System Reference Document 5.2 by Wizards of the Coast LLC, licensed under CC-BY 4.0.",
|
|
223
|
+
dicePresets: DICE_PRESETS,
|
|
224
|
+
conditions: CONDITIONS,
|
|
225
|
+
trackerFields: TRACKER_FIELDS,
|
|
226
|
+
rules: RULES
|
|
227
|
+
};
|
|
228
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
229
|
+
0 && (module.exports = {
|
|
230
|
+
abilityMod,
|
|
231
|
+
dnd5e2024,
|
|
232
|
+
exhaustionPenalty,
|
|
233
|
+
resetRoller,
|
|
234
|
+
setRoller,
|
|
235
|
+
spellAttackBonus,
|
|
236
|
+
spellSaveDC
|
|
237
|
+
});
|
|
238
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Dungeons & Dragons 5th Edition (2024 / \"One D&D\").\n *\n * Conteúdo de regras deriva do System Reference Document 5.2, publicado\n * pela Wizards of the Coast LLC sob a Creative Commons Attribution 4.0\n * International License:\n * https://creativecommons.org/licenses/by/4.0/legalcode\n *\n * Mantém compatibilidade mecânica com a edição 2014 — o que mudou:\n * - Exhaustion: agora uma escala 1..10 com −2 cumulativo em d20 tests\n * (era 6 níveis discretos)\n * - Algumas condições com wording refinado\n * - Math de combate (advantage, attack vs AC, saves, crítico) é igual\n *\n * Esta implementação é original (referência conceitual: foundryvtt/dnd5e,\n * MIT). Para spells e features de classe, use o SRD 5.2 oficial.\n */\n\nimport type {\n ConditionDef,\n DicePreset,\n RollResult,\n System,\n SystemRules,\n TrackerField,\n} from '@lippelt/srd-core'\n\n// ============================================================================\n// Random helper — injetável pra testes determinísticos.\n// ============================================================================\n\ntype Roller = (sides: number) => number\n\nlet roller: Roller = (sides: number) => Math.floor(Math.random() * sides) + 1\n\nexport function setRoller(fn: Roller): void {\n roller = fn\n}\n\nexport function resetRoller(): void {\n roller = (sides: number) => Math.floor(Math.random() * sides) + 1\n}\n\nfunction roll(sides: number, count = 1): number[] {\n const out: number[] = []\n for (let i = 0; i < count; i++) out.push(roller(sides))\n return out\n}\n\n// ============================================================================\n// Presets de dados (idênticos aos 2014)\n// ============================================================================\n\nconst DICE_PRESETS: DicePreset[] = [\n { id: 'd20', label: 'd20', notation: '1d20', category: 'check' },\n { id: 'd20-adv', label: 'd20 (adv.)', notation: 'advantage', category: 'check', description: 'Rola 2d20 e pega o maior.' },\n { id: 'd20-dis', label: 'd20 (desv.)', notation: 'disadvantage', category: 'check', description: 'Rola 2d20 e pega o menor.' },\n { id: 'd4', label: 'd4', notation: '1d4', category: 'damage' },\n { id: 'd6', label: 'd6', notation: '1d6', category: 'damage' },\n { id: 'd8', label: 'd8', notation: '1d8', category: 'damage' },\n { id: 'd10', label: 'd10', notation: '1d10', category: 'damage' },\n { id: 'd12', label: 'd12', notation: '1d12', category: 'damage' },\n { id: 'd100', label: 'd100', notation: '1d100', category: 'special' },\n]\n\n// ============================================================================\n// Condições — SRD 5.2 (CC-BY 4.0)\n//\n// Mudança principal vs 5.1: Exhaustion é uma escala única 1..10, cada nível\n// dá −2 acumulativo em d20 tests; nível 10 = morte.\n// ============================================================================\n\nconst CONDITIONS: ConditionDef[] = [\n { id: 'blinded', label: 'Blinded', summary: 'Cego: falha em testes que exigem visão; atacantes têm vantagem; o cego tem desvantagem.' },\n { id: 'charmed', label: 'Charmed', summary: 'Enfeitiçado: não pode atacar o encantador nem usar habilidades nocivas contra ele.' },\n { id: 'deafened', label: 'Deafened', summary: 'Surdo: falha em testes que dependem de audição.' },\n { id: 'exhaustion', label: 'Exhaustion (level)', summary: 'Exaustão (1..10): −2 acumulativo em todos os testes de d20 por nível; nível 10 = morte.' },\n { id: 'frightened', label: 'Frightened', summary: 'Amedrontado: desvantagem se a fonte do medo estiver à vista; não pode se mover para perto dela.' },\n { id: 'grappled', label: 'Grappled', summary: 'Agarrado: velocidade 0; termina se o agarrador ficar incapacitado ou afastado.' },\n { id: 'incapacitated', label: 'Incapacitated', summary: 'Incapacitado: não pode tomar ações, reações nem mover-se.' },\n { id: 'invisible', label: 'Invisible', summary: 'Invisível: vantagem em ataques; atacantes têm desvantagem.' },\n { id: 'paralyzed', label: 'Paralyzed', summary: 'Paralisado: incapacitado, não pode mover/falar; ataques a até 1,5m crítico automático.' },\n { id: 'petrified', label: 'Petrified', summary: 'Petrificado: incapacitado, transformado em pedra; resistência a todo dano; imune a venenos e doenças.' },\n { id: 'poisoned', label: 'Poisoned', summary: 'Envenenado: desvantagem em ataques e testes de habilidade.' },\n { id: 'prone', label: 'Prone', summary: 'Caído: desvantagem em ataques; corpo-a-corpo contra o alvo têm vantagem; à distância têm desvantagem.' },\n { id: 'restrained', label: 'Restrained', summary: 'Contido: velocidade 0; desvantagem em ataques e em Dex saves; atacantes têm vantagem.' },\n { id: 'stunned', label: 'Stunned', summary: 'Atordoado: incapacitado; falha em Str/Dex saves; atacantes têm vantagem.' },\n { id: 'unconscious', label: 'Unconscious', summary: 'Inconsciente: incapacitado, caído; ataques a até 1,5m crítico automático.' },\n]\n\n// ============================================================================\n// Tracker fields — mesmas do 2014 (AC, death saves)\n// ============================================================================\n\nconst TRACKER_FIELDS: TrackerField[] = [\n {\n key: 'ac',\n label: 'CA',\n kind: 'integer',\n min: 1,\n max: 30,\n default: 10,\n description: 'Classe de Armadura — DC para ser atingido por ataques.',\n },\n {\n key: 'exhaustion',\n label: 'Exh',\n kind: 'integer',\n min: 0,\n max: 10,\n default: 0,\n description: 'Nível de Exhaustion (0..10). Cada ponto dá −2 cumulativo em d20 tests; 10 = morte.',\n },\n {\n key: 'deathSuccesses',\n label: 'M✓',\n kind: 'integer',\n min: 0,\n max: 3,\n default: 0,\n description: 'Sucessos em testes de resistência à morte (3 = estabilizado).',\n },\n {\n key: 'deathFailures',\n label: 'M✗',\n kind: 'integer',\n min: 0,\n max: 3,\n default: 0,\n description: 'Falhas em testes de resistência à morte (3 = morte).',\n },\n]\n\n// ============================================================================\n// Regras automatizadas\n// ============================================================================\n\nexport function abilityMod(score: number): number {\n return Math.floor((score - 10) / 2)\n}\n\nexport function spellSaveDC(proficiency: number, casterMod: number): number {\n return 8 + proficiency + casterMod\n}\n\nexport function spellAttackBonus(proficiency: number, casterMod: number): number {\n return proficiency + casterMod\n}\n\n/** Modificador de exaustão por nível (2024): −2 por nível. */\nexport function exhaustionPenalty(level: number): number {\n const clamped = Math.max(0, Math.min(10, Math.trunc(level)))\n return clamped === 0 ? 0 : -2 * clamped\n}\n\ninterface D20Params {\n modifier?: number\n advantage?: boolean\n disadvantage?: boolean\n /** Nível de exaustão (0..10). Aplica −2 por nível em todos os testes. */\n exhaustion?: number\n}\n\nfunction rollD20({\n modifier = 0,\n advantage = false,\n disadvantage = false,\n exhaustion = 0,\n}: D20Params): RollResult {\n const cancelled = advantage && disadvantage\n const useAdv = advantage && !cancelled\n const useDis = disadvantage && !cancelled\n const count = useAdv || useDis ? 2 : 1\n const dice = roll(20, count)\n const picked = useAdv ? Math.max(...dice) : useDis ? Math.min(...dice) : dice[0]!\n const exhPenalty = exhaustionPenalty(exhaustion)\n const effectiveMod = modifier + exhPenalty\n const total = picked + effectiveMod\n const notes: string[] = []\n if (cancelled) notes.push('vantagem/desvantagem se cancelaram')\n else if (useAdv) notes.push('vantagem')\n else if (useDis) notes.push('desvantagem')\n if (exhPenalty !== 0) notes.push(`exaustão ${exhaustion} (${exhPenalty})`)\n if (picked === 20) notes.push('crítico natural')\n if (picked === 1) notes.push('falha crítica')\n const modStr =\n effectiveMod === 0 ? '' : effectiveMod > 0 ? `+${effectiveMod}` : `${effectiveMod}`\n const dStr = count === 2 ? `2d20${useAdv ? 'kh1' : 'kl1'}` : '1d20'\n return { rolls: dice, modifier: effectiveMod, total, notation: `${dStr}${modStr}`, notes }\n}\n\ninterface AttackParams {\n modifier?: number\n targetAC?: number\n advantage?: boolean\n disadvantage?: boolean\n exhaustion?: number\n}\n\nfunction rollAttack(params: AttackParams): RollResult {\n const result = rollD20(params)\n const adv = params.advantage && !params.disadvantage\n const d20 =\n result.rolls.length === 2\n ? adv\n ? result.rolls[0]! >= result.rolls[1]!\n ? result.rolls[0]!\n : result.rolls[1]!\n : result.rolls[0]! <= result.rolls[1]!\n ? result.rolls[0]!\n : result.rolls[1]!\n : result.rolls[0]!\n const notes = [...(result.notes ?? [])]\n if (params.targetAC !== undefined) {\n const natural20 = d20 === 20\n const natural1 = d20 === 1\n const hit = natural20 || (!natural1 && result.total >= params.targetAC)\n notes.push(\n hit\n ? natural20\n ? 'acerto crítico'\n : 'acertou'\n : natural1\n ? 'erro crítico'\n : 'errou',\n )\n }\n return { ...result, notes }\n}\n\ninterface SaveParams {\n modifier?: number\n dc: number\n advantage?: boolean\n disadvantage?: boolean\n exhaustion?: number\n}\n\nfunction rollSave(params: SaveParams): RollResult {\n const result = rollD20(params)\n const notes = [...(result.notes ?? [])]\n notes.push(result.total >= params.dc ? 'sucesso' : 'falha')\n return { ...result, notes: notes.concat([`DC ${params.dc}`]) }\n}\n\ninterface DamageParams {\n count: number\n sides: number\n modifier?: number\n critical?: boolean\n}\n\nfunction rollDamage({ count, sides, modifier = 0, critical = false }: DamageParams): RollResult {\n const totalDice = critical ? count * 2 : count\n const dice = roll(sides, totalDice)\n const sum = dice.reduce((a, b) => a + b, 0)\n const total = Math.max(0, sum + modifier)\n const modStr = modifier === 0 ? '' : modifier > 0 ? `+${modifier}` : `${modifier}`\n return {\n rolls: dice,\n modifier,\n total,\n notation: `${totalDice}d${sides}${modStr}${critical ? ' (crit)' : ''}`,\n notes: critical ? ['dano crítico — dados dobrados'] : [],\n }\n}\n\ninterface DamageTarget {\n resistance?: string[]\n vulnerability?: string[]\n immunity?: string[]\n}\n\ninterface ApplyDamageInput {\n type?: string\n target?: DamageTarget\n}\n\nfunction applyDndDamage(\n incoming: number,\n ctx: ApplyDamageInput,\n): { final: number; notes: string[] } {\n const t = ctx.target\n const type = ctx.type\n if (!type || !t) return { final: Math.max(0, incoming), notes: [] }\n if (t.immunity?.includes(type)) return { final: 0, notes: [`imune a ${type}`] }\n if (t.resistance?.includes(type)) {\n return { final: Math.floor(incoming / 2), notes: [`resistência a ${type} (metade)`] }\n }\n if (t.vulnerability?.includes(type)) {\n return { final: incoming * 2, notes: [`vulnerabilidade a ${type} (dobro)`] }\n }\n return { final: Math.max(0, incoming), notes: [] }\n}\n\n// ============================================================================\n// Bundle\n// ============================================================================\n\nconst RULES: SystemRules = {\n roll(kind, params) {\n switch (kind) {\n case 'd20':\n case 'check':\n case 'ability':\n return rollD20(params as unknown as D20Params)\n case 'attack':\n return rollAttack(params as unknown as AttackParams)\n case 'save':\n return rollSave(params as unknown as SaveParams)\n case 'damage':\n return rollDamage(params as unknown as DamageParams)\n default:\n return null\n }\n },\n applyDamage(incoming, target) {\n return applyDndDamage(incoming, target as ApplyDamageInput)\n },\n}\n\nexport const dnd5e2024: System = {\n id: 'dnd5e-2024',\n name: 'Dungeons & Dragons 5e (2024)',\n ruleVersion: 'SRD 5.2',\n attribution:\n 'Contains material from the System Reference Document 5.2 by Wizards of the Coast LLC, licensed under CC-BY 4.0.',\n dicePresets: DICE_PRESETS,\n conditions: CONDITIONS,\n trackerFields: TRACKER_FIELDS,\n rules: RULES,\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { System } from '@lippelt/srd-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dungeons & Dragons 5th Edition (2024 / "One D&D").
|
|
5
|
+
*
|
|
6
|
+
* Conteúdo de regras deriva do System Reference Document 5.2, publicado
|
|
7
|
+
* pela Wizards of the Coast LLC sob a Creative Commons Attribution 4.0
|
|
8
|
+
* International License:
|
|
9
|
+
* https://creativecommons.org/licenses/by/4.0/legalcode
|
|
10
|
+
*
|
|
11
|
+
* Mantém compatibilidade mecânica com a edição 2014 — o que mudou:
|
|
12
|
+
* - Exhaustion: agora uma escala 1..10 com −2 cumulativo em d20 tests
|
|
13
|
+
* (era 6 níveis discretos)
|
|
14
|
+
* - Algumas condições com wording refinado
|
|
15
|
+
* - Math de combate (advantage, attack vs AC, saves, crítico) é igual
|
|
16
|
+
*
|
|
17
|
+
* Esta implementação é original (referência conceitual: foundryvtt/dnd5e,
|
|
18
|
+
* MIT). Para spells e features de classe, use o SRD 5.2 oficial.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
type Roller = (sides: number) => number;
|
|
22
|
+
declare function setRoller(fn: Roller): void;
|
|
23
|
+
declare function resetRoller(): void;
|
|
24
|
+
declare function abilityMod(score: number): number;
|
|
25
|
+
declare function spellSaveDC(proficiency: number, casterMod: number): number;
|
|
26
|
+
declare function spellAttackBonus(proficiency: number, casterMod: number): number;
|
|
27
|
+
/** Modificador de exaustão por nível (2024): −2 por nível. */
|
|
28
|
+
declare function exhaustionPenalty(level: number): number;
|
|
29
|
+
declare const dnd5e2024: System;
|
|
30
|
+
|
|
31
|
+
export { abilityMod, dnd5e2024, exhaustionPenalty, resetRoller, setRoller, spellAttackBonus, spellSaveDC };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { System } from '@lippelt/srd-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dungeons & Dragons 5th Edition (2024 / "One D&D").
|
|
5
|
+
*
|
|
6
|
+
* Conteúdo de regras deriva do System Reference Document 5.2, publicado
|
|
7
|
+
* pela Wizards of the Coast LLC sob a Creative Commons Attribution 4.0
|
|
8
|
+
* International License:
|
|
9
|
+
* https://creativecommons.org/licenses/by/4.0/legalcode
|
|
10
|
+
*
|
|
11
|
+
* Mantém compatibilidade mecânica com a edição 2014 — o que mudou:
|
|
12
|
+
* - Exhaustion: agora uma escala 1..10 com −2 cumulativo em d20 tests
|
|
13
|
+
* (era 6 níveis discretos)
|
|
14
|
+
* - Algumas condições com wording refinado
|
|
15
|
+
* - Math de combate (advantage, attack vs AC, saves, crítico) é igual
|
|
16
|
+
*
|
|
17
|
+
* Esta implementação é original (referência conceitual: foundryvtt/dnd5e,
|
|
18
|
+
* MIT). Para spells e features de classe, use o SRD 5.2 oficial.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
type Roller = (sides: number) => number;
|
|
22
|
+
declare function setRoller(fn: Roller): void;
|
|
23
|
+
declare function resetRoller(): void;
|
|
24
|
+
declare function abilityMod(score: number): number;
|
|
25
|
+
declare function spellSaveDC(proficiency: number, casterMod: number): number;
|
|
26
|
+
declare function spellAttackBonus(proficiency: number, casterMod: number): number;
|
|
27
|
+
/** Modificador de exaustão por nível (2024): −2 por nível. */
|
|
28
|
+
declare function exhaustionPenalty(level: number): number;
|
|
29
|
+
declare const dnd5e2024: System;
|
|
30
|
+
|
|
31
|
+
export { abilityMod, dnd5e2024, exhaustionPenalty, resetRoller, setRoller, spellAttackBonus, spellSaveDC };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var roller = (sides) => Math.floor(Math.random() * sides) + 1;
|
|
3
|
+
function setRoller(fn) {
|
|
4
|
+
roller = fn;
|
|
5
|
+
}
|
|
6
|
+
function resetRoller() {
|
|
7
|
+
roller = (sides) => Math.floor(Math.random() * sides) + 1;
|
|
8
|
+
}
|
|
9
|
+
function roll(sides, count = 1) {
|
|
10
|
+
const out = [];
|
|
11
|
+
for (let i = 0; i < count; i++) out.push(roller(sides));
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
var DICE_PRESETS = [
|
|
15
|
+
{ id: "d20", label: "d20", notation: "1d20", category: "check" },
|
|
16
|
+
{ id: "d20-adv", label: "d20 (adv.)", notation: "advantage", category: "check", description: "Rola 2d20 e pega o maior." },
|
|
17
|
+
{ id: "d20-dis", label: "d20 (desv.)", notation: "disadvantage", category: "check", description: "Rola 2d20 e pega o menor." },
|
|
18
|
+
{ id: "d4", label: "d4", notation: "1d4", category: "damage" },
|
|
19
|
+
{ id: "d6", label: "d6", notation: "1d6", category: "damage" },
|
|
20
|
+
{ id: "d8", label: "d8", notation: "1d8", category: "damage" },
|
|
21
|
+
{ id: "d10", label: "d10", notation: "1d10", category: "damage" },
|
|
22
|
+
{ id: "d12", label: "d12", notation: "1d12", category: "damage" },
|
|
23
|
+
{ id: "d100", label: "d100", notation: "1d100", category: "special" }
|
|
24
|
+
];
|
|
25
|
+
var CONDITIONS = [
|
|
26
|
+
{ id: "blinded", label: "Blinded", summary: "Cego: falha em testes que exigem vis\xE3o; atacantes t\xEAm vantagem; o cego tem desvantagem." },
|
|
27
|
+
{ id: "charmed", label: "Charmed", summary: "Enfeiti\xE7ado: n\xE3o pode atacar o encantador nem usar habilidades nocivas contra ele." },
|
|
28
|
+
{ id: "deafened", label: "Deafened", summary: "Surdo: falha em testes que dependem de audi\xE7\xE3o." },
|
|
29
|
+
{ id: "exhaustion", label: "Exhaustion (level)", summary: "Exaust\xE3o (1..10): \u22122 acumulativo em todos os testes de d20 por n\xEDvel; n\xEDvel 10 = morte." },
|
|
30
|
+
{ id: "frightened", label: "Frightened", summary: "Amedrontado: desvantagem se a fonte do medo estiver \xE0 vista; n\xE3o pode se mover para perto dela." },
|
|
31
|
+
{ id: "grappled", label: "Grappled", summary: "Agarrado: velocidade 0; termina se o agarrador ficar incapacitado ou afastado." },
|
|
32
|
+
{ id: "incapacitated", label: "Incapacitated", summary: "Incapacitado: n\xE3o pode tomar a\xE7\xF5es, rea\xE7\xF5es nem mover-se." },
|
|
33
|
+
{ id: "invisible", label: "Invisible", summary: "Invis\xEDvel: vantagem em ataques; atacantes t\xEAm desvantagem." },
|
|
34
|
+
{ id: "paralyzed", label: "Paralyzed", summary: "Paralisado: incapacitado, n\xE3o pode mover/falar; ataques a at\xE9 1,5m cr\xEDtico autom\xE1tico." },
|
|
35
|
+
{ id: "petrified", label: "Petrified", summary: "Petrificado: incapacitado, transformado em pedra; resist\xEAncia a todo dano; imune a venenos e doen\xE7as." },
|
|
36
|
+
{ id: "poisoned", label: "Poisoned", summary: "Envenenado: desvantagem em ataques e testes de habilidade." },
|
|
37
|
+
{ id: "prone", label: "Prone", summary: "Ca\xEDdo: desvantagem em ataques; corpo-a-corpo contra o alvo t\xEAm vantagem; \xE0 dist\xE2ncia t\xEAm desvantagem." },
|
|
38
|
+
{ id: "restrained", label: "Restrained", summary: "Contido: velocidade 0; desvantagem em ataques e em Dex saves; atacantes t\xEAm vantagem." },
|
|
39
|
+
{ id: "stunned", label: "Stunned", summary: "Atordoado: incapacitado; falha em Str/Dex saves; atacantes t\xEAm vantagem." },
|
|
40
|
+
{ id: "unconscious", label: "Unconscious", summary: "Inconsciente: incapacitado, ca\xEDdo; ataques a at\xE9 1,5m cr\xEDtico autom\xE1tico." }
|
|
41
|
+
];
|
|
42
|
+
var TRACKER_FIELDS = [
|
|
43
|
+
{
|
|
44
|
+
key: "ac",
|
|
45
|
+
label: "CA",
|
|
46
|
+
kind: "integer",
|
|
47
|
+
min: 1,
|
|
48
|
+
max: 30,
|
|
49
|
+
default: 10,
|
|
50
|
+
description: "Classe de Armadura \u2014 DC para ser atingido por ataques."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: "exhaustion",
|
|
54
|
+
label: "Exh",
|
|
55
|
+
kind: "integer",
|
|
56
|
+
min: 0,
|
|
57
|
+
max: 10,
|
|
58
|
+
default: 0,
|
|
59
|
+
description: "N\xEDvel de Exhaustion (0..10). Cada ponto d\xE1 \u22122 cumulativo em d20 tests; 10 = morte."
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
key: "deathSuccesses",
|
|
63
|
+
label: "M\u2713",
|
|
64
|
+
kind: "integer",
|
|
65
|
+
min: 0,
|
|
66
|
+
max: 3,
|
|
67
|
+
default: 0,
|
|
68
|
+
description: "Sucessos em testes de resist\xEAncia \xE0 morte (3 = estabilizado)."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: "deathFailures",
|
|
72
|
+
label: "M\u2717",
|
|
73
|
+
kind: "integer",
|
|
74
|
+
min: 0,
|
|
75
|
+
max: 3,
|
|
76
|
+
default: 0,
|
|
77
|
+
description: "Falhas em testes de resist\xEAncia \xE0 morte (3 = morte)."
|
|
78
|
+
}
|
|
79
|
+
];
|
|
80
|
+
function abilityMod(score) {
|
|
81
|
+
return Math.floor((score - 10) / 2);
|
|
82
|
+
}
|
|
83
|
+
function spellSaveDC(proficiency, casterMod) {
|
|
84
|
+
return 8 + proficiency + casterMod;
|
|
85
|
+
}
|
|
86
|
+
function spellAttackBonus(proficiency, casterMod) {
|
|
87
|
+
return proficiency + casterMod;
|
|
88
|
+
}
|
|
89
|
+
function exhaustionPenalty(level) {
|
|
90
|
+
const clamped = Math.max(0, Math.min(10, Math.trunc(level)));
|
|
91
|
+
return clamped === 0 ? 0 : -2 * clamped;
|
|
92
|
+
}
|
|
93
|
+
function rollD20({
|
|
94
|
+
modifier = 0,
|
|
95
|
+
advantage = false,
|
|
96
|
+
disadvantage = false,
|
|
97
|
+
exhaustion = 0
|
|
98
|
+
}) {
|
|
99
|
+
const cancelled = advantage && disadvantage;
|
|
100
|
+
const useAdv = advantage && !cancelled;
|
|
101
|
+
const useDis = disadvantage && !cancelled;
|
|
102
|
+
const count = useAdv || useDis ? 2 : 1;
|
|
103
|
+
const dice = roll(20, count);
|
|
104
|
+
const picked = useAdv ? Math.max(...dice) : useDis ? Math.min(...dice) : dice[0];
|
|
105
|
+
const exhPenalty = exhaustionPenalty(exhaustion);
|
|
106
|
+
const effectiveMod = modifier + exhPenalty;
|
|
107
|
+
const total = picked + effectiveMod;
|
|
108
|
+
const notes = [];
|
|
109
|
+
if (cancelled) notes.push("vantagem/desvantagem se cancelaram");
|
|
110
|
+
else if (useAdv) notes.push("vantagem");
|
|
111
|
+
else if (useDis) notes.push("desvantagem");
|
|
112
|
+
if (exhPenalty !== 0) notes.push(`exaust\xE3o ${exhaustion} (${exhPenalty})`);
|
|
113
|
+
if (picked === 20) notes.push("cr\xEDtico natural");
|
|
114
|
+
if (picked === 1) notes.push("falha cr\xEDtica");
|
|
115
|
+
const modStr = effectiveMod === 0 ? "" : effectiveMod > 0 ? `+${effectiveMod}` : `${effectiveMod}`;
|
|
116
|
+
const dStr = count === 2 ? `2d20${useAdv ? "kh1" : "kl1"}` : "1d20";
|
|
117
|
+
return { rolls: dice, modifier: effectiveMod, total, notation: `${dStr}${modStr}`, notes };
|
|
118
|
+
}
|
|
119
|
+
function rollAttack(params) {
|
|
120
|
+
const result = rollD20(params);
|
|
121
|
+
const adv = params.advantage && !params.disadvantage;
|
|
122
|
+
const d20 = result.rolls.length === 2 ? adv ? result.rolls[0] >= result.rolls[1] ? result.rolls[0] : result.rolls[1] : result.rolls[0] <= result.rolls[1] ? result.rolls[0] : result.rolls[1] : result.rolls[0];
|
|
123
|
+
const notes = [...result.notes ?? []];
|
|
124
|
+
if (params.targetAC !== void 0) {
|
|
125
|
+
const natural20 = d20 === 20;
|
|
126
|
+
const natural1 = d20 === 1;
|
|
127
|
+
const hit = natural20 || !natural1 && result.total >= params.targetAC;
|
|
128
|
+
notes.push(
|
|
129
|
+
hit ? natural20 ? "acerto cr\xEDtico" : "acertou" : natural1 ? "erro cr\xEDtico" : "errou"
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
return { ...result, notes };
|
|
133
|
+
}
|
|
134
|
+
function rollSave(params) {
|
|
135
|
+
const result = rollD20(params);
|
|
136
|
+
const notes = [...result.notes ?? []];
|
|
137
|
+
notes.push(result.total >= params.dc ? "sucesso" : "falha");
|
|
138
|
+
return { ...result, notes: notes.concat([`DC ${params.dc}`]) };
|
|
139
|
+
}
|
|
140
|
+
function rollDamage({ count, sides, modifier = 0, critical = false }) {
|
|
141
|
+
const totalDice = critical ? count * 2 : count;
|
|
142
|
+
const dice = roll(sides, totalDice);
|
|
143
|
+
const sum = dice.reduce((a, b) => a + b, 0);
|
|
144
|
+
const total = Math.max(0, sum + modifier);
|
|
145
|
+
const modStr = modifier === 0 ? "" : modifier > 0 ? `+${modifier}` : `${modifier}`;
|
|
146
|
+
return {
|
|
147
|
+
rolls: dice,
|
|
148
|
+
modifier,
|
|
149
|
+
total,
|
|
150
|
+
notation: `${totalDice}d${sides}${modStr}${critical ? " (crit)" : ""}`,
|
|
151
|
+
notes: critical ? ["dano cr\xEDtico \u2014 dados dobrados"] : []
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function applyDndDamage(incoming, ctx) {
|
|
155
|
+
const t = ctx.target;
|
|
156
|
+
const type = ctx.type;
|
|
157
|
+
if (!type || !t) return { final: Math.max(0, incoming), notes: [] };
|
|
158
|
+
if (t.immunity?.includes(type)) return { final: 0, notes: [`imune a ${type}`] };
|
|
159
|
+
if (t.resistance?.includes(type)) {
|
|
160
|
+
return { final: Math.floor(incoming / 2), notes: [`resist\xEAncia a ${type} (metade)`] };
|
|
161
|
+
}
|
|
162
|
+
if (t.vulnerability?.includes(type)) {
|
|
163
|
+
return { final: incoming * 2, notes: [`vulnerabilidade a ${type} (dobro)`] };
|
|
164
|
+
}
|
|
165
|
+
return { final: Math.max(0, incoming), notes: [] };
|
|
166
|
+
}
|
|
167
|
+
var RULES = {
|
|
168
|
+
roll(kind, params) {
|
|
169
|
+
switch (kind) {
|
|
170
|
+
case "d20":
|
|
171
|
+
case "check":
|
|
172
|
+
case "ability":
|
|
173
|
+
return rollD20(params);
|
|
174
|
+
case "attack":
|
|
175
|
+
return rollAttack(params);
|
|
176
|
+
case "save":
|
|
177
|
+
return rollSave(params);
|
|
178
|
+
case "damage":
|
|
179
|
+
return rollDamage(params);
|
|
180
|
+
default:
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
applyDamage(incoming, target) {
|
|
185
|
+
return applyDndDamage(incoming, target);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
var dnd5e2024 = {
|
|
189
|
+
id: "dnd5e-2024",
|
|
190
|
+
name: "Dungeons & Dragons 5e (2024)",
|
|
191
|
+
ruleVersion: "SRD 5.2",
|
|
192
|
+
attribution: "Contains material from the System Reference Document 5.2 by Wizards of the Coast LLC, licensed under CC-BY 4.0.",
|
|
193
|
+
dicePresets: DICE_PRESETS,
|
|
194
|
+
conditions: CONDITIONS,
|
|
195
|
+
trackerFields: TRACKER_FIELDS,
|
|
196
|
+
rules: RULES
|
|
197
|
+
};
|
|
198
|
+
export {
|
|
199
|
+
abilityMod,
|
|
200
|
+
dnd5e2024,
|
|
201
|
+
exhaustionPenalty,
|
|
202
|
+
resetRoller,
|
|
203
|
+
setRoller,
|
|
204
|
+
spellAttackBonus,
|
|
205
|
+
spellSaveDC
|
|
206
|
+
};
|
|
207
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Dungeons & Dragons 5th Edition (2024 / \"One D&D\").\n *\n * Conteúdo de regras deriva do System Reference Document 5.2, publicado\n * pela Wizards of the Coast LLC sob a Creative Commons Attribution 4.0\n * International License:\n * https://creativecommons.org/licenses/by/4.0/legalcode\n *\n * Mantém compatibilidade mecânica com a edição 2014 — o que mudou:\n * - Exhaustion: agora uma escala 1..10 com −2 cumulativo em d20 tests\n * (era 6 níveis discretos)\n * - Algumas condições com wording refinado\n * - Math de combate (advantage, attack vs AC, saves, crítico) é igual\n *\n * Esta implementação é original (referência conceitual: foundryvtt/dnd5e,\n * MIT). Para spells e features de classe, use o SRD 5.2 oficial.\n */\n\nimport type {\n ConditionDef,\n DicePreset,\n RollResult,\n System,\n SystemRules,\n TrackerField,\n} from '@lippelt/srd-core'\n\n// ============================================================================\n// Random helper — injetável pra testes determinísticos.\n// ============================================================================\n\ntype Roller = (sides: number) => number\n\nlet roller: Roller = (sides: number) => Math.floor(Math.random() * sides) + 1\n\nexport function setRoller(fn: Roller): void {\n roller = fn\n}\n\nexport function resetRoller(): void {\n roller = (sides: number) => Math.floor(Math.random() * sides) + 1\n}\n\nfunction roll(sides: number, count = 1): number[] {\n const out: number[] = []\n for (let i = 0; i < count; i++) out.push(roller(sides))\n return out\n}\n\n// ============================================================================\n// Presets de dados (idênticos aos 2014)\n// ============================================================================\n\nconst DICE_PRESETS: DicePreset[] = [\n { id: 'd20', label: 'd20', notation: '1d20', category: 'check' },\n { id: 'd20-adv', label: 'd20 (adv.)', notation: 'advantage', category: 'check', description: 'Rola 2d20 e pega o maior.' },\n { id: 'd20-dis', label: 'd20 (desv.)', notation: 'disadvantage', category: 'check', description: 'Rola 2d20 e pega o menor.' },\n { id: 'd4', label: 'd4', notation: '1d4', category: 'damage' },\n { id: 'd6', label: 'd6', notation: '1d6', category: 'damage' },\n { id: 'd8', label: 'd8', notation: '1d8', category: 'damage' },\n { id: 'd10', label: 'd10', notation: '1d10', category: 'damage' },\n { id: 'd12', label: 'd12', notation: '1d12', category: 'damage' },\n { id: 'd100', label: 'd100', notation: '1d100', category: 'special' },\n]\n\n// ============================================================================\n// Condições — SRD 5.2 (CC-BY 4.0)\n//\n// Mudança principal vs 5.1: Exhaustion é uma escala única 1..10, cada nível\n// dá −2 acumulativo em d20 tests; nível 10 = morte.\n// ============================================================================\n\nconst CONDITIONS: ConditionDef[] = [\n { id: 'blinded', label: 'Blinded', summary: 'Cego: falha em testes que exigem visão; atacantes têm vantagem; o cego tem desvantagem.' },\n { id: 'charmed', label: 'Charmed', summary: 'Enfeitiçado: não pode atacar o encantador nem usar habilidades nocivas contra ele.' },\n { id: 'deafened', label: 'Deafened', summary: 'Surdo: falha em testes que dependem de audição.' },\n { id: 'exhaustion', label: 'Exhaustion (level)', summary: 'Exaustão (1..10): −2 acumulativo em todos os testes de d20 por nível; nível 10 = morte.' },\n { id: 'frightened', label: 'Frightened', summary: 'Amedrontado: desvantagem se a fonte do medo estiver à vista; não pode se mover para perto dela.' },\n { id: 'grappled', label: 'Grappled', summary: 'Agarrado: velocidade 0; termina se o agarrador ficar incapacitado ou afastado.' },\n { id: 'incapacitated', label: 'Incapacitated', summary: 'Incapacitado: não pode tomar ações, reações nem mover-se.' },\n { id: 'invisible', label: 'Invisible', summary: 'Invisível: vantagem em ataques; atacantes têm desvantagem.' },\n { id: 'paralyzed', label: 'Paralyzed', summary: 'Paralisado: incapacitado, não pode mover/falar; ataques a até 1,5m crítico automático.' },\n { id: 'petrified', label: 'Petrified', summary: 'Petrificado: incapacitado, transformado em pedra; resistência a todo dano; imune a venenos e doenças.' },\n { id: 'poisoned', label: 'Poisoned', summary: 'Envenenado: desvantagem em ataques e testes de habilidade.' },\n { id: 'prone', label: 'Prone', summary: 'Caído: desvantagem em ataques; corpo-a-corpo contra o alvo têm vantagem; à distância têm desvantagem.' },\n { id: 'restrained', label: 'Restrained', summary: 'Contido: velocidade 0; desvantagem em ataques e em Dex saves; atacantes têm vantagem.' },\n { id: 'stunned', label: 'Stunned', summary: 'Atordoado: incapacitado; falha em Str/Dex saves; atacantes têm vantagem.' },\n { id: 'unconscious', label: 'Unconscious', summary: 'Inconsciente: incapacitado, caído; ataques a até 1,5m crítico automático.' },\n]\n\n// ============================================================================\n// Tracker fields — mesmas do 2014 (AC, death saves)\n// ============================================================================\n\nconst TRACKER_FIELDS: TrackerField[] = [\n {\n key: 'ac',\n label: 'CA',\n kind: 'integer',\n min: 1,\n max: 30,\n default: 10,\n description: 'Classe de Armadura — DC para ser atingido por ataques.',\n },\n {\n key: 'exhaustion',\n label: 'Exh',\n kind: 'integer',\n min: 0,\n max: 10,\n default: 0,\n description: 'Nível de Exhaustion (0..10). Cada ponto dá −2 cumulativo em d20 tests; 10 = morte.',\n },\n {\n key: 'deathSuccesses',\n label: 'M✓',\n kind: 'integer',\n min: 0,\n max: 3,\n default: 0,\n description: 'Sucessos em testes de resistência à morte (3 = estabilizado).',\n },\n {\n key: 'deathFailures',\n label: 'M✗',\n kind: 'integer',\n min: 0,\n max: 3,\n default: 0,\n description: 'Falhas em testes de resistência à morte (3 = morte).',\n },\n]\n\n// ============================================================================\n// Regras automatizadas\n// ============================================================================\n\nexport function abilityMod(score: number): number {\n return Math.floor((score - 10) / 2)\n}\n\nexport function spellSaveDC(proficiency: number, casterMod: number): number {\n return 8 + proficiency + casterMod\n}\n\nexport function spellAttackBonus(proficiency: number, casterMod: number): number {\n return proficiency + casterMod\n}\n\n/** Modificador de exaustão por nível (2024): −2 por nível. */\nexport function exhaustionPenalty(level: number): number {\n const clamped = Math.max(0, Math.min(10, Math.trunc(level)))\n return clamped === 0 ? 0 : -2 * clamped\n}\n\ninterface D20Params {\n modifier?: number\n advantage?: boolean\n disadvantage?: boolean\n /** Nível de exaustão (0..10). Aplica −2 por nível em todos os testes. */\n exhaustion?: number\n}\n\nfunction rollD20({\n modifier = 0,\n advantage = false,\n disadvantage = false,\n exhaustion = 0,\n}: D20Params): RollResult {\n const cancelled = advantage && disadvantage\n const useAdv = advantage && !cancelled\n const useDis = disadvantage && !cancelled\n const count = useAdv || useDis ? 2 : 1\n const dice = roll(20, count)\n const picked = useAdv ? Math.max(...dice) : useDis ? Math.min(...dice) : dice[0]!\n const exhPenalty = exhaustionPenalty(exhaustion)\n const effectiveMod = modifier + exhPenalty\n const total = picked + effectiveMod\n const notes: string[] = []\n if (cancelled) notes.push('vantagem/desvantagem se cancelaram')\n else if (useAdv) notes.push('vantagem')\n else if (useDis) notes.push('desvantagem')\n if (exhPenalty !== 0) notes.push(`exaustão ${exhaustion} (${exhPenalty})`)\n if (picked === 20) notes.push('crítico natural')\n if (picked === 1) notes.push('falha crítica')\n const modStr =\n effectiveMod === 0 ? '' : effectiveMod > 0 ? `+${effectiveMod}` : `${effectiveMod}`\n const dStr = count === 2 ? `2d20${useAdv ? 'kh1' : 'kl1'}` : '1d20'\n return { rolls: dice, modifier: effectiveMod, total, notation: `${dStr}${modStr}`, notes }\n}\n\ninterface AttackParams {\n modifier?: number\n targetAC?: number\n advantage?: boolean\n disadvantage?: boolean\n exhaustion?: number\n}\n\nfunction rollAttack(params: AttackParams): RollResult {\n const result = rollD20(params)\n const adv = params.advantage && !params.disadvantage\n const d20 =\n result.rolls.length === 2\n ? adv\n ? result.rolls[0]! >= result.rolls[1]!\n ? result.rolls[0]!\n : result.rolls[1]!\n : result.rolls[0]! <= result.rolls[1]!\n ? result.rolls[0]!\n : result.rolls[1]!\n : result.rolls[0]!\n const notes = [...(result.notes ?? [])]\n if (params.targetAC !== undefined) {\n const natural20 = d20 === 20\n const natural1 = d20 === 1\n const hit = natural20 || (!natural1 && result.total >= params.targetAC)\n notes.push(\n hit\n ? natural20\n ? 'acerto crítico'\n : 'acertou'\n : natural1\n ? 'erro crítico'\n : 'errou',\n )\n }\n return { ...result, notes }\n}\n\ninterface SaveParams {\n modifier?: number\n dc: number\n advantage?: boolean\n disadvantage?: boolean\n exhaustion?: number\n}\n\nfunction rollSave(params: SaveParams): RollResult {\n const result = rollD20(params)\n const notes = [...(result.notes ?? [])]\n notes.push(result.total >= params.dc ? 'sucesso' : 'falha')\n return { ...result, notes: notes.concat([`DC ${params.dc}`]) }\n}\n\ninterface DamageParams {\n count: number\n sides: number\n modifier?: number\n critical?: boolean\n}\n\nfunction rollDamage({ count, sides, modifier = 0, critical = false }: DamageParams): RollResult {\n const totalDice = critical ? count * 2 : count\n const dice = roll(sides, totalDice)\n const sum = dice.reduce((a, b) => a + b, 0)\n const total = Math.max(0, sum + modifier)\n const modStr = modifier === 0 ? '' : modifier > 0 ? `+${modifier}` : `${modifier}`\n return {\n rolls: dice,\n modifier,\n total,\n notation: `${totalDice}d${sides}${modStr}${critical ? ' (crit)' : ''}`,\n notes: critical ? ['dano crítico — dados dobrados'] : [],\n }\n}\n\ninterface DamageTarget {\n resistance?: string[]\n vulnerability?: string[]\n immunity?: string[]\n}\n\ninterface ApplyDamageInput {\n type?: string\n target?: DamageTarget\n}\n\nfunction applyDndDamage(\n incoming: number,\n ctx: ApplyDamageInput,\n): { final: number; notes: string[] } {\n const t = ctx.target\n const type = ctx.type\n if (!type || !t) return { final: Math.max(0, incoming), notes: [] }\n if (t.immunity?.includes(type)) return { final: 0, notes: [`imune a ${type}`] }\n if (t.resistance?.includes(type)) {\n return { final: Math.floor(incoming / 2), notes: [`resistência a ${type} (metade)`] }\n }\n if (t.vulnerability?.includes(type)) {\n return { final: incoming * 2, notes: [`vulnerabilidade a ${type} (dobro)`] }\n }\n return { final: Math.max(0, incoming), notes: [] }\n}\n\n// ============================================================================\n// Bundle\n// ============================================================================\n\nconst RULES: SystemRules = {\n roll(kind, params) {\n switch (kind) {\n case 'd20':\n case 'check':\n case 'ability':\n return rollD20(params as unknown as D20Params)\n case 'attack':\n return rollAttack(params as unknown as AttackParams)\n case 'save':\n return rollSave(params as unknown as SaveParams)\n case 'damage':\n return rollDamage(params as unknown as DamageParams)\n default:\n return null\n }\n },\n applyDamage(incoming, target) {\n return applyDndDamage(incoming, target as ApplyDamageInput)\n },\n}\n\nexport const dnd5e2024: System = {\n id: 'dnd5e-2024',\n name: 'Dungeons & Dragons 5e (2024)',\n ruleVersion: 'SRD 5.2',\n attribution:\n 'Contains material from the System Reference Document 5.2 by Wizards of the Coast LLC, licensed under CC-BY 4.0.',\n dicePresets: DICE_PRESETS,\n conditions: CONDITIONS,\n trackerFields: TRACKER_FIELDS,\n rules: RULES,\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lippelt/srd-dnd5e-2024",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "D&D 5e (2024) system module for @lippelt/srd-core — SRD 5.2, CC-BY 4.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"dev": "tsup --watch",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"lint": "eslint .",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:watch": "vitest"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/flippelt/gmcr-srd-systems.git",
|
|
36
|
+
"directory": "packages/dnd5e-2024"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"@lippelt/srd-core": "^0.1.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@lippelt/srd-core": "*"
|
|
43
|
+
}
|
|
44
|
+
}
|