mirror of
https://github.com/migatu/vtt_work.git
synced 2026-07-14 21:38:39 +00:00
0.1
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
|
||||
/* global game, ui, Roll, foundry, mergeObject */
|
||||
// Minimal helper for Wrath & Glory voidship play
|
||||
// Reads data/voidships.json and creates Vehicle actors representing starships.
|
||||
// Also provides shield tracking, critical hit table creation, and simple actions.
|
||||
Hooks.once("ready", () => {
|
||||
game.wgVoidships = {
|
||||
async importShips(folderName="Voidships") {
|
||||
const module = game.modules.get("wg-voidships-foundry");
|
||||
const path = `${module?.path}/data/voidships.json`;
|
||||
const ships = await (await fetch(path)).json();
|
||||
let folder = game.folders.find(f => f.type==="Actor" && f.name===folderName);
|
||||
if (!folder) folder = await Folder.create({name: folderName, type: "Actor"});
|
||||
for (const s of ships) {
|
||||
const name = s.name;
|
||||
const data = {
|
||||
name,
|
||||
type: "vehicle",
|
||||
folder: folder.id,
|
||||
system: {
|
||||
mnvr: Math.max(0, s.handling),
|
||||
value: 20,
|
||||
traits: { list: [{name:"voidship"}] },
|
||||
combat: {
|
||||
size: "colossal",
|
||||
speed: s.speedVU || 10, // in VU; informational
|
||||
wounds: { value: 0, bonus: 0, max: s.hull || 20 },
|
||||
resilience: { bonus: 0, total: s.armour || 10 },
|
||||
defence: { bonus: s.defence || 3 },
|
||||
fly: 0
|
||||
},
|
||||
notes: `Mounts: ${s.mounts || ""}\nTraits: ${s.traits || ""}`
|
||||
},
|
||||
flags: {
|
||||
"wg-voidships": {
|
||||
shields: { current: s.shields||0, max: s.shields||0 },
|
||||
speedVU: s.speedVU||10,
|
||||
handling: s.handling||0,
|
||||
cr: s.cr||5,
|
||||
tur: s.tur||0,
|
||||
mounts: s.mounts||"",
|
||||
traits: s.traits||"",
|
||||
components: s.components||[]
|
||||
}
|
||||
},
|
||||
prototypeToken: { name, width: 4, height: 4, displayName: CONST.TOKEN_DISPLAY_MODES.OWNER_HOVER }
|
||||
};
|
||||
const existing = game.actors.find(a => a.name===name);
|
||||
const actor = existing ?? await Actor.create(data);
|
||||
if (existing) await actor.update(data);
|
||||
// Add a simple Item that tracks Void Shields
|
||||
if (!actor.items.find(i => i.name==="Void Shields")) {
|
||||
await actor.createEmbeddedDocuments("Item", [{
|
||||
name: "Void Shields",
|
||||
type: "gear",
|
||||
system: {
|
||||
description: `<p>Tracks starship shield layers (SHD). Current: ${s.shields||0}/${s.shields||0}</p>`,
|
||||
value: 0,
|
||||
rarity: "unique",
|
||||
keywords: "VOIDSHIP"
|
||||
}
|
||||
}]);
|
||||
}
|
||||
}
|
||||
ui.notifications.info(`Imported ${ships.length} voidships into folder "${folderName}".`);
|
||||
},
|
||||
|
||||
async ensureCriticalHitsTable() {
|
||||
const name = "Voidship Critical Hits (d66)";
|
||||
let table = game.tables.getName(name);
|
||||
if (table) return table;
|
||||
const module = game.modules.get("wg-voidships-foundry");
|
||||
const rows = await (await fetch(`${module?.path}/data/critical_hits.json`)).json();
|
||||
const results = rows.map(r => ({
|
||||
type: CONST.TABLE_RESULT_TYPES.TEXT,
|
||||
text: `<b>${r.d66}</b> — <u>${r.name}</u><br/>${r.effect}`,
|
||||
range: [1,1],
|
||||
weight: 1
|
||||
}));
|
||||
table = await RollTable.create({name, results, displayRoll: true, formula: "1d36"});
|
||||
ui.notifications.info("Created roll table: " + name);
|
||||
return table;
|
||||
},
|
||||
|
||||
// Utilities for shields and damage
|
||||
async applyHit(a, hits=1, dmg=0) {
|
||||
// Apply hits to shields first (per-hit, not per-damage)
|
||||
const shields = foundry.utils.getProperty(a, "flags.wg-voidships.shields") ?? {current:0,max:0};
|
||||
let absorbed = 0, remaining = hits;
|
||||
while (remaining>0 && shields.current>0) {
|
||||
shields.current -= 1; absorbed += 1; remaining -= 1;
|
||||
}
|
||||
if (absorbed>0) ui.notifications.info(`${a.name}: ${absorbed} hit(s) absorbed by Void Shields.`);
|
||||
if (remaining>0 && dmg>0) {
|
||||
// Simple damage: reduce by Resilience then apply Wounds
|
||||
const res = foundry.utils.getProperty(a, "system.combat.resilience.total") ?? 10;
|
||||
const net = Math.max(0, dmg - res);
|
||||
if (net>0) {
|
||||
const cur = a.system.combat.wounds.value;
|
||||
await a.update({"system.combat.wounds.value": cur + net});
|
||||
ChatMessage.create({content:`${a.name} suffers <b>${net}</b> HULL damage (after AR ${res}).`});
|
||||
} else {
|
||||
ChatMessage.create({content:`${a.name} shrugs the damage (AR ${res}).`});
|
||||
}
|
||||
}
|
||||
await a.update({"flags.wg-voidships.shields.current": shields.current});
|
||||
},
|
||||
|
||||
async rechargeShields(a, amount=null) {
|
||||
const max = foundry.utils.getProperty(a, "flags.wg-voidships.shields.max") ?? 0;
|
||||
const cur = foundry.utils.getProperty(a, "flags.wg-voidships.shields.current") ?? 0;
|
||||
const val = Math.min(max, amount==null ? max : cur + amount);
|
||||
await a.update({"flags.wg-voidships.shields.current": val});
|
||||
ui.notifications.info(`${a.name} shields set to ${val}/${max}.`);
|
||||
},
|
||||
|
||||
async setShields(a, val) {
|
||||
const max = foundry.utils.getProperty(a, "flags.wg-voidships.shields.max") ?? 0;
|
||||
val = Math.max(0, Math.min(max, val));
|
||||
await a.update({"flags.wg-voidships.shields.current": val});
|
||||
ui.notifications.info(`${a.name} shields now ${val}/${max}.`);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Macros (global helpers)
|
||||
game.wgVoidshipsMacros = {
|
||||
async importVoidships() { return game.wgVoidships.importShips(); },
|
||||
async createCritTable() { return game.wgVoidships.ensureCriticalHitsTable(); },
|
||||
async applyHit() {
|
||||
const a = canvas.tokens.controlled[0]?.actor ?? game.user.character;
|
||||
if (!a) return ui.notifications.warn("Select a voidship token or set a default character.");
|
||||
const hits = Number(await Dialog.prompt({title:"Hits", content:"How many hits to apply?", label:"Apply", callback: html => html.querySelector("input")?.value, render: html => html.append('<input type="number" value="1"/>') })) || 1;
|
||||
const dmg = Number(await Dialog.prompt({title:"Damage", content:"Base damage (after weapon calc)?", label:"Apply", callback: html => html.querySelector("input")?.value, render: html => html.append('<input type="number" value="0"/>') })) || 0;
|
||||
return game.wgVoidships.applyHit(a, hits, dmg);
|
||||
},
|
||||
async rechargeShields() {
|
||||
const a = canvas.tokens.controlled[0]?.actor ?? game.user.character;
|
||||
if (!a) return ui.notifications.warn("Select a voidship token or set a default character.");
|
||||
return game.wgVoidships.rechargeShields(a);
|
||||
},
|
||||
async setShields() {
|
||||
const a = canvas.tokens.controlled[0]?.actor ?? game.user.character;
|
||||
if (!a) return ui.notifications.warn("Select a voidship token or set a default character.");
|
||||
const cur = foundry.utils.getProperty(a, "flags.wg-voidships.shields.current") ?? 0;
|
||||
const max = foundry.utils.getProperty(a, "flags.wg-voidships.shields.max") ?? 0;
|
||||
const val = Number(await Dialog.prompt({title:"Set Shields", content:`0..${max}`, label:"Set", callback: html => html.querySelector("input")?.value, render: html => html.append(`<input type="number" value="${cur}"/>`) })) || cur;
|
||||
return game.wgVoidships.setShields(a, val);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user