- Foundry v13/v14 compatibility (verified on 14.364) - Complete "Ringworld '84" retheme: CSS custom properties, warm space-black + bronze + ring-arc amber palette, uniform across sheets, dialogs, chat cards, hit-location picker, and impulse HUD - Fix HP damage evaporating on re-prepare: maximums always derived, current values source-authoritative, with a one-time world migration - Ammunition tracking: per-attack consumption, reload button, ammo badge; empty weapons block attacks - Energy weapons auto-link a carried power pack instead of failing - Special Success rolls damage dice twice; fumbles flag GM adjudication (both toggleable world settings) - Defender Dodge prompt before damage (special hits need special dodge) - Damage Mod and derived rolls always recompute; shown read-only - Creature sheet rebuilt to full parity: characteristics, derived rolls, body-schema hit grid with AP totals, weapons/armor/skills - Seeded "Ringworld GM Reference" journal (outcomes, impulses, attack workflow, wounds, 1984 quirks, quick start) - Shared characteristic-check dialog between explorer and creature Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3680 lines
142 KiB
JavaScript
3680 lines
142 KiB
JavaScript
async function Bt(e) {
|
|
const t = game == null ? void 0 : game.dice3d;
|
|
if (t != null && t.showForRoll)
|
|
try {
|
|
await t.showForRoll(e, game.user, !0, null, !1);
|
|
} catch {
|
|
}
|
|
}
|
|
async function ye(e) {
|
|
const t = await new Roll(e).evaluate();
|
|
return await Bt(t), t;
|
|
}
|
|
async function Ce(e) {
|
|
const t = await ye("1d100");
|
|
return Ut(t.total ?? 0, e);
|
|
}
|
|
function Ut(e, t) {
|
|
const a = Math.max(0, Math.floor(Number(t) || 0)), n = Math.max(1, Math.floor(a / 5)), o = a >= 50 ? 100 : 96, s = e <= a, i = s && e <= n, r = !s && e >= o;
|
|
let l = "failure";
|
|
return r ? l = "fumble" : i ? l = "special" : s && (l = "success"), {
|
|
roll: e,
|
|
target: a,
|
|
outcome: l,
|
|
isSuccess: s,
|
|
isSpecial: i,
|
|
isFumble: r
|
|
};
|
|
}
|
|
function he(e) {
|
|
switch (e) {
|
|
case "special":
|
|
return "Special Success";
|
|
case "success":
|
|
return "Success";
|
|
case "fumble":
|
|
return "Fumble";
|
|
default:
|
|
return "Failure";
|
|
}
|
|
}
|
|
const P = "ringworld", Re = [
|
|
{ slug: "rLeg", label: "Right Leg", min: 1, max: 3 },
|
|
{ slug: "lLeg", label: "Left Leg", min: 4, max: 6 },
|
|
{ slug: "abdomen", label: "Abdomen", min: 7, max: 10 },
|
|
{ slug: "chest", label: "Chest", min: 11, max: 15 },
|
|
{ slug: "rArm", label: "Right Arm", min: 16, max: 17 },
|
|
{ slug: "lArm", label: "Left Arm", min: 18, max: 19 },
|
|
{ slug: "head", label: "Head", min: 20, max: 20 }
|
|
], qt = ["rArm", "lArm", "rLeg", "lLeg"], Gt = ["rLeg", "lLeg"];
|
|
function Vt(e) {
|
|
return qt.includes(e);
|
|
}
|
|
function ut(e) {
|
|
return String(e ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
}
|
|
function ne(e, t) {
|
|
return e.map((a) => ({
|
|
...a,
|
|
hpFactor: (t == null ? void 0 : t[a.slug]) ?? (a.slug === "chest" ? 0.35 : a.slug === "rArm" || a.slug === "lArm" ? 0.25 : 0.3)
|
|
}));
|
|
}
|
|
const mt = {
|
|
id: "humanoid",
|
|
label: "Humanoid",
|
|
bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"],
|
|
entries: ne(Re)
|
|
}, Kt = {
|
|
id: "dolphin",
|
|
label: "Dolphin",
|
|
bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"],
|
|
entries: ne([
|
|
{ slug: "rLeg", label: "Right Fluke", min: 1, max: 3 },
|
|
{ slug: "lLeg", label: "Left Fluke", min: 4, max: 6 },
|
|
{ slug: "abdomen", label: "Lower Body", min: 7, max: 9 },
|
|
{ slug: "chest", label: "Main Body", min: 10, max: 15 },
|
|
{ slug: "rArm", label: "Right Fin", min: 16, max: 17 },
|
|
{ slug: "lArm", label: "Left Fin", min: 18, max: 19 },
|
|
{ slug: "head", label: "Head", min: 20, max: 20 }
|
|
], {
|
|
head: 0.3,
|
|
rArm: 0.2,
|
|
lArm: 0.2,
|
|
chest: 0.4,
|
|
abdomen: 0.35,
|
|
rLeg: 0.25,
|
|
lLeg: 0.25
|
|
})
|
|
}, _t = {
|
|
id: "puppeteer",
|
|
label: "Puppeteer",
|
|
bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"],
|
|
entries: ne([
|
|
{ slug: "rLeg", label: "Right Rear Leg", min: 1, max: 3 },
|
|
{ slug: "lLeg", label: "Left Rear Leg", min: 4, max: 6 },
|
|
{ slug: "rArm", label: "Right Foreleg", min: 7, max: 9 },
|
|
{ slug: "lArm", label: "Left Foreleg", min: 10, max: 12 },
|
|
{ slug: "chest", label: "Fore Body", min: 13, max: 15 },
|
|
{ slug: "abdomen", label: "Hind Body", min: 16, max: 18 },
|
|
{ slug: "head", label: "Twin Heads", min: 19, max: 20 }
|
|
], {
|
|
head: 0.32,
|
|
chest: 0.35,
|
|
abdomen: 0.35,
|
|
rArm: 0.28,
|
|
lArm: 0.28,
|
|
rLeg: 0.28,
|
|
lLeg: 0.28
|
|
})
|
|
}, jt = {
|
|
id: "trinoc",
|
|
label: "Trinoc",
|
|
bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"],
|
|
entries: ne([
|
|
{ slug: "rArm", label: "Right Wing/Arm", min: 1, max: 3 },
|
|
{ slug: "lArm", label: "Left Wing/Arm", min: 4, max: 6 },
|
|
{ slug: "chest", label: "Upper Torso", min: 7, max: 11 },
|
|
{ slug: "abdomen", label: "Lower Torso", min: 12, max: 14 },
|
|
{ slug: "rLeg", label: "Right Leg", min: 15, max: 17 },
|
|
{ slug: "lLeg", label: "Left Leg", min: 18, max: 19 },
|
|
{ slug: "head", label: "Head", min: 20, max: 20 }
|
|
], {
|
|
head: 0.3,
|
|
rArm: 0.27,
|
|
lArm: 0.27,
|
|
chest: 0.36,
|
|
abdomen: 0.3,
|
|
rLeg: 0.27,
|
|
lLeg: 0.27
|
|
})
|
|
}, Wt = {
|
|
id: "bandersnatchi",
|
|
label: "Bandersnatchi",
|
|
bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"],
|
|
entries: ne([
|
|
{ slug: "rLeg", label: "Right Rear Mass", min: 1, max: 2 },
|
|
{ slug: "lLeg", label: "Left Rear Mass", min: 3, max: 4 },
|
|
{ slug: "abdomen", label: "Lower Body Mass", min: 5, max: 11 },
|
|
{ slug: "chest", label: "Upper Body Mass", min: 12, max: 17 },
|
|
{ slug: "rArm", label: "Right Manipulator", min: 18, max: 18 },
|
|
{ slug: "lArm", label: "Left Manipulator", min: 19, max: 19 },
|
|
{ slug: "head", label: "Head Cluster", min: 20, max: 20 }
|
|
], {
|
|
head: 0.28,
|
|
chest: 0.42,
|
|
abdomen: 0.42,
|
|
rArm: 0.2,
|
|
lArm: 0.2,
|
|
rLeg: 0.24,
|
|
lLeg: 0.24
|
|
})
|
|
}, Yt = {
|
|
id: "kzinti",
|
|
label: "Kzinti",
|
|
bodyOrder: ["head", "lArm", "chest", "rArm", "abdomen", "lLeg", "rLeg"],
|
|
entries: ne([
|
|
{ slug: "rLeg", label: "Right Hind Leg", min: 1, max: 3 },
|
|
{ slug: "lLeg", label: "Left Hind Leg", min: 4, max: 6 },
|
|
{ slug: "abdomen", label: "Abdomen", min: 7, max: 10 },
|
|
{ slug: "chest", label: "Chest", min: 11, max: 15 },
|
|
{ slug: "rArm", label: "Right Foreleg", min: 16, max: 17 },
|
|
{ slug: "lArm", label: "Left Foreleg", min: 18, max: 19 },
|
|
{ slug: "head", label: "Head", min: 20, max: 20 }
|
|
])
|
|
}, dt = {
|
|
humanoid: mt,
|
|
dolphin: Kt,
|
|
puppeteer: _t,
|
|
trinoc: jt,
|
|
bandersnatchi: Wt,
|
|
kzinti: Yt
|
|
};
|
|
function gt() {
|
|
return Object.values(dt);
|
|
}
|
|
function zt(e) {
|
|
const t = ut(e);
|
|
return t ? t.includes("dolphin") ? "dolphin" : t.includes("puppeteer") ? "puppeteer" : t.includes("trinoc") ? "trinoc" : t.includes("bandersnatch") ? "bandersnatchi" : t.includes("kzinti") ? "kzinti" : "humanoid" : "humanoid";
|
|
}
|
|
function Ee(e) {
|
|
const t = ut(e);
|
|
return dt[t] ?? mt;
|
|
}
|
|
function oe(e) {
|
|
var n, o, s;
|
|
const t = String(((n = e == null ? void 0 : e.system) == null ? void 0 : n.hitLocationSchema) || "auto").trim().toLowerCase();
|
|
if (t && t !== "auto")
|
|
return Ee(t);
|
|
const a = ((s = (o = e == null ? void 0 : e.system) == null ? void 0 : o.profile) == null ? void 0 : s.species) ?? "";
|
|
return Ee(zt(a));
|
|
}
|
|
function Qt(e) {
|
|
return e ? oe(e).entries : Re;
|
|
}
|
|
function Xt(e, t) {
|
|
return t.find((n) => e >= n.min && e <= n.max) ?? t[t.length - 1];
|
|
}
|
|
async function pt(e) {
|
|
const a = (await ye("1d20")).total ?? 20, n = Qt(e);
|
|
return {
|
|
roll: a,
|
|
location: Xt(a, n)
|
|
};
|
|
}
|
|
async function Zt(e) {
|
|
const t = e ? oe(e) : Ee("humanoid"), a = t.entries, n = new Map(a.map((l) => [l.slug, l])), o = (l, c) => {
|
|
var u;
|
|
return ((u = n.get(l)) == null ? void 0 : u.label) ?? c;
|
|
}, s = `
|
|
<form class="ringworld-hit-picker schema-${t.id}">
|
|
<input type="hidden" name="location" value="random" />
|
|
<div class="rw-hit-picker-top">
|
|
<button type="button" class="rw-hit-picker-choice active" data-location="random">
|
|
Roll Random (1d20)
|
|
</button>
|
|
</div>
|
|
<div class="rw-hit-picker-body">
|
|
<button type="button" class="rw-hit-picker-choice rw-loc-head" data-location="head">${o("head", "Head")}</button>
|
|
<button type="button" class="rw-hit-picker-choice rw-loc-l-arm" data-location="lArm">${o("lArm", "Left Arm")}</button>
|
|
<button type="button" class="rw-hit-picker-choice rw-loc-chest" data-location="chest">${o("chest", "Chest")}</button>
|
|
<button type="button" class="rw-hit-picker-choice rw-loc-r-arm" data-location="rArm">${o("rArm", "Right Arm")}</button>
|
|
<button type="button" class="rw-hit-picker-choice rw-loc-abdomen" data-location="abdomen">${o("abdomen", "Abdomen")}</button>
|
|
<button type="button" class="rw-hit-picker-choice rw-loc-l-leg" data-location="lLeg">${o("lLeg", "Left Leg")}</button>
|
|
<button type="button" class="rw-hit-picker-choice rw-loc-r-leg" data-location="rLeg">${o("rLeg", "Right Leg")}</button>
|
|
</div>
|
|
</form>
|
|
`, i = await new Promise((l) => {
|
|
let c = !1;
|
|
const u = (g) => {
|
|
c || (c = !0, l(g));
|
|
};
|
|
new Dialog({
|
|
title: "Choose Hit Location",
|
|
content: s,
|
|
buttons: {
|
|
apply: {
|
|
label: "Apply",
|
|
callback: (g) => {
|
|
const f = String(g.find("input[name='location']").val() || "random");
|
|
u(f);
|
|
}
|
|
},
|
|
cancel: {
|
|
label: "Cancel",
|
|
callback: () => u(null)
|
|
}
|
|
},
|
|
default: "apply",
|
|
render: (g) => {
|
|
const f = g.find(".ringworld-hit-picker");
|
|
f.find(".rw-hit-picker-choice").on("click", (d) => {
|
|
d.preventDefault();
|
|
const y = d.currentTarget, b = String(y.dataset.location || "random");
|
|
f.find(".rw-hit-picker-choice").removeClass("active"), $(y).addClass("active"), f.find("input[name='location']").val(b);
|
|
});
|
|
},
|
|
close: () => u(null)
|
|
}, {
|
|
classes: ["ringworld-hit-location-dialog"],
|
|
width: 500,
|
|
resizable: !1
|
|
}).render(!0);
|
|
});
|
|
if (!i) return null;
|
|
if (i === "random") {
|
|
const l = await pt(e);
|
|
return {
|
|
slug: l.location.slug,
|
|
label: l.location.label,
|
|
rolled: l.roll
|
|
};
|
|
}
|
|
const r = a.find((l) => l.slug === i);
|
|
return r ? {
|
|
slug: r.slug,
|
|
label: r.label,
|
|
rolled: null
|
|
} : null;
|
|
}
|
|
const Jt = [
|
|
"Agility",
|
|
"Communication",
|
|
"Knowledge",
|
|
"Perception",
|
|
"Technical"
|
|
], ke = [
|
|
{ name: "Archaic Melee Weapon", category: "Agility", base: 5 },
|
|
{ name: "Archaic Ranged Weapons", category: "Agility", base: 3 },
|
|
{ name: "Athletics", category: "Agility", base: 15 },
|
|
{ name: "Hide", category: "Agility", base: 10 },
|
|
{ name: "Sneak", category: "Agility", base: 5 },
|
|
{ name: "Unarmed Combat", category: "Agility", base: 0 },
|
|
{ name: "V. Sword, F. Laser", category: "Agility", base: 15 },
|
|
{ name: "Bargain", category: "Communication", base: 10 },
|
|
{ name: "Debate", category: "Communication", base: 5 },
|
|
{ name: "Fast Talk", category: "Communication", base: 10 },
|
|
{ name: "Fine Arts", category: "Communication", base: 5 },
|
|
{ name: "Musicianship", category: "Communication", base: 5 },
|
|
{ name: "Orate", category: "Communication", base: 5 },
|
|
{ name: "Own Language", category: "Communication", base: 0, specialization: "INTx5 base" },
|
|
{ name: "Perform", category: "Communication", base: 5 },
|
|
{ name: "Psychology", category: "Communication", base: 0 },
|
|
{ name: "Anthropology", category: "Knowledge", base: 0 },
|
|
{ name: "Astronomy", category: "Knowledge", base: 0 },
|
|
{ name: "Biology", category: "Knowledge", base: 0 },
|
|
{ name: "Botany", category: "Knowledge", base: 0 },
|
|
{ name: "Chemistry", category: "Knowledge", base: 0 },
|
|
{ name: "Computers", category: "Knowledge", base: 0 },
|
|
{ name: "Emergency Treatment", category: "Knowledge", base: 1 },
|
|
{ name: "Engineering", category: "Knowledge", base: 0 },
|
|
{ name: "Farming", category: "Knowledge", base: 0 },
|
|
{ name: "History", category: "Knowledge", base: 5 },
|
|
{ name: "Law", category: "Knowledge", base: 0 },
|
|
{ name: "Mathematics", category: "Knowledge", base: 0 },
|
|
{ name: "Physics", category: "Knowledge", base: 0 },
|
|
{ name: "Planetology", category: "Knowledge", base: 0 },
|
|
{ name: "Second Languages", category: "Knowledge", base: 0 },
|
|
{ name: "Strategy", category: "Knowledge", base: 0 },
|
|
{ name: "Theology", category: "Knowledge", base: 0 },
|
|
{ name: "Zoology", category: "Knowledge", base: 0 },
|
|
{ name: "Handgun, Energy", category: "Perception", base: 5 },
|
|
{ name: "Handgun, Projectile", category: "Perception", base: 3 },
|
|
{ name: "Heavy Weapon, Energy", category: "Perception", base: 5 },
|
|
{ name: "Heavy Weapon, Projectile", category: "Perception", base: 3 },
|
|
{ name: "Listen", category: "Perception", base: 5 },
|
|
{ name: "Observe", category: "Perception", base: 5 },
|
|
{ name: "Scent", category: "Perception", base: 0 },
|
|
{ name: "Search", category: "Perception", base: 5 },
|
|
{ name: "Track", category: "Perception", base: 5 },
|
|
{ name: "Aquatic Vehicle", category: "Technical", base: 0 },
|
|
{ name: "Atmospheric Craft", category: "Technical", base: 0 },
|
|
{ name: "Ground Vehicle", category: "Technical", base: 0 },
|
|
{ name: "Hyperdrive", category: "Technical", base: 0 },
|
|
{ name: "Personal Flyer", category: "Technical", base: 15 },
|
|
{ name: "Reaction Drive", category: "Technical", base: 0 },
|
|
{ name: "Reactionless Drive", category: "Technical", base: 0 },
|
|
{ name: "Repair", category: "Technical", base: 0 },
|
|
{ name: "Ringworld", category: "Technical", base: 0 },
|
|
{ name: "Weapons System", category: "Technical", base: 0 }
|
|
];
|
|
function M(e) {
|
|
return String(e || "").toLowerCase().replace(/[^a-z0-9]+/g, "").trim();
|
|
}
|
|
function ea() {
|
|
return new Set(ke.map((e) => M(e.name)));
|
|
}
|
|
const X = "systems/ringworld/assets/icons/skill.svg";
|
|
function ta(e) {
|
|
return String(e).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
}
|
|
function we(e) {
|
|
return `systems/ringworld/assets/icons/skill/${ta(e)}.svg`;
|
|
}
|
|
function $e(e) {
|
|
const t = M(e);
|
|
return ke.find((a) => M(a.name) === t);
|
|
}
|
|
function Fe(e, t) {
|
|
const a = String(t || "").trim();
|
|
return a && a !== "icons/svg/item-bag.svg" ? a : $e(e) ? we(e) : X;
|
|
}
|
|
async function ft(e) {
|
|
var a;
|
|
const t = e.actor ? ChatMessage.getSpeaker({ actor: e.actor }) : ChatMessage.getSpeaker();
|
|
await ChatMessage.create({
|
|
user: (a = game.user) == null ? void 0 : a.id,
|
|
speaker: t,
|
|
content: e.content
|
|
});
|
|
}
|
|
async function ht(e) {
|
|
var n;
|
|
const t = {
|
|
...e,
|
|
actorImg: e.actorImg || ((n = e.actor) == null ? void 0 : n.img) || "icons/svg/mystery-man.svg",
|
|
skillImg: e.skillImg || X
|
|
}, a = await renderTemplate(`systems/${P}/templates/chat/skill-roll-card.hbs`, t);
|
|
await ft({ actor: e.actor, content: a });
|
|
}
|
|
async function Ne(e) {
|
|
var n;
|
|
const t = {
|
|
...e,
|
|
actorImg: e.actorImg || ((n = e.actor) == null ? void 0 : n.img) || "icons/svg/mystery-man.svg",
|
|
weaponImg: e.weaponImg || "icons/svg/sword.svg",
|
|
skillImg: e.skillImg || X,
|
|
hasTarget: e.hasTarget ?? !0
|
|
}, a = await renderTemplate(`systems/${P}/templates/chat/attack-card.hbs`, t);
|
|
await ft({ actor: e.actor, content: a });
|
|
}
|
|
function A(e, t = 0) {
|
|
const a = Number(e);
|
|
return Number.isFinite(a) ? a : t;
|
|
}
|
|
function aa() {
|
|
var t, a;
|
|
const e = Array.from(((t = game.user) == null ? void 0 : t.targets) ?? []);
|
|
return e.length ? ((a = e[0]) == null ? void 0 : a.actor) ?? null : null;
|
|
}
|
|
async function na(e) {
|
|
var o;
|
|
const t = await pt(e), a = e ? ChatMessage.getSpeaker({ actor: e }) : ChatMessage.getSpeaker(), n = `
|
|
<div class="ringworld-chat-card">
|
|
<h3>Hit Location</h3>
|
|
<p><strong>Roll:</strong> ${t.roll}</p>
|
|
<p><strong>Location:</strong> ${t.location.label}</p>
|
|
</div>
|
|
`;
|
|
await ChatMessage.create({
|
|
user: (o = game.user) == null ? void 0 : o.id,
|
|
speaker: a,
|
|
content: n
|
|
});
|
|
}
|
|
async function Be(e, t, a) {
|
|
const n = await Ce(a), o = e.items.find(
|
|
(s) => s.type === "skill" && s.name.toLowerCase() === String(t).toLowerCase()
|
|
);
|
|
await ht({
|
|
actor: e,
|
|
actorName: e.name,
|
|
actorImg: e.img,
|
|
skillName: t,
|
|
skillImg: o == null ? void 0 : o.img,
|
|
roll: n.roll,
|
|
target: n.target,
|
|
outcomeLabel: he(n.outcome),
|
|
isSuccess: n.isSuccess,
|
|
isSpecial: n.isSpecial,
|
|
isFumble: n.isFumble
|
|
});
|
|
}
|
|
function oa(e, t) {
|
|
const a = t.system.linkedSkillId, n = a ? e.items.get(a) : null;
|
|
if ((n == null ? void 0 : n.type) === "skill")
|
|
return { name: n.name, target: A(n.system.value), img: n.img };
|
|
const o = String(t.system.linkedSkill || "").trim();
|
|
if (o) {
|
|
const s = e.items.find(
|
|
(i) => i.type === "skill" && i.name.toLowerCase() === o.toLowerCase()
|
|
);
|
|
if (s)
|
|
return { name: s.name, target: A(s.system.value), img: s.img };
|
|
}
|
|
return {
|
|
name: o || "Unlinked Skill",
|
|
target: 0
|
|
};
|
|
}
|
|
function yt(e) {
|
|
return Math.max(0, Math.min(99, Math.floor(e)));
|
|
}
|
|
async function sa(e, t, a) {
|
|
var m;
|
|
const o = String(((m = t.system) == null ? void 0 : m.attackType) || "ranged") === "ranged", s = "full", i = foundry.utils.escapeHTML(String((e == null ? void 0 : e.name) || "Attacker")), r = foundry.utils.escapeHTML(String((t == null ? void 0 : t.name) || "Weapon")), l = foundry.utils.escapeHTML(String((e == null ? void 0 : e.img) || "icons/svg/mystery-man.svg")), c = foundry.utils.escapeHTML(String((t == null ? void 0 : t.img) || "icons/svg/sword.svg")), u = `
|
|
<form class="ringworld-attack-modifiers">
|
|
<header class="rw-attack-dialog-head">
|
|
<img class="rw-attack-dialog-avatar" src="${l}" alt="${i}" />
|
|
<img class="rw-attack-dialog-weapon" src="${c}" alt="${r}" />
|
|
<div class="rw-attack-dialog-title-block">
|
|
<h3>${r}</h3>
|
|
<p>${i} Attack Setup</p>
|
|
</div>
|
|
</header>
|
|
<div class="form-group">
|
|
<label>Base Skill</label>
|
|
<input type="number" name="baseSkill" value="${a}" disabled />
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Aim State</label>
|
|
<select name="aimMode" ${o ? "" : "disabled"}>
|
|
<option value="unaimed" >Unaimed (Quarter Skill)</option>
|
|
<option value="partial" >Partially Aimed (Half Skill)</option>
|
|
<option value="full" selected>Fully Aimed (Full Skill)</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Cover Modifier</label>
|
|
<input type="number" name="coverMod" value="0" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Movement Modifier</label>
|
|
<input type="number" name="movementMod" value="0" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Range Modifier</label>
|
|
<input type="number" name="rangeMod" value="0" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Situational Modifier</label>
|
|
<input type="number" name="situationalMod" value="0" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Custom Modifier Label</label>
|
|
<input type="text" name="customLabel" value="" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Custom Modifier Value</label>
|
|
<input type="number" name="customMod" value="0" />
|
|
</div>
|
|
<p class="notes">Ringworld rule support: unaimed ranged attacks use quarter skill, partial aim uses half skill, full aim uses full skill.</p>
|
|
</form>
|
|
`;
|
|
return new Promise((g) => {
|
|
let f = !1;
|
|
const d = (y) => {
|
|
f || (f = !0, g(y));
|
|
};
|
|
new Dialog({
|
|
title: `${t.name} - Attack Modifiers`,
|
|
content: u,
|
|
buttons: {
|
|
roll: {
|
|
label: "Roll Attack",
|
|
callback: (y) => {
|
|
var le, ce, ue, me, de, ge, pe;
|
|
const b = y[0], h = (J) => b == null ? void 0 : b.querySelector(`[name="${J}"]`), p = String(((le = h("aimMode")) == null ? void 0 : le.value) || s), w = p === "unaimed" || p === "partial" || p === "full" ? p : s, v = o ? { unaimed: 0.25, partial: 0.5, full: 1 }[w] : 1, x = A((ce = h("coverMod")) == null ? void 0 : ce.value, 0), I = A((ue = h("movementMod")) == null ? void 0 : ue.value, 0), T = A((me = h("rangeMod")) == null ? void 0 : me.value, 0), R = A((de = h("situationalMod")) == null ? void 0 : de.value, 0), D = A((ge = h("customMod")) == null ? void 0 : ge.value, 0), N = String(((pe = h("customLabel")) == null ? void 0 : pe.value) || "").trim(), C = x + I + T + R + D, U = a + C, re = Math.floor(U * v), Z = yt(re), H = [`Base ${a}`];
|
|
if (x && H.push(`Cover ${x >= 0 ? "+" : ""}${x}`), I && H.push(`Movement ${I >= 0 ? "+" : ""}${I}`), T && H.push(`Range ${T >= 0 ? "+" : ""}${T}`), R && H.push(`Situational ${R >= 0 ? "+" : ""}${R}`), D && H.push(`${N || "Custom"} ${D >= 0 ? "+" : ""}${D}`), H.push(`Pre-Aim ${U}`), o) {
|
|
const J = w === "unaimed" ? "Quarter" : w === "partial" ? "Half" : "Full";
|
|
H.push(`Aim ${J} x${v}`);
|
|
}
|
|
H.push(`Final ${Z}`), d({
|
|
finalTarget: Z,
|
|
summary: H.join(" | "),
|
|
aimMode: w,
|
|
aimMultiplier: v,
|
|
flatModifierTotal: C
|
|
});
|
|
}
|
|
},
|
|
cancel: {
|
|
label: "Cancel",
|
|
callback: () => d(null)
|
|
}
|
|
},
|
|
default: "roll",
|
|
close: () => d(null)
|
|
}, {
|
|
classes: ["ringworld-attack-dialog"],
|
|
width: 510,
|
|
resizable: !1
|
|
}).render(!0);
|
|
});
|
|
}
|
|
async function ia(e, t) {
|
|
var i, r, l;
|
|
const a = Math.max(0, A(t.system.ammoUsedPerAttack));
|
|
if (a <= 0)
|
|
return { used: !1, remaining: 0, cost: 0, insufficient: !1 };
|
|
const n = A((i = t.system.ammo) == null ? void 0 : i.value);
|
|
if (A((r = t.system.ammo) == null ? void 0 : r.max) <= 0 && n <= 0)
|
|
return { used: !1, remaining: 0, cost: a, insufficient: !1 };
|
|
if (n < a)
|
|
return (l = ui.notifications) == null || l.warn(`${t.name} is out of ammunition. Reload before attacking.`), { used: !0, remaining: n, cost: a, insufficient: !0 };
|
|
const s = n - a;
|
|
return await e.updateEmbeddedDocuments("Item", [{ _id: t.id, "system.ammo.value": s }]), { used: !0, remaining: s, cost: a, insufficient: !1 };
|
|
}
|
|
async function ra(e, t) {
|
|
var n, o, s;
|
|
const a = A((n = t.system.ammo) == null ? void 0 : n.max);
|
|
if (a <= 0) {
|
|
(o = ui.notifications) == null || o.info(`${t.name} has no ammunition capacity configured (set Ammo Max on the weapon).`);
|
|
return;
|
|
}
|
|
await e.updateEmbeddedDocuments("Item", [{ _id: t.id, "system.ammo.value": a }]), (s = ui.notifications) == null || s.info(`${t.name} reloaded (${a}/${a}).`);
|
|
}
|
|
async function la(e, t) {
|
|
var i, r, l, c, u, m, g;
|
|
const a = Math.max(0, A(t.system.energyUsedPerAttack));
|
|
if (a <= 0)
|
|
return { sourceLabel: "None", remaining: 0, cost: 0, insufficient: !1 };
|
|
if (String(t.system.powerSource || "actor") === "powerPack") {
|
|
const f = String(t.system.powerPackId || "");
|
|
let d = f ? e.items.get(f) : null;
|
|
if ((!d || d.type !== "gear" || !d.system.isPowerPack) && (d = e.items.find(
|
|
(h) => {
|
|
var p;
|
|
return h.type === "gear" && h.system.isPowerPack && !h.system.stowed && A((p = h.system.energy) == null ? void 0 : p.value) >= a;
|
|
}
|
|
) ?? null, d && (await e.updateEmbeddedDocuments("Item", [{ _id: t.id, "system.powerPackId": d.id }]), (i = ui.notifications) == null || i.info(`${t.name} linked to ${d.name}.`))), !d || d.type !== "gear" || !d.system.isPowerPack)
|
|
return (r = ui.notifications) == null || r.warn("Weapon is configured for a power pack but no valid power pack is carried."), { sourceLabel: "Power Pack", remaining: 0, cost: a, insufficient: !0 };
|
|
const y = A((l = d.system.energy) == null ? void 0 : l.value);
|
|
if (y < a)
|
|
return (c = ui.notifications) == null || c.warn(`${d.name} does not have enough energy.`), { sourceLabel: d.name, remaining: y, cost: a, insufficient: !0 };
|
|
const b = y - a;
|
|
return await e.updateEmbeddedDocuments("Item", [{ _id: d.id, "system.energy.value": b }]), { sourceLabel: d.name, remaining: b, cost: a, insufficient: !1 };
|
|
}
|
|
const o = A((m = (u = e.system.resources) == null ? void 0 : u.energy) == null ? void 0 : m.value);
|
|
if (o < a)
|
|
return (g = ui.notifications) == null || g.warn(`${e.name} does not have enough actor energy.`), { sourceLabel: "Actor Energy", remaining: o, cost: a, insufficient: !0 };
|
|
const s = o - a;
|
|
return await e.update({ "system.resources.energy.value": s }), { sourceLabel: "Actor Energy", remaining: s, cost: a, insufficient: !1 };
|
|
}
|
|
function ca(e, t) {
|
|
var n, o;
|
|
if (e.type !== "armor" || !e.system.equipped || e.system.stowed || !((n = e.system.coverage) != null && n[t])) return 0;
|
|
const a = A((o = e.system.perLocation) == null ? void 0 : o[t]);
|
|
return a > 0 ? a : A(e.system.value);
|
|
}
|
|
function ua(e, t) {
|
|
var o, s;
|
|
const a = A((s = (o = e.system.hitLocations) == null ? void 0 : o[t]) == null ? void 0 : s.armor), n = e.items.filter((i) => i.type === "armor").reduce((i, r) => i + ca(r, t), 0);
|
|
return a + n;
|
|
}
|
|
function ma(e, t, a, n, o) {
|
|
const s = foundry.utils.deepClone(e.system.conditions ?? {});
|
|
return s.unusableLimbs = s.unusableLimbs ?? { rArm: !1, lArm: !1, rLeg: !1, lLeg: !1 }, a <= 0 && ((t === "head" || t === "chest") && (s.unconscious = !0, s.prone = !0), t === "abdomen" && (s.prone = !0, s.unusableLimbs.rLeg = !0, s.unusableLimbs.lLeg = !0), (t === "rArm" || t === "lArm") && (s.unusableLimbs[t] = !0), Gt.includes(t) && (s.unusableLimbs[t] = !0, s.prone = !0)), o <= 0 && (s.dying = !0), (t === "head" || t === "chest" || t === "abdomen") && a <= -n && (s.dying = !0), t === "head" && a <= -n && (s.dead = !0, s.unconscious = !0), s;
|
|
}
|
|
async function da(e, t) {
|
|
const a = e.toggleStatusEffect;
|
|
if (typeof a == "function")
|
|
try {
|
|
await a.call(e, "prone", { active: !!t.prone }), await a.call(e, "unconscious", { active: !!t.unconscious }), await a.call(e, "dead", { active: !!t.dead });
|
|
} catch {
|
|
}
|
|
}
|
|
async function ga(e, t, a) {
|
|
var d, y, b, h;
|
|
const n = (d = e.system.hitLocations) == null ? void 0 : d[t], o = A((y = n == null ? void 0 : n.hp) == null ? void 0 : y.value), s = Math.max(1, A((b = n == null ? void 0 : n.hp) == null ? void 0 : b.max, 1)), i = ua(e, t), r = Math.max(0, a - i), l = o - r, c = Vt(t) ? Math.max(l, -s) : l, m = A((h = e.system.generalHp) == null ? void 0 : h.value) - r, g = ma(e, t, c, s, m), f = {
|
|
[`system.hitLocations.${t}.hp.value`]: c,
|
|
"system.generalHp.value": m,
|
|
"system.conditions": g
|
|
};
|
|
return await e.update(f), await da(e, g), {
|
|
armor: i,
|
|
finalDamage: r,
|
|
locationValue: c,
|
|
generalHpValue: m
|
|
};
|
|
}
|
|
async function wt(e, t, a) {
|
|
var c, u;
|
|
if (!(/* @__PURE__ */ new Set(["str", "mas", "con", "int", "pow", "dex", "app", "edu", "age"])).has(t)) return;
|
|
const o = Math.max(0, A((u = (c = e.system) == null ? void 0 : c.characteristics) == null ? void 0 : u[t], 0)), s = `
|
|
<form class="rw-stat-roll-form">
|
|
<div class="form-group">
|
|
<label>Characteristic</label>
|
|
<input type="text" value="${foundry.utils.escapeHTML(a)}" disabled />
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Base Value</label>
|
|
<input type="number" value="${o}" disabled />
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Multiplier</label>
|
|
<select name="multiplier">
|
|
<option value="1">x1</option>
|
|
<option value="2">x2</option>
|
|
<option value="3">x3</option>
|
|
<option value="4">x4</option>
|
|
<option value="5" selected>x5</option>
|
|
<option value="6">x6</option>
|
|
<option value="7">x7</option>
|
|
</select>
|
|
</div>
|
|
<p class="hint">Roll d100 against Characteristic x Multiplier.</p>
|
|
</form>
|
|
`, i = await new Promise((m) => {
|
|
let g = !1;
|
|
const f = (d) => {
|
|
g || (g = !0, m(d));
|
|
};
|
|
new Dialog({
|
|
title: `${a} Check`,
|
|
content: s,
|
|
buttons: {
|
|
roll: {
|
|
label: "Roll",
|
|
callback: (d) => {
|
|
var h, p;
|
|
const y = Number(((p = (h = d[0]) == null ? void 0 : h.querySelector("select[name='multiplier']")) == null ? void 0 : p.value) || 1), b = Math.max(1, Math.min(7, Math.floor(y || 1)));
|
|
f(b);
|
|
}
|
|
},
|
|
cancel: {
|
|
label: "Cancel",
|
|
callback: () => f(null)
|
|
}
|
|
},
|
|
default: "roll",
|
|
close: () => f(null)
|
|
}, {
|
|
classes: ["ringworld-attack-dialog"]
|
|
}).render(!0);
|
|
});
|
|
if (!i) return;
|
|
const r = yt(o * i), l = await Ce(r);
|
|
await ht({
|
|
actor: e,
|
|
actorName: e.name,
|
|
actorImg: e.img,
|
|
skillName: `${a} x${i}`,
|
|
roll: l.roll,
|
|
target: l.target,
|
|
outcomeLabel: he(l.outcome),
|
|
isSuccess: l.isSuccess,
|
|
isSpecial: l.isSpecial,
|
|
isFumble: l.isFumble
|
|
});
|
|
}
|
|
function Je(e, t) {
|
|
try {
|
|
return !!game.settings.get(P, e);
|
|
} catch {
|
|
return t;
|
|
}
|
|
}
|
|
async function pa(e, t) {
|
|
var r, l;
|
|
const a = A((l = (r = e.system) == null ? void 0 : r.derivedRolls) == null ? void 0 : l.dodge, 0), n = foundry.utils.escapeHTML(String(e.name || "Target"));
|
|
if (!await new Promise((c) => {
|
|
let u = !1;
|
|
const m = (g) => {
|
|
u || (u = !0, c(g));
|
|
};
|
|
new Dialog({
|
|
title: `${e.name} - Dodge Attempt`,
|
|
content: `
|
|
<div class="rw-stat-roll-form">
|
|
<p><strong>${n}</strong> has been hit${t ? " by a special success" : ""}.</p>
|
|
<p class="hint">Dodge ${a}%. A successful dodge evades a normal hit; only a special dodge evades a special hit.</p>
|
|
</div>
|
|
`,
|
|
buttons: {
|
|
dodge: { label: `Dodge (${a}%)`, callback: () => m(!0) },
|
|
none: { label: "No Dodge", callback: () => m(!1) }
|
|
},
|
|
default: "dodge",
|
|
close: () => m(!1)
|
|
}, {
|
|
classes: ["ringworld-attack-dialog"],
|
|
width: 420
|
|
}).render(!0);
|
|
})) return null;
|
|
const s = await Ce(a), i = s.isSuccess && (!t || s.isSpecial);
|
|
return {
|
|
attempted: !0,
|
|
roll: s.roll,
|
|
target: s.target,
|
|
success: s.isSuccess,
|
|
special: s.isSpecial,
|
|
negated: i
|
|
};
|
|
}
|
|
async function bt(e, t) {
|
|
var p, w;
|
|
const a = aa(), n = !!a, o = (a == null ? void 0 : a.name) ?? "No Target", s = oa(e, t), i = await sa(e, t, s.target);
|
|
if (!i) return;
|
|
const r = await ia(e, t);
|
|
if (r.insufficient) return;
|
|
const l = await la(e, t);
|
|
if (l.insufficient) return;
|
|
const c = await Ce(i.finalTarget);
|
|
if (!c.isSuccess) {
|
|
const v = a ? A((p = a.system.generalHp) == null ? void 0 : p.value, 0) : 0;
|
|
await Ne({
|
|
actor: e,
|
|
actorName: e.name,
|
|
actorImg: e.img,
|
|
targetName: o,
|
|
hasTarget: n,
|
|
weaponName: t.name,
|
|
weaponImg: t.img,
|
|
skillName: s.name,
|
|
skillImg: s.img,
|
|
attackRoll: c.roll,
|
|
attackTarget: c.target,
|
|
attackOutcomeLabel: he(c.outcome),
|
|
isSuccess: !1,
|
|
isSpecial: c.isSpecial,
|
|
isFumble: c.isFumble,
|
|
hitLocationLabel: "-",
|
|
hitLocationRoll: null,
|
|
rawDamage: 0,
|
|
armor: 0,
|
|
finalDamage: 0,
|
|
remainingLocationHp: 0,
|
|
remainingGeneralHp: v,
|
|
energySourceLabel: l.sourceLabel,
|
|
energyCost: l.cost,
|
|
energyRemaining: l.remaining,
|
|
ammoUsed: r.used,
|
|
ammoCost: r.cost,
|
|
ammoRemaining: r.remaining,
|
|
dodge: null,
|
|
notes: c.isFumble ? `Fumble! The GM should adjudicate a mishap (dropped or damaged weapon, hit an ally, drained power). ${i.summary}` : `Attack failed before damage was rolled. ${i.summary}`
|
|
});
|
|
return;
|
|
}
|
|
let u = null;
|
|
if (a && Je("promptDefenderDodge", !0) && (u = await pa(a, c.isSpecial)), u != null && u.negated) {
|
|
await Ne({
|
|
actor: e,
|
|
actorName: e.name,
|
|
actorImg: e.img,
|
|
targetName: o,
|
|
hasTarget: n,
|
|
weaponName: t.name,
|
|
weaponImg: t.img,
|
|
skillName: s.name,
|
|
skillImg: s.img,
|
|
attackRoll: c.roll,
|
|
attackTarget: c.target,
|
|
attackOutcomeLabel: he(c.outcome),
|
|
isSuccess: !1,
|
|
isSpecial: c.isSpecial,
|
|
isFumble: !1,
|
|
hitLocationLabel: "-",
|
|
hitLocationRoll: null,
|
|
rawDamage: 0,
|
|
armor: 0,
|
|
finalDamage: 0,
|
|
remainingLocationHp: 0,
|
|
remainingGeneralHp: A((w = a.system.generalHp) == null ? void 0 : w.value, 0),
|
|
energySourceLabel: l.sourceLabel,
|
|
energyCost: l.cost,
|
|
energyRemaining: l.remaining,
|
|
ammoUsed: r.used,
|
|
ammoCost: r.cost,
|
|
ammoRemaining: r.remaining,
|
|
dodge: u,
|
|
notes: `${o} dodged the attack. ${i.summary}`
|
|
});
|
|
return;
|
|
}
|
|
const m = await Zt(a ?? e);
|
|
if (!m) return;
|
|
const g = String(t.system.damageFormula || "0").trim() || "0", f = Je("automateSpecialDamage", !0), d = c.isSpecial && f, y = await ye(g);
|
|
let b = Math.max(0, A(y.total));
|
|
if (d) {
|
|
const v = await ye(g);
|
|
b += Math.max(0, A(v.total));
|
|
}
|
|
const h = a ? await ga(a, m.slug, b) : {
|
|
armor: 0,
|
|
finalDamage: b,
|
|
locationValue: 0,
|
|
generalHpValue: 0
|
|
};
|
|
await Ne({
|
|
actor: e,
|
|
actorName: e.name,
|
|
actorImg: e.img,
|
|
targetName: o,
|
|
hasTarget: n,
|
|
weaponName: t.name,
|
|
weaponImg: t.img,
|
|
skillName: s.name,
|
|
skillImg: s.img,
|
|
attackRoll: c.roll,
|
|
attackTarget: c.target,
|
|
attackOutcomeLabel: he(c.outcome),
|
|
isSuccess: c.isSuccess,
|
|
isSpecial: c.isSpecial,
|
|
isFumble: c.isFumble,
|
|
hitLocationLabel: m.label,
|
|
hitLocationRoll: m.rolled,
|
|
rawDamage: b,
|
|
armor: h.armor,
|
|
finalDamage: h.finalDamage,
|
|
remainingLocationHp: h.locationValue,
|
|
remainingGeneralHp: h.generalHpValue,
|
|
energySourceLabel: l.sourceLabel,
|
|
energyCost: l.cost,
|
|
energyRemaining: l.remaining,
|
|
ammoUsed: r.used,
|
|
ammoCost: r.cost,
|
|
ammoRemaining: r.remaining,
|
|
dodge: u,
|
|
notes: [
|
|
d ? "Special success: damage dice rolled twice." : "",
|
|
a ? "Location and general HP were both reduced by final damage, per Ringworld rules." : "No target selected: roll resolved with no damage application.",
|
|
i.summary
|
|
].filter(Boolean).join(" ")
|
|
});
|
|
}
|
|
function fa(e) {
|
|
var r, l, c, u, m, g, f, d, y;
|
|
if (e.type !== "explorer" && e.type !== "creature") return;
|
|
const t = A((l = (r = e.system) == null ? void 0 : r.characteristics) == null ? void 0 : l.con, 10), a = A((u = (c = e.system) == null ? void 0 : c.characteristics) == null ? void 0 : u.mas, 10), n = Math.max(1, Math.floor(t + a)), o = (e._source ?? ((m = e.toObject) == null ? void 0 : m.call(e, !1)) ?? {}).system ?? {}, s = (g = o.generalHp) == null ? void 0 : g.value;
|
|
e.system.generalHp = e.system.generalHp ?? {}, e.system.generalHp.max = n, e.system.generalHp.value = s == null ? n : Math.min(A(s, n), n), e.system.hitLocations = e.system.hitLocations ?? {}, e.system.hitLocationSchema = e.system.hitLocationSchema ?? "auto";
|
|
const i = oe(e);
|
|
for (const b of i.entries) {
|
|
const h = b.slug, p = e.system.hitLocations[h] ?? {
|
|
label: h,
|
|
hp: {},
|
|
armor: 0
|
|
};
|
|
p.label = b.label;
|
|
const w = Math.max(0.05, A(b.hpFactor, 0.3)), v = Math.max(1, Math.ceil(n * w)), x = (y = (d = (f = o.hitLocations) == null ? void 0 : f[h]) == null ? void 0 : d.hp) == null ? void 0 : y.value;
|
|
p.hp = p.hp ?? {}, p.hp.max = v, p.hp.value = x == null ? v : Math.min(A(x, v), v), e.system.hitLocations[h] = p;
|
|
}
|
|
}
|
|
const et = "Ringworld GM Reference";
|
|
function ha() {
|
|
return [
|
|
{
|
|
name: "Percentile Outcomes",
|
|
content: `
|
|
<p>Every check is a d100 roll against a target percentage. The system classifies results automatically:</p>
|
|
<ul>
|
|
<li><strong>Success</strong> — roll ≤ target.</li>
|
|
<li><strong>Special Success</strong> — roll ≤ one fifth of the target (minimum 1). Attacks roll their damage dice twice when <em>Automate Special Success Damage</em> is enabled.</li>
|
|
<li><strong>Failure</strong> — roll > target.</li>
|
|
<li><strong>Fumble</strong> — roll of 96–100 when the target is below 50, or a natural 100 otherwise. The GM adjudicates a mishap: dropped or damaged weapon, hit an ally, drained power pack.</li>
|
|
</ul>
|
|
<p>Characteristic checks use Characteristic × Multiplier (usually ×5): click any characteristic button on a sheet to pick the multiplier.</p>
|
|
`
|
|
},
|
|
{
|
|
name: "Combat Sequence (Impulses)",
|
|
content: `
|
|
<p>Ringworld does not use simple initiative. Combat rounds are split into <strong>impulses</strong> and each combatant acts on a rhythm set by their <strong>Action Ranking (AR)</strong>, which is derived from DEX (lower is faster) and worsened by encumbrance.</p>
|
|
<ol>
|
|
<li>Add combatants to the Combat Tracker as normal. The Ringworld impulse panel replaces the core tracker list.</li>
|
|
<li>Each combatant schedules an action: a <strong>major action</strong> (attack, full move, complex task) completes after AR impulses; a <strong>minor action</strong> (drop prone, shout, draw) completes after 1 impulse.</li>
|
|
<li>Advance the clock with <em>Next Completion</em> or <em>+1</em>. When a combatant's action completes, resolve it, then let them schedule the next one.</li>
|
|
</ol>
|
|
<p>The floating impulse HUD can be dragged anywhere on screen and mirrors the tracker for quick play.</p>
|
|
`
|
|
},
|
|
{
|
|
name: "Attack Workflow",
|
|
content: `
|
|
<p>Target a token, then press the weapon's <strong>Use</strong> button on the attacker's sheet. The system walks through the 1984 rules in order:</p>
|
|
<ol>
|
|
<li><strong>Aim state</strong> (ranged only): unaimed fires at quarter skill, partially aimed at half, fully aimed at full skill. Cover, movement, range and situational modifiers are applied before the aim multiplier.</li>
|
|
<li><strong>Ammunition and energy</strong> are spent. Projectile weapons with an ammo capacity are blocked when empty — use the <em>Reload</em> button on the weapon row. Energy weapons draw from the actor pool or a linked power pack.</li>
|
|
<li><strong>Dodge</strong>: on a hit, the defender is offered a Dodge roll (DEX ×5). A successful dodge evades a normal hit; only a <em>special</em> dodge evades a <em>special</em> hit. Disable with the <em>Prompt Defender Dodge</em> setting.</li>
|
|
<li><strong>Hit location</strong> is rolled on a d20 (or picked manually). Species use their own body schema — puppeteers, dolphins, trinocs, bandersnatchi and kzinti map differently.</li>
|
|
<li><strong>Damage</strong> is rolled, stacked armor at that location is subtracted, and the result reduces <em>both</em> the location HP and the general HP.</li>
|
|
</ol>
|
|
`
|
|
},
|
|
{
|
|
name: "Wounds & Conditions",
|
|
content: `
|
|
<p>When a hit location reaches 0 HP or less, conditions are applied automatically:</p>
|
|
<ul>
|
|
<li><strong>Head or Chest at 0</strong> — unconscious and prone.</li>
|
|
<li><strong>Abdomen at 0</strong> — prone, both legs unusable.</li>
|
|
<li><strong>Arm at 0</strong> — that arm is unusable.</li>
|
|
<li><strong>Leg at 0</strong> — that leg is unusable, and the character falls prone.</li>
|
|
<li><strong>General HP at 0</strong> — dying.</li>
|
|
<li><strong>Head, chest or abdomen at negative full value</strong> — dying; a head at negative full value is death outright.</li>
|
|
</ul>
|
|
<p>Limbs cannot go below their negative maximum. Use <em>Reset Conditions</em> on a sheet after treatment or recovery. Prone, unconscious and dead sync to token status icons.</p>
|
|
`
|
|
},
|
|
{
|
|
name: "Running the 1984 Rules",
|
|
content: `
|
|
<p>Quirks of the original boxed set that the system encodes for you:</p>
|
|
<ul>
|
|
<li><strong>General HP = CON + MAS.</strong> MAS (mass) replaces the familiar SIZ. Location HP scale from general HP by body schema.</li>
|
|
<li><strong>Damage modifier</strong> comes from STR + MAS and is recomputed automatically when characteristics change.</li>
|
|
<li><strong>Skill pools:</strong> Education points = (EDU − 10) × 20, Pursuit points = (Age − EDU − 6) × 20, Special points = INT × 10. The character builder on the Skills tab tracks all three.</li>
|
|
<li><strong>Skill category caps:</strong> each category is capped by a characteristic pair (e.g. Agility by STR + DEX). The builder enforces the caps.</li>
|
|
<li><strong>Gravity matters:</strong> carried weight is halved in light gravity and doubled in heavy gravity before comparing to the STR limit; over-limit explorers slow to Move 2 and suffer an Action Ranking penalty.</li>
|
|
<li><strong>Energy economy:</strong> most Ringworld tech runs on power packs (R20/R50/R100). Weapons can draw from a linked pack — keep spares, and remember fumbles can drain them.</li>
|
|
</ul>
|
|
`
|
|
},
|
|
{
|
|
name: "GM Quick Start",
|
|
content: `
|
|
<p>To get a table playing quickly:</p>
|
|
<ol>
|
|
<li><strong>Seed the catalog</strong> (Items sidebar button) — species, 55 base skills, pursuits, weapons, armor and gear from the boxed set appear in organised folders. This runs automatically for new worlds.</li>
|
|
<li><strong>Create Random Explorer</strong> (Actors sidebar button) — rolls characteristics by species and gravity, adds base skills and a starter kit.</li>
|
|
<li>For bespoke characters, unlock the sheet tools (padlock, top right of the sheet) and use the <strong>Character Builder</strong> on the Skills tab: pick pursuits, then allocate the three point pools.</li>
|
|
<li>Creatures get the same automation — hit locations, dodge, armor stacking — via the creature sheet, with a body schema selector for non-humanoids.</li>
|
|
<li>Check <strong>System Settings</strong> for the automation toggles (special success damage, defender dodge prompts).</li>
|
|
</ol>
|
|
`
|
|
}
|
|
];
|
|
}
|
|
async function ya() {
|
|
var n, o;
|
|
if (!((n = game.user) != null && n.isGM)) return !1;
|
|
const e = game.journal;
|
|
if ((e == null ? void 0 : e.find((s) => {
|
|
var i;
|
|
return ((i = s.getFlag) == null ? void 0 : i.call(s, P, "gmReference")) === !0;
|
|
})) ?? ((o = e == null ? void 0 : e.getName) == null ? void 0 : o.call(e, et))) return !1;
|
|
const a = ha().map((s, i) => ({
|
|
name: s.name,
|
|
type: "text",
|
|
sort: (i + 1) * 1e5,
|
|
text: {
|
|
format: 1,
|
|
content: s.content.trim()
|
|
}
|
|
}));
|
|
return await getDocumentClass("JournalEntry").create({
|
|
name: et,
|
|
pages: a,
|
|
flags: {
|
|
[P]: {
|
|
gmReference: !0
|
|
}
|
|
},
|
|
ownership: { default: 0 }
|
|
}), !0;
|
|
}
|
|
const He = 10, j = {
|
|
speciesDefault: "systems/ringworld/assets/icons/species.svg",
|
|
skillDefault: "systems/ringworld/assets/icons/skill.svg",
|
|
pursuitDefault: "systems/ringworld/assets/icons/pursuit.svg",
|
|
weaponMelee: "systems/ringworld/assets/icons/weapon-melee.svg",
|
|
weaponRanged: "systems/ringworld/assets/icons/weapon-ranged.svg",
|
|
armor: "systems/ringworld/assets/icons/armor.svg",
|
|
gearDefault: "systems/ringworld/assets/icons/gear.svg",
|
|
powerPackDefault: "systems/ringworld/assets/icons/power-pack.svg"
|
|
};
|
|
function ve(e) {
|
|
return String(e).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
}
|
|
function wa(e) {
|
|
return `systems/ringworld/assets/icons/species/${ve(e)}.svg`;
|
|
}
|
|
function ba(e) {
|
|
return `systems/ringworld/assets/icons/weapon/${ve(e)}.svg`;
|
|
}
|
|
function ka(e) {
|
|
return `systems/ringworld/assets/icons/armor/${ve(e)}.svg`;
|
|
}
|
|
function va(e) {
|
|
return `systems/ringworld/assets/icons/gear/${ve(e)}.svg`;
|
|
}
|
|
function kt(e) {
|
|
return `systems/ringworld/assets/icons/pursuit/${ve(e)}.svg`;
|
|
}
|
|
const tt = {
|
|
rLeg: !0,
|
|
lLeg: !0,
|
|
abdomen: !0,
|
|
chest: !0,
|
|
rArm: !0,
|
|
lArm: !0,
|
|
head: !0
|
|
}, xe = {
|
|
rLeg: !1,
|
|
lLeg: !1,
|
|
abdomen: !0,
|
|
chest: !0,
|
|
rArm: !1,
|
|
lArm: !1,
|
|
head: !1
|
|
}, Sa = {
|
|
rLeg: !1,
|
|
lLeg: !1,
|
|
abdomen: !1,
|
|
chest: !1,
|
|
rArm: !1,
|
|
lArm: !1,
|
|
head: !0
|
|
}, Aa = {
|
|
rLeg: !1,
|
|
lLeg: !1,
|
|
abdomen: !1,
|
|
chest: !1,
|
|
rArm: !0,
|
|
lArm: !0,
|
|
head: !1
|
|
}, xa = {
|
|
rLeg: !0,
|
|
lLeg: !0,
|
|
abdomen: !1,
|
|
chest: !1,
|
|
rArm: !1,
|
|
lArm: !1,
|
|
head: !1
|
|
}, vt = [
|
|
{
|
|
name: "Human",
|
|
playable: !0,
|
|
gravity: "normal",
|
|
homeworld: "Known Space",
|
|
modifiers: { str: 0, mas: 0, con: 0, int: 0, pow: 0, dex: 0, app: 0, edu: 0, age: 0 },
|
|
notes: "Baseline human explorer profile."
|
|
},
|
|
{
|
|
name: "Earth Human",
|
|
playable: !0,
|
|
gravity: "normal",
|
|
homeworld: "Earth",
|
|
modifiers: { str: 0, mas: 0, con: 0, int: 0, pow: 1, dex: 0, app: 0, edu: 0, age: 0 },
|
|
notes: "Earth-line humans with stronger latent psionic potential."
|
|
},
|
|
{
|
|
name: "Kzinti",
|
|
playable: !0,
|
|
gravity: "heavy",
|
|
homeworld: "Kzin",
|
|
modifiers: { str: 2, mas: 2, con: 1, int: -1, pow: 0, dex: 0, app: -2, edu: 0, age: 0 },
|
|
notes: "Predatory felinoids with high physical strength."
|
|
},
|
|
{
|
|
name: "Pierson's Puppeteer",
|
|
playable: !0,
|
|
gravity: "light",
|
|
homeworld: "Fleet of Worlds",
|
|
modifiers: { str: -2, mas: -2, con: -1, int: 2, pow: 1, dex: 1, app: 0, edu: 1, age: 0 },
|
|
notes: "Highly intelligent, cautious engineers."
|
|
},
|
|
{
|
|
name: "Dolphin",
|
|
playable: !0,
|
|
gravity: "normal",
|
|
homeworld: "Human Space Oceans",
|
|
modifiers: { str: -2, mas: 1, con: 1, int: 0, pow: 0, dex: 0, app: 0, edu: 0, age: 0 },
|
|
notes: "Aquatic specialists with enhanced endurance."
|
|
},
|
|
{
|
|
name: "Trinoc",
|
|
playable: !0,
|
|
gravity: "light",
|
|
homeworld: "Trinoc Worlds",
|
|
modifiers: { str: -1, mas: -1, con: 0, int: 1, pow: 0, dex: 1, app: 0, edu: 0, age: 0 },
|
|
notes: "Three-eyed flyers with excellent perception."
|
|
},
|
|
{
|
|
name: "Martian",
|
|
playable: !0,
|
|
gravity: "light",
|
|
homeworld: "Mars",
|
|
modifiers: { str: -1, mas: -1, con: -1, int: 1, pow: 0, dex: 1, app: 0, edu: 0, age: 0 },
|
|
notes: "Light-gravity adapted intellect-focused profile."
|
|
},
|
|
{
|
|
name: "Pierin",
|
|
playable: !0,
|
|
gravity: "normal",
|
|
homeworld: "Pierin Space",
|
|
modifiers: { str: 0, mas: 0, con: 0, int: 1, pow: 0, dex: 0, app: 1, edu: 0, age: 0 },
|
|
notes: "Socially adept, technically curious explorers."
|
|
},
|
|
{
|
|
name: "Boat People",
|
|
playable: !0,
|
|
gravity: "normal",
|
|
homeworld: "Ringworld",
|
|
modifiers: { str: 0, mas: 0, con: 1, int: 0, pow: 0, dex: 0, app: 0, edu: -1, age: 0 },
|
|
notes: "Ringworld hominids adapted to river and sea cultures."
|
|
},
|
|
{
|
|
name: "Builder",
|
|
playable: !0,
|
|
gravity: "normal",
|
|
homeworld: "Ringworld",
|
|
modifiers: { str: 1, mas: 1, con: 0, int: 0, pow: 0, dex: 0, app: -1, edu: 0, age: 0 },
|
|
notes: "Ringworld labor stock with robust build."
|
|
},
|
|
{
|
|
name: "Ghoul",
|
|
playable: !0,
|
|
gravity: "normal",
|
|
homeworld: "Ringworld",
|
|
modifiers: { str: 0, mas: 0, con: 1, int: -1, pow: 0, dex: 1, app: -2, edu: -1, age: 0 },
|
|
notes: "Scavenger subspecies with strong survival instincts."
|
|
},
|
|
{
|
|
name: "Bandersnatchi",
|
|
playable: !1,
|
|
gravity: "heavy",
|
|
homeworld: "Known Space",
|
|
modifiers: { str: 4, mas: 4, con: 3, int: 0, pow: 0, dex: -2, app: -3, edu: 0, age: 10 },
|
|
notes: "Massive engineered beings; typically GM creature profile."
|
|
}
|
|
], Pa = [
|
|
{
|
|
name: "Variable Sword",
|
|
equipped: !0,
|
|
weight: 1.5,
|
|
linkedSkill: "V. Sword, F. Laser",
|
|
attackType: "melee",
|
|
damageFormula: "1d10+1",
|
|
range: { close: "Melee", medium: "-", long: "-" },
|
|
energyUsedPerAttack: 1,
|
|
ammoUsedPerAttack: 0,
|
|
powerSource: "powerPack",
|
|
notes: "High-energy blade weapon."
|
|
},
|
|
{
|
|
name: "Monofilament Knife",
|
|
equipped: !0,
|
|
weight: 0.2,
|
|
linkedSkill: "Archaic Melee Weapon",
|
|
attackType: "melee",
|
|
damageFormula: "1d6+1",
|
|
range: { close: "Melee", medium: "-", long: "-" },
|
|
energyUsedPerAttack: 0,
|
|
ammoUsedPerAttack: 0,
|
|
powerSource: "actor",
|
|
notes: "Fine-edge close combat blade."
|
|
},
|
|
{
|
|
name: "Shock Baton",
|
|
equipped: !0,
|
|
weight: 1.2,
|
|
linkedSkill: "Unarmed Combat",
|
|
attackType: "melee",
|
|
damageFormula: "1d8+1",
|
|
range: { close: "Melee", medium: "-", long: "-" },
|
|
energyUsedPerAttack: 1,
|
|
ammoUsedPerAttack: 0,
|
|
powerSource: "powerPack",
|
|
notes: "Close-quarters stun baton."
|
|
},
|
|
{
|
|
name: "Flashlight Laser",
|
|
equipped: !0,
|
|
weight: 0.8,
|
|
linkedSkill: "V. Sword, F. Laser",
|
|
attackType: "ranged",
|
|
damageFormula: "1d10",
|
|
range: { close: "Short", medium: "Medium", long: "Long" },
|
|
energyUsedPerAttack: 2,
|
|
ammoUsedPerAttack: 0,
|
|
powerSource: "powerPack",
|
|
notes: "Compact laser unit."
|
|
},
|
|
{
|
|
name: "Laser Pistol",
|
|
equipped: !0,
|
|
weight: 1.4,
|
|
linkedSkill: "Handgun, Energy",
|
|
attackType: "ranged",
|
|
damageFormula: "1d10+1",
|
|
range: { close: "Short", medium: "Medium", long: "Long" },
|
|
energyUsedPerAttack: 2,
|
|
ammoUsedPerAttack: 0,
|
|
powerSource: "powerPack",
|
|
notes: "Common energy sidearm."
|
|
},
|
|
{
|
|
name: "Laser Rifle",
|
|
equipped: !0,
|
|
weight: 4.5,
|
|
linkedSkill: "Heavy Weapon, Energy",
|
|
attackType: "ranged",
|
|
damageFormula: "2d6+2",
|
|
range: { close: "Short", medium: "Long", long: "Extreme" },
|
|
energyUsedPerAttack: 3,
|
|
ammoUsedPerAttack: 0,
|
|
powerSource: "powerPack",
|
|
notes: "Standard long-arm laser weapon."
|
|
},
|
|
{
|
|
name: "Stunner",
|
|
equipped: !0,
|
|
weight: 1,
|
|
linkedSkill: "Handgun, Energy",
|
|
attackType: "ranged",
|
|
damageFormula: "1d6",
|
|
range: { close: "Short", medium: "Medium", long: "Long" },
|
|
energyUsedPerAttack: 1,
|
|
ammoUsedPerAttack: 0,
|
|
powerSource: "powerPack",
|
|
notes: "Low-lethality disruption sidearm."
|
|
},
|
|
{
|
|
name: "Handgun (Projectile)",
|
|
equipped: !0,
|
|
weight: 1.2,
|
|
linkedSkill: "Handgun, Projectile",
|
|
attackType: "ranged",
|
|
damageFormula: "1d10",
|
|
range: { close: "Short", medium: "Medium", long: "Long" },
|
|
energyUsedPerAttack: 0,
|
|
ammoUsedPerAttack: 1,
|
|
powerSource: "actor",
|
|
notes: "Conventional projectile sidearm."
|
|
},
|
|
{
|
|
name: "Rifle (Projectile)",
|
|
equipped: !0,
|
|
weight: 5,
|
|
linkedSkill: "Heavy Weapon, Projectile",
|
|
attackType: "ranged",
|
|
damageFormula: "2d6",
|
|
range: { close: "Short", medium: "Long", long: "Extreme" },
|
|
energyUsedPerAttack: 0,
|
|
ammoUsedPerAttack: 1,
|
|
powerSource: "actor",
|
|
notes: "Conventional projectile rifle."
|
|
},
|
|
{
|
|
name: "Slug Thrower Carbine",
|
|
equipped: !0,
|
|
weight: 3.8,
|
|
linkedSkill: "Heavy Weapon, Projectile",
|
|
attackType: "ranged",
|
|
damageFormula: "2d6+1",
|
|
range: { close: "Short", medium: "Long", long: "Extreme" },
|
|
energyUsedPerAttack: 0,
|
|
ammoUsedPerAttack: 1,
|
|
powerSource: "actor",
|
|
notes: "Compact projectile carbine."
|
|
}
|
|
], La = [
|
|
{ name: "Cloth Vest", equipped: !0, weight: 2, value: 1, coverage: xe, notes: "Basic torso protection." },
|
|
{ name: "Soft Combat Vest", equipped: !0, weight: 4, value: 2, coverage: xe, notes: "Flexible tactical vest." },
|
|
{ name: "Combat Harness", equipped: !0, weight: 5, value: 2, coverage: { ...xe, rArm: !0, lArm: !0 }, notes: "Torso + upper arms." },
|
|
{ name: "Armored Coat", equipped: !0, weight: 7, value: 3, coverage: { ...xe, rArm: !0, lArm: !0 }, notes: "Heavy coat with arm plating." },
|
|
{ name: "Leg Guards", equipped: !0, weight: 4, value: 2, coverage: xa, notes: "Lower-body armor." },
|
|
{ name: "Arm Bracers", equipped: !0, weight: 2, value: 2, coverage: Aa, notes: "Forearm and upper arm guards." },
|
|
{ name: "Combat Helmet", equipped: !0, weight: 1.5, value: 2, coverage: Sa, notes: "Cranial protection." },
|
|
{ name: "Full Combat Suit", equipped: !0, weight: 12, value: 3, coverage: tt, notes: "Full-body armored suit." },
|
|
{ name: "Impact Suit", equipped: !0, weight: 16, value: 4, coverage: tt, notes: "High-end impact and hazard suit." }
|
|
], Ia = [
|
|
{
|
|
name: "Power Pack R20",
|
|
weight: 1,
|
|
cost: 100,
|
|
energyUse: 0,
|
|
powerSupplyNotes: "Rechargeable power pack",
|
|
isPowerPack: !0,
|
|
energy: { value: 20, max: 20 },
|
|
notes: "Standard personal battery pack."
|
|
},
|
|
{
|
|
name: "Power Pack R50",
|
|
weight: 2,
|
|
cost: 220,
|
|
energyUse: 0,
|
|
powerSupplyNotes: "Rechargeable power pack",
|
|
isPowerPack: !0,
|
|
energy: { value: 50, max: 50 },
|
|
notes: "Extended personal battery pack."
|
|
},
|
|
{
|
|
name: "Power Pack R100",
|
|
weight: 4,
|
|
cost: 400,
|
|
energyUse: 0,
|
|
powerSupplyNotes: "Rechargeable power pack",
|
|
isPowerPack: !0,
|
|
energy: { value: 100, max: 100 },
|
|
notes: "High-capacity field battery."
|
|
},
|
|
{ name: "Portable Generator", weight: 8, cost: 700, energyUse: 0, powerSupplyNotes: "Provides charging capacity", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Portable recharge generator." },
|
|
{ name: "MicroMind Unit", weight: 0.5, cost: 900, energyUse: 1, powerSupplyNotes: "Requires external power", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Portable advanced computation module." },
|
|
{ name: "Memory Bubble Reader", weight: 0.5, cost: 450, energyUse: 1, powerSupplyNotes: "Standard battery compatible", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Reads and writes memory bubbles." },
|
|
{ name: "Autodoc (Portable)", weight: 18, cost: 3500, energyUse: 6, powerSupplyNotes: "High draw medical station", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Field medical support unit." },
|
|
{ name: "Medkit", weight: 1, cost: 120, energyUse: 0, powerSupplyNotes: "None", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Manual emergency treatment supplies." },
|
|
{ name: "Bioscanner", weight: 1, cost: 300, energyUse: 1, powerSupplyNotes: "Battery or generator", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Vitals and hazard scanning tool." },
|
|
{ name: "Survey Scanner", weight: 2, cost: 500, energyUse: 2, powerSupplyNotes: "Battery or generator", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Terrain and structure analysis scanner." },
|
|
{ name: "Translator Module", weight: 0.5, cost: 650, energyUse: 1, powerSupplyNotes: "Battery compatible", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Linguistic conversion and support." },
|
|
{ name: "Holo Projector", weight: 1.5, cost: 800, energyUse: 2, powerSupplyNotes: "Battery compatible", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Short-range holographic projection." },
|
|
{ name: "Stasis Box", weight: 12, cost: 2600, energyUse: 5, powerSupplyNotes: "Continuous power required", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Suspended animation container." },
|
|
{ name: "Reactionless Flycycle", weight: 35, cost: 4200, energyUse: 4, powerSupplyNotes: "Vehicle power core", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Single-rider reactionless vehicle." },
|
|
{ name: "Field Toolkit", weight: 2, cost: 180, energyUse: 0, powerSupplyNotes: "None", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Mechanical and electronics repair kit." },
|
|
{ name: "Vac Suit", weight: 7, cost: 1400, energyUse: 1, powerSupplyNotes: "Suit battery required", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Pressure and vacuum survival suit." },
|
|
{ name: "Air Reprocessor", weight: 3, cost: 950, energyUse: 2, powerSupplyNotes: "Continuous power", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Atmosphere recycling support unit." },
|
|
{ name: "Signal Beacon", weight: 1, cost: 140, energyUse: 1, powerSupplyNotes: "Battery compatible", isPowerPack: !1, energy: { value: 0, max: 0 }, notes: "Emergency and navigation beacon." }
|
|
], be = [
|
|
{
|
|
name: "Artist",
|
|
rollRange: "01-07",
|
|
wealthValue: 0,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Athletics", "Computers", "Fast Talk", "Fine Arts", "Hide", "Musicianship", "Observe", "Perform", "Psychology", "Scent", "Theology"],
|
|
notes: "Creative specialist pursuit."
|
|
},
|
|
{
|
|
name: "Colonist",
|
|
rollRange: "08-27",
|
|
wealthValue: 1,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Atmospheric Craft", "Bargain", "Ground Vehicle", "Farming", "Handgun, Energy", "Personal Flyer", "Listen", "Heavy Weapon, Energy", "Observe", "Planetology", "Search", "Track"],
|
|
notes: "Settlement and frontier operations pursuit."
|
|
},
|
|
{
|
|
name: "Criminal",
|
|
rollRange: "28-29",
|
|
wealthValue: 1,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Bargain", "Computers", "Fast Talk", "Hide", "Personal Flyer", "Handgun, Energy", "Handgun, Projectile", "Sneak", "Unarmed Combat"],
|
|
notes: "Underground survival pursuit."
|
|
},
|
|
{
|
|
name: "Dilettante",
|
|
rollRange: "30",
|
|
wealthValue: 5,
|
|
occupationPointsPerYear: 15,
|
|
fixedSkills: [],
|
|
notes: "Any skills allowed; uses 15 occupation points per year."
|
|
},
|
|
{
|
|
name: "Engineer",
|
|
rollRange: "31-36",
|
|
wealthValue: 2,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Astronomy", "Chemistry", "Computers", "Engineering", "Ground Vehicle", "Mathematics", "Personal Flyer", "Physics", "Planetology", "Reaction Drive", "Reactionless Drive", "Repair"],
|
|
notes: "Technical design and maintenance pursuit."
|
|
},
|
|
{
|
|
name: "Entertainer",
|
|
rollRange: "37-38",
|
|
wealthValue: 2,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Athletics", "Bargain", "Own Language", "Musicianship", "Observe", "Orate", "Perform", "Psychology", "Search", "Sneak"],
|
|
notes: "Performance and social influence pursuit."
|
|
},
|
|
{
|
|
name: "Explorer",
|
|
rollRange: "39-52",
|
|
wealthValue: 1,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Athletics", "Atmospheric Craft", "Bargain", "Biology", "Botany", "Emergency Treatment", "Engineering", "Ground Vehicle", "Handgun, Energy", "Heavy Weapon, Energy", "Hyperdrive", "Observe", "Planetology", "Reactionless Drive", "Repair", "Search", "Track", "Zoology"],
|
|
notes: "Survey and expedition pursuit."
|
|
},
|
|
{
|
|
name: "Functionary",
|
|
rollRange: "53-56",
|
|
wealthValue: 3,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Bargain", "Debate", "Fast Talk", "History", "Law", "Observe", "Orate", "Psychology", "Strategy", "Theology"],
|
|
notes: "Administrative and governance pursuit."
|
|
},
|
|
{
|
|
name: "Journalist/Writer",
|
|
rollRange: "57-59",
|
|
wealthValue: 1,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Anthropology", "Bargain", "Computers", "Debate", "History", "Fine Arts", "Own Language", "Law", "Observe", "Psychology", "Search", "Sneak", "Unarmed Combat", "Weapons System"],
|
|
notes: "Investigation and reporting pursuit."
|
|
},
|
|
{
|
|
name: "Miner",
|
|
rollRange: "60-65",
|
|
wealthValue: 1,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Atmospheric Craft", "Aquatic Vehicle", "Bargain", "Climb (Custom)", "Computers", "Emergency Treatment", "Engineering", "Handgun, Energy", "Heavy Weapon, Energy", "Observe", "Personal Flyer", "Planetology", "Reaction Drive", "Reactionless Drive", "Repair", "Search"],
|
|
notes: "Resource extraction frontier pursuit."
|
|
},
|
|
{
|
|
name: "Pilot",
|
|
rollRange: "66-68",
|
|
wealthValue: 3,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Astronomy", "Atmospheric Craft", "Computers", "Engineering", "Hyperdrive", "History", "Law", "Mathematics", "Physics", "Reaction Drive", "Reactionless Drive", "Repair", "Strategy", "Weapons System"],
|
|
notes: "Flight and spacecraft operations pursuit."
|
|
},
|
|
{
|
|
name: "Police Officer",
|
|
rollRange: "69-76",
|
|
wealthValue: 1,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Atmospheric Craft", "Bargain", "Computers", "Fast Talk", "Emergency Treatment", "Handgun, Energy", "Handgun, Projectile", "Heavy Weapon, Energy", "Heavy Weapon, Projectile", "Law", "Observe", "Personal Flyer", "Psychology", "Search", "Unarmed Combat"],
|
|
notes: "Law enforcement and response pursuit."
|
|
},
|
|
{
|
|
name: "Politician",
|
|
rollRange: "77-78",
|
|
wealthValue: 3,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Bargain", "Computers", "Debate", "Fast Talk", "History", "Orate", "Own Language", "Perform", "Psychology"],
|
|
optionalSkillGroups: [
|
|
{
|
|
choose: 1,
|
|
skills: ["Engineering", "Farming", "Second Languages", "Strategy", "Theology"]
|
|
}
|
|
],
|
|
notes: "Public office and influence pursuit."
|
|
},
|
|
{
|
|
name: "Prisoner",
|
|
rollRange: "79-80",
|
|
wealthValue: 0,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Bargain", "Fast Talk", "Hide", "Observe", "Psychology", "Search", "Sneak", "Unarmed Combat"],
|
|
notes: "Carceral survival pursuit."
|
|
},
|
|
{
|
|
name: "Scholar",
|
|
rollRange: "81-84",
|
|
wealthValue: 1,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: [],
|
|
optionalSkillGroups: [
|
|
{
|
|
choose: 2,
|
|
skills: ["Anthropology", "Astronomy", "Biology", "Botany", "Chemistry", "Computers", "Engineering", "Fine Arts", "History", "Law", "Mathematics", "Own Language", "Physics", "Planetology", "Second Languages", "Theology", "Zoology"]
|
|
}
|
|
],
|
|
notes: "Academic pursuit; pick up to two listed skills."
|
|
},
|
|
{
|
|
name: "Scientific Researcher",
|
|
rollRange: "85-94",
|
|
wealthValue: 1,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: [],
|
|
optionalSkillGroups: [
|
|
{
|
|
choose: 3,
|
|
skills: ["Anthropology", "Astronomy", "Bargain", "Biology", "Botany", "Chemistry", "Computers", "Engineering", "History", "Mathematics", "Observe", "Personal Flyer", "Physics", "Planetology", "Psychology", "Repair", "Search", "Zoology"]
|
|
}
|
|
],
|
|
notes: "Research pursuit; pick up to three listed skills."
|
|
},
|
|
{
|
|
name: "Soldier",
|
|
rollRange: "95-98",
|
|
wealthValue: 1,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Athletics", "Atmospheric Craft", "Aquatic Vehicle", "Computers", "Emergency Treatment", "Fast Talk", "Ground Vehicle", "Handgun, Energy", "Handgun, Projectile", "Heavy Weapon, Energy", "Heavy Weapon, Projectile"],
|
|
notes: "Military operations pursuit."
|
|
},
|
|
{
|
|
name: "Zealot",
|
|
rollRange: "99-00",
|
|
wealthValue: 0,
|
|
occupationPointsPerYear: 20,
|
|
fixedSkills: ["Bargain", "Debate", "Fast Talk", "Fine Arts", "Handgun, Energy", "Handgun, Projectile", "Hide", "Own Language", "Law", "Strategy", "Orate", "Psychology", "Theology"],
|
|
notes: "Ideological dedication pursuit."
|
|
}
|
|
];
|
|
function St() {
|
|
return vt.filter((e) => e.playable);
|
|
}
|
|
function z(e, t) {
|
|
return `${e}:${M(t)}`;
|
|
}
|
|
function Ma(e) {
|
|
var s, i, r, l, c;
|
|
const t = (s = getDocumentClass("Item")) == null ? void 0 : s.TYPES;
|
|
if (Array.isArray(t)) return t.includes(e);
|
|
const a = (r = (i = game == null ? void 0 : game.system) == null ? void 0 : i.documentTypes) == null ? void 0 : r.Item;
|
|
if (Array.isArray(a)) return a.includes(e);
|
|
if (a && typeof a == "object") return Object.prototype.hasOwnProperty.call(a, e);
|
|
const n = (l = CONFIG == null ? void 0 : CONFIG.Item) == null ? void 0 : l.dataModels;
|
|
if (n && typeof n == "object" && Object.prototype.hasOwnProperty.call(n, e))
|
|
return !0;
|
|
const o = (c = game == null ? void 0 : game.model) == null ? void 0 : c.Item;
|
|
return !!(o && typeof o == "object" && Object.prototype.hasOwnProperty.call(o, e));
|
|
}
|
|
function Ca(e) {
|
|
return {
|
|
key: z("skill", e.name),
|
|
folderKey: "skills",
|
|
img: we(e.name) || j.skillDefault,
|
|
data: {
|
|
name: e.name,
|
|
type: "skill",
|
|
system: {
|
|
value: e.base,
|
|
category: e.category,
|
|
specialization: e.specialization ?? ""
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function Ra(e) {
|
|
const t = wa(e.name) || j.speciesDefault;
|
|
return {
|
|
key: z("species", e.name),
|
|
folderKey: "species",
|
|
img: t,
|
|
data: {
|
|
name: e.name,
|
|
type: "species",
|
|
system: {
|
|
playable: e.playable,
|
|
gravity: e.gravity,
|
|
homeworld: e.homeworld,
|
|
modifiers: e.modifiers,
|
|
notes: e.notes
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function $a(e) {
|
|
const t = e.attackType === "melee" ? "weaponMelee" : "weaponRanged", a = ba(e.name) || (e.attackType === "melee" ? j.weaponMelee : j.weaponRanged);
|
|
return {
|
|
key: z("weapon", e.name),
|
|
folderKey: t,
|
|
img: a,
|
|
data: {
|
|
name: e.name,
|
|
type: "weapon",
|
|
system: {
|
|
equipped: e.equipped ?? !0,
|
|
stowed: !1,
|
|
weight: e.weight ?? 0,
|
|
linkedSkill: e.linkedSkill,
|
|
linkedSkillId: "",
|
|
attackType: e.attackType,
|
|
damageFormula: e.damageFormula,
|
|
range: e.range,
|
|
energyUsedPerAttack: e.energyUsedPerAttack,
|
|
ammoUsedPerAttack: e.ammoUsedPerAttack,
|
|
powerSource: e.powerSource,
|
|
powerPackId: "",
|
|
notes: e.notes
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function Ta(e) {
|
|
const t = ka(e.name) || j.armor;
|
|
return {
|
|
key: z("armor", e.name),
|
|
folderKey: "armor",
|
|
img: t,
|
|
data: {
|
|
name: e.name,
|
|
type: "armor",
|
|
system: {
|
|
equipped: e.equipped,
|
|
stowed: !1,
|
|
weight: e.weight ?? 0,
|
|
value: e.value,
|
|
coverage: e.coverage,
|
|
perLocation: {
|
|
rLeg: 0,
|
|
lLeg: 0,
|
|
abdomen: 0,
|
|
chest: 0,
|
|
rArm: 0,
|
|
lArm: 0,
|
|
head: 0
|
|
},
|
|
notes: e.notes
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function Da(e) {
|
|
const t = va(e.name) || (e.isPowerPack ? j.powerPackDefault : j.gearDefault);
|
|
return {
|
|
key: z("gear", e.name),
|
|
folderKey: "gear",
|
|
img: t,
|
|
data: {
|
|
name: e.name,
|
|
type: "gear",
|
|
system: {
|
|
equipped: e.equipped ?? !0,
|
|
stowed: !1,
|
|
weight: e.weight,
|
|
cost: e.cost,
|
|
energyUse: e.energyUse,
|
|
powerSupplyNotes: e.powerSupplyNotes,
|
|
isPowerPack: e.isPowerPack,
|
|
energy: e.energy,
|
|
notes: e.notes
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function Na(e) {
|
|
const t = kt(e.name) || j.pursuitDefault;
|
|
return {
|
|
key: z("pursuit", e.name),
|
|
folderKey: "pursuits",
|
|
img: t,
|
|
data: {
|
|
name: e.name,
|
|
type: "pursuit",
|
|
system: {
|
|
wealthValue: e.wealthValue,
|
|
occupationPointsPerYear: e.occupationPointsPerYear,
|
|
rollRange: e.rollRange,
|
|
fixedSkills: e.fixedSkills,
|
|
optionalSkillGroups: e.optionalSkillGroups ?? [],
|
|
notes: e.notes
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function Ha(e) {
|
|
const t = e.fixedSkills.length ? `Fixed skills: ${e.fixedSkills.join(", ")}` : "Fixed skills: (none)", a = (e.optionalSkillGroups ?? []).map((o) => `Choose ${o.choose}: ${o.skills.join(", ")}`).join(" | "), n = a ? ` Optional: ${a}.` : "";
|
|
return `${e.notes} ${t}.${n} Wealth ${e.wealthValue}. Occupation points/year ${e.occupationPointsPerYear}.`;
|
|
}
|
|
function Oa(e) {
|
|
const t = kt(e.name) || j.pursuitDefault, a = `Pursuit: ${e.name}`;
|
|
return {
|
|
key: z("gear", a),
|
|
folderKey: "pursuits",
|
|
img: t,
|
|
data: {
|
|
name: a,
|
|
type: "gear",
|
|
system: {
|
|
equipped: !1,
|
|
stowed: !0,
|
|
weight: 0,
|
|
cost: 0,
|
|
energyUse: 0,
|
|
powerSupplyNotes: "Pursuit reference",
|
|
isPowerPack: !1,
|
|
energy: { value: 0, max: 0 },
|
|
notes: Ha(e)
|
|
},
|
|
flags: {
|
|
ringworld: {
|
|
pursuitData: {
|
|
pursuitName: e.name,
|
|
rollRange: e.rollRange,
|
|
wealthValue: e.wealthValue,
|
|
occupationPointsPerYear: e.occupationPointsPerYear,
|
|
fixedSkills: e.fixedSkills,
|
|
optionalSkillGroups: e.optionalSkillGroups ?? []
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
async function K(e, t) {
|
|
const a = game.folders.find(
|
|
(n) => {
|
|
var o;
|
|
return n.type === "Item" && n.name === e && (((o = n.folder) == null ? void 0 : o.id) ?? null) === t;
|
|
}
|
|
);
|
|
return a || Folder.create({
|
|
name: e,
|
|
type: "Item",
|
|
folder: t,
|
|
sorting: "a"
|
|
});
|
|
}
|
|
async function Ea() {
|
|
const e = await K("Ringworld Catalog", null), t = await K("Species", e.id), a = await K("Skills", e.id), n = await K("Pursuits", e.id), o = await K("Gear", e.id), s = await K("Armor", o.id), i = await K("Weapons", e.id), r = await K("Melee Weapons", i.id), l = await K("Ranged Weapons", i.id);
|
|
return {
|
|
species: t.id,
|
|
skills: a.id,
|
|
pursuits: n.id,
|
|
gear: o.id,
|
|
armor: s.id,
|
|
weaponMelee: r.id,
|
|
weaponRanged: l.id
|
|
};
|
|
}
|
|
function Fa() {
|
|
const e = Ma("pursuit");
|
|
return [
|
|
...vt.map(Ra),
|
|
...ke.map(Ca),
|
|
...e ? be.map(Na) : be.map(Oa),
|
|
...Pa.map($a),
|
|
...La.map(Ta),
|
|
...Ia.map(Da)
|
|
];
|
|
}
|
|
async function Ue(e) {
|
|
var r, l, c, u, m, g, f;
|
|
if (!((r = game.user) != null && r.isGM)) return { added: 0, updated: 0 };
|
|
const t = await Ea(), a = Fa(), n = new Map(
|
|
game.items.map((d) => [z(d.type, d.name), d])
|
|
), o = [], s = [];
|
|
for (const d of a) {
|
|
const y = t[d.folderKey], b = n.get(d.key);
|
|
if (!b) {
|
|
o.push({
|
|
...d.data,
|
|
img: d.img,
|
|
folder: y,
|
|
flags: {
|
|
ringworld: {
|
|
catalog: !0,
|
|
catalogKey: d.key
|
|
}
|
|
}
|
|
});
|
|
continue;
|
|
}
|
|
const h = { _id: b.id };
|
|
(((l = b.folder) == null ? void 0 : l.id) ?? null) !== y && (h.folder = y), String(b.img || "") !== d.img && (h.img = d.img), ((u = (c = b.flags) == null ? void 0 : c.ringworld) == null ? void 0 : u.catalog) !== !0 && (h["flags.ringworld.catalog"] = !0), ((g = (m = b.flags) == null ? void 0 : m.ringworld) == null ? void 0 : g.catalogKey) !== d.key && (h["flags.ringworld.catalogKey"] = d.key);
|
|
const p = foundry.utils.deepClone(b.system ?? {}), w = foundry.utils.mergeObject(p, d.data.system ?? {}, {
|
|
inplace: !1,
|
|
insertKeys: !0,
|
|
insertValues: !0,
|
|
overwrite: !1
|
|
}), v = foundry.utils.diffObject(p, w);
|
|
Object.keys(v).length > 0 && (h.system = w), Object.keys(h).length > 1 && s.push(h);
|
|
}
|
|
o.length && await Item.createDocuments(o), s.length && await Item.updateDocuments(s);
|
|
const i = await ya();
|
|
if (e != null && e.notify) {
|
|
const d = i ? " GM Reference journal added." : "";
|
|
(f = ui.notifications) == null || f.info(`Ringworld catalog synced: +${o.length} new, ${s.length} updated.${d}`);
|
|
}
|
|
return { added: o.length, updated: s.length };
|
|
}
|
|
function At() {
|
|
return game.items.filter((e) => e.type === "species" && e.system.playable);
|
|
}
|
|
function G(e) {
|
|
return Math.max(1, Math.min(99, Math.floor(e)));
|
|
}
|
|
async function _(e) {
|
|
const t = await ye(e);
|
|
return Number(t.total ?? 0);
|
|
}
|
|
async function Ba() {
|
|
let e = 10;
|
|
for (; ; ) {
|
|
const t = await _("1d6");
|
|
if (e += t, t <= 4) break;
|
|
}
|
|
return G(e);
|
|
}
|
|
async function Ua(e) {
|
|
const t = {
|
|
light: { str: "2d6+3", mas: "2d6+3", con: "2d6+6", dex: "2d6+9" },
|
|
normal: { str: "2d6+6", mas: "2d6+6", con: "2d6+6", dex: "2d6+6" },
|
|
heavy: { str: "2d6+9", mas: "2d6+9", con: "2d6+3", dex: "2d6+6" }
|
|
}[e];
|
|
return {
|
|
str: await _(t.str),
|
|
mas: await _(t.mas),
|
|
con: await _(t.con),
|
|
int: await _("2d6+6"),
|
|
pow: await _("2d6+6"),
|
|
dex: await _(t.dex),
|
|
app: await _("2d6+6"),
|
|
edu: await Ba(),
|
|
age: await _("1d20+16")
|
|
};
|
|
}
|
|
function Pe(e) {
|
|
return e[Math.floor(Math.random() * e.length)];
|
|
}
|
|
function qa(e) {
|
|
const t = ["Ar", "Bel", "Cor", "Den", "Ela", "Far", "Gal", "Hel", "Ira", "Jun", "Kor", "Lys", "Mor", "Nyr", "Or", "Pra", "Qua", "Rin", "Sol", "Tor", "Uri", "Val", "Wen", "Xel", "Yor", "Zen"], a = ["an", "el", "is", "or", "us", "a", "en", "ion", "eth", "al", "ir", "os"];
|
|
return `${Pe(t)}${Pe(a)} (${e})`;
|
|
}
|
|
function Ga(e, t) {
|
|
const a = (t == null ? void 0 : t.modifiers) ?? {};
|
|
return {
|
|
str: G(e.str + Number(a.str || 0)),
|
|
mas: G(e.mas + Number(a.mas || 0)),
|
|
con: G(e.con + Number(a.con || 0)),
|
|
int: G(e.int + Number(a.int || 0)),
|
|
pow: G(e.pow + Number(a.pow || 0)),
|
|
dex: G(e.dex + Number(a.dex || 0)),
|
|
app: G(e.app + Number(a.app || 0)),
|
|
edu: G(e.edu + Number(a.edu || 0)),
|
|
age: G(e.age + Number(a.age || 0))
|
|
};
|
|
}
|
|
async function xt(e) {
|
|
var m, g, f, d;
|
|
const t = String(((g = (m = e.system) == null ? void 0 : m.profile) == null ? void 0 : g.gravity) || "normal"), a = t === "light" || t === "heavy" ? t : "normal", n = String(((d = (f = e.system) == null ? void 0 : f.profile) == null ? void 0 : d.species) || ""), s = At().find((y) => y.name === n), i = St().find((y) => y.name === n), r = (s == null ? void 0 : s.system) ?? i ?? null, l = await Ua(a), c = Ga(l, r);
|
|
await e.update({
|
|
"system.characteristics.str": c.str,
|
|
"system.characteristics.mas": c.mas,
|
|
"system.characteristics.con": c.con,
|
|
"system.characteristics.int": c.int,
|
|
"system.characteristics.pow": c.pow,
|
|
"system.characteristics.dex": c.dex,
|
|
"system.characteristics.app": c.app,
|
|
"system.characteristics.edu": c.edu,
|
|
"system.characteristics.age": c.age,
|
|
"system.profile.chronologicalAge": c.age,
|
|
"system.profile.physiologicalAge": c.age
|
|
});
|
|
const u = e.items.find((y) => y.type === "skill" && y.name.toLowerCase() === "own language");
|
|
u && await e.updateEmbeddedDocuments("Item", [{
|
|
_id: u.id,
|
|
"system.value": Math.min(99, c.int * 5)
|
|
}]);
|
|
}
|
|
async function Va(e) {
|
|
const t = [
|
|
["weapon", "Laser Pistol"],
|
|
["weapon", "Variable Sword"],
|
|
["armor", "Soft Combat Vest"],
|
|
["gear", "Power Pack R20"],
|
|
["gear", "Medkit"],
|
|
["gear", "Survey Scanner"]
|
|
], a = game.items, n = new Set(e.items.map((s) => `${s.type}:${s.name.toLowerCase()}`)), o = [];
|
|
for (const [s, i] of t) {
|
|
if (n.has(`${s}:${String(i).toLowerCase()}`)) continue;
|
|
const r = a.find((l) => l.type === s && l.name === i);
|
|
r && o.push(r.toObject());
|
|
}
|
|
o.length && await e.createEmbeddedDocuments("Item", o);
|
|
}
|
|
async function Pt(e) {
|
|
var c;
|
|
const t = At(), a = St(), n = t.length ? Pe(t) : null, o = n ? null : Pe(a), s = (n == null ? void 0 : n.system) ?? o, i = (n == null ? void 0 : n.name) ?? (o == null ? void 0 : o.name) ?? "Human", r = String(s.gravity || "normal"), l = String(s.homeworld || "Ringworld");
|
|
await e.update({
|
|
name: qa(i),
|
|
"system.profile.species": i,
|
|
"system.profile.gravity": r,
|
|
"system.profile.homeworld": l
|
|
}), await xt(e), await ((c = e.ensureBaseSkills) == null ? void 0 : c.call(e)), await Va(e);
|
|
}
|
|
async function Lt() {
|
|
const t = await getDocumentClass("Actor").create({
|
|
name: "Random Explorer",
|
|
type: "explorer"
|
|
});
|
|
return await Pt(t), t;
|
|
}
|
|
const Ka = ["str", "mas", "con", "int", "pow", "dex", "app", "edu", "age"];
|
|
function S(e, t = 0) {
|
|
const a = Number(e);
|
|
return Number.isFinite(a) ? a : t;
|
|
}
|
|
function Oe(e) {
|
|
return Math.max(1, Math.min(99, Math.floor(e)));
|
|
}
|
|
function _a(e, t) {
|
|
const a = e + t;
|
|
return a <= 12 ? "-1d6" : a <= 24 ? "-1d4" : a <= 32 ? "0" : a <= 40 ? "+1d4" : a <= 56 ? "+1d6" : a <= 72 ? "+2d6" : "+3d6";
|
|
}
|
|
function at(e) {
|
|
const t = S(e == null ? void 0 : e.con, 10), a = S(e == null ? void 0 : e.int, 10), n = S(e == null ? void 0 : e.pow, 10), o = S(e == null ? void 0 : e.dex, 10);
|
|
return {
|
|
health: Math.max(0, Math.min(99, t * 5)),
|
|
reasoning: Math.max(0, Math.min(99, a * 5)),
|
|
luck: Math.max(0, Math.min(99, n * 5)),
|
|
dodge: Math.max(0, Math.min(99, o * 5))
|
|
};
|
|
}
|
|
function ja(e) {
|
|
const t = S(e == null ? void 0 : e.str, 10), a = S(e == null ? void 0 : e.dex, 10), n = S(e == null ? void 0 : e.app, 10), o = S(e == null ? void 0 : e.edu, 10), s = S(e == null ? void 0 : e.int, 10), i = S(e == null ? void 0 : e.pow, 10), r = S(e == null ? void 0 : e.con, 10);
|
|
return {
|
|
agility: Math.max(0, t + a),
|
|
communication: Math.max(0, s + n),
|
|
knowledge: Math.max(0, s + o),
|
|
perception: Math.max(0, i + r),
|
|
technical: Math.max(0, a + s)
|
|
};
|
|
}
|
|
function Wa(e) {
|
|
return e <= 4 ? 7 : e <= 8 ? 6 : e <= 12 ? 5 : e <= 16 ? 4 : e <= 20 ? 3 : e <= 24 ? 2 : 1;
|
|
}
|
|
function Ya(e) {
|
|
return e === "light" ? 0.5 : e === "heavy" ? 2 : 1;
|
|
}
|
|
function za(e) {
|
|
var t;
|
|
return e.type !== "weapon" && e.type !== "armor" && e.type !== "gear" ? !1 : ((t = e.system) == null ? void 0 : t.stowed) !== !0;
|
|
}
|
|
function Qa(e) {
|
|
var t;
|
|
return Math.max(0, S((t = e.system) == null ? void 0 : t.weight, 0));
|
|
}
|
|
function Xa(e, t) {
|
|
var y, b, h, p;
|
|
const a = String(((y = e == null ? void 0 : e.profile) == null ? void 0 : y.gravity) || "normal"), n = Ya(a), o = t.filter((w) => w.type === "weapon" || w.type === "armor" || w.type === "gear").filter(za).reduce((w, v) => w + Qa(v), 0), s = Math.max(0, o), i = s * n, r = Math.max(0, S((b = e == null ? void 0 : e.characteristics) == null ? void 0 : b.str, 0)), l = Math.max(0, i - r), c = l > 0 ? Math.ceil(l / 10) : 0, u = l > 0, m = Math.max(1, S((h = e == null ? void 0 : e.derived) == null ? void 0 : h.move, 3)), g = Math.max(1, S((p = e == null ? void 0 : e.derived) == null ? void 0 : p.run, m * 2));
|
|
return {
|
|
rawLoadKg: s,
|
|
effectiveLoadKg: i,
|
|
limitKg: r,
|
|
overLimitKg: l,
|
|
actionRankingPenalty: c,
|
|
overLimit: u,
|
|
currentMove: u ? 2 : m,
|
|
currentRun: u ? 4 : g,
|
|
gravityMultiplier: n
|
|
};
|
|
}
|
|
class Za extends Actor {
|
|
prepareDerivedData() {
|
|
if (super.prepareDerivedData(), fa(this), this.type !== "explorer" && this.type !== "creature") return;
|
|
const t = this.system ?? {}, a = t.characteristics ?? {}, n = t.profile ?? {};
|
|
n.chronologicalAge = S(n.chronologicalAge, S(a.age, 25)), n.physiologicalAge = S(n.physiologicalAge, n.chronologicalAge), t.profile = n, t.combat = t.combat ?? {}, t.combat.damageMod = _a(S(a.str, 10), S(a.mas, 10)), t.derivedRolls = t.derivedRolls ?? {};
|
|
const o = at(a);
|
|
for (const r of Object.keys(o))
|
|
t.derivedRolls[r] = o[r];
|
|
t.skillRootMax = t.skillRootMax ?? {};
|
|
const s = ja(a);
|
|
for (const [r, l] of Object.entries(s))
|
|
t.skillRootMax[r] = l;
|
|
t.derived = t.derived ?? {}, t.derived.actionRanking = Wa(S(a.dex, 10)), t.derived.move = S(t.derived.move, 3), t.derived.run = S(t.derived.run, Math.max(1, S(t.derived.move, 3) * 2)), t.encumbrance = t.encumbrance ?? {};
|
|
const i = Xa(t, this.items);
|
|
t.encumbrance.rawLoadKg = i.rawLoadKg, t.encumbrance.effectiveLoadKg = i.effectiveLoadKg, t.encumbrance.limitKg = i.limitKg, t.encumbrance.overLimitKg = i.overLimitKg, t.encumbrance.actionRankingPenalty = i.actionRankingPenalty, t.encumbrance.overLimit = i.overLimit, t.encumbrance.gravityMultiplier = i.gravityMultiplier, t.derived.currentMove = i.currentMove, t.derived.currentRun = i.currentRun, t.finance = t.finance ?? { money: 0, inBank: 0 }, t.finance.money = S(t.finance.money, 0), t.finance.inBank = S(t.finance.inBank, 0), t.notes = t.notes ?? { defects: "", habits: "" }, typeof t.sheetToolsUnlocked != "boolean" && (t.sheetToolsUnlocked = !1), t.characterBuilder = t.characterBuilder ?? {
|
|
pursuits: [],
|
|
allocations: {},
|
|
baseBySkillId: {}
|
|
}, t.unconsciousLevel = S(t.unconsciousLevel, 0);
|
|
}
|
|
get skills() {
|
|
return this.items.filter((t) => t.type === "skill");
|
|
}
|
|
get weapons() {
|
|
return this.items.filter((t) => t.type === "weapon");
|
|
}
|
|
get armors() {
|
|
return this.items.filter((t) => t.type === "armor");
|
|
}
|
|
get gears() {
|
|
return this.items.filter((t) => t.type === "gear");
|
|
}
|
|
async rollSkillById(t) {
|
|
const a = this.items.get(t);
|
|
!a || a.type !== "skill" || await Be(this, a.name, Number(a.system.value ?? 0));
|
|
}
|
|
async rollHitLocation() {
|
|
await na(this);
|
|
}
|
|
async useWeaponById(t) {
|
|
const a = this.items.get(t);
|
|
!a || a.type !== "weapon" || await bt(this, a);
|
|
}
|
|
async reloadWeaponById(t) {
|
|
const a = this.items.get(t);
|
|
!a || a.type !== "weapon" || await ra(this, a);
|
|
}
|
|
async ensureBaseSkills() {
|
|
if (this.type !== "explorer" && this.type !== "creature") return 0;
|
|
const t = new Set(
|
|
this.items.filter((n) => n.type === "skill").map((n) => M(n.name))
|
|
), a = ke.filter((n) => !t.has(M(n.name))).map((n) => ({
|
|
name: n.name,
|
|
type: "skill",
|
|
img: we(n.name),
|
|
system: {
|
|
value: n.base,
|
|
category: n.category,
|
|
specialization: n.specialization ?? ""
|
|
}
|
|
}));
|
|
return a.length ? (await this.createEmbeddedDocuments("Item", a), a.length) : 0;
|
|
}
|
|
async rollCharacteristics() {
|
|
this.type === "explorer" && await xt(this);
|
|
}
|
|
async randomizeCharacter() {
|
|
this.type === "explorer" && await Pt(this);
|
|
}
|
|
async applySpeciesItem(t) {
|
|
var y, b, h;
|
|
if (this.type !== "explorer" && this.type !== "creature") return;
|
|
const a = String((t == null ? void 0 : t.name) || "").trim(), n = (t == null ? void 0 : t.system) ?? {}, o = (n == null ? void 0 : n.modifiers) ?? {}, s = ((y = this.system) == null ? void 0 : y.speciesApplied) ?? {}, i = (s == null ? void 0 : s.modifiers) ?? {}, r = ((b = this.system) == null ? void 0 : b.characteristics) ?? {}, l = ((h = this.system) == null ? void 0 : h.profile) ?? {}, c = {}, u = {
|
|
str: 0,
|
|
mas: 0,
|
|
con: 0,
|
|
int: 0,
|
|
pow: 0,
|
|
dex: 0,
|
|
app: 0,
|
|
edu: 0,
|
|
age: 0
|
|
};
|
|
for (const p of Ka) {
|
|
const w = S(i == null ? void 0 : i[p], 0), v = S(o == null ? void 0 : o[p], 0);
|
|
u[p] = v;
|
|
const I = S(r == null ? void 0 : r[p], p === "age" ? 25 : 10) - w;
|
|
c[`system.characteristics.${p}`] = Oe(I + v);
|
|
}
|
|
const m = S(i == null ? void 0 : i.age, 0), g = u.age, f = S(l == null ? void 0 : l.chronologicalAge, S(r == null ? void 0 : r.age, 25)), d = S(l == null ? void 0 : l.physiologicalAge, f);
|
|
c["system.profile.chronologicalAge"] = Oe(f - m + g), c["system.profile.physiologicalAge"] = Oe(d - m + g), a && (c["system.profile.species"] = a), n != null && n.gravity && (c["system.profile.gravity"] = String(n.gravity)), (n == null ? void 0 : n.homeworld) !== void 0 && (c["system.profile.homeworld"] = String(n.homeworld || "")), c["system.speciesApplied"] = {
|
|
name: a,
|
|
modifiers: u
|
|
}, await this.update(c);
|
|
}
|
|
async rollDerivedCheck(t) {
|
|
var s, i, r;
|
|
if (this.type !== "explorer" && this.type !== "creature") return;
|
|
const a = {
|
|
health: "Health Roll",
|
|
reasoning: "Reasoning Roll",
|
|
luck: "Luck Roll",
|
|
dodge: "Dodge Roll"
|
|
}, n = at(((s = this.system) == null ? void 0 : s.characteristics) ?? {}), o = S((r = (i = this.system) == null ? void 0 : i.derivedRolls) == null ? void 0 : r[t], n[t]);
|
|
await Be(this, a[t], o);
|
|
}
|
|
}
|
|
class Ja extends Item {
|
|
async rollSkill() {
|
|
var a;
|
|
if (this.type !== "skill") return;
|
|
const t = this.actor;
|
|
if (!t) {
|
|
(a = ui.notifications) == null || a.warn("Skill must be owned by an Actor to roll.");
|
|
return;
|
|
}
|
|
await Be(t, this.name, Number(this.system.value ?? 0));
|
|
}
|
|
async useWeapon() {
|
|
var a;
|
|
if (this.type !== "weapon") return;
|
|
const t = this.actor;
|
|
if (!t) {
|
|
(a = ui.notifications) == null || a.warn("Weapon must be owned by an Actor to use.");
|
|
return;
|
|
}
|
|
await bt(t, this);
|
|
}
|
|
}
|
|
const en = foundry.appv1.sheets.ActorSheet, qe = {
|
|
Agility: "agility",
|
|
Communication: "communication",
|
|
Knowledge: "knowledge",
|
|
Perception: "perception",
|
|
Technical: "technical"
|
|
}, tn = {
|
|
Agility: "STR + DEX",
|
|
Communication: "INT + APP",
|
|
Knowledge: "INT + EDU",
|
|
Perception: "POW + CON",
|
|
Technical: "DEX + INT"
|
|
}, an = /* @__PURE__ */ new Set([
|
|
M("Archaic Melee Weapon"),
|
|
M("Archaic Ranged Weapons")
|
|
]), nn = M("Hyperdrive");
|
|
function fe(e, t) {
|
|
return e.name.localeCompare(t.name);
|
|
}
|
|
function L(e, t = 0) {
|
|
const a = Number(e);
|
|
return Number.isFinite(a) ? a : t;
|
|
}
|
|
function F(e, t = 0) {
|
|
return Math.max(0, Math.floor(L(e, t)));
|
|
}
|
|
function Le(e) {
|
|
return {
|
|
education: F(e == null ? void 0 : e.education, 0),
|
|
pursuits: F(e == null ? void 0 : e.pursuits, 0),
|
|
special: F(e == null ? void 0 : e.special, 0)
|
|
};
|
|
}
|
|
function on(e) {
|
|
const t = String((e == null ? void 0 : e.name) || "").trim(), a = F(e == null ? void 0 : e.years, 0);
|
|
return !t || a <= 0 ? null : { name: t, years: a };
|
|
}
|
|
function It(e) {
|
|
const t = Array.isArray(e == null ? void 0 : e.pursuits) ? e.pursuits.map((o) => on(o)).filter((o) => !!o) : [], a = {};
|
|
if (e != null && e.allocations && typeof e.allocations == "object")
|
|
for (const [o, s] of Object.entries(e.allocations)) {
|
|
if (!o) continue;
|
|
const i = Le(s);
|
|
i.education + i.pursuits + i.special <= 0 || (a[o] = i);
|
|
}
|
|
const n = {};
|
|
if (e != null && e.baseBySkillId && typeof e.baseBySkillId == "object")
|
|
for (const [o, s] of Object.entries(e.baseBySkillId))
|
|
o && (n[o] = Math.max(0, F(s, 0)));
|
|
return {
|
|
pursuits: t,
|
|
allocations: a,
|
|
baseBySkillId: n
|
|
};
|
|
}
|
|
function sn(e) {
|
|
return !an.has(M(e));
|
|
}
|
|
function rn(e) {
|
|
return M(e) !== nn;
|
|
}
|
|
function ln(e) {
|
|
const t = new Map(
|
|
be.map((n) => [M(n.name), n])
|
|
), a = /* @__PURE__ */ new Set();
|
|
for (const n of e) {
|
|
const o = t.get(M(n.name));
|
|
if (o) {
|
|
for (const s of o.fixedSkills || [])
|
|
a.add(M(s));
|
|
for (const s of o.optionalSkillGroups || [])
|
|
for (const i of s.skills || [])
|
|
a.add(M(i));
|
|
}
|
|
}
|
|
return a;
|
|
}
|
|
function cn(e) {
|
|
const t = F(e == null ? void 0 : e.age, 0), a = F(e == null ? void 0 : e.edu, 0), n = F(e == null ? void 0 : e.int, 0), o = Math.max(0, (a - 10) * 20), s = Math.max(0, t - a - 6), i = s * 20, r = Math.max(0, n * 10);
|
|
return {
|
|
educationTotal: o,
|
|
pursuitsTotal: i,
|
|
specialTotal: r,
|
|
pursuitYearsTotal: s
|
|
};
|
|
}
|
|
function Q(e, t) {
|
|
var m, g, f, d, y, b, h;
|
|
const a = cn(((m = e.system) == null ? void 0 : m.characteristics) ?? {}), n = It(((g = e.system) == null ? void 0 : g.characterBuilder) ?? {}), o = be.map((p) => p.name).sort((p, w) => p.localeCompare(w)), s = ln(n.pursuits), i = /* @__PURE__ */ new Map();
|
|
let r = 0, l = 0, c = 0;
|
|
for (const p of t) {
|
|
const w = Le(n.allocations[p.id]), v = w.education + w.pursuits + w.special, x = n.baseBySkillId[p.id], I = (f = $e(p.name)) == null ? void 0 : f.base, T = Math.max(0, L((d = p.system) == null ? void 0 : d.value, 0) - v), R = Math.max(
|
|
0,
|
|
Number.isFinite(x) ? x : I ?? T
|
|
), D = qe[String(((y = p.system) == null ? void 0 : y.category) || "")] ?? "", N = D ? Math.max(0, F((h = (b = e.system) == null ? void 0 : b.skillRootMax) == null ? void 0 : h[D], 0)) : 99, C = Math.max(0, N - R), U = M(p.name), re = sn(p.name), Z = s.has(U), H = rn(p.name);
|
|
i.set(p.id, {
|
|
allocation: w,
|
|
totalAllocated: v,
|
|
baseValue: R,
|
|
rootCap: N,
|
|
maxAllocation: C,
|
|
allowsEducation: re,
|
|
allowsPursuits: Z,
|
|
allowsSpecial: H
|
|
}), r += w.education, l += w.pursuits, c += w.special;
|
|
}
|
|
const u = n.pursuits.reduce((p, w) => p + w.years, 0);
|
|
return {
|
|
state: n,
|
|
education: {
|
|
total: a.educationTotal,
|
|
spent: r,
|
|
remaining: a.educationTotal - r
|
|
},
|
|
pursuits: {
|
|
total: a.pursuitsTotal,
|
|
spent: l,
|
|
remaining: a.pursuitsTotal - l
|
|
},
|
|
special: {
|
|
total: a.specialTotal,
|
|
spent: c,
|
|
remaining: a.specialTotal - c
|
|
},
|
|
pursuitYears: {
|
|
total: a.pursuitYearsTotal,
|
|
spent: u,
|
|
remaining: a.pursuitYearsTotal - u
|
|
},
|
|
pursuitOptions: o,
|
|
pursuitAllowedSkills: s,
|
|
bySkillId: i
|
|
};
|
|
}
|
|
function un(e, t) {
|
|
var n, o, s, i, r, l, c;
|
|
if (e.type !== "armor" || !((n = e.system) != null && n.equipped) || (o = e.system) != null && o.stowed || !((i = (s = e.system) == null ? void 0 : s.coverage) != null && i[t])) return 0;
|
|
const a = L((l = (r = e.system) == null ? void 0 : r.perLocation) == null ? void 0 : l[t], 0);
|
|
return a > 0 ? a : L((c = e.system) == null ? void 0 : c.value, 0);
|
|
}
|
|
function mn(e, t) {
|
|
var o, s, i;
|
|
const a = L((i = (s = (o = e.system) == null ? void 0 : o.hitLocations) == null ? void 0 : s[t]) == null ? void 0 : i.armor, 0), n = e.items.filter((r) => r.type === "armor").reduce((r, l) => r + un(l, t), 0);
|
|
return a + n;
|
|
}
|
|
function nt(e, t, a) {
|
|
var r, l;
|
|
const n = oe(e).entries.find((c) => c.slug === t), o = ((l = (r = e.system) == null ? void 0 : r.hitLocations) == null ? void 0 : l[t]) ?? {}, s = o.hp ?? {}, i = String(o.label ?? "").trim();
|
|
return {
|
|
slug: t,
|
|
rollRange: a,
|
|
label: i || String((n == null ? void 0 : n.label) || t),
|
|
armor: L(o.armor, 0),
|
|
totalArmor: mn(e, t),
|
|
hp: {
|
|
value: L(s.value, 0),
|
|
max: L(s.max, 0)
|
|
}
|
|
};
|
|
}
|
|
function ot(e) {
|
|
return e[Math.floor(Math.random() * e.length)];
|
|
}
|
|
class dn extends en {
|
|
isToolsUnlocked() {
|
|
var t, a;
|
|
return !!((a = (t = this.actor) == null ? void 0 : t.system) != null && a.sheetToolsUnlocked);
|
|
}
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
classes: ["ringworld", "sheet", "actor", "explorer"],
|
|
width: 930,
|
|
height: 1460,
|
|
tabs: [{ navSelector: ".tabs", contentSelector: ".sheet-body", initial: "overview" }]
|
|
});
|
|
}
|
|
get template() {
|
|
return "systems/ringworld/templates/actor/explorer-sheet.hbs";
|
|
}
|
|
async getData(t) {
|
|
var H, le, ce, ue, me, de, ge, pe, J, _e, je, We, Ye, ze, Qe, Xe;
|
|
const a = await super.getData(t), n = this.actor;
|
|
(H = game.user) != null && H.isGM && n.isOwner && await ((le = n.ensureBaseSkills) == null ? void 0 : le.call(n));
|
|
const o = n.items.filter((k) => k.type === "skill").sort(fe), s = Q(n, o), i = n.items.filter((k) => k.type === "weapon").sort(fe), r = n.items.filter((k) => k.type === "armor").sort(fe), l = n.items.filter((k) => k.type === "gear").sort(fe), c = game.items.filter((k) => k.type === "species" && k.system.playable).map((k) => k.name).sort(), u = ea(), m = /* @__PURE__ */ new Map();
|
|
for (const k of o) {
|
|
const O = M(k.name);
|
|
m.has(O) || m.set(O, k);
|
|
}
|
|
const g = n.system.skillRootMax ?? {}, f = Jt.map((k) => {
|
|
const O = qe[k] ?? k.toLowerCase(), De = ke.filter((V) => V.category === k).map((V) => {
|
|
var Ze;
|
|
const B = m.get(M(V.name)), ee = B ? s.bySkillId.get(B.id) : null;
|
|
return {
|
|
name: V.name,
|
|
base: V.base,
|
|
category: V.category,
|
|
specialization: V.specialization ?? "",
|
|
itemId: (B == null ? void 0 : B.id) ?? null,
|
|
icon: (B == null ? void 0 : B.img) || we(V.name),
|
|
value: ((Ze = B == null ? void 0 : B.system) == null ? void 0 : Ze.value) ?? V.base,
|
|
missing: !B,
|
|
allocationTotal: (ee == null ? void 0 : ee.totalAllocated) ?? 0,
|
|
allocation: (ee == null ? void 0 : ee.allocation) ?? { education: 0, pursuits: 0, special: 0 }
|
|
};
|
|
});
|
|
return {
|
|
category: k,
|
|
rootKey: O,
|
|
rootFormula: tn[k] ?? "",
|
|
rootValue: L(g[O], 0),
|
|
rows: De
|
|
};
|
|
}), d = o.filter((k) => !u.has(M(k.name))).map((k) => {
|
|
const O = s.bySkillId.get(k.id);
|
|
return {
|
|
id: k.id,
|
|
name: k.name,
|
|
system: k.system,
|
|
img: Fe(k.name, k.img || X),
|
|
allocationTotal: (O == null ? void 0 : O.totalAllocated) ?? 0,
|
|
allocation: (O == null ? void 0 : O.allocation) ?? { education: 0, pursuits: 0, special: 0 }
|
|
};
|
|
}), y = f.reduce(
|
|
(k, O) => k + O.rows.filter((De) => De.missing).length,
|
|
0
|
|
), b = oe(n), h = new Map(b.entries.map((k) => [k.slug, `${k.min}-${k.max}`])), w = b.entries.map((k) => k.slug).map(
|
|
(k) => nt(n, k, h.get(k) ?? "")
|
|
), x = b.bodyOrder.map(
|
|
(k) => nt(n, k, h.get(k) ?? "")
|
|
), I = ((ce = n.system) == null ? void 0 : ce.encumbrance) ?? {}, T = L(I.rawLoadKg, 0), R = L(I.effectiveLoadKg, T), D = L(I.limitKg, L((me = (ue = n.system) == null ? void 0 : ue.characteristics) == null ? void 0 : me.str, 0)), N = L(I.actionRankingPenalty, 0), C = L((ge = (de = n.system) == null ? void 0 : de.derived) == null ? void 0 : ge.actionRanking, 0), U = Math.max(1, C + N), re = `${T.toFixed(1)}kg on-person | ${R.toFixed(1)}kg effective / ${D.toFixed(1)}kg limit | AR +${N} (stowed ignored)`, Z = `${L((J = (pe = n.system) == null ? void 0 : pe.derived) == null ? void 0 : J.currentMove, L((je = (_e = n.system) == null ? void 0 : _e.derived) == null ? void 0 : je.move, 3))}m / ${L((Ye = (We = n.system) == null ? void 0 : We.derived) == null ? void 0 : Ye.currentRun, L((Qe = (ze = n.system) == null ? void 0 : ze.derived) == null ? void 0 : Qe.run, 6))}m`;
|
|
return {
|
|
...a,
|
|
system: n.system,
|
|
skills: o,
|
|
skillsByCategory: f,
|
|
customSkills: d,
|
|
missingBaseSkillsCount: y,
|
|
weapons: i,
|
|
armors: r,
|
|
gear: l,
|
|
speciesOptions: c,
|
|
hitLocations: w,
|
|
bodyLocations: x,
|
|
hitLocationSchemaOptions: gt(),
|
|
currentHitLocationSchema: String(n.system.hitLocationSchema || "auto"),
|
|
activeHitLocationSchemaId: b.id,
|
|
activeHitLocationSchemaLabel: b.label,
|
|
sheetToolsUnlocked: !!((Xe = n.system) != null && Xe.sheetToolsUnlocked),
|
|
baseActionRanking: C,
|
|
effectiveActionRanking: U,
|
|
encumbranceSummary: re,
|
|
movementSummary: Z,
|
|
builder: {
|
|
education: s.education,
|
|
pursuits: s.pursuits,
|
|
special: s.special,
|
|
pursuitYears: s.pursuitYears,
|
|
pursuitsList: s.state.pursuits,
|
|
pursuitOptions: s.pursuitOptions
|
|
}
|
|
};
|
|
}
|
|
async updateBuilderState(t) {
|
|
await this.actor.update({
|
|
"system.characterBuilder": {
|
|
pursuits: t.pursuits,
|
|
allocations: t.allocations,
|
|
baseBySkillId: t.baseBySkillId
|
|
}
|
|
});
|
|
}
|
|
async syncSkillsFromBuilderState(t) {
|
|
var o, s, i, r, l, c;
|
|
const a = this.actor.items.filter((u) => u.type === "skill"), n = [];
|
|
for (const u of a) {
|
|
const m = Le(t.allocations[u.id]), g = m.education + m.pursuits + m.special, f = qe[String(((o = u.system) == null ? void 0 : o.category) || "")] ?? "", d = f ? Math.max(0, F((i = (s = this.actor.system) == null ? void 0 : s.skillRootMax) == null ? void 0 : i[f], 0)) : 99;
|
|
let y = t.baseBySkillId[u.id];
|
|
if (!Number.isFinite(y)) {
|
|
const h = (r = $e(u.name)) == null ? void 0 : r.base;
|
|
y = Math.max(0, h ?? L((l = u.system) == null ? void 0 : l.value, 0) - g), t.baseBySkillId[u.id] = y;
|
|
}
|
|
const b = Math.max(0, Math.min(99, Math.min(d, y + g)));
|
|
L((c = u.system) == null ? void 0 : c.value, 0) !== b && n.push({ _id: u.id, "system.value": b });
|
|
}
|
|
n.length && await this.actor.updateEmbeddedDocuments("Item", n);
|
|
}
|
|
async addOrUpdatePursuit(t, a) {
|
|
var c;
|
|
if (!this.isToolsUnlocked()) return;
|
|
const n = String(t || "").trim(), o = Math.max(0, Math.floor(Number(a) || 0));
|
|
if (!n || o <= 0) {
|
|
(c = ui.notifications) == null || c.warn("Choose a pursuit and set years above 0.");
|
|
return;
|
|
}
|
|
const s = this.actor.items.filter((u) => u.type === "skill"), i = Q(this.actor, s), r = foundry.utils.deepClone(i.state), l = r.pursuits.findIndex((u) => M(u.name) === M(n));
|
|
l >= 0 ? r.pursuits[l].years = o : r.pursuits.push({ name: n, years: o }), await this.updateBuilderState(r);
|
|
}
|
|
async removePursuit(t) {
|
|
if (!this.isToolsUnlocked()) return;
|
|
const a = this.actor.items.filter((s) => s.type === "skill"), n = Q(this.actor, a), o = foundry.utils.deepClone(n.state);
|
|
t < 0 || t >= o.pursuits.length || (o.pursuits.splice(t, 1), await this.updateBuilderState(o));
|
|
}
|
|
async resetBuilderAllocations() {
|
|
if (!this.isToolsUnlocked()) return;
|
|
const t = this.actor.items.filter((o) => o.type === "skill"), a = Q(this.actor, t), n = foundry.utils.deepClone(a.state);
|
|
n.allocations = {}, await this.updateBuilderState(n), await this.syncSkillsFromBuilderState(n);
|
|
}
|
|
async randomizeBuilderAllocations() {
|
|
var d, y, b, h;
|
|
if (!this.isToolsUnlocked()) return;
|
|
await ((y = (d = this.actor).ensureBaseSkills) == null ? void 0 : y.call(d));
|
|
const t = this.actor.items.filter((p) => p.type === "skill").sort(fe), a = Q(this.actor, t), n = foundry.utils.deepClone(a.state), o = Math.max(0, a.pursuitYears.total), s = be.map((p) => p.name);
|
|
if (n.pursuits = [], o > 0 && s.length > 0) {
|
|
const p = Math.min(
|
|
s.length,
|
|
Math.max(1, Math.min(3, Math.floor(o / 2) || 1))
|
|
), w = [...s];
|
|
for (let x = 0; x < p && w.length; x += 1) {
|
|
const I = Math.floor(Math.random() * w.length);
|
|
n.pursuits.push({ name: w[I], years: 1 }), w.splice(I, 1);
|
|
}
|
|
let v = o - n.pursuits.length;
|
|
for (; v > 0 && n.pursuits.length; ) {
|
|
const x = ot(n.pursuits);
|
|
x.years += 1, v -= 1;
|
|
}
|
|
}
|
|
const i = It(n);
|
|
i.allocations = {};
|
|
const r = Q(
|
|
{ ...this.actor, system: { ...this.actor.system, characterBuilder: i } },
|
|
t
|
|
);
|
|
for (const p of t) {
|
|
const w = r.bySkillId.get(p.id);
|
|
w && (i.baseBySkillId[p.id] = w.baseValue, i.allocations[p.id] = { education: 0, pursuits: 0, special: 0 });
|
|
}
|
|
const l = /* @__PURE__ */ new Map();
|
|
for (const p of t)
|
|
l.set(p.id, 0);
|
|
const c = (p) => {
|
|
const w = r.bySkillId.get(p);
|
|
return w ? Math.max(0, w.maxAllocation) : 0;
|
|
}, u = (p, w, v) => {
|
|
let x = Math.max(0, p), I = 0;
|
|
for (; x > 0 && I < 3e5; ) {
|
|
I += 1;
|
|
const T = t.filter((N) => {
|
|
const C = r.bySkillId.get(N.id);
|
|
return !C || !v(N, C) ? !1 : (l.get(N.id) ?? 0) < c(N.id);
|
|
});
|
|
if (!T.length) break;
|
|
const R = ot(T), D = i.allocations[R.id] ?? { education: 0, pursuits: 0, special: 0 };
|
|
D[w] += 1, i.allocations[R.id] = D, l.set(R.id, (l.get(R.id) ?? 0) + 1), x -= 1;
|
|
}
|
|
return x;
|
|
}, m = u(
|
|
Math.max(0, r.education.total),
|
|
"education",
|
|
(p, w) => w.allowsEducation
|
|
), g = u(
|
|
Math.max(0, r.pursuits.total),
|
|
"pursuits",
|
|
(p, w) => w.allowsPursuits
|
|
), f = u(
|
|
Math.max(0, r.special.total),
|
|
"special",
|
|
(p, w) => w.allowsSpecial
|
|
);
|
|
for (const p of t) {
|
|
const w = Le(i.allocations[p.id]);
|
|
w.education + w.pursuits + w.special <= 0 && delete i.allocations[p.id];
|
|
}
|
|
await this.updateBuilderState(i), await this.syncSkillsFromBuilderState(i), m > 0 || g > 0 || f > 0 ? (b = ui.notifications) == null || b.info(
|
|
`Random build complete. Unspent points: EDU ${m}, Pursuits ${g}, Special ${f}.`
|
|
) : (h = ui.notifications) == null || h.info("Random build complete. All available points allocated.");
|
|
}
|
|
async promptSkillAllocation(t) {
|
|
var y, b, h;
|
|
if (!this.isToolsUnlocked()) return;
|
|
const a = this.actor.items.get(t);
|
|
if (!a || a.type !== "skill") return;
|
|
const n = this.actor.items.filter((p) => p.type === "skill"), o = Q(this.actor, n), s = o.bySkillId.get(t);
|
|
if (!s) return;
|
|
const i = s.allocation, r = Math.max(0, o.education.remaining + i.education), l = Math.max(0, o.pursuits.remaining + i.pursuits), c = Math.max(0, o.special.remaining + i.special), u = Math.max(0, s.maxAllocation), m = `
|
|
<form class="rw-builder-alloc-form">
|
|
<header class="rw-builder-alloc-head">
|
|
<img src="${a.img || X}" alt="" />
|
|
<div>
|
|
<h3>${a.name}</h3>
|
|
<p>Base ${s.baseValue}% | Root cap ${s.rootCap}% | Current ${L((y = a.system) == null ? void 0 : y.value, 0)}%</p>
|
|
</div>
|
|
</header>
|
|
|
|
<label>Education ${s.allowsEducation ? "" : "(Not eligible)"}
|
|
<input name="education" type="number" min="0" max="${r}" value="${i.education}" ${s.allowsEducation ? "" : "disabled"} />
|
|
</label>
|
|
|
|
<label>Pursuits ${s.allowsPursuits ? "" : "(Not in selected pursuits)"}
|
|
<input name="pursuits" type="number" min="0" max="${l}" value="${i.pursuits}" ${s.allowsPursuits ? "" : "disabled"} />
|
|
</label>
|
|
|
|
<label>Special Interests ${s.allowsSpecial ? "" : "(Hyperdrive excluded)"}
|
|
<input name="special" type="number" min="0" max="${c}" value="${i.special}" ${s.allowsSpecial ? "" : "disabled"} />
|
|
</label>
|
|
|
|
<p class="hint">Max additional from root cap: ${u}. Pools: EDU ${o.education.remaining}, Pursuits ${o.pursuits.remaining}, Special ${o.special.remaining}.</p>
|
|
</form>
|
|
`, g = await new Promise((p) => {
|
|
new Dialog({
|
|
title: `${a.name} - Allocate Character Points`,
|
|
content: m,
|
|
buttons: {
|
|
save: {
|
|
label: "Apply",
|
|
callback: (w) => {
|
|
const v = w.find("form")[0];
|
|
if (!v) {
|
|
p(null);
|
|
return;
|
|
}
|
|
const x = new FormData(v);
|
|
p({
|
|
education: s.allowsEducation ? F(x.get("education"), i.education) : i.education,
|
|
pursuits: s.allowsPursuits ? F(x.get("pursuits"), i.pursuits) : i.pursuits,
|
|
special: s.allowsSpecial ? F(x.get("special"), i.special) : i.special
|
|
});
|
|
}
|
|
},
|
|
cancel: {
|
|
label: "Cancel",
|
|
callback: () => p(null)
|
|
}
|
|
},
|
|
default: "save",
|
|
close: () => p(null)
|
|
}).render(!0);
|
|
});
|
|
if (!g) return;
|
|
const f = g.education + g.pursuits + g.special;
|
|
if (f > u) {
|
|
(b = ui.notifications) == null || b.warn(`Allocation exceeds this skill's root-cap allowance (${u}).`);
|
|
return;
|
|
}
|
|
if (g.education > r || g.pursuits > l || g.special > c) {
|
|
(h = ui.notifications) == null || h.warn("Allocation exceeds remaining pool points.");
|
|
return;
|
|
}
|
|
const d = foundry.utils.deepClone(o.state);
|
|
d.baseBySkillId[a.id] = s.baseValue, f <= 0 ? delete d.allocations[a.id] : d.allocations[a.id] = g, await this.updateBuilderState(d), await this.syncSkillsFromBuilderState(d);
|
|
}
|
|
activateListeners(t) {
|
|
super.activateListeners(t), t.find(".toggle-sheet-lock").on("click", async (a) => {
|
|
var o;
|
|
a.preventDefault();
|
|
const n = !this.isToolsUnlocked();
|
|
await this.actor.update({ "system.sheetToolsUnlocked": n }), (o = ui.notifications) == null || o.info(n ? "Sheet tools unlocked." : "Sheet tools locked.");
|
|
}), t.find(".roll-characteristic").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget, o = String(n.dataset.char || "").trim().toLowerCase(), s = String(n.dataset.label || o.toUpperCase());
|
|
o && await wt(this.actor, o, s);
|
|
}), t.find(".item-create").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = String(a.currentTarget.dataset.type || "gear"), o = { name: `New ${n}`, type: n };
|
|
n === "skill" && (o.img = X, o.system = { value: 0, category: "Technical", specialization: "" }), await this.actor.createEmbeddedDocuments("Item", [o]);
|
|
}), t.find(".item-edit").on("click", (a) => {
|
|
var s;
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item"), o = n ? this.actor.items.get(n.dataset.itemId ?? "") : null;
|
|
(s = o == null ? void 0 : o.sheet) == null || s.render(!0);
|
|
}), t.find(".item-delete").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item");
|
|
n != null && n.dataset.itemId && await this.actor.deleteEmbeddedDocuments("Item", [n.dataset.itemId]);
|
|
}), t.find(".item-roll-skill").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item");
|
|
n != null && n.dataset.itemId && await this.actor.rollSkillById(n.dataset.itemId);
|
|
}), t.find(".roll-derived").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = String(a.currentTarget.dataset.check || "");
|
|
n && await this.actor.rollDerivedCheck(n);
|
|
}), t.find(".item-use-weapon").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item");
|
|
n != null && n.dataset.itemId && await this.actor.useWeaponById(n.dataset.itemId);
|
|
}), t.find(".item-reload-weapon").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item");
|
|
n != null && n.dataset.itemId && await this.actor.reloadWeaponById(n.dataset.itemId);
|
|
}), t.find(".item-toggle-equipped").on("click", async (a) => {
|
|
var r;
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item");
|
|
if (!(n != null && n.dataset.itemId)) return;
|
|
const o = this.actor.items.get(n.dataset.itemId);
|
|
if (!o) return;
|
|
const s = !((r = o.system) != null && r.equipped), i = {
|
|
_id: o.id,
|
|
"system.equipped": s
|
|
};
|
|
s && (i["system.stowed"] = !1), await this.actor.updateEmbeddedDocuments("Item", [i]);
|
|
}), t.find(".item-toggle-stowed").on("click", async (a) => {
|
|
var r;
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item");
|
|
if (!(n != null && n.dataset.itemId)) return;
|
|
const o = this.actor.items.get(n.dataset.itemId);
|
|
if (!o) return;
|
|
const s = !((r = o.system) != null && r.stowed), i = {
|
|
_id: o.id,
|
|
"system.stowed": s
|
|
};
|
|
s && (i["system.equipped"] = !1), await this.actor.updateEmbeddedDocuments("Item", [i]);
|
|
}), t.find(".roll-hit-location").on("click", async (a) => {
|
|
a.preventDefault(), await this.actor.rollHitLocation();
|
|
}), t.find(".reset-conditions").on("click", async (a) => {
|
|
a.preventDefault(), await this.actor.update({
|
|
"system.conditions.prone": !1,
|
|
"system.conditions.unconscious": !1,
|
|
"system.conditions.dead": !1,
|
|
"system.conditions.dying": !1,
|
|
"system.conditions.unusableLimbs.rArm": !1,
|
|
"system.conditions.unusableLimbs.lArm": !1,
|
|
"system.conditions.unusableLimbs.rLeg": !1,
|
|
"system.conditions.unusableLimbs.lLeg": !1
|
|
});
|
|
}), t.find(".populate-base-skills").on("click", async (a) => {
|
|
var o, s;
|
|
a.preventDefault();
|
|
const n = await this.actor.ensureBaseSkills();
|
|
n > 0 ? (o = ui.notifications) == null || o.info(`Added ${n} Ringworld base skills.`) : (s = ui.notifications) == null || s.info("All Ringworld base skills are already present.");
|
|
}), t.find(".add-base-skill").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget, o = String(n.dataset.skillName || ""), s = String(n.dataset.skillCategory || ""), i = Number(n.dataset.skillBase || 0), r = String(n.dataset.skillSpecialization || "");
|
|
o && await this.actor.createEmbeddedDocuments("Item", [{
|
|
name: o,
|
|
type: "skill",
|
|
img: we(o),
|
|
system: { value: i, category: s, specialization: r }
|
|
}]);
|
|
}), t.find(".builder-add-pursuit").on("click", async (a) => {
|
|
var i, r;
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".rw-builder-pursuits");
|
|
if (!n) return;
|
|
const o = String(((i = n.querySelector(".builder-new-pursuit")) == null ? void 0 : i.value) || ""), s = Number(((r = n.querySelector(".builder-new-years")) == null ? void 0 : r.value) || 0);
|
|
await this.addOrUpdatePursuit(o, s);
|
|
}), t.find(".builder-update-pursuit").on("click", async (a) => {
|
|
var i, r;
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".builder-pursuit-row");
|
|
if (!n) return;
|
|
const o = String(((i = n.querySelector(".builder-pursuit-name")) == null ? void 0 : i.value) || ""), s = Number(((r = n.querySelector(".builder-pursuit-years")) == null ? void 0 : r.value) || 0);
|
|
await this.addOrUpdatePursuit(o, s);
|
|
}), t.find(".builder-remove-pursuit").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = Number(a.currentTarget.dataset.index ?? -1);
|
|
!Number.isFinite(n) || n < 0 || await this.removePursuit(n);
|
|
}), t.find(".builder-reset-points").on("click", async (a) => {
|
|
var n;
|
|
a.preventDefault(), await this.resetBuilderAllocations(), (n = ui.notifications) == null || n.info("Character-builder allocations reset.");
|
|
}), t.find(".roll-characteristics").on("click", async (a) => {
|
|
var n, o, s;
|
|
a.preventDefault(), this.isToolsUnlocked() && (await ((o = (n = this.actor).rollCharacteristics) == null ? void 0 : o.call(n)), (s = ui.notifications) == null || s.info("Characteristics rolled."));
|
|
}), t.find(".builder-auto-allocate").on("click", async (a) => {
|
|
var n, o;
|
|
a.preventDefault(), this.isToolsUnlocked() && (await ((o = (n = this.actor).ensureBaseSkills) == null ? void 0 : o.call(n)), await this.randomizeBuilderAllocations());
|
|
}), t.find(".builder-skill-allocate").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item"), o = String((n == null ? void 0 : n.dataset.itemId) || "");
|
|
o && await this.promptSkillAllocation(o);
|
|
});
|
|
}
|
|
async _onDropItemCreate(t) {
|
|
var i, r, l;
|
|
const a = Array.isArray(t) ? t : [t], n = a.filter((c) => (c == null ? void 0 : c.type) === "species"), o = a.filter((c) => (c == null ? void 0 : c.type) !== "species");
|
|
for (const c of n)
|
|
await ((r = (i = this.actor).applySpeciesItem) == null ? void 0 : r.call(i, c));
|
|
if (n.length > 0) {
|
|
const c = n.map((u) => String((u == null ? void 0 : u.name) || "Species")).join(", ");
|
|
(l = ui.notifications) == null || l.info(`Applied species to actor: ${c}`);
|
|
}
|
|
if (o.length === 0) return [];
|
|
const s = o.map((c) => (c == null ? void 0 : c.type) !== "skill" ? c : {
|
|
...c,
|
|
img: Fe(String((c == null ? void 0 : c.name) || "Skill"), String((c == null ? void 0 : c.img) || ""))
|
|
});
|
|
return super._onDropItemCreate(Array.isArray(t) ? s : s[0]);
|
|
}
|
|
}
|
|
const gn = foundry.appv1.sheets.ActorSheet;
|
|
function Ge(e, t = 0) {
|
|
const a = Number(e);
|
|
return Number.isFinite(a) ? a : t;
|
|
}
|
|
function pn(e, t) {
|
|
var n, o;
|
|
if (e.type !== "armor" || !e.system.equipped || e.system.stowed || !((n = e.system.coverage) != null && n[t])) return 0;
|
|
const a = Ge((o = e.system.perLocation) == null ? void 0 : o[t]);
|
|
return a > 0 ? a : Ge(e.system.value);
|
|
}
|
|
class fn extends gn {
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
classes: ["ringworld", "sheet", "actor", "creature"],
|
|
width: 860,
|
|
height: 780
|
|
});
|
|
}
|
|
get template() {
|
|
return "systems/ringworld/templates/actor/creature-sheet.hbs";
|
|
}
|
|
async getData(t) {
|
|
const a = await super.getData(t), n = this.actor, o = oe(n);
|
|
return {
|
|
...a,
|
|
system: n.system,
|
|
skills: n.items.filter((s) => s.type === "skill").sort((s, i) => s.name.localeCompare(i.name)),
|
|
weapons: n.items.filter((s) => s.type === "weapon"),
|
|
armors: n.items.filter((s) => s.type === "armor"),
|
|
hitSchemaId: o.id,
|
|
hitSchemas: gt().map((s) => ({ id: s.id, label: s.label })),
|
|
hitLocations: o.entries.map((s) => {
|
|
var c;
|
|
const i = ((c = n.system.hitLocations) == null ? void 0 : c[s.slug]) ?? {}, r = Ge(i.armor), l = n.items.filter((u) => u.type === "armor").reduce((u, m) => u + pn(m, s.slug), 0);
|
|
return {
|
|
slug: s.slug,
|
|
rollRange: `${s.min}-${s.max}`,
|
|
label: s.label,
|
|
armor: r,
|
|
apTotal: r + l,
|
|
hp: i.hp ?? { value: 1, max: 1 }
|
|
};
|
|
})
|
|
};
|
|
}
|
|
activateListeners(t) {
|
|
super.activateListeners(t), t.find(".item-create").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = String(a.currentTarget.dataset.type || "skill");
|
|
await this.actor.createEmbeddedDocuments("Item", [{ name: `New ${n}`, type: n }]);
|
|
}), t.find(".item-edit").on("click", (a) => {
|
|
var s;
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item"), o = n ? this.actor.items.get(n.dataset.itemId ?? "") : null;
|
|
(s = o == null ? void 0 : o.sheet) == null || s.render(!0);
|
|
}), t.find(".item-delete").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item");
|
|
n != null && n.dataset.itemId && await this.actor.deleteEmbeddedDocuments("Item", [n.dataset.itemId]);
|
|
}), t.find(".item-roll-skill").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item");
|
|
n != null && n.dataset.itemId && await this.actor.rollSkillById(n.dataset.itemId);
|
|
}), t.find(".item-use-weapon").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item");
|
|
n != null && n.dataset.itemId && await this.actor.useWeaponById(n.dataset.itemId);
|
|
}), t.find(".item-reload-weapon").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget.closest(".item");
|
|
n != null && n.dataset.itemId && await this.actor.reloadWeaponById(n.dataset.itemId);
|
|
}), t.find(".roll-hit-location").on("click", async (a) => {
|
|
a.preventDefault(), await this.actor.rollHitLocation();
|
|
}), t.find(".roll-characteristic").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = a.currentTarget, o = String(n.dataset.char || "").trim().toLowerCase(), s = String(n.dataset.label || o.toUpperCase());
|
|
o && await wt(this.actor, o, s);
|
|
}), t.find(".roll-derived").on("click", async (a) => {
|
|
a.preventDefault();
|
|
const n = String(a.currentTarget.dataset.check || "");
|
|
n && await this.actor.rollDerivedCheck(n);
|
|
}), t.find(".reset-conditions").on("click", async (a) => {
|
|
a.preventDefault(), await this.actor.update({
|
|
"system.conditions.prone": !1,
|
|
"system.conditions.unconscious": !1,
|
|
"system.conditions.dead": !1,
|
|
"system.conditions.dying": !1,
|
|
"system.conditions.unusableLimbs.rArm": !1,
|
|
"system.conditions.unusableLimbs.lArm": !1,
|
|
"system.conditions.unusableLimbs.rLeg": !1,
|
|
"system.conditions.unusableLimbs.lLeg": !1
|
|
});
|
|
}), t.find(".rw-portrait-img").on("click", (a) => {
|
|
var s, i, r;
|
|
a.preventDefault();
|
|
const n = ((r = (i = (s = foundry.applications) == null ? void 0 : s.apps) == null ? void 0 : i.FilePicker) == null ? void 0 : r.implementation) ?? globalThis.FilePicker;
|
|
new n({
|
|
type: "image",
|
|
current: this.actor.img,
|
|
callback: async (l) => {
|
|
await this.actor.update({ img: l });
|
|
}
|
|
}).render(!0);
|
|
});
|
|
}
|
|
}
|
|
const hn = foundry.appv1.sheets.ActorSheet;
|
|
class yn extends hn {
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
classes: ["ringworld", "sheet", "actor", "vehicle"],
|
|
width: 680,
|
|
height: 560
|
|
});
|
|
}
|
|
get template() {
|
|
return "systems/ringworld/templates/actor/vehicle-sheet.hbs";
|
|
}
|
|
async getData(t) {
|
|
return {
|
|
...await super.getData(t),
|
|
system: this.actor.system
|
|
};
|
|
}
|
|
}
|
|
const wn = foundry.appv1.sheets.ItemSheet;
|
|
class bn extends wn {
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
classes: ["ringworld", "sheet", "item", "skill"],
|
|
width: 520,
|
|
height: 420
|
|
});
|
|
}
|
|
get template() {
|
|
return "systems/ringworld/templates/item/skill-sheet.hbs";
|
|
}
|
|
async getData(t) {
|
|
return {
|
|
...await super.getData(t),
|
|
system: this.item.system
|
|
};
|
|
}
|
|
activateListeners(t) {
|
|
super.activateListeners(t), t.find(".roll-skill").on("click", async (a) => {
|
|
a.preventDefault(), await this.item.rollSkill();
|
|
});
|
|
}
|
|
}
|
|
const kn = foundry.appv1.sheets.ItemSheet;
|
|
class vn extends kn {
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
classes: ["ringworld", "sheet", "item", "weapon"],
|
|
width: 620,
|
|
height: 620
|
|
});
|
|
}
|
|
get template() {
|
|
return "systems/ringworld/templates/item/weapon-sheet.hbs";
|
|
}
|
|
async getData(t) {
|
|
const a = await super.getData(t), n = this.item.actor;
|
|
return {
|
|
...a,
|
|
system: this.item.system,
|
|
skills: n ? n.items.filter((o) => o.type === "skill") : [],
|
|
powerPacks: n ? n.items.filter((o) => o.type === "gear" && o.system.isPowerPack) : []
|
|
};
|
|
}
|
|
activateListeners(t) {
|
|
super.activateListeners(t), t.find(".use-weapon").on("click", async (a) => {
|
|
a.preventDefault(), await this.item.useWeapon();
|
|
});
|
|
}
|
|
}
|
|
const Sn = foundry.appv1.sheets.ItemSheet;
|
|
class An extends Sn {
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
classes: ["ringworld", "sheet", "item", "armor"],
|
|
width: 620,
|
|
height: 560
|
|
});
|
|
}
|
|
get template() {
|
|
return "systems/ringworld/templates/item/armor-sheet.hbs";
|
|
}
|
|
async getData(t) {
|
|
return {
|
|
...await super.getData(t),
|
|
system: this.item.system
|
|
};
|
|
}
|
|
}
|
|
const xn = foundry.appv1.sheets.ItemSheet;
|
|
class Pn extends xn {
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
classes: ["ringworld", "sheet", "item", "gear"],
|
|
width: 620,
|
|
height: 520
|
|
});
|
|
}
|
|
get template() {
|
|
return "systems/ringworld/templates/item/gear-sheet.hbs";
|
|
}
|
|
async getData(t) {
|
|
return {
|
|
...await super.getData(t),
|
|
system: this.item.system
|
|
};
|
|
}
|
|
}
|
|
const Ln = foundry.appv1.sheets.ItemSheet;
|
|
class In extends Ln {
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
classes: ["ringworld", "sheet", "item", "species"],
|
|
width: 620,
|
|
height: 560
|
|
});
|
|
}
|
|
get template() {
|
|
return "systems/ringworld/templates/item/species-sheet.hbs";
|
|
}
|
|
async getData(t) {
|
|
return {
|
|
...await super.getData(t),
|
|
system: this.item.system
|
|
};
|
|
}
|
|
}
|
|
const Mn = foundry.appv1.sheets.ItemSheet;
|
|
function st(e) {
|
|
return Array.isArray(e) ? e.map((t) => String(t)) : [];
|
|
}
|
|
class Cn extends Mn {
|
|
static get defaultOptions() {
|
|
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
classes: ["ringworld", "sheet", "item", "pursuit"],
|
|
width: 680,
|
|
height: 620
|
|
});
|
|
}
|
|
get template() {
|
|
return "systems/ringworld/templates/item/pursuit-sheet.hbs";
|
|
}
|
|
async getData(t) {
|
|
const a = await super.getData(t), n = this.item.system ?? {}, o = st(n.fixedSkills), s = Array.isArray(n.optionalSkillGroups) ? n.optionalSkillGroups.map((i) => ({
|
|
choose: Number((i == null ? void 0 : i.choose) ?? 1),
|
|
skills: st(i == null ? void 0 : i.skills)
|
|
})) : [];
|
|
return {
|
|
...a,
|
|
system: n,
|
|
fixedSkills: o,
|
|
optionalGroups: s
|
|
};
|
|
}
|
|
}
|
|
const Mt = "impulseState", Ve = `${P}.impulseHudPos`, Rn = [
|
|
{ value: "draw", label: "Draw / Stow Weapon" },
|
|
{ value: "aim", label: "Aim Ranged Weapon" },
|
|
{ value: "melee", label: "Melee Attack" },
|
|
{ value: "stand", label: "Stand / Recover" },
|
|
{ value: "perception", label: "Perception Skill" },
|
|
{ value: "unarmed", label: "Unarmed Combat" }
|
|
], $n = [
|
|
{ value: "fire", label: "Fire Ranged Weapon" },
|
|
{ value: "fall", label: "Fall" },
|
|
{ value: "rise", label: "Rise (Kneeling to Stand/Run)" },
|
|
{ value: "minorCustom", label: "Other Minor Action" }
|
|
];
|
|
function q(e, t = 0) {
|
|
const a = Number(e);
|
|
return Number.isFinite(a) ? a : t;
|
|
}
|
|
function W(e) {
|
|
return String(e ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
}
|
|
function Ke(e) {
|
|
return e <= 4 ? 7 : e <= 8 ? 6 : e <= 12 ? 5 : e <= 16 ? 4 : e <= 20 ? 3 : e <= 24 ? 2 : 1;
|
|
}
|
|
function Te(e) {
|
|
switch (e) {
|
|
case "draw":
|
|
return "Draw / Stow";
|
|
case "aim":
|
|
return "Aim";
|
|
case "melee":
|
|
return "Melee Attack";
|
|
case "stand":
|
|
return "Stand / Recover";
|
|
case "perception":
|
|
return "Perception";
|
|
case "unarmed":
|
|
return "Unarmed Combat";
|
|
case "fire":
|
|
return "Fire Ranged Weapon";
|
|
case "fall":
|
|
return "Fall";
|
|
case "rise":
|
|
return "Rise";
|
|
case "minorCustom":
|
|
return "Minor Action";
|
|
default:
|
|
return String(e);
|
|
}
|
|
}
|
|
function Y(e) {
|
|
var t, a, n;
|
|
return Math.max(1, q((n = (a = (t = e == null ? void 0 : e.actor) == null ? void 0 : t.system) == null ? void 0 : a.characteristics) == null ? void 0 : n.dex, 10));
|
|
}
|
|
function Ct(e) {
|
|
var a, n;
|
|
const t = q((n = (a = e == null ? void 0 : e.flags) == null ? void 0 : a[P]) == null ? void 0 : n.actionRanking, 0);
|
|
return t > 0 ? t : Ke(Y(e));
|
|
}
|
|
function Rt(e) {
|
|
var a, n, o;
|
|
const t = q((o = (n = (a = e == null ? void 0 : e.actor) == null ? void 0 : a.system) == null ? void 0 : n.encumbrance) == null ? void 0 : o.actionRankingPenalty, 0);
|
|
return Math.max(0, Math.trunc(t));
|
|
}
|
|
async function $t(e) {
|
|
var n, o;
|
|
const t = Ke(Y(e));
|
|
return q((o = (n = e == null ? void 0 : e.flags) == null ? void 0 : n[P]) == null ? void 0 : o.actionRanking, 0) !== t && await e.update({ [`flags.${P}.actionRanking`]: t }), t;
|
|
}
|
|
function se(e) {
|
|
const t = foundry.utils.deepClone(e.getFlag(P, Mt) ?? {}), a = Array.isArray(t.queue) ? t.queue : [];
|
|
return {
|
|
currentImpulse: Math.max(1, q(t.currentImpulse, 1)),
|
|
queue: a
|
|
};
|
|
}
|
|
async function ie(e, t) {
|
|
await e.setFlag(P, Mt, {
|
|
currentImpulse: Math.max(1, q(t.currentImpulse, 1)),
|
|
queue: Array.isArray(t.queue) ? t.queue : []
|
|
});
|
|
}
|
|
function te(e, t) {
|
|
return [...e].sort((a, n) => {
|
|
if (a.completeImpulse !== n.completeImpulse) return a.completeImpulse - n.completeImpulse;
|
|
const o = Y(t.combatants.get(a.combatantId));
|
|
return Y(t.combatants.get(n.combatantId)) - o;
|
|
});
|
|
}
|
|
function Se(e, t) {
|
|
const a = new Set(t.combatants.map((n) => n.id));
|
|
return e.filter((n) => a.has(n.combatantId));
|
|
}
|
|
function Tn() {
|
|
try {
|
|
const e = localStorage.getItem(Ve);
|
|
if (!e) return null;
|
|
const t = JSON.parse(e), a = q(t == null ? void 0 : t.left, NaN), n = q(t == null ? void 0 : t.top, NaN);
|
|
return !Number.isFinite(a) || !Number.isFinite(n) ? null : { left: a, top: n };
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
function Dn(e) {
|
|
try {
|
|
localStorage.setItem(
|
|
Ve,
|
|
JSON.stringify({ left: Math.round(e.left), top: Math.round(e.top) })
|
|
);
|
|
} catch {
|
|
}
|
|
}
|
|
function Nn() {
|
|
try {
|
|
localStorage.removeItem(Ve);
|
|
} catch {
|
|
}
|
|
}
|
|
function Hn(e, t) {
|
|
const n = t.offsetWidth || 300, o = t.offsetHeight || 220, s = Math.max(8, window.innerWidth - n - 8), i = Math.max(8, window.innerHeight - o - 8);
|
|
return {
|
|
left: Math.min(Math.max(8, e.left), s),
|
|
top: Math.min(Math.max(8, e.top), i)
|
|
};
|
|
}
|
|
function it(e, t) {
|
|
const a = e.get(0);
|
|
if (!a) return;
|
|
const n = Hn(t, a);
|
|
e.css({
|
|
left: `${n.left}px`,
|
|
top: `${n.top}px`,
|
|
right: "auto",
|
|
bottom: "auto"
|
|
});
|
|
}
|
|
function On(e) {
|
|
const t = e.get(0);
|
|
if (!t) return;
|
|
const a = e.find(".rw-imp-mini-head");
|
|
if (!a.length) return;
|
|
const n = Tn();
|
|
n && it(e, n);
|
|
let o = !1, s = 0, i = 0, r = 0, l = 0;
|
|
a.on("mousedown", (c) => {
|
|
var g;
|
|
const u = c;
|
|
if (u.button !== 0 || (g = u.target) != null && g.closest("button")) return;
|
|
u.preventDefault();
|
|
const m = t.getBoundingClientRect();
|
|
s = u.clientX, i = u.clientY, r = m.left, l = m.top, o = !0, e.addClass("dragging"), e.css({ left: `${r}px`, top: `${l}px`, right: "auto", bottom: "auto" }), $(document).on("mousemove.ringworldImpulseHudDrag", (f) => {
|
|
if (!o) return;
|
|
const d = f, y = d.clientX - s, b = d.clientY - i;
|
|
it(e, { left: r + y, top: l + b });
|
|
}), $(document).one("mouseup.ringworldImpulseHudDrag", () => {
|
|
o = !1, e.removeClass("dragging"), $(document).off("mousemove.ringworldImpulseHudDrag");
|
|
const f = t.getBoundingClientRect();
|
|
Dn({ left: f.left, top: f.top });
|
|
});
|
|
}), a.on("dblclick", () => {
|
|
Nn(), e.css({ left: "", top: "", right: "", bottom: "" });
|
|
});
|
|
}
|
|
function Tt(e, t) {
|
|
return te(Se(t, e), e).map((a) => {
|
|
const n = e.combatants.get(a.combatantId);
|
|
return {
|
|
combatantId: a.combatantId,
|
|
name: String((n == null ? void 0 : n.name) || "Unknown"),
|
|
label: Te(a.kind),
|
|
completeImpulse: a.completeImpulse,
|
|
dex: Y(n)
|
|
};
|
|
});
|
|
}
|
|
function Dt(e, t = 8) {
|
|
return e.length ? e.slice(0, t).map((a) => `
|
|
<div class="rw-imp-mini-row" data-combatant-id="${W(a.combatantId)}">
|
|
<span class="rw-imp-mini-name">${W(a.name)}</span>
|
|
<span class="rw-imp-mini-action">${W(a.label)} @ ${a.completeImpulse}</span>
|
|
</div>
|
|
`).join("") : '<div class="rw-imp-mini-empty">No actions queued.</div>';
|
|
}
|
|
function Nt(e, t) {
|
|
var n;
|
|
const a = !((n = game.user) != null && n.isGM);
|
|
e.find(".rw-mini-next").prop("disabled", a).on("click", async (o) => {
|
|
o.preventDefault(), await Ie(t, "next");
|
|
}), e.find(".rw-mini-step").prop("disabled", a).on("click", async (o) => {
|
|
o.preventDefault(), await Ie(t, "step");
|
|
}), e.find(".rw-mini-reset").prop("disabled", a).on("click", async (o) => {
|
|
o.preventDefault(), await Ht(t);
|
|
});
|
|
}
|
|
function En(e) {
|
|
var s;
|
|
const t = (s = ui == null ? void 0 : ui.chat) == null ? void 0 : s.element;
|
|
if (!(t != null && t.length) || (t.find(".ringworld-impulse-chat-marker").remove(), !e)) return;
|
|
const a = se(e), n = Tt(e, a.queue), o = $(`
|
|
<section class="ringworld-impulse-chat-marker">
|
|
<div class="rw-imp-mini-head">
|
|
<strong>Impulse ${a.currentImpulse}</strong>
|
|
<span>Queued ${n.length}</span>
|
|
</div>
|
|
<div class="rw-imp-mini-list">${Dt(n, 5)}</div>
|
|
<div class="rw-imp-mini-controls">
|
|
<button type="button" class="rw-mini-next">Next</button>
|
|
<button type="button" class="rw-mini-step">+1</button>
|
|
<button type="button" class="rw-mini-reset">Reset</button>
|
|
</div>
|
|
</section>
|
|
`);
|
|
Nt(o, e), t.prepend(o);
|
|
}
|
|
function Fn(e) {
|
|
if ($("#ringworld-impulse-hud").remove(), !e) return;
|
|
const a = se(e), n = Tt(e, a.queue), o = $(`
|
|
<section id="ringworld-impulse-hud" class="ringworld-impulse-hud">
|
|
<div class="rw-imp-mini-head">
|
|
<strong>Impulse ${a.currentImpulse}</strong>
|
|
<span>Queued ${n.length}</span>
|
|
</div>
|
|
<div class="rw-imp-mini-list">${Dt(n, 8)}</div>
|
|
<div class="rw-imp-mini-controls">
|
|
<button type="button" class="rw-mini-next">Next Completion</button>
|
|
<button type="button" class="rw-mini-step">+1 Impulse</button>
|
|
<button type="button" class="rw-mini-reset">Break / Reset</button>
|
|
</div>
|
|
</section>
|
|
`);
|
|
Nt(o, e), $("body").append(o), On(o);
|
|
}
|
|
function E(e) {
|
|
En(e), Fn(e);
|
|
}
|
|
async function Ae() {
|
|
var e, t;
|
|
return (e = game.user) != null && e.isGM ? !0 : ((t = ui.notifications) == null || t.warn("Only the GM can modify Ringworld impulse sequencing."), !1);
|
|
}
|
|
async function rt(e, t, a, n) {
|
|
var h, p, w, v;
|
|
if (!await Ae()) return;
|
|
const o = e.combatants.get(t);
|
|
if (!o) return;
|
|
const s = se(e), i = s.currentImpulse, r = await $t(o), l = Rt(o), c = q((p = (h = o.flags) == null ? void 0 : h[P]) == null ? void 0 : p.impulseModifier, 0), u = Math.max(1, r + l), g = Math.max(1, (n ? u : 1) + c), f = i + g, d = {
|
|
id: foundry.utils.randomID(),
|
|
actorId: ((w = o.actor) == null ? void 0 : w.id) ?? null,
|
|
combatantId: t,
|
|
kind: a,
|
|
major: n,
|
|
startImpulse: i,
|
|
completeImpulse: f
|
|
}, y = Se(
|
|
s.queue.filter((x) => x.combatantId !== t),
|
|
e
|
|
);
|
|
y.push(d), await ie(e, {
|
|
currentImpulse: i,
|
|
queue: te(y, e)
|
|
}), await o.update({
|
|
[`flags.${P}.pendingEventId`]: d.id,
|
|
[`flags.${P}.pendingKind`]: a,
|
|
[`flags.${P}.actionRanking`]: r,
|
|
[`flags.${P}.effectiveActionRanking`]: u
|
|
});
|
|
const b = n ? `AR ${r} + Enc ${l}${c ? ` ${c > 0 ? "+" : ""} Imp ${c}` : ""}` : `Minor 1${c ? ` ${c > 0 ? "+" : ""} Imp ${c}` : ""}`;
|
|
(v = ui.notifications) == null || v.info(
|
|
`${o.name}: ${Te(a)} completes on impulse ${f} (${b} => ${g}).`
|
|
), E(e);
|
|
}
|
|
async function Bn(e, t) {
|
|
if (!await Ae()) return;
|
|
const a = e.combatants.get(t);
|
|
if (!a) return;
|
|
const n = se(e), o = Se(
|
|
n.queue.filter((s) => s.combatantId !== t),
|
|
e
|
|
);
|
|
await ie(e, {
|
|
currentImpulse: n.currentImpulse,
|
|
queue: te(o, e)
|
|
}), await a.update({
|
|
[`flags.${P}.pendingEventId`]: null,
|
|
[`flags.${P}.pendingKind`]: "",
|
|
[`flags.${P}.aimState`]: {
|
|
active: !1,
|
|
targetId: null,
|
|
sinceImpulse: null
|
|
}
|
|
}), E(e);
|
|
}
|
|
async function Un(e, t, a) {
|
|
var i;
|
|
const n = e.combatants.get(t.combatantId);
|
|
if (!n) return;
|
|
const o = {
|
|
[`flags.${P}.pendingEventId`]: null,
|
|
[`flags.${P}.pendingKind`]: ""
|
|
};
|
|
t.kind === "aim" && (o[`flags.${P}.aimState`] = {
|
|
active: !0,
|
|
targetId: t.targetTokenId ?? null,
|
|
sinceImpulse: a
|
|
}), await n.update(o);
|
|
const s = `
|
|
<div class="ringworld-chat-card">
|
|
<h3>Impulse ${a} Completion</h3>
|
|
<p><strong>${W(n.name)}</strong> completes <strong>${W(Te(t.kind))}</strong>.</p>
|
|
<p><small>DEX ${Y(n)} | AR ${Ct(n)}</small></p>
|
|
</div>
|
|
`;
|
|
await ChatMessage.create({
|
|
user: (i = game.user) == null ? void 0 : i.id,
|
|
speaker: ChatMessage.getSpeaker({ actor: n.actor ?? void 0 }),
|
|
content: s
|
|
});
|
|
}
|
|
async function Ie(e, t) {
|
|
var c;
|
|
if (!await Ae()) return;
|
|
const a = se(e), n = te(Se(a.queue, e), e), o = a.currentImpulse;
|
|
let s = o + 1;
|
|
if (t === "next") {
|
|
const u = n.map((m) => m.completeImpulse).filter((m) => m >= o).sort((m, g) => m - g)[0];
|
|
Number.isFinite(u) && (s = u);
|
|
}
|
|
const i = n.filter((u) => u.completeImpulse === s), r = n.filter((u) => u.completeImpulse !== s);
|
|
await ie(e, {
|
|
currentImpulse: s,
|
|
queue: r
|
|
});
|
|
const l = te(i, e);
|
|
for (const u of l)
|
|
await Un(e, u, s);
|
|
l.length || (c = ui.notifications) == null || c.info(`Impulse advanced to ${s}.`), E(e);
|
|
}
|
|
async function Ht(e) {
|
|
if (await Ae()) {
|
|
await ie(e, { currentImpulse: 1, queue: [] });
|
|
for (const t of e.combatants)
|
|
await t.update({
|
|
[`flags.${P}.pendingEventId`]: null,
|
|
[`flags.${P}.pendingKind`]: "",
|
|
[`flags.${P}.aimState`]: {
|
|
active: !1,
|
|
targetId: null,
|
|
sinceImpulse: null
|
|
},
|
|
[`flags.${P}.actionRanking`]: Ke(Y(t)),
|
|
[`flags.${P}.effectiveActionRanking`]: null,
|
|
[`flags.${P}.impulseModifier`]: 0
|
|
});
|
|
E(e);
|
|
}
|
|
}
|
|
function qn(e, t) {
|
|
var u;
|
|
const a = "find" in t ? t : $(t);
|
|
if (!(a != null && a.length)) return;
|
|
a.addClass("ringworld-impulse-mode ringworld-impulse-only");
|
|
const n = (e == null ? void 0 : e.viewed) ?? game.combat;
|
|
if (!n) return;
|
|
const o = se(n), s = te(Se(o.queue, n), n);
|
|
s.length !== o.queue.length && ie(n, { currentImpulse: o.currentImpulse, queue: s }), a.find(
|
|
".token-initiative, .combatant-initiative, .initiative, [data-action='rollInitiative'], [data-control='rollInitiative'], .combatant .fa-dice-d20"
|
|
).addClass("rw-hide-initiative"), a.find(".ringworld-impulse-panel").remove();
|
|
const i = (u = game.user) != null && u.isGM ? "" : "disabled", r = n.combatants.map((m) => {
|
|
var T, R, D, N;
|
|
const g = Ct(m), f = Rt(m), d = q((R = (T = m.flags) == null ? void 0 : T[P]) == null ? void 0 : R.impulseModifier, 0), y = Math.max(1, g + f), b = Y(m), h = s.find((C) => C.combatantId === m.id), p = h ? `${Te(h.kind)} @ ${h.completeImpulse}` : "Ready", w = (N = (D = m.flags) == null ? void 0 : D[P]) == null ? void 0 : N.aimState, v = w != null && w.active ? `Aiming since ${w.sinceImpulse}` : "Not aiming", x = Rn.map((C) => {
|
|
const U = h != null && h.major && (h == null ? void 0 : h.kind) === C.value ? "selected" : "";
|
|
return `<option value="${C.value}" ${U}>${C.label}</option>`;
|
|
}).join(""), I = $n.map((C) => {
|
|
const U = !(h != null && h.major) && (h == null ? void 0 : h.kind) === C.value ? "selected" : "";
|
|
return `<option value="${C.value}" ${U}>${C.label}</option>`;
|
|
}).join("");
|
|
return `
|
|
<div class="rw-imp-row" data-combatant-id="${m.id}">
|
|
<div class="rw-imp-row-head">
|
|
<span class="rw-imp-name">${W(m.name)}</span>
|
|
<span class="rw-imp-meta">DEX ${b} | AR ${g} + Enc ${f} = ${y} | Imp Mod ${d >= 0 ? `+${d}` : d} | ${W(p)} | ${W(v)}</span>
|
|
</div>
|
|
<div class="rw-imp-row-controls">
|
|
<select class="rw-major-kind" ${i}>${x}</select>
|
|
<select class="rw-minor-kind" ${i}>${I}</select>
|
|
<button type="button" class="rw-major-start" ${i}>Major</button>
|
|
<button type="button" class="rw-minor-start" ${i}>Minor</button>
|
|
<button type="button" class="rw-cancel-start" ${i}>Cancel</button>
|
|
<label>Imp Mod <input type="number" class="rw-imp-mod" value="${d}" ${i} /></label>
|
|
<button type="button" class="rw-save-mods" ${i}>Save Mods</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join(""), l = $(`
|
|
<section class="ringworld-impulse-panel">
|
|
<div class="rw-impulse-top">
|
|
<div class="rw-impulse-clock">Impulse <strong>${o.currentImpulse}</strong></div>
|
|
<div class="rw-impulse-queue">Queued: ${s.length}</div>
|
|
<div class="rw-impulse-controls">
|
|
<button type="button" class="rw-advance-next" ${i}>Next Completion</button>
|
|
<button type="button" class="rw-advance-step" ${i}>+1 Impulse</button>
|
|
<button type="button" class="rw-reset-sequence" ${i}>Break / Reset</button>
|
|
</div>
|
|
</div>
|
|
<div class="rw-imp-list">${r}</div>
|
|
</section>
|
|
`);
|
|
a.prepend(l);
|
|
const c = a.get(0);
|
|
if (c)
|
|
for (const m of Array.from(c.children))
|
|
m.classList.contains("ringworld-impulse-panel") || m.classList.add("rw-hide-core-tracker");
|
|
l.find(".rw-advance-next").on("click", async (m) => {
|
|
m.preventDefault(), await Ie(n, "next");
|
|
}), l.find(".rw-advance-step").on("click", async (m) => {
|
|
m.preventDefault(), await Ie(n, "step");
|
|
}), l.find(".rw-reset-sequence").on("click", async (m) => {
|
|
m.preventDefault(), await Ht(n);
|
|
}), l.find(".rw-major-start").on("click", async (m) => {
|
|
m.preventDefault();
|
|
const g = m.currentTarget.closest(".rw-imp-row");
|
|
if (!g) return;
|
|
const f = String(g.getAttribute("data-combatant-id") || ""), d = g.querySelector(".rw-major-kind"), y = String((d == null ? void 0 : d.value) || "draw");
|
|
await rt(n, f, y, !0);
|
|
}), l.find(".rw-minor-start").on("click", async (m) => {
|
|
m.preventDefault();
|
|
const g = m.currentTarget.closest(".rw-imp-row");
|
|
if (!g) return;
|
|
const f = String(g.getAttribute("data-combatant-id") || ""), d = g.querySelector(".rw-minor-kind"), y = String((d == null ? void 0 : d.value) || "minorCustom");
|
|
await rt(n, f, y, !1);
|
|
}), l.find(".rw-cancel-start").on("click", async (m) => {
|
|
m.preventDefault();
|
|
const g = m.currentTarget.closest(".rw-imp-row");
|
|
if (!g) return;
|
|
const f = String(g.getAttribute("data-combatant-id") || "");
|
|
await Bn(n, f);
|
|
}), l.find(".rw-save-mods").on("click", async (m) => {
|
|
if (m.preventDefault(), !await Ae()) return;
|
|
const g = m.currentTarget.closest(".rw-imp-row");
|
|
if (!g) return;
|
|
const f = String(g.getAttribute("data-combatant-id") || ""), d = n.combatants.get(f);
|
|
if (!d) return;
|
|
const y = g.querySelector(".rw-imp-mod"), b = Math.trunc(q(y == null ? void 0 : y.value, 0));
|
|
await d.update({
|
|
[`flags.${P}.impulseModifier`]: b
|
|
});
|
|
}), E(n);
|
|
}
|
|
function Gn() {
|
|
Hooks.on("renderCombatTracker", (e, t) => {
|
|
qn(e, t);
|
|
}), Hooks.on("createCombat", async (e) => {
|
|
await ie(e, { currentImpulse: 1, queue: [] }), E(e);
|
|
}), Hooks.on("createCombatant", async (e) => {
|
|
await $t(e), E(game.combat ?? null);
|
|
}), Hooks.on("deleteCombatant", async () => {
|
|
E(game.combat ?? null);
|
|
}), Hooks.on("updateCombat", (e) => {
|
|
E(e ?? game.combat ?? null);
|
|
}), Hooks.on("deleteCombat", () => {
|
|
E(null);
|
|
}), Hooks.on("renderChatLog", () => {
|
|
E(game.combat ?? null);
|
|
}), Hooks.on("canvasReady", () => {
|
|
E(game.combat ?? null);
|
|
}), Hooks.on("updateActor", (e) => {
|
|
var n;
|
|
const t = game.combat;
|
|
if (!t) return;
|
|
t.combatants.some((o) => {
|
|
var s;
|
|
return ((s = o.actor) == null ? void 0 : s.id) === e.id;
|
|
}) && ((n = ui.combat) == null || n.render(!0), E(t));
|
|
}), Hooks.on("updateItem", (e) => {
|
|
var o;
|
|
const t = e == null ? void 0 : e.parent;
|
|
if (!t) return;
|
|
const a = game.combat;
|
|
if (!a) return;
|
|
a.combatants.some((s) => {
|
|
var i;
|
|
return ((i = s.actor) == null ? void 0 : i.id) === t.id;
|
|
}) && ((o = ui.combat) == null || o.render(!0), E(a));
|
|
});
|
|
}
|
|
const lt = 2, ct = 1;
|
|
async function Vn() {
|
|
var a, n;
|
|
if (!((a = game.user) != null && a.isGM)) return 0;
|
|
let e = 0;
|
|
const t = (o) => {
|
|
var l, c, u, m;
|
|
const s = {}, i = (l = o == null ? void 0 : o.generalHp) == null ? void 0 : l.value, r = (c = o == null ? void 0 : o.generalHp) == null ? void 0 : c.max;
|
|
i != null && Number(i) >= Number(r ?? 0) && (s["system.generalHp.value"] = null);
|
|
for (const [g, f] of Object.entries((o == null ? void 0 : o.hitLocations) ?? {})) {
|
|
const d = (u = f == null ? void 0 : f.hp) == null ? void 0 : u.value, y = (m = f == null ? void 0 : f.hp) == null ? void 0 : m.max;
|
|
d != null && (Number(d) >= Number(y ?? 0) || Number(y ?? 0) <= 1 && Number(d) <= 1) && (s[`system.hitLocations.${g}.hp.value`] = null);
|
|
}
|
|
return Object.keys(s).length ? s : null;
|
|
};
|
|
for (const o of game.actors ?? []) {
|
|
if (o.type !== "explorer" && o.type !== "creature") continue;
|
|
const s = t(((n = o._source) == null ? void 0 : n.system) ?? {});
|
|
s && (await o.update(s), e += 1);
|
|
}
|
|
return e;
|
|
}
|
|
function Ot(e) {
|
|
return String(e ?? "").trim().toLowerCase();
|
|
}
|
|
function Kn() {
|
|
var t;
|
|
const e = /* @__PURE__ */ new Map();
|
|
for (const a of game.items ?? []) {
|
|
if (a.type !== "weapon" && a.type !== "armor" && a.type !== "gear") continue;
|
|
const n = `${a.type}:${Ot(a.name)}`;
|
|
e.set(n, Number(((t = a.system) == null ? void 0 : t.weight) ?? 0) || 0);
|
|
}
|
|
return e;
|
|
}
|
|
async function _n() {
|
|
var a, n, o, s;
|
|
if (!((a = game.user) != null && a.isGM)) return 0;
|
|
const e = Kn();
|
|
let t = 0;
|
|
for (const i of game.actors ?? []) {
|
|
const r = [];
|
|
for (const l of i.items ?? []) {
|
|
if (l.type !== "weapon" && l.type !== "armor" && l.type !== "gear") continue;
|
|
const c = { _id: l.id };
|
|
let u = !1;
|
|
if (((n = l.system) == null ? void 0 : n.equipped) === void 0 && (c["system.equipped"] = !0, u = !0), ((o = l.system) == null ? void 0 : o.weight) === void 0) {
|
|
const m = `${l.type}:${Ot(l.name)}`;
|
|
c["system.weight"] = Number(e.get(m) ?? 0), u = !0;
|
|
}
|
|
((s = l.system) == null ? void 0 : s.stowed) === void 0 && (c["system.stowed"] = !1, u = !0), u && r.push(c);
|
|
}
|
|
r.length && (await i.updateEmbeddedDocuments("Item", r), t += r.length);
|
|
}
|
|
return t;
|
|
}
|
|
function Me(e, t, a, n) {
|
|
if (!e) return;
|
|
const o = "find" in e ? e : $(e);
|
|
if (!o.length || o.find(`.${t}`).length) return;
|
|
const s = $(`<button type="button" class="ringworld-directory-button ${t}">${a}</button>`);
|
|
s.on("click", async () => {
|
|
await n();
|
|
});
|
|
const r = [
|
|
o.find(".directory-footer"),
|
|
o.find("footer.directory-footer"),
|
|
o.find("footer"),
|
|
o.find(".footer-actions"),
|
|
o.find(".header-actions"),
|
|
o.find(".action-buttons")
|
|
].find((c) => c.length);
|
|
if (r) {
|
|
r.first().append(s);
|
|
return;
|
|
}
|
|
let l = o.find(".ringworld-directory-controls");
|
|
l.length || (l = $('<div class="ringworld-directory-controls"></div>'), o.append(l)), l.first().append(s);
|
|
}
|
|
async function Et() {
|
|
await Ue({ notify: !0 });
|
|
}
|
|
async function Ft() {
|
|
var t;
|
|
const e = await Lt();
|
|
(t = e == null ? void 0 : e.sheet) == null || t.render(!0);
|
|
}
|
|
function ae() {
|
|
var a, n, o;
|
|
if (!((a = game.user) != null && a.isGM)) return;
|
|
const e = (n = ui.items) == null ? void 0 : n.element;
|
|
Me(e, "ringworld-seed-items", "Seed Ringworld Items", Et);
|
|
const t = (o = ui.actors) == null ? void 0 : o.element;
|
|
Me(t, "ringworld-random-actor", "Create Random Explorer", Ft);
|
|
}
|
|
function jn(e) {
|
|
var o, s, i, r;
|
|
const t = (o = getDocumentClass("Item")) == null ? void 0 : o.TYPES;
|
|
if (Array.isArray(t)) return t.includes(e);
|
|
const a = (i = (s = game == null ? void 0 : game.system) == null ? void 0 : s.documentTypes) == null ? void 0 : i.Item;
|
|
if (Array.isArray(a)) return a.includes(e);
|
|
if (a && typeof a == "object") return Object.prototype.hasOwnProperty.call(a, e);
|
|
const n = (r = CONFIG == null ? void 0 : CONFIG.Item) == null ? void 0 : r.dataModels;
|
|
return !!(n && typeof n == "object" && Object.prototype.hasOwnProperty.call(n, e));
|
|
}
|
|
Hooks.once("init", async () => {
|
|
var o, s, i, r, l, c, u, m;
|
|
console.log("ringworld | Initializing Ringworld system"), Gn();
|
|
const e = (s = (o = foundry == null ? void 0 : foundry.documents) == null ? void 0 : o.collections) == null ? void 0 : s.Actors, t = (r = (i = foundry == null ? void 0 : foundry.documents) == null ? void 0 : i.collections) == null ? void 0 : r.Items, a = (c = (l = foundry == null ? void 0 : foundry.appv1) == null ? void 0 : l.sheets) == null ? void 0 : c.ActorSheet, n = (m = (u = foundry == null ? void 0 : foundry.appv1) == null ? void 0 : u.sheets) == null ? void 0 : m.ItemSheet;
|
|
if (CONFIG.ringworld = {
|
|
hitLocations: Re
|
|
}, CONFIG.Combat.initiative = foundry.utils.mergeObject(CONFIG.Combat.initiative ?? {}, {
|
|
formula: "1d100",
|
|
decimals: 0
|
|
}, { inplace: !1 }), CONFIG.Actor.documentClass = Za, CONFIG.Item.documentClass = Ja, !e || !t || !a || !n) {
|
|
console.error("ringworld | Foundry sheet registries were not available during init.");
|
|
return;
|
|
}
|
|
e.unregisterSheet("core", a), e.registerSheet("ringworld", dn, {
|
|
types: ["explorer"],
|
|
makeDefault: !0
|
|
}), e.registerSheet("ringworld", fn, {
|
|
types: ["creature"],
|
|
makeDefault: !0
|
|
}), e.registerSheet("ringworld", yn, {
|
|
types: ["vehicle"],
|
|
makeDefault: !0
|
|
}), t.unregisterSheet("core", n), t.registerSheet("ringworld", bn, {
|
|
types: ["skill"],
|
|
makeDefault: !0
|
|
}), t.registerSheet("ringworld", vn, {
|
|
types: ["weapon"],
|
|
makeDefault: !0
|
|
}), t.registerSheet("ringworld", An, {
|
|
types: ["armor"],
|
|
makeDefault: !0
|
|
}), t.registerSheet("ringworld", Pn, {
|
|
types: ["gear"],
|
|
makeDefault: !0
|
|
}), t.registerSheet("ringworld", In, {
|
|
types: ["species"],
|
|
makeDefault: !0
|
|
}), jn("pursuit") && t.registerSheet("ringworld", Cn, {
|
|
types: ["pursuit"],
|
|
makeDefault: !0
|
|
}), game.settings.register("ringworld", "catalogSeedVersion", {
|
|
scope: "world",
|
|
config: !1,
|
|
type: Number,
|
|
default: 0
|
|
}), game.settings.register("ringworld", "encumbranceDataMigrationVersion", {
|
|
scope: "world",
|
|
config: !1,
|
|
type: Number,
|
|
default: 0
|
|
}), game.settings.register("ringworld", "hpDataMigrationVersion", {
|
|
scope: "world",
|
|
config: !1,
|
|
type: Number,
|
|
default: 0
|
|
}), game.settings.register("ringworld", "automateSpecialDamage", {
|
|
name: "Automate Special Success Damage",
|
|
hint: "When an attack is a Special Success, roll the weapon damage dice twice and sum them.",
|
|
scope: "world",
|
|
config: !0,
|
|
type: Boolean,
|
|
default: !0
|
|
}), game.settings.register("ringworld", "promptDefenderDodge", {
|
|
name: "Prompt Defender Dodge",
|
|
hint: "After a successful attack on a target, offer a Dodge roll before damage is applied. A successful dodge evades a normal hit; only a special dodge evades a special hit.",
|
|
scope: "world",
|
|
config: !0,
|
|
type: Boolean,
|
|
default: !0
|
|
}), Handlebars.registerHelper("eq", (g, f) => g === f), Handlebars.registerHelper("or", (g, f) => !!(g || f));
|
|
});
|
|
Hooks.once("ready", async () => {
|
|
var t, a, n, o, s, i;
|
|
const e = "systems/ringworld/resources/hit-locations/humanoid.json";
|
|
try {
|
|
const r = await fetch(e);
|
|
if (r.ok) {
|
|
const l = await r.json();
|
|
CONFIG.ringworld.hitLocations = l.entries ?? Re;
|
|
}
|
|
} catch {
|
|
console.warn(`ringworld | Could not load ${e}, using built-in table`);
|
|
}
|
|
if ((t = game.user) != null && t.isGM) {
|
|
for (const u of game.combats ?? []) {
|
|
const m = String(((n = (a = u == null ? void 0 : u.settings) == null ? void 0 : a.initiative) == null ? void 0 : n.formula) ?? "").trim();
|
|
(!m || m.toLowerCase() === "undefined") && await u.update({
|
|
"settings.initiative.formula": "1d100",
|
|
"settings.initiative.decimals": 0
|
|
});
|
|
}
|
|
if (Number(game.settings.get("ringworld", "catalogSeedVersion") ?? 0) < He && (await Ue({ notify: !0 })).added >= 0 && await game.settings.set("ringworld", "catalogSeedVersion", He), Number(game.settings.get("ringworld", "encumbranceDataMigrationVersion") ?? 0) < lt) {
|
|
const u = await _n();
|
|
await game.settings.set("ringworld", "encumbranceDataMigrationVersion", lt), u > 0 && ((o = ui.notifications) == null || o.info(`Ringworld encumbrance migration updated ${u} actor items.`));
|
|
}
|
|
if (Number(game.settings.get("ringworld", "hpDataMigrationVersion") ?? 0) < ct) {
|
|
const u = await Vn();
|
|
await game.settings.set("ringworld", "hpDataMigrationVersion", ct), u > 0 && ((s = ui.notifications) == null || s.info(`Ringworld HP migration updated ${u} actors.`));
|
|
}
|
|
}
|
|
ae(), window.setTimeout(() => ae(), 300), game.ringworld = {
|
|
...game.ringworld ?? {},
|
|
seedWorldCatalog: async (r) => Ue(r),
|
|
createRandomExplorer: async () => Lt(),
|
|
catalogVersion: He,
|
|
systemId: "ringworld",
|
|
systemVersion: String(((i = game.system) == null ? void 0 : i.version) ?? "0.0.0")
|
|
}, Hooks.callAll("ringworldReady", game.ringworld);
|
|
});
|
|
Hooks.on("createActor", async (e, t, a) => {
|
|
var n, o;
|
|
if ((n = game.user) != null && n.isGM && a === game.user.id && !(!e || e.type !== "explorer" && e.type !== "creature"))
|
|
try {
|
|
const s = await ((o = e.ensureBaseSkills) == null ? void 0 : o.call(e));
|
|
Number(s) > 0 && console.log(`ringworld | Added ${s} base skills to ${e.name}`);
|
|
} catch (s) {
|
|
console.error("ringworld | Failed to auto-populate base skills", s);
|
|
}
|
|
});
|
|
Hooks.on("preCreateItem", (e) => {
|
|
if (e.type !== "skill") return;
|
|
const t = {}, a = String(e.name || "Skill"), n = String(e.img || "");
|
|
t.img = Fe(a, n);
|
|
const o = $e(a), s = e.system ?? {}, i = Number((s == null ? void 0 : s.value) ?? NaN), r = String((s == null ? void 0 : s.category) ?? "").trim();
|
|
t.system = {
|
|
value: Number.isFinite(i) ? i : (o == null ? void 0 : o.base) ?? 0,
|
|
category: r || ((o == null ? void 0 : o.category) ?? "Technical"),
|
|
specialization: String((s == null ? void 0 : s.specialization) ?? (o == null ? void 0 : o.specialization) ?? "")
|
|
}, t.img || (t.img = X), e.updateSource(t);
|
|
});
|
|
Hooks.on("renderItemDirectory", (e, t) => {
|
|
var a;
|
|
(a = game.user) != null && a.isGM && (Me(t, "ringworld-seed-items", "Seed Ringworld Items", Et), ae());
|
|
});
|
|
Hooks.on("renderActorDirectory", (e, t) => {
|
|
var a;
|
|
(a = game.user) != null && a.isGM && (Me(t, "ringworld-random-actor", "Create Random Explorer", Ft), ae());
|
|
});
|
|
Hooks.on("renderSidebar", () => {
|
|
ae();
|
|
});
|
|
Hooks.on("changeSidebarTab", (e) => {
|
|
var a;
|
|
const t = String((e == null ? void 0 : e.tabName) ?? (e == null ? void 0 : e.id) ?? ((a = e == null ? void 0 : e.options) == null ? void 0 : a.id) ?? "");
|
|
(t === "items" || t === "actors") && ae();
|
|
});
|
|
//# sourceMappingURL=ringworld.mjs.map
|