20 Commits

Author SHA1 Message Date
c5a0a47b25 Build release script
Some checks failed
Release Creation / build (release) Failing after 1m9s
2026-04-21 19:56:22 +02:00
a48f447b99 Build release script
Some checks failed
Release Creation / build (release) Failing after 24s
2026-04-21 19:50:55 +02:00
e398b157f4 Build release script 2026-04-21 19:50:33 +02:00
a349402306 Build release script 2026-04-21 19:49:18 +02:00
bdc305abd8 Build release script 2026-04-21 18:57:53 +02:00
6ef454d533 Gestion des traits d'arme et des munitions 2026-04-21 18:50:49 +02:00
74f1b581f7 Ajout des fonctions de gestion des soins 2026-04-21 10:22:39 +02:00
df0a93d715 feat: complete visual redesign of all item sheets + compact chat roll messages
- Rewrote _item.sass with unified layout: type-bar + dark header + stats pills + tabs
 - All 11 item templates converted to new pill-based stats bar
 - Chat roll message font sizes and padding compacted
 - Physical item partial rewritten as stat pills (QTY/WEIGHT/COST/TL)
 - Legacy itemsheet-header and itemsheet-maincol hidden
2026-04-19 21:31:52 +02:00
d62d14c1da Second round de corrections et améliorations 2026-04-19 18:55:34 +02:00
783d4a16e6 Remove assets/regles from remote (keep local) 2026-04-19 10:57:28 +02:00
fa1c02caa6 Migrate to FoundryVTT v13 AppV2 +
│ DataModels
│
│ - Reorganize DataModels into src/module/models/ (one .mjs per type)
│ - Create AppV2 actor/item sheets (HandlebarsApplicationMixin)…
2026-04-19 10:55:44 +02:00
86b2cd5777 Migrate to FoundryVTT v13 AppV2 +
│ DataModels
│
│ - Reorganize DataModels into src/module/models/ (one .mjs per type)
│ - Create AppV2 actor/item sheets (HandlebarsApplicationMixin)…
2026-04-19 10:54:43 +02:00
e3002dd602 Migration FOundry v13/v14 2026-04-19 00:43:33 +02:00
JDR-Ninja
89b3e401a4 Ajouter les sources 2025-06-20 08:43:33 -04:00
JDR-Ninja
f1c4f43c66 Update .gitignore
add src
2025-06-20 08:38:31 -04:00
JDR-Ninja
78ad10918c Merge pull request #4 from cbeach512/#3-Resolve-MGT2-module-deprecation-warnings-for-grid-settings
fix: system.json deprecation warnings
2025-06-20 08:35:53 -04:00
Chad Beach
770e80fd3d updated CHANGELOG.md 2024-08-19 23:42:36 -06:00
Chad Beach
d63526831c change grid to dict 2024-08-19 23:37:08 -06:00
Chad Beach
7f10f617c4 update grid settings calls 2024-08-19 22:51:22 -06:00
JDR-Ninja
5de3533691 Calcul du poids 2024-05-25 08:33:45 -04:00
90 changed files with 14701 additions and 4294 deletions

View File

@@ -0,0 +1,81 @@
name: Release Creation
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will cloned to the runner."
- uses: RouxAntoine/checkout@v3.5.4
# get part of the tag after the `v`
- name: Extract tag version number
id: get_version
uses: battila7/get-version-action@v2
# Substitute the Manifest and Download URLs in the system.json
- name: Substitute Manifest and Download Links For Versioned Ones
id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1
with:
files: "system.json"
env:
version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/${{gitea.repository}}
manifest: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/mgt2.zip
# Install Node.js and build the JS bundle
- name: Setup Node.js
uses: https://github.com/actions/setup-node@v3
with:
node-version: "20"
- name: Install JS dependencies
run: npm install
- name: Build JS bundle
run: npm run build
# Install Sass and compile CSS
- name: Install Sass
run: npm install -g sass
- name: Build CSS
run: sass src/sass/mgt2.sass styles/mgt2.min.css --style=compressed --no-source-map
# Create a zip file with all files required by the system to add to the release
- name: Install zip
run: apt update -y && apt install -y zip
- name: Create release zip
run: zip -r ./mgt2.zip system.json README.md LICENSE mgt2.bundle.js assets/ lang/ styles/ templates/
- name: setup go
uses: https://github.com/actions/setup-go@v4
with:
go-version: ">=1.20.1"
- name: Use Go Action
id: use-go-action
uses: https://gitea.com/actions/release-action@main
with:
files: |-
./mgt2.zip
system.json
api_key: "${{secrets.ALLOW_PUSH_RELEASE}}"
- name: Publish to Foundry server
uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
with:
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
id: "mgt2"
version: ${{github.event.release.tag_name}}
manifest: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/latest/system.json"
notes: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/mgt2.zip"
compatibility-minimum: "13"
compatibility-verified: "14"

3
.gitignore vendored
View File

@@ -27,5 +27,6 @@
# Local History for Visual Studio Code
.history/
src/
mgt2.zip
assets/regles/
.github

View File

@@ -1,4 +1,11 @@
## [0.1.3] (2024-05-25)
* `system.json` no longer generates warnings ([#3](https://github.com/JDR-Ninja/foundryvtt-mgt2/issues/3))
## [0.1.4] (2024-05-25)
### Correctifs
* Erreur lors du calcul du poids lors de différent événement (Drop, Delete)
## [0.1.3] (2024-05-24)
### Correctifs
* Localisation

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",
@@ -54,20 +60,21 @@
"Gender": "Gender",
"Pronouns": "Pronouns",
"Actor": {
"StudyPeriod":"Study Period",
"TrainingInSkill":"Training In Skill",
"Completed":"Completed",
"Weeks":"Weeks",
"NewCareer":"New Career",
"AddCareer":"Add Career",
"EditCareer":"Edit Career",
"EditTrait":"Éditer Trait",
"StudyPeriod": "Study Period",
"TrainingInSkill": "Training In Skill",
"Completed": "Completed",
"Weeks": "Weeks",
"NewCareer": "New Career",
"AddCareer": "Add Career",
"ThisTrait": "this trait",
"EditCareer": "Edit Career",
"EditTrait": "Éditer Trait",
"DeleteTrait": "Supprimer Trait",
"DeleteCareer":"Delete Career",
"NewSkill":"New Skill",
"DeleteSkill":"Delete Skill",
"EditSkill":"EditSkill",
"PsionicTalents":"Psionic Talents",
"DeleteCareer": "Delete Career",
"NewSkill": "New Skill",
"DeleteSkill": "Delete Skill",
"EditSkill": "EditSkill",
"PsionicTalents": "Psionic Talents",
"NewPsionicTalent": "New Psionic Talent",
"AddPsionicTalent": "Add Psionic Talent",
"EditPsionic": "Edit Psionic",
@@ -343,7 +350,36 @@
"EncumbranceDM": "Encumbrance (DM -2)",
"FatigueDM": "Fatigue (DM -2)",
"Boon": "Boon",
"Bane": "Bane"
"Bane": "Bane",
"CreatureSkill": "Skill",
"NoSkill": "No skill",
"Days": "Days",
"RangedModifiers": "Ranged Modifiers",
"Range": "Range",
"RangeShort": "Short Range",
"RangeNormal": "Normal Range",
"RangeLong": "Long Range",
"RangeExtreme": "Extreme Range",
"Aim": "Aim",
"LaserSight": "Laser Sight",
"FastTarget": "Fast-Moving Target",
"Cover": "Cover / Diving",
"Prone": "Prone Target",
"MeleeModifiers": "Melee Modifiers",
"Parry": "Parry (defender's Melee score)",
"Dodge": "Target Dodging",
"DodgeDM": "Dodge DM",
"FireMode": "Fire Mode",
"AutoSingle": "Single",
"AutoBurst": "Burst",
"AutoFull": "Full Auto",
"AutoNoAim": "Burst / Full-Auto: all aiming advantages are cancelled.",
"AutoBurstInfo": "Burst (Auto {level}) — +{level} damage — ammo: {ammo}",
"AutoFullInfo": "Full Auto (Auto {level}) — {level} attacks — ammo: {ammo}",
"ScopeActive": "Scope active",
"ScopeHint": "Scope trait: ignores the automatic Extreme Range rule beyond 100m, provided the traveller aims before firing. Select the actual range below.",
"ZeroGActive": "Zero-G",
"ZeroGHint": "Zero-G trait: this weapon has little or no recoil. It can be used in low or zero gravity without requiring an Athletics (DEX) test. No additional DM required in those conditions."
},
"Timeframes": {
"Normal": "Normal",
@@ -355,7 +391,26 @@
"ApplyDamages": "Apply Damages",
"Damages": "Roll damages",
"Success": "Success",
"Failure": "Failure"
"Failure": "Failure",
"Effect": "Effect",
"Dice": "Dice",
"Result": "Result",
"DiceModifier": "Dice Modifier",
"APIgnore": "AP",
"APIgnoreHint": "This weapon ignores {ap} points of armor (AP trait)",
"BlastArea": "Blast",
"BlastHint": "Blast weapon: damage is applied to all targets within the blast radius (in meters). Dodge reactions cannot be used, but targets may dive for cover. Cover between target and explosion center applies.",
"BlastRules": "No dodge — dive for cover possible",
"StunWeapon": "Stun Weapon — END damage only",
"StunHint": "Stun weapon: damage is only deducted from END (after armor). If END reaches 0, the target is incapacitated for a number of rounds equal to the difference between damage dealt and the target's END. Fully healed after 1 hour of rest."
},
"Radiation": {
"Badge": "Radiation Weapon — RADs",
"Hint": "This weapon emits radiation. On a successful attack, the target receives 2D×20 additional rads. Click the button to roll radiation damage.",
"Title": "Radiation Damage",
"Rads": "RADs",
"RollButton": "Roll RADs",
"Rules": "See p.78 for radiation effects (nausea, illness, death). ×3 multiplier for starship-scale weapons."
}
},
"Items": {
@@ -393,7 +448,7 @@
"Informations": "Informations",
"Improvement": "Improvement",
"Interval": "Interval",
"IsMelee": "IsMelee",
"IsMelee": "Melee Weapon",
"Items": "Items",
"Level": "Level",
"Location": "Location",
@@ -410,6 +465,7 @@
"Occupation": "Occupation",
"OnHand": "On Hand",
"Options": "Options",
"PerDay": "per day",
"PSICost": "PSI Cost",
"Powered": "Powered",
"Processing": "Processing",
@@ -443,10 +499,150 @@
"Weightless": "Weightless",
"Quantity": "Quantity"
},
"WeaponTraits": {
"SectionTitle": "Traits",
"AP": "AP (Armor Piercing)",
"APHint": "Ignores X points of armor protection",
"Auto": "Auto",
"AutoHint": "Automatic fire — Burst and Full-Auto modes available",
"Blast": "Blast",
"BlastHint": "Explosion radius in meters",
"Bulky": "Bulky",
"BulkyHint": "Requires STR 9+ or suffer negative DM",
"VeryBulky": "Very Bulky",
"VeryBulkyHint": "Requires STR 12+ or suffer negative DM",
"Stun": "Stun",
"StunHint": "Non-lethal damage — deducted from END only",
"Smart": "Smart",
"SmartHint": "Guided projectiles — DM = TL difference (min +1, max +6)",
"Radiation": "Radiation",
"RadiationHint": "Inflicts 2D×20 rads on target",
"Scope": "Scope",
"ScopeHint": "Ignores extreme range rule (>100m) if aiming",
"ZeroG": "Zero-G",
"ZeroGHint": "No recoil — no Athletics check needed in microgravity"
},
"Durations": {
"Seconds": "Seconds",
"Minutes": "Minutes",
"Heures": "Hours"
},
"Creature": {
"Name": "Name",
"Life": "HP",
"Speed": "Speed",
"Armor": "Armor",
"Psi": "PSI",
"Initiative": "Initiative",
"SizeHint": "Estimated size based on HP",
"Behavior": "Behavior",
"TabSkills": "Skills",
"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",
"AddAttack": "Add an attack",
"AddTrait": "Add a trait",
"NoSkills": "No skills",
"NoAttacks": "No attacks",
"NoTraits": "No traits",
"Delete": "Delete",
"RollSkill": "Skill roll",
"RollAttack": "Attack roll",
"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",
"charognard": "Scavenger",
"omnivore": "Omnivore"
},
"CreatureBehaviorSubType": {
"accumulateur": "Gatherer",
"brouteur": "Grazer",
"filtreur": "Filter",
"intermittent": "Intermittent",
"chasseur": "Hunter",
"detourneux": "Hijacker",
"guetteur": "Pouncer",
"mangeur": "Eater",
"piegeur": "Trapper",
"intimidateur": "Intimidator",
"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.",
"DamageAppliedAP": "{name} has taken {amount} damage (armor reduced by {ap} from AP trait).",
"BulkyPenalty": "Bulky weapon: insufficient STR (DM too low). Penalty {penalty} applied to roll.",
"VeryBulkyPenalty": "Very Bulky weapon: insufficient STR (DM too low). Penalty {penalty} applied to roll.",
"StunDamageApplied": "{name} took {amount} stun damage (END only).",
"StunIncapacitated": "{name} is incapacitated for {rounds} round(s)! (END reduced to 0)",
"AmmoUsed": "{weapon}: {used} round(s) expended. Magazine remaining: {remaining}.",
"AmmoEmpty": "{weapon}: magazine empty after this shot!",
"NoAmmo": "{weapon}: magazine empty! Reload before firing."
},
"Errors": {
"NoTokenSelected": "No active target. Target 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",
@@ -54,20 +60,21 @@
"Gender": "Genre",
"Pronouns": "Pronoms",
"Actor": {
"StudyPeriod":"Période d'étude",
"TrainingInSkill":"Compétence en formation",
"Completed":"Completée",
"Weeks":"Semaines",
"NewCareer":"Nouvelle Carrière",
"AddCareer":"Ajouter Carrière",
"EditCareer":"Éditer Carrière",
"EditTrait":"Éditer Trait",
"StudyPeriod": "Période d'étude",
"TrainingInSkill": "Compétence en formation",
"Completed": "Completée",
"Weeks": "Semaines",
"NewCareer": "Nouvelle Carrière",
"AddCareer": "Ajouter Carrière",
"ThisTrait": "ce trait",
"EditCareer": "Éditer Carrière",
"EditTrait": "Éditer Trait",
"DeleteTrait": "Supprimer Trait",
"DeleteCareer":"Supprimer Carrière",
"NewSkill":"Nouvelle Compétence",
"DeleteSkill":"Supprimer Compétence",
"EditSkill":"Éditer Compétence",
"PsionicTalents":"Talents Psionique",
"DeleteCareer": "Supprimer Carrière",
"NewSkill": "Nouvelle Compétence",
"DeleteSkill": "Supprimer Compétence",
"EditSkill": "Éditer Compétence",
"PsionicTalents": "Talents Psionique",
"NewPsionicTalent": "Nouveau Talent Psionique",
"AddPsionicTalent": "Ajouter Talent Psionique",
"EditPsionic": "Éditer Talent Psionique",
@@ -343,7 +350,36 @@
"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",
"RangedModifiers": "Modificateurs de tir",
"Range": "Portée",
"RangeShort": "Courte portée",
"RangeNormal": "Portée normale",
"RangeLong": "Longue portée",
"RangeExtreme": "Portée extrême",
"Aim": "Visée",
"LaserSight": "Pointeur laser",
"FastTarget": "Cible bougeant vite",
"Cover": "À couvert / Plongé",
"Prone": "À plat ventre",
"MeleeModifiers": "Modificateurs de mêlée",
"Parry": "Parade (score Mêlée du défenseur)",
"Dodge": "Esquive de la cible",
"DodgeDM": "MD Esquive",
"FireMode": "Mode de tir",
"AutoSingle": "Simple",
"AutoBurst": "Rafale",
"AutoFull": "Auto complet",
"AutoNoAim": "Rafale / Auto : les avantages de la visée sont annulés.",
"AutoBurstInfo": "Rafale (Auto {level}) — +{level} dégâts — munitions: {ammo}",
"AutoFullInfo": "Auto complet (Auto {level}) — {level} attaques — munitions: {ammo}",
"ScopeActive": "Viseur actif",
"ScopeHint": "Trait Viseur : ignore la règle Portée Extrême automatique au-delà de 100m, à condition de viser avant de tirer. Choisissez la portée réelle dans le sélecteur ci-dessous.",
"ZeroGActive": "Zéro-G",
"ZeroGHint": "Trait Zéro-G : cette arme a peu ou pas de recul. Elle peut être utilisée en gravité faible ou nulle sans nécessiter de test d'Athlétisme (DEX). Aucun MD supplémentaire requis dans ces conditions."
},
"Timeframes": {
"Normal": "Normal",
@@ -355,7 +391,26 @@
"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",
"APIgnore": "AP",
"APIgnoreHint": "Cette arme ignore {ap} points d'armure (trait AP)",
"BlastArea": "Explosion",
"BlastHint": "Arme à explosion : les dégâts sont infligés à toutes les cibles dans le rayon indiqué (en mètres). Pas d'esquive possible, mais possibilité de plonger à couvert. Le couvert entre la cible et le centre de l'explosion s'applique.",
"BlastRules": "Pas d'esquive — plonger à couvert possible",
"StunWeapon": "Arme Incapacitante — dégâts END uniquement",
"StunHint": "Arme incapacitante : les dégâts sont déduits uniquement de l'END (après armure). Si l'END atteint 0, la cible est neutralisée pendant un nombre de rounds égal à la différence entre les dégâts et l'END initiale. Guérison complète après 1h de repos."
},
"Radiation": {
"Badge": "Arme à Rayonnement — RADs",
"Hint": "Cette arme émet des radiations. En cas d'attaque réussie, la cible reçoit 2D×20 rads supplémentaires. Cliquez le bouton pour lancer les dégâts de RAD.",
"Title": "Dégâts de Rayonnement",
"Rads": "RADs",
"RollButton": "Lancer RADs",
"Rules": "Consultez p.78 pour l'effet des radiations (nausées, maladies, mort). Multiplicé ×3 pour une arme à l'échelle spatiale."
}
},
"Items": {
@@ -393,7 +448,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 +465,7 @@
"Occupation": "Profession",
"OnHand": "Sur Soi",
"Options": "Options",
"PerDay": "par jour",
"PSICost": "Coût PSI",
"Powered": "Alimenté",
"Processing": "Capacité de Traitement",
@@ -443,10 +499,150 @@
"Weightless": "Aucun Poids",
"Quantity": "Quantité"
},
"WeaponTraits": {
"SectionTitle": "Traits",
"AP": "AP (Perforant)",
"APHint": "Ignore X points de protection d'armure",
"Auto": "Auto",
"AutoHint": "Tir automatique — Rafale et Auto complet disponibles",
"Blast": "Explosion",
"BlastHint": "Rayon de l'explosion en mètres",
"Bulky": "Encombrant",
"BulkyHint": "Requiert FOR 9+ sinon MD négatif",
"VeryBulky": "Très Encombrant",
"VeryBulkyHint": "Requiert FOR 12+ sinon MD négatif",
"Stun": "Incapacitante",
"StunHint": "Dégâts non létaux — déduits uniquement de l'END",
"Smart": "Intelligente",
"SmartHint": "Projectiles guidés — MD = différence de NT (min +1, max +6)",
"Radiation": "Rayonnement",
"RadiationHint": "Inflige 2D×20 rads à la cible",
"Scope": "Viseur",
"ScopeHint": "Ignore la règle portée extrême (>100m) si le tireur vise",
"ZeroG": "Zéro-G",
"ZeroGHint": "Pas de recul — aucun jet d'Athlétisme requis en microgravité"
},
"Durations": {
"Seconds": "Secondes",
"Minutes": "Minutes",
"Heures": "Heures"
},
"Creature": {
"Name": "Nom",
"Life": "PdV",
"Speed": "Vitesse",
"Armor": "Armure",
"Psi": "PSI",
"Initiative": "Initiative",
"SizeHint": "Taille estimée selon les PdV",
"Behavior": "Comportement",
"TabSkills": "Compétences",
"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",
"AddAttack": "Ajouter une attaque",
"AddTrait": "Ajouter un trait",
"NoSkills": "Aucune compétence",
"NoAttacks": "Aucune attaque",
"NoTraits": "Aucun trait",
"Delete": "Supprimer",
"RollSkill": "Jet de compétence",
"RollAttack": "Jet d'attaque",
"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",
"charognard": "Charognard",
"omnivore": "Omnivore"
},
"CreatureBehaviorSubType": {
"accumulateur": "Accumulateur",
"brouteur": "Brouteur",
"filtreur": "Filtreur",
"intermittent": "Intermittent",
"chasseur": "Chasseur",
"detourneux": "Détourneux",
"guetteur": "Guetteur",
"mangeur": "Mangeur",
"piegeur": "Piégeur",
"intimidateur": "Intimidateur",
"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.",
"DamageAppliedAP": "{name} a subi {amount} dégâts (armure réduite de {ap} par AP).",
"BulkyPenalty": "Arme Encombrante : FOR insuffisante (MD FOR trop faible). Pénalité {penalty} appliquée au jet.",
"VeryBulkyPenalty": "Arme Très Encombrante : FOR insuffisante (MD FOR trop faible). Pénalité {penalty} appliquée au jet.",
"StunDamageApplied": "{name} a subi {amount} dégâts incapacitants (END seulement).",
"StunIncapacitated": "{name} est neutralisé(e) pour {rounds} round(s) ! (END réduite à 0)",
"AmmoUsed": "{weapon} : {used} munition(s) consommée(s). Chargeur restant : {remaining}.",
"AmmoEmpty": "{weapon} : chargeur vide après ce tir !",
"NoAmmo": "{weapon} : chargeur vide ! Rechargez avant de tirer."
},
"Errors": {
"NoTokenSelected": "Aucune cible active. Ciblez 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

11
package.json Normal file
View File

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

8
rollup.config.mjs Normal file
View File

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

115
src/module/actors/actor.js Normal file
View File

@@ -0,0 +1,115 @@
import { ActorCharacter } from "./character.js";
export class MGT2Combatant extends Combatant {
}
export class TravellerActor extends Actor {
prepareDerivedData() {
if (this.type === "character") {
this.system.initiative = ActorCharacter.getInitiative(this);
}
}
async _preCreate(data, options, user) {
if ( (await super._preCreate(data, options, user)) === false ) return false;
if (this.type === "character") {
ActorCharacter.preCreate(this, data, options, user);
}
}
async _onDeleteDescendantDocuments(parent, collection, documents, ids, options, userId) {
await super._onDeleteDescendantDocuments(parent, collection, documents, ids, options, userId);
if (this.type === "character") {
await ActorCharacter.onDeleteDescendantDocuments(this, parent, collection, documents, ids, options, userId);
}
}
async _onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId) {
super._onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId);
//console.log("_onUpdateDescendantDocuments");
if (this.type === "character") {
await ActorCharacter.onUpdateDescendantDocuments(this, parent, collection, documents, changes, options, userId);
}
}
async _preUpdate(changed, options, user) {
if ((await super._preUpdate(changed, options, user)) === false) return false;
if (this.type === "character") {
await ActorCharacter.preUpdate(this, changed, options, user);
}
}
getInitiative($this) {
if (this.type === "character") {
return ActorCharacter.getInitiative(this);
}
}
applyDamage(amount, { ignoreArmor = false, ap = 0, stun = false } = {}) {
if (this.type === "character") {
return ActorCharacter.applyDamage(this, amount, { ignoreArmor, ap, stun });
} else if (this.type === "creature") {
if (isNaN(amount) || amount === 0) return Promise.resolve({ incapRounds: 0 });
if (amount < 0) amount = Math.abs(amount);
const rawArmor = ignoreArmor ? 0 : (this.system.armor ?? 0);
const armorValue = Math.max(0, rawArmor - ap);
const effective = Math.max(0, amount - armorValue);
if (effective === 0) return Promise.resolve({ incapRounds: 0 });
const newValue = Math.max(0, (this.system.life.value ?? 0) - effective);
return this.update({ "system.life.value": newValue }).then(() => ({ incapRounds: 0 }));
}
}
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 });
}
}
}
getContainers() {
if (this.type === "character") {
return ActorCharacter.getContainers(this);
}
return [];
}
getComputers() {
if (this.type === "character") {
return ActorCharacter.getComputers(this);
}
return [];
}
getSkills() {
if (this.type === "character") {
return ActorCharacter.getSkills(this);
}
return [];
}
async recalculateWeight() {
if (this.type === "character") {
return ActorCharacter.recalculateWeight(this);
}
}
}

View File

@@ -0,0 +1,105 @@
const { DialogV2 } = foundry.applications.api;
const { renderTemplate } = foundry.applications.handlebars;
const { FormDataExtended } = foundry.applications.ux;
async function _dialogWithForm(title, templatePath, templateData) {
const htmlContent = await renderTemplate(templatePath, templateData);
const theme = game.settings.get("mgt2", "theme");
return await DialogV2.wait({
window: { title },
content: htmlContent,
rejectClose: false,
buttons: [
{
action: "submit",
label: game.i18n.localize("MGT2.Save"),
icon: '<i class="fa-solid fa-floppy-disk"></i>',
default: true,
callback: (event, button, dialog) => {
return new FormDataExtended(dialog.element.querySelector('form')).object;
}
}
]
});
}
export class CharacterPrompts {
static async openConfig(system) {
return _dialogWithForm(
"Configuration",
"systems/mgt2/templates/actors/actor-config-sheet.html",
{ config: CONFIG.MGT2, system }
);
}
static async openCharacteristic(name, show, showMax, showAll = false) {
return _dialogWithForm(
"Configuration: " + name,
"systems/mgt2/templates/actors/actor-config-characteristic-sheet.html",
{ name, show, showMax, showAll }
);
}
static async openTraitEdit(data) {
const title = data.name ?? game.i18n.localize("MGT2.Actor.EditTrait");
return _dialogWithForm(
title,
"systems/mgt2/templates/actors/trait-sheet.html",
{ config: CONFIG.MGT2, data }
);
}
static async openEditorFullView(title, html) {
const safeTitle = title || game.i18n.localize("MGT2.Actor.Species") || "Species";
const htmlContent = await renderTemplate("systems/mgt2/templates/editor-fullview.html", {
config: CONFIG.MGT2,
html: html ?? ""
});
const theme = game.settings.get("mgt2", "theme");
await DialogV2.wait({
window: { title: safeTitle },
content: htmlContent,
rejectClose: false,
buttons: [
{
action: "close",
label: game.i18n.localize("MGT2.Close") || "Fermer",
default: true,
callback: () => null
}
]
});
}
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;
}
}
]
});
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,520 @@
export class ActorCharacter {
static preCreate($this, data, options, user) {
$this.updateSource({ prototypeToken: { actorLink: true } }) // QoL
}
static prepareData(actorData) {
actorData.initiative = this.getInitiative(actorData);
}
static getInitiative($this) {
let c = $this.system.config.initiative;
return $this.system.characteristics[c].dm;
}
static async onDeleteDescendantDocuments($this, parent, collection, documents, ids, options, userId) {
const toDeleteIds = [];
const itemToUpdates = [];
for (let d of documents) {
if (d.type === "container") {
// Delete content
for (let item of $this.items) {
if (item.system.hasOwnProperty("container") && item.system.container.id === d._id)
toDeleteIds.push(item._id);
}
} else if (d.type === "computer") {
// Eject software
for (let item of $this.items) {
if (item.system.hasOwnProperty("software") && item.system.computerId === d._id) {
let clone = foundry.utils.deepClone(item);
clone.system.software.computerId = "";
itemToUpdates.push(clone);
}
}
}
}
if (toDeleteIds.length > 0)
await $this.deleteEmbeddedDocuments("Item", toDeleteIds);
if (itemToUpdates.length > 0)
await $this.updateEmbeddedDocuments('Item', itemToUpdates);
await this.recalculateWeight($this);
}
static async onUpdateDescendantDocuments($this, parent, collection, documents, changes, options, userId) {
await this.calculEncumbranceAndWeight($this, parent, collection, documents, changes, options, userId);
await this.calculComputers($this, parent, collection, documents, changes, options, userId);
}
static async calculComputers($this, parent, collection, documents, changes, options, userId) {
let change;
let i = 0;
let recalculProcessing = false;
for (let d of documents) {
if (changes[i].hasOwnProperty("system")) {
change = changes[i];
if (d.type === "item" && d.system.subType === "software") {
if (change.system.software.hasOwnProperty("bandwidth") || change.system.software.hasOwnProperty("computerId")) {
recalculProcessing = true;
break;
}
}
}
}
if (recalculProcessing) {
let updatedComputers = [];
let computerChanges = {};
let computers = [];
for (let item of $this.items) {
if (item.system.trash === true) continue;
if (item.type === "computer") {
computers.push(item);
computerChanges[item._id] = { processingUsed: 0 };
}
}
for (let item of $this.items) {
if (item.type !== "item" && item.system.subType !== "software") continue;
if (item.system.software.hasOwnProperty("computerId") && item.system.software.computerId !== "") {
computerChanges[item.system.software.computerId].processingUsed += item.system.software.bandwidth;
}
}
for (let computer of computers) {
let newProcessingUsed = computerChanges[computer._id].processingUsed;
if (computer.system.processingUsed !== newProcessingUsed) {
const cloneComputer = foundry.utils.deepClone($this.getEmbeddedDocument("Item", computer._id));
cloneComputer.system.processingUsed = newProcessingUsed;
cloneComputer.system.overload = cloneComputer.system.processingUsed > cloneComputer.system.processing;
updatedComputers.push(cloneComputer);
}
}
if (updatedComputers.length > 0) {
await $this.updateEmbeddedDocuments('Item', updatedComputers);
}
}
}
static async calculEncumbranceAndWeight($this, parent, collection, documents, changes, options, userId) {
let recalculEncumbrance = false;
let recalculWeight = false;
let change;
let i = 0;
for (let d of documents) {
if (changes[i].hasOwnProperty("system")) {
change = changes[i];
if (d.type === "armor" ||
d.type === "computer" ||
d.type === "gear" ||
d.type === "item" ||
d.type === "weapon") {
if (change.system.hasOwnProperty("quantity") ||
change.system.hasOwnProperty("weight") ||
change.system.hasOwnProperty("weightless") ||
change.system.hasOwnProperty("container") ||
change.system.hasOwnProperty("equipped") ||
d.type === "armor") {
recalculWeight = true;
}
} else if (d.type === "talent" && d.system.subType === "skill") {
if (change.system.level || (change.system?.hasOwnProperty("skill") && change.system?.skill.hasOwnProperty("reduceEncumbrance"))) {
recalculEncumbrance = true;
}
} else if (d.type === "container" && (change.system.hasOwnProperty("onHand") || change.system.hasOwnProperty("weightless"))) {
recalculWeight = true;
}
}
i++;
}
if (recalculEncumbrance || recalculWeight) {
const updateData = {};
this.recalculateArmor($this, updateData);
if (recalculEncumbrance) {
const str = $this.system.characteristics.strength.value;
const end = $this.system.characteristics.endurance.value;
let sumSkill = 0;
$this.items.filter(x => x.type === "talent" && x.system.subType === "skill" && x.system.skill.reduceEncumbrance === true).forEach(x => sumSkill += x.system.level);
let normal = str + end + sumSkill;
let heavy = normal * 2;
updateData["system.states.encumbrance"] = $this.system.inventory.weight > normal;
updateData["system.inventory.encumbrance.normal"] = normal;
updateData["system.inventory.encumbrance.heavy"] = heavy;
}
if (recalculWeight)
await this.recalculateWeight($this, updateData);
else if (Object.keys(updateData).length > 0)
await $this.update(updateData);
}
}
static recalculateArmor($this, updateData) {
if (updateData === null || updateData === undefined)
updateData = {};
let armor = 0;
for (let item of $this.items) {
if (item.type === "armor") {
if (item.system.equipped === true && !isNaN(item.system.protection)) {
armor += (+item.system.protection || 0);
}
}
}
updateData["system.inventory.armor"] = armor;
return updateData;
}
static async recalculateWeight($this, updateData) {
if (updateData === null || updateData === undefined)
updateData = {};
let updatedContainers = [];
let containerChanges = {};
let containers = [];
// List all containers
for (let item of $this.items) {
if (item.system.trash === true) continue;
if (item.type === "container") {
containers.push(item);
containerChanges[item._id] = { count: 0, weight: 0 };
}
}
let onHandWeight = 0;
for (let item of $this.items) {
if (item.type === "container") continue;
if (item.system.hasOwnProperty("weightless") && item.system.weightless === true) continue;
let itemWeight = 0;
if (item.system.hasOwnProperty("weight")) {
let itemQty = item.system.quantity;
if (!isNaN(itemQty) && itemQty > 0) {
itemWeight = item.system.weight;
if (itemWeight > 0) {
itemWeight *= itemQty;
}
}
if (item.type === "armor") {
if (item.system.equipped === true) {
if (item.system.powered === true)
itemWeight = 0;
else
itemWeight *= 0.25; // mass of armor that is being worn by 75% OPTIONAL
}
}
if (item.system.container && item.system.container.id && item.system.container.id !== "") {
// bad deleted container id
if (containerChanges.hasOwnProperty(item.system.container.id)) {
containerChanges[item.system.container.id].weight += Math.round(itemWeight * 10) / 10;
containerChanges[item.system.container.id].count += item.system.quantity;
}
} else {
onHandWeight += Math.round(itemWeight * 10) / 10;
}
}
}
// Check containers new weight
for (let container of containers) {
let newWeight = containerChanges[container._id].weight;
let newCount = containerChanges[container._id].count;
if (container.system.weight !== newWeight || container.system.count !== newCount) {
updatedContainers.push({
_id: container._id,
"system.weight": newWeight,
"system.count": newCount,
});
if (container.system.onHand === true &&
(container.system.weight > 0 || container.system.weightless !== true)) {
onHandWeight += container.system.weight;
}
}
}
updateData["system.inventory.weight"] = onHandWeight;
// Use the threshold from updateData if it was just recalculated (e.g. STR/END talent change),
// otherwise fall back to the persisted value.
const encumbranceThreshold = updateData["system.inventory.encumbrance.normal"] ?? $this.system.inventory.encumbrance.normal;
updateData["system.states.encumbrance"] = onHandWeight > encumbranceThreshold;
await $this.update(updateData);
if (updatedContainers.length > 0) {
await $this.updateEmbeddedDocuments('Item', updatedContainers);
}
}
static async preUpdate($this, changed, options, user) {
// Calc encumbrance
const newStr = foundry.utils.getProperty(changed, "system.characteristics.strength.value") ?? $this.system.characteristics.strength.value;
const newEnd = foundry.utils.getProperty(changed, "system.characteristics.endurance.value") ?? $this.system.characteristics.endurance.value;
if ((newStr !== $this.system.characteristics.strength.value) || (newEnd !== $this.system.characteristics.endurance.value)) {
let sumSkill = 0;
$this.items.filter(x => x.type === "talent" && x.system.subType === "skill" && x.system.skill.reduceEncumbrance === true).forEach(x => sumSkill += x.system.level);
let normal = newStr + newEnd + sumSkill;
let heavy = normal * 2;
foundry.utils.setProperty(changed, "system.inventory.encumbrance.normal", normal);
foundry.utils.setProperty(changed, "system.inventory.encumbrance.heavy", heavy);
// Also update the encumbrance state flag against the new threshold
foundry.utils.setProperty(changed, "system.states.encumbrance", $this.system.inventory.weight > normal);
}
//console.log(foundry.utils.getProperty(changed, "system.characteristics.strength.value"));
const characteristicModified = this.computeCharacteristics(changed);
const strengthValue = foundry.utils.getProperty(changed, "system.characteristics.strength.value") ?? $this.system.characteristics.strength.value;
const strengthMax = foundry.utils.getProperty(changed, "system.characteristics.strength.max") ?? $this.system.characteristics.strength.max;
const dexterityValue = foundry.utils.getProperty(changed, "system.characteristics.dexterity.value") ?? $this.system.characteristics.dexterity.value;
const dexterityMax = foundry.utils.getProperty(changed, "system.characteristics.dexterity.max") ?? $this.system.characteristics.dexterity.max;
const enduranceValue = foundry.utils.getProperty(changed, "system.characteristics.endurance.value") ?? $this.system.characteristics.endurance.value;
const enduranceMax = foundry.utils.getProperty(changed, "system.characteristics.endurance.max") ?? $this.system.characteristics.endurance.max;
const lifeValue = strengthValue + dexterityValue + enduranceValue;
const lifeMax = strengthMax + dexterityMax + enduranceMax;
if ($this.system.life.value !== lifeValue)
foundry.utils.setProperty(changed, "system.life.value", lifeValue);
if ($this.system.life.max !== lifeMax)
foundry.utils.setProperty(changed, "system.life.max", lifeMax);
if (characteristicModified && $this.system.personal.ucp === undefined || $this.system.personal.ucp === "") {
// calc
}
//}
// Apply changes in Actor size to Token width/height
// if ( "size" in (this.system.traits || {}) ) {
// const newSize = foundry.utils.getProperty(changed, "system.traits.size");
// if ( newSize && (newSize !== this.system.traits?.size) ) {
// let size = CONFIG.DND5E.tokenSizes[newSize];
// if ( !foundry.utils.hasProperty(changed, "prototypeToken.width") ) {
// changed.prototypeToken ||= {};
// changed.prototypeToken.height = size;
// changed.prototypeToken.width = size;
// }
// }
// }
}
// static applyHealing($this, amount) {
// if (isNaN(amount) || amount === 0) return;
// const strength = $this.system.characteristics.strength;
// const dexterity = $this.system.characteristics.dexterity;
// const endurance = $this.system.characteristics.endurance;
// const data = {
// strength: { value: strength.value },
// dexterity: { value: dexterity.value },
// endurance: { value: endurance.value }
// };
// $this.update({ system: { characteristics: data } });
// }
static async applyDamage($this, amount, { ignoreArmor = false, ap = 0, stun = false } = {}) {
if (isNaN(amount) || amount === 0) return { incapRounds: 0 };
const rank1 = $this.system.config.damages.rank1;
const rank2 = $this.system.config.damages.rank2;
const rank3 = $this.system.config.damages.rank3;
if (amount < 0) amount = Math.abs(amount);
if (!ignoreArmor) {
const rawArmor = $this.system.inventory?.armor ?? 0;
const armorValue = Math.max(0, rawArmor - ap);
amount = Math.max(0, amount - armorValue);
if (amount === 0) return { incapRounds: 0 };
}
// ── Stun / Incapacitating: only deduct from endurance (rank3) ─────────
if (stun) {
const endKey = rank3; // "endurance" by default
const prevEnd = $this.system.characteristics[endKey].value;
const newEnd = Math.max(0, prevEnd - amount);
const incapRounds = newEnd === 0 ? Math.max(0, amount - prevEnd) : 0;
await $this.update({
system: { characteristics: { [endKey]: { value: newEnd, dm: this.getModifier(newEnd) } } }
});
return { incapRounds };
}
// ── Normal damage cascade: rank1 → rank2 → rank3 ────────────────────
const data = {};
data[rank1] = { value: $this.system.characteristics[rank1].value };
data[rank2] = { value: $this.system.characteristics[rank2].value };
data[rank3] = { value: $this.system.characteristics[rank3].value };
for (const [key, rank] of Object.entries(data)) {
if (rank.value > 0) {
if (rank.value >= amount) {
rank.value -= amount;
amount = 0;
} else {
amount -= rank.value;
rank.value = 0;
}
rank.dm = this.getModifier(rank.value);
if (amount <= 0) break;
}
}
await $this.update({ system: { characteristics: data } });
return { incapRounds: 0 };
}
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) {
if (item.type == "container") {
containers.push(item);
}
}
containers.sort(this.compareByName);
return containers;
}
static getComputers($this) {
const containers = [];
for (let item of $this.items) {
if (item.type == "computer") {
containers.push(item);
}
}
containers.sort(this.compareByName);
return containers;
}
static getSkills($this) {
const skills = [];
for (let item of $this.items) {
if (item.type === "talent" && item.system.subType === "skill") {
skills.push(item);
}
}
skills.sort(this.compareByName);
return skills;
}
static computeCharacteristics(changed) {
let modified = this.computeCharacteristic(changed, "strength");
if (this.computeCharacteristic(changed, "dexterity") && !modified) modified = true;
if (this.computeCharacteristic(changed, "endurance") && !modified) modified = true;
if (this.computeCharacteristic(changed, "intellect") && !modified) modified = true;
if (this.computeCharacteristic(changed, "education") && !modified) modified = true;
if (this.computeCharacteristic(changed, "social") && !modified) modified = true;
if (this.computeCharacteristic(changed, "morale") && !modified) modified = true;
if (this.computeCharacteristic(changed, "luck") && !modified) modified = true;
if (this.computeCharacteristic(changed, "sanity") && !modified) modified = true;
if (this.computeCharacteristic(changed, "charm") && !modified) modified = true;
if (this.computeCharacteristic(changed, "psionic") && !modified) modified = true;
if (this.computeCharacteristic(changed, "other") && !modified) modified = true;
return modified;
}
static computeCharacteristic(changed, name) {
//if (isNaN(c.value) || c.value <= 0) c.value = 0;
//c.dm = this._getModifier(c.value)
const path = `system.characteristics.${name}`;
const newValue = foundry.utils.getProperty(changed, path + ".value");// || this.system.characteristics[name].value;
if (newValue) {
const dm = this.getModifier(newValue);
foundry.utils.setProperty(changed, path + ".dm", dm);
return true;
}
return false;
}
static getModifier(value) {
if (isNaN(value) || value <= 0) return -3;
if (value >= 1 && value <= 2) return -2;
if (value >= 3 && value <= 5) return -1;
if (value >= 6 && value <= 8) return 0;
if (value >= 9 && value <= 11) return 1;
if (value >= 12 && value <= 14) return 2;
return 3;
}
static compareByName(a, b) {
if (!a.hasOwnProperty("name") || !b.hasOwnProperty("name")) {
return 0;
}
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}
}

View File

@@ -0,0 +1,18 @@
export class VehiculeActorSheet extends ActorSheet {
static get defaultOptions() {
const options = super.defaultOptions;
//if (game.user.isGM || options.editable)
// options.dragDrop.push({ dragSelector: ".drag-item-list", dropSelector: ".drop-item-list" });
return foundry.utils.mergeObject(options, {
classes: ["mgt2", game.settings.get("mgt2", "theme"), "actor", "vehicule", "nopad"],
template: "systems/mgt2/templates/actors/vehicule-sheet.html",
width: 780,
//height: 600,
tabs: [
{ navSelector: ".sheet-sidebar", contentSelector: "form" }
]
});
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,295 @@
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) {
if (!formula) return "1d6";
return formula
.replace(/(\d*)D(\d*)([+-]\d+)?/gi, (_, count, sides, mod) => {
const n = count || "1";
const d = sides || "6";
return mod ? `${n}d${d}${mod}` : `${n}d${d}`;
});
}
export default class TravellerCreatureSheet extends MGT2ActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "creature", "nopad"],
position: {
width: 720,
height: 600,
},
window: {
...super.DEFAULT_OPTIONS.window,
title: "TYPES.Actor.creature",
},
actions: {
...super.DEFAULT_OPTIONS.actions,
rollAttack: TravellerCreatureSheet.#onRollAttack,
rollSkill: TravellerCreatureSheet.#onRollSkill,
addSkill: TravellerCreatureSheet.#onAddRow,
deleteSkill: TravellerCreatureSheet.#onDeleteRow,
addAttack: TravellerCreatureSheet.#onAddRow,
deleteAttack: TravellerCreatureSheet.#onDeleteRow,
addTrait: TravellerCreatureSheet.#onAddRow,
deleteTrait: TravellerCreatureSheet.#onDeleteRow,
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/mgt2/templates/actors/creature-sheet.html",
},
}
/** @override */
tabGroups = { primary: "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;
return context;
}
_getSizeLabel(pdv) {
if (pdv <= 2) return "Souris / Rat";
if (pdv <= 5) return "Chat";
if (pdv <= 7) return "Blaireau / Chien";
if (pdv <= 13) return "Chimpanzé / Chèvre";
if (pdv <= 28) return "Humain";
if (pdv <= 35) return "Vache / Cheval";
if (pdv <= 49) return "Requin";
if (pdv <= 70) return "Rhinocéros";
if (pdv <= 90) return "Éléphant";
if (pdv <= 125) return "Carnosaure";
return "Sauropode / Baleine";
}
_getSizeTrait(pdv) {
if (pdv <= 2) return "Petit (4)";
if (pdv <= 5) return "Petit (3)";
if (pdv <= 7) return "Petit (2)";
if (pdv <= 13) return "Petit (1)";
if (pdv <= 28) return "—";
if (pdv <= 35) return "Grand (+1)";
if (pdv <= 49) return "Grand (+2)";
if (pdv <= 70) return "Grand (+3)";
if (pdv <= 90) return "Grand (+4)";
if (pdv <= 125) return "Grand (+5)";
return "Grand (+6)";
}
// ───────────────────────────────────────────────────────── Roll Helpers
static async #postCreatureRoll({ actor, roll, rollLabel, dm, difficulty, difficultyLabel, rollMode, extraTooltip, damageFormula }) {
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 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 showRollDamage = success && !!damageFormula;
const chatData = {
creatureName: actor.name,
creatureImg: actor.img,
rollLabel,
formula: roll.formula,
total: roll.total,
tooltip: await roll.getTooltip(),
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}`] : [],
showRollDamage,
};
const chatContent = await renderTemplate(
"systems/mgt2/templates/chat/creature-roll.html",
chatData
);
const flags = showRollDamage ? {
mgt2: {
damage: {
formula: normalizeDice(damageFormula),
effect,
rollObjectName: actor.name,
rollTypeName: rollLabel,
}
}
} : {};
await ChatMessage.create({
content: chatContent,
speaker: ChatMessage.getSpeaker({ actor }),
rolls: [roll],
rollMode: rollMode ?? game.settings.get("core", "rollMode"),
flags,
});
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}`);
await TravellerCreatureSheet.#postCreatureRoll({
actor, roll, rollLabel,
dm,
difficulty: result.difficulty,
rollMode: result.rollMode,
extraTooltip: tooltipParts.join(" | "),
damageFormula: attack.damage || null,
});
}
// ───────────────────────────────────────────────────────── CRUD Handlers
static async #onAddRow(event, target) {
const prop = target.dataset.prop;
if (!prop) return;
const actor = this.document;
const arr = foundry.utils.deepClone(actor.system[prop] ?? []);
arr.push(this._getDefaultRow(prop));
await actor.update({ [`system.${prop}`]: arr });
}
static async #onDeleteRow(event, target) {
const prop = target.dataset.prop;
const index = parseInt(target.dataset.index);
if (!prop || isNaN(index)) return;
const actor = this.document;
const arr = foundry.utils.deepClone(actor.system[prop] ?? []);
arr.splice(index, 1);
await actor.update({ [`system.${prop}`]: arr });
}
_getDefaultRow(prop) {
switch (prop) {
case "skills": return { name: "", level: 0, note: "" };
case "attacks": return { name: "", damage: "1D", skill: -1, description: "" };
case "traits": return { name: "", value: "", description: "" };
default: return {};
}
}
}

View File

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

View File

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

238
src/module/chatHelper.js Normal file
View File

@@ -0,0 +1,238 @@
import { MGT2Helper } from "./helper.js";
export class ChatHelper {
static setupCardListeners(message, element, messageData) {
if (!message || !element) {
return;
}
// Restore disabled state for already-applied buttons
const appliedActions = message.flags?.mgt2?.appliedActions ?? [];
if (appliedActions.length > 0) {
appliedActions.forEach(action => {
element.querySelectorAll(`button[data-action="${action}"]`).forEach(btn => {
btn.disabled = true;
});
});
}
// Apply buttons are GM-only: hide them for players
const GM_ACTIONS = ["damage", "healing", "surgeryDamage"];
if (!game.user.isGM) {
GM_ACTIONS.forEach(action => {
element.querySelectorAll(`button[data-action="${action}"]`).forEach(btn => {
btn.style.display = "none";
});
});
// Hide the buttons container if no visible buttons remain
element.querySelectorAll(".mgt2-buttons").forEach(container => {
const hasVisible = [...container.querySelectorAll("button")]
.some(b => b.style.display !== "none");
if (!hasVisible) container.style.display = "none";
});
}
element.querySelectorAll('button[data-action="rollDamage"]').forEach(el => {
el.addEventListener('click', async event => {
await this._processRollDamageButtonEvent(message, event);
});
});
element.querySelectorAll('button[data-action="rollRadiation"]').forEach(el => {
el.addEventListener('click', async event => {
await this._rollRadiationDamage(message, event);
});
});
element.querySelectorAll('button[data-action="damage"]').forEach(el => {
el.addEventListener('click', async event => {
await this._applyChatCardDamage(message, event);
});
});
element.querySelectorAll('button[data-action="healing"]').forEach(el => {
el.addEventListener('click', async event => {
await this._applyChatCardHealing(message, event);
});
});
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);
});
});
}
static async _processRollButtonEvent(message, event) {
event.preventDefault();
event.stopPropagation();
let buttons = message.flags.mgt2.buttons;
const index = event.target.dataset.index;
const button = buttons[index];
let roll = await new Roll(button.formula, {}).roll();
const chatData = {
user: game.user.id,
speaker: message.speaker,
formula: roll._formula,
tooltip: await roll.getTooltip(),
total: Math.round(roll.total * 100) / 100,
rollObjectName: button.message.objectName,
rollMessage: MGT2Helper.format(button.message.flavor, Math.round(roll.total * 100) / 100),
};
const html = await foundry.applications.handlebars.renderTemplate("systems/mgt2/templates/chat/roll.html", chatData);
chatData.content = html;
return roll.toMessage(chatData);
}
static async _processRollDamageButtonEvent(message, event) {
event.preventDefault();
event.stopPropagation();
const damageFlags = message.flags?.mgt2?.damage;
if (!damageFlags?.formula) {
ui.notifications.warn(game.i18n.localize("MGT2.Errors.InvalidRollFormula"));
return;
}
const effect = damageFlags.effect ?? 0;
const effectPart = effect > 0 ? `+${effect}` : effect < 0 ? `${effect}` : "";
const rollFormula = damageFlags.formula + effectPart;
let roll;
try {
roll = await new Roll(rollFormula, {}).roll();
} catch (e) {
ui.notifications.warn(game.i18n.localize("MGT2.Errors.InvalidRollFormula"));
return;
}
const rollTypeName = damageFlags.rollTypeName
? damageFlags.rollTypeName + " " + game.i18n.localize("MGT2.Actor.Damage")
: null;
const ap = damageFlags.ap ?? 0;
const blast = damageFlags.blast ?? 0;
const stun = damageFlags.stun ?? false;
const radiation = damageFlags.radiation ?? false;
const chatData = {
user: game.user.id,
speaker: message.speaker,
formula: roll._formula,
tooltip: await roll.getTooltip(),
total: Math.round(roll.total * 100) / 100,
showButtons: true,
hasDamage: true,
rollTypeName,
rollObjectName: damageFlags.rollObjectName,
apValue: ap > 0 ? ap : null,
blastRadius: blast > 0 ? blast : null,
stunWeapon: stun || null,
radiationWeapon: radiation || null,
};
const html = await foundry.applications.handlebars.renderTemplate("systems/mgt2/templates/chat/roll.html", chatData);
chatData.content = html;
// Persist ap, blast, stun, radiation in damage message flags so handlers can read them
if (ap > 0 || blast > 0 || stun || radiation) chatData.flags = { mgt2: { damage: { ap, blast, stun, radiation, rollObjectName: damageFlags.rollObjectName } } };
return roll.toMessage(chatData);
}
static async #markButtonApplied(message, btn, action) {
const existing = message.flags?.mgt2?.appliedActions ?? [];
if (!existing.includes(action)) {
await message.setFlag("mgt2", "appliedActions", [...existing, action]);
}
if (btn) btn.disabled = true;
}
static async _applyChatCardDamage(message, event) {
if (game.user.targets.size === 0) {
ui.notifications.warn(game.i18n.localize("MGT2.Errors.NoTokenSelected"));
return;
}
const btn = event.currentTarget;
const roll = message.rolls[0];
const amount = Math.round(roll.total * 100) / 100;
const ap = message.flags?.mgt2?.damage?.ap ?? 0;
const stun = message.flags?.mgt2?.damage?.stun ?? false;
await Promise.all([...game.user.targets].map(async t => {
const result = await t.actor.applyDamage(amount, { ap, stun });
if (stun) {
const incapRounds = result?.incapRounds ?? 0;
if (incapRounds > 0) {
ui.notifications.warn(game.i18n.format("MGT2.Notifications.StunIncapacitated", { name: t.actor.name, rounds: incapRounds }));
} else {
ui.notifications.info(game.i18n.format("MGT2.Notifications.StunDamageApplied", { name: t.actor.name, amount }));
}
} else {
const notifKey = ap > 0 ? "MGT2.Notifications.DamageAppliedAP" : "MGT2.Notifications.DamageApplied";
ui.notifications.info(game.i18n.format(notifKey, { name: t.actor.name, amount, ap }));
}
}));
await ChatHelper.#markButtonApplied(message, btn, "damage");
}
static async _applyChatCardHealing(message, event) {
if (game.user.targets.size === 0) {
ui.notifications.warn(game.i18n.localize("MGT2.Errors.NoTokenSelected"));
return;
}
const btn = event.currentTarget;
const amount = message.flags?.mgt2?.healing?.amount
?? message.flags?.mgt2?.surgery?.healing
?? Math.max(1, message.rolls[0].total);
await Promise.all([...game.user.targets].map(async t => {
await t.actor.applyHealing(amount);
ui.notifications.info(game.i18n.format("MGT2.Notifications.HealingApplied", { name: t.actor.name, amount }));
}));
await ChatHelper.#markButtonApplied(message, btn, "healing");
}
static async _applyChatCardSurgeryDamage(message, event) {
if (game.user.targets.size === 0) {
ui.notifications.warn(game.i18n.localize("MGT2.Errors.NoTokenSelected"));
return;
}
const btn = event.currentTarget;
const amount = message.flags?.mgt2?.surgery?.surgeryDamage ?? 3;
await Promise.all([...game.user.targets].map(async t => {
await t.actor.applyDamage(amount, { ignoreArmor: true });
ui.notifications.info(game.i18n.format("MGT2.Notifications.DamageApplied", { name: t.actor.name, amount }));
}));
await ChatHelper.#markButtonApplied(message, btn, "surgeryDamage");
}
static async _rollRadiationDamage(message, event) {
event.preventDefault();
event.stopPropagation();
const damageFlags = message.flags?.mgt2?.damage;
const rollObjectName = damageFlags?.rollObjectName ?? "";
// 2D × 20 rads (MGT2 rule: 2d6 × 20)
const roll = await new Roll("2d6 * 20", {}).roll();
const chatData = {
user: game.user.id,
speaker: message.speaker,
formula: roll._formula,
tooltip: await roll.getTooltip(),
total: Math.round(roll.total),
rollObjectName,
};
const html = await foundry.applications.handlebars.renderTemplate("systems/mgt2/templates/chat/radiation.html", chatData);
chatData.content = html;
return roll.toMessage(chatData);
}
}

187
src/module/config.js Normal file
View File

@@ -0,0 +1,187 @@
export const MGT2 = {};
MGT2.MetricRange = Object.freeze({
meter: "MGT2.MetricRange.meter",
kilometer: "MGT2.MetricRange.kilometer"
});
MGT2.MetricWeight = Object.freeze({
kilogram: "MGT2.MetricWeight.kilogram",
ton: "MGT2.MetricWeight.ton"
});
MGT2.Difficulty = Object.freeze({
NA: "MGT2.Difficulty.NA",
Simple: "MGT2.Difficulty.Simple",
Easy: "MGT2.Difficulty.Easy",
Routine: "MGT2.Difficulty.Routine",
Average: "MGT2.Difficulty.Average",
Difficult: "MGT2.Difficulty.Difficult",
VeryDifficult: "MGT2.Difficulty.VeryDifficult",
Formidable: "MGT2.Difficulty.Formidable",
Impossible: "MGT2.Difficulty.Impossible"
});
MGT2.ItemSubType = Object.freeze({
loot: "MGT2.ItemSubType.loot",
software: "MGT2.ItemSubType.software"
});
MGT2.EquipmentSubType = Object.freeze({
augment: "MGT2.EquipmentSubType.augment",
clothing: "MGT2.EquipmentSubType.clothing",
equipment: "MGT2.EquipmentSubType.equipment",
trinket: "MGT2.EquipmentSubType.trinket",
toolkit: "MGT2.EquipmentSubType.toolkit"
});
MGT2.TalentSubType = Object.freeze({
skill: "MGT2.TalentSubType.skill",
psionic: "MGT2.TalentSubType.psionic"
});
MGT2.DiseaseSubType = Object.freeze({
disease: "MGT2.DiseaseSubType.disease",
poison: "MGT2.DiseaseSubType.poison",
wound: "MGT2.DiseaseSubType.wound"
});
MGT2.PsionicReach = Object.freeze({
NA: "MGT2.PsionicReach.NA",
Personal: "MGT2.PsionicReach.Personal",
Close: "MGT2.PsionicReach.Close",
Short: "MGT2.PsionicReach.Short",
Medium: "MGT2.PsionicReach.Medium",
Long: "MGT2.PsionicReach.Long",
VeryLong: "MGT2.PsionicReach.VeryLong",
Distant: "MGT2.PsionicReach.Distant",
VeryDistant: "MGT2.PsionicReach.VeryDistant",
Continental: "MGT2.PsionicReach.Continental",
Planetary: "MGT2.PsionicReach.Planetary"
});
MGT2.ContactRelations = Object.freeze({
Allie: "MGT2.Contact.Relation.Allie",
Contact: "MGT2.Contact.Relation.Contact",
Rival: "MGT2.Contact.Relation.Rival",
Enemy: "MGT2.Contact.Relation.Enemy"
});
MGT2.ContactStatus = Object.freeze({
Alive: "MGT2.Contact.Status.Alive",
Unknow: "MGT2.Contact.Status.Unknow",
Dead: "MGT2.Contact.Status.Dead"
});
MGT2.Attitudes = Object.freeze({
Unknow: "MGT2.Contact.Attitude.Unknow",
Hostile: "MGT2.Contact.Attitude.Hostile",
Unfriendly: "MGT2.Contact.Attitude.Unfriendly",
Indifferent: "MGT2.Contact.Attitude.Indifferent",
Friendly: "MGT2.Contact.Attitude.Friendly",
Helpful: "MGT2.Contact.Attitude.Helpful",
Complicated: "MGT2.Contact.Attitude.Complicated"
});
MGT2.Characteristics = Object.freeze({
strength: "MGT2.Characteristics.strength.name",
dexterity: "MGT2.Characteristics.dexterity.name",
endurance: "MGT2.Characteristics.endurance.name",
intellect: "MGT2.Characteristics.intellect.name",
education: "MGT2.Characteristics.education.name",
social: "MGT2.Characteristics.social.name",
morale: "MGT2.Characteristics.morale.name",
luck: "MGT2.Characteristics.luck.name",
sanity: "MGT2.Characteristics.sanity.name",
charm: "MGT2.Characteristics.charm.name",
psionic: "MGT2.Characteristics.psionic.name",
other: "MGT2.Characteristics.other.name"
});
MGT2.InitiativeCharacteristics = Object.freeze({
dexterity: "MGT2.Characteristics.dexterity.name",
intellect: "MGT2.Characteristics.intellect.name"
});
MGT2.DamageCharacteristics = Object.freeze({
strength: "MGT2.Characteristics.strength.name",
dexterity: "MGT2.Characteristics.dexterity.name",
endurance: "MGT2.Characteristics.endurance.name"
});
MGT2.TL = Object.freeze({
NA: "MGT2.TL.NA",
Unknow: "MGT2.TL.Unknow",
NotIdentified: "MGT2.TL.NotIdentified",
TL00: "MGT2.TL.L00",
TL01: "MGT2.TL.L01",
TL02: "MGT2.TL.L02",
TL03: "MGT2.TL.L03",
TL04: "MGT2.TL.L04",
TL05: "MGT2.TL.L05",
TL06: "MGT2.TL.L06",
TL07: "MGT2.TL.L07",
TL08: "MGT2.TL.L08",
TL09: "MGT2.TL.L09",
TL10: "MGT2.TL.L10",
TL11: "MGT2.TL.L11",
TL12: "MGT2.TL.L12",
TL13: "MGT2.TL.L13",
TL14: "MGT2.TL.L14",
TL15: "MGT2.TL.L15"
});
MGT2.Timeframes = Object.freeze({
Normal: "MGT2.Timeframes.Normal",
Slower: "MGT2.Timeframes.Slower",
Faster: "MGT2.Timeframes.Faster"
});
MGT2.SpeedBands = Object.freeze({
Stoppped: "MGT2.SpeedBands.Stoppped",
Idle: "MGT2.SpeedBands.Idle",
VerySlow: "MGT2.SpeedBands.VerySlow",
Slow: "MGT2.SpeedBands.Slow",
Medium: "MGT2.SpeedBands.Medium",
High: "MGT2.SpeedBands.High",
Fast: "MGT2.SpeedBands.Fast",
VeryFast: "MGT2.SpeedBands.VeryFast",
Subsonic: "MGT2.SpeedBands.Subsonic",
Hypersonic: "MGT2.SpeedBands.Hypersonic"
});
MGT2.Durations = Object.freeze({
Seconds: "MGT2.Durations.Seconds",
Minutes: "MGT2.Durations.Minutes",
Heures: "MGT2.Durations.Heures"
});
MGT2.CreatureBehaviorType = Object.freeze({
herbivore: "MGT2.CreatureBehaviorType.herbivore",
carnivore: "MGT2.CreatureBehaviorType.carnivore",
charognard: "MGT2.CreatureBehaviorType.charognard",
omnivore: "MGT2.CreatureBehaviorType.omnivore"
});
MGT2.CreatureBehaviorSubType = Object.freeze({
accumulateur: "MGT2.CreatureBehaviorSubType.accumulateur",
brouteur: "MGT2.CreatureBehaviorSubType.brouteur",
filtreur: "MGT2.CreatureBehaviorSubType.filtreur",
intermittent: "MGT2.CreatureBehaviorSubType.intermittent",
chasseur: "MGT2.CreatureBehaviorSubType.chasseur",
detourneur: "MGT2.CreatureBehaviorSubType.detourneux",
guetteur: "MGT2.CreatureBehaviorSubType.guetteur",
mangeur: "MGT2.CreatureBehaviorSubType.mangeur",
piegeur: "MGT2.CreatureBehaviorSubType.piegeur",
intimidateur: "MGT2.CreatureBehaviorSubType.intimidateur",
necrophage: "MGT2.CreatureBehaviorSubType.necrophage",
reducteur: "MGT2.CreatureBehaviorSubType.reducteur",
opportuniste: "MGT2.CreatureBehaviorSubType.opportuniste"
});
MGT2.HealingType = Object.freeze({
FIRST_AID: "MGT2.Healing.FirstAid",
SURGERY: "MGT2.Healing.Surgery",
MEDICAL_CARE: "MGT2.Healing.MedicalCare",
NATURAL_HEALING: "MGT2.Healing.NaturalHealing"
});

1
src/module/constants.js Normal file
View File

@@ -0,0 +1 @@
export const ATTRIBUTE_TYPES = ["String", "Number", "Boolean", "Formula", "Resource"];

133
src/module/core.js Normal file
View File

@@ -0,0 +1,133 @@
import {
CharacterData,
VehiculeData,
CreatureData,
ItemData,
EquipmentData,
DiseaseData,
CareerData,
TalentData,
ContactData,
ArmorData,
ComputerData,
WeaponData,
ItemContainerData,
SpeciesData
} from "./models/index.mjs";
import { MGT2 } from "./config.js";
import { TravellerActor, MGT2Combatant } from "./actors/actor.js";
import { TravellerItem } from "./item.js";
import { TravellerItemSheet, TravellerCharacterSheet, TravellerVehiculeSheet, TravellerCreatureSheet } from "./applications/sheets/_module.mjs";
import { preloadHandlebarsTemplates } from "./templates.js";
//import { MGT2Helper } from "./helper.js";
import {ChatHelper} from "./chatHelper.js";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
import { registerSettings } from "./settings.js";
function registerHandlebarsHelpers() {
Handlebars.registerHelper('showDM', function (dm) {
if (dm === 0) return "0";
if (dm > 0) return `+${dm}`;
if (dm < 0) return `${dm}`;
return "";
});
}
Hooks.once("init", async function () {
CONFIG.MGT2 = MGT2;
CONFIG.Combat.initiative = {
formula: "2d6 + @initiative",
decimals: 2
};
CONFIG.Actor.trackableAttributes = {
character: {
bar: ["life",
"characteristics.strength",
"characteristics.dexterity",
"characteristics.endurance",
"characteristics.intellect",
"characteristics.education",
"characteristics.social",
"characteristics.morale",
"characteristics.luck",
"characteristics.sanity",
"characteristics.charm",
"characteristics.psionic",
"characteristics.other"
],
value: ["life.value",
"health.radiations",
"characteristics.strength.value",
"characteristics.dexterity.value",
"characteristics.endurance.value",
"characteristics.intellect.value",
"characteristics.education.value",
"characteristics.social.value",
"characteristics.morale.value",
"characteristics.luck.value",
"characteristics.sanity.value",
"characteristics.charm.value",
"characteristics.psionic.value",
"characteristics.other.value"]
},
creature: {
bar: ["life"],
value: ["life.value", "life.max", "speed", "armor", "psi"]
}
};
game.mgt2 = {
TravellerActor,
TravellerItem
};
registerHandlebarsHelpers();
registerSettings();
CONFIG.Combatant.documentClass = MGT2Combatant;
CONFIG.Actor.documentClass = TravellerActor;
CONFIG.Item.documentClass = TravellerItem;
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("mgt2", TravellerCharacterSheet, { types: ["character"], makeDefault: true, label: "Traveller Sheet" });
foundry.documents.collections.Actors.registerSheet("mgt2", TravellerVehiculeSheet, { types: ["vehicule"], makeDefault: true, label: "Vehicule Sheet" });
foundry.documents.collections.Actors.registerSheet("mgt2", TravellerCreatureSheet, { types: ["creature"], makeDefault: true, label: "Creature Sheet" });
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("mgt2", TravellerItemSheet, { makeDefault: true });
Object.assign(CONFIG.Actor.dataModels, {
"character": CharacterData,
"vehicule": VehiculeData,
"creature": CreatureData
});
Object.assign(CONFIG.Item.dataModels, {
"item": ItemData,
"equipment": EquipmentData,
"disease": DiseaseData,
"career": CareerData,
"talent": TalentData,
"contact": ContactData,
"weapon": WeaponData,
"computer": ComputerData,
"armor": ArmorData,
"container": ItemContainerData,
"species": SpeciesData
});
Hooks.on("renderChatMessageHTML", (message, element, messageData) => {
ChatHelper.setupCardListeners(message, element, messageData);
});
// Preload template partials
await preloadHandlebarsTemplates();
});
export { MGT2 };

221
src/module/helper.js Normal file
View File

@@ -0,0 +1,221 @@
export class MGT2Helper {
static POUNDS_CONVERT = 2.20462262185;
static decimalSeparator;
static badDecimalSeparator;
static {
this.decimalSeparator = Number(1.1).toLocaleString().charAt(1);
this.badDecimalSeparator = (this.decimalSeparator === "." ? "," : ".");
}
static format = function() {
var s = arguments[0];
for (var i = 0; i < arguments.length - 1; i++) {
var reg = new RegExp("\\{" + i + "\\}", "gm");
s = s.replace(reg, arguments[i + 1]);
}
return s;
}
static hasValue(object, property) {
return object !== undefined && object.hasOwnProperty(property) && object[property] !== null && object[property] !== undefined && object[property] !== "";
}
static getItemsWeight(items) {
let weight = 0;
for (let i of items) {
let item = i.hasOwnProperty("system") ? i.system : i;
if (item.hasOwnProperty("weightless") && item.weightless === true) {
continue;
}
if (item.hasOwnProperty("weight")) {
let itemQty = item.quantity
if (!isNaN(itemQty) && itemQty > 0) {
let itemWeight = item.weight;
if (itemWeight > 0) {
weight += itemWeight * itemQty;
}
}
}
}
return weight;
}
static generateUID() {
let result = '';
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 36; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
result += characters.charAt(randomIndex);
if (i === 8 || i === 12 || i === 16 || i === 20)
result += "-";
}
return result;
}
static compareByName(a, b) {
if (!a.hasOwnProperty("name") || !b.hasOwnProperty("name")) {
return 0;
}
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}
static getDisplayDM(dm) {
if (dm === 0) return " (0)";
if (dm > 0) return ` (+${dm})`;
if (dm < 0) return ` (${dm})`;
return "";
}
static getFormulaDM(dm) {
if (dm === 0) return "+0";
if (dm > 0) return `+${dm}`;
if (dm < 0) return `${dm}`;
return "";
}
static getDiceResults(roll) {
const results = [];
for (const die of roll.dice) {
results.push(die.results);
}
return results.flat(2);
}
static getDiceTotal(roll) {
let total = 0;
for (const die of roll.dice) {
total += die.total;
}
return total;
}
static getDifficultyValue(difficulty) {
switch(difficulty) {
case "Simple": return 2;
case "Easy": return 4;
case "Routine": return 6;
case "Average": return 8;
case "Difficult": return 10;
case "VeryDifficult": return 12;
case "Formidable": return 14;
case "Impossible": return 16;
default:
return 0;
}
}
static getDifficultyDisplay(difficulty) {
const key = `MGT2.Difficulty.${difficulty}`;
const label = game.i18n.localize(key);
return label !== key ? label : null;
}
static getRangeDisplay(range) {
let value = Number(range.value);
if (isNaN(value)) return null;
let label;
//if (game.settings.get("mgt2", "useDistanceMetric") === true) {
if (range.unit !== null && range.unit !== undefined && range.unit !== "")
label = game.i18n.localize(`MGT2.MetricRange.${range.unit}`).toLowerCase();
else
label = "";
//} else {
// TODO
//}
return `${value}${label}`;
}
static getWeightLabel() {
//const label = game.settings.get("mgt2", "useWeightMetric") === true ? "MGT2.MetricSystem.Weight.kg" : "MGT2.ImperialSystem.Weight.lb";
//return game.i18n.localize(label);
return game.i18n.localize("MGT2.MetricSystem.Weight.kg");
}
static getDistanceLabel() {
//const label = game.settings.get("mgt2", "useDistanceMetric") === true ? "MGT2.MetricSystem.Distance.km" : "MGT2.ImperialSystem.Distance.mi";
//return game.i18n.localize(label);
return game.i18n.localize("MGT2.MetricSystem.Distance.km");
}
static getIntegerFromInput(data) {
return Math.trunc(this.getNumberFromInput(data));
}
static getNumberFromInput(data) {
if (data === undefined || data === null) return 0;
if (typeof data === "string") {
let converted = Number(data.replace(/\s+/g, '').replace(this.badDecimalSeparator, this.decimalSeparator).trim());
if (isNaN(converted))
return 0;
return converted;
}
let converted = Number(data);
if (isNaN(converted))
return 0;
return converted;
}
static convertWeightForDisplay(weight) {
//if (game.settings.get("mgt2", "useWeightMetric") === true || weight === 0)
return weight;
// Metric to Imperial
//const pounds = weight * this.POUNDS_CONVERT;
//return Math.round(pounds * 10) / 10;
}
static convertWeightFromInput(weight) {
//if (game.settings.get("mgt2", "useWeightMetric") === true || weight === 0)
return Math.round(weight * 10) / 10;
// Imperial to Metric
//const kg = this.POUNDS_CONVERT / weight;
//return Math.round(kg * 10) / 10;
}
static getDataFromDropEvent(event) {
let data;
try {
return JSON.parse(event.dataTransfer?.getData("text/plain"));
} catch (err) {
return false;
}
//if ( data.type !== "Item" ) return false;
//const item = await Item.implementation.fromDropData(data);
}
static async getItemDataFromDropData(dropData) {
//console.log("getItemDataFromDropData");
let item;
if (game.modules.get("monks-enhanced-journal")?.active && dropData.itemId && dropData.uuid.includes("JournalEntry")) {
const journalEntry = await fromUuid(dropData.uuid);
} else if (dropData.hasOwnProperty("uuid")) {
item = await fromUuid(dropData.uuid);
} else {
let uuid = `${dropData.type}.${dropData.data._id}`;
item = await fromUuid(uuid);
}
if (!item) {
throw new Error(game.i18n.localize("Errors.CouldNotFindItem").replace("_ITEM_ID_", dropData.uuid));
}
if (item.pack) {
const pack = game.packs.get(item.pack);
item = await pack?.getDocument(item._id);
}
return deepClone(item);
}
}

402
src/module/item-sheet.js Normal file
View File

@@ -0,0 +1,402 @@
import { MGT2Helper } from "./helper.js";
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class TravellerItemSheet extends ItemSheet {
/** @inheritdoc */
static get defaultOptions() {
const options = super.defaultOptions;
return foundry.utils.mergeObject(options, {
classes: ["mgt2", game.settings.get("mgt2", "theme"), "sheet"],
width: 630,
tabs: [{ navSelector: ".horizontal-tabs", contentSelector: ".itemsheet-panel", initial: "tab1" }]
});
}
/* -------------------------------------------- */
get template() {
const path = "systems/mgt2/templates/items";
return `${path}/${this.item.type}-sheet.html`;
}
/** @inheritdoc */
async getData(options) {
const context = await super.getData(options);
//console.log('-=getData=-');
//console.log(context);
const item = context.item;
const source = item.toObject();
context.config = CONFIG.MGT2;
const settings = {};
settings.usePronouns = game.settings.get("mgt2", "usePronouns");
let containers = null;
let computers = null;;
let hadContainer;
if (context.item.actor != null) {
hadContainer = true;
containers = [{ "name": "", "_id": "" }].concat(context.item.actor.getContainers());
computers = [{ "name": "", "_id": "" }].concat(context.item.actor.getComputers());
} else {
hadContainer = false;
}
let weight = null;
if (item.system.hasOwnProperty("weight")) {
weight = MGT2Helper.convertWeightForDisplay(item.system.weight);
}
let unitlabels = {
weight: MGT2Helper.getWeightLabel()
};
let skills = [];
if (this.actor !== null) {
for (let item of this.actor.items) {
if (item.type === "talent") {
if (item.system.subType === "skill")
skills.push({ _id: item._id, name: item.getRollDisplay() });
}
}
}
skills.sort(MGT2Helper.compareByName);
skills = [{ _id: "NP", name: game.i18n.localize("MGT2.Items.NotProficient") }].concat(skills);
foundry.utils.mergeObject(context, {
source: source.system,
system: item.system,
settings: settings,
containers: containers,
computers: computers,
hadContainer: hadContainer,
weight: weight,
unitlabels: unitlabels,
editable: this.isEditable,
isGM: game.user.isGM,
skills: skills,
config: CONFIG
//rollData: this.item.getRollData(),
});
return context;
}
/* -------------------------------------------- */
/** @inheritdoc */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.isEditable) return;
//let handler = ev => this._onDropCustom(ev);
//console.log(html);
// itemsheet-panel
//html.addEventListener("dragstart", this._onDropCustom, false);
html.find('div.itemsheet-panel').each((i, li) => {
// //if (li.classList.contains("inventory-header")) return;
//li.setAttribute("draggable", true);
//li.addEventListener("drop", handler, false);
});
//html.find('div.dropitem').each((i, li) => {
// //if (li.classList.contains("inventory-header")) return;
// li.setAttribute("draggable", true);
// li.addEventListener("dragstart", handler, false);
//});
// if (this.item.type == "weapon") {
// html.find('.trait-create').click(this._onTraitCreate.bind(this));
// html.find('.trait-delete').click(this._onTraitDelete.bind(this));
// }
if (this.item.type == "career") {
html.find('.event-create').click(this._onCareerEventCreate.bind(this));
html.find('.event-delete').click(this._onCareerEventDelete.bind(this));
}
else if (this.item.type == "armor" ||
this.item.type == "computer" ||
this.item.type == "species" ||
this.item.type == "weapon") {
html.find('.options-create').click(this._onOptionCreate.bind(this));
html.find('.options-delete').click(this._onOptionDelete.bind(this));
}
if (this.item.type == "species") {
html.find('.modifiers-create').click(this._onModifierEventCreate.bind(this));
html.find('.modifiers-delete').click(this._onModifierEventDelete.bind(this));
}
}
async _onModifierEventCreate(event) {
event.preventDefault();
await this._onSubmit(event);
let modifiers = this.item.system.modifiers;
let index;
if (modifiers.length === 0) {
modifiers = {};
modifiers["0"] = { characteristic: "Endurance", value: null };
} else {
index = Math.max(...Object.keys(modifiers));
index++;
modifiers[index] = { characteristic: "Endurance", value: null };
}
let update = {
system: {
modifiers: modifiers
}
};
return this.item.update(update);
}
async _onModifierEventDelete(event) {
event.preventDefault();
await this._onSubmit(event);
const element = event.currentTarget.closest(".modifiers-part");
const modifiers = foundry.utils.deepClone(this.item.system.modifiers);
let index = Number(element.dataset.modifiersPart);
const newModifiers = [];
let entries = Object.entries(modifiers);
if (entries.length > 1) {
for (const [key, value] of entries) {
if (key != index)
newModifiers.push(value);
}
}
let update = {
system: {
modifiers: newModifiers
}
};
return this.item.update(update);
}
async _onCareerEventCreate(event) {
event.preventDefault();
await this._onSubmit(event);
let events = this.item.system.events;
let index;
if (events.length === 0) {
events = {};
events["0"] = { age: "", description: "" };
} else {
index = Math.max(...Object.keys(events));
index++;
events[index] = { age: "", description: "" };
}
let update = {
system: {
events: events
}
};
return this.item.update(update);
}
async _onCareerEventDelete(event) {
event.preventDefault();
await this._onSubmit(event);
const element = event.currentTarget.closest(".events-part");
const events = foundry.utils.deepClone(this.item.system.events);
let index = Number(element.dataset.eventsPart);
const newEvents = [];
let entries = Object.entries(events);
if (entries.length > 1) {
for (const [key, value] of entries) {
if (key != index)
newEvents.push(value);
}
}
let update = {
system: {
events: newEvents
}
};
return this.item.update(update);
}
async _onOptionCreate(event) {
event.preventDefault();
await this._onSubmit(event);
//const subType = event.currentTarget.dataset.subType;
const property = event.currentTarget.dataset.property;
//let options = this.item.system[subType][property];
let options = this.item.system[property];
let index;
if (options.length === 0) {
options = {};
options["0"] = { name: "", description: "" };
} else {
index = Math.max(...Object.keys(options));
index++;
options[index] = { name: "", description: "" };
}
let update = {};
//update[`system.${subType}.${property}`] = options;
update[`system.${property}`] = options;
return this.item.update(update);
}
async _onOptionDelete(event) {
event.preventDefault();
await this._onSubmit(event);
const element = event.currentTarget.closest(".options-part");
//const subType = element.dataset.subType;
const property = element.dataset.property;
//const options = foundry.utils.deepClone(this.item.system[subType][property]);
const options = foundry.utils.deepClone(this.item.system[property]);
let index = Number(element.dataset.optionsPart);
const newOptions = [];
let entries = Object.entries(options);
if (entries.length > 1) {
for (const [key, value] of entries) {
if (key != index)
newOptions.push(value);
}
}
let update = {};
//update[`system.${subType}.${property}`] = newOptions;
update[`system.${property}`] = newOptions;
return this.item.update(update);
}
// async _onTraitCreate(event) {
// event.preventDefault();
// await this._onSubmit(event);
// const traits = this.item.system.traits;
// return this.item.update({ "system.traits.parts": traits.parts.concat([["", ""]]) });
// }
// async _onTraitDelete(event) {
// event.preventDefault();
// await this._onSubmit(event);
// const element = event.currentTarget.closest(".traits-part");
// const traits = foundry.utils.deepClone(this.item.system.traits);
// traits.parts.splice(Number(element.dataset.traitsPart), 1);
// return this.item.update({ "system.traits.parts": traits.parts });
// }
_getSubmitData(updateData = {}) {
const formData = foundry.utils.expandObject(super._getSubmitData(updateData));
// Gestion des containers
if (formData.hasOwnProperty("system") && formData.system.hasOwnProperty("container") &&
(this.item.system.hasOwnProperty("equipped"))) {
//*console.log('-=_getSubmitData=-');
//console.log(this.item.system.onHand);
//console.log(formData.system.onHand);
//const onHandChange = this.item.system.onHand !== formData.system.onHand;
const equippedChange = this.item.system.equipped !== formData.system.equipped;
const containerChange = this.item.system.container.id !== formData.system.container.id;
// Maintenant équipé
if (equippedChange) {
if (formData.system.equipped === true) {
//formData.system.onHand = true;
//console.log("clear container");
formData.system.container = {
//inContainer: false,
id: ""
};
}
}
/*else if (onHandChange) {
// Maintenant à portée
if (formData.system.onHand === true) {
//console.log("clear container");
formData.system.container = {
inContainer: false,
id: ""
};
} else {
formData.system.equipped = false;
}
}*/
else if (containerChange) {
// Mise en storage
if (formData.system.container.id !== "" && (this.item.system.container.id === "" || this.item.system.container.id === null)) {
//console.log("put in container");
//formData.system.onHand = false;
formData.system.equipped = false;
//formData.system.container.inContainer = true;
}
}
}
// if (this.item.type == "weapon") {
// const traits = formData.system?.traits;
// if (traits)
// traits.parts = Object.values(traits?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
// }
// else if (this.item.type == "career") {
// const events = formData.system?.events;
// if (events)
// events.parts = Object.values(events?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
// }
// else if (this.item.type == "equipment") {
// if (this.item.system.subType == "armor") {
// // const armor = formData.system?.armor;
// // if (armor)
// // //options.parts = Object.values(options?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
// // console.log(armor.options);
// // armor.options = Object.values(armor?.options || {})
// // .map(d => [d.name || "", d.description || ""]);
// // console.log(armor.options);
// } else if (this.item.system.subType == "computer") {
// const computer = formData.system?.computer;
// if (computer)
// //options.parts = Object.values(options?.parts || {}).map(d => [d[0] || "", d[1] || ""]);
// computer.options = Object.values(computer?.options || {}).map(d => [d[0] || "", d[1] || ""]);
// }
// }
if (formData.hasOwnProperty("weight")) {
formData.system.weight = MGT2Helper.convertWeightFromInput(formData.weight);
delete formData.weight;
}
if (formData.system.hasOwnProperty("quantity")) {
formData.system.quantity = MGT2Helper.getIntegerFromInput(formData.system.quantity);
}
if (formData.system.hasOwnProperty("cost")) {
formData.system.cost = MGT2Helper.getIntegerFromInput(formData.system.cost);
}
//console.log("before flatten");
//console.log(formData);
//console.log("after flatten");
// let x = foundry.utils.flattenObject(formData);;
// console.log(x);
// return x;
return foundry.utils.flattenObject(formData);
}
}

61
src/module/item.js Normal file
View File

@@ -0,0 +1,61 @@
export class TravellerItem extends Item {
/** @inheritdoc */
prepareDerivedData() {
super.prepareDerivedData();
}
async _preUpdate(changed, options, user) {
if ((await super._preUpdate(changed, options, user)) === false) return false;
if (this.type === "computer") {
// Overload
const newProcessing = foundry.utils.getProperty(changed, "system.processing") ?? this.system.processing;
if (newProcessing !== this.system.processing) {
let overload = this.system.processingUsed > newProcessing;
foundry.utils.setProperty(changed, "system.overload", overload);
}
}
// Qty max 1
if (this.type === "computer" || this.type === "container" || (this.type === "item" && this.system.subType === "software")) {
const newQty = foundry.utils.getProperty(changed, "system.quantity") ?? this.system.quantity;
if (newQty !== this.system.quantity && newQty > 1) {
foundry.utils.setProperty(changed, "system.quantity", 1);
}
}
// No Weight
if (this.type === "item" && this.system.subType === "software") {
const newWeight = foundry.utils.getProperty(changed, "system.weight") ?? this.system.weight;
if (newWeight !== this.system.weight && newWeight > 0) {
foundry.utils.setProperty(changed, "system.weight", 0);
}
}
}
getRollDisplay() {
if (this.type === "talent") {
if (this.system.subType === "skill") {
let label;
if (this.system.skill.speciality !== "" && this.system.skill.speciality !== undefined) {
label = `${this.name} (${this.system.skill.speciality})`;
} else {
label = this.name;
}
if (this.system.level > 0)
label += ` (+${this.system.level})`;
else if (this.system.level < 0)
label += ` (${this.system.level})`;
return label;
} else if (this.system.subType === "psionic") {
}
}
return name;
}
}

View File

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

View File

@@ -0,0 +1,97 @@
const fields = foundry.data.fields;
export default class CreatureData extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
life: new fields.SchemaField({
value: new fields.NumberField({ required: true, initial: 10, min: 0, integer: true }),
max: new fields.NumberField({ required: true, initial: 10, min: 0, integer: true })
}),
speed: new fields.NumberField({ required: true, initial: 6, min: 0, integer: true }),
armor: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
psi: new fields.NumberField({ required: true, initial: 0, min: 0, integer: true }),
initiativeBonus: new fields.NumberField({ required: true, initial: 0, integer: true }),
skills: new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true, initial: "" }),
level: new fields.NumberField({ required: true, initial: 0, integer: true }),
note: new fields.StringField({ required: false, blank: true, trim: true, initial: "" })
})
),
attacks: new fields.ArrayField(
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: "" })
})
),
traits: new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true, initial: "" }),
value: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
description: new fields.StringField({ required: false, blank: true, trim: true, initial: "" })
})
),
behavior: new fields.SchemaField({
type: new fields.StringField({ required: false, blank: true, trim: true, initial: "" }),
subtype: new fields.StringField({ required: false, blank: true, trim: true, initial: "" })
}),
biography: new fields.HTMLField({ required: false, blank: true, trim: true }),
notes: new fields.HTMLField({ required: false, blank: true, trim: true }),
};
}
/** @override */
prepareDerivedData() {
// Compute initiative bonus from Métabolisme traits
let bonus = 0;
for (const trait of this.traits) {
const nameLower = trait.name.toLowerCase();
if (nameLower.includes("métabolisme rapide") || nameLower.includes("metabolisme rapide")) {
const val = parseInt(trait.value);
if (!isNaN(val)) bonus += val;
} else if (nameLower.includes("métabolisme lent") || nameLower.includes("metabolisme lent")) {
const val = parseInt(trait.value);
if (!isNaN(val)) bonus -= val;
}
}
this.initiativeBonus = bonus;
// Compute armor from Armure trait if not set manually
if (this.armor === 0) {
for (const trait of this.traits) {
if (trait.name.toLowerCase().startsWith("armure")) {
const val = parseInt(trait.value);
if (!isNaN(val)) {
this.armor = val;
break;
}
}
}
}
// Compute PSI from Psionique trait
if (this.psi === 0) {
for (const trait of this.traits) {
if (trait.name.toLowerCase().startsWith("psionique")) {
const val = parseInt(trait.value);
if (!isNaN(val)) {
this.psi = val;
break;
}
}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,53 @@
import { PhysicalItemData } from "./base-item.mjs";
const fields = foundry.data.fields;
export default class WeaponData extends PhysicalItemData {
static defineSchema() {
const schema = super.defineSchema();
schema.equipped = new fields.BooleanField({ required: false, initial: false });
schema.range = new fields.SchemaField({
isMelee: new fields.BooleanField({ required: false, initial: false }),
value: new fields.NumberField({ required: false, integer: true, nullable: true }),
unit: new fields.StringField({ required: false, blank: true, nullable: true })
});
schema.damage = new fields.StringField({ required: false, blank: true, trim: true });
schema.magazine = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.magazineCost = new fields.NumberField({ required: false, initial: 0, min: 0, integer: true });
schema.traits = new fields.SchemaField({
ap: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true }),
auto: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true }),
blast: new fields.NumberField({ required: false, initial: 0, min: 0, integer: true }),
bulky: new fields.BooleanField({ required: false, initial: false }),
veryBulky: new fields.BooleanField({ required: false, initial: false }),
stun: new fields.BooleanField({ required: false, initial: false }),
smart: new fields.BooleanField({ required: false, initial: false }),
radiation: new fields.BooleanField({ required: false, initial: false }),
scope: new fields.BooleanField({ required: false, initial: false }),
zeroG: new fields.BooleanField({ required: false, initial: false })
});
schema.options = new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({ required: true, blank: true, trim: true }),
description: new fields.StringField({ required: false, blank: true, trim: true })
})
);
return schema;
}
/** Returns a compact display string of active traits (e.g. "AP 2, Auto 3, Blast 5, Bulky") */
static getTraitsSummary(traits) {
if (!traits) return "";
const parts = [];
if (traits.ap > 0) parts.push(`AP ${traits.ap}`);
if (traits.auto > 0) parts.push(`Auto ${traits.auto}`);
if (traits.blast > 0) parts.push(`Blast ${traits.blast}`);
if (traits.bulky) parts.push(game.i18n.localize("MGT2.WeaponTraits.Bulky"));
if (traits.veryBulky) parts.push(game.i18n.localize("MGT2.WeaponTraits.VeryBulky"));
if (traits.stun) parts.push(game.i18n.localize("MGT2.WeaponTraits.Stun"));
if (traits.smart) parts.push(game.i18n.localize("MGT2.WeaponTraits.Smart"));
if (traits.radiation) parts.push(game.i18n.localize("MGT2.WeaponTraits.Radiation"));
if (traits.scope) parts.push(game.i18n.localize("MGT2.WeaponTraits.Scope"));
if (traits.zeroG) parts.push(game.i18n.localize("MGT2.WeaponTraits.ZeroG"));
return parts.join(", ");
}
}

View File

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

84
src/module/roll-prompt.js Normal file
View File

@@ -0,0 +1,84 @@
const { DialogV2 } = foundry.applications.api;
const { renderTemplate } = foundry.applications.handlebars;
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,
// 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,
// Ranged/Melee weapon flags
isRanged: options.isRanged ?? false,
isMelee: options.isMelee ?? false,
isAttack: (options.isRanged ?? false) || (options.isMelee ?? false),
autoLevel: options.autoLevel ?? 0,
hasScope: options.scope ?? false,
hasZeroG: options.zeroG ?? false,
});
return await DialogV2.wait({
window: { title: options.title ?? options.rollTypeName ?? game.i18n.localize("MGT2.RollPrompt.Roll") },
classes: ["mgt2-roll-dialog"],
content: htmlContent,
rejectClose: false,
buttons: [
{
action: "boon",
label: game.i18n.localize("MGT2.RollPrompt.Boon"),
callback: (event, button, dialog) => {
const formData = new FormDataExtended(dialog.element.querySelector('form')).object;
formData.diceModifier = "dl";
return formData;
}
},
{
action: "submit",
label: game.i18n.localize("MGT2.RollPrompt.Roll"),
icon: '<i class="fa-solid fa-dice"></i>',
default: true,
callback: (event, button, dialog) => {
return new FormDataExtended(dialog.element.querySelector('form')).object;
}
},
{
action: "bane",
label: game.i18n.localize("MGT2.RollPrompt.Bane"),
callback: (event, button, dialog) => {
const formData = new FormDataExtended(dialog.element.querySelector('form')).object;
formData.diceModifier = "dh";
return formData;
}
}
]
});
}
}

86
src/module/settings.js Normal file
View File

@@ -0,0 +1,86 @@
export const registerSettings = function () {
game.settings.register("mgt2", "theme", {
name: "MGT2.Settings.theme.name",
hint: "MGT2.Settings.theme.hint",
scope: "client",
config: true,
default: "black-and-red",
type: String,
choices: {
"black-and-red": "MGT2.Themes.BlackAndRed",
"mwamba": "MGT2.Themes.Mwamba",
"blue": "MGT2.Themes.Blue"
},
requiresReload: true
});
game.settings.register('mgt2', 'usePronouns', {
name: "MGT2.Settings.usePronouns.name",
hint: "MGT2.Settings.usePronouns.hint",
default: false,
scope: 'world',
type: Boolean,
config: true,
requiresReload: false
});
game.settings.register('mgt2', 'useGender', {
name: "MGT2.Settings.useGender.name",
hint: "MGT2.Settings.useGender.hint",
default: false,
scope: 'world',
type: Boolean,
config: true,
requiresReload: false
});
game.settings.register('mgt2', 'showLife', {
name: "MGT2.Settings.showLife.name",
hint: "MGT2.Settings.showLife.hint",
default: false,
scope: 'world',
type: Boolean,
config: true,
requiresReload: false
});
// game.settings.register('mgt2', 'useWeightMetric', {
// name: "MGT2.Settings.useWeightMetric.name",
// hint: "MGT2.Settings.useWeightMetric.hint",
// default: true,
// scope: 'world',
// type: Boolean,
// config: true,
// requiresReload: true
// });
// game.settings.register('mgt2', 'useDistanceMetric', {
// name: "MGT2.Settings.useDistanceMetric.name",
// hint: "MGT2.Settings.useDistanceMetric.hint",
// default: true,
// scope: 'world',
// type: Boolean,
// config: true,
// requiresReload: true
// });
// game.settings.register('mgt2', 'showTrash', {
// name: "Show Trash tab to Player",
// hint: "Player can see the Trash tab and recover item",
// default: false,
// scope: 'world',
// type: Boolean,
// config: true,
// requiresReload: false
// });
/*game.settings.register('mgt2', 'containerDropIn', {
name: "Test",
hint: "Mon hint",
default: true,
scope: 'client',
type: Boolean,
config: true
});*/
};

37
src/module/templates.js Normal file
View File

@@ -0,0 +1,37 @@
/**
* Define a set of template paths to pre-load
* Pre-loaded templates are compiled and cached for fast access when rendering
* @return {Promise}
*/
export const preloadHandlebarsTemplates = async function() {
const templatePaths = [
"systems/mgt2/templates/items/armor-sheet.html",
"systems/mgt2/templates/items/career-sheet.html",
"systems/mgt2/templates/items/computer-sheet.html",
"systems/mgt2/templates/items/contact-sheet.html",
"systems/mgt2/templates/items/container-sheet.html",
"systems/mgt2/templates/items/disease-sheet.html",
"systems/mgt2/templates/items/equipment-sheet.html",
"systems/mgt2/templates/items/item-sheet.html",
"systems/mgt2/templates/items/species-sheet.html",
"systems/mgt2/templates/items/talent-sheet.html",
"systems/mgt2/templates/items/weapon-sheet.html",
"systems/mgt2/templates/items/parts/sheet-configuration.html",
"systems/mgt2/templates/items/parts/sheet-physical-item.html",
"systems/mgt2/templates/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",
"systems/mgt2/templates/chat/radiation.html",
"systems/mgt2/templates/actors/actor-config-sheet.html",
"systems/mgt2/templates/actors/actor-config-characteristic-sheet.html",
"systems/mgt2/templates/actors/trait-sheet.html",
"systems/mgt2/templates/actors/creature-sheet.html",
"systems/mgt2/templates/chat/creature-roll.html",
"systems/mgt2/templates/editor-fullview.html"
];
const loader = foundry.applications?.handlebars?.loadTemplates ?? loadTemplates;
return loader(templatePaths);
};

View File

@@ -0,0 +1,292 @@
.characteristics-panel
.tab
padding: 4px
.species
font-size: 13px
margin: 4px 1rem 0 1rem
text-align: justify
a
margin-right: 1rem
.mgt2
.characteristics
flex-basis: 138px
flex-grow: 0
flex-shrink: 0
position: relative
.characteristics-header
color: var(--mgt2-color-primary)
background: var(--mgt2-bgcolor-primary)
font-family: "Rubik", monospace
font-style: normal
font-size: 1rem
line-height: 2rem
text-transform: uppercase
.characteristic-row
display: flex
flex-direction: row
align-items: center
justify-content: center
position: relative
.characteristic-minmax
display: flex
flex-direction: row
align-items: center
justify-content: center
flex-wrap: nowrap
.characteristic-dm
span
&.label
font-size: 0.8rem
font-weight: 600
.characteristic-label
font-family: "Barlow Condensed", sans-serif
font-size: 1.2rem
font-weight: 600
font-style: italic
text-align: center
color: var(--mgt2-color-form)
position: relative
& > a
&.roll
color: black
position: absolute
left: 0
a
&.cfg-characteristic
display: none
font-size: 12px
position: absolute
right: 0
top: 0
&:hover
a
&.cfg-characteristic
display: block
.characteristic-input
color: var(--mgt2-bgcolor-primary)
text-align: center
font-size: 1.5rem
font-weight: 500
width: 2.4rem
height: 2rem
box-sizing: border-box
border: none
outline: none
background: linear-gradient(45deg, #0000 5.66px, #000 0 calc(5.66px + 2px), #0000 0 calc(100% - 5.66px - 2px), #000 0 calc(100% - 5.66px), #0000 0), linear-gradient(-45deg, #0000 5.66px, #000 0 calc(5.66px + 2px), #0000 0 calc(100% - 5.66px - 2px), #000 0 calc(100% - 5.66px), #0000 0), linear-gradient(90deg, #000 4px, #0000 0) -2px 50%/100% calc(100% - 16px) repeat-x, linear-gradient(#000 4px, #0000 0) 50% -2px/calc(100% - 16px) 100% repeat-y
.characteristic-dm
color: var(--mgt2-color-dm)
background-color: transparent
text-align: center
font-size: 1rem
width: 1.5rem
height: 1.4rem
padding: 0
outline: none
border: none
margin: 0
position: absolute
right: 0
background-color: var(--mgt2-bgcolor-dm)
border-radius: 9px
margin-right: 2px
&:focus
outline: none
box-shadow: none
.characteristic-dm-minmax
&:focus
outline: none
box-shadow: none
.minmaxwrapper
display: flex
flex-direction: row
flex-wrap: nowrap
justify-content: center
justify-content: center
align-items: center
margin: 0 0.5rem
box-sizing: border-box
background: linear-gradient(45deg, #0000 7.07px, #000 0 calc(7.07px + 2px), #0000 0 calc(100% - 7.07px - 2px), #000 0 calc(100% - 7.07px), #0000 0), linear-gradient(-45deg, #0000 7.07px, #000 0 calc(7.07px + 2px), #0000 0 calc(100% - 7.07px - 2px), #000 0 calc(100% - 7.07px), #0000 0), linear-gradient(90deg, #000 4px, #0000 0) -2px 50%/100% calc(100% - 20px) repeat-x, linear-gradient(#000 4px, #0000 0) 50% -2px/calc(100% - 20px) 100% repeat-y
input
display: inline-block
color: black
background-color: transparent
text-align: center
font-size: 1.5rem
width: 3rem
height: 2rem
border: none
outline: none
&:focus
outline: none
box-shadow: none
&:first-child
margin-left: 5px
&:last-child
margin-right: 5px
span
font-size: 1.5rem
font-weight: 500
.computer-overload,
.computer-overload i
color: var(--mgt2-color-warning) !important
ul
&.softwares
list-style: none
margin: 0
padding: 0
li
display: inline-block
color: var(--mgt2-color-software)
background-color: var(--mgt2-bgcolor-software)
padding: 3px 7px
border-radius: 8px
a
display: none
&:first-child
margin: 0 0.5rem
&:hover
a
display: inline-block
.character-header
display: flex
margin-top: 8px
margin-right: 8px
flex-direction: row
flex-wrap: nowrap
flex-grow: 0
flex-shrink: 0
justify-content: flex-start
align-items: flex-start
.character-header-img
flex-basis: 138px
flex-grow: 0
flex-shrink: 0
text-align: center
.character-summary
flex: 0 0 100%
margin: 0
padding: 0
list-style: none
border-top: 5px double var(--mgt2-bgcolor-primary)
li
float: left
margin: 0
padding: 0
color: var(--mgt2-color-form)
input
display: block
border: none
font-weight: bold
font-family: "Roboto Condensed", sans-serif
background-color: #fff
color: var(--mgt2-color-form)
font-size: 0.8rem
border: 1px solid #fff
&:hover
border: 1px solid #111
.character-header-body
display: flex
flex-direction: column
flex-wrap: nowrap
justify-content: flex-start
legend
font-size: 0.7rem
text-transform: uppercase
text-wrap: nowrap
color: var(--mgt2-color-form)
i
margin-right: 0.25rem
.character-body
display: flex
flex-direction: row
align-content: flex-start
flex-wrap: nowrap
min-height: 330px
.tab
width: 100%
.lifes
height: 100%
display: flex
flex-direction: row
justify-content: center
align-items: center
div
font-size: 2rem
.character-states
margin: 0
padding: 0
list-style: none
display: flex
flex-direction: column
justify-content: flex-start
align-items: flex-start
width: 100%
float: right
li
display: flex
margin: 0
padding: 0
color: var(--mgt2-color-form)
justify-content: space-between
align-items: center
width: 100%
font-size: 0.7rem
line-height: 1.1rem
.encumbrance-normal
color: var(--mgt2-encumbrance-normal)!important
.encumbrance-heavy
color: var(--mgt2-encumbrance-heavy)!important
font-weight: bold
.character-body
height: 100%
overflow: hidden
display: flex
flex-direction: row
width: 100%
justify-content: flex-start
align-items: flex-start
border-top: 3px solid black
.actor-footer
bottom: 0
color: var(--mgt2-color-primary)
background-color: var(--mgt2-bgcolor-primary)
width: 100%
margin: 0 -8px
height: 1.5rem
justify-content: space-between
align-items: center
padding: 0 1rem
flex-grow: 0
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

@@ -0,0 +1,482 @@
// MGT2 Chat Roll Cards
// Light theme for ALL chat messages - matching character sheet
li.chat-message
background: #ffffff !important
border: 1px solid #ccbbbb !important
box-shadow: 0 2px 8px rgba(0,0,0,0.18) !important
color: #0A0405
padding: 0 !important
.message-header
background: #0A0405
border-bottom: 3px solid #EE4050
padding: 4px 10px
.message-sender
color: #EE4050
font-family: 'Barlow Condensed', sans-serif
font-weight: 700
font-size: 0.75rem
text-transform: uppercase
letter-spacing: 1.5px
margin: 0
.message-timestamp
color: #888888
font-size: 0.68rem
.message-delete i
color: #888888
&:hover .message-delete i
color: #EE4050
// Plain Foundry roll cards (without .mgt2-chat-roll wrapper)
.message-content > .dice-roll
background: #fdf8f8
padding: 6px 12px
.dice-formula
background: #f5eeee
border: 1px solid #ddc8c8
border-radius: 3px
color: #664444
font-size: 0.78rem
padding: 2px 10px
text-align: center
font-family: 'Barlow Condensed', sans-serif
display: inline-block
margin: 0 auto 4px auto
.dice-result
display: flex
flex-direction: column
align-items: center
gap: 3px
h4.dice-total
font-family: 'Barlow Condensed', sans-serif
font-size: 2.2rem !important
font-weight: 900
color: #0A0405 !important
margin: 2px 0
line-height: 1
text-align: center
background: transparent !important
width: 100% !important
display: block !important
.dice-tooltip
.part-header
background: #f5eeee
padding: 2px 8px
display: flex
justify-content: space-between
.part-formula, .part-total
color: #EE4050
font-weight: 700
font-size: 0.78rem
.dice-rolls
display: flex
flex-wrap: wrap
justify-content: center
gap: 4px
padding: 6px 8px
list-style: none
margin: 0
.roll.die
display: flex
align-items: center
justify-content: center
width: 26px
height: 26px
background: #f5eeee
border: 1.5px solid #ddc8c8
border-radius: 4px
color: #3a2020
font-weight: 700
font-size: 0.9rem
// Inner roll card structure
.mgt2-chat-roll
font-family: 'Barlow Condensed', sans-serif
// ── Header: characteristic name + roll type
.mgt2-roll-header
background: #0A0405 !important
border-left: 4px solid #EE4050 !important
padding: 5px 10px 4px 10px !important
.mgt2-roll-char-name
display: block
color: #ffffff
font-size: 1.2rem
font-weight: 800
text-transform: uppercase
letter-spacing: 1px
line-height: 1
.mgt2-roll-meta
display: flex
align-items: center
gap: 6px
margin-top: 2px
.mgt2-roll-type
color: #EE4050
font-size: 0.68rem
font-weight: 700
text-transform: uppercase
letter-spacing: 2px
.mgt2-roll-sep
color: #888888
.mgt2-roll-difficulty
color: #bbbbbb
font-size: 0.68rem
text-transform: uppercase
letter-spacing: 1px
// Modifier line
.mgt2-roll-modifier
background: #f5eeee
border-bottom: 1px solid #e8dada
padding: 3px 14px
color: #664444
font-size: 0.8rem
.mgt2-roll-modifiers
display: flex
gap: 6px
flex-wrap: wrap
background: #f5eeee
border-bottom: 1px solid #e8dada
padding: 3px 14px
.mgt2-roll-mod-tag
color: #664444
font-size: 0.8rem
// Dice block (Foundry .dice-roll structure preserved for tooltip click)
.dice-roll
background: #fdf8f8
padding: 4px 10px
cursor: pointer
.dice-flavor
color: #888
font-size: 0.73rem
text-align: center
margin-bottom: 3px
.dice-result
display: flex
flex-direction: column
align-items: center
gap: 3px
.dice-formula
background: #f5eeee
border: 1px solid #ddc8c8
border-radius: 3px
color: #664444
font-size: 0.78rem
padding: 2px 14px
font-family: 'DM Sans', sans-serif
text-align: center
// Tooltip (individual dice shown on click by Foundry)
.dice-tooltip
width: 100%
.wrapper
background: transparent
.tooltip-part
background: #fdf8f8
border: 1px solid #e8dada
border-radius: 4px
margin-bottom: 6px
overflow: hidden
.part-header
background: #f5eeee
padding: 3px 8px
.part-formula, .part-total
color: #EE4050
font-weight: 700
font-size: 0.8rem
.dice-rolls
display: flex
flex-wrap: wrap
justify-content: center
gap: 5px
padding: 8px 10px
list-style: none
margin: 0
.roll.die
display: flex
align-items: center
justify-content: center
width: 30px
height: 30px
background: #f5eeee
border: 1.5px solid #ddc8c8
border-radius: 5px
color: #3a2020
font-weight: 700
font-size: 1rem
font-family: 'Barlow Condensed', sans-serif
&.max
border-color: #EE4050
color: #EE4050
background: #fde8ea
box-shadow: 0 0 8px rgba(238,64,80,0.2)
&.min
border-color: #ccbbbb
color: #999999
// ── Total: prominent number
h4.dice-total
font-size: 1.6rem !important
font-weight: 900
color: #0A0405 !important
margin: 3px 0 2px 0
line-height: 1
font-family: 'Barlow Condensed', sans-serif
text-align: center
text-shadow: none
background: #f5eeee !important
border: 1px solid #ddc8c8 !important
border-radius: 4px !important
padding: 2px 14px !important
width: auto !important
display: inline-block !important
min-width: 60px
&.success
color: #1a8840 !important
background: rgba(82,232,122,0.1) !important
border-color: rgba(26,136,64,0.35) !important
&.failure
color: #EE4050 !important
background: rgba(238,64,80,0.07) !important
border-color: rgba(238,64,80,0.3) !important
// Outcome badge
.mgt2-outcome
text-align: center
font-size: 0.75rem
font-weight: 700
text-transform: uppercase
letter-spacing: 2px
padding: 3px 10px
i
margin-right: 5px
&.is-success
background: rgba(26,136,64,0.08)
color: #1a8840
border-top: 1px solid rgba(26,136,64,0.2)
&.is-failure
background: rgba(238,64,80,0.07)
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
// AP info badge
.mgt2-ap-info
display: flex
align-items: center
justify-content: center
gap: 5px
font-size: 0.75rem
font-weight: 600
color: #6b4e1a
background: rgba(180,130,40,0.12)
border: 1px solid rgba(180,130,40,0.3)
border-radius: 4px
padding: 3px 8px
margin: 4px 8px 0 8px
i
color: #b48228
// Auto fire mode info badge
.mgt2-auto-info
display: flex
align-items: center
justify-content: center
gap: 5px
font-size: 0.75rem
font-weight: 600
color: #1a3f6b
background: rgba(40,90,180,0.10)
border: 1px solid rgba(40,90,180,0.25)
border-radius: 4px
padding: 3px 8px
margin: 4px 8px 0 8px
i
color: #2855b4
// Blast / explosion area info badge
.mgt2-blast-info
display: flex
align-items: center
justify-content: center
gap: 5px
font-size: 0.75rem
font-weight: 600
color: #7a2a00
background: rgba(210,80,20,0.10)
border: 1px solid rgba(210,80,20,0.30)
border-radius: 4px
padding: 3px 8px
margin: 4px 8px 0 8px
i
color: #d24a10
// Stun / incapacitating weapon info badge
.mgt2-stun-info
display: flex
align-items: center
justify-content: center
gap: 5px
font-size: 0.75rem
font-weight: 600
color: #4a1a6b
background: rgba(120,40,180,0.10)
border: 1px solid rgba(120,40,180,0.28)
border-radius: 4px
padding: 3px 8px
margin: 4px 8px 0 8px
i
color: #8a28c8
// Radiation weapon info badge
.mgt2-radiation-info
display: flex
align-items: center
justify-content: center
gap: 5px
font-size: 0.75rem
font-weight: 600
color: #1a4a1a
background: rgba(40,160,40,0.10)
border: 1px solid rgba(40,160,40,0.30)
border-radius: 4px
padding: 3px 8px
margin: 4px 8px 0 8px
i
color: #28a028
// Radiation roll result card
.mgt2-radiation-card
.mgt2-radiation-label
color: #28a028
font-weight: 700
i
margin-right: 4px
.mgt2-radiation-rules
font-size: 0.72rem
color: #555
background: rgba(40,160,40,0.07)
border: 1px solid rgba(40,160,40,0.20)
border-radius: 4px
padding: 4px 8px
margin: 4px 8px 0 8px
display: flex
gap: 5px
align-items: flex-start
i
color: #e0a020
margin-top: 2px
flex-shrink: 0
// Action buttons
.mgt2-buttons
display: flex
justify-content: center
flex-wrap: wrap
gap: 4px
padding: 5px 10px
background: #f5eeee
border-top: 1px solid #ddc8c8
button
background: #ffffff
border: 1px solid #ccbbbb
color: #3a2020
border-radius: 3px
padding: 4px 14px
font-family: 'Barlow Condensed', sans-serif
font-size: 0.78rem
font-weight: 700
text-transform: uppercase
letter-spacing: 1px
cursor: pointer
transition: background 0.15s ease, box-shadow 0.15s ease
box-shadow: none
i
font-size: 1rem
padding: 0
margin: 0
&:hover
background: #EE4050
border-color: #EE4050
color: #fff
box-shadow: 0 0 8px rgba(238,64,80,0.25)
&:disabled
background: #e8e0e0
border-color: #c8b8b8
color: #a08080
cursor: not-allowed
box-shadow: none
opacity: 0.6

View File

@@ -0,0 +1,225 @@
//
// Creature Sheet Styles
//
.creature-sheet
// Header
.creature-header
display: flex
flex-direction: row
align-items: flex-start
gap: 0.75rem
padding: 0.5rem 0.75rem 0.5rem
background: var(--mgt2-bgcolor-form)
border-bottom: 2px solid var(--mgt2-color-primary)
.creature-header-img
flex: 0 0 80px
img.profile
width: 80px
height: 80px
object-fit: cover
border: 2px solid var(--mgt2-color-primary)
border-radius: 4px
cursor: pointer
.creature-header-body
flex: 1
display: flex
flex-direction: column
gap: 0.4rem
.creature-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)
// Stat boxes
.creature-stats-row
display: flex
flex-direction: row
flex-wrap: wrap
gap: 0.5rem
.creature-stat
display: flex
flex-direction: column
align-items: center
background: var(--mgt2-bgcolor-primary)
border: 1px solid var(--mgt2-color-primary)
border-radius: 4px
padding: 2px 6px
min-width: 4rem
label
font-family: "Barlow Condensed", sans-serif
font-size: 0.7rem
font-weight: 700
text-transform: uppercase
color: var(--mgt2-color-primary)
line-height: 1.2
.creature-stat-value
display: flex
align-items: center
gap: 2px
input
width: 2.5rem
text-align: center
background: transparent
border: none
color: var(--mgt2-bgcolor-form)
font-size: 1rem
font-weight: 700
padding: 0
&:focus
outline: none
border-bottom: 1px solid var(--mgt2-color-primary)
.stat-max
width: 2.5rem
.stat-unit
font-size: 0.7rem
color: var(--mgt2-bgcolor-form)
opacity: 0.7
.stat-readonly
font-size: 1rem
font-weight: 700
color: var(--mgt2-bgcolor-form)
min-width: 2.5rem
text-align: center
// Behavior row
.creature-behavior-row
display: flex
flex-direction: row
align-items: center
gap: 0.5rem
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)
border-radius: 3px
font-size: 0.85rem
padding: 1px 4px
.behavior-sep
flex: 0 0 auto
color: var(--mgt2-color-form)
opacity: 0.5
.creature-size-badge
flex: 0 0 auto
white-space: nowrap
font-size: 0.75rem
font-style: italic
color: var(--mgt2-bgcolor-form)
background: var(--mgt2-bgcolor-primary)
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
&.active
display: block
// Info tab
.creature-info-tab
display: flex
flex-direction: column
gap: 0.5rem
.creature-description
width: 100%
background: var(--mgt2-bgcolor-form)
color: var(--mgt2-color-form)
border: 1px solid var(--mgt2-color-primary)
border-radius: 3px
padding: 4px
resize: vertical
font-size: 0.9rem
// Damage formula
.creature-damage
.damage-formula
font-family: "Barlow Condensed", sans-serif
font-weight: 700
color: var(--mgt2-color-primary)
font-size: 1rem
.trait-name
font-weight: 600
color: var(--mgt2-color-form)
.trait-value
font-style: italic
color: var(--mgt2-color-secondary)
//
// Chat card creature skill roll
//
.mgt2-creature-roll
.mgt2-roll-header
display: flex
flex-direction: row
align-items: center
gap: 0.6rem
.creature-chat-img
width: 36px
height: 36px
object-fit: cover
border: 1px solid var(--mgt2-color-primary)
border-radius: 3px
flex: 0 0 36px
.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

@@ -0,0 +1,275 @@
.mgt2
.dialog-button
color: var(--mgt2-color-primary)
background-color: var(--mgt2-bgcolor-primary) !important
// MGT2 Roll Dialog (DialogV2)
.mgt2-roll-dialog
background: #ffffff !important
border: 1px solid #ccbbbb !important
box-shadow: 0 8px 32px rgba(0,0,0,0.35) !important
border-radius: 6px !important
overflow: hidden !important
.window-header
background: #0A0405 !important
border-bottom: 3px solid #EE4050 !important
padding: 10px 14px !important
position: relative !important
.window-title
color: #ffffff !important
font-family: 'Barlow Condensed', sans-serif !important
font-weight: 700 !important
font-size: 1rem !important
text-transform: uppercase !important
letter-spacing: 2px !important
.window-content
background: #ffffff !important
color: #0A0405 !important
padding: 0 !important
.dialog-content, .standard-form
background: #ffffff !important
padding: 8px 14px 6px !important
// Form group rows
.form-group
display: flex !important
align-items: center !important
gap: 8px !important
margin-bottom: 3px !important
padding: 2px 0 !important
border-bottom: 1px solid #e8e0e0 !important
&:last-child
border-bottom: none !important
label
color: #0A0405 !important
font-family: 'Barlow Condensed', sans-serif !important
font-weight: 700 !important
font-size: 0.72rem !important
text-transform: uppercase !important
letter-spacing: 1.2px !important
flex: 0 0 110px !important
line-height: 1.2 !important
select, input[type="number"], input[type="text"]
flex: 1 !important
background: #ffffff !important
border: 1px solid #ccbbbb !important
color: #0A0405 !important
border-radius: 3px !important
padding: 3px 8px !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.9rem !important
transition: border-color 150ms ease !important
&:focus
border-color: #EE4050 !important
outline: none !important
background: rgba(238,64,80,0.04) !important
option
background: #ffffff !important
color: #0A0405 !important
// Fieldset / ÉTATS section
fieldset, .form-fields
background: #fdf8f8 !important
border: 1px solid #e0c8c8 !important
border-radius: 5px !important
padding: 5px 10px !important
margin-bottom: 4px !important
legend
color: #EE4050 !important
font-family: 'Barlow Condensed', sans-serif !important
font-weight: 700 !important
font-size: 0.72rem !important
text-transform: uppercase !important
letter-spacing: 2px !important
padding: 0 8px !important
background: #ffffff !important
// Checkboxes inside fieldset
.form-group
border-bottom: none !important
margin-bottom: 4px !important
padding: 2px 0 !important
label
color: #3a2020 !important
flex: 1 !important
input[type="checkbox"]
accent-color: #EE4050 !important
width: 14px !important
height: 14px !important
// Read-only state badges
.roll-prompt-states
display: flex !important
gap: 6px !important
flex-wrap: wrap !important
padding: 2px 0 !important
.roll-prompt-state-badge
display: inline-flex !important
align-items: center !important
gap: 4px !important
padding: 2px 8px !important
border-radius: 3px !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.75rem !important
font-weight: 600 !important
text-transform: uppercase !important
letter-spacing: 0.8px !important
background: #ede8e8 !important
border: 1px solid #ccc !important
color: #999 !important
i
font-size: 0.7rem !important
&.is-active
background: #fdf0f0 !important
border-color: #EE4050 !important
color: #EE4050 !important
strong
font-weight: 800 !important
// Footer buttons
.dialog-buttons, .form-footer, footer
background: #f5eeee !important
border-top: 2px solid #EE4050 !important
padding: 7px 14px !important
display: flex !important
gap: 8px !important
justify-content: center !important
button
flex: 1 !important
max-width: 140px !important
background: #ffffff !important
border: 1px solid #ccbbbb !important
color: #3a2020 !important
border-radius: 4px !important
padding: 5px 12px !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.82rem !important
font-weight: 700 !important
text-transform: uppercase !important
letter-spacing: 1.5px !important
cursor: pointer !important
transition: all 150ms ease !important
&:hover
background: #fdf0f0 !important
border-color: #EE4050 !important
color: #EE4050 !important
// Primary action button (DialogV2: data-action="submit", autofocus)
&.default, &[data-action="submit"], &[autofocus]
background: #EE4050 !important
border-color: #EE4050 !important
color: #fff !important
box-shadow: 0 2px 12px rgba(238,64,80,0.3) !important
&:hover
background: #ff5060 !important
box-shadow: 0 4px 18px rgba(238,64,80,0.45) !important
// Ranged modifiers fieldset
.mgt2-ranged-modifiers
margin-top: 4px !important
.mgt2-ranged-checkboxes, .mgt2-ranged-dodge
display: flex !important
flex-wrap: wrap !important
gap: 6px !important
align-items: center !important
border-bottom: none !important
.mgt2-checkbox-tag
display: inline-flex !important
align-items: center !important
gap: 4px !important
padding: 2px 7px !important
border: 1px solid #ddc8c8 !important
border-radius: 3px !important
background: #fdf8f8 !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.73rem !important
font-weight: 600 !important
text-transform: uppercase !important
letter-spacing: 0.8px !important
color: #3a2020 !important
cursor: pointer !important
transition: border-color 0.15s, background 0.15s !important
em
font-style: normal !important
color: #999 !important
font-weight: 400 !important
input[type="checkbox"]
accent-color: #EE4050 !important
width: 13px !important
height: 13px !important
margin: 0 !important
&:has(input:checked)
border-color: #EE4050 !important
background: #fdf0f0 !important
color: #EE4050 !important
em
color: #EE4050 !important
// Auto fire mode selector
.mgt2-auto-mode
border-bottom: 1px solid #ddc8c8 !important
padding-bottom: 4px !important
margin-bottom: 2px !important
.mgt2-auto-hint
font-size: 0.72rem !important
color: #9a6520 !important
margin: 0 0 4px 0 !important
font-style: italic !important
text-align: center !important
.mgt2-scope-badge
display: inline-flex
align-items: center
gap: 5px
font-size: 0.72rem
font-weight: 600
color: #1a4060
background: rgba(30,100,180,0.10)
border: 1px solid rgba(30,100,180,0.28)
border-radius: 4px
padding: 2px 8px
margin-bottom: 4px
cursor: help
i
color: #1a6090
.mgt2-zerog-badge
display: inline-flex
align-items: center
gap: 5px
font-size: 0.72rem
font-weight: 600
color: #2a3a50
background: rgba(60,80,120,0.10)
border: 1px solid rgba(60,80,120,0.28)
border-radius: 4px
padding: 2px 8px
margin-bottom: 4px
cursor: help
i
color: #506090

View File

@@ -0,0 +1,86 @@
.mgt2
&.sheet
textarea
color: var(--mgt2-input-color)
background-color: var(--mgt2-input-bgcolor)
font-family: "Roboto", sans-serif
font-size: 13px
font-stretch: 100%
input:focus,
textarea:focus,
select:focus
outline: none
box-shadow: none
.checkbox-small
flex: none!important
width: auto!important
height: auto!important
margin: 0!important
.header
color: var(--mgt2-color-primary)
background: var(--mgt2-bgcolor-primary)
font-size: 14px
font-family: "Roboto Condensed", sans-serif
font-weight: bold
padding-left: 5px
margin-bottom: 4px
line-height: 30px
text-transform: uppercase
.field-groups
display: flex
flex-direction: row
flex-wrap: nowrap
align-items: center
justify-content: space-between
.field-group
label
text-transform: uppercase
font-weight: 700
font-size: 14px
font-family: "Roboto Condensed", sans-serif
font-optical-sizing: auto
input
&.field
background-color: var(--mgt2-input-bgcolor)
font-size: 13px
&.field-name
background-color: var(--mgt2-input-bgcolor)
color: var(--mgt2-color-primary)
font-size: 2rem
border: none
font-weight: 700
font-family: "Roboto Condensed", sans-serif
margin-bottom: 0.5rem
padding: 0
&.field-item-name
background-color: var(--mgt2-input-bgcolor)
color: var(--mgt2-color-primary)
height: auto
font-size: 2rem
font-weight: 700
font-family: "Roboto Condensed", sans-serif
.fields
display: flex
.editor
min-height: 3rem
border: 1px solid var(--mgt2-editor-border)
height: 100%
.sheet-body
margin-left: 140px
padding-bottom: 1.5rem
label
&.mgt2-checkbox
display: flex
flex-direction: row
align-items: center
input
margin: 0 0.3rem 0 0

View File

@@ -0,0 +1,459 @@
// MGT2 Item Sheets
// New unified layout: type-bar header (image+name) stats-bar full-width tabs
// Outer window
.itemsheet
display: flex !important
flex-direction: column !important
height: 100% !important
background: #ffffff !important
overflow: hidden !important
// Type bar (replaces vertical sidebar label)
.item-type-bar
background: #0A0405 !important
border-bottom: 3px solid #EE4050 !important
padding: 4px 12px !important
display: flex !important
align-items: center !important
gap: 8px !important
min-height: 0 !important
flex-shrink: 0 !important
.item-type-label
color: #EE4050 !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.65rem !important
font-weight: 700 !important
text-transform: uppercase !important
letter-spacing: 2px !important
// ── Header: image + name
.item-sheet-header
background: #0A0405 !important
padding: 10px 14px !important
display: flex !important
align-items: center !important
gap: 12px !important
flex-shrink: 0 !important
.item-header-img
width: 64px !important
height: 64px !important
object-fit: cover !important
border: 2px solid #EE4050 !important
border-radius: 4px !important
cursor: pointer !important
flex-shrink: 0 !important
.item-header-name
flex: 1 !important
background: transparent !important
border: none !important
border-bottom: 1px solid rgba(238,64,80,0.4) !important
color: #ffffff !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 1.6rem !important
font-weight: 800 !important
text-transform: uppercase !important
letter-spacing: 1px !important
padding: 2px 0 !important
line-height: 1.1 !important
width: 100% !important
&:focus
outline: none !important
border-bottom-color: #EE4050 !important
&::placeholder
color: rgba(255,255,255,0.35) !important
// Stats bar (pills)
.item-stats-bar
background: #f5eeee !important
border-bottom: 1px solid #ddc8c8 !important
padding: 6px 14px !important
display: flex !important
flex-wrap: wrap !important
gap: 6px !important
flex-shrink: 0 !important
.item-stat-pill
display: flex !important
flex-direction: column !important
align-items: center !important
background: #ffffff !important
border: 1px solid #ddc8c8 !important
border-radius: 4px !important
padding: 3px 10px !important
min-width: 56px !important
.stat-label
color: #664444 !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.6rem !important
font-weight: 700 !important
text-transform: uppercase !important
letter-spacing: 1.5px !important
line-height: 1.2 !important
.stat-value
color: #0A0405 !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 1rem !important
font-weight: 800 !important
line-height: 1.1 !important
input, select
color: #0A0405 !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 1rem !important
font-weight: 800 !important
background: transparent !important
border: none !important
text-align: center !important
width: 100% !important
padding: 0 !important
line-height: 1.1 !important
&:focus
outline: none !important
border-bottom: 1px solid #EE4050 !important
.item-stat-pill-checkbox
display: flex !important
flex-direction: row !important
align-items: center !important
gap: 5px !important
background: #ffffff !important
border: 1px solid #ddc8c8 !important
border-radius: 4px !important
padding: 4px 10px !important
input[type="checkbox"]
margin: 0 !important
width: 14px !important
height: 14px !important
accent-color: #EE4050 !important
label
color: #664444 !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.65rem !important
font-weight: 700 !important
text-transform: uppercase !important
letter-spacing: 1px !important
cursor: pointer !important
// Tabs navigation
.itemsheet
.horizontal-tabs.tabs
background: #f5eeee !important
border-bottom: 2px solid #ddc8c8 !important
display: flex !important
flex-wrap: wrap !important
gap: 0 !important
padding: 0 10px !important
flex-shrink: 0 !important
.item.tab-select
color: #664444 !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.72rem !important
font-weight: 700 !important
text-transform: uppercase !important
letter-spacing: 1.5px !important
padding: 6px 14px !important
border: none !important
border-bottom: 2px solid transparent !important
margin-bottom: -2px !important
background: transparent !important
cursor: pointer !important
transition: color 0.15s, border-color 0.15s !important
&:hover
color: #EE4050 !important
&.active
color: #EE4050 !important
border-bottom-color: #EE4050 !important
background: transparent !important
// Tab content area
.itemsheet
.tab-content-area
flex: 1 !important
overflow-y: auto !important
padding: 10px 14px !important
background: #ffffff !important
.tab[data-group]
display: none !important
&.active
display: block !important
// Fields inside item sheets
.itemsheet
.field-group
margin-bottom: 8px !important
label
display: block !important
color: #664444 !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.68rem !important
font-weight: 700 !important
text-transform: uppercase !important
letter-spacing: 1px !important
margin-bottom: 2px !important
.field-groups
display: flex !important
flex-wrap: wrap !important
gap: 10px !important
align-items: flex-start !important
.field-group
flex: 1 !important
min-width: 80px !important
input[type="text"],
input[type="number"],
select,
textarea
background: #fdf8f8 !important
border: 1px solid #ddc8c8 !important
border-radius: 3px !important
color: #0A0405 !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.85rem !important
padding: 4px 8px !important
width: 100% !important
&:focus
outline: none !important
border-color: #EE4050 !important
box-shadow: 0 0 0 2px rgba(238,64,80,0.12) !important
textarea
resize: vertical !important
min-height: 80px !important
.mgt2-checkbox
display: flex !important
align-items: center !important
gap: 6px !important
color: #664444 !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.72rem !important
font-weight: 700 !important
text-transform: uppercase !important
letter-spacing: 1px !important
input[type="checkbox"]
width: 14px !important
height: 14px !important
margin: 0 !important
accent-color: #EE4050 !important
// Tables inside item sheets
.itemsheet
.table-container
border: 1px solid #ddc8c8 !important
border-radius: 4px !important
overflow: hidden !important
margin-top: 8px !important
.table-row
display: flex !important
align-items: stretch !important
border-bottom: 1px solid #e8dada !important
&:last-child
border-bottom: none !important
&.heading
background: #0A0405 !important
color: #EE4050 !important
.row-item
color: #EE4050 !important
font-family: 'Barlow Condensed', sans-serif !important
font-size: 0.68rem !important
font-weight: 700 !important
text-transform: uppercase !important
letter-spacing: 1px !important
padding: 5px 8px !important
&:not(.heading)
background: #ffffff !important
&:nth-child(even)
background: #fdf8f8 !important
.row-item
padding: 4px 6px !important
input, textarea, select
font-size: 0.8rem !important
padding: 2px 6px !important
.row-item
flex: 1 !important
border-right: 1px solid #e8dada !important
&:last-child
border-right: none !important
&.row-item-left
justify-content: flex-start !important
&.row-item-right
flex: 0 0 auto !important
min-width: 36px !important
display: flex !important
align-items: center !important
justify-content: center !important
&.row-item-30
flex: 0 0 30% !important
.item-controls
display: flex !important
align-items: center !important
justify-content: center !important
gap: 6px !important
a
color: #888888 !important
cursor: pointer !important
&:hover
color: #EE4050 !important
// Legacy classes kept for compatibility
// (old structure hidden, new structure used)
.itemsheet-header
display: none !important
.itemsheet-maincol
display: none !important
.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
// Weapon traits structured grid
.mgt2-weapon-traits
border: 1px solid var(--color-border-light-tertiary)
border-radius: 4px
padding: 6px 8px
margin-top: 4px
legend
font-size: 0.85em
font-weight: bold
padding: 0 4px
.mgt2-weapon-traits-grid
display: flex
flex-wrap: wrap
gap: 6px 12px
align-items: center
.mgt2-trait-num
display: flex
align-items: center
gap: 4px
label
font-size: 0.85em
white-space: nowrap
input[type="number"]
width: 44px
text-align: center
flex: 0 0 44px
.mgt2-trait-bool
display: flex
align-items: center
gap: 4px
label
font-size: 0.85em
white-space: nowrap
cursor: pointer
input[type="checkbox"]
cursor: pointer

View File

@@ -0,0 +1,111 @@
// ── Overflow fixes: allow sidebar nav to protrude OUTSIDE the window frame
// All ancestors of nav.sheet-sidebar need overflow:visible so absolute-positioned
// 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.vehicule
overflow: visible !important
> .window-content
overflow: visible !important
.editable, .locked
overflow: visible !important
.mgt2.character .character-body
position: relative !important
overflow: visible !important
.mgt2.creature .creature-body
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
width: 62px !important
flex: none !important
display: flex !important
flex-direction: column
// Rich dark gradient matching MGT2 theme
background: linear-gradient(180deg, #1e0507 0%, #110304 40%, #0a0202 100%)
border-top: 1px solid rgba(238,64,80,0.35)
border-right: 1px solid rgba(238,64,80,0.25)
border-bottom: 1px solid rgba(238,64,80,0.2)
border-left: 3px solid var(--mgt2-color-primary)
border-radius: 0 10px 10px 0
box-shadow: 6px 0 24px rgba(0,0,0,0.75), 0 0 0 0 transparent, inset 1px 0 16px rgba(238,64,80,0.05)
z-index: 10
overflow: hidden !important
padding: 2px 0
& > .item
position: relative
display: flex !important
flex-direction: column !important
justify-content: center !important
align-items: center !important
gap: 4px !important
min-height: 54px
padding: 8px 4px
color: rgba(238,64,80,0.45)
border-bottom: 1px solid rgba(238,64,80,0.07)
cursor: pointer
transition: background 180ms ease, color 180ms ease, box-shadow 180ms ease
user-select: none
text-decoration: none !important
// Left accent bar
&::before
content: ''
position: absolute
left: -3px
top: 18%
bottom: 18%
width: 3px
background: transparent
border-radius: 0 3px 3px 0
transition: background 180ms ease, top 180ms ease, bottom 180ms ease, box-shadow 180ms ease
&:hover
color: var(--mgt2-color-primary)
background: rgba(238,64,80,0.07)
&::before
background: rgba(238,64,80,0.5)
.tab-label
color: rgba(238,64,80,0.7)
&.active
color: var(--mgt2-color-primary)
background: linear-gradient(90deg, rgba(238,64,80,0.16) 0%, rgba(238,64,80,0.03) 100%)
box-shadow: inset 0 1px 0 rgba(238,64,80,0.12), inset 0 -1px 0 rgba(238,64,80,0.08)
&::before
background: var(--mgt2-color-primary)
top: 10%
bottom: 10%
box-shadow: 0 0 10px rgba(238,64,80,0.7), 0 0 20px rgba(238,64,80,0.3)
i
filter: drop-shadow(0 0 5px rgba(238,64,80,0.55))
.tab-label
color: rgba(238,64,80,0.85)
i
font-size: 1.15rem
pointer-events: none
line-height: 1
.tab-label
font-family: 'Barlow Condensed', sans-serif
font-size: 0.52rem
font-weight: 700
text-transform: uppercase
letter-spacing: 0.8px
color: rgba(238,64,80,0.3)
line-height: 1
pointer-events: none
transition: color 180ms ease

View File

@@ -0,0 +1,172 @@
.container-controls
display: inline-block
margin-left: 1rem
a
&:not(:last-child)
margin-right: 0.5rem
.table-container
display: flex
flex-flow: column nowrap
width: 100%
margin: 0 auto
.table-row
display: flex
flex-flow: row nowrap
width: 100%
position: relative
align-items: flex-start
&.heading
background-color: var(--mgt2-bgcolor-primary)
align-items: center
.row-item
text-transform: uppercase
font-size: 12px
&:first-child
font-weight: bold
font-size: 13px
letter-spacing: 3px
i
margin-right: 0.5rem
&.color-1
.row-item
background-color: var(--mgt2-bgcolor-primary)
color: var(--mgt2-color-primary)
&.color-2
.row-item
background-color: var(--mgt2-bgcolor-form)
color: var(--mgt2-bgcolor-primary)
div
&.row-item
padding-left: 5px
&:last-child
padding-right: 5px
&:hover
&:not(.heading)
background-color: var(--mgt2-row-hover)
.table-row-mb-4
margin-bottom: 4px
.row-item
display: flex
flex-grow: 1
font-size: 14px
line-height: 25px
align-items: center
transition: all 0.15s ease-in-out
overflow: hidden !important
text-overflow: ellipsis
text-wrap: nowrap
&.item-controls
justify-content: right
padding-right: 4px
a
&:not(:last-child)
margin-right: 0.4rem
i
color: var(--mgt2-color-form)
a[data-roll]
margin-right: 0.5rem
.heading
&.color-1
.row-item
i
color: var(--mgt2-color-primary) !important
&.color-2
.row-item
i
color: var(--mgt2-bgcolor-primary) !important
.table-subrow
border-left: 2px var(--mgt2-subrow-color) dashed
color: var(--mgt2-subrow-color)
.row-item
font-size: 0.8em
line-height: 20px
&:first-child
padding-left: 1rem
& > i
margin-right: 4px
.row-item-center
justify-content: center
text-align: center
.row-item-left
justify-content: left
.row-item-right
justify-content: right
.row-item-space-between
justify-content: space-between
.row-item-2
flex-basis: 4rem
.row-item-5
flex-basis: 5%
.row-item-10
flex-basis: 10%
.row-item-12
flex-basis: 4rem
.row-item-15
flex-basis: 5rem
.row-item-20
flex-basis: 20%
.row-item-25
flex-basis: 25%
.row-item-30
flex-basis: 30%
.row-item-35
flex-basis: 35%
.row-item-40
flex-basis: 40%
.row-item-45
flex-basis: 45%
.row-item-50
flex-basis: 50%
.row-item-65
flex-basis: 50%
.row-item-85
flex-basis: 50%
.row-item-storage
flex-wrap: wrap
flex-grow: 0
flex-basis: 20%
font-size: 0.7rem
line-height: 0.8rem
.item-control
&.item-equip
i
color: var(--mgt2-row-inactive-icon)
&.active
i
color: var(--mgt2-color-form)
.row-description
flex-basis: 100%
font-size: 14px
padding: 4px 0
justify-content: left
transition: all 0.15s ease-in-out
.row-sub-container
display: flex
flex-flow: column nowrap
flex: 1
.row-item
padding: 8px 0
border-bottom: 1px solid var(--mgt2-bgcolor-primary)
.table-row:last-child,
.row-sub-container .row-item:last-child
border-bottom: 0
.table-container
&.editable
.table-row
margin-top: 4px
.table-container
&.editable
.table-row:last-child
margin-bottom: 4px
.item-options
position: absolute
top: 0.7rem
font-size: 0.7em
left: 1.6rem
text-transform: uppercase
font-family: "DM Sans", sans-serif
font-optical-sizing: auto
font-weight: 600
font-style: normal
color: var(--mgt2-subrow-color)

View File

@@ -0,0 +1,58 @@
.mgt2
nav
&.horizontal-tabs
color: var(--mgt2-color-primary)
background: var(--mgt2-bgcolor-primary)
font-style: normal
font-weight: 700
font-size: 14px
line-height: 30px
text-transform: uppercase
justify-content: space-around
align-items: center
font-family: "Roboto Condensed", sans-serif
a
&.item
position: relative
flex: 1 1 auto
i
margin-right: 0.5rem
& > a
&.item
&::after
content: ""
position: absolute
inset: 0.25rem 0.25rem 0.25rem 0.25rem
border: 1px solid var( --mgt2-color-primary-active)
pointer-events: none
&.active
&::after
border-bottom: none
border-top: 2px solid var( --mgt2-color-primary-active)
border-left: 2px solid var( --mgt2-color-primary-active)
border-right: 2px solid var( --mgt2-color-primary-active)
inset: 0.25rem 0.25rem 0 0.25rem
.active
color: var(--mgt2-color-primary)
text-decoration: none
text-shadow: none
border-bottom: none
.tab[data-tab].fullsize
height: calc(100% - 3rem)
.subTab
flex-flow: column
height: 100%
display: flex
justify-content: flex-start
align-items: stretch
.tab-scroll
overflow-y: auto
height: 100%
.subTabs
height: 100%
flex-direction: column
&.active
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

17
src/sass/mgt2.sass Normal file
View File

@@ -0,0 +1,17 @@
@import 'utils/typography'
@import 'utils/colors'
@import 'utils/global'
@import 'utils/window'
@import 'utils/flex'
@import 'components/_forms'
@import 'components/_dialog'
@import 'components/_character'
@import 'components/_item'
@import 'components/_chat-sidebar'
@import 'components/_tabs'
@import 'components/_tab-sidebar'
@import 'components/_tables'
@import 'components/_creature'
@import 'components/_vehicule'

View File

@@ -0,0 +1,63 @@
$primary-color: #3498db
$secondary-color: #2ecc71
$background-color: #ecf0f1
$text-color: #34495e
.black-and-red
--mgt2-color-form: #0A0405
--mgt2-bgcolor-form: #fff
--mgt2-color-primary: #EE4050
--mgt2-color-primary-active: #AF2F3C
--mgt2-bgcolor-primary: #0A0405
--mgt2-color-primary-light: #4b4a44
--mgt2-color-warning: #EE4050
--mgt2-color-dm: #fff
--mgt2-bgcolor-dm: #0A0405
--mgt2-color-software: #fff
--mgt2-bgcolor-software: #0A0405
--mgt2-input-color: #0A0405
--mgt2-input-bgcolor: #fff
--mgt2-editor-border: #C6C6C6
--mgt2-row-hover: #F2F2F2
--mgt2-subrow-color: #727272
--mgt2-row-inactive-icon: #b5b3a4
--mgt2-encumbrance-normal: #D94826
--mgt2-encumbrance-heavy: #D82727
.mwamba
--mgt2-color-form: #0A0405
--mgt2-bgcolor-form: #fff
--mgt2-color-primary: #2A9932
--mgt2-color-primary-active: #40ED4E
--mgt2-bgcolor-primary: #0A0405
--mgt2-color-primary-light: #4b4a44
--mgt2-color-warning: #EE4050
--mgt2-color-dm: #fff
--mgt2-bgcolor-dm: #0A0405
--mgt2-color-software: #fff
--mgt2-bgcolor-software: #0A0405
--mgt2-input-color: #0A0405
--mgt2-input-bgcolor: #fff
--mgt2-editor-border: #C6C6C6
--mgt2-row-hover: #F2F2F2
--mgt2-subrow-color: #727272
--mgt2-row-inactive-icon: #b5b3a4
.blue
--mgt2-color-form: #0A0405
--mgt2-bgcolor-form: #fff
--mgt2-color-primary: #91AAC8
--mgt2-color-primary-active: #BCDCFF
--mgt2-bgcolor-primary: #0A0405
--mgt2-color-primary-light: #4b4a44
--mgt2-color-warning: #EE4050
--mgt2-color-dm: #fff
--mgt2-bgcolor-dm: #0A0405
--mgt2-color-software: #fff
--mgt2-bgcolor-software: #0A0405
--mgt2-input-color: #0A0405
--mgt2-input-bgcolor: #fff
--mgt2-editor-border: #C6C6C6
--mgt2-row-hover: #F2F2F2
--mgt2-subrow-color: #727272
--mgt2-row-inactive-icon: #b5b3a4

18
src/sass/utils/_flex.sass Normal file
View File

@@ -0,0 +1,18 @@
.mgt2
.flex-fix
flex-grow: 0 !important
flex-shrink: 0 !important
.flex-basis-10
flex-basis: 10%
.flex-basis-20
flex-basis: 20%
.flex-basis-30
flex-basis: 30%
.flex-basis-40
flex-basis: 40%
.flex-basis-50
flex-basis: 50%
.flex-basis-60
flex-basis: 60%
.flex-basis-70
flex-basis: 70%

View File

@@ -0,0 +1,39 @@
.upcase
text-transform: uppercase
.w1-10
width: calc(100% / 10)
.w2-10
width: calc(100% / 10 * 2)
.w3-10
width: calc(100% / 10 * 3)
.w4-10
width: calc(100% / 10 * 4)
.w5-10
width: calc(100% / 10 * 5)
.h100
height: 100%
.w100
width: 100%
.mgt2
a:hover
text-shadow: none
.w-100
width: 100%
.mb-1
margin-bottom: 8px
.mt-1, .mt-05
margin-top: 8px
.mt-2
margin-top: 14px

View File

@@ -0,0 +1,3 @@
@import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap')
@import url('https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300..900;1,300..900&display=swap')
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&display=swap')

View File

@@ -0,0 +1,4 @@
$myFont: Helvetica, sans-serif
$myColor: red
$myFontSize: 18px
$myWidth: 680px

View File

@@ -0,0 +1,19 @@
.mgt2
&.sheet
header
&.window-header
color: var(--mgt2-color-primary)
background-color: var(--mgt2-bgcolor-primary)
h4
&.window-title
font-weight: bold
text-transform: uppercase
&.sheet
.window-content
background: var(--mgt2-bgcolor-form)
color: var(--mgt2-color-form)
padding: 0
.nopad
.window-content
padding: 0

11
src/todo.md Normal file
View File

@@ -0,0 +1,11 @@
# BUGS
# Chose à faire
- Enlever les styles inlines
Actors
- NPC
- Creature
- Container

1026
styles/mgt2.min.css vendored

File diff suppressed because one or more lines are too long

1
styles/mgt2.min.css.map Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,45 +1,106 @@
{
"id": "mgt2",
"version": "0.1.3",
"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.3/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"
},
"gridDistance": 1.5,
"gridUnits": "m",
"primaryTokenAttribute": "life"
"id": "mgt2",
"version": "14.0.0",
"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"
}
],
"documentTypes": {
"Actor": {
"character": {
"htmlFields": [
"personal.speciesText.descriptionLong",
"biography",
"finance.notes",
"notes"
]
},
"vehicule": {
"htmlFields": [
"description",
"notes"
]
},
"creature": {
"htmlFields": [
"biography",
"notes"
]
}
},
"Item": {
"armor": {
"htmlFields": [ "description" ]
},
"career": {
"htmlFields": [ "description" ]
},
"computer": {
"htmlFields": [ "description" ]
},
"contact": {
"htmlFields": [ "description", "notes" ]
},
"container": {
"htmlFields": [ "description", "lockedDescription" ]
},
"disease": {
"htmlFields": [ "description" ]
},
"equipment": {
"htmlFields": [ "description" ]
},
"item": {
"htmlFields": [ "description" ]
},
"species": {
"htmlFields": [ "description", "descriptionLong" ]
},
"talent": {
"htmlFields": [ "description" ]
},
"weapon": {
"htmlFields": [ "description" ]
}
}
},
"compatibility": {
"minimum": "13",
"verified": "14"
},
"grid": {
"distance": 1.5,
"units": "m"
},
"primaryTokenAttribute": "life"
}

View File

@@ -2,11 +2,13 @@
"Actor": {
"types": [
"character",
"vehicule"
"vehicule",
"creature"
],
"htmlFields": ["notes"],
"htmlFields": ["notes", "biography"],
"character": {},
"vehicule": {}
"vehicule": {},
"creature": {}
},
"Item": {
"types": [

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

@@ -1,13 +1,4 @@
<form class="{{cssClass}} flexcol" autocomplete="off" style="align-content: flex-start;align-items: baseline;overflow: hidden;height: 100%;">
<nav class="sheet-sidebar tabs" data-group="sidebar">
<!-- <a class="item tab-select" data-tab="personal" title="Personal"><i class="fa-solid fa-id-card"></i></a> -->
<a class="item tab-select" data-tab="health" title="{{ localize 'MGT2.Actor.Health' }}"><i class="fa-solid fa-heart-pulse"></i></a>
<a class="item tab-select" data-tab="skills" title="{{ localize 'MGT2.Actor.TabSkills' }}"><i class="fa-solid fa-head-side"></i></a>
<a class="item tab-select" data-tab="inventory" title="{{ localize 'MGT2.Actor.Inventory' }}"><i class="fa-solid fa-briefcase-blank"></i></a>
<a class="item tab-select" data-tab="relations" title="{{ localize 'MGT2.Actor.Contacts' }}"><i class="fa-solid fa-users"></i></a>
<a class="item tab-select" data-tab="notes" title="{{ localize 'MGT2.Actor.Notes' }}"><i class="fa-solid fa-books"></i></a>
<a class="item tab-select" data-tab="biography" title="{{ localize 'MGT2.Actor.Biography' }}"><i class="fa-solid fa-book-user"></i></a>
</nav>
<div class="{{cssClass}} flexcol" style="align-content: flex-start;align-items: baseline;overflow: hidden;height: 100%;">
<section class="character-header">
<div class="character-header-img">
<img class="profile" src="{{img}}" data-edit="img" title="{{name}}" height="130" width="100" />
@@ -281,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>
@@ -387,7 +394,7 @@
{{#each skills as |skill id|}}
<div class="table-row" data-item-id="{{skill._id}}" role="rowgroup">
<div class="row-item row-item-30 row-item-left flex-fix">
<a data-roll="skill" data-roll-skill="{{skill._id}}"><i class="fa-solid fa-dice"></i></a>{{skill.name}}{{#if skill.system.skill.speciality}} ({{skill.system.skill.speciality}}){{/if}}
<a class="roll" data-roll="skill" data-roll-skill="{{skill._id}}"><i class="fa-solid fa-dice"></i></a>{{skill.name}}{{#if skill.system.skill.speciality}} ({{skill.system.skill.speciality}}){{/if}}
</div>
<div class="row-item row-item-center">{{skill.system.level}}</div>
<div class="row-item item-controls">
@@ -410,7 +417,7 @@
{{#each psionics as |psionic id|}}
<div class="table-row" data-item-id="{{psionic._id}}" role="rowgroup">
<div class="row-item row-item-left">
<a data-roll="psionic" data-item-id="{{psionic._id}}"><i class="fa-solid fa-dice"></i></a>{{psionic.name}}
<a class="roll" data-roll="psionic" data-item-id="{{psionic._id}}"><i class="fa-solid fa-dice"></i></a>{{psionic.name}}
</div>
<div class="row-item row-item-10 row-item-center flex-fix">{{psionic.system.level}}</div>
<div class="row-item row-item-10 row-item-center flex-fix">{{psionic.system.psionic.cost}}</div>
@@ -446,13 +453,13 @@
{{#each weapons as |weapon id|}}
<div class="table-row{{#if weapon.subInfo}} table-row-mb-4{{/if}} drag-item-list" data-item-id="{{weapon._id}}" role="rowgroup">
<div class="row-item row-item-30 row-item-left flex-3 row-large">
<a data-roll="item" data-item-id="{{weapon._id}}"><i class="fa-solid fa-dice"></i></a>{{weapon.name}}{{#if weapon.subInfo}}<div class="item-options">{{weapon.subInfo}}</div>{{/if}}
<a class="roll" data-roll="item" data-item-id="{{weapon._id}}"><i class="fa-solid fa-dice"></i></a>{{weapon.name}}{{#if weapon.subInfo}}<div class="item-options">{{weapon.subInfo}}</div>{{/if}}
</div>
<div class="row-item row-item-2 row-item-center">{{weapon.range}}</div>
<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>
@@ -471,13 +478,13 @@
{{#each armors as |armor id|}}
<div class="table-row{{#if armor.subInfo}} table-row-mb-4{{/if}} drag-item-list" data-item-id="{{armor._id}}" role="rowgroup">
<div class="row-item row-item-40 row-item-left">
<a data-roll="item" data-item-id="{{armor._id}}"><i class="fa-solid fa-dice"></i></a>{{armor.name}}{{#if armor.subInfo}}<div class="item-options">{{armor.subInfo}}</div>{{/if}}
<a class="roll" data-roll="item" data-item-id="{{armor._id}}"><i class="fa-solid fa-dice"></i></a>{{armor.name}}{{#if armor.subInfo}}<div class="item-options">{{armor.subInfo}}</div>{{/if}}
</div>
<div class="row-item row-item-10 row-item-center">{{armor.system.radiations}}</div>
<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>
@@ -496,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>
@@ -514,14 +521,14 @@
{{#each computers as |computer id|}}
<div class="table-row drop-item-list" data-item-id="{{computer._id}}" role="rowgroup">
<div class="row-item row-item-50 row-item-left">
<a data-roll="item" data-item-id="{{computer._id}}"><i class="fa-solid fa-dice"></i></a>{{computer.name}}{{#if computer.subInfo}}<div class="item-options">{{computer.subInfo}}</div>{{/if}}
<a class="roll" data-roll="item" data-item-id="{{computer._id}}"><i class="fa-solid fa-dice"></i></a>{{computer.name}}{{#if computer.subInfo}}<div class="item-options">{{computer.subInfo}}</div>{{/if}}
</div>
<div class="row-item row-item-2 row-item-center {{overloadClass}}">{{computer.system.processingUsed}}/{{computer.system.processing}}
{{#if computer.system.overload}}<a title="Overload"><i class="fa-solid fa-triangle-exclamation"></i></a>{{/if}}
</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>
@@ -566,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>
@@ -696,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>
@@ -729,20 +736,27 @@
</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">
<p>À FAIRE</p>
</div> -->
{{/if}}
<nav class="sheet-sidebar tabs" data-group="sidebar">
<a class="item tab-select" data-tab="health" title="{{ localize 'MGT2.Actor.Health' }}"><i class="fa-solid fa-heart-pulse"></i><span class="tab-label">SANTÉ</span></a>
<a class="item tab-select" data-tab="skills" title="{{ localize 'MGT2.Actor.TabSkills' }}"><i class="fa-solid fa-head-side"></i><span class="tab-label">COMP.</span></a>
<a class="item tab-select" data-tab="inventory" title="{{ localize 'MGT2.Actor.Inventory' }}"><i class="fa-solid fa-briefcase-blank"></i><span class="tab-label">ÉQUIP.</span></a>
<a class="item tab-select" data-tab="relations" title="{{ localize 'MGT2.Actor.Contacts' }}"><i class="fa-solid fa-users"></i><span class="tab-label">CONT.</span></a>
<a class="item tab-select" data-tab="notes" title="{{ localize 'MGT2.Actor.Notes' }}"><i class="fa-solid fa-books"></i><span class="tab-label">NOTES</span></a>
<a class="item tab-select" data-tab="biography" title="{{ localize 'MGT2.Actor.Biography' }}"><i class="fa-solid fa-book-user"></i><span class="tab-label">BIO</span></a>
</nav>
</div>
<section class="actor-footer">
<div><a name="config" title="Config" style="margin-right: 0.5rem;"><i class="fa-solid fa-gear"></i></a></div>
</section>
</div>
</form>
<section class="actor-footer">
<div><a name="config" title="Config" style="margin-right: 0.5rem;"><i class="fa-solid fa-gear"></i></a></div>
</section>
</div>

View File

@@ -0,0 +1,26 @@
<form class="flexcol" autocomplete="off" style="padding: 0 6px;">
<div class="form-group">
<label>{{ localize 'MGT2.Creature.SkillLabel' }}: <strong>{{skillName}}</strong>
({{ localize 'MGT2.Creature.SkillLevel' }} {{skillLevel}})</label>
</div>
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.CustomDM' }}</label>
<input type="number" name="dm" value="0" />
</div>
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.Difficulty' }}</label>
<select name="difficulty">
{{selectOptions config.Difficulty selected="Average" localize=true}}
</select>
</div>
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.RollMode' }}</label>
<select name="rollMode">
<option value="publicroll">{{ localize 'MGT2.RollPrompt.PublicRoll' }}</option>
<option value="gmroll">{{ localize 'MGT2.RollPrompt.PrivateGMRoll' }}</option>
<option value="blindroll">{{ localize 'MGT2.RollPrompt.BlindGMRoll' }}</option>
<option value="selfroll">{{ localize 'MGT2.RollPrompt.SelfRoll' }}</option>
</select>
</div>
<input type="hidden" name="difficultyLabel" value="" />
</form>

View File

@@ -0,0 +1,273 @@
<div class="{{cssClass}} flexcol creature-sheet">
{{!-- ── HEADER ── --}}
<header class="creature-header">
<div class="creature-header-img">
<img class="profile" src="{{img}}" data-edit="img" title="{{name}}" />
</div>
<div class="creature-header-body">
<input class="creature-name" name="name" type="text" value="{{name}}" placeholder="{{ localize 'MGT2.Creature.Name' }}" />
<div class="creature-stats-row">
{{!-- PdV --}}
<div class="creature-stat">
<label class="upcase">{{ localize 'MGT2.Creature.Life' }}</label>
<div class="creature-stat-value">
<input type="number" name="system.life.value" value="{{system.life.value}}" min="0" class="stat-current" />
<span>/</span>
<input type="number" name="system.life.max" value="{{system.life.max}}" min="0" class="stat-max" />
</div>
</div>
{{!-- Vitesse --}}
<div class="creature-stat">
<label class="upcase">{{ localize 'MGT2.Creature.Speed' }}</label>
<div class="creature-stat-value">
<input type="number" name="system.speed" value="{{system.speed}}" min="0" class="stat-current" />
<span class="stat-unit">m</span>
</div>
</div>
{{!-- Armure --}}
<div class="creature-stat">
<label class="upcase">{{ localize 'MGT2.Creature.Armor' }}</label>
<div class="creature-stat-value">
<input type="number" name="system.armor" value="{{system.armor}}" min="0" class="stat-current" />
</div>
</div>
{{!-- Initiative (calculated) --}}
<div class="creature-stat">
<label class="upcase">{{ localize 'MGT2.Creature.Initiative' }}</label>
<div class="creature-stat-value">
<span class="stat-readonly">{{showDM system.initiativeBonus}}</span>
</div>
</div>
{{!-- PSI (shown only if > 0) --}}
{{#if system.psi}}
<div class="creature-stat">
<label class="upcase">{{ localize 'MGT2.Creature.Psi' }}</label>
<div class="creature-stat-value">
<input type="number" name="system.psi" value="{{system.psi}}" min="0" class="stat-current" />
</div>
</div>
{{/if}}
</div>
{{!-- Comportement --}}
<div class="creature-behavior-row">
<label class="upcase">{{ localize 'MGT2.Creature.Behavior' }}</label>
<select name="system.behavior.type" class="behavior-select">
<option value=""></option>
{{selectOptions config.CreatureBehaviorType selected=system.behavior.type localize=true}}
</select>
<span class="behavior-sep">,</span>
<select name="system.behavior.subtype" class="behavior-select">
<option value=""></option>
{{selectOptions config.CreatureBehaviorSubType selected=system.behavior.subtype localize=true}}
</select>
{{!-- Taille indicative --}}
<span class="creature-size-badge" title="{{ localize 'MGT2.Creature.SizeHint' }}">
{{sizeTraitLabel}} — {{sizeLabel}}
</span>
</div>
</div>
</header>
{{!-- ── TAB CONTENT ── --}}
<div class="creature-body">
{{!-- ── TAB : COMBAT (compétences + attaques + traits) ── --}}
<div class="tab" data-group="primary" data-tab="combat">
{{!-- 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 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">
{{#if ../isEditable}}
<input type="text" name="system.skills.{{i}}.name" value="{{skill.name}}" placeholder="{{ localize 'MGT2.Creature.SkillName' }}" />
{{else}}
<span>{{skill.name}}</span>
{{/if}}
</div>
<div class="row-item row-item-center" style="flex: 1">
{{#if ../isEditable}}
<input type="number" name="system.skills.{{i}}.level" value="{{skill.level}}" min="-3" max="6" class="text-center" />
{{else}}
<span>{{showDM skill.level}}</span>
{{/if}}
</div>
<div class="row-item row-item-left" style="flex: 3">
{{#if ../isEditable}}
<input type="text" name="system.skills.{{i}}.note" value="{{skill.note}}" placeholder="{{ localize 'MGT2.Creature.SkillNote' }}" />
{{else}}
<span class="text-muted">{{skill.note}}</span>
{{/if}}
</div>
<div class="row-item row-item-right item-controls" style="flex: 0 0 3rem">
<a data-action="rollSkill" data-index="{{i}}" title="{{ localize 'MGT2.Creature.RollSkill' }}"><i class="fas fa-dice-d6"></i></a>
{{#if ../isEditable}}
<a data-action="deleteSkill" data-prop="skills" data-index="{{i}}" title="{{ localize 'MGT2.Creature.Delete' }}"><i class="fas fa-trash"></i></a>
{{/if}}
</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>
{{!-- 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 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">
{{#if ../isEditable}}
<input type="text" name="system.attacks.{{i}}.name" value="{{atk.name}}" placeholder="{{ localize 'MGT2.Creature.AttackName' }}" />
{{else}}
<span>{{atk.name}}</span>
{{/if}}
</div>
<div class="row-item row-item-center creature-damage" style="flex: 2">
{{#if ../isEditable}}
<input type="text" name="system.attacks.{{i}}.damage" value="{{atk.damage}}" class="text-center" />
{{else}}
<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}}" />
{{else}}
<span class="text-muted">{{atk.description}}</span>
{{/if}}
</div>
<div class="row-item row-item-right item-controls" style="flex: 0 0 4rem">
<a data-action="rollAttack" data-index="{{i}}" title="{{ localize 'MGT2.Creature.RollAttack' }}"><i class="fas fa-dice-d6 color-primary"></i></a>
{{#if ../isEditable}}
<a data-action="deleteAttack" data-prop="attacks" data-index="{{i}}" title="{{ localize 'MGT2.Creature.Delete' }}"><i class="fas fa-trash"></i></a>
{{/if}}
</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>
{{!-- 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 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">
{{#if ../isEditable}}
<input type="text" name="system.traits.{{i}}.name" value="{{trait.name}}" placeholder="{{ localize 'MGT2.Creature.TraitName' }}" />
{{else}}
<span class="trait-name">{{trait.name}}</span>
{{/if}}
</div>
<div class="row-item row-item-center" style="flex: 1">
{{#if ../isEditable}}
<input type="text" name="system.traits.{{i}}.value" value="{{trait.value}}" class="text-center" />
{{else}}
<span class="trait-value">{{trait.value}}</span>
{{/if}}
</div>
<div class="row-item row-item-left" style="flex: 4">
{{#if ../isEditable}}
<input type="text" name="system.traits.{{i}}.description" value="{{trait.description}}" />
{{else}}
<span class="text-muted">{{trait.description}}</span>
{{/if}}
</div>
<div class="row-item row-item-right item-controls" style="flex: 0 0 3rem">
{{#if ../isEditable}}
<a data-action="deleteTrait" data-prop="traits" data-index="{{i}}" title="{{ localize 'MGT2.Creature.Delete' }}"><i class="fas fa-trash"></i></a>
{{/if}}
</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>{{!-- /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>
{{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>
{{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="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>
</div>{{!-- .creature-body --}}
</div>

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

@@ -0,0 +1,49 @@
<div class="mgt2-chat-roll mgt2-creature-roll">
<div class="mgt2-roll-header">
<img class="creature-chat-img" src="{{creatureImg}}" title="{{creatureName}}" />
<div class="mgt2-roll-header-text">
<span class="mgt2-roll-char-name">{{creatureName}}</span>
<div class="mgt2-roll-meta">
<span class="mgt2-roll-type">{{rollLabel}}</span>
{{#if difficulty}}
<span class="mgt2-roll-sep"></span>
<span class="mgt2-roll-difficulty">{{difficultyLabel}}</span>
{{/if}}
</div>
</div>
</div>
{{#if modifiers}}
<div class="mgt2-roll-modifiers">
{{#each modifiers as |mod|}}
<span class="mgt2-roll-mod-tag">{{mod}}</span>
{{/each}}
</div>
{{/if}}
<div class="dice-roll">
<div class="dice-result">
<div class="dice-formula">{{formula}}</div>
{{{tooltip}}}
<h4 class="dice-total {{#if success}}success{{else if failure}}failure{{/if}}" {{#if rollBreakdown}}data-tooltip="{{rollBreakdown}}"{{/if}}>{{total}}</h4>
</div>
</div>
{{#if success}}
<div class="mgt2-outcome is-success"><i class="fa-solid fa-check"></i> {{ localize 'MGT2.Chat.Roll.Success' }}</div>
{{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}}
{{#if showRollDamage}}
<div class="mgt2-buttons">
<button data-action="rollDamage">{{ localize 'MGT2.Chat.Roll.Damages' }}</button>
</div>
{{/if}}
</div>

View File

@@ -0,0 +1,24 @@
<div class="mgt2-chat-roll mgt2-radiation-card">
<div class="mgt2-roll-header">
<span class="mgt2-roll-char-name">{{rollObjectName}}</span>
<div class="mgt2-roll-meta">
<span class="mgt2-roll-type mgt2-radiation-label">
<i class="fa-solid fa-radiation"></i>
{{ localize 'MGT2.Chat.Radiation.Title' }}
</span>
</div>
</div>
<div class="dice-roll">
<div class="dice-result">
<div class="dice-formula">{{formula}}</div>
{{{tooltip}}}
<h4 class="dice-total">{{total}} {{ localize 'MGT2.Chat.Radiation.Rads' }}</h4>
</div>
</div>
<div class="mgt2-radiation-rules">
<i class="fa-solid fa-triangle-exclamation"></i>
{{ localize 'MGT2.Chat.Radiation.Rules' }}
</div>
</div>

View File

@@ -1,46 +1,103 @@
<div class="roll-info">
<div class="roll-object-name">{{rollObjectName}}</div>
{{#if rollTypeName}}
{{#if rollDifficulty}}
<div class="roll-type-group"><div class="roll-type-name">{{rollTypeName}}</div><div class="roll-type-name">{{ localize rollDifficultyLabel }}</div></div>
{{else}}
<div class="roll-type-name">{{rollTypeName}}</div>
<div class="mgt2-chat-roll">
<div class="mgt2-roll-header">
<span class="mgt2-roll-char-name">{{rollObjectName}}</span>
{{#if rollTypeName}}
<div class="mgt2-roll-meta">
<span class="mgt2-roll-type">{{rollTypeName}}</span>
{{#if rollDifficulty}}
<span class="mgt2-roll-sep"></span>
<span class="mgt2-roll-difficulty">{{rollDifficultyLabel}}</span>
{{/if}}
</div>
{{/if}}
{{/if}}
</div>
{{#if rollMessage}}
<div>{{rollMessage}}</div>
<div class="mgt2-roll-modifier">{{rollMessage}}</div>
{{/if}}
{{#if rollModifiers}}
{{#each rollModifiers as |rollModifier i| }}
<div>{{rollModifier}}</div>
{{/each}}
<div class="mgt2-roll-modifiers">
{{#each rollModifiers as |mod i|}}
<span class="mgt2-roll-mod-tag">{{mod}}</span>
{{/each}}
</div>
{{/if}}
</div>
<div class="dice-roll">
{{#if flavor}}
<div class="dice-flavor">{{flavor}}</div>
<div class="dice-roll">
{{#if flavor}}
<div class="dice-flavor">{{flavor}}</div>
{{/if}}
<div class="dice-result">
<div class="dice-formula">{{formula}}</div>
{{{tooltip}}}
<h4 class="dice-total {{#if rollSuccess}}success{{else if rollFailure}}failure{{/if}}" {{#if rollBreakdown}}data-tooltip="{{rollBreakdown}}"{{/if}}>{{total}}</h4>
</div>
</div>
{{#if apValue}}
<div class="mgt2-ap-info" data-tooltip="{{ localize 'MGT2.Chat.Roll.APIgnoreHint' }}">
<i class="fa-solid fa-shield-halved"></i>
{{ localize 'MGT2.Chat.Roll.APIgnore' }} {{apValue}}
</div>
{{/if}}
<div class="dice-result"><div class="dice-formula">{{formula}}</div>{{{tooltip}}}<h4 class="dice-total">{{total}}</h4></div>
</div>
{{#if rollSuccess}}
<div class="roll-success">{{ localize 'MGT2.Chat.Roll.Success' }}</div>
{{else if rollFailure}}
<div class="roll-success">{{ localize 'MGT2.Chat.Roll.Failure' }}</div>
{{/if}}
{{#if showButtons}}
<div class="mgt2-buttons">
{{#if hasDamage}}
<button data-action="damage" title="{{ localize 'MGT2.Chat.Roll.ApplyDamages' }}"><i class="fa-regular fa-heart-circle-minus"></i></button>
<!-- <button data-action="healing" data-multiplier="1" title="Apply Healing"><i class="fa-regular fa-heart-circle-plus"></i></button> -->
{{#if blastRadius}}
<div class="mgt2-blast-info" data-tooltip="{{ localize 'MGT2.Chat.Roll.BlastHint' }}">
<i class="fa-solid fa-burst"></i>
{{ localize 'MGT2.Chat.Roll.BlastArea' }} {{blastRadius}}m — {{ localize 'MGT2.Chat.Roll.BlastRules' }}
</div>
{{/if}}
{{#if showRollRequest}}
<button data-action="requestRoll" data-roll="characteristic" data-roll-characteristic="strength" title="Roll!"><i class="fa-solid fa-dice"></i></button>
{{#if stunWeapon}}
<div class="mgt2-stun-info" data-tooltip="{{ localize 'MGT2.Chat.Roll.StunHint' }}">
<i class="fa-solid fa-bolt"></i>
{{ localize 'MGT2.Chat.Roll.StunWeapon' }}
</div>
{{/if}}
{{#if showRollDamage}}
<button data-action="rollDamage" title="Roll">{{ localize 'MGT2.Chat.Roll.Damages' }}</button>
{{#if radiationWeapon}}
<div class="mgt2-radiation-info" data-tooltip="{{ localize 'MGT2.Chat.Radiation.Hint' }}">
<i class="fa-solid fa-radiation"></i>
{{ localize 'MGT2.Chat.Radiation.Badge' }}
</div>
{{/if}}
{{#each cardButtons as |cardButton i| }}
<button data-index="{{i}}" title="{{cardButton.label}}">{{cardButton.label}}</button>
{{/each}}
</div>
{{/if}}
{{#if autoInfo}}
<div class="mgt2-auto-info">
<i class="fa-solid fa-gun"></i> {{autoInfo}}
</div>
{{/if}}
{{#if rollSuccess}}
<div class="mgt2-outcome is-success"><i class="fa-solid fa-check"></i> {{ localize 'MGT2.Chat.Roll.Success' }}</div>
{{else if rollFailure}}
<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}}
<button data-action="damage" title="{{ localize 'MGT2.Chat.Roll.ApplyDamages' }}"><i class="fa-regular fa-heart-circle-minus"></i></button>
{{/if}}
{{#if showRollDamage}}
<button data-action="rollDamage">{{ localize 'MGT2.Chat.Roll.Damages' }}</button>
{{/if}}
{{#if radiationWeapon}}
<button data-action="rollRadiation" title="{{ localize 'MGT2.Chat.Radiation.RollButton' }}">
<i class="fa-solid fa-radiation"></i> {{ localize 'MGT2.Chat.Radiation.RollButton' }}
</button>
{{/if}}
{{#each cardButtons as |cardButton|}}
<button data-action="{{cardButton.action}}" title="{{cardButton.label}}">{{cardButton.label}}</button>
{{/each}}
</div>
{{/if}}
</div>

View File

@@ -1,74 +1,71 @@
<form class="{{cssClass}} itemsheet" autocomplete="off">
<div class="itemsheet-header">
<label>{{localize 'MGT2.TYPES.Item.armor'}}</label>
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
<span class="item-type-label"><i class="fas fa-shield-halved"></i> {{localize 'MGT2.TYPES.Item.armor'}}</span>
</div>
<div class="itemsheet-panel">
<div class="itemsheet-maincol">
<img class="profile-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
{{> systems/mgt2/templates/items/parts/sheet-physical-item.html }}
<div class="item-sheet-header">
<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>
<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="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
</nav>
<div class="tab-content-area">
<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="w-100">
<input class="field field-item-name" name="name" type="text" value="{{item.name}}" />
<nav class="horizontal-tabs tabs mt-1" 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="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
</nav>
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
<div class="tab" data-group="primary" data-tab="tab2">
<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-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-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}}
</div>
<div class="tab" data-group="primary" data-tab="tab2">
<div class="field-groups mt-1">
<div class="field-group w3-10">
<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 w3-10">
<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 w3-10">
<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 class="table-container">
<div class="table-row heading">
<div class="row-item row-item-left row-item-30">{{ localize 'MGT2.Items.Options' }}</div>
<div class="row-item row-item-left">Description</div>
<div class="row-item row-item-right"><a class="options-create" data-property="options"><i class="fas fa-plus"></i></a></div>
</div>
<div class="field-groups mt-1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Radiations' }}</label>
<input type="number" name="system.radiations" value="{{system.radiations}}" data-dtype="Number" />
{{#each system.options as |option i| }}
<div class="table-row dropitem options-part" data-options-part="{{i}}" data-property="options" role="rowgroup">
<div class="row-item row-item-left row-item-30"><input type="text" name="system.options.{{i}}.name" value="{{option.name}}" /></div>
<div class="row-item row-item-left">
<textarea name="system.options.{{i}}.description" rows="2">{{option.description}}</textarea>
</div>
<div class="field-group">
<label>{{ localize 'MGT2.Items.Protection' }}</label>
<input type="text" name="system.protection" value="{{system.protection}}" data-dtype="String" />
<div class="row-item row-item-right item-controls">
<a class="item-control options-delete" title="Delete Option"><i class="fas fa-trash"></i></a>
</div>
</div>
<div class="table-container mt-1">
<div class="table-row heading color-2">
<div class="row-item row-item-left row-item-30 flex-fix upcase">{{ localize 'MGT2.Items.Options' }}</div>
<div class="row-item row-item-left">Description</div>
<div class="row-item row-item-right row-item-5 flex-fix"><a class="options-create" data-property="options"><i class="fas fa-plus"></i></a></div>
</div>
{{#each system.options as |option i| }}
<div class="table-row dropitem options-part" data-options-part="{{i}}" data-property="options" role="rowgroup">
<div class="row-item row-item-left row-item-30 flex-fix"><input type="text" name="system.options.{{i}}.name" value="{{option.name}}" /></div>
<div class="row-item row-item-left">
<textarea name="system.options.{{i}}.description" rows="3">{{option.description}}</textarea>
</div>
<div class="row-item row-item-right row-item-5 flex-fix item-controls">
<a class="item-control options-delete" title="Delete Option"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
{{/each}}
</div>
<div class="tab" data-group="primary" data-tab="tab3">
</div>
<div class="tab" data-group="primary" data-tab="tab3">
{{> systems/mgt2/templates/items/parts/sheet-configuration.html }}
</div>
</div>
</div>
</form>
</div>

View File

@@ -1,50 +1,55 @@
<form class="{{cssClass}} itemsheet" autocomplete="off">
<div class="itemsheet-header"><label class="upcase">{{ localize 'MGT2.Items.Career' }}</label></div>
<div class="itemsheet-panel">
<div class="itemsheet-maincol">
<img class="profile-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<div class="field-group mt-1">
<label class="upcase">{{ localize 'MGT2.Items.Terms' }}</label>
<input type="number" name="system.terms" value="{{system.terms}}" data-dtype="Number" />
</div>
<div class="field-group mt-1">
<label class="upcase">{{ localize 'MGT2.Items.Rank' }}</label>
<input type="number" name="system.rank" value="{{system.rank}}" data-dtype="Number" />
</div>
</div>
<div class="w-100">
<input class="field field-item-name" name="name" type="text" value="{{item.name}}" data-dtype="String" />
<div class="field-group mt-1">
<label class="upcase">{{ localize 'MGT2.Items.Assignment' }}</label>
<input type="text" name="system.assignment" value="{{system.assignment}}" data-dtype="String" />
</div>
<nav class="horizontal-tabs tabs mt-1" data-group="primary">
<a class="item tab-select" data-tab="tab1">{{ localize 'MGT2.Items.Description' }}</a>
<a class="item tab-select" data-tab="events">{{ localize 'MGT2.Items.EventsMishaps' }}</a>
</nav>
<div class="tab" data-group="primary" data-tab="tab1">
<textarea name="system.description" rows="6">{{system.description}}</textarea>
</div>
<div class="tab" data-group="primary" data-tab="events">
<div class="table-container color-2 mt-1">
<div class="table-row heading color-2">
<div class="row-item row-item-left upcase">{{ localize 'MGT2.Items.Age' }}</div>
<div class="row-item row-item-left upcase">{{ localize 'MGT2.Items.Details' }}</div>
<div class="row-item item-controls"><a class="event-create"><i class="fas fa-plus"></i></a></div>
</div>
{{#each system.events as |event i| }}
<div class="table-row dropitem events-part" data-events-part="{{i}}" role="rowgroup">
<div class="row-item row-item-left"><input type="number" name="system.events.{{i}}.age" value="{{event.age}}" data-dtype="Number" /></div>
<div class="row-item row-item-left">
<textarea name="system.events.{{i}}.description" rows="3">{{event.description}}</textarea>
</div>
<div class="row-item item-controls">
<a class="item-control event-delete" title="Delete Event"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</div>
</div>
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
<span class="item-type-label"><i class="fas fa-briefcase"></i> {{ localize 'MGT2.Items.Career' }}</span>
</div>
<div class="item-sheet-header">
<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>
<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">
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
</form>
<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">
<div class="table-row heading">
<div class="row-item row-item-left">{{ localize 'MGT2.Items.Age' }}</div>
<div class="row-item row-item-left">{{ localize 'MGT2.Items.Details' }}</div>
<div class="row-item row-item-right item-controls"><a class="event-create"><i class="fas fa-plus"></i></a></div>
</div>
{{#each system.events as |event i| }}
<div class="table-row dropitem events-part" data-events-part="{{i}}" role="rowgroup">
<div class="row-item row-item-left"><input type="number" name="system.events.{{i}}.age" value="{{event.age}}" data-dtype="Number" /></div>
<div class="row-item row-item-left">
<textarea name="system.events.{{i}}.description" rows="2">{{event.description}}</textarea>
</div>
<div class="row-item row-item-right item-controls">
<a class="item-control event-delete" title="Delete Event"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</div>
</div>
</div>

View File

@@ -1,65 +1,64 @@
<form class="{{cssClass}} itemsheet" autocomplete="off">
<div class="itemsheet-header">
<label>{{localize 'MGT2.EquipmentSubType.computer'}}</label>
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
<span class="item-type-label"><i class="fas fa-desktop"></i> {{localize 'MGT2.EquipmentSubType.computer'}}</span>
</div>
<div class="itemsheet-panel">
<div class="itemsheet-maincol">
<img class="profile-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
{{> systems/mgt2/templates/items/parts/sheet-physical-item.html }}
</div>
<div class="w-100">
<input class="field field-item-name" name="name" type="text" value="{{item.name}}" />
<nav class="horizontal-tabs tabs mt-1" 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="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
</nav>
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
</div>
<div class="item-sheet-header">
<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>
<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="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
</nav>
<div class="tab-content-area">
<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 class="tab" data-group="primary" data-tab="tab2">
<div class="field-groups mt-1">
<div class="field-group">
<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">
<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>
<div class="field-group mt-1">
</div>
<div class="tab" data-group="primary" data-tab="tab2">
<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" />
<input type="number" name="system.processing" value="{{system.processing}}" data-dtype="Number" class="short" />
</div>
<div class="table-container mt-1">
<div class="table-row heading color-2">
<div class="row-item row-item-left flex-grow-2 upcase">{{ localize 'MGT2.Items.Options' }}</div>
<div class="row-item row-item-left flex-grow-3">{{ localize 'MGT2.Items.Description' }}</div>
<div class="row-item row-item-right"><a class="options-create" data-property="options"><i class="fas fa-plus"></i></a></div>
</div>
{{#each system.options as |option i| }}
<div class="table-row dropitem options-part" data-options-part="{{i}}" data-property="options" role="rowgroup">
<div class="row-item row-item-left flex-grow-2"><input type="text" name="system.options.{{i}}.name" value="{{option.name}}" /></div>
<div class="row-item row-item-left flex-grow-3">
<textarea name="system.options.{{i}}.description" rows="3">{{option.description}}</textarea>
</div>
<div class="row-item row-item-right item-controls">
<a class="item-control options-delete" title="Delete Option"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
<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-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}}
</div>
<div class="tab" data-group="primary" data-tab="tab3">
{{> systems/mgt2/templates/items/parts/sheet-configuration.html }}
<div class="table-container" style="margin-top:8px;">
<div class="table-row heading">
<div class="row-item row-item-left">{{ localize 'MGT2.Items.Options' }}</div>
<div class="row-item row-item-left">{{ localize 'MGT2.Items.Description' }}</div>
<div class="row-item row-item-right"><a class="options-create" data-property="options"><i class="fas fa-plus"></i></a></div>
</div>
{{#each system.options as |option i| }}
<div class="table-row dropitem options-part" data-options-part="{{i}}" data-property="options" role="rowgroup">
<div class="row-item row-item-left"><input type="text" name="system.options.{{i}}.name" value="{{option.name}}" /></div>
<div class="row-item row-item-left">
<textarea name="system.options.{{i}}.description" rows="2">{{option.description}}</textarea>
</div>
<div class="row-item row-item-right item-controls">
<a class="item-control options-delete" title="Delete Option"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab3">
{{> systems/mgt2/templates/items/parts/sheet-configuration.html }}
</div>
</div>
</form>
</div>

View File

@@ -1,84 +1,80 @@
<form class="{{cssClass}} itemsheet" autocomplete="off">
<div class="itemsheet-header"><label class="upcase">{{ localize 'MGT2.Items.Contact' }}</label></div>
<div class="itemsheet-panel">
<div class="itemsheet-maincol">
<img class="profile-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Relation' }}</label>
<select name="system.relation">
{{selectOptions config.ContactRelations selected = system.relation localize = true}}
</select>
</div>
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Attitude' }}</label>
<select name="system.attitude">
{{selectOptions config.Attitudes selected = system.attitude localize = true}}
</select>
</div>
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Status' }}</label>
<select name="system.status">
{{selectOptions config.ContactStatus selected = system.status localize = true}}
</select>
</div>
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
<span class="item-type-label"><i class="fas fa-address-card"></i> {{ localize 'MGT2.Items.Contact' }}</span>
</div>
<div class="item-sheet-header">
<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>
<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>
<a class="item tab-select" data-tab="notes">{{ localize 'MGT2.Items.Notes' }}</a>
</nav>
<div class="tab-content-area">
<div class="tab" data-group="primary" data-tab="tab1">
<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="w-100 h100 flexcol">
<input class="field field-item-name" name="name" type="text" value="{{item.name}}" style="flex-grow: 0;" />
<div class="field-groups mt-1" style="flex-grow: 0;">
<div class="field-group">
<label>{{ localize 'MGT2.Species' }}</label>
<input type="text" name="system.species" value="{{system.species}}" data-dtype="String" />
</div>
{{#if settings.useGender}}
<div class="field-group" style="margin:0 1rem">
<label>{{ localize 'MGT2.Gender' }}</label>
<input type="text" name="system.gender" value="{{system.gender}}" data-dtype="String" />
</div>
{{/if}}
{{#if settings.usePronouns}}
<div class="field-group">
<label>{{ localize 'MGT2.Pronouns' }}</label>
<input type="text" name="system.pronouns" value="{{system.pronouns}}" data-dtype="String" />
</div>
{{/if}}
</div>
<nav class="horizontal-tabs tabs mt-1" data-group="primary" style="flex-grow: 0;">
<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>
<a class="item tab-select" data-tab="notes">{{ localize 'MGT2.Items.Notes' }}</a>
</nav>
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Title' }}</label>
<input type="text" name="system.title" value="{{system.title}}" data-dtype="String" />
</div>
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Nickname' }}</label>
<input type="text" name="system.nickname" value="{{system.nickname}}" data-dtype="String" />
</div>
<div class="field-groups mt-1">
<div class="field-group flex-1">
<label>{{ localize 'MGT2.Items.Homeworld' }}</label>
<input type="text" name="system.homeworld" value="{{system.homeworld}}" data-dtype="String" />
</div>
<div class="field-group flex-1">
<label>{{ localize 'MGT2.Items.Location' }}</label>
<input type="text" name="system.location" value="{{system.location}}" data-dtype="String" />
</div>
</div>
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Occupation' }}</label>
<input type="text" name="system.occupation" value="{{system.occupation}}" data-dtype="String" />
</div>
</div>
<div class="tab w100 h100" data-group="primary" data-tab="description">
{{editor system.description.value target="system.description" button=true editable=true}}
</div>
<div class="tab w100 h100" data-group="primary" data-tab="notes">
{{editor system.notes target="system.notes" button=true editable=true}}
</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-row">
<label>{{ localize 'MGT2.Pronouns' }}</label>
<input type="text" name="system.pronouns" value="{{system.pronouns}}" data-dtype="String" />
</div>
{{/if}}
<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-row">
<label>{{ localize 'MGT2.Items.Location' }}</label>
<input type="text" name="system.location" value="{{system.location}}" data-dtype="String" />
</div>
<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>
</form>
<div class="tab" data-group="primary" data-tab="description">
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
<div class="tab" data-group="primary" data-tab="notes">
{{formInput systemFields.notes enriched=enrichedNotes value=system.notes name="system.notes" toggled=true}}
</div>
</div>
</div>

View File

@@ -1,55 +1,59 @@
<form class="{{cssClass}} itemsheet" autocomplete="off">
<div class="itemsheet-header"><label class="upcase">{{ localize 'MGT2.Items.Container' }}</label></div>
<div class="itemsheet-panel">
<div class="itemsheet-maincol">
<img class="profile-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
{{#if system.locked}}
<div class="field-group mt-05">
<i class="fa-solid fa-lock"></i><label class="upcase">{{ localize 'MGT2.Items.Locked' }}</label>
</div>
{{/if}}
<div class="field-group mt-05">
<label>{{ localize 'MGT2.Items.Weight' }} ({{unitlabels.weight}})</label>
<input type="number" value="{{weight}}" readonly />
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
<span class="item-type-label"><i class="fas fa-box"></i> {{ localize 'MGT2.Items.Container' }}</span>
{{#if system.locked}}<span class="item-type-label" style="margin-left:8px;"><i class="fa-solid fa-lock"></i></span>{{/if}}
</div>
<div class="item-sheet-header">
<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>
<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>
{{#if isGM}}
<a class="item tab-select" data-tab="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
{{/if}}
</nav>
<div class="tab-content-area">
<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 style="flex: 1">
<input class="field field-item-name" name="name" type="text" value="{{item.name}}" data-dtype="String" />
<nav class="horizontal-tabs tabs mt-1" 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="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
</nav>
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
<div class="tab" data-group="primary" data-tab="tab2">
<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>
<div class="tab" data-group="primary" data-tab="tab2">
<div class="field-groups mt-05">
<div class="field-group flex-1">
<label class="mgt2-checkbox"><input type="checkbox" name="system.onHand" data-dtype="Boolean" {{checked system.onHand}} />{{ localize 'MGT2.Items.OnHand' }}</label>
</div>
{{#if isGM}}
<div class="field-group flex-1">
<label class="mgt2-checkbox"><input type="checkbox" name="system.locked" data-dtype="Boolean" {{checked system.locked}} />{{ localize 'MGT2.Items.Locked' }}</label>
</div>
{{/if}}
</div>
<div class="field-group flex-1">
<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="tab" data-group="primary" data-tab="tab3">
<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-group mt-05">
<label>{{ localize 'MGT2.Items.LockedDescription' }}</label>
<textarea name="system.lockedDescription" rows="6">{{system.lockedDescription}}</textarea>
<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>
{{formInput systemFields.lockedDescription enriched=enrichedLockedDescription value=system.lockedDescription name="system.lockedDescription" toggled=true}}
</div>
</div>
{{/if}}
</div>
</form>
</div>

View File

@@ -1,45 +1,51 @@
<form class="{{cssClass}} itemsheet" autocomplete="off">
<div class="itemsheet-header">
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
{{#if (eq system.subType "disease")}}
<label>{{localize 'MGT2.DiseaseSubType.disease'}}</label>
<span class="item-type-label"><i class="fas fa-bacterium"></i> {{localize 'MGT2.DiseaseSubType.disease'}}</span>
{{else if (eq system.subType "poison")}}
<label>{{localize 'MGT2.DiseaseSubType.poison'}}</label>
<span class="item-type-label"><i class="fas fa-skull-crossbones"></i> {{localize 'MGT2.DiseaseSubType.poison'}}</span>
{{else}}
<label>{{localize 'MGT2.DiseaseSubType.wound'}}</label>
<span class="item-type-label"><i class="fas fa-heart-crack"></i> {{localize 'MGT2.DiseaseSubType.wound'}}</span>
{{/if}}
</div>
<div class="itemsheet-panel">
<div class="itemsheet-maincol">
<img class="profile-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Type' }}</label>
<select name="system.subType">
{{selectOptions config.DiseaseSubType selected = system.subType localize = true}}
</select>
<div class="item-sheet-header">
<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>
<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="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="w-100">
<input class="field field-item-name" name="name" type="text" value="{{item.name}}" />
<div class="field-groups mt-1">
<div class="field-group w3-10">
<label>{{ localize 'MGT2.Items.Difficulty' }}</label>
<select name="system.difficulty">
{{selectOptions config.Difficulty selected = system.difficulty localize = true}}
<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-group w3-10" style="margin: 0 1rem;">
<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-group w3-10">
<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 class="field-group mt-1 w-100">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
</div>
</div>
</div>
</form>
</div>

View File

@@ -1,57 +1,56 @@
<form class="{{cssClass}} itemsheet" autocomplete="off">
<div class="itemsheet-header">
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
{{#if (eq system.subType "augment")}}
<label>{{localize 'MGT2.EquipmentSubType.augment'}}</label>
<span class="item-type-label"><i class="fas fa-dna"></i> {{localize 'MGT2.EquipmentSubType.augment'}}</span>
{{else if (eq system.subType "trinket")}}
<label>{{localize 'MGT2.EquipmentSubType.trinket'}}</label>
<span class="item-type-label"><i class="fas fa-gem"></i> {{localize 'MGT2.EquipmentSubType.trinket'}}</span>
{{else if (eq system.subType "clothing")}}
<label>{{localize 'MGT2.EquipmentSubType.clothing'}}</label>
<span class="item-type-label"><i class="fas fa-shirt"></i> {{localize 'MGT2.EquipmentSubType.clothing'}}</span>
{{else}}
<label>{{localize 'MGT2.EquipmentSubType.equipment'}}</label>
<span class="item-type-label"><i class="fas fa-toolbox"></i> {{localize 'MGT2.EquipmentSubType.equipment'}}</span>
{{/if}}
</div>
<div class="itemsheet-panel">
<div class="itemsheet-maincol">
<img class="profile-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Type' }}</label>
<select name="system.subType">
{{selectOptions config.EquipmentSubType selected = system.subType localize = true}}
</select>
<div class="item-sheet-header">
<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>
<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="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
</nav>
<div class="tab-content-area">
<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>
{{> systems/mgt2/templates/items/parts/sheet-physical-item.html }}
</div>
<div class="w-100">
<input class="field field-item-name" name="name" type="text" value="{{item.name}}" />
<nav class="horizontal-tabs tabs mt-1" 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="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
</nav>
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
<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.EquipmentSubType selected=system.subType localize=true}}
</select>
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab2">
<div class="field-groups mt-1">
<div class="field-group">
<label class="mgt2-checkbox"><input type="checkbox" name="system.equipped" data-dtype="Boolean" {{checked system.equipped}} />Equipped</label>
</div>
{{#if hadContainer}}
<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}}
{{> 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-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}}
</div>
<div class="tab" data-group="primary" data-tab="tab3">
{{> systems/mgt2/templates/items/parts/sheet-configuration.html }}
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab3">
{{> systems/mgt2/templates/items/parts/sheet-configuration.html }}
</div>
</div>
</form>
</div>

View File

@@ -1,60 +1,59 @@
<form class="{{cssClass}} itemsheet" autocomplete="off">
<div class="itemsheet-header">
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
{{#if (eq system.subType "loot")}}
<label>{{localize 'MGT2.ItemSubType.loot'}}</label>
<span class="item-type-label"><i class="fas fa-coins"></i> {{localize 'MGT2.ItemSubType.loot'}}</span>
{{else}}
<label>{{localize 'MGT2.ItemSubType.software'}}</label>
<span class="item-type-label"><i class="fas fa-floppy-disk"></i> {{localize 'MGT2.ItemSubType.software'}}</span>
{{/if}}
</div>
<div class="itemsheet-panel">
<div class="itemsheet-maincol">
<img class="profile-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Type' }}</label>
<select name="system.subType">
{{selectOptions config.ItemSubType selected = system.subType localize = true}}
</select>
<div class="item-sheet-header">
<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>
<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="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>
{{> systems/mgt2/templates/items/parts/sheet-physical-item.html }}
</div>
<div class="flex-1">
<input class="field field-item-name" name="name" type="text" value="{{item.name}}" />
<nav class="horizontal-tabs tabs mt-1" 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" data-group="primary" data-tab="tab1">
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
<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.ItemSubType selected=system.subType localize=true}}
</select>
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab2">
{{> systems/mgt2/templates/items/parts/sheet-physical-item-tab.html }}
{{#if hadContainer}}
<div class="field-groups mt-1">
{{#if (eq system.subType "software")}}
<div class="field-group">
<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>
<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 mt-1">
<div class="field-row">
<label>{{ localize 'MGT2.Items.Bandwidth' }}</label>
<input type="number" name="system.software.bandwidth" value="{{system.software.bandwidth}}" data-dtype="Number" />
<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}}
{{/if}}
</div>
</div>
</div>
</form>
</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

@@ -1,21 +1,22 @@
<div class="field-group mt-05">
<label>{{ localize 'MGT2.Items.Quantity' }}</label>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Quantity' }}</span>
<input type="number" name="system.quantity" value="{{system.quantity}}" data-dtype="Number" integer="true" positive="true" />
</div>
<div class="field-group mt-05">
<label>{{ localize 'MGT2.Items.Weight' }} ({{unitlabels.weight}})</label>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Weight' }} ({{unitlabels.weight}})</span>
<input type="number" name="weight" value="{{weight}}" data-dtype="Number" step="0.5" />
</div>
<div class="field-group mt-05">
<label class="mgt2-checkbox"><input type="checkbox" name="system.weightless" data-dtype="Boolean" {{checked system.weightless}} />{{ localize 'MGT2.Items.Weightless' }}</label>
<div class="item-stat-pill-checkbox">
<input type="checkbox" name="system.weightless" id="weightless-{{item.id}}" data-dtype="Boolean" {{checked system.weightless}} />
<label for="weightless-{{item.id}}">{{ localize 'MGT2.Items.Weightless' }}</label>
</div>
<div class="field-group mt-05">
<label>{{ localize 'MGT2.Items.Cost' }}</label>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.Cost' }}</span>
<input type="number" name="system.cost" value="{{system.cost}}" data-dtype="Number" step="1" />
</div>
<div class="field-group mt-05">
<label>{{ localize 'MGT2.Items.TL' }}</label>
<div class="item-stat-pill">
<span class="stat-label">{{ localize 'MGT2.Items.TL' }}</span>
<select name="system.tl">
{{selectOptions config.TL selected = system.tl localize = true}}
</select>
</select>
</div>

View File

@@ -1,70 +1,67 @@
<form class="{{cssClass}} itemsheet" autocomplete="off">
<div class="itemsheet-header"><label>{{ localize 'MGT2.Specie' }}</label></div>
<div class="itemsheet-panel">
<div class="itemsheet-maincol">
<img class="profile-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
</div>
<div class="w-100" style="display: flex;flex-direction: column;justify-content: flex-start;align-items: stretch;">
<input class="field item-name" name="name" type="text" value="{{item.name}}" />
<div class="field-group mt-1">
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
<span class="item-type-label"><i class="fas fa-dna"></i> {{ localize 'MGT2.Specie' }}</span>
</div>
<div class="item-sheet-header">
<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>
<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.DetailedDescription' }}</a>
<a class="item tab-select" data-tab="tab3">{{ localize 'MGT2.Items.Details' }}</a>
</nav>
<div class="tab-content-area">
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<nav class="horizontal-tabs tabs mt-1" 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.DetailedDescription' }}</a>
<a class="item tab-select" data-tab="tab3">{{ localize 'MGT2.Items.Details' }}</a>
</nav>
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
</div>
</div>
<div class="tab w100 h100" data-group="primary" data-tab="tab2">
{{editor system.descriptionLong target="system.descriptionLong" button=true editable=true}}
</div>
<div class="tab" data-group="primary" data-tab="tab3">
<div class="table-container">
<div class="table-row heading color-2">
<div class="row-item row-item-left upcase">{{ localize 'MGT2.Items.Characteristics' }}</div>
<div class="row-item row-item-center row-item-15 flex-fix">{{ localize 'MGT2.Items.Modifiers' }}</div>
<div class="row-item row-item-15 item-controls flex-fix"><a class="modifiers-create"><i class="fas fa-plus"></i></a></div>
</div>
{{#each system.modifiers as |modifier i| }}
<div class="table-row modifiers-part" data-modifiers-part="{{i}}" role="rowgroup">
<div class="row-item row-item-left">
<select name="system.modifiers.{{i}}.characteristic">
{{selectOptions ../config.Characteristics selected=modifier.characteristic localize = true}}
</select>
</div>
<div class="row-item row-item-left row-item-15 flex-fix">
<input type="number" name="system.modifiers.{{i}}.value" value="{{modifier.value}}" />
</div>
<div class="row-item row-item-15 item-controls flex-fix">
<a class="item-control modifiers-delete" title="Delete Trait"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
<div class="table-container mt-1">
<div class="table-row heading color-2">
<div class="row-item row-item-30 row-item-left upcase">{{ localize 'MGT2.Items.Traits' }}</div>
<div class="row-item row-item-left">{{ localize 'MGT2.Items.Description' }}</div>
<div class="row-item row-item-15 item-controls flex-fix"><a class="options-create" data-property="traits"><i class="fas fa-plus"></i></a></div>
</div>
{{#each system.traits as |trait i| }}
<div class="table-row options-part" data-options-part="{{i}}" data-property="traits" role="rowgroup">
<div class="row-item row-item-30 row-item-left"><input type="text" name="system.traits.{{i}}.name" value="{{trait.name}}" /></div>
<div class="row-item row-item-left">
<textarea name="system.traits.{{i}}.description" rows="3">{{trait.description}}</textarea>
</div>
<div class="row-item row-item-15 item-controls flex-fix">
<a class="item-control options-delete" title="Delete Trait"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</div>
{{formInput systemFields.description enriched=enrichedDescription value=system.description name="system.description" toggled=true}}
</div>
</div>
<div class="tab" data-group="primary" data-tab="tab2">
{{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">
<div class="table-row heading">
<div class="row-item row-item-left">{{ localize 'MGT2.Items.Characteristics' }}</div>
<div class="row-item row-item-left" style="flex:0 0 120px;">{{ localize 'MGT2.Items.Modifiers' }}</div>
<div class="row-item row-item-right item-controls"><a class="modifiers-create"><i class="fas fa-plus"></i></a></div>
</div>
{{#each system.modifiers as |modifier i| }}
<div class="table-row modifiers-part" data-modifiers-part="{{i}}" role="rowgroup">
<div class="row-item row-item-left">
<select name="system.modifiers.{{i}}.characteristic">
{{selectOptions ../config.Characteristics selected=modifier.characteristic localize=true}}
</select>
</div>
<div class="row-item row-item-left" style="flex:0 0 120px;">
<input type="number" name="system.modifiers.{{i}}.value" value="{{modifier.value}}" />
</div>
<div class="row-item row-item-right item-controls">
<a class="item-control modifiers-delete" title="Delete Trait"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
<div class="table-container" style="margin-top:8px;">
<div class="table-row heading">
<div class="row-item row-item-left" style="flex:0 0 30%;">{{ localize 'MGT2.Items.Traits' }}</div>
<div class="row-item row-item-left">{{ localize 'MGT2.Items.Description' }}</div>
<div class="row-item row-item-right item-controls"><a class="options-create" data-property="traits"><i class="fas fa-plus"></i></a></div>
</div>
{{#each system.traits as |trait i| }}
<div class="table-row options-part" data-options-part="{{i}}" data-property="traits" role="rowgroup">
<div class="row-item row-item-left" style="flex:0 0 30%;"><input type="text" name="system.traits.{{i}}.name" value="{{trait.name}}" /></div>
<div class="row-item row-item-left">
<textarea name="system.traits.{{i}}.description" rows="2">{{trait.description}}</textarea>
</div>
<div class="row-item row-item-right item-controls">
<a class="item-control options-delete" title="Delete Trait"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
</div>
</div>
</div>
</form>
</div>

View File

@@ -1,79 +1,84 @@
<form class="{{cssClass}} flexrow itemsheet" autocomplete="off">
<div class="itemsheet-header">
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
{{#if (eq system.subType "skill")}}
<label>{{localize 'MGT2.TalentSubType.skill'}}</label>
<span class="item-type-label"><i class="fas fa-star"></i> {{localize 'MGT2.TalentSubType.skill'}}</span>
{{else if (eq system.subType "psionic")}}
<label>{{localize 'MGT2.TalentSubType.psionic'}}</label>
<span class="item-type-label"><i class="fas fa-brain"></i> {{localize 'MGT2.TalentSubType.psionic'}}</span>
{{/if}}
</div>
<div class="item-sheet-header">
<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>
<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="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
</nav>
<div class="tab-content-area">
{{!-- Tab 1 : Description --}}
<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="itemsheet-panel">
<div class="itemsheet-maincol">
<img class="profile-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Type' }}</label>
<select name="system.subType">
{{selectOptions config.TalentSubType selected = system.subType localize = true}}
</select>
</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="w-100">
<input class="field field-item-name" name="name" type="text" value="{{item.name}}" />
{{#if (eq system.subType "skill")}}
<div class="field-groups mt-1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Speciality' }}</label>
<input type="text" name="system.skill.speciality" value="{{system.skill.speciality}}" data-dtype="String" />
</div>
<div class="field-group">
<label>{{ localize 'MGT2.Items.Level' }}</label>
<input type="text" name="system.level" value="{{system.level}}" data-dtype="Number" />
</div>
</div>
{{else if (eq system.subType "psionic")}}
<div class="field-groups mt-1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Level' }}</label>
<input type="text" name="system.level" value="{{system.level}}" data-dtype="Number" />
</div>
<div class="field-group">
<label>{{ localize 'MGT2.Items.Reach' }}</label>
<select name="system.psionic.reach">
<option></option>
{{selectOptions config.PsionicReach selected = system.psionic.reach localize = true}}
</select>
</div>
<div class="field-group">
<label>{{ localize 'MGT2.Items.PSICost' }}</label>
<input type="number" name="system.psionic.cost" value="{{system.psionic.cost}}" data-dtype="Number" />
</div>
</div>
<div class="field-groups mt-1">
<div class="field-group">
<label>{{ localize 'MGT2.Items.Duration' }}</label>
<div class="flexrow">
<input type="text" name="system.psionic.duration" value="{{system.psionic.duration}}" data-dtype="String" class="flex-basis-50" />
<select name="system.psionic.durationUnit" class="flex-basis-50 flex-fix">
{{selectOptions config.Durations selected=system.psionic.durationUnit localize = true}}
</select>
</div>
</div>
</div>
{{/if}}
<nav class="horizontal-tabs tabs mt-1" 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>
</nav>
<div class="tab" data-group="primary" data-tab="tab1">
<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 mt-05">
<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}}
{{> systems/mgt2/templates/items/parts/sheet-configuration.html }}
</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 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>
</div>
</form>
{{!-- 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

@@ -1,89 +1,124 @@
<form class="{{cssClass}} itemsheet" autocomplete="off">
<div class="itemsheet-header"><label class="upcase">{{ localize 'MGT2.Items.Weapon' }}</label></div>
<div class="itemsheet-panel">
<div class="itemsheet-maincol">
<img class="profile-img" src="{{item.img}}" data-edit="img" data-tooltip="{{item.name}}" />
{{> systems/mgt2/templates/items/parts/sheet-physical-item.html }}
<div class="{{cssClass}} itemsheet">
<div class="item-type-bar">
<span class="item-type-label"><i class="fas fa-gun"></i> {{ localize 'MGT2.Items.Weapon' }}</span>
</div>
<div class="item-sheet-header">
<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>
<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="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
</nav>
<div class="tab-content-area">
<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="w-100">
<input class="field field-item-name" name="name" type="text" value="{{item.name}}" />
<nav class="horizontal-tabs tabs mt-1" 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="tab3">{{ localize 'MGT2.Items.Configuration' }}</a>
</nav>
<div class="tab" data-group="primary" data-tab="tab1">
<div class="field-group mt-1">
<label>{{ localize 'MGT2.Items.Description' }}</label>
<textarea name="system.description" rows="6">{{system.description}}</textarea>
<div class="tab" data-group="primary" data-tab="tab2">
<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>
<div class="tab" data-group="primary" data-tab="tab2">
<div class="field-groups mt-1">
<div class="field-group flex-1">
<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">
<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 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-groups mt-1">
<div class="field-group w3-10">
<label class="mgt2-checkbox"><input type="checkbox" name="system.range.isMelee" data-dtype="Boolean" {{checked system.range.isMelee}} />{{ localize 'MGT2.Items.IsMelee' }}</label>
</div>
{{#unless system.range.isMelee}}
<div class="field-group w3-10">
<label>{{ localize 'MGT2.Items.Range' }}</label>
<div class="flexrow">
<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" class="flex-fix" style="width: 5rem;">
{{selectOptions config.MetricRange selected=system.range.unit localize = true}}
<select name="system.range.unit">
{{selectOptions config.MetricRange selected=system.range.unit localize=true}}
</select>
</div>
</div>
{{/unless}}
</div>
<div class="field-groups mt-1">
<div class="field-group w3-10">
<label>{{ localize 'MGT2.Items.Damage' }}</label>
<input type="text" name="system.damage" value="{{system.damage}}" data-dtype="String" />
</div>
<div class="field-group w3-10" style="margin: 0 1rem;">
<label>{{ localize 'MGT2.Items.Magazine' }}</label>
<input type="number" name="system.magazine" value="{{system.magazine}}" data-dtype="Number" />
</div>
<div class="field-group w3-10">
<label>{{ localize 'MGT2.Items.MagazineCost' }}</label>
<input type="number" name="system.magazineCost" value="{{system.magazineCost}}" data-dtype="Number" />
</div>
</div>
<div class="table-container">
<div class="table-row heading color-2">
<div class="row-item row-item-left upcase">{{ localize 'MGT2.Items.Trait' }}</div>
<div class="row-item row-item-left">{{ localize 'MGT2.Items.Description' }}</div>
<div class="row-item row-item-right"><a class="options-create" data-property="traits"><i class="fas fa-plus"></i></a></div>
</div>
{{#each system.traits as |trait i| }}
<div class="table-row dropitem options-part" data-options-part="{{i}}" data-property="traits" role="rowgroup">
<div class="row-item row-item-left"><input type="text" name="system.traits.{{i}}.name" value="{{trait.name}}" /></div>
<div class="row-item row-item-left">
<textarea name="system.traits.{{i}}.description" rows="3">{{trait.description}}</textarea>
</div>
<div class="row-item row-item-right item-controls">
<a class="item-control options-delete" title="Delete Trait"><i class="fas fa-trash"></i></a>
</div>
</div>
{{/each}}
{{/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-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-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}}
</div>
<div class="tab" data-group="primary" data-tab="tab3">
{{> systems/mgt2/templates/items/parts/sheet-configuration.html }}
</div>
<fieldset class="mgt2-weapon-traits">
<legend>{{ localize 'MGT2.WeaponTraits.SectionTitle' }}</legend>
<div class="mgt2-weapon-traits-grid">
<div class="mgt2-trait-num" data-tooltip="{{ localize 'MGT2.WeaponTraits.APHint' }}">
<label>{{ localize 'MGT2.WeaponTraits.AP' }}</label>
<input type="number" name="system.traits.ap" value="{{system.traits.ap}}" data-dtype="Number" min="0" class="short" />
</div>
<div class="mgt2-trait-num" data-tooltip="{{ localize 'MGT2.WeaponTraits.AutoHint' }}">
<label>{{ localize 'MGT2.WeaponTraits.Auto' }}</label>
<input type="number" name="system.traits.auto" value="{{system.traits.auto}}" data-dtype="Number" min="0" class="short" />
</div>
<div class="mgt2-trait-num" data-tooltip="{{ localize 'MGT2.WeaponTraits.BlastHint' }}">
<label>{{ localize 'MGT2.WeaponTraits.Blast' }}</label>
<input type="number" name="system.traits.blast" value="{{system.traits.blast}}" data-dtype="Number" min="0" class="short" />
</div>
<label class="mgt2-checkbox mgt2-trait-bool" data-tooltip="{{ localize 'MGT2.WeaponTraits.BulkyHint' }}">
<input type="checkbox" name="system.traits.bulky" data-dtype="Boolean" {{checked system.traits.bulky}} />{{ localize 'MGT2.WeaponTraits.Bulky' }}
</label>
<label class="mgt2-checkbox mgt2-trait-bool" data-tooltip="{{ localize 'MGT2.WeaponTraits.VeryBulkyHint' }}">
<input type="checkbox" name="system.traits.veryBulky" data-dtype="Boolean" {{checked system.traits.veryBulky}} />{{ localize 'MGT2.WeaponTraits.VeryBulky' }}
</label>
<label class="mgt2-checkbox mgt2-trait-bool" data-tooltip="{{ localize 'MGT2.WeaponTraits.StunHint' }}">
<input type="checkbox" name="system.traits.stun" data-dtype="Boolean" {{checked system.traits.stun}} />{{ localize 'MGT2.WeaponTraits.Stun' }}
</label>
<label class="mgt2-checkbox mgt2-trait-bool" data-tooltip="{{ localize 'MGT2.WeaponTraits.SmartHint' }}">
<input type="checkbox" name="system.traits.smart" data-dtype="Boolean" {{checked system.traits.smart}} />{{ localize 'MGT2.WeaponTraits.Smart' }}
</label>
<label class="mgt2-checkbox mgt2-trait-bool" data-tooltip="{{ localize 'MGT2.WeaponTraits.RadiationHint' }}">
<input type="checkbox" name="system.traits.radiation" data-dtype="Boolean" {{checked system.traits.radiation}} />{{ localize 'MGT2.WeaponTraits.Radiation' }}
</label>
<label class="mgt2-checkbox mgt2-trait-bool" data-tooltip="{{ localize 'MGT2.WeaponTraits.ScopeHint' }}">
<input type="checkbox" name="system.traits.scope" data-dtype="Boolean" {{checked system.traits.scope}} />{{ localize 'MGT2.WeaponTraits.Scope' }}
</label>
<label class="mgt2-checkbox mgt2-trait-bool" data-tooltip="{{ localize 'MGT2.WeaponTraits.ZeroGHint' }}">
<input type="checkbox" name="system.traits.zeroG" data-dtype="Boolean" {{checked system.traits.zeroG}} />{{ localize 'MGT2.WeaponTraits.ZeroG' }}
</label>
</div>
</fieldset>
</div>
<div class="tab" data-group="primary" data-tab="tab3">
{{> systems/mgt2/templates/items/parts/sheet-configuration.html }}
</div>
</div>
</form>
</div>

View File

@@ -1,38 +1,205 @@
<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">
{{selectOptions characteristics selected=characteristic nameAttr="_id" labelAttr="name"}}
{{selectOptions characteristics selected=characteristic valueAttr="_id" labelAttr="name"}}
</select>
</div>
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.SkillDM' }}</label>
<select name="skill">
<option></option>
{{selectOptions skills selected=skill nameAttr="_id" labelAttr="name"}}
{{selectOptions skills selected=skill valueAttr="_id" labelAttr="name"}}
</select>
</div>
{{#unless isAttack}}
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.Timeframes' }}</label>
<select name="timeframes">
{{selectOptions config.Timeframes selected = timeframe localize = true}}
</select>
</div>
{{/unless}}
<fieldset>
<legend>{{ localize 'MGT2.RollPrompt.States' }}</legend>
<div class="form-group">
<label class="mgt2-checkbox"><input type="checkbox" name="encumbrance" data-dtype="Boolean" {{checked encumbrance}} />{{ localize 'MGT2.RollPrompt.EncumbranceDM' }}</label>
<label class="mgt2-checkbox"><input type="checkbox" name="fatigue" data-dtype="Boolean" {{checked fatigue}} />{{ localize 'MGT2.RollPrompt.FatigueDM' }}</label>
{{!-- Hidden checkboxes preserve form values for the roll calculation --}}
<input type="checkbox" name="encumbrance" data-dtype="Boolean" {{checked encumbrance}} style="display:none" />
<input type="checkbox" name="fatigue" data-dtype="Boolean" {{checked fatigue}} style="display:none" />
{{!-- Read-only state badges --}}
<div class="roll-prompt-states">
<span class="roll-prompt-state-badge {{#if encumbrance}}is-active{{/if}}">
<i class="fa-solid fa-weight-hanging"></i>
{{ localize 'MGT2.RollPrompt.EncumbranceDM' }}
{{#if encumbrance}}<strong>2</strong>{{/if}}
</span>
<span class="roll-prompt-state-badge {{#if fatigue}}is-active{{/if}}">
<i class="fa-solid fa-person-dots-from-line"></i>
{{ localize 'MGT2.RollPrompt.FatigueDM' }}
{{#if fatigue}}<strong>2</strong>{{/if}}
</span>
</div>
</fieldset>
{{#if isRanged}}
<fieldset class="mgt2-ranged-modifiers">
<legend>{{ localize 'MGT2.RollPrompt.RangedModifiers' }}</legend>
{{#if hasScope}}
<div class="mgt2-scope-badge" data-tooltip="{{ localize 'MGT2.RollPrompt.ScopeHint' }}">
<i class="fa-solid fa-crosshairs"></i>
{{ localize 'MGT2.RollPrompt.ScopeActive' }}
</div>
{{/if}}
{{#if hasZeroG}}
<div class="mgt2-zerog-badge" data-tooltip="{{ localize 'MGT2.RollPrompt.ZeroGHint' }}">
<i class="fa-solid fa-satellite"></i>
{{ localize 'MGT2.RollPrompt.ZeroGActive' }}
</div>
{{/if}}
{{#if autoLevel}}
<div class="form-group mgt2-auto-mode">
<label>{{ localize 'MGT2.RollPrompt.FireMode' }}</label>
<select name="autoMode">
<option value="single">{{ localize 'MGT2.RollPrompt.AutoSingle' }}</option>
<option value="burst">{{ localize 'MGT2.RollPrompt.AutoBurst' }} (+{{autoLevel}})</option>
<option value="fullAuto">{{ localize 'MGT2.RollPrompt.AutoFull' }} (×{{autoLevel}})</option>
</select>
</div>
<p class="mgt2-auto-hint">⚠ {{ localize 'MGT2.RollPrompt.AutoNoAim' }}</p>
{{/if}}
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.Range' }}</label>
<select name="rangedRange">
<option value="1">{{ localize 'MGT2.RollPrompt.RangeShort' }} (+1)</option>
<option value="0" selected>{{ localize 'MGT2.RollPrompt.RangeNormal' }}</option>
<option value="-2">{{ localize 'MGT2.RollPrompt.RangeLong' }} (2)</option>
<option value="-4">{{ localize 'MGT2.RollPrompt.RangeExtreme' }} (4)</option>
</select>
</div>
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.Aim' }}</label>
<select name="rangedAim">
<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>
</select>
</div>
<div class="form-group">
<label>{{ localize 'MGT2.RollPrompt.FastTarget' }}</label>
<select name="rangedFastTarget">
<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>
</select>
</div>
<div class="form-group mgt2-ranged-checkboxes">
<label class="mgt2-checkbox-tag"><input type="checkbox" name="rangedLaserSight" data-dtype="Boolean" />{{ localize 'MGT2.RollPrompt.LaserSight' }} <em>(+1 si Viser)</em></label>
<label class="mgt2-checkbox-tag"><input type="checkbox" name="rangedCover" data-dtype="Boolean" />{{ localize 'MGT2.RollPrompt.Cover' }} <em>(2)</em></label>
<label class="mgt2-checkbox-tag"><input type="checkbox" name="rangedProne" data-dtype="Boolean" />{{ localize 'MGT2.RollPrompt.Prone' }} <em>(1)</em></label>
</div>
<div class="form-group mgt2-ranged-dodge">
<label class="mgt2-checkbox-tag"><input type="checkbox" name="rangedDodge" data-dtype="Boolean" />{{ localize 'MGT2.RollPrompt.Dodge' }}</label>
<select name="rangedDodgeDM">
<option value="0" selected>MD 0</option>
<option value="-1">MD 1</option>
<option value="-2">MD 2</option>
<option value="-3">MD 3</option>
<option value="-4">MD 4</option>
<option value="-5">MD 5</option>
<option value="-6">MD 6</option>
</select>
</div>
</fieldset>
{{/if}}
{{#if isMelee}}
<fieldset class="mgt2-ranged-modifiers">
<legend>{{ localize 'MGT2.RollPrompt.MeleeModifiers' }}</legend>
<div class="form-group mgt2-ranged-dodge">
<label class="mgt2-checkbox-tag"><input type="checkbox" name="meleeDodge" data-dtype="Boolean" />{{ localize 'MGT2.RollPrompt.Dodge' }}</label>
<select name="meleeDodgeDM">
<option value="0" selected>MD 0</option>
<option value="-1">MD 1</option>
<option value="-2">MD 2</option>
<option value="-3">MD 3</option>
<option value="-4">MD 4</option>
<option value="-5">MD 5</option>
<option value="-6">MD 6</option>
</select>
</div>
<div class="form-group mgt2-ranged-dodge">
<label class="mgt2-checkbox-tag"><input type="checkbox" name="meleeParry" data-dtype="Boolean" />{{ localize 'MGT2.RollPrompt.Parry' }}</label>
<select name="meleeParryDM">
<option value="0" selected>MD 0</option>
<option value="-1">MD 1</option>
<option value="-2">MD 2</option>
<option value="-3">MD 3</option>
<option value="-4">MD 4</option>
<option value="-5">MD 5</option>
<option value="-6">MD 6</option>
</select>
</div>
</fieldset>
{{/if}}
{{/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>