Second round de corrections et améliorations

This commit is contained in:
2026-04-19 18:55:34 +02:00
parent 783d4a16e6
commit d62d14c1da
33 changed files with 2225 additions and 390 deletions

View File

@@ -1,20 +1,20 @@
const fields$d = foundry.data.fields;
const fields$e = foundry.data.fields;
function createCharacteristicField(show = true, showMax = false) {
return new fields$d.SchemaField({
value: new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true }),
max: new fields$d.NumberField({ required: false, initial: 0, min: 0, integer: true }),
dm: new fields$d.NumberField({ required: false, initial: 0, integer: true }),
show: new fields$d.BooleanField({ required: false, initial: show }),
showMax: new fields$d.BooleanField({ required: false, initial: showMax })
return new fields$e.SchemaField({
value: new fields$e.NumberField({ required: true, initial: 0, min: 0, integer: true }),
max: new fields$e.NumberField({ required: false, initial: 0, min: 0, integer: true }),
dm: new fields$e.NumberField({ required: false, initial: 0, integer: true }),
show: new fields$e.BooleanField({ required: false, initial: show }),
showMax: new fields$e.BooleanField({ required: false, initial: showMax })
});
}
class ItemBaseData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
description: new fields$d.StringField({ required: false, blank: true, trim: true, nullable: true }),
subType: new fields$d.StringField({ required: false, blank: false, nullable: true })
description: new fields$e.StringField({ required: false, blank: true, trim: true, nullable: true }),
subType: new fields$e.StringField({ required: false, blank: false, nullable: true })
};
}
}
@@ -22,57 +22,57 @@ class ItemBaseData extends foundry.abstract.TypeDataModel {
class PhysicalItemData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.quantity = new fields$d.NumberField({ required: true, initial: 1, min: 0, integer: true });
schema.weight = new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: false });
schema.weightless = new fields$d.BooleanField({ required: false, initial: false });
schema.cost = new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true });
schema.tl = new fields$d.StringField({ required: true, blank: false, initial: "TL12" });
schema.container = new fields$d.SchemaField({
id: new fields$d.StringField({ required: false, blank: true })
schema.quantity = new fields$e.NumberField({ required: true, initial: 1, min: 0, integer: true });
schema.weight = new fields$e.NumberField({ required: true, initial: 0, min: 0, integer: false });
schema.weightless = new fields$e.BooleanField({ required: false, initial: false });
schema.cost = new fields$e.NumberField({ required: true, initial: 0, min: 0, integer: true });
schema.tl = new fields$e.StringField({ required: true, blank: false, initial: "TL12" });
schema.container = new fields$e.SchemaField({
id: new fields$e.StringField({ required: false, blank: true })
});
schema.roll = new fields$d.SchemaField({
characteristic: new fields$d.StringField({ required: false, blank: true, trim: true }),
skill: new fields$d.StringField({ required: false, blank: true, trim: true }),
difficulty: new fields$d.StringField({ required: false, blank: true, trim: true })
schema.roll = new fields$e.SchemaField({
characteristic: new fields$e.StringField({ required: false, blank: true, trim: true }),
skill: new fields$e.StringField({ required: false, blank: true, trim: true }),
difficulty: new fields$e.StringField({ required: false, blank: true, trim: true })
});
schema.trash = new fields$d.BooleanField({ required: false, initial: false });
schema.trash = new fields$e.BooleanField({ required: false, initial: false });
return schema;
}
}
const fields$c = foundry.data.fields;
const fields$d = foundry.data.fields;
class CharacterData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
name: new fields$c.StringField({ required: false, blank: false, trim: true }),
life: new fields$c.SchemaField({
value: new fields$c.NumberField({ required: false, initial: 0, integer: true }),
max: new fields$c.NumberField({ required: true, initial: 0, integer: true })
name: new fields$d.StringField({ required: false, blank: false, trim: true }),
life: new fields$d.SchemaField({
value: new fields$d.NumberField({ required: false, initial: 0, integer: true }),
max: new fields$d.NumberField({ required: true, initial: 0, integer: true })
}),
personal: new fields$c.SchemaField({
title: new fields$c.StringField({ required: false, blank: true, trim: true }),
species: new fields$c.StringField({ required: false, blank: true, trim: true }),
speciesText: new fields$c.SchemaField({
description: new fields$c.StringField({ required: false, blank: true, trim: true, nullable: true }),
descriptionLong: new fields$c.HTMLField({ required: false, blank: true, trim: true })
personal: new fields$d.SchemaField({
title: new fields$d.StringField({ required: false, blank: true, trim: true }),
species: new fields$d.StringField({ required: false, blank: true, trim: true }),
speciesText: new fields$d.SchemaField({
description: new fields$d.StringField({ required: false, blank: true, trim: true, nullable: true }),
descriptionLong: new fields$d.HTMLField({ required: false, blank: true, trim: true })
}),
age: new fields$c.StringField({ required: false, blank: true, trim: true }),
gender: new fields$c.StringField({ required: false, blank: true, trim: true }),
pronouns: new fields$c.StringField({ required: false, blank: true, trim: true }),
homeworld: new fields$c.StringField({ required: false, blank: true, trim: true }),
ucp: new fields$c.StringField({ required: false, blank: true, trim: true, initial: "" }),
traits: new fields$c.ArrayField(
new fields$c.SchemaField({
name: new fields$c.StringField({ required: true, blank: true, trim: true }),
description: new fields$c.StringField({ required: false, blank: true, trim: true })
age: new fields$d.StringField({ required: false, blank: true, trim: true }),
gender: new fields$d.StringField({ required: false, blank: true, trim: true }),
pronouns: new fields$d.StringField({ required: false, blank: true, trim: true }),
homeworld: new fields$d.StringField({ required: false, blank: true, trim: true }),
ucp: new fields$d.StringField({ required: false, blank: true, trim: true, initial: "" }),
traits: new fields$d.ArrayField(
new fields$d.SchemaField({
name: new fields$d.StringField({ required: true, blank: true, trim: true }),
description: new fields$d.StringField({ required: false, blank: true, trim: true })
})
)
}),
biography: new fields$c.HTMLField({ required: false, blank: true, trim: true }),
biography: new fields$d.HTMLField({ required: false, blank: true, trim: true }),
characteristics: new fields$c.SchemaField({
characteristics: new fields$d.SchemaField({
strength: createCharacteristicField(true, true),
dexterity: createCharacteristicField(true, true),
endurance: createCharacteristicField(true, true),
@@ -87,87 +87,184 @@ class CharacterData extends foundry.abstract.TypeDataModel {
other: createCharacteristicField(true, false)
}),
health: new fields$c.SchemaField({
radiations: new fields$c.NumberField({ required: false, initial: 0, min: 0, integer: true })
health: new fields$d.SchemaField({
radiations: new fields$d.NumberField({ required: false, initial: 0, min: 0, integer: true })
}),
study: new fields$c.SchemaField({
skill: new fields$c.StringField({ required: false, blank: true, trim: true, initial: "" }),
total: new fields$c.NumberField({ required: false, initial: 0, min: 0, integer: true }),
completed: new fields$c.NumberField({ required: false, initial: 0, min: 0, integer: true })
study: new fields$d.SchemaField({
skill: new fields$d.StringField({ required: false, blank: true, trim: true, initial: "" }),
total: new fields$d.NumberField({ required: false, initial: 0, min: 0, integer: true }),
completed: new fields$d.NumberField({ required: false, initial: 0, min: 0, integer: true })
}),
finance: new fields$c.SchemaField({
pension: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
credits: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
cashOnHand: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
debt: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
livingCost: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
monthlyShipPayments: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
notes: new fields$c.StringField({ required: false, blank: true, trim: true, initial: "" })
finance: new fields$d.SchemaField({
pension: new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true }),
credits: new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true }),
cashOnHand: new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true }),
debt: new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true }),
livingCost: new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true }),
monthlyShipPayments: new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true }),
notes: new fields$d.StringField({ required: false, blank: true, trim: true, initial: "" })
}),
containerView: new fields$c.StringField({ required: false, blank: true, trim: true, initial: "" }),
containerDropIn: new fields$c.StringField({ required: false, blank: true, trim: true, initial: "" }),
notes: new fields$c.HTMLField({ required: false, blank: true, trim: true }),
containerView: new fields$d.StringField({ required: false, blank: true, trim: true, initial: "" }),
containerDropIn: new fields$d.StringField({ required: false, blank: true, trim: true, initial: "" }),
notes: new fields$d.HTMLField({ required: false, blank: true, trim: true }),
inventory: new fields$c.SchemaField({
armor: new fields$c.NumberField({ required: true, initial: 0, integer: true }),
weight: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: false }),
encumbrance: new fields$c.SchemaField({
normal: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
heavy: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true })
inventory: new fields$d.SchemaField({
armor: new fields$d.NumberField({ required: true, initial: 0, integer: true }),
weight: new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: false }),
encumbrance: new fields$d.SchemaField({
normal: new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true }),
heavy: new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true })
})
}),
states: new fields$c.SchemaField({
encumbrance: new fields$c.BooleanField({ required: false, initial: false }),
fatigue: new fields$c.BooleanField({ required: false, initial: false }),
unconscious: new fields$c.BooleanField({ required: false, initial: false }),
surgeryRequired: new fields$c.BooleanField({ required: false, initial: false })
states: new fields$d.SchemaField({
encumbrance: new fields$d.BooleanField({ required: false, initial: false }),
fatigue: new fields$d.BooleanField({ required: false, initial: false }),
unconscious: new fields$d.BooleanField({ required: false, initial: false }),
surgeryRequired: new fields$d.BooleanField({ required: false, initial: false })
}),
config: new fields$c.SchemaField({
psionic: new fields$c.BooleanField({ required: false, initial: true }),
initiative: new fields$c.StringField({ required: false, blank: true, initial: "dexterity" }),
damages: new fields$c.SchemaField({
rank1: new fields$c.StringField({ required: false, blank: true, initial: "strength" }),
rank2: new fields$c.StringField({ required: false, blank: true, initial: "dexterity" }),
rank3: new fields$c.StringField({ required: false, blank: true, initial: "endurance" })
config: new fields$d.SchemaField({
psionic: new fields$d.BooleanField({ required: false, initial: true }),
initiative: new fields$d.StringField({ required: false, blank: true, initial: "dexterity" }),
damages: new fields$d.SchemaField({
rank1: new fields$d.StringField({ required: false, blank: true, initial: "strength" }),
rank2: new fields$d.StringField({ required: false, blank: true, initial: "dexterity" }),
rank3: new fields$d.StringField({ required: false, blank: true, initial: "endurance" })
})
})
};
}
}
const fields$c = foundry.data.fields;
class VehiculeData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
name: new fields$c.StringField({ required: false, blank: false, trim: true }),
skillId: new fields$c.StringField({ required: false, initial: "", blank: true, trim: true }),
speed: new fields$c.SchemaField({
cruise: new fields$c.StringField({ required: false, initial: "Slow", blank: true }),
maximum: new fields$c.StringField({ required: false, initial: "Medium", blank: true })
}),
agility: new fields$c.NumberField({ required: false, min: 0, integer: true }),
crew: new fields$c.NumberField({ required: false, min: 0, integer: true }),
passengers: new fields$c.NumberField({ required: false, min: 0, integer: true }),
cargo: new fields$c.NumberField({ required: false, min: 0, integer: false }),
life: new fields$c.SchemaField({
value: new fields$c.NumberField({ required: true, initial: 0, integer: true }),
max: new fields$c.NumberField({ required: true, initial: 0, integer: true })
}),
shipping: new fields$c.NumberField({ required: false, min: 0, integer: true }),
cost: new fields$c.NumberField({ required: false, min: 0, integer: true }),
armor: new fields$c.SchemaField({
front: new fields$c.NumberField({ required: true, initial: 0, integer: true }),
rear: new fields$c.NumberField({ required: true, initial: 0, integer: true }),
sides: new fields$c.NumberField({ required: true, initial: 0, integer: true })
}),
skills: new fields$c.SchemaField({
autopilot: new fields$c.NumberField({ required: true, initial: 0, integer: true })
})
};
}
}
const fields$b = foundry.data.fields;
class VehiculeData extends foundry.abstract.TypeDataModel {
class CreatureData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
name: new fields$b.StringField({ required: false, blank: false, trim: true }),
skillId: new fields$b.StringField({ required: false, initial: "", blank: true, trim: true }),
speed: new fields$b.SchemaField({
cruise: new fields$b.StringField({ required: false, initial: "Slow", blank: true }),
maximum: new fields$b.StringField({ required: false, initial: "Medium", blank: true })
}),
agility: new fields$b.NumberField({ required: false, min: 0, integer: true }),
crew: new fields$b.NumberField({ required: false, min: 0, integer: true }),
passengers: new fields$b.NumberField({ required: false, min: 0, integer: true }),
cargo: new fields$b.NumberField({ required: false, min: 0, integer: false }),
life: new fields$b.SchemaField({
value: new fields$b.NumberField({ required: true, initial: 0, integer: true }),
max: new fields$b.NumberField({ required: true, initial: 0, integer: true })
value: new fields$b.NumberField({ required: true, initial: 10, min: 0, integer: true }),
max: new fields$b.NumberField({ required: true, initial: 10, min: 0, integer: true })
}),
shipping: new fields$b.NumberField({ required: false, min: 0, integer: true }),
cost: new fields$b.NumberField({ required: false, min: 0, integer: true }),
armor: new fields$b.SchemaField({
front: new fields$b.NumberField({ required: true, initial: 0, integer: true }),
rear: new fields$b.NumberField({ required: true, initial: 0, integer: true }),
sides: new fields$b.NumberField({ required: true, initial: 0, integer: true })
speed: new fields$b.NumberField({ required: true, initial: 6, min: 0, integer: true }),
armor: new fields$b.NumberField({ required: true, initial: 0, min: 0, integer: true }),
psi: new fields$b.NumberField({ required: true, initial: 0, min: 0, integer: true }),
initiativeBonus: new fields$b.NumberField({ required: true, initial: 0, integer: true }),
skills: new fields$b.ArrayField(
new fields$b.SchemaField({
name: new fields$b.StringField({ required: true, blank: true, trim: true, initial: "" }),
level: new fields$b.NumberField({ required: true, initial: 0, integer: true }),
note: new fields$b.StringField({ required: false, blank: true, trim: true, initial: "" })
})
),
attacks: new fields$b.ArrayField(
new fields$b.SchemaField({
name: new fields$b.StringField({ required: true, blank: true, trim: true, initial: "" }),
damage: new fields$b.StringField({ required: true, blank: true, trim: true, initial: "1D" }),
description: new fields$b.StringField({ required: false, blank: true, trim: true, initial: "" })
})
),
traits: new fields$b.ArrayField(
new fields$b.SchemaField({
name: new fields$b.StringField({ required: true, blank: true, trim: true, initial: "" }),
value: new fields$b.StringField({ required: false, blank: true, trim: true, initial: "" }),
description: new fields$b.StringField({ required: false, blank: true, trim: true, initial: "" })
})
),
behavior: new fields$b.SchemaField({
type: new fields$b.StringField({ required: false, blank: true, trim: true, initial: "" }),
subtype: new fields$b.StringField({ required: false, blank: true, trim: true, initial: "" })
}),
skills: new fields$b.SchemaField({
autopilot: new fields$b.NumberField({ required: true, initial: 0, integer: true })
})
biography: new fields$b.HTMLField({ required: false, blank: true, trim: true }),
notes: new fields$b.HTMLField({ required: false, blank: true, trim: true }),
};
}
/** @override */
prepareDerivedData() {
// Compute initiative bonus from Métabolisme traits
let bonus = 0;
for (const trait of this.traits) {
const nameLower = trait.name.toLowerCase();
if (nameLower.includes("métabolisme rapide") || nameLower.includes("metabolisme rapide")) {
const val = parseInt(trait.value);
if (!isNaN(val)) bonus += val;
} else if (nameLower.includes("métabolisme lent") || nameLower.includes("metabolisme lent")) {
const val = parseInt(trait.value);
if (!isNaN(val)) bonus -= val;
}
}
this.initiativeBonus = bonus;
// Compute armor from Armure trait if not set manually
if (this.armor === 0) {
for (const trait of this.traits) {
if (trait.name.toLowerCase().startsWith("armure")) {
const val = parseInt(trait.value);
if (!isNaN(val)) {
this.armor = val;
break;
}
}
}
}
// Compute PSI from Psionique trait
if (this.psi === 0) {
for (const trait of this.traits) {
if (trait.name.toLowerCase().startsWith("psionique")) {
const val = parseInt(trait.value);
if (!isNaN(val)) {
this.psi = val;
break;
}
}
}
}
}
}
const fields$a = foundry.data.fields;
@@ -555,6 +652,29 @@ MGT2.Durations = Object.freeze({
Heures: "MGT2.Durations.Heures"
});
MGT2.CreatureBehaviorType = Object.freeze({
herbivore: "MGT2.CreatureBehaviorType.herbivore",
carnivore: "MGT2.CreatureBehaviorType.carnivore",
charognard: "MGT2.CreatureBehaviorType.charognard",
omnivore: "MGT2.CreatureBehaviorType.omnivore"
});
MGT2.CreatureBehaviorSubType = Object.freeze({
accumulateur: "MGT2.CreatureBehaviorSubType.accumulateur",
brouteur: "MGT2.CreatureBehaviorSubType.brouteur",
filtreur: "MGT2.CreatureBehaviorSubType.filtreur",
intermittent: "MGT2.CreatureBehaviorSubType.intermittent",
chasseur: "MGT2.CreatureBehaviorSubType.chasseur",
detourneur: "MGT2.CreatureBehaviorSubType.detourneux",
guetteur: "MGT2.CreatureBehaviorSubType.guetteur",
mangeur: "MGT2.CreatureBehaviorSubType.mangeur",
piegeur: "MGT2.CreatureBehaviorSubType.piegeur",
intimidateur: "MGT2.CreatureBehaviorSubType.intimidateur",
necrophage: "MGT2.CreatureBehaviorSubType.necrophage",
reducteur: "MGT2.CreatureBehaviorSubType.reducteur",
opportuniste: "MGT2.CreatureBehaviorSubType.opportuniste"
});
class ActorCharacter {
static preCreate($this, data, options, user) {
$this.updateSource({ prototypeToken: { actorLink: true } }); // QoL
@@ -1374,18 +1494,9 @@ class MGT2Helper {
}
static getDifficultyDisplay(difficulty) {
switch(difficulty) {
case "Simple": return game.i18n.localize("MGT2.Difficulty.Simple") + " (2+)";
case "Easy": return game.i18n.localize("MGT2.Difficulty.Easy") + " (4+)";
case "Routine": return game.i18n.localize("MGT2.Difficulty.Routine") + " (6+)";
case "Average": return game.i18n.localize("MGT2.Difficulty.Average") + " (8+)";
case "Difficult": return game.i18n.localize("MGT2.Difficulty.Difficult") + " (10+)";
case "VeryDifficult": return game.i18n.localize("MGT2.Difficulty.VeryDifficult") + " (12+)";
case "Formidable": return game.i18n.localize("MGT2.Difficulty.Formidable") + " (14+)";
case "Impossible": return game.i18n.localize("MGT2.Difficulty.Impossible") + " (16+)";
default:
return null;
}
const key = `MGT2.Difficulty.${difficulty}`;
const label = game.i18n.localize(key);
return label !== key ? label : null;
}
static getRangeDisplay(range) {
@@ -1494,13 +1605,13 @@ class MGT2Helper {
}
const { DialogV2: DialogV2$1 } = foundry.applications.api;
const { renderTemplate: renderTemplate$1 } = foundry.applications.handlebars;
const { renderTemplate: renderTemplate$2 } = foundry.applications.handlebars;
const { FormDataExtended: FormDataExtended$1 } = foundry.applications.ux;
class RollPromptHelper {
static async roll(options) {
const htmlContent = await renderTemplate$1('systems/mgt2/templates/roll-prompt.html', {
const htmlContent = await renderTemplate$2('systems/mgt2/templates/roll-prompt.html', {
config: CONFIG.MGT2,
characteristics: options.characteristics,
characteristic: options.characteristic,
@@ -1515,6 +1626,7 @@ class RollPromptHelper {
return await DialogV2$1.wait({
window: { title: options.title ?? options.rollTypeName ?? game.i18n.localize("MGT2.RollPrompt.Roll") },
classes: ["mgt2-roll-dialog"],
content: htmlContent,
rejectClose: false,
buttons: [
@@ -1551,11 +1663,11 @@ class RollPromptHelper {
}
const { DialogV2 } = foundry.applications.api;
const { renderTemplate } = foundry.applications.handlebars;
const { renderTemplate: renderTemplate$1 } = foundry.applications.handlebars;
const { FormDataExtended } = foundry.applications.ux;
async function _dialogWithForm(title, templatePath, templateData) {
const htmlContent = await renderTemplate(templatePath, templateData);
const htmlContent = await renderTemplate$1(templatePath, templateData);
game.settings.get("mgt2", "theme");
return await DialogV2.wait({
window: { title },
@@ -1603,7 +1715,7 @@ class CharacterPrompts {
}
static async openEditorFullView(title, html) {
const htmlContent = await renderTemplate("systems/mgt2/templates/editor-fullview.html", {
const htmlContent = await renderTemplate$1("systems/mgt2/templates/editor-fullview.html", {
config: CONFIG.MGT2,
html
});
@@ -1903,7 +2015,7 @@ class TravellerCharacterSheet extends MGT2ActorSheet {
const html = this.element;
if (!this.isEditable) return;
this._bindClassEvent(html, ".roll", "click", TravellerCharacterSheet.#onRoll);
this._bindClassEvent(html, "a[data-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);
@@ -1994,26 +2106,22 @@ class TravellerCharacterSheet extends MGT2ActorSheet {
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 (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;
await sourceItem.update({ "system.software.computerId": targetItem.system.software.computerId });
else
sourceItem.system.container.id = targetItem.system.container.id;
this.actor.updateEmbeddedDocuments("Item", [sourceItem]);
await sourceItem.update({ "system.container.id": targetItem.system.container.id });
return true;
} else if (targetItem.type === "computer") {
sourceItem.system.software.computerId = targetId;
this.actor.updateEmbeddedDocuments("Item", [sourceItem]);
await sourceItem.update({ "system.software.computerId": targetId });
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]);
await sourceItem.update({ "system.container.id": targetId });
return true;
}
}
@@ -2086,20 +2194,19 @@ class TravellerCharacterSheet extends MGT2ActorSheet {
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));
const item = this.actor.getEmbeddedDocument("Item", li?.dataset.itemId);
if (!item) return;
item.system.equipped = !item.system.equipped;
this.actor.updateEmbeddedDocuments("Item", [item]);
await item.update({ "system.equipped": !item.system.equipped });
}
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));
const item = this.actor.getEmbeddedDocument("Item", li?.dataset.itemId);
if (!item) return;
if (item.type === "container") {
item.system.onHand = false;
await item.update({ "system.onHand": false });
} else {
const containers = this.actor.getContainers();
let container;
@@ -2117,27 +2224,24 @@ class TravellerCharacterSheet extends MGT2ActorSheet {
ui.notifications.error("Objet verrouillé");
return;
}
item.system.container.id = container._id;
await item.update({ "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));
const item = this.actor.getEmbeddedDocument("Item", li?.dataset.itemId);
if (!item) return;
item.system.container.id = "";
this.actor.updateEmbeddedDocuments("Item", [item]);
await item.update({ "system.container.id": "" });
}
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));
const item = this.actor.getEmbeddedDocument("Item", li?.dataset.itemId);
if (!item) return;
item.system.software.computerId = "";
this.actor.updateEmbeddedDocuments("Item", [item]);
await item.update({ "system.software.computerId": "" });
}
static async #onContainerCreate(event) {
@@ -2163,23 +2267,19 @@ class TravellerCharacterSheet extends MGT2ActorSheet {
);
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 updates = containerItems.map(item => ({ _id: item.id, "system.container.id": "" }));
await this.actor.updateEmbeddedDocuments("Item", updates);
}
const cloneActor = foundry.utils.deepClone(this.actor);
cloneActor.system.containerView = "";
if (cloneActor.system.containerDropIn === container._id) {
cloneActor.system.containerDropIn = "";
const actorUpdate = { "system.containerView": "" };
if (this.actor.system.containerDropIn === container._id) {
actorUpdate["system.containerDropIn"] = "";
const remaining = containers.filter(x => x._id !== container._id);
if (remaining.length > 0) cloneActor.system.containerDropIn = remaining[0]._id;
if (remaining.length > 0) actorUpdate["system.containerDropIn"] = remaining[0]._id;
}
this.actor.deleteEmbeddedDocuments("Item", [container._id]);
this.actor.update(cloneActor);
await this.actor.deleteEmbeddedDocuments("Item", [container._id]);
await this.actor.update(actorUpdate);
}
static async #onRoll(event, target) {
@@ -2489,6 +2589,236 @@ class TravellerVehiculeSheet extends MGT2ActorSheet {
tabGroups = { primary: "stats" }
}
/** Convert Traveller dice notation (e.g. "2D", "4D+2", "3D6") to FoundryVTT formula */
function normalizeDice(formula) {
if (!formula) return "1d6";
return formula
.replace(/(\d*)D(\d*)([+-]\d+)?/gi, (_, count, sides, mod) => {
const n = count || "1";
const d = sides || "6";
return mod ? `${n}d${d}${mod}` : `${n}d${d}`;
});
}
class TravellerCreatureSheet extends MGT2ActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "creature", "nopad"],
position: {
width: 720,
height: 600,
},
window: {
...super.DEFAULT_OPTIONS.window,
title: "TYPES.Actor.creature",
},
actions: {
...super.DEFAULT_OPTIONS.actions,
rollAttack: TravellerCreatureSheet.#onRollAttack,
rollSkill: TravellerCreatureSheet.#onRollSkill,
addSkill: TravellerCreatureSheet.#onAddRow,
deleteSkill: TravellerCreatureSheet.#onDeleteRow,
addAttack: TravellerCreatureSheet.#onAddRow,
deleteAttack: TravellerCreatureSheet.#onDeleteRow,
addTrait: TravellerCreatureSheet.#onAddRow,
deleteTrait: TravellerCreatureSheet.#onDeleteRow,
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/mgt2/templates/actors/creature-sheet.html",
},
}
/** @override */
tabGroups = { primary: "skills" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
const actor = this.document;
context.sizeLabel = this._getSizeLabel(actor.system.life.max);
context.sizeTraitLabel = this._getSizeTrait(actor.system.life.max);
context.config = CONFIG.MGT2;
return context;
}
_getSizeLabel(pdv) {
if (pdv <= 2) return "Souris / Rat";
if (pdv <= 5) return "Chat";
if (pdv <= 7) return "Blaireau / Chien";
if (pdv <= 13) return "Chimpanzé / Chèvre";
if (pdv <= 28) return "Humain";
if (pdv <= 35) return "Vache / Cheval";
if (pdv <= 49) return "Requin";
if (pdv <= 70) return "Rhinocéros";
if (pdv <= 90) return "Éléphant";
if (pdv <= 125) return "Carnosaure";
return "Sauropode / Baleine";
}
_getSizeTrait(pdv) {
if (pdv <= 2) return "Petit (4)";
if (pdv <= 5) return "Petit (3)";
if (pdv <= 7) return "Petit (2)";
if (pdv <= 13) return "Petit (1)";
if (pdv <= 28) return "—";
if (pdv <= 35) return "Grand (+1)";
if (pdv <= 49) return "Grand (+2)";
if (pdv <= 70) return "Grand (+3)";
if (pdv <= 90) return "Grand (+4)";
if (pdv <= 125) return "Grand (+5)";
return "Grand (+6)";
}
// ───────────────────────────────────────────────────────── Roll Handlers
/** Roll an attack (damage) with optional difficulty dialog */
static async #onRollAttack(event, target) {
const index = parseInt(target.dataset.index ?? 0);
const actor = this.document;
const attack = actor.system.attacks[index];
if (!attack) return;
const rollFormula = normalizeDice(attack.damage);
const roll = await new Roll(rollFormula).evaluate();
roll.total;
await roll.toMessage({
speaker: ChatMessage.getSpeaker({ actor }),
flavor: `<strong>${actor.name}</strong> — ${attack.name}`,
rollMode: game.settings.get("core", "rollMode"),
});
}
/** Roll a skill check (2d6 + level vs difficulty) */
static async #onRollSkill(event, target) {
const index = parseInt(target.dataset.index ?? 0);
const actor = this.document;
const skill = actor.system.skills[index];
if (!skill) return;
const htmlContent = await renderTemplate(
"systems/mgt2/templates/actors/creature-roll-prompt.html",
{
skillName: skill.name,
skillLevel: skill.level,
config: CONFIG.MGT2
}
);
const result = await foundry.applications.api.DialogV2.wait({
window: { title: game.i18n.localize("MGT2.Creature.RollSkill") + " — " + skill.name },
content: htmlContent,
rejectClose: false,
buttons: [
{
action: "boon",
label: game.i18n.localize("MGT2.RollPrompt.Boon"),
callback: (event, button, dialog) => {
const fd = new foundry.applications.ux.FormDataExtended(dialog.element.querySelector("form")).object;
fd.diceModifier = "dl";
return fd;
}
},
{
action: "roll",
label: game.i18n.localize("MGT2.RollPrompt.Roll"),
icon: '<i class="fa-solid fa-dice"></i>',
default: true,
callback: (event, button, dialog) =>
new foundry.applications.ux.FormDataExtended(dialog.element.querySelector("form")).object
},
{
action: "bane",
label: game.i18n.localize("MGT2.RollPrompt.Bane"),
callback: (event, button, dialog) => {
const fd = new foundry.applications.ux.FormDataExtended(dialog.element.querySelector("form")).object;
fd.diceModifier = "dh";
return fd;
}
}
]
});
if (!result) return;
const dm = parseInt(result.dm ?? 0) + (skill.level ?? 0);
const modifier = result.diceModifier ?? "";
const difficultyTarget = parseInt(result.difficulty ?? 8);
const difficultyLabel = result.difficultyLabel ?? "";
const diceFormula = modifier ? `3d6${modifier}` : "2d6";
const fullFormula = dm !== 0 ? `${diceFormula} + ${dm}` : diceFormula;
const roll = await new Roll(fullFormula).evaluate();
const success = roll.total >= difficultyTarget;
const chatData = {
creatureName: actor.name,
creatureImg: actor.img,
rollLabel: skill.name.toUpperCase(),
formula: fullFormula,
total: roll.total,
tooltip: await roll.getTooltip(),
difficulty: difficultyTarget,
difficultyLabel,
success,
failure: !success,
modifiers: dm !== 0 ? [`DM ${dm >= 0 ? "+" : ""}${dm}`] : [],
};
const chatContent = await renderTemplate(
"systems/mgt2/templates/chat/creature-roll.html",
chatData
);
await ChatMessage.create({
content: chatContent,
speaker: ChatMessage.getSpeaker({ actor }),
rolls: [roll],
rollMode: game.settings.get("core", "rollMode"),
});
}
// ───────────────────────────────────────────────────────── CRUD Handlers
static async #onAddRow(event, target) {
const prop = target.dataset.prop;
if (!prop) return;
const actor = this.document;
const arr = foundry.utils.deepClone(actor.system[prop] ?? []);
arr.push(this._getDefaultRow(prop));
await actor.update({ [`system.${prop}`]: arr });
}
static async #onDeleteRow(event, target) {
const prop = target.dataset.prop;
const index = parseInt(target.dataset.index);
if (!prop || isNaN(index)) return;
const actor = this.document;
const arr = foundry.utils.deepClone(actor.system[prop] ?? []);
arr.splice(index, 1);
await actor.update({ [`system.${prop}`]: arr });
}
_getDefaultRow(prop) {
switch (prop) {
case "skills": return { name: "", level: 0, note: "" };
case "attacks": return { name: "", damage: "1D", description: "" };
case "traits": return { name: "", value: "", description: "" };
default: return {};
}
}
}
const { HandlebarsApplicationMixin } = foundry.applications.api;
class TravellerItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
@@ -2774,6 +3104,9 @@ const preloadHandlebarsTemplates = async function() {
"systems/mgt2/templates/actors/actor-config-sheet.html",
"systems/mgt2/templates/actors/actor-config-characteristic-sheet.html",
"systems/mgt2/templates/actors/trait-sheet.html",
"systems/mgt2/templates/actors/creature-sheet.html",
"systems/mgt2/templates/actors/creature-roll-prompt.html",
"systems/mgt2/templates/chat/creature-roll.html",
"systems/mgt2/templates/editor-fullview.html"
];
@@ -2849,7 +3182,7 @@ class ChatHelper {
speaker = game.user.character;
}
let rollTypeName = message.flags.mgt2.damage.rollTypeName ? message.flags.mgt2.damage.rollTypeName + " DAMAGE" : null;
let rollTypeName = message.flags.mgt2.damage.rollTypeName ? message.flags.mgt2.damage.rollTypeName + " " + game.i18n.localize("MGT2.Actor.Damage") : null;
const chatData = {
user: game.user.id,
@@ -3011,6 +3344,10 @@ Hooks.once("init", async function () {
"characteristics.charm.value",
"characteristics.psionic.value",
"characteristics.other.value"]
},
creature: {
bar: ["life"],
value: ["life.value", "life.max", "speed", "armor", "psi"]
}
};
@@ -3029,13 +3366,15 @@ Hooks.once("init", async function () {
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
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" });
foundry.documents.collections.Actors.registerSheet("mgt2", TravellerCreatureSheet, { types: ["creature"], makeDefault: true, label: "Creature Sheet" });
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("mgt2", TravellerItemSheet, { makeDefault: true });
Object.assign(CONFIG.Actor.dataModels, {
"character": CharacterData,
"vehicule": VehiculeData
"vehicule": VehiculeData,
"creature": CreatureData
});
Object.assign(CONFIG.Item.dataModels, {