Migration FOundry v13/v14

This commit is contained in:
2026-04-19 00:43:33 +02:00
parent 89b3e401a4
commit e3002dd602
28 changed files with 4584 additions and 2956 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

8
package.json Normal file
View File

@@ -0,0 +1,8 @@
{
"name": "foundryvtt-mgt2",
"description": "Mongoose Traveller 2nd Edition for FoundryVTT",
"scripts": {
"build": "rollup -c rollup.config.mjs",
"watch": "rollup -c rollup.config.mjs --watch"
}
}

8
rollup.config.mjs Normal file
View File

@@ -0,0 +1,8 @@
export default {
input: 'src/module/core.js',
output: {
file: 'mgt2.bundle.js',
format: 'es',
sourcemap: true,
},
};

View File

@@ -82,6 +82,10 @@ export class TravellerActor extends Actor {
return []; return [];
} }
async recalculateWeight() {
if (this.type === "character") {
return ActorCharacter.recalculateWeight(this);
}
}
} }

View File

@@ -27,7 +27,7 @@ export class ActorCharacter {
// Eject software // Eject software
for (let item of $this.items) { for (let item of $this.items) {
if (item.system.hasOwnProperty("software") && item.system.computerId === d._id) { if (item.system.hasOwnProperty("software") && item.system.computerId === d._id) {
let clone = duplicate(item); let clone = foundry.utils.deepClone(item);
clone.system.software.computerId = ""; clone.system.software.computerId = "";
itemToUpdates.push(clone); itemToUpdates.push(clone);
} }
@@ -41,7 +41,7 @@ export class ActorCharacter {
if (itemToUpdates.length > 0) if (itemToUpdates.length > 0)
await $this.updateEmbeddedDocuments('Item', itemToUpdates); await $this.updateEmbeddedDocuments('Item', itemToUpdates);
await this.recalculateWeight(); await this.recalculateWeight($this);
} }
static async onUpdateDescendantDocuments($this, parent, collection, documents, changes, options, userId) { static async onUpdateDescendantDocuments($this, parent, collection, documents, changes, options, userId) {
@@ -90,7 +90,7 @@ export class ActorCharacter {
for (let computer of computers) { for (let computer of computers) {
let newProcessingUsed = computerChanges[computer._id].processingUsed; let newProcessingUsed = computerChanges[computer._id].processingUsed;
if (computer.system.processingUsed !== newProcessingUsed) { if (computer.system.processingUsed !== newProcessingUsed) {
const cloneComputer = duplicate($this.getEmbeddedDocument("Item", computer._id)); const cloneComputer = foundry.utils.deepClone($this.getEmbeddedDocument("Item", computer._id));
cloneComputer.system.processingUsed = newProcessingUsed; cloneComputer.system.processingUsed = newProcessingUsed;
cloneComputer.system.overload = cloneComputer.system.processingUsed > cloneComputer.system.processing; cloneComputer.system.overload = cloneComputer.system.processingUsed > cloneComputer.system.processing;
updatedComputers.push(cloneComputer); updatedComputers.push(cloneComputer);
@@ -138,7 +138,7 @@ export class ActorCharacter {
} }
if (recalculEncumbrance || recalculWeight) { if (recalculEncumbrance || recalculWeight) {
const cloneActor = duplicate($this); const cloneActor = foundry.utils.deepClone($this);
await this.recalculateArmor($this, cloneActor); await this.recalculateArmor($this, cloneActor);
@@ -163,7 +163,7 @@ export class ActorCharacter {
static async recalculateArmor($this, cloneActor) { static async recalculateArmor($this, cloneActor) {
if (cloneActor === null || cloneActor === undefined) if (cloneActor === null || cloneActor === undefined)
cloneActor = duplicate($this); cloneActor = foundry.utils.deepClone($this);
let armor = 0; let armor = 0;
for (let item of $this.items) { for (let item of $this.items) {
@@ -180,7 +180,7 @@ export class ActorCharacter {
static async recalculateWeight($this, cloneActor) { static async recalculateWeight($this, cloneActor) {
if (cloneActor === null || cloneActor === undefined) if (cloneActor === null || cloneActor === undefined)
cloneActor = duplicate($this); cloneActor = foundry.utils.deepClone($this);
let updatedContainers = []; let updatedContainers = [];
let containerChanges = {}; let containerChanges = {};
@@ -241,8 +241,8 @@ export class ActorCharacter {
let newWeight = containerChanges[container._id].weight; let newWeight = containerChanges[container._id].weight;
let newCount = containerChanges[container._id].count; let newCount = containerChanges[container._id].count;
if (container.system.weight !== newWeight || container.system.count !== newCount) { if (container.system.weight !== newWeight || container.system.count !== newCount) {
//const cloneContainer = duplicate(); //const cloneContainer = foundry.utils.deepClone();
const cloneContainer = duplicate($this.getEmbeddedDocument("Item", container._id)); const cloneContainer = foundry.utils.deepClone($this.getEmbeddedDocument("Item", container._id));
//foundry.utils.setProperty(cloneContainer, "system.weight", newWeight); //foundry.utils.setProperty(cloneContainer, "system.weight", newWeight);
cloneContainer.system.weight = newWeight; cloneContainer.system.weight = newWeight;
cloneContainer.system.count = newCount; cloneContainer.system.count = newCount;

View File

@@ -0,0 +1,4 @@
export { default as MGT2ActorSheet } from "./base-actor-sheet.mjs";
export { default as TravellerCharacterSheet } from "./character-sheet.mjs";
export { default as TravellerVehiculeSheet } from "./vehicule-sheet.mjs";
export { default as TravellerItemSheet } from "./item-sheet.mjs";

View File

@@ -0,0 +1,104 @@
const { HandlebarsApplicationMixin } = foundry.applications.api;
export default class MGT2ActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
constructor(options = {}) {
super(options);
this._sheetMode = this.constructor.SHEET_MODES.PLAY;
}
/** @override */
static DEFAULT_OPTIONS = {
classes: ["mgt2", "sheet", "actor"],
position: {
width: 780,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
window: {
resizable: true,
},
dragDrop: [{ dragSelector: ".drag-item-list", dropSelector: ".drop-item-list" }],
actions: {
toggleSheet: MGT2ActorSheet.#onToggleSheet,
},
}
get isPlayMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY;
return this._sheetMode === this.constructor.SHEET_MODES.PLAY;
}
get isEditMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY;
return this._sheetMode === this.constructor.SHEET_MODES.EDIT;
}
tabGroups = { primary: "stats" }
/** @override */
async _prepareContext() {
const base = await super._prepareContext();
const actor = this.document;
return {
...base,
actor: actor,
// Flat shorthands for template backward-compat (AppV1 style)
name: actor.name,
img: actor.img,
cssClass: this.isEditable ? "editable" : "locked",
system: actor.system,
source: actor.toObject(),
fields: actor.schema.fields,
systemFields: actor.system.schema.fields,
isEditable: this.isEditable,
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isGM: game.user.isGM,
config: CONFIG.MGT2,
};
}
/** @override */
_onRender(context, options) {
super._onRender(context, options);
this._activateTabGroups();
}
_activateTabGroups() {
for (const [group, activeTab] of Object.entries(this.tabGroups)) {
const nav = this.element.querySelector(`nav[data-group="${group}"]`);
if (!nav) continue;
nav.querySelectorAll('[data-tab]').forEach(link => {
link.classList.toggle('active', link.dataset.tab === activeTab);
link.addEventListener('click', event => {
event.preventDefault();
this.tabGroups[group] = link.dataset.tab;
this.render();
});
});
this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab);
});
}
}
/** @override */
_canDragDrop(selector) {
return this.isEditable;
}
static async #onToggleSheet(event) {
event.preventDefault();
this._sheetMode = this.isPlayMode
? this.constructor.SHEET_MODES.EDIT
: this.constructor.SHEET_MODES.PLAY;
this.render();
}
}

View File

@@ -0,0 +1,848 @@
import MGT2ActorSheet from "./base-actor-sheet.mjs";
import { MGT2 } from "../../config.js";
import { MGT2Helper } from "../../helper.js";
import { RollPromptHelper } from "../../roll-prompt.js";
import { CharacterPrompts } from "../../actors/character-prompts.js";
export default class TravellerCharacterSheet extends MGT2ActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "character", "nopad"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "TYPES.Actor.character",
},
actions: {
...super.DEFAULT_OPTIONS.actions,
createItem: TravellerCharacterSheet.#onCreateItem,
editItem: TravellerCharacterSheet.#onEditItem,
deleteItem: TravellerCharacterSheet.#onDeleteItem,
equipItem: TravellerCharacterSheet.#onEquipItem,
itemStorageIn: TravellerCharacterSheet.#onItemStorageIn,
itemStorageOut: TravellerCharacterSheet.#onItemStorageOut,
softwareEject: TravellerCharacterSheet.#onSoftwareEject,
createContainer: TravellerCharacterSheet.#onContainerCreate,
editContainer: TravellerCharacterSheet.#onContainerEdit,
deleteContainer: TravellerCharacterSheet.#onContainerDelete,
roll: TravellerCharacterSheet.#onRoll,
openConfig: TravellerCharacterSheet.#onOpenConfig,
openCharacteristic: TravellerCharacterSheet.#onOpenCharacteristic,
traitCreate: TravellerCharacterSheet.#onTraitCreate,
traitEdit: TravellerCharacterSheet.#onTraitEdit,
traitDelete: TravellerCharacterSheet.#onTraitDelete,
openEditor: TravellerCharacterSheet.#onOpenEditor,
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/mgt2/templates/actors/actor-sheet.html",
},
}
/** @override */
tabGroups = {
primary: "inventory",
characteristics: "core",
inventory: "onhand",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
const actor = this.document;
context.settings = {
weightUnit: "kg",
usePronouns: game.settings.get("mgt2", "usePronouns"),
useGender: game.settings.get("mgt2", "useGender"),
showLife: game.settings.get("mgt2", "showLife"),
};
context.isGM = game.user.isGM;
context.showTrash = false;
context.initiative = actor.getInitiative();
this._prepareCharacterItems(context);
return context;
}
_prepareCharacterItems(context) {
const actor = this.document;
const settings = context.settings;
const items = actor.items;
const weapons = [], armors = [], augments = [], computers = [], softwares = [];
const miscItems = [], equipments = [], containerItems = [], careers = [];
const skills = [], psionics = [], diseases = [], wounds = [], contacts = [];
const actorContainers = [];
for (let i of items) {
if (i.type === "container") {
actorContainers.push(i);
} else if (i.type === "computer") {
computers.push(i);
i._subItems = [];
if (i.system.overload === true)
i._overloadClass = "computer-overload";
}
}
actorContainers.sort(MGT2Helper.compareByName);
const containers = [{ name: "(tous)", _id: "" }].concat(actorContainers);
const containerIndex = new Map();
for (let c of actorContainers) {
containerIndex.set(c._id, c);
if (c.system.weight > 0) {
const w = MGT2Helper.convertWeightForDisplay(c.system.weight) + " " + settings.weightUnit;
c._display = c.name.length > 12 ? `${c.name.substring(0, 12)}... (${w})` : `${c.name} (${w})`;
} else {
c._display = c.name.length > 12 ? c.name.substring(0, 12) + "..." : c.name;
}
if (c.system.onHand === true)
c._subItems = [];
}
const containerView = actor.system.containerView;
let currentContainerView = containerView !== "" ? containerIndex.get(containerView) : null;
context.containerView = currentContainerView || null;
context.containerWeight = currentContainerView
? MGT2Helper.convertWeightForDisplay(currentContainerView.system.weight)
: MGT2Helper.convertWeightForDisplay(0);
context.containerShowAll = containerView === "";
for (let i of items) {
const item = i.system;
if (item.hasOwnProperty("weight") && item.weight > 0) {
i._weight = isNaN(item.quantity)
? MGT2Helper.convertWeightForDisplay(item.weight) + " " + settings.weightUnit
: MGT2Helper.convertWeightForDisplay(item.weight * item.quantity) + " " + settings.weightUnit;
}
if (item.hasOwnProperty("container") && item.container.id !== "" && item.container.id !== undefined) {
const container = containerIndex.get(item.container.id);
if (container === undefined) {
if (context.containerShowAll) {
i._containerName = "#deleted#";
containerItems.push(i);
}
continue;
}
if (container.system.locked && !game.user.isGM) continue;
if (container.system.onHand === true)
container._subItems.push(i);
if (context.containerShowAll || actor.system.containerView === item.container.id) {
i._containerName = container.name;
containerItems.push(i);
}
continue;
}
if (item.hasOwnProperty("equipped")) {
i._canEquip = true;
i._toggleClass = item.equipped ? "active" : "";
} else {
i._canEquip = false;
}
switch (i.type) {
case "equipment":
(i.system.subType === "augment" ? augments : equipments).push(i);
break;
case "armor":
if (i.system.options?.length > 0)
i._subInfo = i.system.options.map(x => x.name).join(", ");
armors.push(i);
break;
case "computer":
if (i.system.options?.length > 0)
i._subInfo = i.system.options.map(x => x.name).join(", ");
break;
case "item":
if (i.system.subType === "software") {
if (i.system.software.computerId && i.system.software.computerId !== "") {
const computer = computers.find(x => x._id === i.system.software.computerId);
if (computer !== undefined) computer._subItems.push(i);
else softwares.push(i);
} else {
i._display = i.system.software.bandwidth > 0
? `${i.name} (${i.system.software.bandwidth})`
: i.name;
softwares.push(i);
}
} else {
miscItems.push(i);
}
break;
case "weapon":
i._range = i.system.range.isMelee
? game.i18n.localize("MGT2.Melee")
: MGT2Helper.getRangeDisplay(i.system.range);
if (i.system.traits?.length > 0)
i._subInfo = i.system.traits.map(x => x.name).join(", ");
weapons.push(i);
break;
case "career":
careers.push(i);
break;
case "contact":
contacts.push(i);
break;
case "disease":
(i.system.subType === "wound" ? wounds : diseases).push(i);
break;
case "talent":
if (i.system.subType === "skill") {
skills.push(i);
} else {
if (MGT2Helper.hasValue(i.system.psionic, "reach"))
i._reach = game.i18n.localize(`MGT2.PsionicReach.${i.system.psionic.reach}`);
if (MGT2Helper.hasValue(i.system.roll, "difficulty"))
i._difficulty = game.i18n.localize(`MGT2.Difficulty.${i.system.roll.difficulty}`);
psionics.push(i);
}
break;
case "container":
if (i.system.onHand === true)
miscItems.push(i);
break;
}
}
const byName = MGT2Helper.compareByName;
const byEquipName = (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase());
context.encumbranceNormal = MGT2Helper.convertWeightForDisplay(actor.system.inventory.encumbrance.normal);
context.encumbranceHeavy = MGT2Helper.convertWeightForDisplay(actor.system.inventory.encumbrance.heavy);
const totalWeight = actor.system.inventory.weight;
if (totalWeight > actor.system.inventory.encumbrance.heavy) {
context.encumbranceClasses = "encumbrance-heavy";
context.encumbrance = 2;
} else if (totalWeight > actor.system.inventory.encumbrance.normal) {
context.encumbranceClasses = "encumbrance-normal";
context.encumbrance = 1;
} else {
context.encumbrance = 0;
}
if (softwares.length > 0) { softwares.sort(byName); context.softwares = softwares; }
augments.sort(byEquipName); context.augments = augments;
armors.sort(byEquipName); context.armors = armors;
computers.sort(byEquipName); context.computers = computers;
context.careers = careers;
contacts.sort(byName); context.contacts = contacts;
containers.sort(byName); context.containers = containers;
diseases.sort(byName); context.diseases = diseases;
context.wounds = wounds;
equipments.sort(byEquipName); context.equipments = equipments;
miscItems.sort(byEquipName); context.items = miscItems;
actorContainers.sort(byName); context.actorContainers = actorContainers;
skills.sort(byName); context.skills = skills;
psionics.sort(byName); context.psionics = psionics;
weapons.sort(byEquipName); context.weapons = weapons;
if (containerItems.length > 0) {
containerItems.sort((a, b) => {
const r = a._containerName.localeCompare(b._containerName);
return r !== 0 ? r : a.name.toLowerCase().localeCompare(b.name.toLowerCase());
});
}
context.containerItems = containerItems;
}
// =========================================================
// Event Binding (AppV2 _onRender — replaces jQuery activateListeners)
// Templates still use CSS class selectors, so we bind manually here.
// =========================================================
/** @override */
_onRender(context, options) {
super._onRender(context, options);
const html = this.element;
if (!this.isEditable) return;
this._bindClassEvent(html, ".roll", "click", TravellerCharacterSheet.#onRoll);
this._bindClassEvent(html, ".cfg-characteristic", "click", TravellerCharacterSheet.#onOpenCharacteristic);
this._bindClassEvent(html, ".item-create", "click", TravellerCharacterSheet.#onCreateItem);
this._bindClassEvent(html, ".item-edit", "click", TravellerCharacterSheet.#onEditItem);
this._bindClassEvent(html, ".item-delete", "click", TravellerCharacterSheet.#onDeleteItem);
this._bindClassEvent(html, ".item-equip", "click", TravellerCharacterSheet.#onEquipItem);
this._bindClassEvent(html, ".item-storage-in", "click", TravellerCharacterSheet.#onItemStorageIn);
this._bindClassEvent(html, ".item-storage-out", "click", TravellerCharacterSheet.#onItemStorageOut);
this._bindClassEvent(html, ".software-eject", "click", TravellerCharacterSheet.#onSoftwareEject);
this._bindClassEvent(html, ".container-create", "click", TravellerCharacterSheet.#onContainerCreate);
this._bindClassEvent(html, ".container-edit", "click", TravellerCharacterSheet.#onContainerEdit);
this._bindClassEvent(html, ".container-delete", "click", TravellerCharacterSheet.#onContainerDelete);
this._bindClassEvent(html, ".traits-create", "click", TravellerCharacterSheet.#onTraitCreate);
this._bindClassEvent(html, ".traits-edit", "click", TravellerCharacterSheet.#onTraitEdit);
this._bindClassEvent(html, ".traits-delete", "click", TravellerCharacterSheet.#onTraitDelete);
this._bindClassEvent(html, "[data-editor='open']", "click", TravellerCharacterSheet.#onOpenEditor);
html.querySelector("[name='config']")?.addEventListener("click", (ev) => TravellerCharacterSheet.#onOpenConfig.call(this, ev, ev.currentTarget));
}
/** Helper: bind a handler to all matching elements, with `this` set to the sheet instance */
_bindClassEvent(html, selector, event, handler) {
for (const el of html.querySelectorAll(selector)) {
el.addEventListener(event, (ev) => handler.call(this, ev, ev.currentTarget));
}
}
// =========================================================
// Drag & Drop
// =========================================================
/** @override */
async _onDrop(event) {
event.preventDefault();
event.stopImmediatePropagation();
const dropData = MGT2Helper.getDataFromDropEvent(event);
if (!dropData) return false;
const sourceItemData = await MGT2Helper.getItemDataFromDropData(dropData);
if (sourceItemData.type === "species") {
const update = {
system: {
personal: {
species: sourceItemData.name,
speciesText: {
description: sourceItemData.system.description,
descriptionLong: sourceItemData.system.descriptionLong,
},
},
},
};
update.system.personal.traits = this.actor.system.personal.traits.concat(sourceItemData.system.traits);
if (sourceItemData.system.modifiers?.length > 0) {
update.system.characteristics = {};
for (let modifier of sourceItemData.system.modifiers) {
if (MGT2Helper.hasValue(modifier, "characteristic") && MGT2Helper.hasValue(modifier, "value")) {
const c = this.actor.system.characteristics[modifier.characteristic];
const updateValue = { value: c.value + modifier.value };
if (c.showMax) updateValue.max = c.max + modifier.value;
update.system.characteristics[modifier.characteristic] = updateValue;
}
}
}
this.actor.update(update);
return true;
}
if (["contact", "disease", "career", "talent"].includes(sourceItemData.type)) {
let transferData = {};
try { transferData = sourceItemData.toJSON(); } catch (e) { transferData = sourceItemData; }
delete transferData._id;
delete transferData.id;
await this.actor.createEmbeddedDocuments("Item", [transferData]);
return true;
}
if (!["armor", "weapon", "computer", "container", "item", "equipment"].includes(sourceItemData.type)) return false;
const target = event.target.closest(".table-row");
let targetId = null;
let targetItem = null;
if (target !== null) {
targetId = target.dataset.itemId;
targetItem = this.actor.getEmbeddedDocument("Item", targetId);
}
let sourceItem = this.actor.getEmbeddedDocument("Item", sourceItemData.id);
if (sourceItem) {
if (!targetItem) return false;
sourceItem = foundry.utils.deepClone(sourceItem);
if (sourceItem._id === targetId) return false;
if (targetItem.type === "item" || targetItem.type === "equipment") {
if (targetItem.system.subType === "software")
sourceItem.system.software.computerId = targetItem.system.software.computerId;
else
sourceItem.system.container.id = targetItem.system.container.id;
this.actor.updateEmbeddedDocuments("Item", [sourceItem]);
return true;
} else if (targetItem.type === "computer") {
sourceItem.system.software.computerId = targetId;
this.actor.updateEmbeddedDocuments("Item", [sourceItem]);
return true;
} else if (targetItem.type === "container") {
if (targetItem.system.locked && !game.user.isGM) {
ui.notifications.error("Verrouillé");
} else {
sourceItem.system.container.id = targetId;
this.actor.updateEmbeddedDocuments("Item", [sourceItem]);
return true;
}
}
} else {
let transferData = {};
try { transferData = sourceItemData.toJSON(); } catch (e) { transferData = sourceItemData; }
delete transferData._id;
delete transferData.id;
const recalcWeight = transferData.system.hasOwnProperty("weight");
if (transferData.system.hasOwnProperty("container")) transferData.system.container.id = "";
if (transferData.type === "item" && transferData.system.subType === "software") transferData.system.software.computerId = "";
if (transferData.type === "container") transferData.system.onHand = true;
if (transferData.system.hasOwnProperty("equipment")) transferData.system.equipped = false;
if (targetItem !== null) {
if (transferData.type === "item" && transferData.system.subType === "software") {
if (targetItem.type === "item" && targetItem.system.subType === "software")
transferData.system.software.computerId = targetItem.system.software.computerId;
else if (targetItem.type === "computer")
transferData.system.software.computerId = targetItem._id;
} else if (["armor", "computer", "equipment", "item", "weapon"].includes(transferData.type)) {
if (targetItem.type === "container") {
if (!targetItem.system.locked || game.user.isGM)
transferData.system.container.id = targetId;
} else {
transferData.system.container.id = targetItem.system.container.id;
}
}
}
await this.actor.createEmbeddedDocuments("Item", [transferData]);
if (recalcWeight) await this.actor.recalculateWeight();
}
return true;
}
// =========================================================
// Actions (static private methods)
// =========================================================
static async #onCreateItem(event, target) {
event.preventDefault();
const data = {
name: target.dataset.createName,
type: target.dataset.typeItem,
};
if (target.dataset.subtype) {
data.system = { subType: target.dataset.subtype };
}
const cls = getDocumentClass("Item");
return cls.create(data, { parent: this.actor });
}
static async #onEditItem(event, target) {
event.preventDefault();
const li = target.closest("[data-item-id]");
const item = this.actor.getEmbeddedDocument("Item", li?.dataset.itemId);
if (item) item.sheet.render(true);
}
static async #onDeleteItem(event, target) {
event.preventDefault();
const li = target.closest("[data-item-id]");
if (!li?.dataset.itemId) return;
this.actor.deleteEmbeddedDocuments("Item", [li.dataset.itemId]);
}
static async #onEquipItem(event, target) {
event.preventDefault();
const li = target.closest("[data-item-id]");
const item = foundry.utils.deepClone(this.actor.getEmbeddedDocument("Item", li?.dataset.itemId));
if (!item) return;
item.system.equipped = !item.system.equipped;
this.actor.updateEmbeddedDocuments("Item", [item]);
}
static async #onItemStorageIn(event, target) {
event.preventDefault();
const li = target.closest("[data-item-id]");
const item = foundry.utils.deepClone(this.actor.getEmbeddedDocument("Item", li?.dataset.itemId));
if (!item) return;
if (item.type === "container") {
item.system.onHand = false;
} else {
const containers = this.actor.getContainers();
let container;
const dropInId = this.actor.system.containerDropIn;
if (!dropInId) {
container = containers.length === 0
? await getDocumentClass("Item").create({ name: "New container", type: "container" }, { parent: this.actor })
: containers[0];
} else {
container = containers.find(x => x._id === dropInId);
}
if (container?.system.locked && !game.user.isGM) {
ui.notifications.error("Objet verrouillé");
return;
}
item.system.container.id = container._id;
}
this.actor.updateEmbeddedDocuments("Item", [item]);
}
static async #onItemStorageOut(event, target) {
event.preventDefault();
const li = target.closest("[data-item-id]");
const item = foundry.utils.deepClone(this.actor.getEmbeddedDocument("Item", li?.dataset.itemId));
if (!item) return;
item.system.container.id = "";
this.actor.updateEmbeddedDocuments("Item", [item]);
}
static async #onSoftwareEject(event, target) {
event.preventDefault();
const li = target.closest("[data-item-id]");
const item = foundry.utils.deepClone(this.actor.getEmbeddedDocument("Item", li?.dataset.itemId));
if (!item) return;
item.system.software.computerId = "";
this.actor.updateEmbeddedDocuments("Item", [item]);
}
static async #onContainerCreate(event) {
event.preventDefault();
const cls = getDocumentClass("Item");
return cls.create({ name: "New container", type: "container" }, { parent: this.actor });
}
static async #onContainerEdit(event) {
event.preventDefault();
const container = this.actor.getEmbeddedDocument("Item", this.actor.system.containerView);
if (container) container.sheet.render(true);
}
static async #onContainerDelete(event) {
event.preventDefault();
const containers = this.actor.getContainers();
const container = containers.find(x => x._id === this.actor.system.containerView);
if (!container) return;
const containerItems = this.actor.items.filter(
x => x.system.hasOwnProperty("container") && x.system.container.id === container._id
);
if (containerItems.length > 0) {
for (let item of containerItems) {
let clone = foundry.utils.deepClone(item);
clone.system.container.id = "";
this.actor.updateEmbeddedDocuments("Item", [clone]);
}
}
const cloneActor = foundry.utils.deepClone(this.actor);
cloneActor.system.containerView = "";
if (cloneActor.system.containerDropIn === container._id) {
cloneActor.system.containerDropIn = "";
const remaining = containers.filter(x => x._id !== container._id);
if (remaining.length > 0) cloneActor.system.containerDropIn = remaining[0]._id;
}
this.actor.deleteEmbeddedDocuments("Item", [container._id]);
this.actor.update(cloneActor);
}
static async #onRoll(event, target) {
event.preventDefault();
const rollOptions = {
rollTypeName: game.i18n.localize("MGT2.RollPrompt.Roll"),
rollObjectName: "",
characteristics: [{ _id: "", name: "" }],
characteristic: "",
skills: [],
skill: "",
fatigue: this.actor.system.states.fatigue,
encumbrance: this.actor.system.states.encumbrance,
difficulty: null,
damageFormula: null,
};
const cardButtons = [];
for (const [key, label] of Object.entries(MGT2.Characteristics)) {
const c = this.actor.system.characteristics[key];
if (c.show) {
rollOptions.characteristics.push({
_id: key,
name: game.i18n.localize(label) + MGT2Helper.getDisplayDM(c.dm),
});
}
}
for (let item of this.actor.items) {
if (item.type === "talent" && item.system.subType === "skill")
rollOptions.skills.push({ _id: item._id, name: item.getRollDisplay() });
}
rollOptions.skills.sort(MGT2Helper.compareByName);
rollOptions.skills = [{ _id: "NP", name: game.i18n.localize("MGT2.Items.NotProficient") }].concat(rollOptions.skills);
let itemObj = null;
let isInitiative = false;
const rollType = target.dataset.roll;
if (rollType === "initiative") {
rollOptions.rollTypeName = game.i18n.localize("MGT2.RollPrompt.InitiativeRoll");
rollOptions.characteristic = this.actor.system.config.initiative;
isInitiative = true;
} else if (rollType === "characteristic") {
rollOptions.characteristic = target.dataset.rollCharacteristic;
rollOptions.rollTypeName = game.i18n.localize("MGT2.RollPrompt.CharacteristicRoll");
rollOptions.rollObjectName = game.i18n.localize(`MGT2.Characteristics.${rollOptions.characteristic}.name`);
} else {
if (rollType === "skill") {
rollOptions.skill = target.dataset.rollSkill;
itemObj = this.actor.getEmbeddedDocument("Item", rollOptions.skill);
rollOptions.rollTypeName = game.i18n.localize("MGT2.RollPrompt.SkillRoll");
rollOptions.rollObjectName = itemObj.name;
} else if (rollType === "psionic") {
rollOptions.rollTypeName = game.i18n.localize("MGT2.RollPrompt.PsionicRoll");
}
if (itemObj === null && target.dataset.itemId) {
itemObj = this.actor.getEmbeddedDocument("Item", target.dataset.itemId);
rollOptions.rollObjectName = itemObj.name;
if (itemObj.type === "weapon") rollOptions.rollTypeName = game.i18n.localize("TYPES.Item.weapon");
else if (itemObj.type === "armor") rollOptions.rollTypeName = game.i18n.localize("TYPES.Item.armor");
else if (itemObj.type === "computer") rollOptions.rollTypeName = game.i18n.localize("TYPES.Item.computer");
}
if (rollType === "psionic" && itemObj) {
rollOptions.rollObjectName = itemObj.name;
if (MGT2Helper.hasValue(itemObj.system.psionic, "duration")) {
cardButtons.push({
label: game.i18n.localize("MGT2.Items.Duration"),
formula: itemObj.system.psionic.duration,
message: {
objectName: itemObj.name,
flavor: "{0} ".concat(game.i18n.localize(`MGT2.Durations.${itemObj.system.psionic.durationUnit}`)),
},
});
}
}
if (itemObj?.system.hasOwnProperty("damage")) {
rollOptions.damageFormula = itemObj.system.damage;
if (itemObj.type === "disease") {
if (itemObj.system.subType === "disease")
rollOptions.rollTypeName = game.i18n.localize("MGT2.DiseaseSubType.disease");
else if (itemObj.system.subType === "poison")
rollOptions.rollTypeName = game.i18n.localize("MGT2.DiseaseSubType.poison");
}
}
if (itemObj?.system.hasOwnProperty("roll")) {
if (MGT2Helper.hasValue(itemObj.system.roll, "characteristic")) rollOptions.characteristic = itemObj.system.roll.characteristic;
if (MGT2Helper.hasValue(itemObj.system.roll, "skill")) rollOptions.skill = itemObj.system.roll.skill;
if (MGT2Helper.hasValue(itemObj.system.roll, "difficulty")) rollOptions.difficulty = itemObj.system.roll.difficulty;
}
}
const userRollData = await RollPromptHelper.roll(rollOptions);
const rollModifiers = [];
const rollFormulaParts = [];
if (userRollData.diceModifier) {
rollFormulaParts.push("3d6", userRollData.diceModifier);
} else {
rollFormulaParts.push("2d6");
}
if (userRollData.characteristic) {
const c = this.actor.system.characteristics[userRollData.characteristic];
rollFormulaParts.push(MGT2Helper.getFormulaDM(c.dm));
rollModifiers.push(game.i18n.localize(`MGT2.Characteristics.${userRollData.characteristic}.name`) + MGT2Helper.getDisplayDM(c.dm));
}
if (userRollData.skill) {
if (userRollData.skill === "NP") {
rollFormulaParts.push("-3");
rollModifiers.push(game.i18n.localize("MGT2.Items.NotProficient"));
} else {
const skillObj = this.actor.getEmbeddedDocument("Item", userRollData.skill);
rollFormulaParts.push(MGT2Helper.getFormulaDM(skillObj.system.level));
rollModifiers.push(skillObj.getRollDisplay());
}
}
if (userRollData.psionic) {
const psionicObj = this.actor.getEmbeddedDocument("Item", userRollData.psionic);
rollFormulaParts.push(MGT2Helper.getFormulaDM(psionicObj.system.level));
rollModifiers.push(psionicObj.getRollDisplay());
}
if (userRollData.timeframes && userRollData.timeframes !== "" && userRollData.timeframes !== "Normal") {
rollModifiers.push(game.i18n.localize(`MGT2.Timeframes.${userRollData.timeframes}`));
rollFormulaParts.push(userRollData.timeframes === "Slower" ? "+2" : "-2");
}
if (userRollData.encumbrance === true) {
rollFormulaParts.push("-2");
rollModifiers.push(game.i18n.localize("MGT2.Actor.Encumbrance") + " -2");
}
if (userRollData.fatigue === true) {
rollFormulaParts.push("-2");
rollModifiers.push(game.i18n.localize("MGT2.Actor.Fatigue") + " -2");
}
if (userRollData.customDM) {
const s = userRollData.customDM.trim();
if (/^[0-9]/.test(s)) rollFormulaParts.push("+");
rollFormulaParts.push(s);
}
if (MGT2Helper.hasValue(userRollData, "difficulty")) rollOptions.difficulty = userRollData.difficulty;
const rollFormula = rollFormulaParts.join("");
if (!Roll.validate(rollFormula)) {
ui.notifications.error(game.i18n.localize("MGT2.Errors.InvalidRollFormula"));
return;
}
let roll = await new Roll(rollFormula, this.actor.getRollData()).roll({ async: true, rollMode: userRollData.rollMode });
if (isInitiative && this.token?.combatant) {
await this.token.combatant.update({ initiative: roll.total });
}
const chatData = {
user: game.user.id,
speaker: this.actor ? ChatMessage.getSpeaker({ actor: this.actor }) : null,
formula: roll._formula,
tooltip: await roll.getTooltip(),
total: Math.round(roll.total * 100) / 100,
type: CONST.CHAT_MESSAGE_TYPES.ROLL,
showButtons: true,
showLifeButtons: false,
showRollRequest: false,
rollTypeName: rollOptions.rollTypeName,
rollObjectName: rollOptions.rollObjectName,
rollModifiers: rollModifiers,
showRollDamage: rollOptions.damageFormula !== null && rollOptions.damageFormula !== "",
cardButtons: cardButtons,
};
if (MGT2Helper.hasValue(rollOptions, "difficulty")) {
chatData.rollDifficulty = rollOptions.difficulty;
chatData.rollDifficultyLabel = MGT2Helper.getDifficultyDisplay(rollOptions.difficulty);
if (roll.total >= MGT2Helper.getDifficultyValue(rollOptions.difficulty))
chatData.rollSuccess = true;
else
chatData.rollFailure = true;
}
const html = await renderTemplate("systems/mgt2/templates/chat/roll.html", chatData);
chatData.content = html;
let flags = null;
if (rollOptions.damageFormula) {
flags = { mgt2: { damage: { formula: rollOptions.damageFormula, rollObjectName: rollOptions.rollObjectName, rollTypeName: rollOptions.rollTypeName } } };
}
if (cardButtons.length > 0) {
if (!flags) flags = { mgt2: {} };
flags.mgt2.buttons = cardButtons;
}
if (flags) chatData.flags = flags;
return roll.toMessage(chatData);
}
static async #onOpenConfig(event) {
event.preventDefault();
const userConfig = await CharacterPrompts.openConfig(this.actor.system);
if (userConfig) this.actor.update({ "system.config": userConfig });
}
static async #onOpenCharacteristic(event, target) {
event.preventDefault();
const name = target.dataset.cfgCharacteristic;
const c = this.actor.system.characteristics[name];
let showAll = false;
for (const value of Object.values(this.actor.system.characteristics)) {
if (!value.show) { showAll = true; break; }
}
const userConfig = await CharacterPrompts.openCharacteristic(
game.i18n.localize(`MGT2.Characteristics.${name}.name`),
c.show, c.showMax, showAll
);
if (userConfig) {
const data = { system: { characteristics: {} } };
data.system.characteristics[name] = { show: userConfig.show, showMax: userConfig.showMax };
if (userConfig.showAll === true) {
for (const [key, value] of Object.entries(this.actor.system.characteristics)) {
if (key !== name && !value.show)
data.system.characteristics[key] = { show: true };
}
}
this.actor.update(data);
}
}
static async #onTraitCreate(event) {
event.preventDefault();
let traits = this.actor.system.personal.traits;
let newTraits;
if (traits.length === 0) {
newTraits = [{ name: "", description: "" }];
} else {
newTraits = [...traits, { name: "", description: "" }];
}
return this.actor.update({ system: { personal: { traits: newTraits } } });
}
static async #onTraitEdit(event, target) {
event.preventDefault();
const element = target.closest("[data-traits-part]");
const index = Number(element.dataset.traitsPart);
const trait = this.actor.system.personal.traits[index];
const result = await CharacterPrompts.openTraitEdit(trait);
const traits = [...this.actor.system.personal.traits];
traits[index] = { ...traits[index], name: result.name, description: result.description };
return this.actor.update({ system: { personal: { traits: traits } } });
}
static async #onTraitDelete(event, target) {
event.preventDefault();
const element = target.closest("[data-traits-part]");
const index = Number(element.dataset.traitsPart);
const traits = foundry.utils.deepClone(this.actor.system.personal.traits);
const newTraits = Object.entries(traits)
.filter(([key]) => Number(key) !== index)
.map(([, value]) => value);
return this.actor.update({ system: { personal: { traits: newTraits } } });
}
static async #onOpenEditor(event) {
event.preventDefault();
await CharacterPrompts.openEditorFullView(
this.actor.system.personal.species,
this.actor.system.personal.speciesText.descriptionLong
);
}
}

View File

@@ -0,0 +1,253 @@
const { HandlebarsApplicationMixin } = foundry.applications.api;
import { MGT2Helper } from "../../helper.js";
export default class TravellerItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["mgt2", "sheet", "item"],
position: { width: 630 },
form: {
submitOnChange: true,
closeOnSubmit: false,
},
window: { resizable: true },
actions: {
careerEventCreate: TravellerItemSheet.#onCareerEventCreate,
careerEventDelete: TravellerItemSheet.#onCareerEventDelete,
optionCreate: TravellerItemSheet.#onOptionCreate,
optionDelete: TravellerItemSheet.#onOptionDelete,
modifierCreate: TravellerItemSheet.#onModifierCreate,
modifierDelete: TravellerItemSheet.#onModifierDelete,
},
}
/** @override */
static PARTS = {
sheet: {
// template is dynamic — resolved in _prepareContext / _renderHTML
template: "",
},
}
/** Resolve template dynamically based on item type */
get template() {
return `systems/mgt2/templates/items/${this.document.type}-sheet.html`;
}
tabGroups = { primary: "tab1" }
/** @override */
async _prepareContext() {
const item = this.document;
const source = item.toObject();
const settings = {
usePronouns: game.settings.get("mgt2", "usePronouns"),
};
let containers = null;
let computers = null;
let hadContainer = false;
if (item.actor !== null) {
hadContainer = true;
containers = [{ name: "", _id: "" }].concat(item.actor.getContainers());
computers = [{ name: "", _id: "" }].concat(item.actor.getComputers());
}
let weight = null;
if (item.system.hasOwnProperty("weight")) {
weight = MGT2Helper.convertWeightForDisplay(item.system.weight);
}
let skills = [];
if (this.actor !== null) {
for (let actorItem of this.actor.items) {
if (actorItem.type === "talent" && actorItem.system.subType === "skill")
skills.push({ _id: actorItem._id, name: actorItem.getRollDisplay() });
}
}
skills.sort(MGT2Helper.compareByName);
skills = [{ _id: "NP", name: game.i18n.localize("MGT2.Items.NotProficient") }].concat(skills);
return {
item: item,
document: item,
cssClass: this.isEditable ? "editable" : "locked",
system: item.system,
source: source.system,
fields: item.schema.fields,
systemFields: item.system.schema.fields,
isEditable: this.isEditable,
isGM: game.user.isGM,
config: CONFIG,
settings: settings,
containers: containers,
computers: computers,
hadContainer: hadContainer,
weight: weight,
unitlabels: { weight: MGT2Helper.getWeightLabel() },
skills: skills,
};
}
/** @override — resolve the per-type template before rendering */
async _renderHTML(context, options) {
const templatePath = `systems/mgt2/templates/items/${this.document.type}-sheet.html`;
const html = await renderTemplate(templatePath, context);
return { sheet: html };
}
/** @override — put rendered HTML into the window content */
_replaceHTML(result, content, options) {
content.innerHTML = result.sheet;
this._activateTabGroups();
this._bindItemEvents();
}
/** Bind CSS class-based events (templates not yet migrated to data-action) */
_bindItemEvents() {
const html = this.element;
if (!this.isEditable) return;
const bind = (sel, handler) => {
for (const el of html.querySelectorAll(sel)) {
el.addEventListener("click", (ev) => handler.call(this, ev, ev.currentTarget));
}
};
bind(".event-create", TravellerItemSheet.#onCareerEventCreate);
bind(".event-delete", TravellerItemSheet.#onCareerEventDelete);
bind(".options-create", TravellerItemSheet.#onOptionCreate);
bind(".options-delete", TravellerItemSheet.#onOptionDelete);
bind(".modifiers-create", TravellerItemSheet.#onModifierCreate);
bind(".modifiers-delete", TravellerItemSheet.#onModifierDelete);
}
_activateTabGroups() {
for (const [group, activeTab] of Object.entries(this.tabGroups)) {
const nav = this.element.querySelector(`nav[data-group="${group}"], .horizontal-tabs`);
if (!nav) continue;
nav.querySelectorAll('[data-tab]').forEach(link => {
link.classList.toggle('active', link.dataset.tab === activeTab);
link.addEventListener('click', event => {
event.preventDefault();
this.tabGroups[group] = link.dataset.tab;
this.render();
});
});
this.element.querySelectorAll(`.itemsheet-panel [data-tab], [data-group="${group}"][data-tab]`).forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab);
});
}
}
/** @override — process form data before submit (weight/qty/cost conversions + container logic) */
_prepareSubmitData(event, form, formData) {
const data = foundry.utils.expandObject(formData.object);
if (data.hasOwnProperty("weight")) {
data.system = data.system || {};
data.system.weight = MGT2Helper.convertWeightFromInput(data.weight);
delete data.weight;
}
if (data.system?.hasOwnProperty("quantity")) {
data.system.quantity = MGT2Helper.getIntegerFromInput(data.system.quantity);
}
if (data.system?.hasOwnProperty("cost")) {
data.system.cost = MGT2Helper.getIntegerFromInput(data.system.cost);
}
// Container/equipped logic
if (data.system?.hasOwnProperty("container") && this.document.system.hasOwnProperty("equipped")) {
const equippedChange = this.document.system.equipped !== data.system.equipped;
const containerChange = this.document.system.container?.id !== data.system.container?.id;
if (equippedChange && data.system.equipped === true) {
data.system.container = { id: "" };
} else if (containerChange && data.system.container?.id !== "" && this.document.system.container?.id === "") {
data.system.equipped = false;
}
}
return foundry.utils.flattenObject(data);
}
// =========================================================
// Actions
// =========================================================
static async #onCareerEventCreate(event) {
event.preventDefault();
const events = this.document.system.events;
let newEvents;
if (!events || events.length === 0) {
newEvents = [{ age: "", description: "" }];
} else {
newEvents = [...events, { age: "", description: "" }];
}
return this.document.update({ system: { events: newEvents } });
}
static async #onCareerEventDelete(event, target) {
event.preventDefault();
const element = target.closest("[data-events-part]");
const index = Number(element.dataset.eventsPart);
const events = foundry.utils.deepClone(this.document.system.events);
const newEvents = Object.entries(events)
.filter(([key]) => Number(key) !== index)
.map(([, val]) => val);
return this.document.update({ system: { events: newEvents } });
}
static async #onOptionCreate(event, target) {
event.preventDefault();
const property = target.dataset.property;
const options = this.document.system[property];
let newOptions;
if (!options || options.length === 0) {
newOptions = [{ name: "", description: "" }];
} else {
newOptions = [...options, { name: "", description: "" }];
}
return this.document.update({ [`system.${property}`]: newOptions });
}
static async #onOptionDelete(event, target) {
event.preventDefault();
const element = target.closest("[data-options-part]");
const property = element.dataset.property;
const index = Number(element.dataset.optionsPart);
const options = foundry.utils.deepClone(this.document.system[property]);
const newOptions = Object.entries(options)
.filter(([key]) => Number(key) !== index)
.map(([, val]) => val);
return this.document.update({ [`system.${property}`]: newOptions });
}
static async #onModifierCreate(event) {
event.preventDefault();
const modifiers = this.document.system.modifiers;
let newModifiers;
if (!modifiers || modifiers.length === 0) {
newModifiers = [{ characteristic: "Endurance", value: null }];
} else {
newModifiers = [...modifiers, { characteristic: "Endurance", value: null }];
}
return this.document.update({ system: { modifiers: newModifiers } });
}
static async #onModifierDelete(event, target) {
event.preventDefault();
const element = target.closest("[data-modifiers-part]");
const index = Number(element.dataset.modifiersPart);
const modifiers = foundry.utils.deepClone(this.document.system.modifiers);
const newModifiers = Object.entries(modifiers)
.filter(([key]) => Number(key) !== index)
.map(([, val]) => val);
return this.document.update({ system: { modifiers: newModifiers } });
}
}

View File

@@ -0,0 +1,24 @@
import MGT2ActorSheet from "./base-actor-sheet.mjs";
export default class TravellerVehiculeSheet extends MGT2ActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "vehicule", "nopad"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "TYPES.Actor.vehicule",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/mgt2/templates/actors/vehicule-sheet.html",
},
}
/** @override */
tabGroups = { primary: "stats" }
}

View File

@@ -1,5 +1,6 @@
import { import {
CharacterData, CharacterData,
VehiculeData,
ItemData, ItemData,
EquipmentData, EquipmentData,
DiseaseData, DiseaseData,
@@ -11,13 +12,12 @@ import {
WeaponData, WeaponData,
ItemContainerData, ItemContainerData,
SpeciesData SpeciesData
} from "./datamodels.js"; } from "./models/index.mjs";
import { MGT2 } from "./config.js"; import { MGT2 } from "./config.js";
import { TravellerActor, MGT2Combatant } from "./actors/actor.js"; import { TravellerActor, MGT2Combatant } from "./actors/actor.js";
import { TravellerItem } from "./item.js"; import { TravellerItem } from "./item.js";
import { TravellerItemSheet } from "./item-sheet.js"; import { TravellerItemSheet, TravellerCharacterSheet, TravellerVehiculeSheet } from "./applications/sheets/_module.mjs";
import { TravellerActorSheet } from "./actors/character-sheet.js";
import { preloadHandlebarsTemplates } from "./templates.js"; import { preloadHandlebarsTemplates } from "./templates.js";
//import { MGT2Helper } from "./helper.js"; //import { MGT2Helper } from "./helper.js";
import {ChatHelper} from "./chatHelper.js"; import {ChatHelper} from "./chatHelper.js";
@@ -88,14 +88,16 @@ Hooks.once("init", async function () {
CONFIG.Actor.documentClass = TravellerActor; CONFIG.Actor.documentClass = TravellerActor;
CONFIG.Item.documentClass = TravellerItem; CONFIG.Item.documentClass = TravellerItem;
Actors.unregisterSheet("core", ActorSheet); foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
Actors.registerSheet("mgt2", TravellerActorSheet, { types: ["character"], makeDefault: true, label: "Traveller Sheet" }); foundry.documents.collections.Actors.registerSheet("mgt2", TravellerCharacterSheet, { types: ["character"], makeDefault: true, label: "Traveller Sheet" });
foundry.documents.collections.Actors.registerSheet("mgt2", TravellerVehiculeSheet, { types: ["vehicule"], makeDefault: true, label: "Vehicule Sheet" });
Items.unregisterSheet("core", ItemSheet); foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
Items.registerSheet("mgt2", TravellerItemSheet, { makeDefault: true }); foundry.documents.collections.Items.registerSheet("mgt2", TravellerItemSheet, { makeDefault: true });
Object.assign(CONFIG.Actor.dataModels, { Object.assign(CONFIG.Actor.dataModels, {
"character": CharacterData "character": CharacterData,
"vehicule": VehiculeData
}); });
Object.assign(CONFIG.Item.dataModels, { Object.assign(CONFIG.Item.dataModels, {

View File

@@ -1,485 +0,0 @@
// https://foundryvtt.com/article/system-data-models/
// https://foundryvtt.com/api/classes/foundry.data.fields.NumberField.html
// https://foundryvtt.com/api/v10/classes/foundry.data.fields.DataField.html
const fields = foundry.data.fields;
export class CharacterData extends foundry.abstract.TypeDataModel {
static defineSchema() {
// XP
return {
name: new fields.StringField({ required: false, blank: false, trim: true }),
life: new fields.SchemaField({
value: new fields.NumberField({ required: false, initial: 0, integer: true }),
max: new fields.NumberField({ required: true, initial: 0, integer: true })
}),
personal: new fields.SchemaField({
title: new fields.StringField({ required: false, blank: true, trim: true }),
species: new fields.StringField({ required: false, blank: true, trim: true }),
speciesText: new fields.SchemaField({
description: new fields.StringField({ required: false, blank: true, trim: true, nullable: true }),
descriptionLong: new fields.HTMLField({ required: false, blank: true, trim: true })
}),
age: new fields.StringField({ required: false, blank: true, trim: true }),
gender: new fields.StringField({ required: false, blank: true, trim: true }),
pronouns: new fields.StringField({ required: false, blank: true, trim: true }),
homeworld: new fields.StringField({ required: false, blank: true, trim: true }),
ucp: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
traits: new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
)
}),
biography: new fields.HTMLField({ required: false, blank: true, trim: true }),
characteristics: new fields.SchemaField({
strength: createCharacteristicField(true, true),
dexterity: createCharacteristicField(true, true),
endurance: createCharacteristicField(true, true),
intellect: createCharacteristicField(true, false),
education: createCharacteristicField(true, false),
social: createCharacteristicField(true, false),
morale: createCharacteristicField(true, false),
luck: createCharacteristicField(true, false),
sanity: createCharacteristicField(true, false),
charm: createCharacteristicField(true, false),
psionic: createCharacteristicField(true, false),
other: createCharacteristicField(true, false)
}),
health: new fields.SchemaField({
radiations: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true })
}),
study: new fields.SchemaField({
skill: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
total: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true }),
completed: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true })
}),
finance: new fields.SchemaField({
pension: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
credits: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
cashOnHand: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
debt: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
livingCost: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
monthlyShipPayments: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
notes: new fields.StringField({ required: false, blank: true, trim: true, initial: "" })
}),
containerView: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
containerDropIn: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
notes: new fields.HTMLField({ required: false, blank: true, trim: true }),
inventory: new fields.SchemaField({
armor: new fields.NumberField({ required: true, initial: 0, integer: true }),
weight: new fields.NumberField({ required: true, initial: 0, min: 0, integer: false }),
encumbrance: new fields.SchemaField({
normal: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
heavy: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true })
})
}),
states: new fields.SchemaField({
encumbrance: new fields.BooleanField({ required: false, initial: false }),
fatigue: new fields.BooleanField({ required: false, initial: false }),
unconscious: new fields.BooleanField({ required: false, initial: false }),
surgeryRequired: new fields.BooleanField({ required: false, initial: false })
}),
config: new fields.SchemaField({
psionic: new fields.BooleanField({ required: false, initial: true }),
initiative: new fields.StringField({ required: false, blank: true, initial: "dexterity" }),
damages: new fields.SchemaField({
rank1: new fields.StringField({ required: false, blank: true, initial: "strength" }),
rank2: new fields.StringField({ required: false, blank: true, initial: "dexterity" }),
rank3: new fields.StringField({ required: false, blank: true, initial: "endurance" })
})
})
};
}
}
// export class CreatureData extends foundry.abstract.TypeDataModel {
// static defineSchema() {
// return {
// name: new fields.StringField({ required: false, blank: false, trim: true }),
// TL: new fields.StringField({ required: true, blank: false, initial: "NA" }),
// species: new fields.StringField({ required: false, blank: true, trim: true }),
// //cost: new fields.NumberField({ required: true, integer: true }),
// armor: new fields.NumberField({ required: false, initial: 0, integer: true }),
// life: new fields.SchemaField({
// value: new fields.NumberField({ required: false, initial: 0, integer: true }),
// max: new fields.NumberField({ required: true, initial: 0, integer: true })
// }),
// speed: new fields.StringField({ required: false, initial: "4m", blank: true, trim: true }),
// traits: new fields.ArrayField(
// new fields.SchemaField({
// name: new fields.StringField({ required: true, blank: true, trim: true }),
// description: new fields.StringField({ required: false, blank: true, trim: true })
// })
// ),
// description: new fields.HTMLField({ required: false, blank: true, trim: true }),
// behaviour: new fields.StringField({ required: false, blank: true, trim: true })
// }
// };
// }
// export class NPCData extends CreatureData {
// static defineSchema() {
// const schema = super.defineSchema();
// // Species, Gender, Age
// // STR, DEX, END, INT,. EDU, SOC, PSI, SKILL/Psy, equipment
// // Status
// schema.secret = new fields.HTMLField({ required: false, blank: true, trim: true });
// return schema;
// }
// }
export class VehiculeData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
name: new fields.StringField({ required: false, blank: false, trim: true }),
skillId: new fields.StringField({ required: false, initial: "", blank: true, trim: true }),
speed: new fields.SchemaField({
cruise: new fields.StringField({ required: false, initial: "Slow", blank: true }),
maximum: new fields.StringField({ required: false, initial: "Medium", blank: true })
}),
agility: new fields.NumberField({ required: false, min: 0, integer: true }),
crew: new fields.NumberField({ required: false, min: 0, integer: true }),
passengers: new fields.NumberField({ required: false, min: 0, integer: true }),
cargo: new fields.NumberField({ required: false, min: 0, integer: false }),
//hull
life: new fields.SchemaField({
value: new fields.NumberField({ required: true, initial: 0, integer: true }),
max: new fields.NumberField({ required: true, initial: 0, integer: true })
}),
shipping: new fields.NumberField({ required: false, min: 0, integer: true }),
cost: new fields.NumberField({ required: false, min: 0, integer: true }),
armor: new fields.SchemaField({
front: new fields.NumberField({ required: true, initial: 0, integer: true }),
rear: new fields.NumberField({ required: true, initial: 0, integer: true }),
sides: new fields.NumberField({ required: true, initial: 0, integer: true })
}),
skills: new fields.SchemaField({
// Skill Level
autopilot: new fields.NumberField({ required: true, initial: 0, integer: true })
// Communication Range
// Navigation
// Sensors
// Camouflage / Recon
// Stealth
})
// config: new fields.SchemaField({
// })
};
}
}
class ItemBaseData extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const schema = {
//name: new fields.StringField({ required: true, blank: true, trim: true, nullable: true }),
description: new fields.StringField({ required: false, blank: true, trim: true, nullable: true }),
//type: new fields.StringField({ required: false, blank: false }),
subType: new fields.StringField({ required: false, blank: false, nullable: true })
};
return schema;
}
}
class PhysicalItemData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.quantity = new fields.NumberField({ required: true, initial: 1, min: 0, integer: true });
schema.weight = new fields.NumberField({ required: true, initial: 0, min: 0, integer: false });
schema.weightless = new fields.BooleanField({ required: false, initial: false });
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true });
schema.tl = new fields.StringField({ required: true, blank: false, initial: "TL12" });
schema.container = new fields.SchemaField({
//inContainer: new fields.BooleanField({ required: false, initial: false }),
id: new fields.StringField({ required: false, blank: true })
});
schema.roll = new fields.SchemaField({
characteristic: new fields.StringField({ required: false, blank: true, trim: true }),
skill: new fields.StringField({ required: false, blank: true, trim: true }),
difficulty: new fields.StringField({ required: false, blank: true, trim: true })
});
schema.trash = new fields.BooleanField({ required: false, initial: false });
return schema;
}
}
export class ItemData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "loot";
schema.software = new fields.SchemaField({
bandwidth: new fields.NumberField({ required: false, initial: 0, min: 0, max: 10, integer: true }),
effect: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
computerId: new fields.StringField({ required: false, blank: true, initial: "" })
});
return schema;
}
}
export class EquipmentData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
// augment, clothes
schema.equipped = new fields.BooleanField({ required: false, initial: false });
//schema.skillModifier = new fields.StringField({ required: false, blank: true });
//schema.characteristicModifier = new fields.StringField({ required: false, blank: true });
schema.augment = new fields.SchemaField({
improvement: new fields.StringField({ required: false, blank: true, trim: true })
});
schema.subType.initial = "equipment"; // augment, clothing, trinket, toolkit, equipment
return schema;
}
}
export class DiseaseData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "disease"; // disease;poison
schema.difficulty = new fields.StringField({ required: true, initial: "Average" });
schema.damage = new fields.StringField({ required: false, blank: true });
schema.interval = new fields.StringField({ required: false, blank: true });
return schema;
}
}
export class CareerData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.difficulty = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true });
schema.damage = new fields.StringField({ required: false, blank: true });
schema.interval = new fields.StringField({ required: false, blank: true });
schema.assignment = new fields.StringField({ required: false, blank: true });
schema.terms = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.rank = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.events = new fields.ArrayField(
new fields.SchemaField({
age: new fields.NumberField({ required: false, integer: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
}
export class TalentData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "skill";
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true })
schema.level = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true })
schema.skill = new fields.SchemaField({
speciality: new fields.StringField({ required: false, blank: true, trim: true }),
reduceEncumbrance: new fields.BooleanField({ required: false, initial: false })
});
schema.psionic = new fields.SchemaField({
reach: new fields.StringField({ required: false, blank: true, trim: true }),
cost: new fields.NumberField({ required: false, initial: 1, min: 0, integer: true }),
duration: new fields.StringField({ required: false, blank: true, trim: true }),
durationUnit: new fields.StringField({ required: false })
});
schema.roll = new fields.SchemaField({
characteristic: new fields.StringField({ required: false, blank: true, trim: true }),
skill: new fields.StringField({ required: false, blank: true, trim: true }),
difficulty: new fields.StringField({ required: false, blank: true, trim: true })
});
return schema;
}
}
export class ContactData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "skill";
schema.cost = new fields.NumberField({ required: true, initial: 1, min: 0, integer: true })
schema.skill = new fields.SchemaField({
speciality: new fields.StringField({ required: false, blank: true, trim: true }),
characteristic: new fields.StringField({ required: false, blank: true, trim: true })
});
schema.status = new fields.StringField({ required: false, blank: true, trim: true, initial: "Alive" });
schema.attitude = new fields.StringField({ required: false, blank: true, trim: true, initial: "Unknow" });
schema.relation = new fields.StringField({ required: false, blank: true, trim: true, initial: "Contact" });
schema.title = new fields.StringField({ required: false, blank: true, trim: true });
schema.nickname = new fields.StringField({ required: false, blank: true, trim: true });
schema.species = new fields.StringField({ required: false, blank: true, trim: true });
schema.gender = new fields.StringField({ required: false, blank: true, trim: true });
schema.pronouns = new fields.StringField({ required: false, blank: true, trim: true });
schema.homeworld = new fields.StringField({ required: false, blank: true, trim: true });
schema.location = new fields.StringField({ required: false, blank: true, trim: true });
schema.occupation = new fields.StringField({ required: false, blank: true, trim: true });
schema.notes = new fields.HTMLField({ required: false, blank: true, trim: true });
return schema;
}
}
export class WeaponData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.equipped = new fields.BooleanField({ required: false, initial: false });
schema.range = new fields.SchemaField({
isMelee: new fields.BooleanField({ required: false, initial: false }),
value: new fields.NumberField({ required: false, integer: true, nullable: true }),
unit: new fields.StringField({ required: false, blank: true, nullable: true })
}),
//schema.tons = new fields.NumberField({ required: false, initial: 0, min: 0, integer: false });
schema.damage = new fields.StringField({ required: false, blank: true, trim: true });
schema.magazine = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.magazineCost = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.traits = new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
);
schema.options = new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
}
export class ArmorData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.equipped = new fields.BooleanField({ required: false, initial: false });
schema.radiations = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.protection = new fields.StringField({ required: false, blank: false, trim: true });
// Some armours have a required skill. A Traveller suffers DM-1 to all checks taken in the armour per missing
// skill level. For example, a Traveller with Vacc Suit skill 0 who is in a suit that requires Vacc Suit 2 would have
// DM-2 to all their checks. Not having the skill at all inflicts the usual DM-3 unskilled penalty instead.
schema.requireSkill = new fields.StringField({ required: false, blank: false });
schema.requireSkillLevel = new fields.NumberField({ required: false, min: 0, integer: true });
//requirements: new fields.StringField({ required: false, blank: false, trim: true }),
// As powered armour, battle dress supports its own weight. While powered and active, the mass of battle dress
// does not count against the encumbrance of the wearer and is effectively weightless.
schema.powered = new fields.BooleanField({ required: false, initial: false });
schema.options = new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
);
// Characteristics Modifiers (Pirate of Drinax - ASLAN BATTLE DRESS STR/DEX, Slot)
return schema;
}
}
export class ComputerData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.processing = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.processingUsed = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.overload = new fields.BooleanField({ required: false, initial: false });
//schema.softwares = new fields.ArrayField(new fields.StringField({ required: false, blank: true, trim: true }));
schema.options = new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
}
export class SoftwareData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.bandwidth = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.inUse = new fields.BooleanField({ required: false, initial: false });
schema.computer = new fields.StringField({ required: false, blank: true, nullable: true });
return schema;
}
}
export class SpeciesData extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const schema = {
description: new fields.StringField({ required: false, blank: true, trim: true, nullable: true }),
descriptionLong: new fields.HTMLField({ required: false, blank: true, trim: true }),
traits: new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
),
modifiers: new fields.ArrayField(
new fields.SchemaField({
characteristic: new fields.StringField({ required: false, blank: true, trim: true }),
value: new fields.NumberField({ required: false, integer: true, nullable: true })
})
)
};
return schema;
}
}
export class ItemContainerData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.onHand = new fields.BooleanField({ required: false, initial: false });
schema.location = new fields.StringField({ required: false, blank: true, trim: true });
schema.count = new fields.NumberField({ required: false, initial: 0, integer: true });
schema.weight = new fields.NumberField({ required: false, initial: 0, integer: false });
schema.weightless = new fields.BooleanField({ required: false, initial: false });
schema.locked = new fields.BooleanField({ required: false, initial: false }); // GM only
schema.lockedDescription = new fields.StringField({ required: false, blank: true, trim: true, nullable: true });
return schema;
}
}
function createCharacteristicField(show = true, showMax = false) {
return new fields.SchemaField({
value: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
max: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true }),
dm: new fields.NumberField({ required: false, initial: 0, integer: true }),
show: new fields.BooleanField({ required: false, initial: show }),
showMax: new fields.BooleanField({ required: false, initial: showMax })
});
}

View File

@@ -0,0 +1,96 @@
import { createCharacteristicField } from "./items/base-item.mjs";
const fields = foundry.data.fields;
export default class CharacterData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
name: new fields.StringField({ required: false, blank: false, trim: true }),
life: new fields.SchemaField({
value: new fields.NumberField({ required: false, initial: 0, integer: true }),
max: new fields.NumberField({ required: true, initial: 0, integer: true })
}),
personal: new fields.SchemaField({
title: new fields.StringField({ required: false, blank: true, trim: true }),
species: new fields.StringField({ required: false, blank: true, trim: true }),
speciesText: new fields.SchemaField({
description: new fields.StringField({ required: false, blank: true, trim: true, nullable: true }),
descriptionLong: new fields.HTMLField({ required: false, blank: true, trim: true })
}),
age: new fields.StringField({ required: false, blank: true, trim: true }),
gender: new fields.StringField({ required: false, blank: true, trim: true }),
pronouns: new fields.StringField({ required: false, blank: true, trim: true }),
homeworld: new fields.StringField({ required: false, blank: true, trim: true }),
ucp: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
traits: new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
)
}),
biography: new fields.HTMLField({ required: false, blank: true, trim: true }),
characteristics: new fields.SchemaField({
strength: createCharacteristicField(true, true),
dexterity: createCharacteristicField(true, true),
endurance: createCharacteristicField(true, true),
intellect: createCharacteristicField(true, false),
education: createCharacteristicField(true, false),
social: createCharacteristicField(true, false),
morale: createCharacteristicField(true, false),
luck: createCharacteristicField(true, false),
sanity: createCharacteristicField(true, false),
charm: createCharacteristicField(true, false),
psionic: createCharacteristicField(true, false),
other: createCharacteristicField(true, false)
}),
health: new fields.SchemaField({
radiations: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true })
}),
study: new fields.SchemaField({
skill: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
total: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true }),
completed: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true })
}),
finance: new fields.SchemaField({
pension: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
credits: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
cashOnHand: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
debt: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
livingCost: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
monthlyShipPayments: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
notes: new fields.StringField({ required: false, blank: true, trim: true, initial: "" })
}),
containerView: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
containerDropIn: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
notes: new fields.HTMLField({ required: false, blank: true, trim: true }),
inventory: new fields.SchemaField({
armor: new fields.NumberField({ required: true, initial: 0, integer: true }),
weight: new fields.NumberField({ required: true, initial: 0, min: 0, integer: false }),
encumbrance: new fields.SchemaField({
normal: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
heavy: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true })
})
}),
states: new fields.SchemaField({
encumbrance: new fields.BooleanField({ required: false, initial: false }),
fatigue: new fields.BooleanField({ required: false, initial: false }),
unconscious: new fields.BooleanField({ required: false, initial: false }),
surgeryRequired: new fields.BooleanField({ required: false, initial: false })
}),
config: new fields.SchemaField({
psionic: new fields.BooleanField({ required: false, initial: true }),
initiative: new fields.StringField({ required: false, blank: true, initial: "dexterity" }),
damages: new fields.SchemaField({
rank1: new fields.StringField({ required: false, blank: true, initial: "strength" }),
rank2: new fields.StringField({ required: false, blank: true, initial: "dexterity" }),
rank3: new fields.StringField({ required: false, blank: true, initial: "endurance" })
})
})
};
}
}

View File

@@ -0,0 +1,16 @@
// Actor DataModels
export { default as CharacterData } from "./character.mjs";
export { default as VehiculeData } from "./vehicule.mjs";
// Item DataModels
export { default as ItemData } from "./items/item.mjs";
export { default as EquipmentData } from "./items/equipment.mjs";
export { default as DiseaseData } from "./items/disease.mjs";
export { default as CareerData } from "./items/career.mjs";
export { default as TalentData } from "./items/talent.mjs";
export { default as ContactData } from "./items/contact.mjs";
export { default as WeaponData } from "./items/weapon.mjs";
export { default as ArmorData } from "./items/armor.mjs";
export { default as ComputerData } from "./items/computer.mjs";
export { default as ItemContainerData } from "./items/container.mjs";
export { default as SpeciesData } from "./items/species.mjs";

View File

@@ -0,0 +1,23 @@
import { PhysicalItemData } from "./base-item.mjs";
const fields = foundry.data.fields;
export default class ArmorData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.equipped = new fields.BooleanField({ required: false, initial: false });
schema.radiations = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.protection = new fields.StringField({ required: false, blank: false, trim: true });
// A Traveller suffers DM-1 to all checks per missing skill level in the required skill.
schema.requireSkill = new fields.StringField({ required: false, blank: false });
schema.requireSkillLevel = new fields.NumberField({ required: false, min: 0, integer: true });
// Powered armour supports its own weight and is effectively weightless for encumbrance.
schema.powered = new fields.BooleanField({ required: false, initial: false });
schema.options = new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
}

View File

@@ -0,0 +1,41 @@
const fields = foundry.data.fields;
export function createCharacteristicField(show = true, showMax = false) {
return new fields.SchemaField({
value: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
max: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true }),
dm: new fields.NumberField({ required: false, initial: 0, integer: true }),
show: new fields.BooleanField({ required: false, initial: show }),
showMax: new fields.BooleanField({ required: false, initial: showMax })
});
}
export class ItemBaseData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
description: new fields.StringField({ required: false, blank: true, trim: true, nullable: true }),
subType: new fields.StringField({ required: false, blank: false, nullable: true })
};
}
}
export class PhysicalItemData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.quantity = new fields.NumberField({ required: true, initial: 1, min: 0, integer: true });
schema.weight = new fields.NumberField({ required: true, initial: 0, min: 0, integer: false });
schema.weightless = new fields.BooleanField({ required: false, initial: false });
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true });
schema.tl = new fields.StringField({ required: true, blank: false, initial: "TL12" });
schema.container = new fields.SchemaField({
id: new fields.StringField({ required: false, blank: true })
});
schema.roll = new fields.SchemaField({
characteristic: new fields.StringField({ required: false, blank: true, trim: true }),
skill: new fields.StringField({ required: false, blank: true, trim: true }),
difficulty: new fields.StringField({ required: false, blank: true, trim: true })
});
schema.trash = new fields.BooleanField({ required: false, initial: false });
return schema;
}
}

View File

@@ -0,0 +1,21 @@
import { ItemBaseData } from "./base-item.mjs";
const fields = foundry.data.fields;
export default class CareerData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.difficulty = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true });
schema.damage = new fields.StringField({ required: false, blank: true });
schema.interval = new fields.StringField({ required: false, blank: true });
schema.assignment = new fields.StringField({ required: false, blank: true });
schema.terms = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.rank = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.events = new fields.ArrayField(
new fields.SchemaField({
age: new fields.NumberField({ required: false, integer: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
}

View File

@@ -0,0 +1,18 @@
import { PhysicalItemData } from "./base-item.mjs";
const fields = foundry.data.fields;
export default class ComputerData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.processing = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.processingUsed = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.overload = new fields.BooleanField({ required: false, initial: false });
schema.options = new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
}

View File

@@ -0,0 +1,27 @@
import { ItemBaseData } from "./base-item.mjs";
const fields = foundry.data.fields;
export default class ContactData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "skill";
schema.cost = new fields.NumberField({ required: true, initial: 1, min: 0, integer: true });
schema.skill = new fields.SchemaField({
speciality: new fields.StringField({ required: false, blank: true, trim: true }),
characteristic: new fields.StringField({ required: false, blank: true, trim: true })
});
schema.status = new fields.StringField({ required: false, blank: true, trim: true, initial: "Alive" });
schema.attitude = new fields.StringField({ required: false, blank: true, trim: true, initial: "Unknow" });
schema.relation = new fields.StringField({ required: false, blank: true, trim: true, initial: "Contact" });
schema.title = new fields.StringField({ required: false, blank: true, trim: true });
schema.nickname = new fields.StringField({ required: false, blank: true, trim: true });
schema.species = new fields.StringField({ required: false, blank: true, trim: true });
schema.gender = new fields.StringField({ required: false, blank: true, trim: true });
schema.pronouns = new fields.StringField({ required: false, blank: true, trim: true });
schema.homeworld = new fields.StringField({ required: false, blank: true, trim: true });
schema.location = new fields.StringField({ required: false, blank: true, trim: true });
schema.occupation = new fields.StringField({ required: false, blank: true, trim: true });
schema.notes = new fields.HTMLField({ required: false, blank: true, trim: true });
return schema;
}
}

View File

@@ -0,0 +1,16 @@
import { ItemBaseData } from "./base-item.mjs";
const fields = foundry.data.fields;
export default class ItemContainerData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.onHand = new fields.BooleanField({ required: false, initial: false });
schema.location = new fields.StringField({ required: false, blank: true, trim: true });
schema.count = new fields.NumberField({ required: false, initial: 0, integer: true });
schema.weight = new fields.NumberField({ required: false, initial: 0, integer: false });
schema.weightless = new fields.BooleanField({ required: false, initial: false });
schema.locked = new fields.BooleanField({ required: false, initial: false }); // GM only
schema.lockedDescription = new fields.StringField({ required: false, blank: true, trim: true, nullable: true });
return schema;
}
}

View File

@@ -0,0 +1,13 @@
import { ItemBaseData } from "./base-item.mjs";
const fields = foundry.data.fields;
export default class DiseaseData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "disease"; // disease, poison
schema.difficulty = new fields.StringField({ required: true, initial: "Average" });
schema.damage = new fields.StringField({ required: false, blank: true });
schema.interval = new fields.StringField({ required: false, blank: true });
return schema;
}
}

View File

@@ -0,0 +1,14 @@
import { PhysicalItemData } from "./base-item.mjs";
const fields = foundry.data.fields;
export default class EquipmentData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.equipped = new fields.BooleanField({ required: false, initial: false });
schema.augment = new fields.SchemaField({
improvement: new fields.StringField({ required: false, blank: true, trim: true })
});
schema.subType.initial = "equipment"; // augment, clothing, trinket, toolkit, equipment
return schema;
}
}

View File

@@ -0,0 +1,15 @@
import { PhysicalItemData } from "./base-item.mjs";
const fields = foundry.data.fields;
export default class ItemData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "loot";
schema.software = new fields.SchemaField({
bandwidth: new fields.NumberField({ required: false, initial: 0, min: 0, max: 10, integer: true }),
effect: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
computerId: new fields.StringField({ required: false, blank: true, initial: "" })
});
return schema;
}
}

View File

@@ -0,0 +1,22 @@
const fields = foundry.data.fields;
export default class SpeciesData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
description: new fields.StringField({ required: false, blank: true, trim: true, nullable: true }),
descriptionLong: new fields.HTMLField({ required: false, blank: true, trim: true }),
traits: new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
),
modifiers: new fields.ArrayField(
new fields.SchemaField({
characteristic: new fields.StringField({ required: false, blank: true, trim: true }),
value: new fields.NumberField({ required: false, integer: true, nullable: true })
})
)
};
}
}

View File

@@ -0,0 +1,27 @@
import { ItemBaseData } from "./base-item.mjs";
const fields = foundry.data.fields;
export default class TalentData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "skill";
schema.cost = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true });
schema.level = new fields.NumberField({ required: true, initial: 0, min: 0, integer: true });
schema.skill = new fields.SchemaField({
speciality: new fields.StringField({ required: false, blank: true, trim: true }),
reduceEncumbrance: new fields.BooleanField({ required: false, initial: false })
});
schema.psionic = new fields.SchemaField({
reach: new fields.StringField({ required: false, blank: true, trim: true }),
cost: new fields.NumberField({ required: false, initial: 1, min: 0, integer: true }),
duration: new fields.StringField({ required: false, blank: true, trim: true }),
durationUnit: new fields.StringField({ required: false })
});
schema.roll = new fields.SchemaField({
characteristic: new fields.StringField({ required: false, blank: true, trim: true }),
skill: new fields.StringField({ required: false, blank: true, trim: true }),
difficulty: new fields.StringField({ required: false, blank: true, trim: true })
});
return schema;
}
}

View File

@@ -0,0 +1,30 @@
import { PhysicalItemData } from "./base-item.mjs";
const fields = foundry.data.fields;
export default class WeaponData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.equipped = new fields.BooleanField({ required: false, initial: false });
schema.range = new fields.SchemaField({
isMelee: new fields.BooleanField({ required: false, initial: false }),
value: new fields.NumberField({ required: false, integer: true, nullable: true }),
unit: new fields.StringField({ required: false, blank: true, nullable: true })
});
schema.damage = new fields.StringField({ required: false, blank: true, trim: true });
schema.magazine = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.magazineCost = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.traits = new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
);
schema.options = new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
}

View File

@@ -0,0 +1,33 @@
const fields = foundry.data.fields;
export default class VehiculeData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
name: new fields.StringField({ required: false, blank: false, trim: true }),
skillId: new fields.StringField({ required: false, initial: "", blank: true, trim: true }),
speed: new fields.SchemaField({
cruise: new fields.StringField({ required: false, initial: "Slow", blank: true }),
maximum: new fields.StringField({ required: false, initial: "Medium", blank: true })
}),
agility: new fields.NumberField({ required: false, min: 0, integer: true }),
crew: new fields.NumberField({ required: false, min: 0, integer: true }),
passengers: new fields.NumberField({ required: false, min: 0, integer: true }),
cargo: new fields.NumberField({ required: false, min: 0, integer: false }),
life: new fields.SchemaField({
value: new fields.NumberField({ required: true, initial: 0, integer: true }),
max: new fields.NumberField({ required: true, initial: 0, integer: true })
}),
shipping: new fields.NumberField({ required: false, min: 0, integer: true }),
cost: new fields.NumberField({ required: false, min: 0, integer: true }),
armor: new fields.SchemaField({
front: new fields.NumberField({ required: true, initial: 0, integer: true }),
rear: new fields.NumberField({ required: true, initial: 0, integer: true }),
sides: new fields.NumberField({ required: true, initial: 0, integer: true })
}),
skills: new fields.SchemaField({
autopilot: new fields.NumberField({ required: true, initial: 0, integer: true })
})
};
}
}