sync: wg-voidships-builder v1.4.11

This commit is contained in:
2025-09-19 17:28:20 +02:00
parent 862e0c3ca5
commit c2a93cbd86
3 changed files with 115 additions and 167 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
"id": "wg-voidships-builder", "id": "wg-voidships-builder",
"title": "Wrath & Glory: Voidships — Builder", "title": "Wrath & Glory: Voidships — Builder",
"description": "Składanie okrętów jako Actorów przez dodawanie wyposażenia (gear) z kompendium.", "description": "Składanie okrętów jako Actorów przez dodawanie wyposażenia (gear) z kompendium.",
"version": "1.4.10", "version": "1.4.11",
"compatibility": { "compatibility": {
"minimum": "12", "minimum": "12",
"verified": "13" "verified": "13"
@@ -1,16 +1,5 @@
{"_id":"BRIDGECLV0000001","name":"Command Bridge (Civilian)","type":"gear","img":"icons/svg/book.svg","flags":{"wg-voidships-builder":{"component":{"kind":"bridge","subtype":"civilian","tags":["bridge","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":2,"power":2},"mods":{"armour":0,"integrity":0,"manoeuvre":1,"detection":1,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Standard command bridge and cogitators.","value":0,"keywords":[],"effect":"","test":"","voidship":{"use":{},"mods":{}}},"effects":[]} {"_id":"ITEMREACESCJOV01","name":"Plasma Reactor (Escort) — Jovian-Pattern","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"reactor","subtype":"escort","tags":["reactor","plasma"]},"voidship":{"slot":"Internal","use":{"space":6,"power":0},"mods":{"power":28}}}},"system":{"description":"Lżejszy reaktor plazmowy dla eskort. Generuje ~28 Power, zajmuje 6 Space."},"effects":[]}
{"_id":"REACTORPLSM00001","name":"Plasma Reactor (Civilian)","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"component":{"kind":"reactor","subtype":"plasma","tags":["reactor","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":4,"power":-10},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":10}}}},"system":{"quantity":1,"rarity":"Common","description":"Primary plasma reactor for voidship operations.","value":0,"keywords":[],"effect":"","test":"","voidship":{"use":{"power":0,"space":0},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0},"slot":"Internal"}},"effects":[]} {"_id":"ITEMTORPPROW0001","name":"Torpedo Tubes — Jovian-Pattern","type":"gear","img":"icons/svg/anchor.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"weapon","subtype":"torpedo","tags":["weapon","torpedo"],"allowedSlots":["prow"]},"voidship":{"allowedSlots":["prow"],"use":{"space":10,"power":5},"mods":{}}}},"system":{"description":"Wyrzutnie torpedowe montowane w dziobie. Zużycie 10 Space, 5 Power."},"effects":[]}
{"_id":"GELLARSTD0000001","name":"Gellar Field Projector","type":"gear","img":"icons/svg/temple.svg","flags":{"wg-voidships-builder":{"component":{"kind":"gellar","subtype":"standard","tags":["gellar","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":2,"power":3},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Essential protection in the Immaterium.","value":0,"keywords":[],"effect":"","test":"","voidship":{"use":{},"mods":{}}},"effects":[]} {"_id":"ITEMLANCEDSL0001","name":"Lance Battery — Starflare-Pattern","type":"gear","img":"icons/svg/sun.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"weapon","subtype":"lance","tags":["weapon","lance"],"allowedSlots":["dorsal","port","starboard"]},"voidship":{"allowedSlots":["dorsal","port","starboard"],"use":{"space":10,"power":6},"mods":{}}}},"system":{"description":"Bateria lanc. Montaż: Dorsal/Port/Starboard. Zużycie 10 Space, 6 Power."},"effects":[]}
{"_id":"WARPDRVMILO00001","name":"Miloslav Warp Drive (Civilian)","type":"gear","img":"icons/svg/wing.svg","flags":{"wg-voidships-builder":{"component":{"kind":"drive","subtype":"warp","tags":["drive","warp","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":3,"power":6},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Reliable civilian warp drive.","value":0,"keywords":[],"effect":"","test":"","voidship":{"use":{},"mods":{}}},"effects":[]} {"_id":"ITEMWPMACBRD0001","name":"Macrobattery (Broadside) — Mars-Pattern","type":"gear","img":"icons/svg/dice-target.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"weapon","subtype":"macro-broadside","tags":["weapon","macro"],"allowedSlots":["port","starboard"]},"voidship":{"allowedSlots":["port","starboard"],"use":{"space":9,"power":4},"mods":{}}}},"system":{"description":"Makrobateria burtowa. Montaż wyłącznie Port/Starboard. Zużycie 9 Space, 4 Power."},"effects":[]}
{"_id":"AUGURARRAY000001","name":"Augur Array (Civilian)","type":"gear","img":"icons/svg/mystery-man.svg","flags":{"wg-voidships-builder":{"component":{"kind":"sensor","subtype":"augur","tags":["sensor","auspex","aetherics"]},"voidOnly":true,"voidship":{"slot":"Aetherics","use":{"space":1,"power":2},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":2,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Standard augur/sensor package.","value":0,"keywords":[],"effect":"","test":"","voidship":{"use":{},"mods":{}}},"effects":[]} {"_id":"ITEMMACROUNI0001","name":"Macrobattery (Universal) — Mars-Pattern","type":"gear","img":"icons/svg/dice-target.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"weapon","subtype":"macro","tags":["weapon","macro"],"allowedSlots":["prow","dorsal","port","starboard"]},"voidship":{"allowedSlots":["prow","dorsal","port","starboard"],"use":{"space":8,"power":4},"mods":{}}}},"system":{"description":"Uniwersalna makrobateria (prow/dorsal/port/starboard). Zużycie 8 Space, 4 Power."},"effects":[]}
{"_id":"HULLPLATE0000001","name":"Reinforced Hull Plating","type":"gear","img":"icons/svg/armor.svg","flags":{"wg-voidships-builder":{"component":{"kind":"armour","subtype":"plating","tags":["hull","armour","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":1,"power":0},"mods":{"armour":2,"integrity":4,"manoeuvre":-1,"detection":0,"speed":0,"space":0,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"Additional armour plates and bracing.","value":0,"keywords":[],"effect":"","test":"","voidship":{"use":{},"mods":{}}},"effects":[]}
{"_id":"CARGOHOLD0000001","name":"Cargo Hold & Lighter Bay","type":"gear","img":"icons/svg/crate.svg","flags":{"wg-voidships-builder":{"component":{"kind":"cargo-bay","subtype":"standard","tags":["cargo-bay","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":-5,"power":1},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":-5,"power":0}}}},"system":{"quantity":1,"rarity":"Common","description":"General-purpose cargo hold with lighter handling.","value":0,"keywords":[],"effect":"","test":"","voidship":{"use":{},"mods":{}}},"effects":[]}
{"_id":"AUXPLASMA0000001","name":"Auxiliary Plasma Generator","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"component":{"kind":"plasma","subtype":"aux-generator","tags":["aux-plasma","internal"]},"voidOnly":true,"voidship":{"slot":"Internal","use":{"space":2,"power":-6},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":6}}}},"system":{"quantity":1,"rarity":"Common","description":"Auxiliary plasma reactor increasing available power.","value":0,"keywords":[],"effect":"","test":"","voidship":{"use":{"power":0},"mods":{}}},"effects":[]}
{"_id":"ITEMREACTCRU0001","name":"Plasma Reactor (Cruiser) — Mars-Pattern","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"reactor","subtype":"cruiser","tags":["reactor","plasma"]}}},"system":{"description":"Standardowy reaktor plazmowy klasy krążownik (Mars-Pattern). Generuje moc dla wszystkich systemów okrętu.","voidship":{"slot":"Internal","use":{"space":20,"power":0},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":60}}},"effects":[]}
{"_id":"ITEMREACTLCR0001","name":"Plasma Reactor (Light Cruiser) — Mars-Pattern","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"reactor","subtype":"light-cruiser","tags":["reactor","plasma"]}}},"system":{"description":"Reaktor plazmowy dla lekkich krążowników (Mars-Pattern).","voidship":{"slot":"Internal","use":{"space":18,"power":0},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":55}}},"effects":[]}
{"_id":"ITEMREACTFRI0001","name":"Plasma Reactor (Frigate) — Jovian-Pattern","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"reactor","subtype":"frigate","tags":["reactor","plasma"]}}},"system":{"description":"Reaktor plazmowy okrętów klasy fregata (Jovian-Pattern).","voidship":{"slot":"Internal","use":{"space":15,"power":0},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":45}}},"effects":[]}
{"_id":"ITEMREACTESC0001","name":"Plasma Reactor (Escort) — Jovian-Pattern","type":"gear","img":"icons/svg/energy.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"reactor","subtype":"escort","tags":["reactor","plasma"]}}},"system":{"description":"Lżejszy reaktor plazmowy dla eskort.","voidship":{"slot":"Internal","use":{"space":12,"power":0},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":40}}},"effects":[]}
{"_id":"ITEMMACROUNI0001","name":"Macrobattery (Universal) — Mars-Pattern","type":"gear","img":"icons/svg/dice-target.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"weapon","subtype":"macro","tags":["weapon","macro"]}}},"system":{"description":"Uniwersalna makrobateria. Może być montowana na dowolnym stanowisku broni.","voidship":{"allowedSlots":["prow","dorsal","port","starboard"],"use":{"space":8,"power":4},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}},"effects":[]}
{"_id":"ITEMWPMACBRD0001","name":"Macrobattery (Broadside) — Mars-Pattern","type":"gear","img":"icons/svg/dice-target.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"weapon","subtype":"macro-broadside","tags":["weapon","macro"]}}},"system":{"description":"Makrobateria burtowa. Montaż wyłącznie na burtach.","voidship":{"allowedSlots":["port","starboard"],"use":{"space":9,"power":4},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}},"effects":[]}
{"_id":"ITEMTORPPROW0001","name":"Torpedo Tubes — Jovian-Pattern","type":"gear","img":"icons/svg/anchor.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"weapon","subtype":"torpedo","tags":["weapon","torpedo"]}}},"system":{"description":"Wyrzutnie torpedowe montowane w dziobie.","voidship":{"allowedSlots":["prow"],"use":{"space":10,"power":5},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}},"effects":[]}
{"_id":"ITEMLANCEDSL0001","name":"Lance Battery — Starflare-Pattern","type":"gear","img":"icons/svg/sun.svg","flags":{"wg-voidships-builder":{"voidOnly":true,"component":{"kind":"weapon","subtype":"lance","tags":["weapon","lance"]}}},"system":{"description":"Bateria lanc. Preferowane stanowiska: dorsal/port/starboard.","voidship":{"allowedSlots":["dorsal","port","starboard"],"use":{"space":10,"power":6},"mods":{"armour":0,"integrity":0,"manoeuvre":0,"detection":0,"speed":0,"space":0,"power":0}}},"effects":[]}
+105 -146
View File
@@ -11,31 +11,26 @@ function normalizeSlotName(s) {
const v = String(s || "").trim().toLowerCase(); const v = String(s || "").trim().toLowerCase();
return VB_WEAPON_SLOTS.includes(v) ? v : null; return VB_WEAPON_SLOTS.includes(v) ? v : null;
} }
function normalizeAllowedSlots(allowed) { function normalizeAllowedSlots(allowed) {
if (!allowed) return []; if (!allowed) return [];
if (Array.isArray(allowed)) return allowed.map(normalizeSlotName).filter(Boolean); if (Array.isArray(allowed)) return allowed.map(normalizeSlotName).filter(Boolean);
// string "prow|dorsal|port" albo "prow,dorsal" return String(allowed).split(/[|,; ]+/g).map(normalizeSlotName).filter(Boolean);
return String(allowed) }
.split(/[|,; ]+/g) function getVSH(it) {
.map(normalizeSlotName) // canonical: flags; fallback: legacy system.voidship (skopiujemy do flags przy dropie)
.filter(Boolean); return it.getFlag(VB_MOD, "voidship") || it.system?.voidship || null;
} }
function countUsedWeaponSlots(actor) { function countUsedWeaponSlots(actor) {
const used = { prow: 0, dorsal: 0, port: 0, starboard: 0, keel: 0 }; const used = { prow: 0, dorsal: 0, port: 0, starboard: 0, keel: 0 };
for (const it of actor.items ?? []) { for (const it of actor.items ?? []) {
const vs = it.system?.voidship; const v = getVSH(it);
if (!vs) continue;
const slot = normalizeSlotName(vs.slot);
const eq = (it.system?.equipped ?? true); const eq = (it.system?.equipped ?? true);
const slot = normalizeSlotName(v?.slot);
if (eq && slot && (slot in used)) used[slot] += 1; if (eq && slot && (slot in used)) used[slot] += 1;
} }
return used; return used;
} }
function pickFirstFreeSlot(allowed, hullSlots, used) { function pickFirstFreeSlot(allowed, hullSlots, used) {
// Kolejność zgodnie z wymaganiem: "pierwszy wolny pasujący"
const order = ["prow", "dorsal", "port", "starboard", "keel"]; const order = ["prow", "dorsal", "port", "starboard", "keel"];
for (const s of order) { for (const s of order) {
if (!allowed.includes(s)) continue; if (!allowed.includes(s)) continue;
@@ -130,8 +125,6 @@ const defaultHull = { class: actor.name, size: "Cruiser", slots: { prow: 0, dors
const defaultBase = { armour: 0, integrity: 0, manoeuvre: 0, detection: 0, speed: 0, space: 0, power: 0 }; const defaultBase = { armour: 0, integrity: 0, manoeuvre: 0, detection: 0, speed: 0, space: 0, power: 0 };
const defaultCrew = { command: 0, pilot: 0, sensors: 0, engineering: 0, gunnery: 0, security: 0, flightDeck: 0, passengers: 0 }; const defaultCrew = { command: 0, pilot: 0, sensors: 0, engineering: 0, gunnery: 0, security: 0, flightDeck: 0, passengers: 0 };
const hull = actor.getFlag(VB_MOD, "hull") ?? flagsNs.hull ?? defaultHull;
const base = actor.getFlag(VB_MOD, "base") ?? flagsNs.base ?? defaultBase;
const crew = actor.getFlag(VB_MOD, "crew") ?? flagsNs.crew ?? defaultCrew; const crew = actor.getFlag(VB_MOD, "crew") ?? flagsNs.crew ?? defaultCrew;
const rtRaw = actor.getFlag(VB_MOD, "rtRaw") ?? flagsNs.rtRaw ?? {}; const rtRaw = actor.getFlag(VB_MOD, "rtRaw") ?? flagsNs.rtRaw ?? {};
@@ -157,115 +150,91 @@ const rtRaw = actor.getFlag(VB_MOD, "rtRaw") ?? flagsNs.rtRaw ?? {};
source: rt.source ?? rtRaw.source ?? "" source: rt.source ?? rtRaw.source ?? ""
}; };
// ===== KOMPONENTY: czytamy Z FLAG ===== // --- komponenty z itemów (flags canonical) ---
const items = actor.items.contents; const items = actor.items.contents || [];
const inferSlotFromTags = (tags=[]) => {
const low = (Array.isArray(tags)?tags:[]).map(t => String(t).toLowerCase());
const pref = ["prow","dorsal","port","starboard","keel","aetherics","internal"];
const hit = pref.find(p => low.includes(p));
return hit ? (hit[0].toUpperCase()+hit.slice(1)) : "Internal";
};
const components = []; const components = [];
for (const i of items) { const slotsUsed = { prow: 0, dorsal: 0, port: 0, starboard: 0, keel: 0 };
const fmod = i.flags?.[VB_MOD] || {}; const mods = { armour: 0, integrity: 0, manoeuvre: 0, detection: 0, speed: 0, space: 0, power: 0 };
const vflag = fmod.voidship || null; // <-- TU trzymamy liczby const useTotal = { space: 0, power: 0 };
const meta = fmod.component || {}; // kind/subtype/tags
// Dopuszczamy też stare itemy z system.voidship (jeśli coś przetrwa)
const vsys = i.system?.voidship || null;
// komponent, jeśli ma vflag LUB meta.tags LUB vsys for (const it of items) {
const isComponent = !!(vflag || vsys || (Array.isArray(meta.tags) && meta.tags.length)); const v = getVSH(it);
if (!isComponent) continue; if (!v) continue;
const c = foundry.utils.duplicate(v);
c.name = it.name;
c.itemId = it.id;
c.equipped = (it.system?.equipped ?? true);
c.slot = normalizeSlotName(c.slot) || "Internal";
c.allowedSlots = normalizeAllowedSlots(c.allowedSlots || foundry.utils.getProperty(it.flags, `${VB_MOD}.component.allowedSlots`));
const vf = vflag || vsys || {}; // defaults
const use = { c.use = { space: 0, power: 0, ...(c.use || {}) };
space: Number(vf?.use?.space ?? 0), c.mods = { armour: 0, integrity: 0, manoeuvre: 0, detection: 0, speed: 0, space: 0, power: 0, ...(c.mods || {}) };
power: Number(vf?.use?.power ?? 0)
};
const mods = {
armour: Number(vf?.mods?.armour ?? 0),
integrity: Number(vf?.mods?.integrity ?? 0),
manoeuvre: Number(vf?.mods?.manoeuvre ?? 0),
detection: Number(vf?.mods?.detection ?? 0),
speed: Number(vf?.mods?.speed ?? 0),
space: Number(vf?.mods?.space ?? 0),
power: Number(vf?.mods?.power ?? 0)
};
let slot = vf.slot || meta.slot || inferSlotFromTags(meta.tags);
const equipped = (i.system?.equipped ?? true) && (vf.disabled !== true);
components.push({ components.push(c);
itemId: i.id,
name: i.name, if (c.equipped) {
slot, // sumuj mody
equipped, for (const k of Object.keys(mods)) {
use, mods, const val = Number(c.mods[k] || 0);
tags: meta.tags || [] if (Number.isFinite(val)) mods[k] += val;
}); }
// sumuj zużycie/produkcję
const us = Number(c.use.space || 0);
const up = Number(c.use.power || 0);
if (Number.isFinite(us)) useTotal.space += us;
if (Number.isFinite(up)) useTotal.power += up;
// slot usage (tylko jeśli to broń z legalnym slotem)
if (VB_WEAPON_SLOTS.includes(c.slot)) {
slotsUsed[c.slot] = (slotsUsed[c.slot] || 0) + 1;
}
}
} }
// Zliczanie slotów (tylko equipped) const base = actor.getFlag(VB_MOD, "base") || { armour:0, integrity:0, manoeuvre:0, detection:0, speed:0, space:0, power:0 };
const slotsUsed = components.reduce((acc, c) => {
if (!c.equipped) return acc;
const key = String(c.slot || "Internal").toLowerCase();
if (["prow","dorsal","port","starboard","keel"].includes(key)) acc[key] = (acc[key] || 0) + 1;
return acc;
}, { prow: 0, dorsal: 0, port: 0, starboard: 0, keel: 0 });
// Capacity: bazowe + mody z komponentów (equipped) vs użycie (equipped)
const modsSum = components.reduce((acc, c) => {
if (!c.equipped) return acc;
for (const k of Object.keys(acc)) acc[k] += Number(c.mods[k] || 0);
return acc;
}, { armour:0, integrity:0, manoeuvre:0, detection:0, speed:0, space:0, power:0 });
const useSum = components.reduce((acc, c) => {
if (!c.equipped) return acc;
acc.space += Number(c.use.space || 0);
acc.power += Number(c.use.power || 0);
return acc;
}, { space: 0, power: 0 });
// total = base + mods
const totals = { const totals = {
armour: (Number(base.armour)||0) + modsSum.armour, armour: (Number(base.armour)||0) + mods.armour,
integrity: (Number(base.integrity)||0) + modsSum.integrity, integrity: (Number(base.integrity)||0) + mods.integrity,
manoeuvre: (Number(base.manoeuvre)||0) + modsSum.manoeuvre, manoeuvre: (Number(base.manoeuvre)||0) + mods.manoeuvre,
detection: (Number(base.detection)||0) + modsSum.detection, detection: (Number(base.detection)||0) + mods.detection,
speed: (Number(base.speed)||0) + modsSum.speed, speed: (Number(base.speed)||0) + mods.speed,
space: (Number(base.space)||0) + modsSum.space, space: (Number(base.space)||0) + mods.space,
power: (Number(base.power)||0) + modsSum.power power: (Number(base.power)||0) + mods.power
}; };
totals.spaceFree = totals.space - useSum.space; totals.spaceFree = totals.space - useTotal.space;
totals.powerFree = totals.power - useSum.power; totals.powerFree = totals.power - useTotal.power;
// Ostrzeżenia // warnings
const hull = actor.getFlag(VB_MOD, "hull") || { slots: {} };
const warnings = {}; const warnings = {};
const overSlots = for (const s of VB_WEAPON_SLOTS) {
(slotsUsed.prow > (hull.slots?.prow || 0)) || if ((slotsUsed[s]||0) > (hull.slots?.[s]||0)) { warnings.slots = "One or more weapon slot groups exceed hull limits."; break; }
(slotsUsed.dorsal > (hull.slots?.dorsal || 0)) || }
(slotsUsed.port > (hull.slots?.port || 0)) || if (totals.spaceFree < 0) warnings.capacity = "Not enough Space for installed components.";
(slotsUsed.starboard > (hull.slots?.starboard || 0)) || if (totals.powerFree < 0) warnings.capacity = "Not enough Power for installed components.";
(slotsUsed.keel > (hull.slots?.keel || 0));
if (overSlots) warnings.slots = "One or more weapon slot groups exceed hull limits.";
if (totals.spaceFree < 0 || totals.powerFree < 0) // zaszyj
warnings.capacity = "Not enough Space/Power for installed components.";
// wstrzyknij do template
data.hull = hull; data.hull = hull;
data.base = base; data.base = base;
data.crew = crew;
data.rtCard = rtCard;
data.components = components; data.components = components;
data.slotsUsed = slotsUsed; data.slotsUsed = slotsUsed;
data.totals = totals; data.totals = totals;
data.mods = modsSum; // ← DODANE: żeby HBS miał liczby modów data.mods = mods;
data.warnings = warnings; data.useTotal = useTotal;
// extra debug
data.debug = {
usedWeaponSlots: slotsUsed,
itemsCount: items.length,
componentsCount: components.length,
itemsNoVSH: items.filter(i => !getVSH(i)).map(i => ({id:i.id, name:i.name}))
};
dbg("sheet.getData", { base, hull, totals, slotsUsed, componentsCount: components.length });
return data; return data;
} }
// ---- listeners & UI ---- // ---- listeners & UI ----
@@ -343,85 +312,75 @@ data.warnings = warnings;
}); });
} }
// --- robust drop handlers: flag-only storage (survives W&G) ---
async _onDrop(event) {
event.preventDefault();
let raw = {};
try { raw = JSON.parse(event.dataTransfer?.getData("text/plain") || "{}"); }
catch { ui.notifications.error("Drop failed: cannot parse drop payload."); return; }
if (raw?.type === "Item") return this._onDropItem(event, raw);
dbg("drop.ignored", raw);
}
/** Drop itemów na arkusz: tylko komponenty voidship.
* - Jeżeli item ma allowedSlots (broń): przydziel "pierwszy wolny pasujący" i zapisz do system.voidship.slot
* - Jeżeli nie ma allowedSlots: zachowaj istniejący system.voidship.slot (albo slot "Internal" dla nie-broni)
*/
async _onDropItem(event, data) { async _onDropItem(event, data) {
event.preventDefault(); event.preventDefault();
// 1) Rozwiąż źródło dropu (UUID/pack/data) możliwie bezpiecznie // 1) Rozwiąż źródło (uuid/pack/data)
let doc = null; let doc = null;
try { try {
if (data?.uuid) doc = await fromUuid(data.uuid); if (data?.uuid) doc = await fromUuid(data.uuid);
else if (data?.pack && data?._id) { else if (data?.pack && data?._id) {
const pack = game.packs.get(data.pack); const pack = game.packs.get(data.pack);
if (pack) doc = await pack.getDocument(data._id); if (pack) doc = await pack.getDocument(data._id);
} else if (data?.data || data?.type) { } else if (data) {
// Foundry potrafi zrzucać raw "data"
doc = await Item.implementation.fromDropData(data); doc = await Item.implementation.fromDropData(data);
} }
} catch (e) { /* fall-through */ } } catch (e) { /* ignore */ }
// 2) Wyprowadź "raw" do stworzenia osadzonego itemu
const raw = doc?.toObject?.(false) ?? data?.data ?? data; const raw = doc?.toObject?.(false) ?? data?.data ?? data;
if (!raw?.system) { if (!raw?.type) { ui.notifications.error("Drop failed: cannot resolve item data (no UUID/DATA)."); return false; }
ui.notifications.error("Drop failed: cannot resolve item data (no UUID/DATA).");
return false;
}
// 3) Sprawdź, czy to komponent voidship (wymagamy system.voidship) // 2) Czy to komponent?
const isComponent = !!raw.system?.voidship || !!raw.flags?.["wg-voidships-builder"]?.component; const flagsIn = foundry.utils.getProperty(raw, `flags.${VB_MOD}`) || {};
if (!isComponent) { const legacySys = foundry.utils.getProperty(raw, "system.voidship") || null;
const hasCompTag = !!flagsIn.component;
const hasLegacy = !!legacySys;
if (!hasCompTag && !hasLegacy) {
ui.notifications.warn("This item is not a voidship component."); ui.notifications.warn("This item is not a voidship component.");
return false; return false;
} }
// 4) Upewnij się, że istnieje sekcja voidship // 3) Skonstruuj flags.${VB_MOD}.voidship (kanoniczne)
raw.system.voidship = raw.system.voidship || {}; const vshIn = flagsIn.voidship || legacySys || {};
const vsh = raw.system.voidship; const flagsOut = foundry.utils.duplicate(flagsIn);
flagsOut.voidship = foundry.utils.mergeObject({
slot: vshIn.slot || null,
use: { space: 0, power: 0, ...(vshIn.use || {}) },
mods: { armour: 0, integrity: 0, manoeuvre: 0, detection: 0, speed: 0, space: 0, power: 0, ...(vshIn.mods || {}) },
allowedSlots: normalizeAllowedSlots(vshIn.allowedSlots || flagsIn?.component?.allowedSlots || [])
}, {}, { inplace: false });
// 5) Wyciągnij allowedSlots (preferuj z system.voidship.allowedSlots; ewentualnie z flags.component.allowedSlots) // 4) Auto-przydział slotu dla broni (jeśli allowedSlots != pusty)
const fromFlags = foundry.utils.getProperty(raw, "flags.wg-voidships-builder.component.allowedSlots"); const allowed = flagsOut.voidship.allowedSlots || [];
const allowed = normalizeAllowedSlots(vsh.allowedSlots ?? fromFlags);
// 6) Jeżeli to broń (ma ograniczenia slotów), wybierz pierwszy wolny pasujący i wpisz do vsh.slot.
if (allowed.length > 0) { if (allowed.length > 0) {
const hull = this.actor.getFlag("wg-voidships-builder", "hull") || {}; const hull = this.actor.getFlag(VB_MOD, "hull") || {};
const hullSlots = hull.slots || {}; const hullSlots = hull.slots || {};
const used = countUsedWeaponSlots(this.actor); const used = countUsedWeaponSlots(this.actor);
const chosen = pickFirstFreeSlot(allowed, hullSlots, used); const chosen = pickFirstFreeSlot(allowed, hullSlots, used);
if (!chosen) { if (!chosen) {
ui.notifications.warn("No free matching weapon slot for this component."); ui.notifications.warn("No free matching weapon slot for this component.");
return false; return false;
} }
vsh.slot = chosen; // przypisz ostateczny slot montażu flagsOut.voidship.slot = chosen;
} else { } else {
// Nie-broń: zostaw istniejący slot albo nadaj "Internal" // nie-broń
vsh.slot = vsh.slot || "Internal"; flagsOut.voidship.slot = flagsOut.voidship.slot || "Internal";
} }
// 7) Domyślnie traktuj jako "equipped" (zamontowane), jeśli schema na to pozwala // 5) wpisz flags i domyślne "equipped"
if (raw.system.equipped === undefined) raw.system.equipped = true; raw.flags = raw.flags || {};
raw.flags[VB_MOD] = flagsOut;
if (raw.system?.equipped === undefined) {
raw.system = raw.system || {};
raw.system.equipped = true;
}
// 8) Utwórz item na aktorze // 6) Utwórz na aktorze
await this.actor.createEmbeddedDocuments("Item", [raw]); await this.actor.createEmbeddedDocuments("Item", [raw]);
// 9) Przelicz i odśwież
this.render(true); this.render(true);
return true; return true;
} }
} }
// Register the sheet // Register the sheet