mirror of
https://github.com/migatu/vtt_work.git
synced 2026-07-14 21:38:39 +00:00
sync: wg-voidships-builder v1.4.9
This commit is contained in:
@@ -4,6 +4,47 @@
|
||||
const VB_MOD = "wg-voidships-builder";
|
||||
const VB_DEBUG = true; // Debug on
|
||||
function dbg(...args) { if (VB_DEBUG) console.debug("[VB]", ...args); }
|
||||
// --- SLOT HELPERS: normalizacja i przydział "pierwszy wolny pasujący" ---
|
||||
const VB_WEAPON_SLOTS = ["prow", "dorsal", "port", "starboard", "keel"];
|
||||
|
||||
function normalizeSlotName(s) {
|
||||
const v = String(s || "").trim().toLowerCase();
|
||||
return VB_WEAPON_SLOTS.includes(v) ? v : null;
|
||||
}
|
||||
|
||||
function normalizeAllowedSlots(allowed) {
|
||||
if (!allowed) return [];
|
||||
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);
|
||||
}
|
||||
|
||||
function countUsedWeaponSlots(actor) {
|
||||
const used = { prow: 0, dorsal: 0, port: 0, starboard: 0, keel: 0 };
|
||||
for (const it of actor.items ?? []) {
|
||||
const vs = it.system?.voidship;
|
||||
if (!vs) continue;
|
||||
const slot = normalizeSlotName(vs.slot);
|
||||
const eq = (it.system?.equipped ?? true);
|
||||
if (eq && slot && (slot in used)) used[slot] += 1;
|
||||
}
|
||||
return used;
|
||||
}
|
||||
|
||||
function pickFirstFreeSlot(allowed, hullSlots, used) {
|
||||
// Kolejność zgodnie z wymaganiem: "pierwszy wolny pasujący"
|
||||
const order = ["prow", "dorsal", "port", "starboard", "keel"];
|
||||
for (const s of order) {
|
||||
if (!allowed.includes(s)) continue;
|
||||
const cap = Number(hullSlots?.[s] ?? 0);
|
||||
const u = Number(used?.[s] ?? 0);
|
||||
if (u < cap) return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------------- Handlebars helpers ----------------
|
||||
Hooks.once("init", () => {
|
||||
@@ -311,88 +352,73 @@ async _onDrop(event) {
|
||||
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) {
|
||||
const VB_MOD = "wg-voidships-builder";
|
||||
dbg("drop.item.raw", foundry.utils.deepClone(data));
|
||||
event.preventDefault();
|
||||
|
||||
// 1) spróbuj fromUuid
|
||||
let itemDoc = null;
|
||||
if (data?.uuid) {
|
||||
try { itemDoc = await fromUuid(data.uuid); } catch (e) { dbg("fromUuid.error", e); }
|
||||
}
|
||||
// 2) inline data
|
||||
let itemData = itemDoc?.toObject?.() ?? data?.data ?? null;
|
||||
// 3) ostatecznie spróbuj Foundry helpera
|
||||
if (!itemData) {
|
||||
try {
|
||||
const i = await Item.implementation.fromDropData(data);
|
||||
itemData = i?.toObject?.() ?? null;
|
||||
} catch (e) {
|
||||
ui.notifications.error("Drop failed: cannot resolve item data (no UUID/DATA).");
|
||||
dbg("fromDropData.error", e, data); return;
|
||||
// 1) Rozwiąż źródło dropu (UUID/pack/data) możliwie bezpiecznie
|
||||
let doc = null;
|
||||
try {
|
||||
if (data?.uuid) doc = await fromUuid(data.uuid);
|
||||
else if (data?.pack && data?._id) {
|
||||
const pack = game.packs.get(data.pack);
|
||||
if (pack) doc = await pack.getDocument(data._id);
|
||||
} else if (data?.data || data?.type) {
|
||||
// Foundry potrafi zrzucać raw "data"
|
||||
doc = await Item.implementation.fromDropData(data);
|
||||
}
|
||||
}
|
||||
if (!itemData) { ui.notifications.error("Drop failed: no item data."); return; }
|
||||
} catch (e) { /* fall-through */ }
|
||||
|
||||
// 4) Czy to komponent? – akceptujemy po flagach lub po system.voidship
|
||||
const fmod = itemData.flags?.[VB_MOD] || {};
|
||||
const meta = fmod.component || {};
|
||||
const vflag = fmod.voidship || null;
|
||||
const vsys = itemData.system?.voidship || null;
|
||||
const tags = Array.isArray(meta.tags) ? meta.tags : [];
|
||||
const isComponent = !!(vflag || vsys || tags.length);
|
||||
// 2) Wyprowadź "raw" do stworzenia osadzonego itemu
|
||||
const raw = doc?.toObject?.(false) ?? data?.data ?? data;
|
||||
if (!raw?.system) {
|
||||
ui.notifications.error("Drop failed: cannot resolve item data (no UUID/DATA).");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3) Sprawdź, czy to komponent voidship (wymagamy system.voidship)
|
||||
const isComponent = !!raw.system?.voidship || !!raw.flags?.["wg-voidships-builder"]?.component;
|
||||
if (!isComponent) {
|
||||
ui.notifications.warn("This item is not a voidship component.");
|
||||
dbg("notComponent", { name: itemData.name, flags: itemData.flags });
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 5) wyznacz slot + liczby (preferuj to co już jest w flagach; jeśli było tylko w system.voidship – przenieś do flag)
|
||||
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";
|
||||
};
|
||||
// 4) Upewnij się, że istnieje sekcja voidship
|
||||
raw.system.voidship = raw.system.voidship || {};
|
||||
const vsh = raw.system.voidship;
|
||||
|
||||
const vf = vflag || vsys || {};
|
||||
const slot = vf.slot || meta.slot || inferSlotFromTags(meta.tags);
|
||||
const vfixed = {
|
||||
slot,
|
||||
use: {
|
||||
space: Number(vf?.use?.space ?? 0),
|
||||
power: Number(vf?.use?.power ?? 0)
|
||||
},
|
||||
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)
|
||||
// 5) Wyciągnij allowedSlots (preferuj z system.voidship.allowedSlots; ewentualnie z flags.component.allowedSlots)
|
||||
const fromFlags = foundry.utils.getProperty(raw, "flags.wg-voidships-builder.component.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) {
|
||||
const hull = this.actor.getFlag("wg-voidships-builder", "hull") || {};
|
||||
const hullSlots = hull.slots || {};
|
||||
const used = countUsedWeaponSlots(this.actor);
|
||||
|
||||
const chosen = pickFirstFreeSlot(allowed, hullSlots, used);
|
||||
if (!chosen) {
|
||||
ui.notifications.warn("No free matching weapon slot for this component.");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 6) Nie dotykamy schema W&G – zapisujemy w FLAGS
|
||||
itemData.flags = foundry.utils.mergeObject(itemData.flags || {}, {
|
||||
[VB_MOD]: foundry.utils.mergeObject(fmod, { voidship: vfixed }, { inplace: false })
|
||||
}, { inplace: false });
|
||||
|
||||
// 7) dopnij equipped (żeby mody się naliczały od razu)
|
||||
itemData.system = foundry.utils.mergeObject(itemData.system || {}, { equipped: true }, { inplace: false });
|
||||
|
||||
dbg("drop.finalized", { name: itemData.name, vfixed });
|
||||
|
||||
try {
|
||||
const created = await this.actor.createEmbeddedDocuments("Item", [itemData]);
|
||||
dbg("drop.created", created?.map(c => ({ id: c.id, name: c.name })));
|
||||
this.render(true);
|
||||
} catch (e) {
|
||||
ui.notifications.error("Failed to add component to this voidship (see console).");
|
||||
dbg("drop.create.error", e);
|
||||
vsh.slot = chosen; // przypisz ostateczny slot montażu
|
||||
} else {
|
||||
// Nie-broń: zostaw istniejący slot albo nadaj "Internal"
|
||||
vsh.slot = vsh.slot || "Internal";
|
||||
}
|
||||
|
||||
// 7) Domyślnie traktuj jako "equipped" (zamontowane), jeśli schema na to pozwala
|
||||
if (raw.system.equipped === undefined) raw.system.equipped = true;
|
||||
|
||||
// 8) Utwórz item na aktorze
|
||||
await this.actor.createEmbeddedDocuments("Item", [raw]);
|
||||
// 9) Przelicz i odśwież
|
||||
this.render(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user