@lippelt/srd-dnd-3.5 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 ADDED
@@ -0,0 +1,30 @@
1
+ # @lippelt/srd-dnd-3.5
2
+
3
+ Módulo D&D 3.5 para [`@lippelt/srd-core`](../core).
4
+
5
+ Baseado no [System Reference Document 3.5](https://www.opengamingfoundation.org/) sob a **Open Game License v1.0a**.
6
+
7
+ ## Bundle
8
+
9
+ - **7 dice presets** — d20, d4–d12, d100
10
+ - **24 conditions** SRD 3.5 (Blinded, Cowering, Dazed, Dying, Frightened, Grappled, Helpless, Panicked, Paralyzed, Petrified, Pinned, Prone, Shaken, Sickened, Stunned, etc)
11
+ - **5 tracker fields** — CA, Fort, Ref, Will, BAB
12
+ - **Rules:**
13
+ - `roll('d20'/'check'/'ability'/'skill', { modifier })` — d20 + mod, anota 20/1 natural
14
+ - `roll('attack', { modifier, targetAC, critRange? })` — vs AC, marca crit (20 nat auto + threat range)
15
+ - `roll('save', { modifier, dc })` — 20 nat auto-passa, 1 nat auto-falha
16
+ - `roll('damage', { count, sides, modifier, critMultiplier? })` — multiplica dados em crit (×2/×3/×4)
17
+ - **Helpers:** `abilityMod`, `spellSaveDC`
18
+
19
+ ## Uso
20
+
21
+ ```ts
22
+ import { register } from '@lippelt/srd-core'
23
+ import { dnd35 } from '@lippelt/srd-dnd-3.5'
24
+
25
+ register(dnd35)
26
+ ```
27
+
28
+ ## Licença
29
+
30
+ [MIT](LICENSE). Conteúdo do SRD 3.5 sob OGL 1.0a.
package/dist/index.cjs ADDED
@@ -0,0 +1,178 @@
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
+ dnd35: () => dnd35,
25
+ resetRoller: () => resetRoller,
26
+ setRoller: () => setRoller,
27
+ spellSaveDC: () => spellSaveDC
28
+ });
29
+ module.exports = __toCommonJS(index_exports);
30
+ var roller = (sides) => Math.floor(Math.random() * sides) + 1;
31
+ function setRoller(fn) {
32
+ roller = fn;
33
+ }
34
+ function resetRoller() {
35
+ roller = (sides) => Math.floor(Math.random() * sides) + 1;
36
+ }
37
+ function rollDice(sides, count = 1) {
38
+ const out = [];
39
+ for (let i = 0; i < count; i++) out.push(roller(sides));
40
+ return out;
41
+ }
42
+ function abilityMod(score) {
43
+ return Math.floor((score - 10) / 2);
44
+ }
45
+ function spellSaveDC(spellLevel, abilityMod2) {
46
+ return 10 + spellLevel + abilityMod2;
47
+ }
48
+ var DICE_PRESETS = [
49
+ { id: "d20", label: "d20", notation: "1d20", category: "check" },
50
+ { id: "d4", label: "d4", notation: "1d4", category: "damage" },
51
+ { id: "d6", label: "d6", notation: "1d6", category: "damage" },
52
+ { id: "d8", label: "d8", notation: "1d8", category: "damage" },
53
+ { id: "d10", label: "d10", notation: "1d10", category: "damage" },
54
+ { id: "d12", label: "d12", notation: "1d12", category: "damage" },
55
+ { id: "d100", label: "d100", notation: "1d100", category: "special" }
56
+ ];
57
+ var CONDITIONS = [
58
+ { id: "blinded", label: "Blinded", summary: "Cego: \u22122 na AC, perde Dex \xE0 AC; metade da velocidade." },
59
+ { id: "confused", label: "Confused", summary: "Confuso: comportamento aleat\xF3rio por d% (atacar, fugir, divagar)." },
60
+ { id: "cowering", label: "Cowering", summary: "Encolhido de medo: sem a\xE7\xF5es; perde Dex \xE0 AC; atacantes t\xEAm +2." },
61
+ { id: "dazed", label: "Dazed", summary: "Aturdido: nenhuma a\xE7\xE3o nesta rodada (livre de defesas)." },
62
+ { id: "dazzled", label: "Dazzled", summary: "Cegado por luz: \u22121 em ataques e Spot/Search." },
63
+ { id: "deafened", label: "Deafened", summary: "Surdo: \u22124 em iniciativa; 20% de chance de falhar feiti\xE7o verbal." },
64
+ { id: "disabled", label: "Disabled", summary: "Com HP em 0: 1 a\xE7\xE3o parcial por turno; a\xE7\xE3o extenuante perde 1 HP." },
65
+ { id: "dying", label: "Dying", summary: "Inconsciente entre \u22121 e \u22129 HP: estabiliza com 10% por turno; sen\xE3o perde 1 HP." },
66
+ { id: "entangled", label: "Entangled", summary: "Enredado: \xBD velocidade, \u22122 em ataques, \u22124 em Dex efetivo." },
67
+ { id: "exhausted", label: "Exhausted", summary: "Exausto: \u22126 em Str/Dex; metade da velocidade. Vira fatigado ap\xF3s 1 hora." },
68
+ { id: "fatigued", label: "Fatigued", summary: "Fatigado: \u22122 em Str/Dex; n\xE3o pode correr ou carga." },
69
+ { id: "frightened", label: "Frightened", summary: "Amedrontado: foge da fonte; \u22122 em ataques/saves/checks." },
70
+ { id: "grappled", label: "Grappled", summary: "Agarrado: sem feiti\xE7os com somatic ou material; perde Dex \xE0 AC." },
71
+ { id: "helpless", label: "Helpless", summary: "Indefeso: Dex efetivo 0; melee crit auto; coup de grace poss\xEDvel." },
72
+ { id: "nauseated", label: "Nauseated", summary: "Nauseado: s\xF3 move a\xE7\xF5es; sem ataques, feiti\xE7os ou concentra\xE7\xE3o." },
73
+ { id: "panicked", label: "Panicked", summary: "Em p\xE2nico: \u22122 em saves/skills; deve fugir; sem ataques." },
74
+ { id: "paralyzed", label: "Paralyzed", summary: "Paralisado: sem a\xE7\xF5es f\xEDsicas; Dex/Str efetivos 0; helpless." },
75
+ { id: "petrified", label: "Petrified", summary: "Petrificado: virou pedra; inconsciente e indefeso." },
76
+ { id: "pinned", label: "Pinned", summary: "Imobilizado em grapple: perde Dex \xE0 AC; sem a\xE7\xF5es exceto escapar." },
77
+ { id: "prone", label: "Prone", summary: "Ca\xEDdo: \u22124 em ataques corpo-a-corpo; +4 AC vs ranged; \u22124 AC vs melee." },
78
+ { id: "shaken", label: "Shaken", summary: "Abalado: \u22122 em ataques, saves, skills." },
79
+ { id: "sickened", label: "Sickened", summary: "Enjoado: \u22122 em ataques, saves, skills, damage." },
80
+ { id: "stunned", label: "Stunned", summary: "Atordoado: sem a\xE7\xF5es; perde Dex; \u22122 AC; solta itens." },
81
+ { id: "unconscious", label: "Unconscious", summary: "Inconsciente: indefeso, normalmente porque HP \u2264 \u22121." }
82
+ ];
83
+ var TRACKER_FIELDS = [
84
+ { key: "ac", label: "CA", kind: "integer", min: 0, max: 50, default: 10, description: "Armor Class (Classe de Armadura)." },
85
+ { key: "fort", label: "Fort", kind: "integer", min: -10, max: 30, default: 0, description: "Fortitude save modifier." },
86
+ { key: "ref", label: "Ref", kind: "integer", min: -10, max: 30, default: 0, description: "Reflex save modifier." },
87
+ { key: "will", label: "Will", kind: "integer", min: -10, max: 30, default: 0, description: "Will save modifier." },
88
+ { key: "bab", label: "BAB", kind: "integer", min: 0, max: 25, default: 0, description: "Base Attack Bonus." }
89
+ ];
90
+ function rollD20({ modifier = 0 }) {
91
+ const d20 = rollDice(20)[0];
92
+ const total = d20 + modifier;
93
+ const notes = [];
94
+ if (d20 === 20) notes.push("20 natural");
95
+ if (d20 === 1) notes.push("1 natural");
96
+ const modStr = modifier === 0 ? "" : modifier > 0 ? `+${modifier}` : `${modifier}`;
97
+ return { rolls: [d20], modifier, total, notation: `1d20${modStr}`, notes };
98
+ }
99
+ function rollAttack(params) {
100
+ const r = rollD20(params);
101
+ const d20 = r.rolls[0];
102
+ const notes = [...r.notes ?? []];
103
+ if (params.targetAC !== void 0) {
104
+ const critRange = params.critRange ?? 20;
105
+ const natural20 = d20 === 20;
106
+ const natural1 = d20 === 1;
107
+ const threat = d20 >= critRange && !natural1;
108
+ const hit = natural20 || !natural1 && r.total >= params.targetAC;
109
+ if (natural20) notes.push("acerto cr\xEDtico (20 natural)");
110
+ else if (threat) notes.push(`amea\xE7a de cr\xEDtico (${d20} \u2265 ${critRange})`);
111
+ else if (natural1) notes.push("erro autom\xE1tico (1 natural)");
112
+ else notes.push(hit ? "acertou" : "errou");
113
+ }
114
+ return { ...r, notes };
115
+ }
116
+ function rollSave({ modifier = 0, dc }) {
117
+ const r = rollD20({ modifier });
118
+ const notes = [...r.notes ?? []];
119
+ const d20 = r.rolls[0];
120
+ const auto = d20 === 1 ? false : d20 === 20 ? true : null;
121
+ const passed = auto !== null ? auto : r.total >= dc;
122
+ notes.push(passed ? "sucesso" : "falha");
123
+ notes.push(`DC ${dc}`);
124
+ return { ...r, notes };
125
+ }
126
+ function rollDamage({ count, sides, modifier = 0, critMultiplier }) {
127
+ const m = critMultiplier && critMultiplier > 1 ? Math.trunc(critMultiplier) : 1;
128
+ const dice = rollDice(sides, count * m);
129
+ const sum = dice.reduce((a, b) => a + b, 0);
130
+ const total = Math.max(0, sum + modifier * m);
131
+ const modStr = modifier === 0 ? "" : modifier > 0 ? `+${modifier}` : `${modifier}`;
132
+ const critStr = m > 1 ? ` (crit \xD7${m})` : "";
133
+ return {
134
+ rolls: dice,
135
+ modifier: modifier * m,
136
+ total,
137
+ notation: `${count * m}d${sides}${modStr}${critStr}`,
138
+ notes: m > 1 ? [`dano cr\xEDtico \u2014 dados e mod \xD7${m}`] : []
139
+ };
140
+ }
141
+ var RULES = {
142
+ roll(kind, params) {
143
+ switch (kind) {
144
+ case "d20":
145
+ case "check":
146
+ case "ability":
147
+ case "skill":
148
+ return rollD20(params);
149
+ case "attack":
150
+ return rollAttack(params);
151
+ case "save":
152
+ return rollSave(params);
153
+ case "damage":
154
+ return rollDamage(params);
155
+ default:
156
+ return null;
157
+ }
158
+ }
159
+ };
160
+ var dnd35 = {
161
+ id: "dnd-3.5",
162
+ name: "Dungeons & Dragons 3.5",
163
+ ruleVersion: "SRD 3.5",
164
+ attribution: "Contains material from the System Reference Document 3.5 by Wizards of the Coast LLC under the Open Game License v1.0a.",
165
+ dicePresets: DICE_PRESETS,
166
+ conditions: CONDITIONS,
167
+ trackerFields: TRACKER_FIELDS,
168
+ rules: RULES
169
+ };
170
+ // Annotate the CommonJS export names for ESM import in node:
171
+ 0 && (module.exports = {
172
+ abilityMod,
173
+ dnd35,
174
+ resetRoller,
175
+ setRoller,
176
+ spellSaveDC
177
+ });
178
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Dungeons & Dragons 3.5 (System Reference Document 3.5).\n *\n * Conteúdo de regras deriva do SRD 3.5 da Wizards of the Coast, sob a\n * Open Game License v1.0a:\n * https://www.opengamingfoundation.org/ogl.html\n *\n * Esta implementação é original (não copia código de outras\n * implementações). Termos de game design (saves Fortitude/Reflex/Will,\n * Base Attack Bonus, AC, etc) são parte da Open Game Content do SRD 3.5.\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\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 rollDice(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// Helpers públicos do D&D 3.5\n// ============================================================================\n\n/** Modificador de habilidade: floor((score - 10) / 2). */\nexport function abilityMod(score: number): number {\n return Math.floor((score - 10) / 2)\n}\n\n/** DC de feitiço: 10 + nível do feitiço + modificador da habilidade. */\nexport function spellSaveDC(spellLevel: number, abilityMod: number): number {\n return 10 + spellLevel + abilityMod\n}\n\n// ============================================================================\n// Presets de dados\n// ============================================================================\n\nconst DICE_PRESETS: DicePreset[] = [\n { id: 'd20', label: 'd20', notation: '1d20', category: 'check' },\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// Conditions — SRD 3.5 (Open Game Content)\n// ============================================================================\n\nconst CONDITIONS: ConditionDef[] = [\n { id: 'blinded', label: 'Blinded', summary: 'Cego: −2 na AC, perde Dex à AC; metade da velocidade.' },\n { id: 'confused', label: 'Confused', summary: 'Confuso: comportamento aleatório por d% (atacar, fugir, divagar).' },\n { id: 'cowering', label: 'Cowering', summary: 'Encolhido de medo: sem ações; perde Dex à AC; atacantes têm +2.' },\n { id: 'dazed', label: 'Dazed', summary: 'Aturdido: nenhuma ação nesta rodada (livre de defesas).' },\n { id: 'dazzled', label: 'Dazzled', summary: 'Cegado por luz: −1 em ataques e Spot/Search.' },\n { id: 'deafened', label: 'Deafened', summary: 'Surdo: −4 em iniciativa; 20% de chance de falhar feitiço verbal.' },\n { id: 'disabled', label: 'Disabled', summary: 'Com HP em 0: 1 ação parcial por turno; ação extenuante perde 1 HP.' },\n { id: 'dying', label: 'Dying', summary: 'Inconsciente entre −1 e −9 HP: estabiliza com 10% por turno; senão perde 1 HP.' },\n { id: 'entangled', label: 'Entangled', summary: 'Enredado: ½ velocidade, −2 em ataques, −4 em Dex efetivo.' },\n { id: 'exhausted', label: 'Exhausted', summary: 'Exausto: −6 em Str/Dex; metade da velocidade. Vira fatigado após 1 hora.' },\n { id: 'fatigued', label: 'Fatigued', summary: 'Fatigado: −2 em Str/Dex; não pode correr ou carga.' },\n { id: 'frightened', label: 'Frightened', summary: 'Amedrontado: foge da fonte; −2 em ataques/saves/checks.' },\n { id: 'grappled', label: 'Grappled', summary: 'Agarrado: sem feitiços com somatic ou material; perde Dex à AC.' },\n { id: 'helpless', label: 'Helpless', summary: 'Indefeso: Dex efetivo 0; melee crit auto; coup de grace possível.' },\n { id: 'nauseated', label: 'Nauseated', summary: 'Nauseado: só move ações; sem ataques, feitiços ou concentração.' },\n { id: 'panicked', label: 'Panicked', summary: 'Em pânico: −2 em saves/skills; deve fugir; sem ataques.' },\n { id: 'paralyzed', label: 'Paralyzed', summary: 'Paralisado: sem ações físicas; Dex/Str efetivos 0; helpless.' },\n { id: 'petrified', label: 'Petrified', summary: 'Petrificado: virou pedra; inconsciente e indefeso.' },\n { id: 'pinned', label: 'Pinned', summary: 'Imobilizado em grapple: perde Dex à AC; sem ações exceto escapar.' },\n { id: 'prone', label: 'Prone', summary: 'Caído: −4 em ataques corpo-a-corpo; +4 AC vs ranged; −4 AC vs melee.' },\n { id: 'shaken', label: 'Shaken', summary: 'Abalado: −2 em ataques, saves, skills.' },\n { id: 'sickened', label: 'Sickened', summary: 'Enjoado: −2 em ataques, saves, skills, damage.' },\n { id: 'stunned', label: 'Stunned', summary: 'Atordoado: sem ações; perde Dex; −2 AC; solta itens.' },\n { id: 'unconscious', label: 'Unconscious', summary: 'Inconsciente: indefeso, normalmente porque HP ≤ −1.' },\n]\n\n// ============================================================================\n// Tracker fields\n// ============================================================================\n\nconst TRACKER_FIELDS: TrackerField[] = [\n { key: 'ac', label: 'CA', kind: 'integer', min: 0, max: 50, default: 10, description: 'Armor Class (Classe de Armadura).' },\n { key: 'fort', label: 'Fort', kind: 'integer', min: -10, max: 30, default: 0, description: 'Fortitude save modifier.' },\n { key: 'ref', label: 'Ref', kind: 'integer', min: -10, max: 30, default: 0, description: 'Reflex save modifier.' },\n { key: 'will', label: 'Will', kind: 'integer', min: -10, max: 30, default: 0, description: 'Will save modifier.' },\n { key: 'bab', label: 'BAB', kind: 'integer', min: 0, max: 25, default: 0, description: 'Base Attack Bonus.' },\n]\n\n// ============================================================================\n// Rules\n// ============================================================================\n\ninterface D20Params {\n modifier?: number\n}\n\nfunction rollD20({ modifier = 0 }: D20Params): RollResult {\n const d20 = rollDice(20)[0]!\n const total = d20 + modifier\n const notes: string[] = []\n if (d20 === 20) notes.push('20 natural')\n if (d20 === 1) notes.push('1 natural')\n const modStr = modifier === 0 ? '' : modifier > 0 ? `+${modifier}` : `${modifier}`\n return { rolls: [d20], modifier, total, notation: `1d20${modStr}`, notes }\n}\n\ninterface AttackParams {\n modifier?: number\n targetAC?: number\n /** Threshold do crítico (default 20). Armas como rapier 18-20, picareta 20/×4 etc. */\n critRange?: number\n}\n\nfunction rollAttack(params: AttackParams): RollResult {\n const r = rollD20(params)\n const d20 = r.rolls[0]!\n const notes = [...(r.notes ?? [])]\n if (params.targetAC !== undefined) {\n const critRange = params.critRange ?? 20\n const natural20 = d20 === 20\n const natural1 = d20 === 1\n const threat = d20 >= critRange && !natural1\n const hit = natural20 || (!natural1 && r.total >= params.targetAC)\n if (natural20) notes.push('acerto crítico (20 natural)')\n else if (threat) notes.push(`ameaça de crítico (${d20} ≥ ${critRange})`)\n else if (natural1) notes.push('erro automático (1 natural)')\n else notes.push(hit ? 'acertou' : 'errou')\n }\n return { ...r, notes }\n}\n\ninterface SaveParams {\n modifier?: number\n dc: number\n}\n\nfunction rollSave({ modifier = 0, dc }: SaveParams): RollResult {\n const r = rollD20({ modifier })\n const notes = [...(r.notes ?? [])]\n // 1 natural sempre falha; 20 natural sempre passa (regra do SRD 3.5).\n const d20 = r.rolls[0]!\n const auto = d20 === 1 ? false : d20 === 20 ? true : null\n const passed = auto !== null ? auto : r.total >= dc\n notes.push(passed ? 'sucesso' : 'falha')\n notes.push(`DC ${dc}`)\n return { ...r, notes }\n}\n\ninterface DamageParams {\n count: number\n sides: number\n modifier?: number\n /** Multiplica os dados de dano em caso de crítico (×2 default, alguns ×3 ou ×4). */\n critMultiplier?: number\n}\n\nfunction rollDamage({ count, sides, modifier = 0, critMultiplier }: DamageParams): RollResult {\n const m = critMultiplier && critMultiplier > 1 ? Math.trunc(critMultiplier) : 1\n const dice = rollDice(sides, count * m)\n const sum = dice.reduce((a, b) => a + b, 0)\n const total = Math.max(0, sum + modifier * m)\n const modStr = modifier === 0 ? '' : modifier > 0 ? `+${modifier}` : `${modifier}`\n const critStr = m > 1 ? ` (crit ×${m})` : ''\n return {\n rolls: dice,\n modifier: modifier * m,\n total,\n notation: `${count * m}d${sides}${modStr}${critStr}`,\n notes: m > 1 ? [`dano crítico — dados e mod ×${m}`] : [],\n }\n}\n\nconst RULES: SystemRules = {\n roll(kind, params) {\n switch (kind) {\n case 'd20':\n case 'check':\n case 'ability':\n case 'skill':\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}\n\n// ============================================================================\n// Bundle\n// ============================================================================\n\nexport const dnd35: System = {\n id: 'dnd-3.5',\n name: 'Dungeons & Dragons 3.5',\n ruleVersion: 'SRD 3.5',\n attribution:\n 'Contains material from the System Reference Document 3.5 by Wizards of the Coast LLC under the Open Game License v1.0a.',\n dicePresets: DICE_PRESETS,\n conditions: CONDITIONS,\n trackerFields: TRACKER_FIELDS,\n rules: RULES,\n}\n"],"mappings":"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","names":["abilityMod"]}
@@ -0,0 +1,24 @@
1
+ import { System } from '@lippelt/srd-core';
2
+
3
+ /**
4
+ * Dungeons & Dragons 3.5 (System Reference Document 3.5).
5
+ *
6
+ * Conteúdo de regras deriva do SRD 3.5 da Wizards of the Coast, sob a
7
+ * Open Game License v1.0a:
8
+ * https://www.opengamingfoundation.org/ogl.html
9
+ *
10
+ * Esta implementação é original (não copia código de outras
11
+ * implementações). Termos de game design (saves Fortitude/Reflex/Will,
12
+ * Base Attack Bonus, AC, etc) são parte da Open Game Content do SRD 3.5.
13
+ */
14
+
15
+ type Roller = (sides: number) => number;
16
+ declare function setRoller(fn: Roller): void;
17
+ declare function resetRoller(): void;
18
+ /** Modificador de habilidade: floor((score - 10) / 2). */
19
+ declare function abilityMod(score: number): number;
20
+ /** DC de feitiço: 10 + nível do feitiço + modificador da habilidade. */
21
+ declare function spellSaveDC(spellLevel: number, abilityMod: number): number;
22
+ declare const dnd35: System;
23
+
24
+ export { abilityMod, dnd35, resetRoller, setRoller, spellSaveDC };
@@ -0,0 +1,24 @@
1
+ import { System } from '@lippelt/srd-core';
2
+
3
+ /**
4
+ * Dungeons & Dragons 3.5 (System Reference Document 3.5).
5
+ *
6
+ * Conteúdo de regras deriva do SRD 3.5 da Wizards of the Coast, sob a
7
+ * Open Game License v1.0a:
8
+ * https://www.opengamingfoundation.org/ogl.html
9
+ *
10
+ * Esta implementação é original (não copia código de outras
11
+ * implementações). Termos de game design (saves Fortitude/Reflex/Will,
12
+ * Base Attack Bonus, AC, etc) são parte da Open Game Content do SRD 3.5.
13
+ */
14
+
15
+ type Roller = (sides: number) => number;
16
+ declare function setRoller(fn: Roller): void;
17
+ declare function resetRoller(): void;
18
+ /** Modificador de habilidade: floor((score - 10) / 2). */
19
+ declare function abilityMod(score: number): number;
20
+ /** DC de feitiço: 10 + nível do feitiço + modificador da habilidade. */
21
+ declare function spellSaveDC(spellLevel: number, abilityMod: number): number;
22
+ declare const dnd35: System;
23
+
24
+ export { abilityMod, dnd35, resetRoller, setRoller, spellSaveDC };
package/dist/index.js ADDED
@@ -0,0 +1,149 @@
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 rollDice(sides, count = 1) {
10
+ const out = [];
11
+ for (let i = 0; i < count; i++) out.push(roller(sides));
12
+ return out;
13
+ }
14
+ function abilityMod(score) {
15
+ return Math.floor((score - 10) / 2);
16
+ }
17
+ function spellSaveDC(spellLevel, abilityMod2) {
18
+ return 10 + spellLevel + abilityMod2;
19
+ }
20
+ var DICE_PRESETS = [
21
+ { id: "d20", label: "d20", notation: "1d20", category: "check" },
22
+ { id: "d4", label: "d4", notation: "1d4", category: "damage" },
23
+ { id: "d6", label: "d6", notation: "1d6", category: "damage" },
24
+ { id: "d8", label: "d8", notation: "1d8", category: "damage" },
25
+ { id: "d10", label: "d10", notation: "1d10", category: "damage" },
26
+ { id: "d12", label: "d12", notation: "1d12", category: "damage" },
27
+ { id: "d100", label: "d100", notation: "1d100", category: "special" }
28
+ ];
29
+ var CONDITIONS = [
30
+ { id: "blinded", label: "Blinded", summary: "Cego: \u22122 na AC, perde Dex \xE0 AC; metade da velocidade." },
31
+ { id: "confused", label: "Confused", summary: "Confuso: comportamento aleat\xF3rio por d% (atacar, fugir, divagar)." },
32
+ { id: "cowering", label: "Cowering", summary: "Encolhido de medo: sem a\xE7\xF5es; perde Dex \xE0 AC; atacantes t\xEAm +2." },
33
+ { id: "dazed", label: "Dazed", summary: "Aturdido: nenhuma a\xE7\xE3o nesta rodada (livre de defesas)." },
34
+ { id: "dazzled", label: "Dazzled", summary: "Cegado por luz: \u22121 em ataques e Spot/Search." },
35
+ { id: "deafened", label: "Deafened", summary: "Surdo: \u22124 em iniciativa; 20% de chance de falhar feiti\xE7o verbal." },
36
+ { id: "disabled", label: "Disabled", summary: "Com HP em 0: 1 a\xE7\xE3o parcial por turno; a\xE7\xE3o extenuante perde 1 HP." },
37
+ { id: "dying", label: "Dying", summary: "Inconsciente entre \u22121 e \u22129 HP: estabiliza com 10% por turno; sen\xE3o perde 1 HP." },
38
+ { id: "entangled", label: "Entangled", summary: "Enredado: \xBD velocidade, \u22122 em ataques, \u22124 em Dex efetivo." },
39
+ { id: "exhausted", label: "Exhausted", summary: "Exausto: \u22126 em Str/Dex; metade da velocidade. Vira fatigado ap\xF3s 1 hora." },
40
+ { id: "fatigued", label: "Fatigued", summary: "Fatigado: \u22122 em Str/Dex; n\xE3o pode correr ou carga." },
41
+ { id: "frightened", label: "Frightened", summary: "Amedrontado: foge da fonte; \u22122 em ataques/saves/checks." },
42
+ { id: "grappled", label: "Grappled", summary: "Agarrado: sem feiti\xE7os com somatic ou material; perde Dex \xE0 AC." },
43
+ { id: "helpless", label: "Helpless", summary: "Indefeso: Dex efetivo 0; melee crit auto; coup de grace poss\xEDvel." },
44
+ { id: "nauseated", label: "Nauseated", summary: "Nauseado: s\xF3 move a\xE7\xF5es; sem ataques, feiti\xE7os ou concentra\xE7\xE3o." },
45
+ { id: "panicked", label: "Panicked", summary: "Em p\xE2nico: \u22122 em saves/skills; deve fugir; sem ataques." },
46
+ { id: "paralyzed", label: "Paralyzed", summary: "Paralisado: sem a\xE7\xF5es f\xEDsicas; Dex/Str efetivos 0; helpless." },
47
+ { id: "petrified", label: "Petrified", summary: "Petrificado: virou pedra; inconsciente e indefeso." },
48
+ { id: "pinned", label: "Pinned", summary: "Imobilizado em grapple: perde Dex \xE0 AC; sem a\xE7\xF5es exceto escapar." },
49
+ { id: "prone", label: "Prone", summary: "Ca\xEDdo: \u22124 em ataques corpo-a-corpo; +4 AC vs ranged; \u22124 AC vs melee." },
50
+ { id: "shaken", label: "Shaken", summary: "Abalado: \u22122 em ataques, saves, skills." },
51
+ { id: "sickened", label: "Sickened", summary: "Enjoado: \u22122 em ataques, saves, skills, damage." },
52
+ { id: "stunned", label: "Stunned", summary: "Atordoado: sem a\xE7\xF5es; perde Dex; \u22122 AC; solta itens." },
53
+ { id: "unconscious", label: "Unconscious", summary: "Inconsciente: indefeso, normalmente porque HP \u2264 \u22121." }
54
+ ];
55
+ var TRACKER_FIELDS = [
56
+ { key: "ac", label: "CA", kind: "integer", min: 0, max: 50, default: 10, description: "Armor Class (Classe de Armadura)." },
57
+ { key: "fort", label: "Fort", kind: "integer", min: -10, max: 30, default: 0, description: "Fortitude save modifier." },
58
+ { key: "ref", label: "Ref", kind: "integer", min: -10, max: 30, default: 0, description: "Reflex save modifier." },
59
+ { key: "will", label: "Will", kind: "integer", min: -10, max: 30, default: 0, description: "Will save modifier." },
60
+ { key: "bab", label: "BAB", kind: "integer", min: 0, max: 25, default: 0, description: "Base Attack Bonus." }
61
+ ];
62
+ function rollD20({ modifier = 0 }) {
63
+ const d20 = rollDice(20)[0];
64
+ const total = d20 + modifier;
65
+ const notes = [];
66
+ if (d20 === 20) notes.push("20 natural");
67
+ if (d20 === 1) notes.push("1 natural");
68
+ const modStr = modifier === 0 ? "" : modifier > 0 ? `+${modifier}` : `${modifier}`;
69
+ return { rolls: [d20], modifier, total, notation: `1d20${modStr}`, notes };
70
+ }
71
+ function rollAttack(params) {
72
+ const r = rollD20(params);
73
+ const d20 = r.rolls[0];
74
+ const notes = [...r.notes ?? []];
75
+ if (params.targetAC !== void 0) {
76
+ const critRange = params.critRange ?? 20;
77
+ const natural20 = d20 === 20;
78
+ const natural1 = d20 === 1;
79
+ const threat = d20 >= critRange && !natural1;
80
+ const hit = natural20 || !natural1 && r.total >= params.targetAC;
81
+ if (natural20) notes.push("acerto cr\xEDtico (20 natural)");
82
+ else if (threat) notes.push(`amea\xE7a de cr\xEDtico (${d20} \u2265 ${critRange})`);
83
+ else if (natural1) notes.push("erro autom\xE1tico (1 natural)");
84
+ else notes.push(hit ? "acertou" : "errou");
85
+ }
86
+ return { ...r, notes };
87
+ }
88
+ function rollSave({ modifier = 0, dc }) {
89
+ const r = rollD20({ modifier });
90
+ const notes = [...r.notes ?? []];
91
+ const d20 = r.rolls[0];
92
+ const auto = d20 === 1 ? false : d20 === 20 ? true : null;
93
+ const passed = auto !== null ? auto : r.total >= dc;
94
+ notes.push(passed ? "sucesso" : "falha");
95
+ notes.push(`DC ${dc}`);
96
+ return { ...r, notes };
97
+ }
98
+ function rollDamage({ count, sides, modifier = 0, critMultiplier }) {
99
+ const m = critMultiplier && critMultiplier > 1 ? Math.trunc(critMultiplier) : 1;
100
+ const dice = rollDice(sides, count * m);
101
+ const sum = dice.reduce((a, b) => a + b, 0);
102
+ const total = Math.max(0, sum + modifier * m);
103
+ const modStr = modifier === 0 ? "" : modifier > 0 ? `+${modifier}` : `${modifier}`;
104
+ const critStr = m > 1 ? ` (crit \xD7${m})` : "";
105
+ return {
106
+ rolls: dice,
107
+ modifier: modifier * m,
108
+ total,
109
+ notation: `${count * m}d${sides}${modStr}${critStr}`,
110
+ notes: m > 1 ? [`dano cr\xEDtico \u2014 dados e mod \xD7${m}`] : []
111
+ };
112
+ }
113
+ var RULES = {
114
+ roll(kind, params) {
115
+ switch (kind) {
116
+ case "d20":
117
+ case "check":
118
+ case "ability":
119
+ case "skill":
120
+ return rollD20(params);
121
+ case "attack":
122
+ return rollAttack(params);
123
+ case "save":
124
+ return rollSave(params);
125
+ case "damage":
126
+ return rollDamage(params);
127
+ default:
128
+ return null;
129
+ }
130
+ }
131
+ };
132
+ var dnd35 = {
133
+ id: "dnd-3.5",
134
+ name: "Dungeons & Dragons 3.5",
135
+ ruleVersion: "SRD 3.5",
136
+ attribution: "Contains material from the System Reference Document 3.5 by Wizards of the Coast LLC under the Open Game License v1.0a.",
137
+ dicePresets: DICE_PRESETS,
138
+ conditions: CONDITIONS,
139
+ trackerFields: TRACKER_FIELDS,
140
+ rules: RULES
141
+ };
142
+ export {
143
+ abilityMod,
144
+ dnd35,
145
+ resetRoller,
146
+ setRoller,
147
+ spellSaveDC
148
+ };
149
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Dungeons & Dragons 3.5 (System Reference Document 3.5).\n *\n * Conteúdo de regras deriva do SRD 3.5 da Wizards of the Coast, sob a\n * Open Game License v1.0a:\n * https://www.opengamingfoundation.org/ogl.html\n *\n * Esta implementação é original (não copia código de outras\n * implementações). Termos de game design (saves Fortitude/Reflex/Will,\n * Base Attack Bonus, AC, etc) são parte da Open Game Content do SRD 3.5.\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\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 rollDice(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// Helpers públicos do D&D 3.5\n// ============================================================================\n\n/** Modificador de habilidade: floor((score - 10) / 2). */\nexport function abilityMod(score: number): number {\n return Math.floor((score - 10) / 2)\n}\n\n/** DC de feitiço: 10 + nível do feitiço + modificador da habilidade. */\nexport function spellSaveDC(spellLevel: number, abilityMod: number): number {\n return 10 + spellLevel + abilityMod\n}\n\n// ============================================================================\n// Presets de dados\n// ============================================================================\n\nconst DICE_PRESETS: DicePreset[] = [\n { id: 'd20', label: 'd20', notation: '1d20', category: 'check' },\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// Conditions — SRD 3.5 (Open Game Content)\n// ============================================================================\n\nconst CONDITIONS: ConditionDef[] = [\n { id: 'blinded', label: 'Blinded', summary: 'Cego: −2 na AC, perde Dex à AC; metade da velocidade.' },\n { id: 'confused', label: 'Confused', summary: 'Confuso: comportamento aleatório por d% (atacar, fugir, divagar).' },\n { id: 'cowering', label: 'Cowering', summary: 'Encolhido de medo: sem ações; perde Dex à AC; atacantes têm +2.' },\n { id: 'dazed', label: 'Dazed', summary: 'Aturdido: nenhuma ação nesta rodada (livre de defesas).' },\n { id: 'dazzled', label: 'Dazzled', summary: 'Cegado por luz: −1 em ataques e Spot/Search.' },\n { id: 'deafened', label: 'Deafened', summary: 'Surdo: −4 em iniciativa; 20% de chance de falhar feitiço verbal.' },\n { id: 'disabled', label: 'Disabled', summary: 'Com HP em 0: 1 ação parcial por turno; ação extenuante perde 1 HP.' },\n { id: 'dying', label: 'Dying', summary: 'Inconsciente entre −1 e −9 HP: estabiliza com 10% por turno; senão perde 1 HP.' },\n { id: 'entangled', label: 'Entangled', summary: 'Enredado: ½ velocidade, −2 em ataques, −4 em Dex efetivo.' },\n { id: 'exhausted', label: 'Exhausted', summary: 'Exausto: −6 em Str/Dex; metade da velocidade. Vira fatigado após 1 hora.' },\n { id: 'fatigued', label: 'Fatigued', summary: 'Fatigado: −2 em Str/Dex; não pode correr ou carga.' },\n { id: 'frightened', label: 'Frightened', summary: 'Amedrontado: foge da fonte; −2 em ataques/saves/checks.' },\n { id: 'grappled', label: 'Grappled', summary: 'Agarrado: sem feitiços com somatic ou material; perde Dex à AC.' },\n { id: 'helpless', label: 'Helpless', summary: 'Indefeso: Dex efetivo 0; melee crit auto; coup de grace possível.' },\n { id: 'nauseated', label: 'Nauseated', summary: 'Nauseado: só move ações; sem ataques, feitiços ou concentração.' },\n { id: 'panicked', label: 'Panicked', summary: 'Em pânico: −2 em saves/skills; deve fugir; sem ataques.' },\n { id: 'paralyzed', label: 'Paralyzed', summary: 'Paralisado: sem ações físicas; Dex/Str efetivos 0; helpless.' },\n { id: 'petrified', label: 'Petrified', summary: 'Petrificado: virou pedra; inconsciente e indefeso.' },\n { id: 'pinned', label: 'Pinned', summary: 'Imobilizado em grapple: perde Dex à AC; sem ações exceto escapar.' },\n { id: 'prone', label: 'Prone', summary: 'Caído: −4 em ataques corpo-a-corpo; +4 AC vs ranged; −4 AC vs melee.' },\n { id: 'shaken', label: 'Shaken', summary: 'Abalado: −2 em ataques, saves, skills.' },\n { id: 'sickened', label: 'Sickened', summary: 'Enjoado: −2 em ataques, saves, skills, damage.' },\n { id: 'stunned', label: 'Stunned', summary: 'Atordoado: sem ações; perde Dex; −2 AC; solta itens.' },\n { id: 'unconscious', label: 'Unconscious', summary: 'Inconsciente: indefeso, normalmente porque HP ≤ −1.' },\n]\n\n// ============================================================================\n// Tracker fields\n// ============================================================================\n\nconst TRACKER_FIELDS: TrackerField[] = [\n { key: 'ac', label: 'CA', kind: 'integer', min: 0, max: 50, default: 10, description: 'Armor Class (Classe de Armadura).' },\n { key: 'fort', label: 'Fort', kind: 'integer', min: -10, max: 30, default: 0, description: 'Fortitude save modifier.' },\n { key: 'ref', label: 'Ref', kind: 'integer', min: -10, max: 30, default: 0, description: 'Reflex save modifier.' },\n { key: 'will', label: 'Will', kind: 'integer', min: -10, max: 30, default: 0, description: 'Will save modifier.' },\n { key: 'bab', label: 'BAB', kind: 'integer', min: 0, max: 25, default: 0, description: 'Base Attack Bonus.' },\n]\n\n// ============================================================================\n// Rules\n// ============================================================================\n\ninterface D20Params {\n modifier?: number\n}\n\nfunction rollD20({ modifier = 0 }: D20Params): RollResult {\n const d20 = rollDice(20)[0]!\n const total = d20 + modifier\n const notes: string[] = []\n if (d20 === 20) notes.push('20 natural')\n if (d20 === 1) notes.push('1 natural')\n const modStr = modifier === 0 ? '' : modifier > 0 ? `+${modifier}` : `${modifier}`\n return { rolls: [d20], modifier, total, notation: `1d20${modStr}`, notes }\n}\n\ninterface AttackParams {\n modifier?: number\n targetAC?: number\n /** Threshold do crítico (default 20). Armas como rapier 18-20, picareta 20/×4 etc. */\n critRange?: number\n}\n\nfunction rollAttack(params: AttackParams): RollResult {\n const r = rollD20(params)\n const d20 = r.rolls[0]!\n const notes = [...(r.notes ?? [])]\n if (params.targetAC !== undefined) {\n const critRange = params.critRange ?? 20\n const natural20 = d20 === 20\n const natural1 = d20 === 1\n const threat = d20 >= critRange && !natural1\n const hit = natural20 || (!natural1 && r.total >= params.targetAC)\n if (natural20) notes.push('acerto crítico (20 natural)')\n else if (threat) notes.push(`ameaça de crítico (${d20} ≥ ${critRange})`)\n else if (natural1) notes.push('erro automático (1 natural)')\n else notes.push(hit ? 'acertou' : 'errou')\n }\n return { ...r, notes }\n}\n\ninterface SaveParams {\n modifier?: number\n dc: number\n}\n\nfunction rollSave({ modifier = 0, dc }: SaveParams): RollResult {\n const r = rollD20({ modifier })\n const notes = [...(r.notes ?? [])]\n // 1 natural sempre falha; 20 natural sempre passa (regra do SRD 3.5).\n const d20 = r.rolls[0]!\n const auto = d20 === 1 ? false : d20 === 20 ? true : null\n const passed = auto !== null ? auto : r.total >= dc\n notes.push(passed ? 'sucesso' : 'falha')\n notes.push(`DC ${dc}`)\n return { ...r, notes }\n}\n\ninterface DamageParams {\n count: number\n sides: number\n modifier?: number\n /** Multiplica os dados de dano em caso de crítico (×2 default, alguns ×3 ou ×4). */\n critMultiplier?: number\n}\n\nfunction rollDamage({ count, sides, modifier = 0, critMultiplier }: DamageParams): RollResult {\n const m = critMultiplier && critMultiplier > 1 ? Math.trunc(critMultiplier) : 1\n const dice = rollDice(sides, count * m)\n const sum = dice.reduce((a, b) => a + b, 0)\n const total = Math.max(0, sum + modifier * m)\n const modStr = modifier === 0 ? '' : modifier > 0 ? `+${modifier}` : `${modifier}`\n const critStr = m > 1 ? ` (crit ×${m})` : ''\n return {\n rolls: dice,\n modifier: modifier * m,\n total,\n notation: `${count * m}d${sides}${modStr}${critStr}`,\n notes: m > 1 ? [`dano crítico — dados e mod ×${m}`] : [],\n }\n}\n\nconst RULES: SystemRules = {\n roll(kind, params) {\n switch (kind) {\n case 'd20':\n case 'check':\n case 'ability':\n case 'skill':\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}\n\n// ============================================================================\n// Bundle\n// ============================================================================\n\nexport const dnd35: System = {\n id: 'dnd-3.5',\n name: 'Dungeons & Dragons 3.5',\n ruleVersion: 'SRD 3.5',\n attribution:\n 'Contains material from the System Reference Document 3.5 by Wizards of the Coast LLC under the Open Game License v1.0a.',\n dicePresets: DICE_PRESETS,\n conditions: CONDITIONS,\n trackerFields: TRACKER_FIELDS,\n rules: RULES,\n}\n"],"mappings":"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","names":["abilityMod"]}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@lippelt/srd-dnd-3.5",
3
+ "version": "0.1.0",
4
+ "description": "D&D 3.5 system module for @lippelt/srd-core — SRD 3.5, OGL 1.0a",
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": ["dist", "README.md", "LICENSE"],
17
+ "publishConfig": { "access": "public" },
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "dev": "tsup --watch",
21
+ "typecheck": "tsc --noEmit",
22
+ "lint": "eslint .",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest"
25
+ },
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/flippelt/gmcr-srd-systems.git",
30
+ "directory": "packages/dnd-3.5"
31
+ },
32
+ "peerDependencies": { "@lippelt/srd-core": "^0.1.0" },
33
+ "devDependencies": { "@lippelt/srd-core": "*" }
34
+ }