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:``,
+ 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