Ajout des fonctions de gestion des soins

This commit is contained in:
2026-04-21 10:22:39 +02:00
parent df0a93d715
commit 74f1b581f7
52 changed files with 2697 additions and 650 deletions

View File

@@ -18,6 +18,12 @@
"Save": "Save",
"Cancel": "Cancel",
"Close": "Close",
"Dialog": {
"ConfirmDeleteTitle": "Confirm Deletion",
"ConfirmDeleteContent": "Are you sure you want to delete \"{name}\"?",
"Yes": "Yes",
"No": "No"
},
"Themes": {
"BlackAndRed": "Classic Traveller Cover",
"Mwamba": "Oppa Mwamba Style",
@@ -60,6 +66,7 @@
"Weeks": "Weeks",
"NewCareer": "New Career",
"AddCareer": "Add Career",
"ThisTrait": "this trait",
"EditCareer": "Edit Career",
"EditTrait": "Éditer Trait",
"DeleteTrait": "Supprimer Trait",
@@ -343,7 +350,10 @@
"EncumbranceDM": "Encumbrance (DM -2)",
"FatigueDM": "Fatigue (DM -2)",
"Boon": "Boon",
"Bane": "Bane"
"Bane": "Bane",
"CreatureSkill": "Skill",
"NoSkill": "No skill",
"Days": "Days"
},
"Timeframes": {
"Normal": "Normal",
@@ -355,7 +365,11 @@
"ApplyDamages": "Apply Damages",
"Damages": "Roll damages",
"Success": "Success",
"Failure": "Failure"
"Failure": "Failure",
"Effect": "Effect",
"Dice": "Dice",
"Result": "Result",
"DiceModifier": "Dice Modifier"
}
},
"Items": {
@@ -393,7 +407,7 @@
"Informations": "Informations",
"Improvement": "Improvement",
"Interval": "Interval",
"IsMelee": "IsMelee",
"IsMelee": "Melee Weapon",
"Items": "Items",
"Level": "Level",
"Location": "Location",
@@ -410,6 +424,7 @@
"Occupation": "Occupation",
"OnHand": "On Hand",
"Options": "Options",
"PerDay": "per day",
"PSICost": "PSI Cost",
"Powered": "Powered",
"Processing": "Processing",
@@ -461,11 +476,13 @@
"TabAttacks": "Attacks",
"TabTraits": "Traits",
"TabInfo": "Information",
"TabCombat": "Combat",
"SkillName": "Skill",
"SkillLevel": "Level",
"SkillNote": "Note",
"AttackName": "Attack",
"AttackDamage": "Damage",
"AttackSkill": "Skill",
"TraitName": "Trait",
"TraitValue": "Value",
"AddSkill": "Add a skill",
@@ -480,6 +497,36 @@
"SkillLabel": "Skill",
"RollTitle": "Creature roll"
},
"SpeedBands": {
"Stoppped": "Stopped",
"Idle": "Idle",
"VerySlow": "Very Slow",
"Slow": "Slow",
"Medium": "Medium",
"High": "High",
"Fast": "Fast",
"VeryFast": "Very Fast",
"Subsonic": "Subsonic",
"Hypersonic": "Hypersonic"
},
"Vehicule": {
"Hull": "Hull",
"ArmorFront": "Front",
"ArmorSides": "Sides",
"ArmorRear": "Rear",
"Armor": "Armor",
"SpeedCruise": "Cruise Speed",
"SpeedMax": "Max Speed",
"Agility": "Agility",
"Crew": "Crew",
"Passengers": "Passengers",
"Cargo": "Cargo (t)",
"Shipping": "Shipping (t)",
"Cost": "Cost (Cr)",
"Autopilot": "Autopilot",
"TabStats": "Statistics",
"TabDescription": "Description"
},
"CreatureBehaviorType": {
"herbivore": "Herbivore",
"carnivore": "Carnivore",
@@ -500,6 +547,29 @@
"necrophage": "Carrion-eater",
"reducteur": "Reducer",
"opportuniste": "Opportunist"
},
"Healing": {
"Title": "Healing",
"FirstAid": "First Aid",
"Surgery": "Surgery",
"MedicalCare": "Medical Care",
"NaturalHealing": "Natural Healing",
"WillRestore": "Will restore",
"SurgeryFailed": "Surgery failed - patient takes damage",
"ApplyHealing": "Apply Healing",
"ApplySurgeryDamage": "Apply Surgery Damage",
"SurgeryDamage": "surgery damage",
"ApplyToTarget": "Apply healing to selected target",
"NoMedicineSkill": "No Medicine Skill",
"Heals": "heals"
},
"Notifications": {
"HealingApplied": "{name} has been healed for {amount} points.",
"DamageApplied": "{name} has taken {amount} damage."
},
"Errors": {
"NoTokenSelected": "No token selected. Select a token on the scene before applying.",
"InvalidRollFormula": "Invalid roll formula."
}
},
"TYPES.Actor.creature": "Creature"

View File

@@ -18,6 +18,12 @@
"Save": "Sauvegarder",
"Cancel": "Annuler",
"Close": "Fermer",
"Dialog": {
"ConfirmDeleteTitle": "Confirmer la suppression",
"ConfirmDeleteContent": "Êtes-vous sûr de vouloir supprimer \"{name}\" ?",
"Yes": "Oui",
"No": "Non"
},
"Themes": {
"BlackAndRed": "Couverture Classique Traveller",
"Mwamba": "Oppa Mwamba Style",
@@ -60,6 +66,7 @@
"Weeks": "Semaines",
"NewCareer": "Nouvelle Carrière",
"AddCareer": "Ajouter Carrière",
"ThisTrait": "ce trait",
"EditCareer": "Éditer Carrière",
"EditTrait": "Éditer Trait",
"DeleteTrait": "Supprimer Trait",
@@ -343,7 +350,10 @@
"EncumbranceDM": "Encombrement (MD -2)",
"FatigueDM": "Fatigue (MD -2)",
"Boon": "Avantage",
"Bane": "Désavantage"
"Bane": "Désavantage",
"CreatureSkill": "Compétence",
"NoSkill": "Aucune compétence",
"Days": "Jours"
},
"Timeframes": {
"Normal": "Normal",
@@ -355,7 +365,11 @@
"ApplyDamages": "Appliquer Dégâts",
"Damages": "Lancer les Dégâts",
"Success": "Succès",
"Failure": "Échec"
"Failure": "Échec",
"Effect": "Effet",
"Dice": "Dés",
"Result": "Résultat",
"DiceModifier": "Modificateur de dés"
}
},
"Items": {
@@ -393,7 +407,7 @@
"Informations": "Informations",
"Improvement": "Améliorations",
"Interval": "Intervalle",
"IsMelee": "Est Mêlée",
"IsMelee": "Arme de Mêlée",
"Items": "Objets",
"Level": "Niveau",
"Location": "Localisation",
@@ -410,6 +424,7 @@
"Occupation": "Profession",
"OnHand": "Sur Soi",
"Options": "Options",
"PerDay": "par jour",
"PSICost": "Coût PSI",
"Powered": "Alimenté",
"Processing": "Capacité de Traitement",
@@ -461,11 +476,13 @@
"TabAttacks": "Attaques",
"TabTraits": "Traits",
"TabInfo": "Informations",
"TabCombat": "Combat",
"SkillName": "Compétence",
"SkillLevel": "Niveau",
"SkillNote": "Note",
"AttackName": "Attaque",
"AttackDamage": "Dommages",
"AttackSkill": "Compétence",
"TraitName": "Trait",
"TraitValue": "Valeur",
"AddSkill": "Ajouter une compétence",
@@ -480,6 +497,36 @@
"SkillLabel": "Compétence",
"RollTitle": "Jet de créature"
},
"SpeedBands": {
"Stoppped": "Arrêté",
"Idle": "Au ralenti",
"VerySlow": "Très lent",
"Slow": "Lent",
"Medium": "Moyen",
"High": "Élevé",
"Fast": "Rapide",
"VeryFast": "Très rapide",
"Subsonic": "Subsonique",
"Hypersonic": "Hypersonique"
},
"Vehicule": {
"Hull": "Coque",
"ArmorFront": "Av.",
"ArmorSides": "Lat.",
"ArmorRear": "Arr.",
"Armor": "Armure",
"SpeedCruise": "Vitesse croisière",
"SpeedMax": "Vitesse max",
"Agility": "Agilité",
"Crew": "Équipage",
"Passengers": "Passagers",
"Cargo": "Cargo (t)",
"Shipping": "Expédition (t)",
"Cost": "Coût (Cr)",
"Autopilot": "Autopilote",
"TabStats": "Statistiques",
"TabDescription": "Description"
},
"CreatureBehaviorType": {
"herbivore": "Herbivore",
"carnivore": "Carnivore",
@@ -500,6 +547,29 @@
"necrophage": "Nécrophage",
"reducteur": "Réducteur",
"opportuniste": "Opportuniste"
},
"Healing": {
"Title": "Soins",
"FirstAid": "Premiers soins",
"Surgery": "Chirurgie",
"MedicalCare": "Soins médicaux",
"NaturalHealing": "Guérison naturelle",
"WillRestore": "Restaurera",
"SurgeryFailed": "Chirurgie échouée - patient subit des dégâts",
"ApplyHealing": "Appliquer les soins",
"ApplySurgeryDamage": "Appliquer les dégâts chirurgicaux",
"SurgeryDamage": "dégâts chirurgicaux",
"ApplyToTarget": "Appliquer les soins à la cible sélectionnée",
"NoMedicineSkill": "Pas de compétence Médecine",
"Heals": "soigne"
},
"Notifications": {
"HealingApplied": "{name} a été soigné(e) de {amount} points.",
"DamageApplied": "{name} a subi {amount} dégâts."
},
"Errors": {
"NoTokenSelected": "Aucun token sélectionné. Sélectionnez un token sur la scène avant d'appliquer.",
"InvalidRollFormula": "Formule de jet invalide."
}
},
"TYPES.Actor.creature": "Créature"

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -52,9 +52,32 @@ export class TravellerActor extends Actor {
}
}
applyDamage(amount) {
applyDamage(amount, { ignoreArmor = false } = {}) {
if (this.type === "character") {
ActorCharacter.applyDamage(this, amount);
return ActorCharacter.applyDamage(this, amount, { ignoreArmor });
} else if (this.type === "creature") {
if (isNaN(amount) || amount === 0) return;
if (amount < 0) amount = Math.abs(amount);
const armorValue = ignoreArmor ? 0 : (this.system.armor ?? 0);
const effective = Math.max(0, amount - armorValue);
if (effective === 0) return;
const newValue = Math.max(0, (this.system.life.value ?? 0) - effective);
return this.update({ "system.life.value": newValue });
}
}
applyHealing(amount) {
if (this.type === "character") {
return ActorCharacter.applyHealing(this, amount);
} else if (this.type === "creature") {
if (isNaN(amount) || amount === 0) return;
if (amount < 0) amount = Math.abs(amount);
const maxValue = this.system.life.max ?? 0;
const current = this.system.life.value ?? 0;
const newValue = Math.min(current + amount, maxValue);
if (newValue !== current) {
return this.update({ "system.life.value": newValue });
}
}
}

View File

@@ -70,4 +70,35 @@ export class CharacterPrompts {
]
});
}
static async openHealingDays() {
return await DialogV2.wait({
window: {
title: game.i18n.localize("MGT2.Healing.Title")
},
classes: ["mgt2-roll-dialog"],
content: `
<form>
<div style="padding: 12px;">
<div class="form-group">
<label>${game.i18n.localize("MGT2.RollPrompt.Days") || "Jours"}</label>
<input type="number" name="days" value="1" min="1" max="999" />
</div>
</div>
</form>
`,
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;
}
}
]
});
}
}

View File

@@ -331,7 +331,7 @@ export class ActorCharacter {
// $this.update({ system: { characteristics: data } });
// }
static applyDamage($this, amount) {
static applyDamage($this, amount, { ignoreArmor = false } = {}) {
if (isNaN(amount) || amount === 0) return;
const rank1 = $this.system.config.damages.rank1;
const rank2 = $this.system.config.damages.rank2;
@@ -344,6 +344,12 @@ export class ActorCharacter {
if (amount < 0) amount = Math.abs(amount);
if (!ignoreArmor) {
const armorValue = $this.system.inventory?.armor ?? 0;
amount = Math.max(0, amount - armorValue);
if (amount === 0) return;
}
for (const [key, rank] of Object.entries(data)) {
if (rank.value > 0) {
if (rank.value >= amount) {
@@ -361,6 +367,48 @@ export class ActorCharacter {
$this.update({ system: { characteristics: data } });
}
static applyHealing($this, amount, type) {
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;
// Data to restore (reverse cascade: END → DEX → STR)
const data = {};
const rankOrder = [rank3, rank2, rank1]; // Reverse order for healing
const maxValues = {
[rank1]: $this.system.characteristics[rank1].max,
[rank2]: $this.system.characteristics[rank2].max,
[rank3]: $this.system.characteristics[rank3].max
};
if (amount < 0) amount = Math.abs(amount);
// Distribute healing from lowest rank first (END → DEX → STR typically)
for (const rank of rankOrder) {
const current = $this.system.characteristics[rank].value;
const max = maxValues[rank];
if (current < max && amount > 0) {
const canRestore = max - current;
const restore = Math.min(amount, canRestore);
if (!data[rank]) {
data[rank] = { value: current };
}
data[rank].value += restore;
data[rank].dm = this.getModifier(data[rank].value);
amount -= restore;
}
}
// Only update if something was restored
if (Object.keys(data).length > 0) {
return $this.update({ system: { characteristics: data } });
}
}
static getContainers($this) {
const containers = [];
for (let item of $this.items) {

View File

@@ -33,6 +33,7 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
traitEdit: TravellerCharacterSheet.#onTraitEdit,
traitDelete: TravellerCharacterSheet.#onTraitDelete,
openEditor: TravellerCharacterSheet.#onOpenEditor,
heal: TravellerCharacterSheet.#onHeal,
},
}
@@ -54,7 +55,11 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
async _prepareContext() {
const context = await super._prepareContext();
const actor = this.document;
const enrich = (html) => foundry.applications.ux.TextEditor.implementation.enrichHTML(html ?? "", { async: true });
context.enrichedBiography = await enrich(actor.system.biography);
context.enrichedNotes = await enrich(actor.system.notes);
context.enrichedFinanceNotes = await enrich(actor.system.finance?.notes);
context.settings = {
weightUnit: "kg",
usePronouns: game.settings.get("mgt2", "usePronouns"),
@@ -152,9 +157,10 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
if (item.hasOwnProperty("equipped")) {
i._canEquip = true;
i._toggleClass = item.equipped ? "active" : "";
i.toggleClass = item.equipped ? "active" : "";
} else {
i._canEquip = false;
i.toggleClass = "";
}
switch (i.type) {
@@ -288,7 +294,6 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
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);
@@ -452,17 +457,34 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
if (item) item.sheet.render(true);
}
static async #confirmDelete(name) {
return foundry.applications.api.DialogV2.confirm({
window: { title: game.i18n.localize("MGT2.Dialog.ConfirmDeleteTitle") },
content: `<p>${game.i18n.format("MGT2.Dialog.ConfirmDeleteContent", { name })}</p>`,
yes: { label: game.i18n.localize("MGT2.Dialog.Yes"), icon: "fas fa-trash" },
no: { label: game.i18n.localize("MGT2.Dialog.No"), icon: "fas fa-times" },
rejectClose: false,
modal: true
});
}
static async #onDeleteItem(event, target) {
event.preventDefault();
const li = target.closest("[data-item-id]");
if (!li?.dataset.itemId) return;
const item = this.actor.items.get(li.dataset.itemId);
if (!item) return;
const confirmed = await TravellerCharacterSheet.#confirmDelete(item.name);
if (!confirmed) 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);
const itemId = li?.dataset.itemId;
if (!itemId) return;
const item = this.actor.items.get(itemId);
if (!item) return;
await item.update({ "system.equipped": !item.system.equipped });
}
@@ -530,6 +552,9 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
const container = containers.find(x => x._id === this.actor.system.containerView);
if (!container) return;
const confirmed = await TravellerCharacterSheet.#confirmDelete(container.name);
if (!confirmed) return;
const containerItems = this.actor.items.filter(
x => x.system.hasOwnProperty("container") && x.system.container.id === container._id
);
@@ -564,6 +589,7 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
encumbrance: this.actor.system.states.encumbrance,
difficulty: null,
damageFormula: null,
isMelee: false,
};
const cardButtons = [];
@@ -631,6 +657,9 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
if (itemObj?.system.hasOwnProperty("damage")) {
rollOptions.damageFormula = itemObj.system.damage;
if (itemObj.type === "weapon") {
rollOptions.isMelee = itemObj.system.range?.isMelee === true;
}
if (itemObj.type === "disease") {
if (itemObj.system.subType === "disease")
rollOptions.rollTypeName = game.i18n.localize("MGT2.DiseaseSubType.disease");
@@ -695,13 +724,13 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
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);
const customDMVal = parseInt(userRollData.customDM ?? "0", 10);
if (!isNaN(customDMVal) && customDMVal !== 0) {
rollFormulaParts.push(customDMVal > 0 ? `+${customDMVal}` : `${customDMVal}`);
rollModifiers.push(game.i18n.localize("MGT2.RollPrompt.CustomDM") + " " + (customDMVal > 0 ? `+${customDMVal}` : `${customDMVal}`));
}
if (MGT2Helper.hasValue(userRollData, "difficulty")) rollOptions.difficulty = userRollData.difficulty;
if (MGT2Helper.hasValue(userRollData, "difficulty") && userRollData.difficulty !== "") rollOptions.difficulty = userRollData.difficulty;
const rollFormula = rollFormulaParts.join("");
if (!Roll.validate(rollFormula)) {
@@ -715,37 +744,74 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
await this.token.combatant.update({ initiative: roll.total });
}
// ── Compute effect and effective damage formula ──────────────────────
let rollSuccess = false;
let rollFailure = false;
let rollEffect = undefined;
let rollEffectStr = undefined;
let difficultyValue = null;
if (MGT2Helper.hasValue(rollOptions, "difficulty")) {
difficultyValue = MGT2Helper.getDifficultyValue(rollOptions.difficulty);
rollEffect = roll.total - difficultyValue;
rollEffectStr = (rollEffect >= 0 ? "+" : "") + rollEffect;
rollSuccess = rollEffect >= 0;
rollFailure = !rollSuccess;
}
// Build effective damage formula: base + effect + STR DM (melee)
let effectiveDamageFormula = rollOptions.damageFormula || null;
if (effectiveDamageFormula) {
if (rollEffect !== undefined && rollEffect !== 0) {
effectiveDamageFormula += (rollEffect >= 0 ? "+" : "") + rollEffect;
}
if (rollOptions.isMelee) {
const strDm = this.actor.system.characteristics.strength?.dm ?? 0;
if (strDm !== 0) effectiveDamageFormula += (strDm >= 0 ? "+" : "") + strDm;
}
}
// ── Build roll breakdown tooltip ─────────────────────────────────────
const diceRawTotal = roll.dice.reduce((s, d) => s + d.total, 0);
const breakdownParts = [game.i18n.localize("MGT2.Chat.Roll.Dice") + " " + diceRawTotal];
for (const mod of rollModifiers) breakdownParts.push(mod);
if (rollEffectStr !== undefined)
breakdownParts.push(game.i18n.localize("MGT2.Chat.Roll.Effect") + " " + rollEffectStr);
const rollBreakdown = breakdownParts.join(" | ");
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,
rollBreakdown,
showButtons: true,
showLifeButtons: false,
showRollRequest: false,
rollTypeName: rollOptions.rollTypeName,
rollObjectName: rollOptions.rollObjectName,
rollModifiers: rollModifiers,
showRollDamage: rollOptions.damageFormula !== null && rollOptions.damageFormula !== "",
// Show damage button only if there's a formula AND (no difficulty check OR roll succeeded)
showRollDamage: !!effectiveDamageFormula && (!difficultyValue || rollSuccess),
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;
chatData.rollEffect = rollEffect;
chatData.rollEffectStr = rollEffectStr;
chatData.rollSuccess = rollSuccess || undefined;
chatData.rollFailure = rollFailure || undefined;
}
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 (effectiveDamageFormula) {
flags = { mgt2: { damage: { formula: effectiveDamageFormula, rollObjectName: rollOptions.rollObjectName, rollTypeName: rollOptions.rollTypeName } } };
}
if (cardButtons.length > 0) {
if (!flags) flags = { mgt2: {} };
@@ -816,6 +882,8 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
static async #onTraitDelete(event, target) {
event.preventDefault();
const confirmed = await TravellerCharacterSheet.#confirmDelete(game.i18n.localize("MGT2.Actor.ThisTrait"));
if (!confirmed) return;
const element = target.closest("[data-traits-part]");
const index = Number(element.dataset.traitsPart);
const traits = foundry.utils.deepClone(this.actor.system.personal.traits);
@@ -832,4 +900,323 @@ export default class TravellerCharacterSheet extends MGT2ActorSheet {
this.actor.system.personal.speciesText.descriptionLong
);
}
static async #onHeal(event, target) {
event.preventDefault();
const healType = target.dataset.healType;
if (canvas.tokens.controlled.length === 0) {
ui.notifications.warn(game.i18n.localize("MGT2.Errors.NoTokenSelected"));
return;
}
if (healType === "firstaid") {
// Find Medicine skill to pre-select
// Use normalized string matching to handle accents
const medSkill = this.actor.items.find(i => {
if (i.type !== "talent" || i.system.subType !== "skill") return false;
const normalized = i.name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
return normalized.includes("medecin") || normalized.includes("medicine");
});
// Only EDU characteristic available for First Aid
const characteristics = [
{ _id: "education", name: game.i18n.localize("MGT2.Characteristics.education.name") }
];
const rollOptions = {
rollTypeName: game.i18n.localize("MGT2.Healing.FirstAid"),
rollObjectName: this.actor.name,
characteristics: characteristics, // Only EDU
characteristic: "education", // Pre-selected
skill: medSkill?.id ?? "", // Medicine skill ID for pre-selection (must match _id in array)
skillName: medSkill?.name ?? game.i18n.localize("MGT2.Healing.NoMedicineSkill"), // Display name
skillLevel: medSkill?.system.level ?? -3, // -3 if not found
skills: medSkill ? [{ _id: medSkill.id, name: medSkill.name, level: medSkill.system.level }] : [],
difficulty: "Average", // First Aid difficulty is 8 (Average)
showHeal: true,
healType: MGT2.HealingType.FIRST_AID,
};
const userRollData = await RollPromptHelper.roll(rollOptions);
if (userRollData) {
// Build formula with all DMs — same pattern as standard skill roll
const rollFormulaParts = [];
const rollModifiers = [];
if (userRollData.diceModifier) {
rollFormulaParts.push("3d6");
rollFormulaParts.push(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 && 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.customDM && userRollData.customDM !== "") {
let s = userRollData.customDM.trim();
if (/^[0-9]/.test(s)) rollFormulaParts.push("+");
rollFormulaParts.push(s);
rollModifiers.push("DM " + s);
}
const rollFormula = rollFormulaParts.join("");
const roll = await new Roll(rollFormula, this.actor.getRollData()).roll();
// Difficulty for First Aid is Average (8)
const difficulty = 8;
const effect = roll.total - difficulty;
const isSuccess = effect >= 0;
const healing = isSuccess ? Math.max(1, effect) : 0;
const cardButtons = isSuccess
? [{ label: game.i18n.localize("MGT2.Healing.ApplyHealing"), action: "healing" }]
: [];
const chatData = {
user: game.user.id,
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
formula: roll._formula,
tooltip: await roll.getTooltip(),
total: Math.round(roll.total * 100) / 100,
rollTypeName: game.i18n.localize("MGT2.Healing.FirstAid"),
rollObjectName: this.actor.name,
rollModifiers: rollModifiers,
rollDifficulty: difficulty,
rollDifficultyLabel: MGT2Helper.getDifficultyDisplay("Average"),
rollEffectStr: isSuccess ? effect.toString() : undefined,
healingAmount: isSuccess ? healing : undefined,
rollSuccess: isSuccess || undefined,
rollFailure: !isSuccess || undefined,
showButtons: isSuccess,
hasDamage: false,
showRollDamage: false,
cardButtons: cardButtons,
};
const html = await foundry.applications.handlebars.renderTemplate("systems/mgt2/templates/chat/roll.html", chatData);
chatData.content = html;
chatData.flags = { mgt2: { healing: { amount: healing } } };
return roll.toMessage(chatData);
}
} else if (healType === "surgery") {
// Find Medicine skill to pre-select (same as first aid)
const medSkill = this.actor.items.find(i => {
if (i.type !== "talent" || i.system.subType !== "skill") return false;
const normalized = i.name.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
return normalized.includes("medecin") || normalized.includes("medicine");
});
const characteristics = [
{ _id: "education", name: game.i18n.localize("MGT2.Characteristics.education.name") }
];
const rollOptions = {
rollTypeName: game.i18n.localize("MGT2.Healing.Surgery"),
rollObjectName: this.actor.name,
characteristics: characteristics,
characteristic: "education",
skill: medSkill?.id ?? "",
skillName: medSkill?.name ?? game.i18n.localize("MGT2.Healing.NoMedicineSkill"),
skillLevel: medSkill?.system.level ?? -3,
skills: medSkill ? [{ _id: medSkill.id, name: medSkill.name, level: medSkill.system.level }] : [],
difficulty: "Average",
showHeal: true,
healType: MGT2.HealingType.SURGERY,
};
const userRollData = await RollPromptHelper.roll(rollOptions);
if (userRollData) {
// Build formula with all DMs — same pattern as standard skill roll
const rollFormulaParts = [];
const rollModifiers = [];
if (userRollData.diceModifier) {
rollFormulaParts.push("3d6");
rollFormulaParts.push(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 && 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.customDM && userRollData.customDM !== "") {
let s = userRollData.customDM.trim();
if (/^[0-9]/.test(s)) rollFormulaParts.push("+");
rollFormulaParts.push(s);
rollModifiers.push("DM " + s);
}
const rollFormula = rollFormulaParts.join("");
const roll = await new Roll(rollFormula, this.actor.getRollData()).roll();
// Difficulty for Surgery is Average (8)
const difficulty = 8;
const effect = roll.total - difficulty;
const isSuccess = effect >= 0;
// Success: heal Math.max(1, effect); Failure: patient takes 3 + |effect| damage
const healing = isSuccess ? Math.max(1, effect) : 0;
const surgeryDamage = isSuccess ? 0 : 3 + Math.abs(effect);
const cardButtons = [];
if (isSuccess) {
cardButtons.push({ label: game.i18n.localize("MGT2.Healing.ApplyHealing"), action: "healing" });
} else {
cardButtons.push({ label: game.i18n.localize("MGT2.Healing.ApplySurgeryDamage"), action: "surgeryDamage" });
}
const chatData = {
user: game.user.id,
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
formula: roll._formula,
tooltip: await roll.getTooltip(),
total: Math.round(roll.total * 100) / 100,
rollTypeName: game.i18n.localize("MGT2.Healing.Surgery"),
rollObjectName: this.actor.name,
rollModifiers: rollModifiers,
rollDifficulty: difficulty,
rollDifficultyLabel: MGT2Helper.getDifficultyDisplay("Average"),
rollEffectStr: effect.toString(),
healingAmount: isSuccess ? healing : undefined,
surgeryDamageAmount: isSuccess ? undefined : surgeryDamage,
rollSuccess: isSuccess || undefined,
rollFailure: !isSuccess || undefined,
showButtons: true,
hasDamage: false,
showRollDamage: false,
cardButtons: cardButtons,
};
const html = await foundry.applications.handlebars.renderTemplate("systems/mgt2/templates/chat/roll.html", chatData);
chatData.content = html;
chatData.flags = { mgt2: { surgery: { healing, surgeryDamage } } };
return roll.toMessage(chatData);
}
} else if (healType === "medical") {
const result = await CharacterPrompts.openHealingDays();
if (result) {
const endMD = this.actor.system.characteristics.endurance.dm;
const medSkill = this.actor.items.find(i =>
i.type === "talent" &&
i.system.subType === "skill" &&
(i.name.toLowerCase().includes("medecin") || i.name.toLowerCase().includes("medicine"))
);
const skillValue = medSkill ? medSkill.system.level : 0;
const days = result.days;
const healingPerDay = Math.max(1, 3 + endMD + skillValue);
const totalHealing = healingPerDay * days;
const rollModifiers = [
`3 (base)`,
`${endMD >= 0 ? "+" : ""}${endMD} END`,
`+${skillValue} ${medSkill?.name ?? "Médecine"}`,
`× ${days} ${game.i18n.localize("MGT2.RollPrompt.Days").toLowerCase()}`
];
const templateData = {
rollObjectName: this.actor.name,
rollTypeName: game.i18n.localize("MGT2.Healing.MedicalCare"),
rollModifiers,
formula: `${healingPerDay} ${game.i18n.localize("MGT2.Items.PerDay")}`,
tooltip: "",
total: totalHealing,
rollSuccess: true,
showButtons: true,
cardButtons: [
{ action: "healing", label: game.i18n.localize("MGT2.Healing.ApplyHealing") }
]
};
const content = await renderTemplate(
"systems/mgt2/templates/chat/roll.html",
templateData
);
await ChatMessage.create({
user: game.user.id,
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
content,
flags: { mgt2: { healing: { amount: totalHealing } } }
});
}
} else if (healType === "natural") {
const result = await CharacterPrompts.openHealingDays();
if (result) {
const endMD = this.actor.system.characteristics.endurance.dm;
let totalAmount = 0;
const rolls = [];
for (let i = 0; i < result.days; i++) {
const roll = await new Roll("1d6").evaluate();
const dayHealing = Math.max(1, roll.total + endMD);
rolls.push({ roll, dayHealing });
totalAmount += dayHealing;
}
// Build roll details
const rollDisplay = rolls.map((r, idx) =>
`<div><strong>${game.i18n.localize("MGT2.RollPrompt.Days")} ${idx + 1}:</strong> 1d6 = ${r.roll.total} + ${endMD > 0 ? "+" : ""}${endMD} = <strong>${r.dayHealing}</strong></div>`
).join("");
const chatData = {
user: game.user.id,
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
content: `<div class="mgt2-chat-roll">
<div class="mgt2-roll-header">
<span class="mgt2-roll-char-name">${this.actor.name}</span>
<div class="mgt2-roll-meta">
<span class="mgt2-roll-type">${game.i18n.localize("MGT2.Healing.NaturalHealing")}</span>
</div>
</div>
<div class="mgt2-roll-modifier">${result.days} ${game.i18n.localize("MGT2.RollPrompt.Days")}</div>
<div style="padding: 8px 0; font-size: 0.9em;">
${rollDisplay}
</div>
<div class="mgt2-effect is-success">
${game.i18n.localize("MGT2.Chat.Roll.Effect")} <span class="mgt2-effect-value">${totalAmount}</span>
</div>
</div>`,
flags: { mgt2: { healing: { amount: totalAmount } } }
};
await ChatMessage.create(chatData);
// Apply healing immediately
await this.actor.applyHealing(totalAmount);
ui.notifications.info(
game.i18n.format("MGT2.Notifications.HealingApplied",
{ name: this.actor.name, amount: totalAmount })
);
}
}
}
}

View File

@@ -1,4 +1,8 @@
import MGT2ActorSheet from "./base-actor-sheet.mjs";
import { RollPromptHelper } from "../../roll-prompt.js";
import { MGT2Helper } from "../../helper.js";
const { renderTemplate } = foundry.applications.handlebars;
/** Convert Traveller dice notation (e.g. "2D", "4D+2", "3D6") to FoundryVTT formula */
function normalizeDice(formula) {
@@ -46,13 +50,16 @@ export default class TravellerCreatureSheet extends MGT2ActorSheet {
}
/** @override */
tabGroups = { primary: "skills" }
tabGroups = { primary: "combat" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
const actor = this.document;
const enrich = (html) => foundry.applications.ux.TextEditor.implementation.enrichHTML(html ?? "", { async: true });
context.enrichedBiography = await enrich(actor.system.biography);
context.enrichedNotes = await enrich(actor.system.notes);
context.sizeLabel = this._getSizeLabel(actor.system.life.max);
context.sizeTraitLabel = this._getSizeTrait(actor.system.life.max);
context.config = CONFIG.MGT2;
@@ -88,101 +95,36 @@ export default class TravellerCreatureSheet extends MGT2ActorSheet {
return "Grand (+6)";
}
// ───────────────────────────────────────────────────────── Roll Handlers
// ───────────────────────────────────────────────────────── Roll Helpers
/** 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;
static async #postCreatureRoll({ actor, roll, rollLabel, dm, difficulty, difficultyLabel, rollMode, extraTooltip }) {
const diffTarget = MGT2Helper.getDifficultyValue(difficulty ?? "Average");
const hasDifficulty = !!difficulty;
const success = hasDifficulty ? roll.total >= diffTarget : true;
const effect = roll.total - diffTarget;
const effectStr = (effect >= 0 ? "+" : "") + effect;
const rollFormula = normalizeDice(attack.damage);
const roll = await new Roll(rollFormula).evaluate();
const total = 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 diceRawTotal = roll.dice.reduce((s, d) => s + d.total, 0);
const breakdownParts = [game.i18n.localize("MGT2.Chat.Roll.Dice") + " " + diceRawTotal];
if (dm !== 0) breakdownParts.push(`DM ${dm >= 0 ? "+" : ""}${dm}`);
if (hasDifficulty) breakdownParts.push(game.i18n.localize("MGT2.Chat.Roll.Effect") + " " + effectStr);
if (extraTooltip) breakdownParts.push(extraTooltip);
const rollBreakdown = breakdownParts.join(" | ");
const chatData = {
creatureName: actor.name,
creatureImg: actor.img,
rollLabel: skill.name.toUpperCase(),
formula: fullFormula,
rollLabel,
formula: roll.formula,
total: roll.total,
tooltip: await roll.getTooltip(),
difficulty: difficultyTarget,
difficultyLabel,
success,
failure: !success,
rollBreakdown,
difficulty: hasDifficulty ? diffTarget : null,
difficultyLabel: difficultyLabel ?? MGT2Helper.getDifficultyDisplay(difficulty),
success: hasDifficulty ? success : null,
failure: hasDifficulty ? !success : null,
effect: hasDifficulty ? effect : null,
effectStr: hasDifficulty ? effectStr : null,
modifiers: dm !== 0 ? [`DM ${dm >= 0 ? "+" : ""}${dm}`] : [],
};
@@ -195,8 +137,125 @@ export default class TravellerCreatureSheet extends MGT2ActorSheet {
content: chatContent,
speaker: ChatMessage.getSpeaker({ actor }),
rolls: [roll],
rollMode: game.settings.get("core", "rollMode"),
rollMode: rollMode ?? game.settings.get("core", "rollMode"),
});
return { success, effect, total: roll.total };
}
// ───────────────────────────────────────────────────────── Roll Handlers
/** Roll a skill check (2d6 + level vs difficulty) — uses unified dialog */
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 result = await RollPromptHelper.roll({
isCreature: true,
showSkillSelector: false,
skillName: skill.name,
skillLevel: skill.level,
difficulty: "Average",
title: game.i18n.localize("MGT2.Creature.RollSkill") + " — " + skill.name,
});
if (!result) return;
const customDM = parseInt(result.customDM ?? "0", 10) || 0;
const skillLevel = parseInt(skill.level ?? 0, 10) || 0;
const dm = skillLevel + customDM;
const diceModifier = result.diceModifier ?? "";
// Build formula exactly like character-sheet: parts joined without spaces
const parts = [];
if (diceModifier) {
parts.push("3d6", diceModifier);
} else {
parts.push("2d6");
}
if (dm !== 0) parts.push(MGT2Helper.getFormulaDM(dm));
const fullFormula = parts.join("");
const roll = await new Roll(fullFormula).evaluate();
const rollLabel = `${skill.name.toUpperCase()} (${skillLevel >= 0 ? "+" : ""}${skillLevel})`;
const tooltipParts = [`Dés: ${roll.dice.reduce((s, d) => s + d.total, 0)}`];
if (skillLevel !== 0) tooltipParts.push(`${skill.name} ${skillLevel >= 0 ? "+" : ""}${skillLevel}`);
if (customDM !== 0) tooltipParts.push(`MD perso ${customDM >= 0 ? "+" : ""}${customDM}`);
await TravellerCreatureSheet.#postCreatureRoll({
actor, roll, rollLabel,
dm,
difficulty: result.difficulty,
rollMode: result.rollMode,
extraTooltip: tooltipParts.join(" | "),
});
}
/** Roll an attack: dialog with skill selector, then roll 2d6+skill+DM vs difficulty; on success roll damage */
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 skills = actor.system.skills ?? [];
const result = await RollPromptHelper.roll({
isCreature: true,
showSkillSelector: true,
creatureSkills: skills,
selectedSkillIndex: attack.skill ?? -1,
difficulty: "Average",
title: game.i18n.localize("MGT2.Creature.RollAttack") + " — " + attack.name,
});
if (!result) return;
const skillIndex = parseInt(result.creatureSkillIndex ?? "-1", 10);
const chosenSkill = (skillIndex >= 0 && skillIndex < skills.length) ? skills[skillIndex] : null;
const skillLevel = parseInt(chosenSkill?.level ?? 0, 10) || 0;
const customDM = parseInt(result.customDM ?? "0", 10) || 0;
const dm = skillLevel + customDM;
const diceModifier = result.diceModifier ?? "";
// Build formula exactly like character-sheet: parts joined without spaces
const parts = [];
if (diceModifier) {
parts.push("3d6", diceModifier);
} else {
parts.push("2d6");
}
if (dm !== 0) parts.push(MGT2Helper.getFormulaDM(dm));
const fullFormula = parts.join("");
const roll = await new Roll(fullFormula).evaluate();
const rollLabel = chosenSkill
? `${attack.name}${chosenSkill.name} (${skillLevel >= 0 ? "+" : ""}${skillLevel})`
: attack.name;
const tooltipParts = [`Dés: ${roll.dice.reduce((s, d) => s + d.total, 0)}`];
if (chosenSkill) tooltipParts.push(`${chosenSkill.name} ${skillLevel >= 0 ? "+" : ""}${skillLevel}`);
if (customDM !== 0) tooltipParts.push(`MD perso ${customDM >= 0 ? "+" : ""}${customDM}`);
const { success } = await TravellerCreatureSheet.#postCreatureRoll({
actor, roll, rollLabel,
dm,
difficulty: result.difficulty,
rollMode: result.rollMode,
extraTooltip: tooltipParts.join(" | "),
});
// Roll damage only on success
if (success && attack.damage) {
const dmgFormula = normalizeDice(attack.damage);
const dmgRoll = await new Roll(dmgFormula).evaluate();
await dmgRoll.toMessage({
speaker: ChatMessage.getSpeaker({ actor }),
flavor: `<strong>${actor.name}</strong> — ${game.i18n.localize("MGT2.Chat.Weapon.Damage")}: ${attack.name} (${attack.damage})`,
rollMode: result.rollMode ?? game.settings.get("core", "rollMode"),
});
}
}
// ───────────────────────────────────────────────────────── CRUD Handlers
@@ -223,7 +282,7 @@ export default class TravellerCreatureSheet extends MGT2ActorSheet {
_getDefaultRow(prop) {
switch (prop) {
case "skills": return { name: "", level: 0, note: "" };
case "attacks": return { name: "", damage: "1D", description: "" };
case "attacks": return { name: "", damage: "1D", skill: -1, description: "" };
case "traits": return { name: "", value: "", description: "" };
default: return {};
}

View File

@@ -73,6 +73,8 @@ export default class TravellerItemSheet extends HandlebarsApplicationMixin(found
skills.sort(MGT2Helper.compareByName);
skills = [{ _id: "NP", name: game.i18n.localize("MGT2.Items.NotProficient") }].concat(skills);
const enrich = (html) => foundry.applications.ux.TextEditor.implementation.enrichHTML(html ?? "", { async: true });
return {
item: item,
document: item,
@@ -91,6 +93,10 @@ export default class TravellerItemSheet extends HandlebarsApplicationMixin(found
weight: weight,
unitlabels: { weight: MGT2Helper.getWeightLabel() },
skills: skills,
enrichedDescription: await enrich(item.system.description),
enrichedDescriptionLong: await enrich(item.system.descriptionLong),
enrichedNotes: await enrich(item.system.notes),
enrichedLockedDescription: await enrich(item.system.lockedDescription),
};
}
@@ -126,6 +132,27 @@ export default class TravellerItemSheet extends HandlebarsApplicationMixin(found
bind(".options-delete", TravellerItemSheet.#onOptionDelete);
bind(".modifiers-create", TravellerItemSheet.#onModifierCreate);
bind(".modifiers-delete", TravellerItemSheet.#onModifierDelete);
// Activate ProseMirror editors for HTMLField fields
for (const btn of html.querySelectorAll(".editor-edit")) {
btn.addEventListener("click", async (event) => {
event.preventDefault();
const editorWrapper = btn.closest(".editor");
if (!editorWrapper) return;
const editorContent = editorWrapper.querySelector(".editor-content");
if (!editorContent || editorContent.classList.contains("ProseMirror")) return;
const target = editorContent.dataset.edit;
const value = foundry.utils.getProperty(this.document, target) ?? "";
btn.remove();
editorWrapper.classList.add("prosemirror");
await ProseMirrorEditor.create(editorContent, value, {
document: this.document,
fieldName: target,
plugins: {},
collaborate: false,
});
});
}
}
_activateTabGroups() {

View File

@@ -21,4 +21,15 @@ export default class TravellerVehiculeSheet extends MGT2ActorSheet {
/** @override */
tabGroups = { primary: "stats" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext();
const actor = this.document;
const enrich = (html) => foundry.applications.ux.TextEditor.implementation.enrichHTML(html ?? "", { async: true });
context.enrichedDescription = await enrich(actor.system.description);
context.enrichedNotes = await enrich(actor.system.notes);
return context;
}
}

View File

@@ -20,11 +20,17 @@ export class ChatHelper {
element.querySelectorAll('button[data-action="healing"]').forEach(el => {
el.addEventListener('click', async event => {
ui.notifications.warn("healing");
await this._applyChatCardHealing(message, event);
});
});
element.querySelectorAll('button[data-index]').forEach(el => {
element.querySelectorAll('button[data-action="surgeryDamage"]').forEach(el => {
el.addEventListener('click', async event => {
await this._applyChatCardSurgeryDamage(message, event);
});
});
element.querySelectorAll('button[data-index]:not([data-action])').forEach(el => {
el.addEventListener('click', async event => {
await this._processRollButtonEvent(message, event);
});
@@ -90,10 +96,41 @@ export class ChatHelper {
}
static _applyChatCardDamage(message, event) {
if (canvas.tokens.controlled.length === 0) {
ui.notifications.warn(game.i18n.localize("MGT2.Errors.NoTokenSelected"));
return;
}
const roll = message.rolls[0];
return Promise.all(canvas.tokens.controlled.map(t => {
const a = t.actor;
return a.applyDamage(roll.total);
}));
}
static _applyChatCardHealing(message, event) {
if (canvas.tokens.controlled.length === 0) {
ui.notifications.warn(game.i18n.localize("MGT2.Errors.NoTokenSelected"));
return;
}
// For First Aid/Surgery healing, use amount from flags; otherwise use roll total
const amount = message.flags?.mgt2?.healing?.amount
?? message.flags?.mgt2?.surgery?.healing
?? Math.max(1, message.rolls[0].total);
return Promise.all(canvas.tokens.controlled.map(t => {
const a = t.actor;
return a.applyHealing(amount);
}));
}
static _applyChatCardSurgeryDamage(message, event) {
if (canvas.tokens.controlled.length === 0) {
ui.notifications.warn(game.i18n.localize("MGT2.Errors.NoTokenSelected"));
return;
}
const amount = message.flags?.mgt2?.surgery?.surgeryDamage ?? 3;
return Promise.all(canvas.tokens.controlled.map(t => {
const a = t.actor;
return a.applyDamage(amount, { ignoreArmor: true });
}));
}
}

View File

@@ -143,7 +143,7 @@ MGT2.SpeedBands = Object.freeze({
VerySlow: "MGT2.SpeedBands.VerySlow",
Slow: "MGT2.SpeedBands.Slow",
Medium: "MGT2.SpeedBands.Medium",
High: "MGT2.SpeedBands.High.",
High: "MGT2.SpeedBands.High",
Fast: "MGT2.SpeedBands.Fast",
VeryFast: "MGT2.SpeedBands.VeryFast",
Subsonic: "MGT2.SpeedBands.Subsonic",
@@ -178,3 +178,10 @@ MGT2.CreatureBehaviorSubType = Object.freeze({
reducteur: "MGT2.CreatureBehaviorSubType.reducteur",
opportuniste: "MGT2.CreatureBehaviorSubType.opportuniste"
});
MGT2.HealingType = Object.freeze({
FIRST_AID: "MGT2.Healing.FirstAid",
SURGERY: "MGT2.Healing.Surgery",
MEDICAL_CARE: "MGT2.Healing.MedicalCare",
NATURAL_HEALING: "MGT2.Healing.NaturalHealing"
});

View File

@@ -47,7 +47,9 @@ export default class CharacterData extends foundry.abstract.TypeDataModel {
}),
health: new fields.SchemaField({
radiations: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true })
radiations: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true }),
lastFirstAidDate: new fields.StringField({ required: false, blank: true, trim: true }),
healingRecoveryMode: new fields.StringField({ required: false, blank: true, trim: true, initial: "" })
}),
study: new fields.SchemaField({
skill: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
@@ -61,7 +63,7 @@ export default class CharacterData extends foundry.abstract.TypeDataModel {
debt: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
livingCost: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
monthlyShipPayments: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
notes: new fields.StringField({ required: false, blank: true, trim: true, initial: "" })
notes: new fields.HTMLField({ required: false, blank: true, trim: true })
}),
containerView: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
containerDropIn: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),

View File

@@ -29,6 +29,7 @@ export default class CreatureData extends foundry.abstract.TypeDataModel {
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true, initial: "" }),
damage: new fields.StringField({ required: true, blank: true, trim: true, initial: "1D" }),
skill: new fields.NumberField({ required: false, initial: -1, integer: true }),
description: new fields.StringField({ required: false, blank: true, trim: true, initial: "" })
})
),

View File

@@ -13,7 +13,7 @@ export function createCharacteristicField(show = true, showMax = false) {
export class ItemBaseData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
description: new fields.StringField({ required: false, blank: true, trim: true, nullable: true }),
description: new fields.HTMLField({ required: false, blank: true, trim: true }),
subType: new fields.StringField({ required: false, blank: false, nullable: true })
};
}

View File

@@ -10,7 +10,7 @@ export default class ItemContainerData extends ItemBaseData {
schema.weight = new fields.NumberField({ required: false, initial: 0, integer: false });
schema.weightless = new fields.BooleanField({ required: false, initial: false });
schema.locked = new fields.BooleanField({ required: false, initial: false }); // GM only
schema.lockedDescription = new fields.StringField({ required: false, blank: true, trim: true, nullable: true });
schema.lockedDescription = new fields.HTMLField({ required: false, blank: true, trim: true });
return schema;
}
}

View File

@@ -3,7 +3,7 @@ const fields = foundry.data.fields;
export default class SpeciesData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
description: new fields.StringField({ required: false, blank: true, trim: true, nullable: true }),
description: new fields.HTMLField({ required: false, blank: true, trim: true }),
descriptionLong: new fields.HTMLField({ required: false, blank: true, trim: true }),
traits: new fields.ArrayField(
new fields.SchemaField({

View File

@@ -27,7 +27,9 @@ export default class VehiculeData extends foundry.abstract.TypeDataModel {
}),
skills: new fields.SchemaField({
autopilot: new fields.NumberField({ required: true, initial: 0, integer: true })
})
}),
description: new fields.HTMLField({ required: false, blank: true, trim: true }),
notes: new fields.HTMLField({ required: false, blank: true, trim: true })
};
}
}

View File

@@ -5,19 +5,39 @@ const { FormDataExtended } = foundry.applications.ux;
export class RollPromptHelper {
static async roll(options) {
// Backward compat: allow (actor, options) or just (options)
if (options.rollTypeName || options.characteristics || options.skill !== undefined) {
// Normal call with options
} else {
// Called with (actor, options)
options = arguments[1] || options;
}
const htmlContent = await renderTemplate('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
// Character-mode fields
characteristics: options.characteristics ?? [],
characteristic: options.characteristic ?? "",
skills: options.skills ?? [],
skill: options.skill ?? "",
fatigue: options.fatigue ?? false,
encumbrance: options.encumbrance ?? false,
difficulty: options.difficulty ?? "Average",
timeframe: options.timeframe ?? "Normal",
customDM: options.customDM ?? "0",
rollMode: options.rollMode ?? "publicroll",
// Creature-mode flags
isCreature: options.isCreature ?? false,
creatureSkills: options.creatureSkills ?? [],
selectedSkillIndex: options.selectedSkillIndex ?? -1,
showSkillSelector: options.showSkillSelector ?? false,
skillName: options.skillName ?? "",
skillLevel: options.skillLevel ?? 0,
// Healing fields
showHeal: options.showHeal ?? false,
healType: options.healType ?? null
});
const theme = game.settings.get("mgt2", "theme");
return await DialogV2.wait({
window: { title: options.title ?? options.rollTypeName ?? game.i18n.localize("MGT2.RollPrompt.Roll") },
classes: ["mgt2-roll-dialog"],

View File

@@ -19,6 +19,7 @@ export const preloadHandlebarsTemplates = async function() {
"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/items/parts/sheet-physical-item-tab.html",
"systems/mgt2/templates/roll-prompt.html",
"systems/mgt2/templates/chat/roll.html",
//"systems/mgt2/templates/chat/roll-characteristic.html",
@@ -26,10 +27,10 @@ export const preloadHandlebarsTemplates = async function() {
"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);
const loader = foundry.applications?.handlebars?.loadTemplates ?? loadTemplates;
return loader(templatePaths);
};

View File

@@ -213,6 +213,7 @@ ul
flex-direction: row
align-content: flex-start
flex-wrap: nowrap
min-height: 330px
.tab
width: 100%
@@ -277,3 +278,15 @@ ul
flex-shrink: 0
display: flex
flex-direction: row
// HTMLField editor min-height in notes/biography/finance tabs
.mgt2.character
.tab[data-tab="notes"],
.tab[data-tab="biography"],
.tab[data-tab="finance"]
.editor,
.editor-content,
prose-mirror
min-height: 300px !important
height: auto !important

View File

@@ -294,6 +294,41 @@ li.chat-message
color: #EE4050
border-top: 1px solid rgba(238,64,80,0.2)
// Effect line
.mgt2-effect
text-align: center
font-size: 0.72rem
font-weight: 600
letter-spacing: 1.5px
text-transform: uppercase
padding: 2px 10px 4px 10px
border-bottom: 2px solid transparent
.mgt2-effect-value
font-size: 1rem
font-weight: 900
margin-left: 4px
.mgt2-healing-amount
font-size: 0.75rem
font-weight: 600
margin-left: 6px
opacity: 0.85
&.is-success
color: #1a8840
border-bottom-color: rgba(26,136,64,0.25)
.mgt2-effect-value
color: #1a8840
&.is-failure
color: #EE4050
border-bottom-color: rgba(238,64,80,0.25)
.mgt2-effect-value
color: #EE4050
// Action buttons
.mgt2-buttons
display: flex

View File

@@ -105,15 +105,19 @@
flex-direction: row
align-items: center
gap: 0.5rem
flex-wrap: wrap
flex-wrap: nowrap
label
flex: 0 0 auto
font-size: 0.75rem
text-transform: uppercase
color: var(--mgt2-color-primary)
font-weight: 700
white-space: nowrap
.behavior-select
flex: 1 1 auto
min-width: 0
background: var(--mgt2-bgcolor-form)
color: var(--mgt2-color-form)
border: 1px solid var(--mgt2-color-primary)
@@ -122,24 +126,29 @@
padding: 1px 4px
.behavior-sep
flex: 0 0 auto
color: var(--mgt2-color-form)
opacity: 0.5
.creature-size-badge
margin-left: auto
flex: 0 0 auto
white-space: nowrap
font-size: 0.75rem
font-style: italic
color: var(--mgt2-color-secondary)
color: var(--mgt2-bgcolor-form)
background: var(--mgt2-bgcolor-primary)
border: 1px solid var(--mgt2-color-secondary)
border: 1px solid var(--mgt2-color-primary)
border-radius: 3px
padding: 1px 6px
// Body / Tabs
// min-height ensures all 4 sidebar tabs are always visible
// (4 tabs × 54px each + 8px padding = 224px)
.creature-body
flex: 1
overflow-y: auto
padding: 0.5rem 0.75rem
min-height: 228px
.tab
display: none
@@ -199,3 +208,18 @@
.mgt2-roll-header-text
flex: 1
// ── Section headers: use shared .header class (same as character sheet)
// HTMLField editor min-height in info tab
.mgt2.creature
// Section headers (skills/attacks/traits) margin between successive sections
.table-container + .header
margin-top: 8px
.tab[data-tab="info"]
.editor,
.editor-content,
prose-mirror
min-height: 300px !important
height: auto !important

View File

@@ -31,15 +31,15 @@
.dialog-content, .standard-form
background: #ffffff !important
padding: 14px 18px 10px !important
padding: 8px 14px 6px !important
// Form group rows
.form-group
display: flex !important
align-items: center !important
gap: 10px !important
margin-bottom: 8px !important
padding: 4px 0 !important
gap: 8px !important
margin-bottom: 3px !important
padding: 2px 0 !important
border-bottom: 1px solid #e8e0e0 !important
&:last-child
@@ -61,7 +61,7 @@
border: 1px solid #ccbbbb !important
color: #0A0405 !important
border-radius: 3px !important
padding: 5px 10px !important
padding: 3px 8px !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.9rem !important
transition: border-color 150ms ease !important
@@ -80,8 +80,8 @@
background: #fdf8f8 !important
border: 1px solid #e0c8c8 !important
border-radius: 5px !important
padding: 10px 14px !important
margin-bottom: 8px !important
padding: 5px 10px !important
margin-bottom: 4px !important
legend
color: #EE4050 !important
@@ -112,7 +112,7 @@
.dialog-buttons, .form-footer, footer
background: #f5eeee !important
border-top: 2px solid #EE4050 !important
padding: 10px 14px !important
padding: 7px 14px !important
display: flex !important
gap: 8px !important
justify-content: center !important
@@ -124,7 +124,7 @@
border: 1px solid #ccbbbb !important
color: #3a2020 !important
border-radius: 4px !important
padding: 7px 14px !important
padding: 5px 12px !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.82rem !important
font-weight: 700 !important

View File

@@ -343,3 +343,76 @@
.itemsheet-panel
display: contents !important
// ── Details tab: 2-column grid layout
.itemsheet
.item-details-grid
display: grid !important
grid-template-columns: 1fr 1fr !important
gap: 4px 16px !important
align-items: start !important
// Traits table spans full width
.table-container
grid-column: 1 / -1 !important
margin-top: 10px !important
// ── Field row: label + input on the same line
.itemsheet
.field-row
display: flex !important
align-items: center !important
gap: 8px !important
min-height: 28px !important
label
flex: 0 0 100px !important
min-width: 0 !important
margin-bottom: 0 !important
white-space: nowrap !important
overflow: hidden !important
text-overflow: ellipsis !important
input[type="text"],
input[type="number"],
select
flex: 1 !important
width: auto !important
height: 24px !important
padding: 2px 6px !important
input.short
flex: 0 0 56px !important
width: 56px !important
.range-inputs
display: flex !important
gap: 4px !important
flex: 1 !important
input
flex: 0 0 52px !important
width: 52px !important
select
flex: 1 !important
// Full-width row (e.g. storage)
&.full
grid-column: 1 / -1 !important
// Checkbox row variant
.field-row--check
label
flex: unset !important
display: flex !important
align-items: center !important
gap: 6px !important
cursor: pointer !important
// ── Description tab: editor min-height
.itemsheet
.tab[data-tab="tab1"]
.editor,
.editor-container
min-height: 200px !important

View File

@@ -3,7 +3,7 @@
// nav (left: 100% of character-body) can extend to the right of the window border.
// Layered !important beats Foundry's unlayered overflow:hidden per CSS cascade spec.
.mgt2.character, .mgt2.creature
.mgt2.character, .mgt2.creature, .mgt2.vehicule
overflow: visible !important
> .window-content
overflow: visible !important
@@ -18,13 +18,16 @@
position: relative !important
overflow: visible !important
.mgt2.vehicule .vehicule-content
position: relative !important
overflow: visible !important
// Vertical sidebar tab navigation (outside window, right side)
.mgt2
nav.sheet-sidebar.tabs
position: absolute !important
left: 100% !important
top: 0 !important
bottom: 0 !important
width: 62px !important
flex: none !important
display: flex !important

View File

@@ -0,0 +1,203 @@
//
// Vehicule Sheet Styles
//
.vehicule-sheet
// Header
.vehicule-header
display: flex
flex-direction: row
align-items: flex-start
gap: 0.75rem
padding: 0.5rem 0.75rem
background: var(--mgt2-bgcolor-form)
border-bottom: 2px solid var(--mgt2-color-primary)
flex-shrink: 0
.vehicule-header-img
flex: 0 0 90px
img.profile
width: 90px
height: 90px
object-fit: cover
border: 2px solid var(--mgt2-color-primary)
border-radius: 4px
cursor: pointer
.vehicule-header-body
flex: 1
display: flex
flex-direction: column
gap: 0.4rem
min-width: 0
.vehicule-name
font-family: "Barlow Condensed", sans-serif
font-size: 1.6rem
font-weight: 700
font-style: italic
color: var(--mgt2-color-form)
background: transparent
border: none
border-bottom: 1px solid var(--mgt2-color-primary)
width: 100%
padding: 0
&:focus
outline: none
border-bottom-color: var(--mgt2-color-secondary)
.vehicule-header-stats
display: flex
flex-direction: row
align-items: flex-start
gap: 0.75rem
flex-wrap: wrap
// Stat boxes (hull, armor)
.vehicule-stat-box
display: flex
flex-direction: column
align-items: center
background: var(--mgt2-bgcolor-primary)
border: 1px solid var(--mgt2-color-primary)
border-radius: 4px
padding: 3px 8px
min-width: 4rem
label
font-family: "Barlow Condensed", sans-serif
font-size: 0.65rem
font-weight: 700
text-transform: uppercase
color: var(--mgt2-color-primary)
line-height: 1.2
white-space: nowrap
.vehicule-stat-value
display: flex
align-items: center
gap: 2px
span
color: var(--mgt2-color-primary)
font-weight: 700
input[type="number"]
width: 2.8rem
text-align: center
background: transparent
border: none
color: var(--mgt2-color-form)
font-family: "Rubik", monospace
font-size: 1rem
font-weight: 600
padding: 0
&:focus
outline: none
border-bottom: 1px solid var(--mgt2-color-secondary)
.vehicule-hull
min-width: 6rem
.vehicule-stat-value input[type="number"]
width: 2.5rem
// Armor group
.vehicule-armor-group
display: flex
flex-direction: column
gap: 2px
.vehicule-armor-label
font-family: "Barlow Condensed", sans-serif
font-size: 0.65rem
font-weight: 700
text-transform: uppercase
color: var(--mgt2-color-primary)
text-align: center
.vehicule-armor-row
display: flex
flex-direction: row
gap: 4px
.vehicule-armor-box
min-width: 3.5rem
// Body wrapper (contains tabs + sidebar nav)
// min-height ensures both tabs in the sidebar are always visible
// (2 tabs × 54px each + 8px padding = 116px)
.vehicule-content
flex: 1
display: flex
flex-direction: column
overflow: hidden
min-height: 320px
// Tab panels
.vehicule-tab
flex: 1
overflow-y: auto
padding: 0.75rem
display: none
&.active
display: block
// Stats grid
.vehicule-stats-grid
display: grid
grid-template-columns: 1fr 1fr
gap: 4px 12px
.vehicule-field
display: flex
flex-direction: row
align-items: center
gap: 8px
padding: 3px 0
border-bottom: 1px solid rgba(0,0,0,0.08)
&:last-child
border-bottom: none
label
font-family: "Barlow Condensed", sans-serif
font-size: 0.72rem
font-weight: 700
text-transform: uppercase
color: var(--mgt2-color-primary)
flex: 0 0 120px
white-space: nowrap
input[type="number"],
select
flex: 1
background: transparent
border: 1px solid transparent
border-radius: 3px
color: var(--mgt2-color-form)
font-family: "Barlow Condensed", sans-serif
font-size: 0.9rem
padding: 2px 4px
&:focus
outline: none
border-color: var(--mgt2-color-primary)
background: rgba(255,255,255,0.1)
input[type="number"]
text-align: center
width: 4rem
select
cursor: pointer
// HTMLField editor min-height in description tab
.mgt2.vehicule
.vehicule-tab[data-tab="description"]
.editor,
.editor-content,
prose-mirror
min-height: 300px !important
height: auto !important

View File

@@ -14,3 +14,4 @@
@import 'components/_tab-sidebar'
@import 'components/_tables'
@import 'components/_creature'
@import 'components/_vehicule'

2
styles/mgt2.min.css vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,47 +1,47 @@
{
"id": "mgt2",
"version": "0.1.4",
"title": "MGT2 - Mongoose Traveller (Unofficial)",
"description": "An unofficial implementation of Mongoose Publishing Traveller (VO/VF). Traveller is the property of Mongoose Publishing, and can be purchased at https://www.mongoosepublishing.com",
"background": "systems/mgt2/assets/screens/rosette-nebula-ngc2239-hoo.webp",
"url": "https://github.com/JDR-Ninja/foundryvtt-mgt2",
"manifest": "https://github.com/JDR-Ninja/foundryvtt-mgt2/releases/latest/download/system.json",
"readme": "https://raw.githubusercontent.com/JDR-Ninja/foundryvtt-mgt2/main/README.md",
"download": "https://github.com/JDR-Ninja/foundryvtt-mgt2/releases/download/v0.1.4/mgt2.zip",
"changelog": "https://raw.githubusercontent.com/JDR-Ninja/foundryvtt-mgt2/main/CHANGELOG.md",
"authors": [
{
"name": "JdR Ninja",
"url": "https://www.jdr.ninja/",
"discord": "jdr.ninja"
}
],
"esmodules": [
"mgt2.bundle.js"
],
"styles": [
"styles/mgt2.min.css"
],
"packs": [],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
},
{
"lang": "fr",
"name": "Français",
"path": "lang/fr.json"
}
],
"compatibility": {
"minimum": "11",
"verified": "12.324"
},
"grid": {
"distance": 1.5,
"units": "m"
},
"primaryTokenAttribute": "life"
"id": "mgt2",
"version": "0.1.4",
"title": "MGT2 - Mongoose Traveller (Unofficial)",
"description": "An unofficial implementation of Mongoose Publishing Traveller (VO/VF). Traveller is the property of Mongoose Publishing, and can be purchased at https://www.mongoosepublishing.com",
"background": "systems/mgt2/assets/screens/rosette-nebula-ngc2239-hoo.webp",
"url": "https://github.com/JDR-Ninja/foundryvtt-mgt2",
"manifest": "https://github.com/JDR-Ninja/foundryvtt-mgt2/releases/latest/download/system.json",
"readme": "https://raw.githubusercontent.com/JDR-Ninja/foundryvtt-mgt2/main/README.md",
"download": "https://github.com/JDR-Ninja/foundryvtt-mgt2/releases/download/v0.1.4/mgt2.zip",
"changelog": "https://raw.githubusercontent.com/JDR-Ninja/foundryvtt-mgt2/main/CHANGELOG.md",
"authors": [
{
"name": "JdR Ninja",
"url": "https://www.jdr.ninja/",
"discord": "jdr.ninja"
}
],
"esmodules": [
"mgt2.bundle.js"
],
"styles": [
"styles/mgt2.min.css"
],
"packs": [],
"languages": [
{
"lang": "en",
"name": "English",
"path": "lang/en.json"
},
{
"lang": "fr",
"name": "Français",
"path": "lang/fr.json"
}
],
"compatibility": {
"minimum": "11",
"verified": "12.324"
},
"grid": {
"distance": 1.5,
"units": "m"
},
"primaryTokenAttribute": "life"
}

View File

@@ -5,7 +5,7 @@
"vehicule",
"creature"
],
"htmlFields": ["notes"],
"htmlFields": ["notes", "biography"],
"character": {},
"vehicule": {},
"creature": {}

View File

@@ -1,6 +1,6 @@
<form class="{{cssClass}} flexcol" autocomplete="off">
<div class="form-group">
<label class="mgt2-checkbox"><input type="checkbox" name="system.config.psionic" data-dtype="Boolean" {{checked system.config.psionic}} />{{ localize 'MGT2.Actor.ShowPsionicTalents' }}</label>
<label class="mgt2-checkbox"><input type="checkbox" name="psionic" data-dtype="Boolean" {{checked system.config.psionic}} />{{ localize 'MGT2.Actor.ShowPsionicTalents' }}</label>
</div>
<fieldset>
<legend>{{ localize 'MGT2.Actor.Initiative' }}</legend>

View File

@@ -272,6 +272,22 @@
<label class="upcase">{{ localize 'MGT2.Actor.Rads' }}</label>
<input class="field" name="system.health.radiations" type="text" value="{{system.health.radiations}}" />
</div>
<!-- HEALING SECTION -->
<div class="header upcase">{{ localize 'MGT2.Healing.Title' }}</div>
<div class="healing-buttons" style="display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px;">
<button type="button" data-action="heal" data-heal-type="firstaid" class="button sm" title="{{ localize 'MGT2.Healing.FirstAid' }}">
<i class="fas fa-bandage"></i> {{ localize 'MGT2.Healing.FirstAid' }}
</button>
<button type="button" data-action="heal" data-heal-type="surgery" class="button sm" title="{{ localize 'MGT2.Healing.Surgery' }}">
<i class="fas fa-flask-vial"></i> {{ localize 'MGT2.Healing.Surgery' }}
</button>
<button type="button" data-action="heal" data-heal-type="medical" class="button sm" title="{{ localize 'MGT2.Healing.MedicalCare' }}">
<i class="fas fa-hospital"></i> {{ localize 'MGT2.Healing.MedicalCare' }}
</button>
<button type="button" data-action="heal" data-heal-type="natural" class="button sm" title="{{ localize 'MGT2.Healing.NaturalHealing' }}">
<i class="fas fa-leaf"></i> {{ localize 'MGT2.Healing.NaturalHealing' }}
</button>
</div>
<div class="table-container">
<div class="table-row heading color-1">
<div class="row-item row-item-30 row-item-left upcase">{{ localize 'MGT2.Actor.Wounds' }}</div>
@@ -443,7 +459,7 @@
<div class="row-item row-item-20 row-item-center">{{weapon.system.damage}}</div>
<div class="row-item row-item-2 row-item-right flex-fix">{{weapon.weight}}</div>
<div class="row-item row-item-15 item-controls flex-fix">
<a class="item-control item-equip {{weapon.toggleClass}}" title="Equip"><i class="fa-solid fa-shield-halved"></i></a>
<a class="item-control item-equip {{weapon.toggleClass}}" data-action="equipItem" title="Equip"><i class="fa-solid fa-shield-halved"></i></a>
<a class="item-control item-storage-in" title="{{ localize 'MGT2.Actor.StoreItem' }}"><i class="fa-solid fa-inbox-in"></i></a>
<a class="item-control item-edit" title="{{ localize 'MGT2.Actor.EditWeapon' }}"><i class="fas fa-edit"></i></a>
<a class="item-control item-delete" title="{{ localize 'MGT2.Actor.DeleteWeapon' }}"><i class="fas fa-trash"></i></a>
@@ -468,7 +484,7 @@
<div class="row-item row-item-10 row-item-center">{{armor.system.protection}}</div>
<div class="row-item row-item-2 row-item-right flex-fix">{{armor.weight}}</div>
<div class="row-item row-item-15 item-controls flex-fix">
<a class="item-control item-equip {{armor.toggleClass}}" title="Equip"><i class="fa-solid fa-shield-halved"></i></a>
<a class="item-control item-equip {{armor.toggleClass}}" data-action="equipItem" title="Equip"><i class="fa-solid fa-shield-halved"></i></a>
<a class="item-control item-storage-in" title="{{ localize 'MGT2.Actor.StoreItem' }}"><i class="fa-solid fa-inbox-in"></i></a>
<a class="item-control item-edit" title="{{ localize 'MGT2.Actor.EditArmor' }}"><i class="fas fa-edit"></i></a>
<a class="item-control item-delete" title="{{ localize 'MGT2.Actor.DeleteArmor' }}"><i class="fas fa-trash"></i></a>
@@ -487,7 +503,7 @@
<div class="row-item row-item-30 row-item-left">{{augment.name}}</div>
<div class="row-item row-item-40 row-item-left">{{augment.system.improvement}}</div>
<div class="row-item row-item-15 item-controls flex-fix">
<a class="item-control item-equip {{augment.toggleClass}}" title="Equip"><i class="fa-solid fa-shield-halved"></i></a>
<a class="item-control item-equip {{augment.toggleClass}}" data-action="equipItem" title="Equip"><i class="fa-solid fa-shield-halved"></i></a>
<a class="item-control item-storage-in" title="{{ localize 'MGT2.Actor.StoreItem' }}"><i class="fa-solid fa-inbox-in"></i></a>
<a class="item-control item-edit" title="{{ localize 'MGT2.Actor.EditAugment' }}"><i class="fas fa-edit"></i></a>
<a class="item-control item-delete" title="{{ localize 'MGT2.Actor.DeleteAugment' }}"><i class="fas fa-trash"></i></a>
@@ -512,7 +528,7 @@
</div>
<div class="row-item row-item-2 row-item-right flex-fix">{{computer.weight}}</div>
<div class="row-item row-item-15 item-controls flex-fix">
<a class="item-control item-equip {{computer.toggleClass}}" title="Equip"><i class="fa-solid fa-shield-halved"></i></a>
<a class="item-control item-equip {{computer.toggleClass}}" data-action="equipItem" title="Equip"><i class="fa-solid fa-shield-halved"></i></a>
<a class="item-control item-storage-in" title="{{ localize 'MGT2.Actor.StoreItem' }}"><i class="fa-solid fa-inbox-in"></i></a>
<a class="item-control item-edit" title="{{ localize 'MGT2.Actor.EditComputer' }}"><i class="fas fa-edit"></i></a>
<a class="item-control item-delete" title="{{ localize 'MGT2.Actor.DeleteComputer' }}"><i class="fas fa-trash"></i></a>
@@ -557,7 +573,7 @@
<div class="row-item row-item-2 row-item-center">{{equipment.system.quantity}}</div>
<div class="row-item row-item-2 row-item-right flex-fix">{{equipment.weight}}</div>
<div class="row-item row-item-15 item-controls flex-fix">
<a class="item-control item-equip {{equipment.toggleClass}}" title="{{ localize 'MGT2.Actor.EquipUnequip' }}"><i class="fa-solid fa-shield-halved"></i></a>
<a class="item-control item-equip {{equipment.toggleClass}}" data-action="equipItem" title="{{ localize 'MGT2.Actor.EquipUnequip' }}"><i class="fa-solid fa-shield-halved"></i></a>
<a class="item-control item-storage-in" title="{{ localize 'MGT2.Actor.StoreEquipment' }}"><i class="fa-solid fa-inbox-in"></i></a>
<a class="item-control item-edit" title="{{ localize 'MGT2.Actor.EditEquipment' }}"><i class="fas fa-edit"></i></a>
<a class="item-control item-delete" title="{{ localize 'MGT2.Actor.DeleteEquipment' }}"><i class="fas fa-trash"></i></a>
@@ -687,8 +703,8 @@
</div>
</div>
<div class="field-group mt-1">
<label class="upcase">{{ localize 'MGT2.Actor.Notes' }}</label>
<textarea name="system.finance.description" rows="3">{{system.finance.description}}</textarea>
<label class="upcase">{{ localize 'MGT2.Actor.Notes' }}</label>
{{formInput systemFields.finance.fields.notes enriched=enrichedFinanceNotes value=system.finance.notes name="system.finance.notes" toggled=true}}
</div>
</div>
</div>
@@ -720,11 +736,11 @@
</div>
<div class="tab w100 h100" data-group="sidebar" data-tab="notes">
<div class="header upcase">{{ localize 'MGT2.Actor.Notes' }}</div>
{{editor system.notes target="system.notes" button=true editable=true}}
{{formInput systemFields.notes enriched=enrichedNotes value=system.notes name="system.notes" toggled=true}}
</div>
<div class="tab w100 h100" data-group="sidebar" data-tab="biography">
<div class="header upcase">{{ localize 'MGT2.Actor.Biography' }}</div>
{{editor system.biography target="system.biography" button=true editable=true}}
{{formInput systemFields.biography enriched=enrichedBiography value=system.biography name="system.biography" toggled=true}}
</div>
{{#if showTrash}}
<!-- <div class="tab" data-group="inventory" data-tab="trash">

View File

@@ -80,19 +80,20 @@
{{!-- ── TAB CONTENT ── --}}
<div class="creature-body">
{{!-- ── TAB : COMBAT (compétences + attaques + traits) ── --}}
<div class="tab" data-group="primary" data-tab="combat">
{{!-- ── TAB : COMPÉTENCES ── --}}
<div class="tab" data-group="primary" data-tab="skills">
{{!-- Compétences --}}
<div class="header upcase">{{ localize 'MGT2.Creature.TabSkills' }}</div>
<div class="table-container">
<div class="table-row heading color-1">
<div class="row-item row-item-left upcase" style="flex: 3">{{ localize 'MGT2.Creature.SkillName' }}</div>
<div class="row-item row-item-center upcase" style="flex: 1">{{ localize 'MGT2.Creature.SkillLevel' }}</div>
<div class="row-item row-item-left upcase" style="flex: 3">{{ localize 'MGT2.Creature.SkillNote' }}</div>
<div class="row-item row-item-right" style="flex: 0 0 3rem">
<a data-action="addSkill" data-prop="skills" title="{{ localize 'MGT2.Creature.AddSkill' }}"><i class="fas fa-plus"></i></a>
<div class="row-item row-item-right item-controls" style="flex: 0 0 3rem">
{{#if isEditable}}<a data-action="addSkill" data-prop="skills" title="{{ localize 'MGT2.Creature.AddSkill' }}"><i class="fas fa-plus"></i></a>{{/if}}
</div>
</div>
{{#each system.skills as |skill i|}}
<div class="table-row">
<div class="row-item row-item-left" style="flex: 3">
@@ -124,29 +125,25 @@
</div>
</div>
{{/each}}
{{#unless system.skills.length}}
<div class="table-row">
<div class="row-item row-item-center text-muted" style="flex:1">{{ localize 'MGT2.Creature.NoSkills' }}</div>
</div>
{{/unless}}
</div>
</div>
{{!-- ── TAB : ATTAQUES ── --}}
<div class="tab" data-group="primary" data-tab="attacks">
{{!-- Attaques --}}
<div class="header upcase">{{ localize 'MGT2.Creature.TabAttacks' }}</div>
<div class="table-container">
<div class="table-row heading color-1">
<div class="row-item row-item-left upcase" style="flex: 3">{{ localize 'MGT2.Creature.AttackName' }}</div>
<div class="row-item row-item-center upcase" style="flex: 2">{{ localize 'MGT2.Creature.AttackDamage' }}</div>
<div class="row-item row-item-left upcase" style="flex: 3">{{ localize 'MGT2.Creature.AttackSkill' }}</div>
<div class="row-item row-item-left upcase" style="flex: 3">{{ localize 'MGT2.Items.Description' }}</div>
<div class="row-item row-item-right" style="flex: 0 0 4rem">
{{#if isEditable}}
<a data-action="addAttack" data-prop="attacks" title="{{ localize 'MGT2.Creature.AddAttack' }}"><i class="fas fa-plus"></i></a>
{{/if}}
<div class="row-item row-item-right item-controls" style="flex: 0 0 4rem">
{{#if isEditable}}<a data-action="addAttack" data-prop="attacks" title="{{ localize 'MGT2.Creature.AddAttack' }}"><i class="fas fa-plus"></i></a>{{/if}}
</div>
</div>
{{#each system.attacks as |atk i|}}
<div class="table-row">
<div class="row-item row-item-left" style="flex: 3">
@@ -163,6 +160,22 @@
<span class="damage-formula">{{atk.damage}}</span>
{{/if}}
</div>
<div class="row-item row-item-left" style="flex: 3">
{{#if ../isEditable}}
<select name="system.attacks.{{i}}.skill">
<option value="-1">— {{ localize 'MGT2.RollPrompt.NoSkill' }} —</option>
{{#each ../system.skills as |sk si|}}
<option value="{{si}}" {{#if (eq si ../skill)}}selected{{/if}}>{{sk.name}} ({{showDM sk.level}})</option>
{{/each}}
</select>
{{else}}
{{#if (gte atk.skill 0)}}
<span>{{lookup (lookup ../system.skills atk.skill) "name"}}</span>
{{else}}
<span class="text-muted"></span>
{{/if}}
{{/if}}
</div>
<div class="row-item row-item-left" style="flex: 3">
{{#if ../isEditable}}
<input type="text" name="system.attacks.{{i}}.description" value="{{atk.description}}" />
@@ -178,29 +191,24 @@
</div>
</div>
{{/each}}
{{#unless system.attacks.length}}
<div class="table-row">
<div class="row-item row-item-center text-muted" style="flex:1">{{ localize 'MGT2.Creature.NoAttacks' }}</div>
</div>
{{/unless}}
</div>
</div>
{{!-- ── TAB : TRAITS ── --}}
<div class="tab" data-group="primary" data-tab="traits">
{{!-- Traits --}}
<div class="header upcase">{{ localize 'MGT2.Creature.TabTraits' }}</div>
<div class="table-container">
<div class="table-row heading color-1">
<div class="row-item row-item-left upcase" style="flex: 3">{{ localize 'MGT2.Creature.TraitName' }}</div>
<div class="row-item row-item-center upcase" style="flex: 1">{{ localize 'MGT2.Creature.TraitValue' }}</div>
<div class="row-item row-item-left upcase" style="flex: 4">{{ localize 'MGT2.Items.Description' }}</div>
<div class="row-item row-item-right" style="flex: 0 0 3rem">
{{#if isEditable}}
<a data-action="addTrait" data-prop="traits" title="{{ localize 'MGT2.Creature.AddTrait' }}"><i class="fas fa-plus"></i></a>
{{/if}}
<div class="row-item row-item-right item-controls" style="flex: 0 0 3rem">
{{#if isEditable}}<a data-action="addTrait" data-prop="traits" title="{{ localize 'MGT2.Creature.AddTrait' }}"><i class="fas fa-plus"></i></a>{{/if}}
</div>
</div>
{{#each system.traits as |trait i|}}
<div class="table-row">
<div class="row-item row-item-left" style="flex: 3">
@@ -231,34 +239,32 @@
</div>
</div>
{{/each}}
{{#unless system.traits.length}}
<div class="table-row">
<div class="row-item row-item-center text-muted" style="flex:1">{{ localize 'MGT2.Creature.NoTraits' }}</div>
</div>
{{/unless}}
</div>
</div>
</div>{{!-- /tab combat --}}
{{!-- ── TAB : INFORMATIONS ── --}}
<div class="tab" data-group="primary" data-tab="info">
<div class="creature-info-tab">
<div class="field-group mt-1">
<label class="upcase">{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.biography" rows="8" class="creature-description">{{system.biography}}</textarea>
{{formInput systemFields.biography enriched=enrichedBiography value=system.biography name="system.biography" toggled=true}}
</div>
<div class="field-group mt-1">
<label class="upcase">{{ localize 'MGT2.Items.Notes' }}</label>
<textarea name="system.notes" rows="4" class="creature-description">{{system.notes}}</textarea>
{{formInput systemFields.notes enriched=enrichedNotes value=system.notes name="system.notes" toggled=true}}
</div>
</div>
</div>
{{!-- ── VERTICAL SIDEBAR TABS (outside window, right side) ── --}}
<nav class="sheet-sidebar tabs" data-group="primary">
<a class="item tab-select" data-tab="skills" title="{{ localize 'MGT2.Creature.TabSkills' }}"><i class="fa-solid fa-head-side"></i><span class="tab-label">COMP.</span></a>
<a class="item tab-select" data-tab="attacks" title="{{ localize 'MGT2.Creature.TabAttacks' }}"><i class="fa-solid fa-bolt"></i><span class="tab-label">ATT.</span></a>
<a class="item tab-select" data-tab="traits" title="{{ localize 'MGT2.Creature.TabTraits' }}"><i class="fa-solid fa-star"></i><span class="tab-label">TRAITS</span></a>
<a class="item tab-select" data-tab="combat" title="{{ localize 'MGT2.Creature.TabCombat' }}"><i class="fa-solid fa-swords"></i><span class="tab-label">COMBAT</span></a>
<a class="item tab-select" data-tab="info" title="{{ localize 'MGT2.Creature.TabInfo' }}"><i class="fa-solid fa-circle-info"></i><span class="tab-label">INFO</span></a>
</nav>

View File

@@ -1,16 +1,124 @@
<form class="{{cssClass}} flexcol" autocomplete="off" style="align-content: flex-start;align-items: baseline;overflow: hidden;height: 100%;">
<div class="{{cssClass}} vehicule-sheet flexcol" style="overflow: hidden; height: 100%;">
<!-- ── Header ─────────────────────────────────────── -->
<section class="vehicule-header">
<div class="vehicule-header-img">
<img class="profile" src="{{img}}" data-edit="img" title="{{name}}" height="130" width="100" />
</div>
<div class="vehicule-header-img">
<img class="profile" src="{{img}}" data-edit="img" title="{{name}}" />
</div>
<div class="vehicule-header-body">
<input class="field-name" name="name" type="text" value="{{name}}" />
<ul class="character-summary">
<li class="w5-10"><input name="system.personal.title" type="text" value="{{system.personal.title}}" placeholder="{{ localize 'MGT2.Actor.PlaceholderTITLE' }}" /></li>
<li class="w2-10"><input name="system.personal.species" type="text" value="{{system.personal.species}}" placeholder="{{ localize 'MGT2.Actor.PlaceholderSPECIES' }}" /></li>
<li class="w1-10"><input name="system.personal.age" type="text" value="{{system.personal.age}}" placeholder="{{ localize 'MGT2.Actor.PlaceholderAGE' }}" /></li>
<li class="w2-10"><input name="system.personal.wup" type="text" value="{{system.personal.wup}}" placeholder="{{ localize 'MGT2.Actor.PlaceholderUCP' }}" /></li>
</ul>
<input class="vehicule-name" name="name" type="text" value="{{name}}" />
<div class="vehicule-header-stats">
<div class="vehicule-stat-box vehicule-hull">
<label>{{ localize 'MGT2.Vehicule.Hull' }}</label>
<div class="vehicule-stat-value">
<input type="number" name="system.life.value" value="{{system.life.value}}" />
<span>/</span>
<input type="number" name="system.life.max" value="{{system.life.max}}" />
</div>
</div>
<div class="vehicule-armor-group">
<div class="vehicule-armor-label">{{ localize 'MGT2.Vehicule.Armor' }}</div>
<div class="vehicule-armor-row">
<div class="vehicule-stat-box vehicule-armor-box">
<label>{{ localize 'MGT2.Vehicule.ArmorFront' }}</label>
<div class="vehicule-stat-value">
<input type="number" name="system.armor.front" value="{{system.armor.front}}" />
</div>
</div>
<div class="vehicule-stat-box vehicule-armor-box">
<label>{{ localize 'MGT2.Vehicule.ArmorSides' }}</label>
<div class="vehicule-stat-value">
<input type="number" name="system.armor.sides" value="{{system.armor.sides}}" />
</div>
</div>
<div class="vehicule-stat-box vehicule-armor-box">
<label>{{ localize 'MGT2.Vehicule.ArmorRear' }}</label>
<div class="vehicule-stat-value">
<input type="number" name="system.armor.rear" value="{{system.armor.rear}}" />
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</form>
<!-- ── Body: tab content + vertical sidebar nav ──── -->
<div class="vehicule-content">
<!-- Tab: Stats -->
<div class="tab vehicule-tab" data-group="primary" data-tab="stats">
<div class="vehicule-stats-grid">
<div class="vehicule-field">
<label>{{ localize 'MGT2.Vehicule.SpeedCruise' }}</label>
<select name="system.speed.cruise">
{{selectOptions config.SpeedBands selected=system.speed.cruise localize=true}}
</select>
</div>
<div class="vehicule-field">
<label>{{ localize 'MGT2.Vehicule.SpeedMax' }}</label>
<select name="system.speed.maximum">
{{selectOptions config.SpeedBands selected=system.speed.maximum localize=true}}
</select>
</div>
<div class="vehicule-field">
<label>{{ localize 'MGT2.Vehicule.Agility' }}</label>
<input type="number" name="system.agility" value="{{system.agility}}" />
</div>
<div class="vehicule-field">
<label>{{ localize 'MGT2.Vehicule.Crew' }}</label>
<input type="number" name="system.crew" value="{{system.crew}}" />
</div>
<div class="vehicule-field">
<label>{{ localize 'MGT2.Vehicule.Passengers' }}</label>
<input type="number" name="system.passengers" value="{{system.passengers}}" />
</div>
<div class="vehicule-field">
<label>{{ localize 'MGT2.Vehicule.Cargo' }}</label>
<input type="number" name="system.cargo" value="{{system.cargo}}" />
</div>
<div class="vehicule-field">
<label>{{ localize 'MGT2.Vehicule.Shipping' }}</label>
<input type="number" name="system.shipping" value="{{system.shipping}}" />
</div>
<div class="vehicule-field">
<label>{{ localize 'MGT2.Vehicule.Cost' }}</label>
<input type="number" name="system.cost" value="{{system.cost}}" />
</div>
<div class="vehicule-field">
<label>{{ localize 'MGT2.Vehicule.Autopilot' }}</label>
<input type="number" name="system.skills.autopilot" value="{{system.skills.autopilot}}" />
</div>
</div>
</div>
<!-- Tab: Description -->
<div class="tab vehicule-tab" data-group="primary" data-tab="description">
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
<!-- Vertical sidebar nav (positioned absolutely right of window) -->
<nav class="sheet-sidebar tabs" data-group="primary">
<a class="item tab-select" data-tab="stats" title="{{ localize 'MGT2.Vehicule.TabStats' }}">
<i class="fa-solid fa-gauge-high"></i>
<span class="tab-label">STATS</span>
</a>
<a class="item tab-select" data-tab="description" title="{{ localize 'MGT2.Vehicule.TabDescription' }}">
<i class="fa-solid fa-circle-info"></i>
<span class="tab-label">INFO</span>
</a>
</nav>
</div>
</div>

View File

@@ -25,7 +25,7 @@
<div class="dice-result">
<div class="dice-formula">{{formula}}</div>
{{{tooltip}}}
<h4 class="dice-total {{#if success}}success{{else if failure}}failure{{/if}}">{{total}}</h4>
<h4 class="dice-total {{#if success}}success{{else if failure}}failure{{/if}}" {{#if rollBreakdown}}data-tooltip="{{rollBreakdown}}"{{/if}}>{{total}}</h4>
</div>
</div>
@@ -34,4 +34,10 @@
{{else if failure}}
<div class="mgt2-outcome is-failure"><i class="fa-solid fa-xmark"></i> {{ localize 'MGT2.Chat.Roll.Failure' }}</div>
{{/if}}
{{#if effectStr}}
<div class="mgt2-effect {{#if success}}is-success{{else}}is-failure{{/if}}">
{{ localize 'MGT2.Chat.Roll.Effect' }} <span class="mgt2-effect-value">{{effectStr}}</span>
</div>
{{/if}}
</div>

View File

@@ -30,7 +30,7 @@
<div class="dice-result">
<div class="dice-formula">{{formula}}</div>
{{{tooltip}}}
<h4 class="dice-total {{#if rollSuccess}}success{{else if rollFailure}}failure{{/if}}">{{total}}</h4>
<h4 class="dice-total {{#if rollSuccess}}success{{else if rollFailure}}failure{{/if}}" {{#if rollBreakdown}}data-tooltip="{{rollBreakdown}}"{{/if}}>{{total}}</h4>
</div>
</div>
@@ -40,6 +40,14 @@
<div class="mgt2-outcome is-failure"><i class="fa-solid fa-xmark"></i> {{ localize 'MGT2.Chat.Roll.Failure' }}</div>
{{/if}}
{{#if rollEffectStr}}
<div class="mgt2-effect {{#if rollSuccess}}is-success{{else}}is-failure{{/if}}">
{{ localize 'MGT2.Chat.Roll.Effect' }} <span class="mgt2-effect-value">{{rollEffectStr}}</span>
{{#if healingAmount}}<span class="mgt2-healing-amount">({{ localize 'MGT2.Healing.Heals' }} {{healingAmount}})</span>{{/if}}
{{#if surgeryDamageAmount}}<span class="mgt2-healing-amount">({{ localize 'MGT2.Healing.SurgeryDamage' }} {{surgeryDamageAmount}})</span>{{/if}}
</div>
{{/if}}
{{#if showButtons}}
<div class="mgt2-buttons">
{{#if hasDamage}}
@@ -48,8 +56,8 @@
{{#if showRollDamage}}
<button data-action="rollDamage">{{ localize 'MGT2.Chat.Roll.Damages' }}</button>
{{/if}}
{{#each cardButtons as |cardButton i|}}
<button data-index="{{i}}" title="{{cardButton.label}}">{{cardButton.label}}</button>
{{#each cardButtons as |cardButton|}}
<button data-action="{{cardButton.action}}" title="{{cardButton.label}}">{{cardButton.label}}</button>
{{/each}}
</div>
{{/if}}

View File

@@ -6,13 +6,7 @@
<img class="item-header-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<input class="item-header-name" name="name" type="text" value="{{item.name}}" />
</div>
<div class="item-stats-bar">
{{> systems/mgt2/templates/items/parts/sheet-physical-item.html }}
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Protection' }}</span>
<input type="text" name="system.protection" value="{{system.protection}}" data-dtype="String" />
</div>
</div>
<nav class="horizontal-tabs tabs" data-group="primary">
<a class="item tab-select" data-tab="tab1">{{ localize 'MGT2.Items.Description' }}</a>
<a class="item tab-select" data-tab="tab2">{{ localize 'MGT2.Items.Details' }}</a>
@@ -22,19 +16,28 @@
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab2">
<div class="field-groups">
<div class="field-group">
<div class="item-details-grid">
{{> systems/mgt2/templates/items/parts/sheet-physical-item-tab.html }}
<div class="field-row">
<label>{{ localize 'MGT2.Items.Protection' }}</label>
<input type="text" name="system.protection" value="{{system.protection}}" data-dtype="String" class="short" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Radiations' }}</label>
<input type="number" name="system.radiations" value="{{system.radiations}}" data-dtype="Number" class="short" />
</div>
<div class="field-row field-row--check">
<label class="mgt2-checkbox"><input type="checkbox" name="system.equipped" data-dtype="Boolean" {{checked system.equipped}} />{{ localize 'MGT2.Items.Equipped' }}</label>
</div>
<div class="field-group">
<div class="field-row field-row--check">
<label class="mgt2-checkbox"><input type="checkbox" name="system.powered" data-dtype="Boolean" {{checked system.powered}} />{{ localize 'MGT2.Items.Powered' }}</label>
</div>
{{#if hadContainer}}
<div class="field-group">
<div class="field-row full">
<label>{{ localize 'MGT2.Items.Storage' }}</label>
<select name="system.container.id">
{{selectOptions containers selected=system.container.id valueAttr="_id" labelAttr="name"}}
@@ -42,10 +45,6 @@
</div>
{{/if}}
</div>
<div class="field-group" style="margin-top:8px;">
<label>{{ localize 'MGT2.Items.Radiations' }}</label>
<input type="number" name="system.radiations" value="{{system.radiations}}" data-dtype="Number" />
</div>
<div class="table-container">
<div class="table-row heading">
<div class="row-item row-item-left row-item-30">{{ localize 'MGT2.Items.Options' }}</div>

View File

@@ -6,27 +6,30 @@
<img class="item-header-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<input class="item-header-name" name="name" type="text" value="{{item.name}}" data-dtype="String" />
</div>
<div class="item-stats-bar">
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Terms' }}</span>
<input type="number" name="system.terms" value="{{system.terms}}" data-dtype="Number" />
</div>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Rank' }}</span>
<input type="number" name="system.rank" value="{{system.rank}}" data-dtype="Number" />
</div>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Assignment' }}</span>
<input type="text" name="system.assignment" value="{{system.assignment}}" data-dtype="String" />
</div>
</div>
<nav class="horizontal-tabs tabs" data-group="primary">
<a class="item tab-select" data-tab="tab1">{{ localize 'MGT2.Items.Description' }}</a>
<a class="item tab-select" data-tab="tab2">{{ localize 'MGT2.Items.Details' }}</a>
<a class="item tab-select" data-tab="events">{{ localize 'MGT2.Items.EventsMishaps' }}</a>
</nav>
<div class="tab-content-area">
<div class="tab" data-group="primary" data-tab="tab1">
<textarea name="system.description" rows="6">{{system.description}}</textarea>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
<div class="tab" data-group="primary" data-tab="tab2">
<div class="item-details-grid">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Terms' }}</label>
<input type="number" name="system.terms" value="{{system.terms}}" data-dtype="Number" class="short" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Rank' }}</label>
<input type="number" name="system.rank" value="{{system.rank}}" data-dtype="Number" class="short" />
</div>
<div class="field-row full">
<label>{{ localize 'MGT2.Items.Assignment' }}</label>
<input type="text" name="system.assignment" value="{{system.assignment}}" data-dtype="String" />
</div>
</div>
</div>
<div class="tab" data-group="primary" data-tab="events">
<div class="table-container">

View File

@@ -6,13 +6,7 @@
<img class="item-header-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<input class="item-header-name" name="name" type="text" value="{{item.name}}" />
</div>
<div class="item-stats-bar">
{{> systems/mgt2/templates/items/parts/sheet-physical-item.html }}
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Processing' }}</span>
<input type="number" name="system.processing" value="{{system.processing}}" data-dtype="Number" />
</div>
</div>
<nav class="horizontal-tabs tabs" data-group="primary">
<a class="item tab-select" data-tab="tab1">{{ localize 'MGT2.Items.Description' }}</a>
<a class="item tab-select" data-tab="tab2">{{ localize 'MGT2.Items.Details' }}</a>
@@ -22,16 +16,21 @@
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab2">
<div class="field-groups">
<div class="field-group">
<div class="item-details-grid">
{{> systems/mgt2/templates/items/parts/sheet-physical-item-tab.html }}
<div class="field-row">
<label>{{ localize 'MGT2.Items.Processing' }}</label>
<input type="number" name="system.processing" value="{{system.processing}}" data-dtype="Number" class="short" />
</div>
<div class="field-row field-row--check">
<label class="mgt2-checkbox"><input type="checkbox" name="system.equipped" data-dtype="Boolean" {{checked system.equipped}} />{{ localize 'MGT2.Items.Equipped' }}</label>
</div>
{{#if hadContainer}}
<div class="field-group">
<div class="field-row full">
<label>{{ localize 'MGT2.Items.Storage' }}</label>
<select name="system.container.id">
{{selectOptions containers selected=system.container.id valueAttr="_id" labelAttr="name"}}

View File

@@ -6,32 +6,6 @@
<img class="item-header-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<input class="item-header-name" name="name" type="text" value="{{item.name}}" />
</div>
<div class="item-stats-bar">
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Relation' }}</span>
<select name="system.relation">
{{selectOptions config.ContactRelations selected=system.relation localize=true}}
</select>
</div>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Attitude' }}</span>
<select name="system.attitude">
{{selectOptions config.Attitudes selected=system.attitude localize=true}}
</select>
</div>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Status' }}</span>
<select name="system.status">
{{selectOptions config.ContactStatus selected=system.status localize=true}}
</select>
</div>
{{#if settings.useGender}}
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Gender' }}</span>
<input type="text" name="system.gender" value="{{system.gender}}" data-dtype="String" />
</div>
{{/if}}
</div>
<nav class="horizontal-tabs tabs" data-group="primary">
<a class="item tab-select" data-tab="tab1">{{ localize 'MGT2.Items.Informations' }}</a>
<a class="item tab-select" data-tab="description">{{ localize 'MGT2.Items.Description' }}</a>
@@ -39,46 +13,68 @@
</nav>
<div class="tab-content-area">
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-groups">
<div class="field-group">
<div class="item-details-grid">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Relation' }}</label>
<select name="system.relation">
{{selectOptions config.ContactRelations selected=system.relation localize=true}}
</select>
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Attitude' }}</label>
<select name="system.attitude">
{{selectOptions config.Attitudes selected=system.attitude localize=true}}
</select>
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Status' }}</label>
<select name="system.status">
{{selectOptions config.ContactStatus selected=system.status localize=true}}
</select>
</div>
{{#if settings.useGender}}
<div class="field-row">
<label>{{ localize 'MGT2.Gender' }}</label>
<input type="text" name="system.gender" value="{{system.gender}}" data-dtype="String" />
</div>
{{/if}}
<div class="field-row">
<label>{{ localize 'MGT2.Species' }}</label>
<input type="text" name="system.species" value="{{system.species}}" data-dtype="String" />
</div>
{{#if settings.usePronouns}}
<div class="field-group">
<div class="field-row">
<label>{{ localize 'MGT2.Pronouns' }}</label>
<input type="text" name="system.pronouns" value="{{system.pronouns}}" data-dtype="String" />
</div>
{{/if}}
</div>
<div class="field-group" style="margin-top:8px;">
<label>{{ localize 'MGT2.Items.Title' }}</label>
<input type="text" name="system.title" value="{{system.title}}" data-dtype="String" />
</div>
<div class="field-group">
<label>{{ localize 'MGT2.Items.Nickname' }}</label>
<input type="text" name="system.nickname" value="{{system.nickname}}" data-dtype="String" />
</div>
<div class="field-groups">
<div class="field-group">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Title' }}</label>
<input type="text" name="system.title" value="{{system.title}}" data-dtype="String" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Nickname' }}</label>
<input type="text" name="system.nickname" value="{{system.nickname}}" data-dtype="String" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Homeworld' }}</label>
<input type="text" name="system.homeworld" value="{{system.homeworld}}" data-dtype="String" />
</div>
<div class="field-group">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Location' }}</label>
<input type="text" name="system.location" value="{{system.location}}" data-dtype="String" />
</div>
</div>
<div class="field-group">
<label>{{ localize 'MGT2.Items.Occupation' }}</label>
<input type="text" name="system.occupation" value="{{system.occupation}}" data-dtype="String" />
<div class="field-row full">
<label>{{ localize 'MGT2.Items.Occupation' }}</label>
<input type="text" name="system.occupation" value="{{system.occupation}}" data-dtype="String" />
</div>
</div>
</div>
<div class="tab" data-group="primary" data-tab="description">
{{editor system.description.value target="system.description" button=true editable=true}}
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
<div class="tab" data-group="primary" data-tab="notes">
{{editor system.notes target="system.notes" button=true editable=true}}
{{formInput systemFields.notes enriched=enrichedNotes value=system.notes name="system.notes" toggled=true}}
</div>
</div>
</div>

View File

@@ -7,22 +7,6 @@
<img class="item-header-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<input class="item-header-name" name="name" type="text" value="{{item.name}}" data-dtype="String" />
</div>
<div class="item-stats-bar">
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Weight' }} ({{unitlabels.weight}})</span>
<input type="number" value="{{weight}}" readonly style="opacity:0.7;" />
</div>
<div class="item-stat-pill-checkbox">
<input type="checkbox" name="system.onHand" id="onhand-{{item.id}}" data-dtype="Boolean" {{checked system.onHand}} />
<label for="onhand-{{item.id}}">{{ localize 'MGT2.Items.OnHand' }}</label>
</div>
{{#if isGM}}
<div class="item-stat-pill-checkbox">
<input type="checkbox" name="system.locked" id="locked-{{item.id}}" data-dtype="Boolean" {{checked system.locked}} />
<label for="locked-{{item.id}}">{{ localize 'MGT2.Items.Locked' }}</label>
</div>
{{/if}}
</div>
<nav class="horizontal-tabs tabs" data-group="primary">
<a class="item tab-select" data-tab="tab1">{{ localize 'MGT2.Items.Description' }}</a>
<a class="item tab-select" data-tab="tab2">{{ localize 'MGT2.Items.Details' }}</a>
@@ -34,20 +18,40 @@
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab2">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Location' }}</label>
<input type="text" name="system.location" value="{{system.location}}" data-dtype="String" />
<div class="item-details-grid">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Weight' }} ({{unitlabels.weight}})</label>
<input type="number" value="{{weight}}" readonly style="opacity:0.7;" class="short" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Location' }}</label>
<input type="text" name="system.location" value="{{system.location}}" data-dtype="String" />
</div>
<div class="field-row field-row--check">
<label class="mgt2-checkbox">
<input type="checkbox" name="system.onHand" id="onhand-{{item.id}}" data-dtype="Boolean" {{checked system.onHand}} />
{{ localize 'MGT2.Items.OnHand' }}
</label>
</div>
{{#if isGM}}
<div class="field-row field-row--check">
<label class="mgt2-checkbox">
<input type="checkbox" name="system.locked" id="locked-{{item.id}}" data-dtype="Boolean" {{checked system.locked}} />
{{ localize 'MGT2.Items.Locked' }}
</label>
</div>
{{/if}}
</div>
</div>
{{#if isGM}}
<div class="tab" data-group="primary" data-tab="tab3">
<div class="field-group">
<label>{{ localize 'MGT2.Items.LockedDescription' }}</label>
<textarea name="system.lockedDescription" rows="6">{{system.lockedDescription}}</textarea>
{{formInput systemFields.lockedDescription enriched=enrichedLockedDescription value=system.lockedDescription name="system.lockedDescription" toggled=true}}
</div>
</div>
{{/if}}

View File

@@ -12,32 +12,40 @@
<img class="item-header-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<input class="item-header-name" name="name" type="text" value="{{item.name}}" />
</div>
<div class="item-stats-bar">
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Type' }}</span>
<select name="system.subType" style="font-size:0.75rem;">
{{selectOptions config.DiseaseSubType selected=system.subType localize=true}}
</select>
</div>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Difficulty' }}</span>
<select name="system.difficulty" style="font-size:0.75rem;">
{{selectOptions config.Difficulty selected=system.difficulty localize=true}}
</select>
</div>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Damage' }}</span>
<input type="text" name="system.damage" value="{{system.damage}}" data-dtype="String" />
</div>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Interval' }}</span>
<input type="text" name="system.interval" value="{{system.interval}}" data-dtype="String" />
</div>
</div>
<nav class="horizontal-tabs tabs" data-group="primary">
<a class="item tab-select" data-tab="tab1">{{ localize 'MGT2.Items.Description' }}</a>
<a class="item tab-select" data-tab="tab2">{{ localize 'MGT2.Items.Details' }}</a>
</nav>
<div class="tab-content-area">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="8">{{system.description}}</textarea>
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Description' }}</label>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab2">
<div class="item-details-grid">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Type' }}</label>
<select name="system.subType">
{{selectOptions config.DiseaseSubType selected=system.subType localize=true}}
</select>
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Difficulty' }}</label>
<select name="system.difficulty">
{{selectOptions config.Difficulty selected=system.difficulty localize=true}}
</select>
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Damage' }}</label>
<input type="text" name="system.damage" value="{{system.damage}}" data-dtype="String" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Interval' }}</label>
<input type="text" name="system.interval" value="{{system.interval}}" data-dtype="String" />
</div>
</div>
</div>
</div>
</div>

View File

@@ -14,15 +14,7 @@
<img class="item-header-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<input class="item-header-name" name="name" type="text" value="{{item.name}}" />
</div>
<div class="item-stats-bar">
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Type' }}</span>
<select name="system.subType" style="font-size:0.75rem;">
{{selectOptions config.EquipmentSubType selected=system.subType localize=true}}
</select>
</div>
{{> systems/mgt2/templates/items/parts/sheet-physical-item.html }}
</div>
<nav class="horizontal-tabs tabs" data-group="primary">
<a class="item tab-select" data-tab="tab1">{{ localize 'MGT2.Items.Description' }}</a>
<a class="item tab-select" data-tab="tab2">{{ localize 'MGT2.Items.Details' }}</a>
@@ -32,16 +24,23 @@
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab2">
<div class="field-groups">
<div class="field-group">
<label class="mgt2-checkbox"><input type="checkbox" name="system.equipped" data-dtype="Boolean" {{checked system.equipped}} />Equipped</label>
<div class="item-details-grid">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Type' }}</label>
<select name="system.subType">
{{selectOptions config.EquipmentSubType selected=system.subType localize=true}}
</select>
</div>
{{> systems/mgt2/templates/items/parts/sheet-physical-item-tab.html }}
<div class="field-row field-row--check">
<label class="mgt2-checkbox"><input type="checkbox" name="system.equipped" data-dtype="Boolean" {{checked system.equipped}} />{{ localize 'MGT2.Items.Equipped' }}</label>
</div>
{{#if hadContainer}}
<div class="field-group">
<div class="field-row full">
<label>{{ localize 'MGT2.Items.Storage' }}</label>
<select name="system.container.id">
{{selectOptions containers selected=system.container.id valueAttr="_id" labelAttr="name"}}

View File

@@ -10,15 +10,7 @@
<img class="item-header-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<input class="item-header-name" name="name" type="text" value="{{item.name}}" />
</div>
<div class="item-stats-bar">
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Type' }}</span>
<select name="system.subType" style="font-size:0.75rem;">
{{selectOptions config.ItemSubType selected=system.subType localize=true}}
</select>
</div>
{{> systems/mgt2/templates/items/parts/sheet-physical-item.html }}
</div>
<nav class="horizontal-tabs tabs" data-group="primary">
<a class="item tab-select" data-tab="tab1">{{ localize 'MGT2.Items.Description' }}</a>
<a class="item tab-select" data-tab="tab2">{{ localize 'MGT2.Items.Details' }}</a>
@@ -27,34 +19,41 @@
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab2">
{{#if hadContainer}}
<div class="field-groups">
<div class="item-details-grid">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Type' }}</label>
<select name="system.subType">
{{selectOptions config.ItemSubType selected=system.subType localize=true}}
</select>
</div>
{{> systems/mgt2/templates/items/parts/sheet-physical-item-tab.html }}
{{#if hadContainer}}
<div class="field-row full">
<label>{{ localize 'MGT2.Items.Storage' }}</label>
<select name="system.container.id">
{{selectOptions containers selected=system.container.id valueAttr="_id" labelAttr="name"}}
</select>
</div>
{{/if}}
{{#if (eq system.subType "software")}}
<div class="field-group">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Bandwidth' }}</label>
<input type="number" name="system.software.bandwidth" value="{{system.software.bandwidth}}" data-dtype="Number" class="short" />
</div>
{{#if hadContainer}}
<div class="field-row full">
<label>{{ localize 'MGT2.Items.Computer' }}</label>
<select name="system.software.computerId">
{{selectOptions computers selected=system.software.computerId valueAttr="_id" labelAttr="name"}}
</select>
</div>
{{/if}}
<div class="field-group">
<label>{{ localize 'MGT2.Items.Storage' }}</label>
<select name="system.container.id">
{{selectOptions containers selected=system.container.id valueAttr="_id" labelAttr="name"}}
</select>
</div>
{{/if}}
</div>
{{/if}}
{{#if (eq system.subType "software")}}
<div class="field-group" style="margin-top:8px;">
<label>{{ localize 'MGT2.Items.Bandwidth' }}</label>
<input type="number" name="system.software.bandwidth" value="{{system.software.bandwidth}}" data-dtype="Number" />
</div>
{{/if}}
</div>
</div>
</div>

View File

@@ -0,0 +1,24 @@
<div class="field-row">
<label>{{ localize 'MGT2.Items.Quantity' }}</label>
<input type="number" name="system.quantity" value="{{system.quantity}}" data-dtype="Number" integer="true" positive="true" class="short" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Weight' }} ({{unitlabels.weight}})</label>
<input type="number" name="weight" value="{{weight}}" data-dtype="Number" step="0.5" class="short" />
</div>
<div class="field-row field-row--check">
<label class="mgt2-checkbox">
<input type="checkbox" name="system.weightless" data-dtype="Boolean" {{checked system.weightless}} />
{{ localize 'MGT2.Items.Weightless' }}
</label>
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Cost' }}</label>
<input type="number" name="system.cost" value="{{system.cost}}" data-dtype="Number" step="1" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.TL' }}</label>
<select name="system.tl">
{{selectOptions config.TL selected=system.tl localize=true}}
</select>
</div>

View File

@@ -15,11 +15,11 @@
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab2">
{{editor system.descriptionLong target="system.descriptionLong" button=true editable=true}}
{{formInput systemFields.descriptionLong enriched=enrichedDescriptionLong value=system.descriptionLong name="system.descriptionLong" toggled=true}}
</div>
<div class="tab" data-group="primary" data-tab="tab3">
<div class="table-container">

View File

@@ -1,4 +1,4 @@
<div class="{{cssClass}} flexrow itemsheet">
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
{{#if (eq system.subType "skill")}}
<span class="item-type-label"><i class="fas fa-star"></i> {{localize 'MGT2.TalentSubType.skill'}}</span>
@@ -10,68 +10,75 @@
<img class="item-header-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<input class="item-header-name" name="name" type="text" value="{{item.name}}" />
</div>
<div class="item-stats-bar">
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Type' }}</span>
<select name="system.subType" style="font-size:0.75rem;">
{{selectOptions config.TalentSubType selected=system.subType localize=true}}
</select>
</div>
{{#if (eq system.subType "skill")}}
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Level' }}</span>
<input type="number" name="system.level" value="{{system.level}}" data-dtype="Number" />
</div>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Speciality' }}</span>
<input type="text" name="system.skill.speciality" value="{{system.skill.speciality}}" data-dtype="String" />
</div>
{{else if (eq system.subType "psionic")}}
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Level' }}</span>
<input type="number" name="system.level" value="{{system.level}}" data-dtype="Number" />
</div>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.PSICost' }}</span>
<input type="number" name="system.psionic.cost" value="{{system.psionic.cost}}" data-dtype="Number" />
</div>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Reach' }}</span>
<select name="system.psionic.reach">
<option></option>
{{selectOptions config.PsionicReach selected=system.psionic.reach localize=true}}
</select>
</div>
{{/if}}
</div>
<nav class="horizontal-tabs tabs" data-group="primary">
<a class="item tab-select" data-tab="tab1">{{ localize 'MGT2.Items.Description' }}</a>
<a class="item tab-select" data-tab="config">{{ localize 'MGT2.Items.Configuration' }}</a>
<a class="item tab-select" data-tab="tab2">{{ localize 'MGT2.Items.Details' }}</a>
<a class="item tab-select" data-tab="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
</nav>
<div class="tab-content-area">
{{!-- Tab 1 : Description --}}
<div class="tab" data-group="primary" data-tab="tab1">
{{#if (eq system.subType "psionic")}}
<div class="field-groups" style="margin-bottom:8px;">
<div class="field-group">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Description' }}</label>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
</div>
{{!-- Tab 2 : Détails --}}
<div class="tab" data-group="primary" data-tab="tab2">
<div class="item-details-grid">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Type' }}</label>
<select name="system.subType">
{{selectOptions config.TalentSubType selected=system.subType localize=true}}
</select>
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Level' }}</label>
<input type="number" name="system.level" value="{{system.level}}" data-dtype="Number" class="short" />
</div>
{{#if (eq system.subType "skill")}}
<div class="field-row full">
<label>{{ localize 'MGT2.Items.Speciality' }}</label>
<input type="text" name="system.skill.speciality" value="{{system.skill.speciality}}" data-dtype="String" />
</div>
<div class="field-row field-row--check">
<label class="mgt2-checkbox">
<input type="checkbox" name="system.skill.reduceEncumbrance" data-dtype="Boolean" {{checked system.skill.reduceEncumbrance}} />
{{ localize 'MGT2.Items.ReduceEncumbrance' }}
</label>
</div>
{{else if (eq system.subType "psionic")}}
<div class="field-row">
<label>{{ localize 'MGT2.Items.PSICost' }}</label>
<input type="number" name="system.psionic.cost" value="{{system.psionic.cost}}" data-dtype="Number" class="short" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Reach' }}</label>
<select name="system.psionic.reach">
<option value=""></option>
{{selectOptions config.PsionicReach selected=system.psionic.reach localize=true}}
</select>
</div>
<div class="field-row full">
<label>{{ localize 'MGT2.Items.Duration' }}</label>
<div style="display:flex;gap:4px;">
<div class="range-inputs">
<input type="text" name="system.psionic.duration" value="{{system.psionic.duration}}" data-dtype="String" />
<select name="system.psionic.durationUnit">
{{selectOptions config.Durations selected=system.psionic.durationUnit localize=true}}
</select>
</div>
</div>
{{/if}}
</div>
{{/if}}
<textarea name="system.description" rows="6">{{system.description}}</textarea>
</div>
<div class="tab" data-group="primary" data-tab="config">
{{#if (eq system.subType "skill")}}
<div class="field-group">
<label class="mgt2-checkbox"><input type="checkbox" name="system.skill.reduceEncumbrance" data-dtype="Boolean" {{checked system.skill.reduceEncumbrance}} />{{ localize 'MGT2.Items.ReduceEncumbrance' }}</label>
</div>
{{/if}}
{{!-- Tab 3 : Configuration (jet de dé) --}}
<div class="tab" data-group="primary" data-tab="tab3">
{{> systems/mgt2/templates/items/parts/sheet-configuration.html }}
</div>
</div>
</div>

View File

@@ -6,28 +6,7 @@
<img class="item-header-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<input class="item-header-name" name="name" type="text" value="{{item.name}}" />
</div>
<div class="item-stats-bar">
{{> systems/mgt2/templates/items/parts/sheet-physical-item.html }}
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Damage' }}</span>
<input type="text" name="system.damage" value="{{system.damage}}" data-dtype="String" />
</div>
{{#unless system.range.isMelee}}
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Range' }}</span>
<div style="display:flex;gap:3px;align-items:center;">
<input type="text" name="system.range.value" value="{{system.range.value}}" data-dtype="String" style="width:3rem;" />
<select name="system.range.unit" style="width:5rem;">
{{selectOptions config.MetricRange selected=system.range.unit localize=true}}
</select>
</div>
</div>
{{/unless}}
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Magazine' }}</span>
<input type="number" name="system.magazine" value="{{system.magazine}}" data-dtype="Number" />
</div>
</div>
<nav class="horizontal-tabs tabs" data-group="primary">
<a class="item tab-select" data-tab="tab1">{{ localize 'MGT2.Items.Description' }}</a>
<a class="item tab-select" data-tab="tab2">{{ localize 'MGT2.Items.Details' }}</a>
@@ -37,19 +16,61 @@
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab2">
<div class="field-groups">
<div class="field-group">
<div class="item-details-grid">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Quantity' }}</label>
<input type="number" name="system.quantity" value="{{system.quantity}}" data-dtype="Number" integer="true" positive="true" class="short" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.TL' }}</label>
<select name="system.tl">{{selectOptions config.TL selected=system.tl localize=true}}</select>
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Weight' }} ({{unitlabels.weight}})</label>
<input type="number" name="weight" value="{{weight}}" data-dtype="Number" step="0.5" class="short" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Cost' }}</label>
<input type="number" name="system.cost" value="{{system.cost}}" data-dtype="Number" step="1" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Damage' }}</label>
<input type="text" name="system.damage" value="{{system.damage}}" data-dtype="String" />
</div>
<div class="field-row">
<label>{{ localize 'MGT2.Items.Magazine' }}</label>
<input type="number" name="system.magazine" value="{{system.magazine}}" data-dtype="Number" class="short" />
</div>
{{#unless system.range.isMelee}}
<div class="field-row">
<label>{{ localize 'MGT2.Items.Range' }}</label>
<div class="range-inputs">
<input type="text" name="system.range.value" value="{{system.range.value}}" data-dtype="String" />
<select name="system.range.unit">
{{selectOptions config.MetricRange selected=system.range.unit localize=true}}
</select>
</div>
</div>
{{/unless}}
<div class="field-row">
<label>{{ localize 'MGT2.Items.MagazineCost' }}</label>
<input type="number" name="system.magazineCost" value="{{system.magazineCost}}" data-dtype="Number" class="short" />
</div>
<div class="field-row field-row--check">
<label class="mgt2-checkbox"><input type="checkbox" name="system.weightless" data-dtype="Boolean" {{checked system.weightless}} />{{ localize 'MGT2.Items.Weightless' }}</label>
</div>
<div class="field-row field-row--check">
<label class="mgt2-checkbox"><input type="checkbox" name="system.range.isMelee" data-dtype="Boolean" {{checked system.range.isMelee}} />{{ localize 'MGT2.Items.IsMelee' }}</label>
</div>
<div class="field-group">
<div class="field-row field-row--check">
<label class="mgt2-checkbox"><input type="checkbox" name="system.equipped" data-dtype="Boolean" {{checked system.equipped}} />{{ localize 'MGT2.Items.Equipped' }}</label>
</div>
{{#if hadContainer}}
<div class="field-group">
<div class="field-row full">
<label>{{ localize 'MGT2.Items.Storage' }}</label>
<select name="system.container.id">
{{selectOptions containers selected=system.container.id valueAttr="_id" labelAttr="name"}}
@@ -57,10 +78,6 @@
</div>
{{/if}}
</div>
<div class="field-group" style="margin-top:8px;">
<label>{{ localize 'MGT2.Items.MagazineCost' }}</label>
<input type="number" name="system.magazineCost" value="{{system.magazineCost}}" data-dtype="Number" />
</div>
<div class="table-container">
<div class="table-row heading">
<div class="row-item row-item-left">{{ localize 'MGT2.Items.Trait' }}</div>

View File

@@ -1,4 +1,32 @@
<form class="{{cssClass}} flexcol" autocomplete="off" style="padding: 0 6px;">
{{!-- Hidden fields for healing --}}
{{#if showHeal}}
<input type="hidden" name="healType" value="{{healType}}" />
{{/if}}
{{!-- ── Mode CRÉATURE : sélecteur de compétence de la créature ── --}}
{{#if isCreature}}
{{#if showSkillSelector}}
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.CreatureSkill' }}</label>
<select name="creatureSkillIndex">
<option value="-1">— {{ localize 'MGT2.RollPrompt.NoSkill' }} —</option>
{{#each creatureSkills}}
<option value="{{@index}}" {{#if (eq @index ../selectedSkillIndex)}}selected{{/if}}>{{name}} ({{showDM level}})</option>
{{/each}}
</select>
</div>
{{else}}
<div class="form-group roll-prompt-info">
<span class="roll-prompt-skill-name">{{skillName}}</span>
<span class="roll-prompt-skill-level">{{showDM skillLevel}}</span>
</div>
{{/if}}
{{!-- ── Mode PERSONNAGE : caractéristique + compétence + états ── --}}
{{else}}
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.CharacteristicDM' }}</label>
<select name="characteristic">
@@ -25,14 +53,34 @@
<label class="mgt2-checkbox"><input type="checkbox" name="fatigue" data-dtype="Boolean" {{checked fatigue}} />{{ localize 'MGT2.RollPrompt.FatigueDM' }}</label>
</div>
</fieldset>
{{/if}}
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.CustomDM' }}</label>
<input type="text" name="customDM" maxlength="15" />
<select name="customDM">
<option value="-8">8</option>
<option value="-7">7</option>
<option value="-6">6</option>
<option value="-5">5</option>
<option value="-4">4</option>
<option value="-3">3</option>
<option value="-2">2</option>
<option value="-1">1</option>
<option value="0" selected>0</option>
<option value="1">+1</option>
<option value="2">+2</option>
<option value="3">+3</option>
<option value="4">+4</option>
<option value="5">+5</option>
<option value="6">+6</option>
<option value="7">+7</option>
<option value="8">+8</option>
</select>
</div>
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.Difficulty' }}</label>
<select name="difficulty">
<option></option>
<option value=""></option>
{{selectOptions config.Difficulty selected = difficulty localize = true}}
</select>
</div>