diff --git a/rpg/armour.csv b/rpg/armour.csv new file mode 100644 index 0000000..1afa6f2 --- /dev/null +++ b/rpg/armour.csv @@ -0,0 +1,3 @@ +name,type,locations,ap_head,ap_body,ap_larm,ap_rarm,ap_lleg,ap_rleg,max_ag,traits,weight,availability,source,notes +Flak Vest,armor,"Body",0,4,0,0,0,0,,Flak,7,Common,"DH2 CRB","PRZYKŁAD – ZASTĄP" +Power Armour (Astartes),armor,"All",8,10,8,8,9,9,,Environmental; Auto-senses,100,"Very Rare","DW CRB","PRZYKŁAD – ZASTĄP" diff --git a/rpg/attack_test.js b/rpg/attack_test.js new file mode 100644 index 0000000..2f8e32d --- /dev/null +++ b/rpg/attack_test.js @@ -0,0 +1,60 @@ +// DH2 Attack Test v2 — presets: Aim/Range/Fire + Size/Light/Cover +const actor = canvas.tokens.controlled[0]?.actor ?? game.user.character; +if (!actor) return ui.notifications.warn("Zaznacz token albo przypisz postać."); +new Dialog({ + title: "🎯 Attack Test (WS/BS)", + content: ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`, + buttons: { + roll: { + label: "Roll", + callback: async (html) => { + const get = n => Number(html.find(`[name="${n}"]`).val()); + const base = get("base"); + const total = base + get("aim") + get("range") + get("stance") + get("size") + get("light") + get("cover") + get("mod"); + const r = await(new Roll("1d100")).roll({async:true}); + const ok = r.total <= total; + const margin = Math.abs(total - r.total); + const dox = ok ? 1 + Math.floor(margin/10) : Math.floor(margin/10); + const table = ` + + + +
Target${total}Roll${r.total}
Result${ok?'SUCCESS':'FAIL'} — ${ok?dox+' DoS':dox+' DoF'}
`; + r.toMessage({speaker: ChatMessage.getSpeaker({actor}), flavor: `🎯 Attack Test
${table}`}); + } + } + } +}).render(true); diff --git a/rpg/critical_hits.csv b/rpg/critical_hits.csv new file mode 100644 index 0000000..d30ef62 --- /dev/null +++ b/rpg/critical_hits.csv @@ -0,0 +1,6 @@ +Roll,Result +1,Energy/Head — wpis 1 +2,Energy/Head — wpis 2 +3,Energy/Head — wpis 3 +4,Energy/Head — wpis 4 +5,Energy/Head — wpis 5 diff --git a/rpg/focus_power.js b/rpg/focus_power.js new file mode 100644 index 0000000..8b8cddd --- /dev/null +++ b/rpg/focus_power.js @@ -0,0 +1,50 @@ +// 🧠 Focus Power (DH2) — WP test + Phenomena/Perils with mode presets +const actor = canvas.tokens.controlled[0]?.actor ?? game.user.character; +if (!actor) return ui.notifications.warn("Zaznacz token."); +new Dialog({ + title: "🧠 Focus Power", + content: ` +
+
+
+
+
+
+
+
`, + buttons: { + roll: { label: "Roll", callback: async html => { + const wp = Number(html.find('[name="wp"]').val()); + const pr = Number(html.find('[name="pr"]').val()); + const mode = html.find('[name="mode"]').val(); + const flat = Number(html.find('[name="flat"]').val()); + const thr = Number(html.find('[name="thr"]').val()); + let effPR = pr, ppmod = 0, ppAlways = false, note = ""; + if (mode==="fettered"){ effPR = Math.max(1, Math.floor(pr/2)); note="(Fettered: PR/2, brak Phenomena)"; } + if (mode==="push"){ effPR = pr+3; ppmod=10; ppAlways = true; note="(Push: +3 PR, Phenomena zawsze, +10)"; } + const target = wp + flat; + const roll = await (new Roll("1d100")).roll({async:true}); + const ok = roll.total <= target; + const dos = ok ? 1 + Math.floor((target - roll.total)/10) : Math.floor((roll.total - target)/10); + const doubles = (roll.total%11===0) || (roll.total===100); + const info = ` + + + +
Target${target}Roll${roll.total}
Result${ok?'SUCCESS':'FAIL'} — ${ok?dos+' DoS':dos+' DoF'} ${doubles?' — DOUBLES':''}
Eff. PR${effPR}Range hint${effPR*10} m (jeśli moc tak działa)
`; + roll.toMessage({speaker: ChatMessage.getSpeaker({actor}), flavor:`🧠 Focus Power ${note}
${info}`}); + const needPP = (mode==="unfettered" && doubles) || (mode==="push") ; + if (needPP){ + const tbl = game.tables.getName("Psychic Phenomena"); + if (tbl){ + const r = await (new Roll(`1d100 + ${ppmod}`)).roll({async:true}); + await tbl.draw({displayResults:true, roll:r}); + if (r.total >= thr){ + const per = game.tables.getName("Perils of the Warp"); + if (per) await per.draw({displayResults:true}); + } + } else ChatMessage.create({content:"Utwórz RollTable: Psychic Phenomena (+ Perils of the Warp)"}); + } + }} + } +}).render(true); diff --git a/rpg/hit_location.js b/rpg/hit_location.js new file mode 100644 index 0000000..7c2b2a6 --- /dev/null +++ b/rpg/hit_location.js @@ -0,0 +1,22 @@ +// 🎯 Hit Location (DH mapping by reversed roll) +new Dialog({ + title:"🎯 Hit Location", + content:`
+
+
`, + buttons:{ + go:{label:"Resolve", callback: html=>{ + const n = Math.max(1, Math.min(100, Number(html.find('[name="roll"]').val()))); + const rev = Number(String(n).padStart(2,"0").split("").reverse().join("")); + let loc = ""; + if (rev<=10) loc="Head"; + else if (rev<=20) loc="Right Arm"; + else if (rev<=30) loc="Left Arm"; + else if (rev<=70) loc="Body"; + else if (rev<=85) loc="Right Leg"; + else loc="Left Leg"; + ChatMessage.create({content:`🎯 Hit Location: roll ${n} → reversed ${rev} → ${loc}`}); + }} + } + }).render(true); + \ No newline at end of file diff --git a/rpg/hose_rules.js b/rpg/hose_rules.js new file mode 100644 index 0000000..eec1dcd --- /dev/null +++ b/rpg/hose_rules.js @@ -0,0 +1,6 @@ +const text=`House Rules — DH2 chassis
+• Unnatural mapowanie: dawne ×2 bonus → Unnatural (+X) tak, by SB/TB odzwierciedlały linię źródłową.
+• Pancerz Astartes: zachowaj AP; zużycie zasilania jako +1 Fatigue co X scen zamiast liczenia minut.
+• Aptitudes: archetypy z RT/DW/BC mają przypisane 2–3 Aptitudes DH2 dla kosztów XP.
+• Psy: testy i PR z DH2 dla wszystkich; GK posiada Aegis Discipline (1×/scena reroll Perils) + 1–2 signature powers z DW.
+• RT Acquisition → DH2 Influence z modyfikatorami kontekstowymi (teatr działań, mandat Inkwizycji, czas).`;ChatMessage.create({content:text}); diff --git a/rpg/inititative.js b/rpg/inititative.js new file mode 100644 index 0000000..f37d0c8 --- /dev/null +++ b/rpg/inititative.js @@ -0,0 +1,11 @@ +// 🚦 Initiative for selected tokens (1d10 + AG Bonus prompt) +if (!canvas.tokens.controlled.length) return ui.notifications.warn("Zaznacz co najmniej jeden token."); +const combat = game.combat ?? await Combat.implementation.create({}); +for (const t of canvas.tokens.controlled){ + if (!combat.combatants.some(c=>c.tokenId===t.id)) await combat.createEmbeddedDocuments("Combatant",[ {tokenId:t.id, sceneId: canvas.scene.id, hidden:false} ]); + const ag = Number(await Dialog.prompt({title:`AG Bonus for ${t.name}`, content:``, label:"OK"})); + const r = await (new Roll(`1d10 + ${ag}`)).roll({async:true}); + await combat.setInitiative(combat.combatants.find(c=>c.tokenId===t.id).id, r.total); + r.toMessage({flavor:`🚦 Initiative — ${t.name}: ${r.total}`}); +} +ui.notifications.info("Inicjatywy ustawione."); diff --git a/rpg/perils.csv b/rpg/perils.csv new file mode 100644 index 0000000..aa97592 --- /dev/null +++ b/rpg/perils.csv @@ -0,0 +1,21 @@ +Roll,Result +1-5,Perils 1–5 — WPISZ +6-10,Perils 6–10 — WPISZ +11-15,Perils 11–15 — WPISZ +16-20,Perils 16–20 — WPISZ +21-25,Perils 21–25 — WPISZ +26-30,Perils 26–30 — WPISZ +31-35,Perils 31–35 — WPISZ +36-40,Perils 36–40 — WPISZ +41-45,Perils 41–45 — WPISZ +46-50,Perils 46–50 — WPISZ +51-55,Perils 51–55 — WPISZ +56-60,Perils 56–60 — WPISZ +61-65,Perils 61–65 — WPISZ +66-70,Perils 66–70 — WPISZ +71-75,Perils 71–75 — WPISZ +76-80,Perils 76–80 — WPISZ +81-85,Perils 81–85 — WPISZ +86-90,Perils 86–90 — WPISZ +91-95,Perils 91–95 — WPISZ +96-100,Perils 96–100 — WPISZ diff --git a/rpg/powers.csv b/rpg/powers.csv new file mode 100644 index 0000000..c274d44 --- /dev/null +++ b/rpg/powers.csv @@ -0,0 +1,3 @@ +name,type,discipline,action,test,range,sustained,effect,source,notes +Smite,power,Biomancy,Half,"WP Challenging (+0)","PR*10m",No,"1d10+PR E; Tearing","DH2 CRB","PRZYKŁAD – ZASTĄP" +Foreboding,power,Divination,Reaction,"Per Difficult (-10)","Self",No,"Use as Evasion; DoS rules","DH2 CRB","PRZYKŁAD – ZASTĄP" diff --git a/rpg/pp.csv b/rpg/pp.csv new file mode 100644 index 0000000..a5bd82f --- /dev/null +++ b/rpg/pp.csv @@ -0,0 +1,21 @@ +Roll,Result +1-5,PP 1–5 — WPISZ +6-10,PP 6–10 — WPISZ +11-15,PP 11–15 — WPISZ +16-20,PP 16–20 — WPISZ +21-25,PP 21–25 — WPISZ +26-30,PP 26–30 — WPISZ +31-35,PP 31–35 — WPISZ +36-40,PP 36–40 — WPISZ +41-45,PP 41–45 — WPISZ +46-50,PP 46–50 — WPISZ +51-55,PP 51–55 — WPISZ +56-60,PP 56–60 — WPISZ +61-65,PP 61–65 — WPISZ +66-70,PP 66–70 — WPISZ +71-75,PP 71–75 — WPISZ +76-80,PP 76–80 — WPISZ +81-85,PP 81–85 — WPISZ +86-90,PP 86–90 — WPISZ +91-95,PP 91–95 — WPISZ +96-100,PP 96–100 — WPISZ diff --git a/rpg/quick_conditions.js b/rpg/quick_conditions.js new file mode 100644 index 0000000..90689f6 --- /dev/null +++ b/rpg/quick_conditions.js @@ -0,0 +1,28 @@ +// 🩹 Toggle conditions on selected tokens (Foundry v13) +const choices = [ + {id:"fatigued", label:"Fatigued"}, + {id:"stunned", label:"Stunned"}, + {id:"prone", label:"Prone"}, + {id:"frightened", label:"Frightened (Fear)"} + ]; + const opts = choices.map(c=>``).join("
"); + new Dialog({ + title:"🩹 Conditions", + content:`
${opts}
+
`, + buttons:{ + go:{label:"Apply",callback: html=>{ + const ids = Array.from(html.find('input[name="c"]:checked')).map(e=>e.value); + const mode = html.find('[name="mode"]').val(); + const getEf = id => CONFIG.statusEffects.find(e=>e.id===id) ?? {id}; + canvas.tokens.controlled.forEach(t=>{ + ids.forEach(id=>{ + if (mode==="toggle") t.toggleEffect(getEf(id)); + else if (mode==="on") t.actor?.effects?.some(e=>e.getFlag("core","statusId")===id) ? null : t.toggleEffect(getEf(id)); + else if (mode==="off") t.actor?.effects?.some(e=>e.getFlag("core","statusId")===id) ? t.toggleEffect(getEf(id)) : null; + }); + }); + }} + } + }).render(true); + \ No newline at end of file diff --git a/rpg/quick_damage.js b/rpg/quick_damage.js new file mode 100644 index 0000000..2020436 --- /dev/null +++ b/rpg/quick_damage.js @@ -0,0 +1,38 @@ +// 💥 Quick Damage — supports Tearing, Proven(X), Primitive(X), flat mod +new Dialog({ + title:"💥 Damage Roller", + content: ` +
+
+
+ + + + + +
+
`, + buttons:{ + go:{label:"Roll", callback: async html=>{ + const mod = Number(html.find('[name="mod"]').val()); + const tearing = html.find('[name="tear"]')[0].checked; + const proven = html.find('[name="prov"]')[0].checked ? Number(html.find('[name="provV"]').val()) : 0; + const primitive = html.find('[name="prim"]')[0].checked ? Number(html.find('[name="primV"]').val()) : 0; + // base die (d10) with tearing (best of 2) + const r1 = await (new Roll("1d10")).roll({async:true}); + const r2 = tearing ? await (new Roll("1d10")).roll({async:true}) : null; + let die = tearing ? Math.max(r1.total, r2.total) : r1.total; + // apply Proven/Primitive + if (proven>0) die = Math.max(die, proven); + if (primitive>0) die = Math.min(die, primitive); + const rf = (die===10); // potential Zealous Hatred trigger + const total = die + mod; + let flavor = `💥 Damage
Die: ${die}${tearing?` (Tearing ${r1.total}/${r2.total.total})`:''} + Mod ${mod} = ${total}`; + if (proven>0) flavor += `
Proven(${proven}) zastosowano`; + if (primitive>0) flavor += `
Primitive(${primitive}) zastosowano`; + if (rf) flavor += `
⚡ Natural 10 — rozważ Zealous Hatred.`; + ChatMessage.create({speaker: ChatMessage.getSpeaker(), content: flavor}); + }} + } + }).render(true); + \ No newline at end of file diff --git a/rpg/table_gen.js b/rpg/table_gen.js new file mode 100644 index 0000000..ec0f29c --- /dev/null +++ b/rpg/table_gen.js @@ -0,0 +1,26 @@ +// 📚 Create placeholder RollTables for Crits + Psychic Phenomena/Perils (with icons) +const icon = {Energy:"⚡", Impact:"🔨", Rending:"🗡️", Explosive:"💥"}; +const dmgTypes = ["Energy","Impact","Rending","Explosive"]; +const locs = ["Head","Body","Left Arm","Right Arm","Left Leg","Right Leg"]; +async function makeCrit(dtype, loc){ + const name = `Crit: ${dtype} - ${loc}`; + if (game.tables.getName(name)) return; + const results = []; + for (let i=1;i<=5;i++){ + results.push({type:0, text:`${icon[dtype]||""} ${dtype}/${loc} — wpis ${i} (uzupełnij z PDF)`, weight:1, range:[i,i]}); + } + await RollTable.implementation.create({name, formula:"1d5", replacement:true, displayRoll:false, results}); +} +async function makeWide(name, emoji){ + if (game.tables.getName(name)) return; + const results = []; + for (let i=0;i<20;i++){ + const lo=i*5+1, hi=i*5+5; + results.push({type:0, text:`${emoji} ${name} ${lo}-${hi} — wpis (uzupełnij z PDF)`, weight:1, range:[lo,hi]}); + } + await RollTable.implementation.create({name, formula:"1d100", replacement:true, displayRoll:false, results}); +} +for (const d of dmgTypes) for (const l of locs) await makeCrit(d,l); +await makeWide("Psychic Phenomena","🌀"); +await makeWide("Perils of the Warp","☠️"); +ui.notifications.info("Utworzono puste tabele: Crits (4×6) + Phenomena + Perils."); diff --git a/rpg/talents.csv b/rpg/talents.csv new file mode 100644 index 0000000..529b9bb --- /dev/null +++ b/rpg/talents.csv @@ -0,0 +1,3 @@ +name,type,tier,aptitudes,prereq,effect,source,notes +Ambidextrous,talent,1,"Agility; Offence","Ag 30","-10 to off-hand penalty","DH2 CRB","PRZYKŁAD – ZASTĄP" +Aegis Discipline (GK),talent,2,"Willpower; Defence","Psyker","Reroll Perils 1×scene","HOUSE","DODAJ WŁASNY OPIS" diff --git a/rpg/weapons.csv b/rpg/weapons.csv new file mode 100644 index 0000000..8c9eee5 --- /dev/null +++ b/rpg/weapons.csv @@ -0,0 +1,3 @@ +name,type,subtype,damage,pen,range,rof,qualities,weight,availability,source,notes +Lasgun M36,weapon,Basic,"1d10+3",0,100m,"S/3/–","Reliable",4,Common,"DH2 CRB p.142","PRZYKŁAD – ZASTĄP" +Astartes Bolter,weapon,Basic,"1d10+9",4,90m,"S/2/–","Tearing; Unreliable",9,Rare,"DW CRB","PRZYKŁAD – ZASTĄP" diff --git a/rpg/zealous_hatred.js b/rpg/zealous_hatred.js new file mode 100644 index 0000000..a945ca9 --- /dev/null +++ b/rpg/zealous_hatred.js @@ -0,0 +1,33 @@ +// ⚡ Zealous Hatred helper (DH2) — roll 1d5 crit OR add +1d5 dmg +new Dialog({ + title: "⚡ Zealous Hatred", + content: ` +
+
+
+
+
+
+
+
+
`, + buttons: { + go: { label: "Resolve", callback: async html => { + const pen = html.find('[name="penetrated"]').val(); + if (pen === "yes") { + const dtype = html.find('[name="dtype"]').val(); + const loc = html.find('[name="loc"]').val(); + const override = html.find('[name="tname"]').val()?.trim(); + const name = override || `Crit: ${dtype} - ${loc}`; + const r = await (new Roll("1d5")).roll({async:true}); + const table = game.tables.getName(name); + if (table) await table.draw({displayResults:true, roll:r}); + else r.toMessage({flavor:`⚡ Zealous Hatred: Critical ${r.total} — brak tabeli ${name} (utwórz lub zmień nazwę).`}); + } else { + const r = await (new Roll("1d5")).roll({async:true}); + r.toMessage({flavor:"⚡ Zealous Hatred: Dodaj do obrażeń +1d5 (atak nie przebił Soak)."}); + } + }} + } + }).render(true); + \ No newline at end of file