Files
foundryvtt-mgt2/mgt2.bundle.js

3405 lines
134 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const fields$e = foundry.data.fields;
function createCharacteristicField(show = true, showMax = false) {
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$e.StringField({ required: false, blank: true, trim: true, nullable: true }),
subType: new fields$e.StringField({ required: false, blank: false, nullable: true })
};
}
}
class PhysicalItemData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
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$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$e.BooleanField({ required: false, initial: false });
return schema;
}
}
const fields$d = foundry.data.fields;
class CharacterData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
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$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$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$d.HTMLField({ required: false, blank: true, trim: true }),
characteristics: new fields$d.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$d.SchemaField({
radiations: new fields$d.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$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$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$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$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$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 CreatureData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
life: new fields$b.SchemaField({
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 })
}),
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: "" })
}),
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;
class ItemData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "loot";
schema.software = new fields$a.SchemaField({
bandwidth: new fields$a.NumberField({ required: false, initial: 0, min: 0, max: 10, integer: true }),
effect: new fields$a.StringField({ required: false, blank: true, trim: true, initial: "" }),
computerId: new fields$a.StringField({ required: false, blank: true, initial: "" })
});
return schema;
}
}
const fields$9 = foundry.data.fields;
class EquipmentData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.equipped = new fields$9.BooleanField({ required: false, initial: false });
schema.augment = new fields$9.SchemaField({
improvement: new fields$9.StringField({ required: false, blank: true, trim: true })
});
schema.subType.initial = "equipment"; // augment, clothing, trinket, toolkit, equipment
return schema;
}
}
const fields$8 = foundry.data.fields;
class DiseaseData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "disease"; // disease, poison
schema.difficulty = new fields$8.StringField({ required: true, initial: "Average" });
schema.damage = new fields$8.StringField({ required: false, blank: true });
schema.interval = new fields$8.StringField({ required: false, blank: true });
return schema;
}
}
const fields$7 = foundry.data.fields;
class CareerData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.difficulty = new fields$7.NumberField({ required: true, initial: 0, min: 0, integer: true });
schema.damage = new fields$7.StringField({ required: false, blank: true });
schema.interval = new fields$7.StringField({ required: false, blank: true });
schema.assignment = new fields$7.StringField({ required: false, blank: true });
schema.terms = new fields$7.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.rank = new fields$7.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.events = new fields$7.ArrayField(
new fields$7.SchemaField({
age: new fields$7.NumberField({ required: false, integer: true }),
description: new fields$7.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
}
const fields$6 = foundry.data.fields;
class TalentData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "skill";
schema.cost = new fields$6.NumberField({ required: true, initial: 0, min: 0, integer: true });
schema.level = new fields$6.NumberField({ required: true, initial: 0, min: 0, integer: true });
schema.skill = new fields$6.SchemaField({
speciality: new fields$6.StringField({ required: false, blank: true, trim: true }),
reduceEncumbrance: new fields$6.BooleanField({ required: false, initial: false })
});
schema.psionic = new fields$6.SchemaField({
reach: new fields$6.StringField({ required: false, blank: true, trim: true }),
cost: new fields$6.NumberField({ required: false, initial: 1, min: 0, integer: true }),
duration: new fields$6.StringField({ required: false, blank: true, trim: true }),
durationUnit: new fields$6.StringField({ required: false })
});
schema.roll = new fields$6.SchemaField({
characteristic: new fields$6.StringField({ required: false, blank: true, trim: true }),
skill: new fields$6.StringField({ required: false, blank: true, trim: true }),
difficulty: new fields$6.StringField({ required: false, blank: true, trim: true })
});
return schema;
}
}
const fields$5 = foundry.data.fields;
class ContactData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.subType.initial = "skill";
schema.cost = new fields$5.NumberField({ required: true, initial: 1, min: 0, integer: true });
schema.skill = new fields$5.SchemaField({
speciality: new fields$5.StringField({ required: false, blank: true, trim: true }),
characteristic: new fields$5.StringField({ required: false, blank: true, trim: true })
});
schema.status = new fields$5.StringField({ required: false, blank: true, trim: true, initial: "Alive" });
schema.attitude = new fields$5.StringField({ required: false, blank: true, trim: true, initial: "Unknow" });
schema.relation = new fields$5.StringField({ required: false, blank: true, trim: true, initial: "Contact" });
schema.title = new fields$5.StringField({ required: false, blank: true, trim: true });
schema.nickname = new fields$5.StringField({ required: false, blank: true, trim: true });
schema.species = new fields$5.StringField({ required: false, blank: true, trim: true });
schema.gender = new fields$5.StringField({ required: false, blank: true, trim: true });
schema.pronouns = new fields$5.StringField({ required: false, blank: true, trim: true });
schema.homeworld = new fields$5.StringField({ required: false, blank: true, trim: true });
schema.location = new fields$5.StringField({ required: false, blank: true, trim: true });
schema.occupation = new fields$5.StringField({ required: false, blank: true, trim: true });
schema.notes = new fields$5.HTMLField({ required: false, blank: true, trim: true });
return schema;
}
}
const fields$4 = foundry.data.fields;
class WeaponData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.equipped = new fields$4.BooleanField({ required: false, initial: false });
schema.range = new fields$4.SchemaField({
isMelee: new fields$4.BooleanField({ required: false, initial: false }),
value: new fields$4.NumberField({ required: false, integer: true, nullable: true }),
unit: new fields$4.StringField({ required: false, blank: true, nullable: true })
});
schema.damage = new fields$4.StringField({ required: false, blank: true, trim: true });
schema.magazine = new fields$4.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.magazineCost = new fields$4.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.traits = new fields$4.ArrayField(
new fields$4.SchemaField({
name: new fields$4.StringField({ required: true, blank: true, trim: true }),
description: new fields$4.StringField({ required: false, blank: true, trim: true })
})
);
schema.options = new fields$4.ArrayField(
new fields$4.SchemaField({
name: new fields$4.StringField({ required: true, blank: true, trim: true }),
description: new fields$4.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
}
const fields$3 = foundry.data.fields;
class ArmorData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.equipped = new fields$3.BooleanField({ required: false, initial: false });
schema.radiations = new fields$3.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.protection = new fields$3.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$3.StringField({ required: false, blank: false });
schema.requireSkillLevel = new fields$3.NumberField({ required: false, min: 0, integer: true });
// Powered armour supports its own weight and is effectively weightless for encumbrance.
schema.powered = new fields$3.BooleanField({ required: false, initial: false });
schema.options = new fields$3.ArrayField(
new fields$3.SchemaField({
name: new fields$3.StringField({ required: true, blank: true, trim: true }),
description: new fields$3.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
}
const fields$2 = foundry.data.fields;
class ComputerData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.processing = new fields$2.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.processingUsed = new fields$2.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.overload = new fields$2.BooleanField({ required: false, initial: false });
schema.options = new fields$2.ArrayField(
new fields$2.SchemaField({
name: new fields$2.StringField({ required: true, blank: true, trim: true }),
description: new fields$2.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
}
const fields$1 = foundry.data.fields;
class ItemContainerData extends ItemBaseData {
static defineSchema() {
const schema = super.defineSchema();
schema.onHand = new fields$1.BooleanField({ required: false, initial: false });
schema.location = new fields$1.StringField({ required: false, blank: true, trim: true });
schema.count = new fields$1.NumberField({ required: false, initial: 0, integer: true });
schema.weight = new fields$1.NumberField({ required: false, initial: 0, integer: false });
schema.weightless = new fields$1.BooleanField({ required: false, initial: false });
schema.locked = new fields$1.BooleanField({ required: false, initial: false }); // GM only
schema.lockedDescription = new fields$1.StringField({ required: false, blank: true, trim: true, nullable: true });
return schema;
}
}
const fields = foundry.data.fields;
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 })
})
)
};
}
}
const MGT2 = {};
MGT2.MetricRange = Object.freeze({
meter: "MGT2.MetricRange.meter",
kilometer: "MGT2.MetricRange.kilometer"
});
MGT2.MetricWeight = Object.freeze({
kilogram: "MGT2.MetricWeight.kilogram",
ton: "MGT2.MetricWeight.ton"
});
MGT2.Difficulty = Object.freeze({
NA: "MGT2.Difficulty.NA",
Simple: "MGT2.Difficulty.Simple",
Easy: "MGT2.Difficulty.Easy",
Routine: "MGT2.Difficulty.Routine",
Average: "MGT2.Difficulty.Average",
Difficult: "MGT2.Difficulty.Difficult",
VeryDifficult: "MGT2.Difficulty.VeryDifficult",
Formidable: "MGT2.Difficulty.Formidable",
Impossible: "MGT2.Difficulty.Impossible"
});
MGT2.ItemSubType = Object.freeze({
loot: "MGT2.ItemSubType.loot",
software: "MGT2.ItemSubType.software"
});
MGT2.EquipmentSubType = Object.freeze({
augment: "MGT2.EquipmentSubType.augment",
clothing: "MGT2.EquipmentSubType.clothing",
equipment: "MGT2.EquipmentSubType.equipment",
trinket: "MGT2.EquipmentSubType.trinket",
toolkit: "MGT2.EquipmentSubType.toolkit"
});
MGT2.TalentSubType = Object.freeze({
skill: "MGT2.TalentSubType.skill",
psionic: "MGT2.TalentSubType.psionic"
});
MGT2.DiseaseSubType = Object.freeze({
disease: "MGT2.DiseaseSubType.disease",
poison: "MGT2.DiseaseSubType.poison",
wound: "MGT2.DiseaseSubType.wound"
});
MGT2.PsionicReach = Object.freeze({
NA: "MGT2.PsionicReach.NA",
Personal: "MGT2.PsionicReach.Personal",
Close: "MGT2.PsionicReach.Close",
Short: "MGT2.PsionicReach.Short",
Medium: "MGT2.PsionicReach.Medium",
Long: "MGT2.PsionicReach.Long",
VeryLong: "MGT2.PsionicReach.VeryLong",
Distant: "MGT2.PsionicReach.Distant",
VeryDistant: "MGT2.PsionicReach.VeryDistant",
Continental: "MGT2.PsionicReach.Continental",
Planetary: "MGT2.PsionicReach.Planetary"
});
MGT2.ContactRelations = Object.freeze({
Allie: "MGT2.Contact.Relation.Allie",
Contact: "MGT2.Contact.Relation.Contact",
Rival: "MGT2.Contact.Relation.Rival",
Enemy: "MGT2.Contact.Relation.Enemy"
});
MGT2.ContactStatus = Object.freeze({
Alive: "MGT2.Contact.Status.Alive",
Unknow: "MGT2.Contact.Status.Unknow",
Dead: "MGT2.Contact.Status.Dead"
});
MGT2.Attitudes = Object.freeze({
Unknow: "MGT2.Contact.Attitude.Unknow",
Hostile: "MGT2.Contact.Attitude.Hostile",
Unfriendly: "MGT2.Contact.Attitude.Unfriendly",
Indifferent: "MGT2.Contact.Attitude.Indifferent",
Friendly: "MGT2.Contact.Attitude.Friendly",
Helpful: "MGT2.Contact.Attitude.Helpful",
Complicated: "MGT2.Contact.Attitude.Complicated"
});
MGT2.Characteristics = Object.freeze({
strength: "MGT2.Characteristics.strength.name",
dexterity: "MGT2.Characteristics.dexterity.name",
endurance: "MGT2.Characteristics.endurance.name",
intellect: "MGT2.Characteristics.intellect.name",
education: "MGT2.Characteristics.education.name",
social: "MGT2.Characteristics.social.name",
morale: "MGT2.Characteristics.morale.name",
luck: "MGT2.Characteristics.luck.name",
sanity: "MGT2.Characteristics.sanity.name",
charm: "MGT2.Characteristics.charm.name",
psionic: "MGT2.Characteristics.psionic.name",
other: "MGT2.Characteristics.other.name"
});
MGT2.InitiativeCharacteristics = Object.freeze({
dexterity: "MGT2.Characteristics.dexterity.name",
intellect: "MGT2.Characteristics.intellect.name"
});
MGT2.DamageCharacteristics = Object.freeze({
strength: "MGT2.Characteristics.strength.name",
dexterity: "MGT2.Characteristics.dexterity.name",
endurance: "MGT2.Characteristics.endurance.name"
});
MGT2.TL = Object.freeze({
NA: "MGT2.TL.NA",
Unknow: "MGT2.TL.Unknow",
NotIdentified: "MGT2.TL.NotIdentified",
TL00: "MGT2.TL.L00",
TL01: "MGT2.TL.L01",
TL02: "MGT2.TL.L02",
TL03: "MGT2.TL.L03",
TL04: "MGT2.TL.L04",
TL05: "MGT2.TL.L05",
TL06: "MGT2.TL.L06",
TL07: "MGT2.TL.L07",
TL08: "MGT2.TL.L08",
TL09: "MGT2.TL.L09",
TL10: "MGT2.TL.L10",
TL11: "MGT2.TL.L11",
TL12: "MGT2.TL.L12",
TL13: "MGT2.TL.L13",
TL14: "MGT2.TL.L14",
TL15: "MGT2.TL.L15"
});
MGT2.Timeframes = Object.freeze({
Normal: "MGT2.Timeframes.Normal",
Slower: "MGT2.Timeframes.Slower",
Faster: "MGT2.Timeframes.Faster"
});
MGT2.SpeedBands = Object.freeze({
Stoppped: "MGT2.SpeedBands.Stoppped",
Idle: "MGT2.SpeedBands.Idle",
VerySlow: "MGT2.SpeedBands.VerySlow",
Slow: "MGT2.SpeedBands.Slow",
Medium: "MGT2.SpeedBands.Medium",
High: "MGT2.SpeedBands.High.",
Fast: "MGT2.SpeedBands.Fast",
VeryFast: "MGT2.SpeedBands.VeryFast",
Subsonic: "MGT2.SpeedBands.Subsonic",
Hypersonic: "MGT2.SpeedBands.Hypersonic"
});
MGT2.Durations = Object.freeze({
Seconds: "MGT2.Durations.Seconds",
Minutes: "MGT2.Durations.Minutes",
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
}
static prepareData(actorData) {
actorData.initiative = this.getInitiative(actorData);
}
static getInitiative($this) {
let c = $this.system.config.initiative;
return $this.system.characteristics[c].dm;
}
static async onDeleteDescendantDocuments($this, parent, collection, documents, ids, options, userId) {
const toDeleteIds = [];
const itemToUpdates = [];
for (let d of documents) {
if (d.type === "container") {
// Delete content
for (let item of $this.items) {
if (item.system.hasOwnProperty("container") && item.system.container.id === d._id)
toDeleteIds.push(item._id);
}
} else if (d.type === "computer") {
// Eject software
for (let item of $this.items) {
if (item.system.hasOwnProperty("software") && item.system.computerId === d._id) {
let clone = foundry.utils.deepClone(item);
clone.system.software.computerId = "";
itemToUpdates.push(clone);
}
}
}
}
if (toDeleteIds.length > 0)
await $this.deleteEmbeddedDocuments("Item", toDeleteIds);
if (itemToUpdates.length > 0)
await $this.updateEmbeddedDocuments('Item', itemToUpdates);
await this.recalculateWeight($this);
}
static async onUpdateDescendantDocuments($this, parent, collection, documents, changes, options, userId) {
await this.calculEncumbranceAndWeight($this, parent, collection, documents, changes, options, userId);
await this.calculComputers($this, parent, collection, documents, changes, options, userId);
}
static async calculComputers($this, parent, collection, documents, changes, options, userId) {
let change;
let i = 0;
let recalculProcessing = false;
for (let d of documents) {
if (changes[i].hasOwnProperty("system")) {
change = changes[i];
if (d.type === "item" && d.system.subType === "software") {
if (change.system.software.hasOwnProperty("bandwidth") || change.system.software.hasOwnProperty("computerId")) {
recalculProcessing = true;
break;
}
}
}
}
if (recalculProcessing) {
let updatedComputers = [];
let computerChanges = {};
let computers = [];
for (let item of $this.items) {
if (item.system.trash === true) continue;
if (item.type === "computer") {
computers.push(item);
computerChanges[item._id] = { processingUsed: 0 };
}
}
for (let item of $this.items) {
if (item.type !== "item" && item.system.subType !== "software") continue;
if (item.system.software.hasOwnProperty("computerId") && item.system.software.computerId !== "") {
computerChanges[item.system.software.computerId].processingUsed += item.system.software.bandwidth;
}
}
for (let computer of computers) {
let newProcessingUsed = computerChanges[computer._id].processingUsed;
if (computer.system.processingUsed !== newProcessingUsed) {
const cloneComputer = foundry.utils.deepClone($this.getEmbeddedDocument("Item", computer._id));
cloneComputer.system.processingUsed = newProcessingUsed;
cloneComputer.system.overload = cloneComputer.system.processingUsed > cloneComputer.system.processing;
updatedComputers.push(cloneComputer);
}
}
if (updatedComputers.length > 0) {
await $this.updateEmbeddedDocuments('Item', updatedComputers);
}
}
}
static async calculEncumbranceAndWeight($this, parent, collection, documents, changes, options, userId) {
let recalculEncumbrance = false;
let recalculWeight = false;
let change;
let i = 0;
for (let d of documents) {
if (changes[i].hasOwnProperty("system")) {
change = changes[i];
if (d.type === "armor" ||
d.type === "computer" ||
d.type === "gear" ||
d.type === "item" ||
d.type === "weapon") {
if (change.system.hasOwnProperty("quantity") ||
change.system.hasOwnProperty("weight") ||
change.system.hasOwnProperty("weightless") ||
change.system.hasOwnProperty("container") ||
change.system.hasOwnProperty("equipped") ||
d.type === "armor") {
recalculWeight = true;
}
} else if (d.type === "talent" && d.system.subType === "skill") {
if (change.system.level || (change.system?.hasOwnProperty("skill") && change.system?.skill.hasOwnProperty("reduceEncumbrance"))) {
recalculEncumbrance = true;
}
} else if (d.type === "container" && (change.system.hasOwnProperty("onHand") || change.system.hasOwnProperty("weightless"))) {
recalculWeight = true;
}
}
i++;
}
if (recalculEncumbrance || recalculWeight) {
const updateData = {};
this.recalculateArmor($this, updateData);
if (recalculEncumbrance) {
const str = $this.system.characteristics.strength.value;
const end = $this.system.characteristics.endurance.value;
let sumSkill = 0;
$this.items.filter(x => x.type === "talent" && x.system.subType === "skill" && x.system.skill.reduceEncumbrance === true).forEach(x => sumSkill += x.system.level);
let normal = str + end + sumSkill;
let heavy = normal * 2;
updateData["system.states.encumbrance"] = $this.system.inventory.weight > normal;
updateData["system.inventory.encumbrance.normal"] = normal;
updateData["system.inventory.encumbrance.heavy"] = heavy;
}
if (recalculWeight)
await this.recalculateWeight($this, updateData);
else if (Object.keys(updateData).length > 0)
await $this.update(updateData);
}
}
static recalculateArmor($this, updateData) {
if (updateData === null || updateData === undefined)
updateData = {};
let armor = 0;
for (let item of $this.items) {
if (item.type === "armor") {
if (item.system.equipped === true && !isNaN(item.system.protection)) {
armor += (+item.system.protection || 0);
}
}
}
updateData["system.inventory.armor"] = armor;
return updateData;
}
static async recalculateWeight($this, updateData) {
if (updateData === null || updateData === undefined)
updateData = {};
let updatedContainers = [];
let containerChanges = {};
let containers = [];
// List all containers
for (let item of $this.items) {
if (item.system.trash === true) continue;
if (item.type === "container") {
containers.push(item);
containerChanges[item._id] = { count: 0, weight: 0 };
}
}
let onHandWeight = 0;
for (let item of $this.items) {
if (item.type === "container") continue;
if (item.system.hasOwnProperty("weightless") && item.system.weightless === true) continue;
let itemWeight = 0;
if (item.system.hasOwnProperty("weight")) {
let itemQty = item.system.quantity;
if (!isNaN(itemQty) && itemQty > 0) {
itemWeight = item.system.weight;
if (itemWeight > 0) {
itemWeight *= itemQty;
}
}
if (item.type === "armor") {
if (item.system.equipped === true) {
if (item.system.powered === true)
itemWeight = 0;
else
itemWeight *= 0.25; // mass of armor that is being worn by 75% OPTIONAL
}
}
if (item.system.container && item.system.container.id && item.system.container.id !== "") {
// bad deleted container id
if (containerChanges.hasOwnProperty(item.system.container.id)) {
containerChanges[item.system.container.id].weight += Math.round(itemWeight * 10) / 10;
containerChanges[item.system.container.id].count += item.system.quantity;
}
} else {
onHandWeight += Math.round(itemWeight * 10) / 10;
}
}
}
// Check containers new weight
for (let container of containers) {
let newWeight = containerChanges[container._id].weight;
let newCount = containerChanges[container._id].count;
if (container.system.weight !== newWeight || container.system.count !== newCount) {
updatedContainers.push({
_id: container._id,
"system.weight": newWeight,
"system.count": newCount,
});
if (container.system.onHand === true &&
(container.system.weight > 0 || container.system.weightless !== true)) {
onHandWeight += container.system.weight;
}
}
}
updateData["system.inventory.weight"] = onHandWeight;
updateData["system.states.encumbrance"] = onHandWeight > $this.system.inventory.encumbrance.normal;
await $this.update(updateData);
if (updatedContainers.length > 0) {
await $this.updateEmbeddedDocuments('Item', updatedContainers);
}
}
static async preUpdate($this, changed, options, user) {
// Calc encumbrance
const newStr = foundry.utils.getProperty(changed, "system.characteristics.strength.value") ?? $this.system.characteristics.strength.value;
const newEnd = foundry.utils.getProperty(changed, "system.characteristics.endurance.value") ?? $this.system.characteristics.endurance.value;
if ((newStr !== $this.system.characteristics.strength.value) || (newEnd !== $this.system.characteristics.endurance.value)) {
let sumSkill = 0;
$this.items.filter(x => x.type === "talent" && x.system.subType === "skill" && x.system.skill.reduceEncumbrance === true).forEach(x => sumSkill += x.system.level);
let normal = newStr + newEnd + sumSkill;
let heavy = normal * 2;
foundry.utils.setProperty(changed, "system.inventory.encumbrance.normal", normal);
foundry.utils.setProperty(changed, "system.inventory.encumbrance.heavy", heavy);
}
//console.log(foundry.utils.getProperty(changed, "system.characteristics.strength.value"));
const characteristicModified = this.computeCharacteristics(changed);
const strengthValue = foundry.utils.getProperty(changed, "system.characteristics.strength.value") ?? $this.system.characteristics.strength.value;
const strengthMax = foundry.utils.getProperty(changed, "system.characteristics.strength.max") ?? $this.system.characteristics.strength.max;
const dexterityValue = foundry.utils.getProperty(changed, "system.characteristics.dexterity.value") ?? $this.system.characteristics.dexterity.value;
const dexterityMax = foundry.utils.getProperty(changed, "system.characteristics.dexterity.max") ?? $this.system.characteristics.dexterity.max;
const enduranceValue = foundry.utils.getProperty(changed, "system.characteristics.endurance.value") ?? $this.system.characteristics.endurance.value;
const enduranceMax = foundry.utils.getProperty(changed, "system.characteristics.endurance.max") ?? $this.system.characteristics.endurance.max;
const lifeValue = strengthValue + dexterityValue + enduranceValue;
const lifeMax = strengthMax + dexterityMax + enduranceMax;
if ($this.system.life.value !== lifeValue)
foundry.utils.setProperty(changed, "system.life.value", lifeValue);
if ($this.system.life.max !== lifeMax)
foundry.utils.setProperty(changed, "system.life.max", lifeMax);
if (characteristicModified && $this.system.personal.ucp === undefined || $this.system.personal.ucp === "") ;
//}
// Apply changes in Actor size to Token width/height
// if ( "size" in (this.system.traits || {}) ) {
// const newSize = foundry.utils.getProperty(changed, "system.traits.size");
// if ( newSize && (newSize !== this.system.traits?.size) ) {
// let size = CONFIG.DND5E.tokenSizes[newSize];
// if ( !foundry.utils.hasProperty(changed, "prototypeToken.width") ) {
// changed.prototypeToken ||= {};
// changed.prototypeToken.height = size;
// changed.prototypeToken.width = size;
// }
// }
// }
}
// static applyHealing($this, amount) {
// if (isNaN(amount) || amount === 0) return;
// const strength = $this.system.characteristics.strength;
// const dexterity = $this.system.characteristics.dexterity;
// const endurance = $this.system.characteristics.endurance;
// const data = {
// strength: { value: strength.value },
// dexterity: { value: dexterity.value },
// endurance: { value: endurance.value }
// };
// $this.update({ system: { characteristics: data } });
// }
static applyDamage($this, amount) {
if (isNaN(amount) || amount === 0) return;
const rank1 = $this.system.config.damages.rank1;
const rank2 = $this.system.config.damages.rank2;
const rank3 = $this.system.config.damages.rank3;
const data = {};
data[rank1] = { value: $this.system.characteristics[rank1].value };
data[rank2] = { value: $this.system.characteristics[rank2].value };
data[rank3] = { value: $this.system.characteristics[rank3].value };
if (amount < 0) amount = Math.abs(amount);
for (const [key, rank] of Object.entries(data)) {
if (rank.value > 0) {
if (rank.value >= amount) {
rank.value -= amount;
amount = 0;
} else {
amount -= rank.value;
rank.value = 0;
}
rank.dm = this.getModifier(rank.value);
if (amount <= 0) break;
}
}
$this.update({ system: { characteristics: data } });
}
static getContainers($this) {
const containers = [];
for (let item of $this.items) {
if (item.type == "container") {
containers.push(item);
}
}
containers.sort(this.compareByName);
return containers;
}
static getComputers($this) {
const containers = [];
for (let item of $this.items) {
if (item.type == "computer") {
containers.push(item);
}
}
containers.sort(this.compareByName);
return containers;
}
static getSkills($this) {
const skills = [];
for (let item of $this.items) {
if (item.type === "talent" && item.system.subType === "skill") {
skills.push(item);
}
}
skills.sort(this.compareByName);
return skills;
}
static computeCharacteristics(changed) {
let modified = this.computeCharacteristic(changed, "strength");
if (this.computeCharacteristic(changed, "dexterity") && !modified) modified = true;
if (this.computeCharacteristic(changed, "endurance") && !modified) modified = true;
if (this.computeCharacteristic(changed, "intellect") && !modified) modified = true;
if (this.computeCharacteristic(changed, "education") && !modified) modified = true;
if (this.computeCharacteristic(changed, "social") && !modified) modified = true;
if (this.computeCharacteristic(changed, "morale") && !modified) modified = true;
if (this.computeCharacteristic(changed, "luck") && !modified) modified = true;
if (this.computeCharacteristic(changed, "sanity") && !modified) modified = true;
if (this.computeCharacteristic(changed, "charm") && !modified) modified = true;
if (this.computeCharacteristic(changed, "psionic") && !modified) modified = true;
if (this.computeCharacteristic(changed, "other") && !modified) modified = true;
return modified;
}
static computeCharacteristic(changed, name) {
//if (isNaN(c.value) || c.value <= 0) c.value = 0;
//c.dm = this._getModifier(c.value)
const path = `system.characteristics.${name}`;
const newValue = foundry.utils.getProperty(changed, path + ".value");// || this.system.characteristics[name].value;
if (newValue) {
const dm = this.getModifier(newValue);
foundry.utils.setProperty(changed, path + ".dm", dm);
return true;
}
return false;
}
static getModifier(value) {
if (isNaN(value) || value <= 0) return -3;
if (value >= 1 && value <= 2) return -2;
if (value >= 3 && value <= 5) return -1;
if (value >= 6 && value <= 8) return 0;
if (value >= 9 && value <= 11) return 1;
if (value >= 12 && value <= 14) return 2;
return 3;
}
static compareByName(a, b) {
if (!a.hasOwnProperty("name") || !b.hasOwnProperty("name")) {
return 0;
}
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}
}
class MGT2Combatant extends Combatant {
}
class TravellerActor extends Actor {
prepareDerivedData() {
if (this.type === "character") {
this.system.initiative = ActorCharacter.getInitiative(this);
}
}
async _preCreate(data, options, user) {
if ( (await super._preCreate(data, options, user)) === false ) return false;
if (this.type === "character") {
ActorCharacter.preCreate(this, data, options, user);
}
}
async _onDeleteDescendantDocuments(parent, collection, documents, ids, options, userId) {
await super._onDeleteDescendantDocuments(parent, collection, documents, ids, options, userId);
if (this.type === "character") {
await ActorCharacter.onDeleteDescendantDocuments(this, parent, collection, documents, ids, options, userId);
}
}
async _onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId) {
super._onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId);
//console.log("_onUpdateDescendantDocuments");
if (this.type === "character") {
await ActorCharacter.onUpdateDescendantDocuments(this, parent, collection, documents, changes, options, userId);
}
}
async _preUpdate(changed, options, user) {
if ((await super._preUpdate(changed, options, user)) === false) return false;
if (this.type === "character") {
await ActorCharacter.preUpdate(this, changed, options, user);
}
}
getInitiative($this) {
if (this.type === "character") {
return ActorCharacter.getInitiative(this);
}
}
applyDamage(amount) {
if (this.type === "character") {
ActorCharacter.applyDamage(this, amount);
}
}
getContainers() {
if (this.type === "character") {
return ActorCharacter.getContainers(this);
}
return [];
}
getComputers() {
if (this.type === "character") {
return ActorCharacter.getComputers(this);
}
return [];
}
getSkills() {
if (this.type === "character") {
return ActorCharacter.getSkills(this);
}
return [];
}
async recalculateWeight() {
if (this.type === "character") {
return ActorCharacter.recalculateWeight(this);
}
}
}
class TravellerItem extends Item {
/** @inheritdoc */
prepareDerivedData() {
super.prepareDerivedData();
}
async _preUpdate(changed, options, user) {
if ((await super._preUpdate(changed, options, user)) === false) return false;
if (this.type === "computer") {
// Overload
const newProcessing = foundry.utils.getProperty(changed, "system.processing") ?? this.system.processing;
if (newProcessing !== this.system.processing) {
let overload = this.system.processingUsed > newProcessing;
foundry.utils.setProperty(changed, "system.overload", overload);
}
}
// Qty max 1
if (this.type === "computer" || this.type === "container" || (this.type === "item" && this.system.subType === "software")) {
const newQty = foundry.utils.getProperty(changed, "system.quantity") ?? this.system.quantity;
if (newQty !== this.system.quantity && newQty > 1) {
foundry.utils.setProperty(changed, "system.quantity", 1);
}
}
// No Weight
if (this.type === "item" && this.system.subType === "software") {
const newWeight = foundry.utils.getProperty(changed, "system.weight") ?? this.system.weight;
if (newWeight !== this.system.weight && newWeight > 0) {
foundry.utils.setProperty(changed, "system.weight", 0);
}
}
}
getRollDisplay() {
if (this.type === "talent") {
if (this.system.subType === "skill") {
let label;
if (this.system.skill.speciality !== "" && this.system.skill.speciality !== undefined) {
label = `${this.name} (${this.system.skill.speciality})`;
} else {
label = this.name;
}
if (this.system.level > 0)
label += ` (+${this.system.level})`;
else if (this.system.level < 0)
label += ` (${this.system.level})`;
return label;
} else if (this.system.subType === "psionic") ;
}
return name;
}
}
const { HandlebarsApplicationMixin: HandlebarsApplicationMixin$1 } = foundry.applications.api;
class MGT2ActorSheet extends HandlebarsApplicationMixin$1(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 = { sidebar: "health" }
/** @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);
// Inject theme class dynamically (can't use game.settings in static DEFAULT_OPTIONS)
const theme = game.settings.get("mgt2", "theme");
if (theme) this.element.classList.add(theme);
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();
}
}
class MGT2Helper {
static POUNDS_CONVERT = 2.20462262185;
static decimalSeparator;
static badDecimalSeparator;
static {
this.decimalSeparator = Number(1.1).toLocaleString().charAt(1);
this.badDecimalSeparator = (this.decimalSeparator === "." ? "," : ".");
}
static format = function() {
var s = arguments[0];
for (var i = 0; i < arguments.length - 1; i++) {
var reg = new RegExp("\\{" + i + "\\}", "gm");
s = s.replace(reg, arguments[i + 1]);
}
return s;
}
static hasValue(object, property) {
return object !== undefined && object.hasOwnProperty(property) && object[property] !== null && object[property] !== undefined && object[property] !== "";
}
static getItemsWeight(items) {
let weight = 0;
for (let i of items) {
let item = i.hasOwnProperty("system") ? i.system : i;
if (item.hasOwnProperty("weightless") && item.weightless === true) {
continue;
}
if (item.hasOwnProperty("weight")) {
let itemQty = item.quantity;
if (!isNaN(itemQty) && itemQty > 0) {
let itemWeight = item.weight;
if (itemWeight > 0) {
weight += itemWeight * itemQty;
}
}
}
}
return weight;
}
static generateUID() {
let result = '';
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 36; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
result += characters.charAt(randomIndex);
if (i === 8 || i === 12 || i === 16 || i === 20)
result += "-";
}
return result;
}
static compareByName(a, b) {
if (!a.hasOwnProperty("name") || !b.hasOwnProperty("name")) {
return 0;
}
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}
static getDisplayDM(dm) {
if (dm === 0) return " (0)";
if (dm > 0) return ` (+${dm})`;
if (dm < 0) return ` (${dm})`;
return "";
}
static getFormulaDM(dm) {
if (dm === 0) return "+0";
if (dm > 0) return `+${dm}`;
if (dm < 0) return `${dm}`;
return "";
}
static getDiceResults(roll) {
const results = [];
for (const die of roll.dice) {
results.push(die.results);
}
return results.flat(2);
}
static getDiceTotal(roll) {
let total = 0;
for (const die of roll.dice) {
total += die.total;
}
return total;
}
static getDifficultyValue(difficulty) {
switch(difficulty) {
case "Simple": return 2;
case "Easy": return 4;
case "Routine": return 6;
case "Average": return 8;
case "Difficult": return 10;
case "VeryDifficult": return 12;
case "Formidable": return 14;
case "Impossible": return 16;
default:
return 0;
}
}
static getDifficultyDisplay(difficulty) {
const key = `MGT2.Difficulty.${difficulty}`;
const label = game.i18n.localize(key);
return label !== key ? label : null;
}
static getRangeDisplay(range) {
let value = Number(range.value);
if (isNaN(value)) return null;
let label;
//if (game.settings.get("mgt2", "useDistanceMetric") === true) {
if (range.unit !== null && range.unit !== undefined && range.unit !== "")
label = game.i18n.localize(`MGT2.MetricRange.${range.unit}`).toLowerCase();
else
label = "";
//} else {
// TODO
//}
return `${value}${label}`;
}
static getWeightLabel() {
//const label = game.settings.get("mgt2", "useWeightMetric") === true ? "MGT2.MetricSystem.Weight.kg" : "MGT2.ImperialSystem.Weight.lb";
//return game.i18n.localize(label);
return game.i18n.localize("MGT2.MetricSystem.Weight.kg");
}
static getDistanceLabel() {
//const label = game.settings.get("mgt2", "useDistanceMetric") === true ? "MGT2.MetricSystem.Distance.km" : "MGT2.ImperialSystem.Distance.mi";
//return game.i18n.localize(label);
return game.i18n.localize("MGT2.MetricSystem.Distance.km");
}
static getIntegerFromInput(data) {
return Math.trunc(this.getNumberFromInput(data));
}
static getNumberFromInput(data) {
if (data === undefined || data === null) return 0;
if (typeof data === "string") {
let converted = Number(data.replace(/\s+/g, '').replace(this.badDecimalSeparator, this.decimalSeparator).trim());
if (isNaN(converted))
return 0;
return converted;
}
let converted = Number(data);
if (isNaN(converted))
return 0;
return converted;
}
static convertWeightForDisplay(weight) {
//if (game.settings.get("mgt2", "useWeightMetric") === true || weight === 0)
return weight;
// Metric to Imperial
//const pounds = weight * this.POUNDS_CONVERT;
//return Math.round(pounds * 10) / 10;
}
static convertWeightFromInput(weight) {
//if (game.settings.get("mgt2", "useWeightMetric") === true || weight === 0)
return Math.round(weight * 10) / 10;
// Imperial to Metric
//const kg = this.POUNDS_CONVERT / weight;
//return Math.round(kg * 10) / 10;
}
static getDataFromDropEvent(event) {
try {
return JSON.parse(event.dataTransfer?.getData("text/plain"));
} catch (err) {
return false;
}
//if ( data.type !== "Item" ) return false;
//const item = await Item.implementation.fromDropData(data);
}
static async getItemDataFromDropData(dropData) {
//console.log("getItemDataFromDropData");
let item;
if (game.modules.get("monks-enhanced-journal")?.active && dropData.itemId && dropData.uuid.includes("JournalEntry")) {
await fromUuid(dropData.uuid);
} else if (dropData.hasOwnProperty("uuid")) {
item = await fromUuid(dropData.uuid);
} else {
let uuid = `${dropData.type}.${dropData.data._id}`;
item = await fromUuid(uuid);
}
if (!item) {
throw new Error(game.i18n.localize("Errors.CouldNotFindItem").replace("_ITEM_ID_", dropData.uuid));
}
if (item.pack) {
const pack = game.packs.get(item.pack);
item = await pack?.getDocument(item._id);
}
return deepClone(item);
}
}
const { DialogV2: DialogV2$1 } = foundry.applications.api;
const { renderTemplate: renderTemplate$2 } = foundry.applications.handlebars;
const { FormDataExtended: FormDataExtended$1 } = foundry.applications.ux;
class RollPromptHelper {
static async roll(options) {
const htmlContent = await renderTemplate$2('systems/mgt2/templates/roll-prompt.html', {
config: CONFIG.MGT2,
characteristics: options.characteristics,
characteristic: options.characteristic,
skills: options.skills,
skill: options.skill,
fatigue: options.fatigue,
encumbrance: options.encumbrance,
difficulty: options.difficulty
});
game.settings.get("mgt2", "theme");
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: [
{
action: "boon",
label: game.i18n.localize("MGT2.RollPrompt.Boon"),
callback: (event, button, dialog) => {
const formData = new FormDataExtended$1(dialog.element.querySelector('form')).object;
formData.diceModifier = "dl";
return formData;
}
},
{
action: "submit",
label: game.i18n.localize("MGT2.RollPrompt.Roll"),
icon: '<i class="fa-solid fa-dice"></i>',
default: true,
callback: (event, button, dialog) => {
return new FormDataExtended$1(dialog.element.querySelector('form')).object;
}
},
{
action: "bane",
label: game.i18n.localize("MGT2.RollPrompt.Bane"),
callback: (event, button, dialog) => {
const formData = new FormDataExtended$1(dialog.element.querySelector('form')).object;
formData.diceModifier = "dh";
return formData;
}
}
]
});
}
}
const { DialogV2 } = foundry.applications.api;
const { renderTemplate: renderTemplate$1 } = foundry.applications.handlebars;
const { FormDataExtended } = foundry.applications.ux;
async function _dialogWithForm(title, templatePath, templateData) {
const htmlContent = await renderTemplate$1(templatePath, templateData);
game.settings.get("mgt2", "theme");
return await DialogV2.wait({
window: { title },
content: htmlContent,
rejectClose: false,
buttons: [
{
action: "submit",
label: game.i18n.localize("MGT2.Save"),
icon: '<i class="fa-solid fa-floppy-disk"></i>',
default: true,
callback: (event, button, dialog) => {
return new FormDataExtended(dialog.element.querySelector('form')).object;
}
}
]
});
}
class CharacterPrompts {
static async openConfig(system) {
return _dialogWithForm(
"Configuration",
"systems/mgt2/templates/actors/actor-config-sheet.html",
{ config: CONFIG.MGT2, system }
);
}
static async openCharacteristic(name, show, showMax, showAll = false) {
return _dialogWithForm(
"Configuration: " + name,
"systems/mgt2/templates/actors/actor-config-characteristic-sheet.html",
{ name, show, showMax, showAll }
);
}
static async openTraitEdit(data) {
const title = data.name ?? game.i18n.localize("MGT2.Actor.EditTrait");
return _dialogWithForm(
title,
"systems/mgt2/templates/actors/trait-sheet.html",
{ config: CONFIG.MGT2, data }
);
}
static async openEditorFullView(title, html) {
const htmlContent = await renderTemplate$1("systems/mgt2/templates/editor-fullview.html", {
config: CONFIG.MGT2,
html
});
game.settings.get("mgt2", "theme");
await DialogV2.wait({
window: { title },
content: htmlContent,
rejectClose: false,
buttons: [
{
action: "close",
label: game.i18n.localize("MGT2.Close") || "Fermer",
default: true,
callback: () => null
}
]
});
}
}
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 = {
sidebar: "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, "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);
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;
if (sourceItem.id === targetId) return false;
if (targetItem.type === "item" || targetItem.type === "equipment") {
if (targetItem.system.subType === "software")
await sourceItem.update({ "system.software.computerId": targetItem.system.software.computerId });
else
await sourceItem.update({ "system.container.id": targetItem.system.container.id });
return true;
} else if (targetItem.type === "computer") {
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 {
await sourceItem.update({ "system.container.id": targetId });
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 = this.actor.getEmbeddedDocument("Item", li?.dataset.itemId);
if (!item) return;
await item.update({ "system.equipped": !item.system.equipped });
}
static async #onItemStorageIn(event, target) {
event.preventDefault();
const li = target.closest("[data-item-id]");
const item = this.actor.getEmbeddedDocument("Item", li?.dataset.itemId);
if (!item) return;
if (item.type === "container") {
await item.update({ "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;
}
await item.update({ "system.container.id": container._id });
}
}
static async #onItemStorageOut(event, target) {
event.preventDefault();
const li = target.closest("[data-item-id]");
const item = this.actor.getEmbeddedDocument("Item", li?.dataset.itemId);
if (!item) return;
await item.update({ "system.container.id": "" });
}
static async #onSoftwareEject(event, target) {
event.preventDefault();
const li = target.closest("[data-item-id]");
const item = this.actor.getEmbeddedDocument("Item", li?.dataset.itemId);
if (!item) return;
await item.update({ "system.software.computerId": "" });
}
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) {
const updates = containerItems.map(item => ({ _id: item.id, "system.container.id": "" }));
await this.actor.updateEmbeddedDocuments("Item", updates);
}
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) actorUpdate["system.containerDropIn"] = remaining[0]._id;
}
await this.actor.deleteEmbeddedDocuments("Item", [container._id]);
await this.actor.update(actorUpdate);
}
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({ 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,
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 foundry.applications.handlebars.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
);
}
}
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" }
}
/** 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) {
/** @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,
},
}
/** Dynamic PARTS: template resolved per item type */
get PARTS() {
const type = this.document?.type ?? "item";
return {
sheet: {
template: `systems/mgt2/templates/items/${type}-sheet.html`,
},
};
}
/** 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.MGT2,
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 foundry.applications.handlebars.renderTemplate(templatePath, context);
return { sheet: html };
}
/** @override — put rendered HTML into the window content */
_replaceHTML(result, content, options) {
content.innerHTML = result.sheet;
// Inject theme class dynamically (can't use game.settings in static DEFAULT_OPTIONS)
const theme = game.settings.get("mgt2", "theme");
if (theme) this.element.classList.add(theme);
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 } });
}
}
/**
* Define a set of template paths to pre-load
* Pre-loaded templates are compiled and cached for fast access when rendering
* @return {Promise}
*/
const preloadHandlebarsTemplates = async function() {
const templatePaths = [
"systems/mgt2/templates/items/armor-sheet.html",
"systems/mgt2/templates/items/career-sheet.html",
"systems/mgt2/templates/items/computer-sheet.html",
"systems/mgt2/templates/items/contact-sheet.html",
"systems/mgt2/templates/items/container-sheet.html",
"systems/mgt2/templates/items/disease-sheet.html",
"systems/mgt2/templates/items/equipment-sheet.html",
"systems/mgt2/templates/items/item-sheet.html",
"systems/mgt2/templates/items/species-sheet.html",
"systems/mgt2/templates/items/talent-sheet.html",
"systems/mgt2/templates/items/weapon-sheet.html",
"systems/mgt2/templates/items/parts/sheet-configuration.html",
"systems/mgt2/templates/items/parts/sheet-physical-item.html",
"systems/mgt2/templates/roll-prompt.html",
"systems/mgt2/templates/chat/roll.html",
//"systems/mgt2/templates/chat/roll-characteristic.html",
"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"
];
return loadTemplates(templatePaths);
};
class ChatHelper {
static setupCardListeners(message, element, messageData) {
if (!message || !element) {
return;
}
element.querySelectorAll('button[data-action="rollDamage"]').forEach(el => {
el.addEventListener('click', async event => {
await this._processRollDamageButtonEvent(message, event);
});
});
element.querySelectorAll('button[data-action="damage"]').forEach(el => {
el.addEventListener('click', async event => {
await this._applyChatCardDamage(message, event);
});
});
element.querySelectorAll('button[data-action="healing"]').forEach(el => {
el.addEventListener('click', async event => {
ui.notifications.warn("healing");
});
});
element.querySelectorAll('button[data-index]').forEach(el => {
el.addEventListener('click', async event => {
await this._processRollButtonEvent(message, event);
});
});
}
static async _processRollButtonEvent(message, event) {
event.preventDefault();
event.stopPropagation();
let buttons = message.flags.mgt2.buttons;
const index = event.target.dataset.index;
const button = buttons[index];
let roll = await new Roll(button.formula, {}).roll();
const chatData = {
user: game.user.id,
speaker: message.speaker,
formula: roll._formula,
tooltip: await roll.getTooltip(),
total: Math.round(roll.total * 100) / 100,
rollObjectName: button.message.objectName,
rollMessage: MGT2Helper.format(button.message.flavor, Math.round(roll.total * 100) / 100),
};
const html = await foundry.applications.handlebars.renderTemplate("systems/mgt2/templates/chat/roll.html", chatData);
chatData.content = html;
return roll.toMessage(chatData);
}
static async _processRollDamageButtonEvent(message, event) {
event.preventDefault();
event.stopPropagation();
let rollFormula = message.flags.mgt2.damage.formula;
let roll = await new Roll(rollFormula, {}).roll();
let speaker;
let selectTokens = canvas.tokens.controlled;
if (selectTokens.length > 0) {
speaker = selectTokens[0].actor;
} else {
speaker = game.user.character;
}
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,
speaker: ChatMessage.getSpeaker({ actor: speaker }),
formula: roll._formula,
tooltip: await roll.getTooltip(),
total: Math.round(roll.total * 100) / 100,
showButtons: true,
hasDamage: true,
rollTypeName: rollTypeName,
rollObjectName: message.flags.mgt2.damage.rollObjectName
};
const html = await foundry.applications.handlebars.renderTemplate("systems/mgt2/templates/chat/roll.html", chatData);
chatData.content = html;
return roll.toMessage(chatData);
}
static _applyChatCardDamage(message, event) {
const roll = message.rolls[0];
return Promise.all(canvas.tokens.controlled.map(t => {
const a = t.actor;
return a.applyDamage(roll.total);
}));
}
}
const registerSettings = function () {
game.settings.register("mgt2", "theme", {
name: "MGT2.Settings.theme.name",
hint: "MGT2.Settings.theme.hint",
scope: "client",
config: true,
default: "black-and-red",
type: String,
choices: {
"black-and-red": "MGT2.Themes.BlackAndRed",
"mwamba": "MGT2.Themes.Mwamba",
"blue": "MGT2.Themes.Blue"
},
requiresReload: true
});
game.settings.register('mgt2', 'usePronouns', {
name: "MGT2.Settings.usePronouns.name",
hint: "MGT2.Settings.usePronouns.hint",
default: false,
scope: 'world',
type: Boolean,
config: true,
requiresReload: false
});
game.settings.register('mgt2', 'useGender', {
name: "MGT2.Settings.useGender.name",
hint: "MGT2.Settings.useGender.hint",
default: false,
scope: 'world',
type: Boolean,
config: true,
requiresReload: false
});
game.settings.register('mgt2', 'showLife', {
name: "MGT2.Settings.showLife.name",
hint: "MGT2.Settings.showLife.hint",
default: false,
scope: 'world',
type: Boolean,
config: true,
requiresReload: false
});
// game.settings.register('mgt2', 'useWeightMetric', {
// name: "MGT2.Settings.useWeightMetric.name",
// hint: "MGT2.Settings.useWeightMetric.hint",
// default: true,
// scope: 'world',
// type: Boolean,
// config: true,
// requiresReload: true
// });
// game.settings.register('mgt2', 'useDistanceMetric', {
// name: "MGT2.Settings.useDistanceMetric.name",
// hint: "MGT2.Settings.useDistanceMetric.hint",
// default: true,
// scope: 'world',
// type: Boolean,
// config: true,
// requiresReload: true
// });
// game.settings.register('mgt2', 'showTrash', {
// name: "Show Trash tab to Player",
// hint: "Player can see the Trash tab and recover item",
// default: false,
// scope: 'world',
// type: Boolean,
// config: true,
// requiresReload: false
// });
/*game.settings.register('mgt2', 'containerDropIn', {
name: "Test",
hint: "Mon hint",
default: true,
scope: 'client',
type: Boolean,
config: true
});*/
};
function registerHandlebarsHelpers() {
Handlebars.registerHelper('showDM', function (dm) {
if (dm === 0) return "0";
if (dm > 0) return `+${dm}`;
if (dm < 0) return `${dm}`;
return "";
});
}
Hooks.once("init", async function () {
CONFIG.MGT2 = MGT2;
CONFIG.Combat.initiative = {
formula: "2d6 + @initiative",
decimals: 2
};
CONFIG.Actor.trackableAttributes = {
character: {
bar: ["life",
"characteristics.strength",
"characteristics.dexterity",
"characteristics.endurance",
"characteristics.intellect",
"characteristics.education",
"characteristics.social",
"characteristics.morale",
"characteristics.luck",
"characteristics.sanity",
"characteristics.charm",
"characteristics.psionic",
"characteristics.other"
],
value: ["life.value",
"health.radiations",
"characteristics.strength.value",
"characteristics.dexterity.value",
"characteristics.endurance.value",
"characteristics.intellect.value",
"characteristics.education.value",
"characteristics.social.value",
"characteristics.morale.value",
"characteristics.luck.value",
"characteristics.sanity.value",
"characteristics.charm.value",
"characteristics.psionic.value",
"characteristics.other.value"]
},
creature: {
bar: ["life"],
value: ["life.value", "life.max", "speed", "armor", "psi"]
}
};
game.mgt2 = {
TravellerActor,
TravellerItem
};
registerHandlebarsHelpers();
registerSettings();
CONFIG.Combatant.documentClass = MGT2Combatant;
CONFIG.Actor.documentClass = TravellerActor;
CONFIG.Item.documentClass = TravellerItem;
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,
"creature": CreatureData
});
Object.assign(CONFIG.Item.dataModels, {
"item": ItemData,
"equipment": EquipmentData,
"disease": DiseaseData,
"career": CareerData,
"talent": TalentData,
"contact": ContactData,
"weapon": WeaponData,
"computer": ComputerData,
"armor": ArmorData,
"container": ItemContainerData,
"species": SpeciesData
});
Hooks.on("renderChatMessageHTML", (message, element, messageData) => {
ChatHelper.setupCardListeners(message, element, messageData);
});
// Preload template partials
await preloadHandlebarsTemplates();
});
export { MGT2 };
//# sourceMappingURL=mgt2.bundle.js.map