Fix apv2, WIP
@@ -0,0 +1,69 @@
|
|||||||
|
# Makefile pour Vermine2047
|
||||||
|
# Ce fichier fournit des commandes courantes pour le développement
|
||||||
|
# Utilise uniquement LESS comme préprocesseur CSS
|
||||||
|
|
||||||
|
.PHONY: help install build build-less build-dev build-css watch clean lint
|
||||||
|
|
||||||
|
# Couleurs pour l'affichage
|
||||||
|
GREEN := \033[0;32m
|
||||||
|
YELLOW := \033[1;33m
|
||||||
|
NC := \033[0m # No Color
|
||||||
|
|
||||||
|
help: ## Affiche cette aide
|
||||||
|
@echo "Commandes disponibles pour Vermine2047:"
|
||||||
|
@echo ""
|
||||||
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[0;32m%-20s\033[0m %s\n", $$1, $$2}'
|
||||||
|
@echo ""
|
||||||
|
|
||||||
|
install: ## Installe les dépendances npm
|
||||||
|
@echo "$(YELLOW)Installation des dépendances npm...$(NC)"
|
||||||
|
npm install
|
||||||
|
@echo "$(GREEN)✓ Dépendances installées$(NC)"
|
||||||
|
|
||||||
|
build: build-less ## Compile LESS → vermine2047.min.css
|
||||||
|
|
||||||
|
build-less: ## Compile le LESS en CSS minifié (vermine2047.min.css)
|
||||||
|
@echo "$(YELLOW)Compilation du LESS...$(NC)"
|
||||||
|
npm run build:less
|
||||||
|
@echo "$(GREEN)✓ LESS compilé$(NC)"
|
||||||
|
|
||||||
|
build-dev: ## Compile le LESS en CSS non minifié (vermine2047.dev.css)
|
||||||
|
@echo "$(YELLOW)Compilation du LESS (mode dev)...$(NC)"
|
||||||
|
npm run build:less:dev
|
||||||
|
@echo "$(GREEN)✓ LESS compilé en mode dev$(NC)"
|
||||||
|
|
||||||
|
build-css: build-less build-dev ## Compile tout le CSS (minifié + dev)
|
||||||
|
|
||||||
|
watch: ## Lance le mode watch (recompilation automatique)
|
||||||
|
@echo "$(YELLOW)Lancement du mode watch...$(NC)"
|
||||||
|
npm run watch
|
||||||
|
|
||||||
|
clean: clean-css ## Nettoie les fichiers CSS générés
|
||||||
|
|
||||||
|
clean-css: ## Supprime les fichiers CSS compilés (garde vermine2047.css original)
|
||||||
|
@echo "$(YELLOW)Nettoyage des fichiers CSS...$(NC)"
|
||||||
|
npm run clean:css
|
||||||
|
@echo "$(GREEN)✓ Fichiers CSS nettoyés$(NC)"
|
||||||
|
|
||||||
|
rebuild: clean build ## Reconstruit tout le CSS
|
||||||
|
@echo "$(YELLOW)Reconstruction complète du CSS...$(NC)"
|
||||||
|
npm run rebuild:css
|
||||||
|
@echo "$(GREEN)✓ CSS reconstruit$(NC)"
|
||||||
|
|
||||||
|
lint: lint-less ## Lance le linting du code LESS
|
||||||
|
|
||||||
|
lint-less: ## Vérifie la qualité du code LESS
|
||||||
|
@echo "$(YELLOW)Linting du code LESS...$(NC)"
|
||||||
|
npm run lint:less
|
||||||
|
@echo "$(GREEN)✓ Linting terminé$(NC)"
|
||||||
|
|
||||||
|
# Commandes utilitaires
|
||||||
|
|
||||||
|
launch-foundry: ## Lance FoundryVTT
|
||||||
|
npm run launch_Foundry12
|
||||||
|
|
||||||
|
push-yaml: ## Push LDB vers YAML
|
||||||
|
node ./tools/pushLDBtoYAML.mjs
|
||||||
|
|
||||||
|
pull-yaml: ## Pull YAML vers LDB
|
||||||
|
node ./tools/pullYAMLtoLDB.mjs
|
||||||
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 855 KiB |
|
After Width: | Height: | Size: 776 KiB |
@@ -0,0 +1,680 @@
|
|||||||
|
uid=78_0 RootWebArea "Foundry Virtual Tabletop" url="https://localhost:31000/game"
|
||||||
|
uid=78_1 ignored
|
||||||
|
uid=78_2 generic
|
||||||
|
uid=78_3 list
|
||||||
|
uid=78_4 generic
|
||||||
|
uid=78_5 generic
|
||||||
|
uid=78_6 generic
|
||||||
|
uid=78_7 generic
|
||||||
|
uid=78_8 generic
|
||||||
|
uid=78_9 generic
|
||||||
|
uid=78_10 list
|
||||||
|
uid=78_11 listitem level="1"
|
||||||
|
uid=78_12 tab "Outils de token" selectable
|
||||||
|
uid=78_13 listitem level="1"
|
||||||
|
uid=78_14 tab "Outils de tuile" selectable
|
||||||
|
uid=78_15 listitem level="1"
|
||||||
|
uid=78_16 tab "Outils de dessin" selectable
|
||||||
|
uid=78_17 listitem level="1"
|
||||||
|
uid=78_18 tab "Outils de mur" selectable
|
||||||
|
uid=78_19 listitem level="1"
|
||||||
|
uid=78_20 tab "Outils de lumière" selectable
|
||||||
|
uid=78_21 listitem level="1"
|
||||||
|
uid=78_22 tab "Outils de son d’ambiance" selectable
|
||||||
|
uid=78_23 listitem level="1"
|
||||||
|
uid=78_24 tab "Outils de région" selectable
|
||||||
|
uid=78_25 listitem level="1"
|
||||||
|
uid=78_26 tab "Notes" selectable
|
||||||
|
uid=78_27 list
|
||||||
|
uid=78_28 listitem level="1"
|
||||||
|
uid=78_29 button "Sélection de tokens" pressed
|
||||||
|
uid=78_30 listitem level="1"
|
||||||
|
uid=78_31 button "Sélection de cibles"
|
||||||
|
uid=78_32 listitem level="1"
|
||||||
|
uid=78_33 button "Règle"
|
||||||
|
uid=78_34 listitem level="1"
|
||||||
|
uid=78_35 button "Déplacement sans contrainte"
|
||||||
|
uid=78_36 generic
|
||||||
|
uid=78_37 generic
|
||||||
|
uid=78_38 list
|
||||||
|
uid=78_39 listitem level="1"
|
||||||
|
uid=78_40 ignored
|
||||||
|
uid=78_41 StaticText "Gamemaster [MJ]"
|
||||||
|
uid=78_42 InlineTextBox "Gamemaster [MJ]"
|
||||||
|
uid=78_43 generic
|
||||||
|
uid=78_44 generic
|
||||||
|
uid=78_45 LabelText
|
||||||
|
uid=78_46 StaticText "Latence"
|
||||||
|
uid=78_42 InlineTextBox "Latence"
|
||||||
|
uid=78_47 StaticText " "
|
||||||
|
uid=78_42 InlineTextBox " "
|
||||||
|
uid=78_48 StaticText "1ms"
|
||||||
|
uid=78_42 InlineTextBox "1ms"
|
||||||
|
uid=78_49 generic
|
||||||
|
uid=78_50 LabelText
|
||||||
|
uid=78_51 StaticText "IPS"
|
||||||
|
uid=78_42 InlineTextBox "IPS"
|
||||||
|
uid=78_52 StaticText " "
|
||||||
|
uid=78_42 InlineTextBox " "
|
||||||
|
uid=78_53 StaticText "--"
|
||||||
|
uid=78_42 InlineTextBox "--"
|
||||||
|
uid=78_54 button
|
||||||
|
uid=78_55 generic
|
||||||
|
uid=78_56 navigation
|
||||||
|
uid=78_57 list
|
||||||
|
uid=78_58 generic
|
||||||
|
uid=78_59 sectionheader
|
||||||
|
uid=78_60 generic
|
||||||
|
uid=78_61 generic
|
||||||
|
uid=78_62 LabelText
|
||||||
|
uid=78_63 LabelText
|
||||||
|
uid=78_64 sectionfooter
|
||||||
|
uid=78_65 generic roledescription="Barre de raccourcis"
|
||||||
|
uid=78_66 generic
|
||||||
|
uid=78_67 button "Couper le son"
|
||||||
|
uid=78_68 button "Menu principal"
|
||||||
|
uid=78_69 list
|
||||||
|
uid=78_70 button "Emplacement vide"
|
||||||
|
uid=78_71 ignored
|
||||||
|
uid=78_72 StaticText "1"
|
||||||
|
uid=78_42 InlineTextBox "1"
|
||||||
|
uid=78_73 button "Emplacement vide"
|
||||||
|
uid=78_74 ignored
|
||||||
|
uid=78_75 StaticText "2"
|
||||||
|
uid=78_42 InlineTextBox "2"
|
||||||
|
uid=78_76 button "Emplacement vide"
|
||||||
|
uid=78_77 ignored
|
||||||
|
uid=78_78 StaticText "3"
|
||||||
|
uid=78_42 InlineTextBox "3"
|
||||||
|
uid=78_79 button "Emplacement vide"
|
||||||
|
uid=78_80 ignored
|
||||||
|
uid=78_81 StaticText "4"
|
||||||
|
uid=78_42 InlineTextBox "4"
|
||||||
|
uid=78_82 button "Emplacement vide"
|
||||||
|
uid=78_83 ignored
|
||||||
|
uid=78_84 StaticText "5"
|
||||||
|
uid=78_42 InlineTextBox "5"
|
||||||
|
uid=78_85 button "Emplacement vide"
|
||||||
|
uid=78_86 ignored
|
||||||
|
uid=78_87 StaticText "6"
|
||||||
|
uid=78_42 InlineTextBox "6"
|
||||||
|
uid=78_88 button "Emplacement vide"
|
||||||
|
uid=78_89 ignored
|
||||||
|
uid=78_90 StaticText "7"
|
||||||
|
uid=78_42 InlineTextBox "7"
|
||||||
|
uid=78_91 button "Emplacement vide"
|
||||||
|
uid=78_92 ignored
|
||||||
|
uid=78_93 StaticText "8"
|
||||||
|
uid=78_42 InlineTextBox "8"
|
||||||
|
uid=78_94 button "Emplacement vide"
|
||||||
|
uid=78_95 ignored
|
||||||
|
uid=78_96 StaticText "9"
|
||||||
|
uid=78_42 InlineTextBox "9"
|
||||||
|
uid=78_97 button "Emplacement vide"
|
||||||
|
uid=78_98 ignored
|
||||||
|
uid=78_99 StaticText "0"
|
||||||
|
uid=78_42 InlineTextBox "0"
|
||||||
|
uid=78_100 generic
|
||||||
|
uid=78_101 navigation
|
||||||
|
uid=78_102 button "Page suivante"
|
||||||
|
uid=78_103 ignored
|
||||||
|
uid=78_104 StaticText "1"
|
||||||
|
uid=78_42 InlineTextBox "1"
|
||||||
|
uid=78_105 button "Page précédente"
|
||||||
|
uid=78_106 ignored
|
||||||
|
uid=78_107 button "Verrouiller la barre de raccourcis"
|
||||||
|
uid=78_108 button "Effacer la barre de raccourcis"
|
||||||
|
uid=78_109 generic
|
||||||
|
uid=78_110 generic
|
||||||
|
uid=78_111 generic
|
||||||
|
uid=78_112 ignored
|
||||||
|
uid=78_113 list
|
||||||
|
uid=78_114 generic "Tchat"
|
||||||
|
uid=78_115 generic
|
||||||
|
uid=78_116 generic value="
|
||||||
|
"
|
||||||
|
uid=78_117 paragraph
|
||||||
|
uid=78_118 LineBreak "
|
||||||
|
"
|
||||||
|
uid=78_42 InlineTextBox "
|
||||||
|
"
|
||||||
|
uid=78_119 ignored
|
||||||
|
uid=78_42 generic
|
||||||
|
uid=78_42 StaticText "Entrer un message"
|
||||||
|
uid=78_42 InlineTextBox "Entrer un message"
|
||||||
|
uid=78_120 generic
|
||||||
|
uid=78_121 generic
|
||||||
|
uid=78_122 button "Public en tant qu'utilisateur" pressed
|
||||||
|
uid=78_123 button "Privé pour les maîtres de jeu"
|
||||||
|
uid=78_124 button "Aveugle pour les maîtres de jeu"
|
||||||
|
uid=78_125 button "Seulement pour soi-même"
|
||||||
|
uid=78_126 button "Public en tant que personnage"
|
||||||
|
uid=78_127 generic
|
||||||
|
uid=78_128 tablist orientation="horizontal"
|
||||||
|
uid=78_129 list
|
||||||
|
uid=78_130 listitem level="1"
|
||||||
|
uid=78_131 tab "Messages du tchat" selectable
|
||||||
|
uid=78_132 listitem level="1"
|
||||||
|
uid=78_133 tab "Rencontres de combat" selectable
|
||||||
|
uid=78_134 listitem level="1"
|
||||||
|
uid=78_135 tab "Scènes" selectable
|
||||||
|
uid=78_136 listitem level="1"
|
||||||
|
uid=78_137 tab "Objets plaçables" selectable
|
||||||
|
uid=78_138 listitem level="1"
|
||||||
|
uid=78_139 tab "Acteurs" selectable
|
||||||
|
uid=78_140 listitem level="1"
|
||||||
|
uid=78_141 tab "Objets" selectable
|
||||||
|
uid=78_142 listitem level="1"
|
||||||
|
uid=78_143 tab "Journaux" selectable
|
||||||
|
uid=78_144 listitem level="1"
|
||||||
|
uid=78_145 tab "Tables aléatoires" selectable
|
||||||
|
uid=78_146 listitem level="1"
|
||||||
|
uid=78_147 tab "Jeux de cartes" selectable
|
||||||
|
uid=78_148 listitem level="1"
|
||||||
|
uid=78_149 tab "Macros" selectable
|
||||||
|
uid=78_150 listitem level="1"
|
||||||
|
uid=78_151 tab "Playlists" selectable
|
||||||
|
uid=78_152 listitem level="1"
|
||||||
|
uid=78_153 tab "Compendiums" selectable
|
||||||
|
uid=78_154 listitem level="1"
|
||||||
|
uid=78_155 tab "Paramètres" selectable
|
||||||
|
uid=78_156 listitem level="1"
|
||||||
|
uid=78_157 button "Réduire"
|
||||||
|
uid=78_158 generic
|
||||||
|
uid=78_159 ignored
|
||||||
|
uid=78_160 ignored
|
||||||
|
uid=78_161 ignored
|
||||||
|
uid=78_162 ignored
|
||||||
|
uid=78_163 generic
|
||||||
|
uid=78_164 sectionheader
|
||||||
|
uid=78_165 ignored
|
||||||
|
uid=78_166 button "Créer un acteur"
|
||||||
|
uid=78_167 ignored
|
||||||
|
uid=78_168 ignored
|
||||||
|
uid=78_169 StaticText "Créer un acteur"
|
||||||
|
uid=78_42 InlineTextBox "Créer un acteur"
|
||||||
|
uid=78_170 button "Créer un dossier"
|
||||||
|
uid=78_171 ignored
|
||||||
|
uid=78_172 ignored
|
||||||
|
uid=78_173 StaticText "Créer un dossier"
|
||||||
|
uid=78_42 InlineTextBox "Créer un dossier"
|
||||||
|
uid=78_174 search
|
||||||
|
uid=78_175 button "Recherche par nom uniquement"
|
||||||
|
uid=78_176 searchbox "Chercher dans les Acteurs"
|
||||||
|
uid=78_177 ignored
|
||||||
|
uid=78_178 ignored
|
||||||
|
uid=78_179 generic
|
||||||
|
uid=78_180 button "Trier par ordre alphabétique"
|
||||||
|
uid=78_181 button "Réduire tous les dossiers"
|
||||||
|
uid=78_182 list
|
||||||
|
uid=78_183 listitem level="1"
|
||||||
|
uid=78_184 image "Acteur" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/creature.webp"
|
||||||
|
uid=78_185 generic
|
||||||
|
uid=78_186 StaticText "Acteur"
|
||||||
|
uid=78_42 InlineTextBox "Acteur"
|
||||||
|
uid=78_187 listitem level="1"
|
||||||
|
uid=78_188 image "Acteur (2)" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/group.webp"
|
||||||
|
uid=78_189 generic
|
||||||
|
uid=78_190 StaticText "Acteur (2)"
|
||||||
|
uid=78_42 InlineTextBox "Acteur (2)"
|
||||||
|
uid=78_191 listitem level="1"
|
||||||
|
uid=78_192 image "Acteur (3)" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/character.webp"
|
||||||
|
uid=78_193 generic
|
||||||
|
uid=78_194 StaticText "Acteur (3)"
|
||||||
|
uid=78_42 InlineTextBox "Acteur (3)"
|
||||||
|
uid=78_195 sectionfooter
|
||||||
|
uid=78_196 ignored
|
||||||
|
uid=78_197 ignored
|
||||||
|
uid=78_198 ignored
|
||||||
|
uid=78_199 ignored
|
||||||
|
uid=78_200 ignored
|
||||||
|
uid=78_201 ignored
|
||||||
|
uid=78_202 ignored
|
||||||
|
uid=78_203 ignored
|
||||||
|
uid=78_204 ignored
|
||||||
|
uid=78_205 ignored
|
||||||
|
uid=78_206 ignored
|
||||||
|
uid=78_207 ignored
|
||||||
|
uid=78_208 ignored
|
||||||
|
uid=78_209 ignored
|
||||||
|
uid=78_210 ignored
|
||||||
|
uid=78_211 ignored
|
||||||
|
uid=78_212 ignored
|
||||||
|
uid=78_213 ignored
|
||||||
|
uid=78_214 ignored
|
||||||
|
uid=78_215 ignored
|
||||||
|
uid=78_216 ignored
|
||||||
|
uid=78_217 ignored
|
||||||
|
uid=78_218 ignored
|
||||||
|
uid=78_219 figure
|
||||||
|
uid=78_220 image url="https://localhost:31000/ui/pause.svg"
|
||||||
|
uid=78_221 Figcaption
|
||||||
|
uid=78_222 StaticText "JEU EN PAUSE"
|
||||||
|
uid=78_42 InlineTextBox "JEU EN PAUSE"
|
||||||
|
uid=78_223 form
|
||||||
|
uid=78_224 banner
|
||||||
|
uid=78_225 heading "Personnage: Acteur (3)" level="1"
|
||||||
|
uid=78_226 StaticText "Personnage: Acteur (3)"
|
||||||
|
uid=78_42 InlineTextBox "Personnage: Acteur (3)"
|
||||||
|
uid=78_227 button "Basculer les contrôles"
|
||||||
|
uid=78_228 button "Copier l'UUID du document"
|
||||||
|
uid=78_229 button "Fermer la fenêtre"
|
||||||
|
uid=78_230 generic
|
||||||
|
uid=78_231 ignored
|
||||||
|
uid=78_232 ignored
|
||||||
|
uid=78_233 button "MODE ÉDITION"
|
||||||
|
uid=78_234 StaticText "MODE ÉDITION"
|
||||||
|
uid=78_42 InlineTextBox "MODE ÉDITION"
|
||||||
|
uid=78_235 image "logo Vermine" url="https://localhost:31000/systems/vermine2047/assets/images/ui/logo.webp"
|
||||||
|
uid=78_236 ignored
|
||||||
|
uid=78_237 image "Acteur (3)" url="https://localhost:31000/systems/vermine2047/assets/icons/actors/character.webp"
|
||||||
|
uid=78_238 generic
|
||||||
|
uid=78_239 heading "ADAPTATION" level="3"
|
||||||
|
uid=78_240 StaticText "ADAPTATION"
|
||||||
|
uid=78_42 InlineTextBox "ADAPTATION"
|
||||||
|
uid=78_241 heading "L'Humain" level="5"
|
||||||
|
uid=78_242 StaticText "L'Humain"
|
||||||
|
uid=78_42 InlineTextBox "L'Humain"
|
||||||
|
uid=78_243 image url="https://localhost:31000/systems/vermine2047/assets/images/ui/totems/human.webp"
|
||||||
|
uid=78_244 heading "L'adapté" level="5"
|
||||||
|
uid=78_245 StaticText "L'adapté"
|
||||||
|
uid=78_42 InlineTextBox "L'adapté"
|
||||||
|
uid=78_246 image url="https://localhost:31000/systems/vermine2047/assets/images/ui/totems/adapted.webp"
|
||||||
|
uid=78_247 ignored
|
||||||
|
uid=78_248 ignored
|
||||||
|
uid=78_249 ignored
|
||||||
|
uid=78_250 generic
|
||||||
|
uid=78_251 generic
|
||||||
|
uid=78_252 ignored
|
||||||
|
uid=78_253 ignored
|
||||||
|
uid=78_254 ignored
|
||||||
|
uid=78_255 ignored
|
||||||
|
uid=78_256 generic
|
||||||
|
uid=78_257 generic
|
||||||
|
uid=78_258 ignored
|
||||||
|
uid=78_259 ignored
|
||||||
|
uid=78_260 list
|
||||||
|
uid=78_261 listitem level="1"
|
||||||
|
uid=78_262 ignored
|
||||||
|
uid=78_263 heading "LA HORDE" level="4"
|
||||||
|
uid=78_264 StaticText "LA HORDE"
|
||||||
|
uid=78_42 InlineTextBox "LA HORDE"
|
||||||
|
uid=78_265 image "La Horde" url="https://localhost:31000/systems/vermine2047/assets/images/ui/totems/horde.webp"
|
||||||
|
uid=78_266 navigation roledescription="Navigation dans lâonglet des feuilles de personnages"
|
||||||
|
uid=78_267 generic
|
||||||
|
uid=78_268 ignored
|
||||||
|
uid=78_269 StaticText "Caractéristiques et compétences"
|
||||||
|
uid=78_42 InlineTextBox "Caractéristiques "
|
||||||
|
uid=78_42 InlineTextBox "et compétences"
|
||||||
|
uid=78_270 generic
|
||||||
|
uid=78_271 ignored
|
||||||
|
uid=78_272 StaticText "Totem et ajustements"
|
||||||
|
uid=78_42 InlineTextBox "Totem et "
|
||||||
|
uid=78_42 InlineTextBox "ajustements"
|
||||||
|
uid=78_273 generic
|
||||||
|
uid=78_274 ignored
|
||||||
|
uid=78_275 StaticText "Matériel"
|
||||||
|
uid=78_42 InlineTextBox "Matériel"
|
||||||
|
uid=78_276 generic
|
||||||
|
uid=78_277 ignored
|
||||||
|
uid=78_278 StaticText "Histoire"
|
||||||
|
uid=78_42 InlineTextBox "Histoire"
|
||||||
|
uid=78_279 generic
|
||||||
|
uid=78_280 ignored
|
||||||
|
uid=78_281 StaticText "Combat et reserves"
|
||||||
|
uid=78_42 InlineTextBox "Combat "
|
||||||
|
uid=78_42 InlineTextBox "et "
|
||||||
|
uid=78_42 InlineTextBox "reserves"
|
||||||
|
uid=78_282 generic
|
||||||
|
uid=78_283 sectionheader
|
||||||
|
uid=78_284 generic
|
||||||
|
uid=78_285 heading "Nom Acteur (3)" level="1"
|
||||||
|
uid=78_286 LabelText
|
||||||
|
uid=78_287 StaticText "Nom"
|
||||||
|
uid=78_42 InlineTextBox "Nom"
|
||||||
|
uid=78_288 ignored
|
||||||
|
uid=78_289 StaticText "Acteur (3)"
|
||||||
|
uid=78_42 InlineTextBox "Acteur (3)"
|
||||||
|
uid=78_290 ignored
|
||||||
|
uid=78_291 LabelText
|
||||||
|
uid=78_292 StaticText "Profil"
|
||||||
|
uid=78_42 InlineTextBox "Profil"
|
||||||
|
uid=78_293 ignored
|
||||||
|
uid=78_294 ignored
|
||||||
|
uid=78_295 LabelText
|
||||||
|
uid=78_296 StaticText "Age"
|
||||||
|
uid=78_42 InlineTextBox "Age"
|
||||||
|
uid=78_297 ignored
|
||||||
|
uid=78_298 ignored
|
||||||
|
uid=78_299 StaticText "15"
|
||||||
|
uid=78_42 InlineTextBox "15"
|
||||||
|
uid=78_300 generic
|
||||||
|
uid=78_301 StaticText "(Jeune)"
|
||||||
|
uid=78_42 InlineTextBox "(Jeune)"
|
||||||
|
uid=78_302 generic
|
||||||
|
uid=78_303 heading "Totem La Horde" level="1"
|
||||||
|
uid=78_304 LabelText
|
||||||
|
uid=78_305 StaticText "Totem"
|
||||||
|
uid=78_42 InlineTextBox "Totem"
|
||||||
|
uid=78_306 generic
|
||||||
|
uid=78_307 StaticText "La Horde"
|
||||||
|
uid=78_42 InlineTextBox "La Horde"
|
||||||
|
uid=78_308 ignored
|
||||||
|
uid=78_309 LabelText
|
||||||
|
uid=78_310 StaticText "Réputation"
|
||||||
|
uid=78_42 InlineTextBox "Réputation"
|
||||||
|
uid=78_311 ignored
|
||||||
|
uid=78_312 StaticText "0"
|
||||||
|
uid=78_42 InlineTextBox "0"
|
||||||
|
uid=78_313 LabelText
|
||||||
|
uid=78_314 StaticText "Expérience"
|
||||||
|
uid=78_42 InlineTextBox "Expérience"
|
||||||
|
uid=78_315 ignored
|
||||||
|
uid=78_316 StaticText "0"
|
||||||
|
uid=78_42 InlineTextBox "0"
|
||||||
|
uid=78_317 heading "CARACTÉRISTIQUES" level="3"
|
||||||
|
uid=78_318 StaticText "CARACTÉRISTIQUES"
|
||||||
|
uid=78_42 InlineTextBox "CARACTÉRISTIQUES"
|
||||||
|
uid=78_319 ignored
|
||||||
|
uid=78_320 ignored
|
||||||
|
uid=78_321 heading "PHYSIQUE" level="4"
|
||||||
|
uid=78_322 StaticText "PHYSIQUE"
|
||||||
|
uid=78_42 InlineTextBox "PHYSIQUE"
|
||||||
|
uid=78_323 generic
|
||||||
|
uid=78_324 LabelText
|
||||||
|
uid=78_325 StaticText "Vigueur"
|
||||||
|
uid=78_42 InlineTextBox "Vigueur"
|
||||||
|
uid=78_326 generic
|
||||||
|
uid=78_327 LabelText
|
||||||
|
uid=78_328 StaticText "Santé"
|
||||||
|
uid=78_42 InlineTextBox "Santé"
|
||||||
|
uid=78_329 ignored
|
||||||
|
uid=78_330 heading "MANUEL" level="4"
|
||||||
|
uid=78_331 StaticText "MANUEL"
|
||||||
|
uid=78_42 InlineTextBox "MANUEL"
|
||||||
|
uid=78_332 generic
|
||||||
|
uid=78_333 LabelText
|
||||||
|
uid=78_334 StaticText "Précision"
|
||||||
|
uid=78_42 InlineTextBox "Précision"
|
||||||
|
uid=78_335 generic
|
||||||
|
uid=78_336 LabelText
|
||||||
|
uid=78_337 StaticText "Réflexes"
|
||||||
|
uid=78_42 InlineTextBox "Réflexes"
|
||||||
|
uid=78_338 ignored
|
||||||
|
uid=78_339 heading "MENTAL" level="4"
|
||||||
|
uid=78_340 StaticText "MENTAL"
|
||||||
|
uid=78_42 InlineTextBox "MENTAL"
|
||||||
|
uid=78_341 generic
|
||||||
|
uid=78_342 LabelText
|
||||||
|
uid=78_343 StaticText "Savoir"
|
||||||
|
uid=78_42 InlineTextBox "Savoir"
|
||||||
|
uid=78_344 generic
|
||||||
|
uid=78_345 LabelText
|
||||||
|
uid=78_346 StaticText "Perception"
|
||||||
|
uid=78_42 InlineTextBox "Perception"
|
||||||
|
uid=78_347 ignored
|
||||||
|
uid=78_348 heading "SOCIAL" level="4"
|
||||||
|
uid=78_349 StaticText "SOCIAL"
|
||||||
|
uid=78_42 InlineTextBox "SOCIAL"
|
||||||
|
uid=78_350 generic
|
||||||
|
uid=78_351 LabelText
|
||||||
|
uid=78_352 StaticText "Volonté"
|
||||||
|
uid=78_42 InlineTextBox "Volonté"
|
||||||
|
uid=78_353 generic
|
||||||
|
uid=78_354 LabelText
|
||||||
|
uid=78_355 StaticText "Empathie"
|
||||||
|
uid=78_42 InlineTextBox "Empathie"
|
||||||
|
uid=78_356 heading "COMPÉTENCES" level="3"
|
||||||
|
uid=78_357 StaticText "COMPÉTENCES"
|
||||||
|
uid=78_42 InlineTextBox "COMPÉTENCES"
|
||||||
|
uid=78_358 ignored
|
||||||
|
uid=78_359 ignored
|
||||||
|
uid=78_360 heading "L'HOMME" level="4"
|
||||||
|
uid=78_361 StaticText "L'HOMME"
|
||||||
|
uid=78_42 InlineTextBox "L'HOMME"
|
||||||
|
uid=78_362 generic
|
||||||
|
uid=78_363 LabelText
|
||||||
|
uid=78_364 StaticText "Arts "
|
||||||
|
uid=78_42 InlineTextBox "Arts "
|
||||||
|
uid=78_365 superscript
|
||||||
|
uid=78_366 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_367 generic
|
||||||
|
uid=78_368 LabelText
|
||||||
|
uid=78_369 StaticText "Civilisation "
|
||||||
|
uid=78_42 InlineTextBox "Civilisation "
|
||||||
|
uid=78_370 superscript
|
||||||
|
uid=78_371 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_372 generic
|
||||||
|
uid=78_373 LabelText
|
||||||
|
uid=78_374 StaticText "Psychologie "
|
||||||
|
uid=78_42 InlineTextBox "Psychologie "
|
||||||
|
uid=78_375 superscript
|
||||||
|
uid=78_376 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_377 generic
|
||||||
|
uid=78_378 LabelText
|
||||||
|
uid=78_379 StaticText "Rumeurs"
|
||||||
|
uid=78_42 InlineTextBox "Rumeurs"
|
||||||
|
uid=78_380 generic
|
||||||
|
uid=78_381 LabelText
|
||||||
|
uid=78_382 StaticText "Soins "
|
||||||
|
uid=78_42 InlineTextBox "Soins "
|
||||||
|
uid=78_383 superscript
|
||||||
|
uid=78_384 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_385 ignored
|
||||||
|
uid=78_386 heading "L'ANIMAL" level="4"
|
||||||
|
uid=78_387 StaticText "L'ANIMAL"
|
||||||
|
uid=78_42 InlineTextBox "L'ANIMAL"
|
||||||
|
uid=78_388 generic
|
||||||
|
uid=78_389 LabelText
|
||||||
|
uid=78_390 StaticText "Animalisme "
|
||||||
|
uid=78_42 InlineTextBox "Animalisme "
|
||||||
|
uid=78_391 superscript
|
||||||
|
uid=78_392 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_393 generic
|
||||||
|
uid=78_394 LabelText
|
||||||
|
uid=78_395 StaticText "Dissection "
|
||||||
|
uid=78_42 InlineTextBox "Dissection "
|
||||||
|
uid=78_396 superscript
|
||||||
|
uid=78_397 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_398 generic
|
||||||
|
uid=78_399 LabelText
|
||||||
|
uid=78_400 StaticText "Faune "
|
||||||
|
uid=78_42 InlineTextBox "Faune "
|
||||||
|
uid=78_401 superscript
|
||||||
|
uid=78_402 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_403 generic
|
||||||
|
uid=78_404 LabelText
|
||||||
|
uid=78_405 StaticText "Répulsion"
|
||||||
|
uid=78_42 InlineTextBox "Répulsion"
|
||||||
|
uid=78_406 generic
|
||||||
|
uid=78_407 LabelText
|
||||||
|
uid=78_408 StaticText "Pistage"
|
||||||
|
uid=78_42 InlineTextBox "Pistage"
|
||||||
|
uid=78_409 ignored
|
||||||
|
uid=78_410 heading "LA MACHINE" level="4"
|
||||||
|
uid=78_411 StaticText "LA MACHINE"
|
||||||
|
uid=78_42 InlineTextBox "LA MACHINE"
|
||||||
|
uid=78_412 generic
|
||||||
|
uid=78_413 LabelText
|
||||||
|
uid=78_414 StaticText "Artisanat "
|
||||||
|
uid=78_42 InlineTextBox "Artisanat "
|
||||||
|
uid=78_415 superscript
|
||||||
|
uid=78_416 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_417 generic
|
||||||
|
uid=78_418 LabelText
|
||||||
|
uid=78_419 StaticText "Bricolage"
|
||||||
|
uid=78_42 InlineTextBox "Bricolage"
|
||||||
|
uid=78_420 generic
|
||||||
|
uid=78_421 LabelText
|
||||||
|
uid=78_422 StaticText "Mécanique "
|
||||||
|
uid=78_42 InlineTextBox "Mécanique "
|
||||||
|
uid=78_423 superscript
|
||||||
|
uid=78_424 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_425 generic
|
||||||
|
uid=78_426 LabelText
|
||||||
|
uid=78_427 StaticText "Pilotage "
|
||||||
|
uid=78_42 InlineTextBox "Pilotage "
|
||||||
|
uid=78_428 superscript
|
||||||
|
uid=78_429 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_430 generic
|
||||||
|
uid=78_431 LabelText
|
||||||
|
uid=78_432 StaticText "Technologie "
|
||||||
|
uid=78_42 InlineTextBox "Technologie "
|
||||||
|
uid=78_433 superscript
|
||||||
|
uid=78_434 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_435 ignored
|
||||||
|
uid=78_436 heading "L'ARME" level="4"
|
||||||
|
uid=78_437 StaticText "L'ARME"
|
||||||
|
uid=78_42 InlineTextBox "L'ARME"
|
||||||
|
uid=78_438 generic
|
||||||
|
uid=78_439 LabelText
|
||||||
|
uid=78_440 StaticText "Armes à feu "
|
||||||
|
uid=78_42 InlineTextBox "Armes à feu "
|
||||||
|
uid=78_441 superscript
|
||||||
|
uid=78_442 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_443 generic
|
||||||
|
uid=78_444 LabelText
|
||||||
|
uid=78_445 StaticText "Tir à l'arc"
|
||||||
|
uid=78_42 InlineTextBox "Tir à l'arc"
|
||||||
|
uid=78_446 generic
|
||||||
|
uid=78_447 LabelText
|
||||||
|
uid=78_448 StaticText "Armurerie "
|
||||||
|
uid=78_42 InlineTextBox "Armurerie "
|
||||||
|
uid=78_449 superscript
|
||||||
|
uid=78_450 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_451 generic
|
||||||
|
uid=78_452 LabelText
|
||||||
|
uid=78_453 StaticText "Lancer"
|
||||||
|
uid=78_42 InlineTextBox "Lancer"
|
||||||
|
uid=78_454 generic
|
||||||
|
uid=78_455 LabelText
|
||||||
|
uid=78_456 StaticText "Mêlée"
|
||||||
|
uid=78_42 InlineTextBox "Mêlée"
|
||||||
|
uid=78_457 ignored
|
||||||
|
uid=78_458 heading "LA SURVIE" level="4"
|
||||||
|
uid=78_459 StaticText "LA SURVIE"
|
||||||
|
uid=78_42 InlineTextBox "LA SURVIE"
|
||||||
|
uid=78_460 generic
|
||||||
|
uid=78_461 LabelText
|
||||||
|
uid=78_462 StaticText "Vigilance"
|
||||||
|
uid=78_42 InlineTextBox "Vigilance"
|
||||||
|
uid=78_463 generic
|
||||||
|
uid=78_464 LabelText
|
||||||
|
uid=78_465 StaticText "Athlétisme"
|
||||||
|
uid=78_42 InlineTextBox "Athlétisme"
|
||||||
|
uid=78_466 generic
|
||||||
|
uid=78_467 LabelText
|
||||||
|
uid=78_468 StaticText "Alimentation"
|
||||||
|
uid=78_42 InlineTextBox "Alimentation"
|
||||||
|
uid=78_469 generic
|
||||||
|
uid=78_470 LabelText
|
||||||
|
uid=78_471 StaticText "Discrétion"
|
||||||
|
uid=78_42 InlineTextBox "Discrétion"
|
||||||
|
uid=78_472 generic
|
||||||
|
uid=78_473 LabelText
|
||||||
|
uid=78_474 StaticText "Corps-à-corps"
|
||||||
|
uid=78_42 InlineTextBox "Corps-à-corps"
|
||||||
|
uid=78_475 ignored
|
||||||
|
uid=78_476 heading "LA TERRE" level="4"
|
||||||
|
uid=78_477 StaticText "LA TERRE"
|
||||||
|
uid=78_42 InlineTextBox "LA TERRE"
|
||||||
|
uid=78_478 generic
|
||||||
|
uid=78_479 LabelText
|
||||||
|
uid=78_480 StaticText "Environnement "
|
||||||
|
uid=78_42 InlineTextBox "Environnement "
|
||||||
|
uid=78_481 superscript
|
||||||
|
uid=78_482 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_483 generic
|
||||||
|
uid=78_484 LabelText
|
||||||
|
uid=78_485 StaticText "Flore "
|
||||||
|
uid=78_42 InlineTextBox "Flore "
|
||||||
|
uid=78_486 superscript
|
||||||
|
uid=78_487 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_488 generic
|
||||||
|
uid=78_489 LabelText
|
||||||
|
uid=78_490 StaticText "Route"
|
||||||
|
uid=78_42 InlineTextBox "Route"
|
||||||
|
uid=78_491 generic
|
||||||
|
uid=78_492 LabelText
|
||||||
|
uid=78_493 StaticText "Toxiques "
|
||||||
|
uid=78_42 InlineTextBox "Toxiques "
|
||||||
|
uid=78_494 superscript
|
||||||
|
uid=78_495 StaticText "(II)"
|
||||||
|
uid=78_42 InlineTextBox "(II)"
|
||||||
|
uid=78_496 generic
|
||||||
|
uid=78_497 LabelText
|
||||||
|
uid=78_498 StaticText "Vestiges "
|
||||||
|
uid=78_42 InlineTextBox "Vestiges "
|
||||||
|
uid=78_499 superscript
|
||||||
|
uid=78_500 StaticText "(I)"
|
||||||
|
uid=78_42 InlineTextBox "(I)"
|
||||||
|
uid=78_501 ignored
|
||||||
|
uid=78_502 ignored
|
||||||
|
uid=78_503 ignored
|
||||||
|
uid=78_504 ignored
|
||||||
|
uid=78_505 ignored
|
||||||
|
uid=78_506 ignored
|
||||||
|
uid=78_507 ignored
|
||||||
|
uid=78_508 ignored
|
||||||
|
uid=78_509 ignored
|
||||||
|
uid=78_510 ignored
|
||||||
|
uid=78_511 ignored
|
||||||
|
uid=78_512 ignored
|
||||||
|
uid=78_513 ignored
|
||||||
|
uid=78_514 ignored
|
||||||
|
uid=78_515 ignored
|
||||||
|
uid=78_516 ignored
|
||||||
|
uid=78_517 ignored
|
||||||
|
uid=78_518 ignored
|
||||||
|
uid=78_519 ignored
|
||||||
|
uid=78_520 ignored
|
||||||
|
uid=78_521 ignored
|
||||||
|
uid=78_522 ignored
|
||||||
|
uid=78_523 ignored
|
||||||
|
uid=78_524 ignored
|
||||||
|
uid=78_525 ignored
|
||||||
|
uid=78_526 ignored
|
||||||
|
uid=78_527 ignored
|
||||||
|
uid=78_528 ignored
|
||||||
|
uid=78_529 ignored
|
||||||
|
uid=78_530 ignored
|
||||||
|
uid=78_531 ignored
|
||||||
|
uid=78_532 ignored
|
||||||
|
uid=78_533 ignored
|
||||||
|
uid=78_534 ignored
|
||||||
|
uid=78_535 ignored
|
||||||
|
uid=78_536 ignored
|
||||||
|
uid=78_537 ignored
|
||||||
|
uid=78_538 ignored
|
||||||
|
uid=78_539 ignored
|
||||||
|
uid=78_540 ignored
|
||||||
|
uid=78_541 ignored
|
||||||
|
uid=78_542 ignored
|
||||||
|
uid=78_543 ignored
|
||||||
|
uid=78_544 ignored
|
||||||
|
uid=78_545 ignored
|
||||||
|
uid=78_546 ignored
|
||||||
|
uid=78_547 ignored
|
||||||
|
uid=78_548 ignored
|
||||||
|
uid=78_549 ignored
|
||||||
|
uid=78_550 ignored
|
||||||
|
uid=78_551 ignored
|
||||||
|
uid=78_552 ignored
|
||||||
|
uid=78_553 ignored
|
||||||
|
uid=78_554 ignored
|
||||||
|
uid=78_555 generic
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
vermine2047.min.css
|
||||||
@@ -1,40 +1,83 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const gulp = require('gulp');
|
const gulp = require('gulp');
|
||||||
const sass = require('gulp-sass')(require('sass'));
|
const less = require('gulp-less');
|
||||||
|
const autoprefixer = require('gulp-autoprefixer');
|
||||||
|
const cleanCSS = require('gulp-clean-css');
|
||||||
|
const rename = require('gulp-rename');
|
||||||
var browserSync = require('browser-sync').create();
|
var browserSync = require('browser-sync').create();
|
||||||
|
|
||||||
function buildStyles() {
|
// ============================================
|
||||||
return gulp.src('./scss/vermine2047.scss')
|
// LESS Tasks
|
||||||
.pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError))
|
// ============================================
|
||||||
|
|
||||||
|
function buildLess() {
|
||||||
|
return gulp.src('./less/vermine2047.less')
|
||||||
|
.pipe(less().on('error', function(err) {
|
||||||
|
console.error('LESS compilation error:', err.message);
|
||||||
|
this.emit('end');
|
||||||
|
}))
|
||||||
|
.pipe(autoprefixer({
|
||||||
|
overrideBrowserslist: ['> 1%', 'last 2 versions', 'Firefox ESR'],
|
||||||
|
cascade: false
|
||||||
|
}))
|
||||||
|
.pipe(cleanCSS({ compatibility: 'ie11' }))
|
||||||
|
.pipe(rename({ suffix: '.min' }))
|
||||||
.pipe(gulp.dest('./css'))
|
.pipe(gulp.dest('./css'))
|
||||||
.pipe(browserSync.stream());
|
.pipe(browserSync.stream());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build LESS without minification (for debugging)
|
||||||
|
function buildLessDev() {
|
||||||
|
return gulp.src('./less/vermine2047.less')
|
||||||
|
.pipe(less().on('error', function(err) {
|
||||||
|
console.error('LESS compilation error:', err.message);
|
||||||
|
this.emit('end');
|
||||||
|
}))
|
||||||
|
.pipe(autoprefixer({
|
||||||
|
overrideBrowserslist: ['> 1%', 'last 2 versions', 'Firefox ESR'],
|
||||||
|
cascade: false
|
||||||
|
}))
|
||||||
|
.pipe(rename({ suffix: '.dev' }))
|
||||||
|
.pipe(gulp.dest('./css'))
|
||||||
|
.pipe(browserSync.stream());
|
||||||
|
}
|
||||||
|
|
||||||
|
function watchLess() {
|
||||||
|
gulp.watch(['./less/**/*.less', './less/*.less'], buildLess);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
|
||||||
function reloadTemplatesHTML() {
|
function reloadTemplatesHTML() {
|
||||||
|
return browserSync.reload("templates/**/*.html");
|
||||||
return browserSync.reload("templates/**/*.html")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadTemplatesHBS() {
|
function reloadTemplatesHBS() {
|
||||||
|
return browserSync.reload("templates/**/*.hbs");
|
||||||
return browserSync.reload("templates/**/*.hbs")
|
|
||||||
}
|
}
|
||||||
exports.buildStyles = buildStyles;
|
|
||||||
exports.watch = function () {
|
|
||||||
browserSync.init(
|
|
||||||
{
|
|
||||||
server: false,
|
|
||||||
proxy: {
|
|
||||||
target: "https://localhost:30000/",
|
|
||||||
ws: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Exports
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
exports.buildLess = buildLess;
|
||||||
|
exports.buildLessDev = buildLessDev;
|
||||||
|
exports.buildCSS = gulp.series(buildLess);
|
||||||
|
exports.buildAllCSS = gulp.series(buildLess, buildLessDev);
|
||||||
|
exports.watch = function () {
|
||||||
|
browserSync.init({
|
||||||
|
server: false,
|
||||||
|
proxy: {
|
||||||
|
target: "https://localhost:30000/",
|
||||||
|
ws: true,
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
|
// Watch templates
|
||||||
gulp.watch("./templates/**/*.html").on('change', reloadTemplatesHTML);
|
gulp.watch("./templates/**/*.html").on('change', reloadTemplatesHTML);
|
||||||
gulp.watch("./templates/**/*.hbs").on('change', reloadTemplatesHBS);
|
gulp.watch("./templates/**/*.hbs").on('change', reloadTemplatesHBS);
|
||||||
gulp.watch(['./scss/**/*.scss', './scss/*.scss'], buildStyles);
|
|
||||||
|
|
||||||
|
// Watch LESS files
|
||||||
|
gulp.watch(['./less/**/*.less', './less/*.less'], buildLess);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.default = buildLess;
|
||||||
|
|||||||
|
After Width: | Height: | Size: 834 KiB |
@@ -13,6 +13,22 @@
|
|||||||
},
|
},
|
||||||
"VERMINE.WorldSettings.GameMode.Name": "Game Mode Selection",
|
"VERMINE.WorldSettings.GameMode.Name": "Game Mode Selection",
|
||||||
"VERMINE.WorldSettings.GameMode.Hint": "Just like some video games offer different modes, Vermine 2047 lets players choose their Game Mode and set the degree of realism, supernatural and dangerosity of the universe themselves.",
|
"VERMINE.WorldSettings.GameMode.Hint": "Just like some video games offer different modes, Vermine 2047 lets players choose their Game Mode and set the degree of realism, supernatural and dangerosity of the universe themselves.",
|
||||||
|
"TYPES.Item.item": "Item",
|
||||||
|
"TYPES.Item.weapon": "Weapon",
|
||||||
|
"TYPES.Item.defense": "Defense",
|
||||||
|
"TYPES.Item.vehicle": "Vehicle",
|
||||||
|
"TYPES.Item.ability": "Ability",
|
||||||
|
"TYPES.Item.specialty": "Specialty",
|
||||||
|
"TYPES.Item.background": "Background",
|
||||||
|
"TYPES.Item.trauma": "Trauma",
|
||||||
|
"TYPES.Item.evolution": "Adaptation",
|
||||||
|
"TYPES.Item.rumor": "Rumor",
|
||||||
|
"TYPES.Item.target": "Target",
|
||||||
|
"TYPES.Item.rite": "Rite",
|
||||||
|
"TYPES.Actor.character": "Character",
|
||||||
|
"TYPES.Actor.npc": "NPC",
|
||||||
|
"TYPES.Actor.group": "Group",
|
||||||
|
"TYPES.Actor.creature": "Creature",
|
||||||
"GAME_MODES": {
|
"GAME_MODES": {
|
||||||
"heroic": {
|
"heroic": {
|
||||||
"name": "Heroic"
|
"name": "Heroic"
|
||||||
@@ -22,7 +38,10 @@
|
|||||||
},
|
},
|
||||||
"legendary": {
|
"legendary": {
|
||||||
"name": "Legendary"
|
"name": "Legendary"
|
||||||
}
|
},
|
||||||
|
"survival": "Survival",
|
||||||
|
"nightmare": "Nightmare",
|
||||||
|
"apocalypse": "Apocalypse"
|
||||||
},
|
},
|
||||||
"ROLLS": {
|
"ROLLS": {
|
||||||
"tool": "Dice Roller",
|
"tool": "Dice Roller",
|
||||||
@@ -86,7 +105,8 @@
|
|||||||
"relations": "Relations",
|
"relations": "Relations",
|
||||||
"morale": "Morale",
|
"morale": "Morale",
|
||||||
"morale_level": "Morale level",
|
"morale_level": "Morale level",
|
||||||
"notes": "Notes"
|
"notes": "Notes",
|
||||||
|
"biography": "Biography"
|
||||||
},
|
},
|
||||||
"ADVERSITY": {
|
"ADVERSITY": {
|
||||||
"threat": "Threat",
|
"threat": "Threat",
|
||||||
@@ -108,7 +128,11 @@
|
|||||||
"skills": "Skills",
|
"skills": "Skills",
|
||||||
"pattern": "Pattern",
|
"pattern": "Pattern",
|
||||||
"size": "Size",
|
"size": "Size",
|
||||||
"pack": "Group"
|
"pack": "Group",
|
||||||
|
"gear_hindrance": "Gear Hindrance",
|
||||||
|
"skills_placeholder": "Skills Placeholder",
|
||||||
|
"threat_details": "Threat Details",
|
||||||
|
"role_details": "Role Details"
|
||||||
},
|
},
|
||||||
"VERMINE": {
|
"VERMINE": {
|
||||||
"name": "Vermine",
|
"name": "Vermine",
|
||||||
@@ -180,7 +204,106 @@
|
|||||||
"encounters": "Encounters",
|
"encounters": "Encounters",
|
||||||
"road": "The Road",
|
"road": "The Road",
|
||||||
"totem_picker": "Totem picker",
|
"totem_picker": "Totem picker",
|
||||||
"actor_picker": "Actor picker"
|
"actor_picker": "Actor picker",
|
||||||
|
"ability_category": {
|
||||||
|
"physical": "Physical",
|
||||||
|
"manual": "Manual",
|
||||||
|
"mental": "Mental",
|
||||||
|
"social": "Social"
|
||||||
|
},
|
||||||
|
"skill_category": {
|
||||||
|
"man": "Man",
|
||||||
|
"animal": "Animal",
|
||||||
|
"tool": "Tool",
|
||||||
|
"weapon": "Weapon",
|
||||||
|
"survival": "Survival",
|
||||||
|
"world": "World"
|
||||||
|
},
|
||||||
|
"CharacterNamePlaceholder": "Character Name",
|
||||||
|
"none": "None",
|
||||||
|
"bonuses": "Bonuses",
|
||||||
|
"choose_ability": "Choose Ability",
|
||||||
|
"choose_skill": "Choose Skill",
|
||||||
|
"grant_reroll": "Grant Reroll",
|
||||||
|
"handicap": "Handicap",
|
||||||
|
"keep_totem": "Keep Totem",
|
||||||
|
"morale_crisis": "Morale Crisis",
|
||||||
|
"morale_high": "High Morale",
|
||||||
|
"morale_low": "Low Morale",
|
||||||
|
"morale_normal": "Normal Morale",
|
||||||
|
"rerolls_possible": "Rerolls Possible",
|
||||||
|
"reserve": "Reserve",
|
||||||
|
"score": "Score",
|
||||||
|
"success_count": "Success Count",
|
||||||
|
"success_required": "Success Required",
|
||||||
|
"test_of": "Test of",
|
||||||
|
"total": "Total",
|
||||||
|
"totem_dice": "Totem Dice",
|
||||||
|
"totem_hint": "Totem Hint",
|
||||||
|
"FightTool": "Fight Tool",
|
||||||
|
"RollTool": "Roll Tool",
|
||||||
|
"Roll4Fight": "Roll for Fight",
|
||||||
|
"Selected": "Selected",
|
||||||
|
"PurposeTrait": "Purpose Trait",
|
||||||
|
"SpleenTrait": "Spleen Trait",
|
||||||
|
"ConfrontationHint": "Confrontation Hint",
|
||||||
|
"Achievement": "Achievement",
|
||||||
|
"Conservation": "Conservation",
|
||||||
|
"error_cannot_reroll": "Cannot reroll",
|
||||||
|
"error_no_rerolls_left": "No rerolls left",
|
||||||
|
"error_no_actor_selected": "No actor selected",
|
||||||
|
"error_not_enough_self_control": "Not enough self control",
|
||||||
|
"error_select_ability": "Select an ability",
|
||||||
|
"tabs": {
|
||||||
|
"abilities": "Abilities",
|
||||||
|
"combat": "Combat",
|
||||||
|
"equipment": "Equipment",
|
||||||
|
"stories": "Stories",
|
||||||
|
"totem": "Totem"
|
||||||
|
},
|
||||||
|
"base_values": "Base Values",
|
||||||
|
"computed_values": "Computed Values",
|
||||||
|
"cost": "Cost",
|
||||||
|
"creature": "Creature",
|
||||||
|
"effect": "Effect",
|
||||||
|
"error_select_skill": "Select a skill",
|
||||||
|
"error_unknown_actor": "Unknown actor",
|
||||||
|
"instincts": "Instincts",
|
||||||
|
"instincts_placeholder": "eg: Aggressive, Defensive",
|
||||||
|
"learn": "Learn",
|
||||||
|
"major_objectives": "Major Objectives",
|
||||||
|
"minor_objectives": "Minor Objectives",
|
||||||
|
"morale": "Morale",
|
||||||
|
"needed": "Needed",
|
||||||
|
"objective_placeholder": "eg: Survive, Explore",
|
||||||
|
"objectives": "Objectives",
|
||||||
|
"pack": "Pack",
|
||||||
|
"pattern": "Pattern",
|
||||||
|
"preferred_category": "Preferred Category",
|
||||||
|
"prohibits": "Prohibits",
|
||||||
|
"prohibits_placeholder": "eg: Fire, Water",
|
||||||
|
"rarity_0": "Common",
|
||||||
|
"rarity_1": "Rare",
|
||||||
|
"rarity_2": "Very Rare",
|
||||||
|
"reserves": "Reserves",
|
||||||
|
"ritual": "Ritual",
|
||||||
|
"sexes": {
|
||||||
|
"female": "Female",
|
||||||
|
"male": "Male"
|
||||||
|
},
|
||||||
|
"size": "Size",
|
||||||
|
"total_attack": "Total Attack",
|
||||||
|
"total_damage": "Total Damage",
|
||||||
|
"trance": "Trance",
|
||||||
|
"vote_reserve": "Vote Reserve",
|
||||||
|
"wound_thresholds": "Wound Thresholds",
|
||||||
|
"types": {
|
||||||
|
"shield": "Shield"
|
||||||
|
},
|
||||||
|
"roll": "Roll",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"playMode": "Play Mode",
|
||||||
|
"editMode": "Edit Mode"
|
||||||
},
|
},
|
||||||
"UI": {
|
"UI": {
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
@@ -195,9 +318,15 @@
|
|||||||
"temporary": "Temporary effects",
|
"temporary": "Temporary effects",
|
||||||
"passive": "Passive effects",
|
"passive": "Passive effects",
|
||||||
"inactive": "Inactive effects"
|
"inactive": "Inactive effects"
|
||||||
}
|
},
|
||||||
|
"effects.inactive": "Inactive",
|
||||||
|
"effects.passive": "Passive",
|
||||||
|
"effects.temporary": "Temporary"
|
||||||
},
|
},
|
||||||
"ITEMS": {
|
"ITEMS": {
|
||||||
|
"item": "Item",
|
||||||
|
"items": "Items",
|
||||||
|
"new_item": "New item",
|
||||||
"defense": "Protection",
|
"defense": "Protection",
|
||||||
"defenses": "Protections",
|
"defenses": "Protections",
|
||||||
"new_defense": "New protection",
|
"new_defense": "New protection",
|
||||||
@@ -216,6 +345,12 @@
|
|||||||
"abilities": "Abilities",
|
"abilities": "Abilities",
|
||||||
"specialties": "Specialties",
|
"specialties": "Specialties",
|
||||||
"new_specialty": "New specialty",
|
"new_specialty": "New specialty",
|
||||||
|
"target": "Target",
|
||||||
|
"targets": "Targets",
|
||||||
|
"new_target": "New target",
|
||||||
|
"rite": "Rite",
|
||||||
|
"rites": "Rites",
|
||||||
|
"new_rite": "New rite",
|
||||||
"evolution": "Adaptation",
|
"evolution": "Adaptation",
|
||||||
"new_evolution": "New adaptation",
|
"new_evolution": "New adaptation",
|
||||||
"evolutions": "Adaptations",
|
"evolutions": "Adaptations",
|
||||||
@@ -227,7 +362,8 @@
|
|||||||
"rituel": "Ritual",
|
"rituel": "Ritual",
|
||||||
"transe": "Trance",
|
"transe": "Trance",
|
||||||
"effects": "Effects",
|
"effects": "Effects",
|
||||||
"details": "Details"
|
"details": "Details",
|
||||||
|
"shield": "Shield"
|
||||||
},
|
},
|
||||||
"ABILITIES": {
|
"ABILITIES": {
|
||||||
"vigor": {
|
"vigor": {
|
||||||
@@ -498,10 +634,26 @@
|
|||||||
},
|
},
|
||||||
"ruins": {
|
"ruins": {
|
||||||
"name": "Ruins"
|
"name": "Ruins"
|
||||||
|
},
|
||||||
|
"athletics": {
|
||||||
|
"name": "Athletics"
|
||||||
|
},
|
||||||
|
"fauna": {
|
||||||
|
"name": "Fauna"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SEXES": {
|
"SEXES": {
|
||||||
"male": "Masculine",
|
"male": "Masculine",
|
||||||
"female": "Feminine"
|
"female": "Feminine"
|
||||||
|
},
|
||||||
|
"COMBAT": {
|
||||||
|
"Encounter": "Encounter",
|
||||||
|
"None": "None",
|
||||||
|
"Round": "Round"
|
||||||
|
},
|
||||||
|
"SIZE_LEVELS": {
|
||||||
|
"small": "Small",
|
||||||
|
"medium": "Medium",
|
||||||
|
"large": "Large"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,26 @@
|
|||||||
{
|
{
|
||||||
|
"NAME": "Nom",
|
||||||
|
"NONE": "Aucun",
|
||||||
|
"ORIGIN": "Origine",
|
||||||
|
"PROFILE": "Profil",
|
||||||
|
"THEME": "Concept",
|
||||||
|
"TOTEM": "Totem",
|
||||||
|
"TYPES.Item.item": "Objet",
|
||||||
|
"TYPES.Item.weapon": "Arme",
|
||||||
|
"TYPES.Item.defense": "Protection",
|
||||||
|
"TYPES.Item.vehicle": "Véhicule",
|
||||||
|
"TYPES.Item.ability": "Capacité",
|
||||||
|
"TYPES.Item.specialty": "Spécialité",
|
||||||
|
"TYPES.Item.background": "Historique",
|
||||||
|
"TYPES.Item.trauma": "Traumatisme",
|
||||||
|
"TYPES.Item.evolution": "Adaptation",
|
||||||
|
"TYPES.Item.rumor": "Rumeur",
|
||||||
|
"TYPES.Item.target": "Cible",
|
||||||
|
"TYPES.Item.rite": "Rite",
|
||||||
|
"TYPES.Actor.character": "Personnage",
|
||||||
|
"TYPES.Actor.npc": "PNJ",
|
||||||
|
"TYPES.Actor.group": "Groupe",
|
||||||
|
"TYPES.Actor.creature": "Créature",
|
||||||
"SETTINGS": {
|
"SETTINGS": {
|
||||||
"world": {
|
"world": {
|
||||||
"game_mode": {
|
"game_mode": {
|
||||||
@@ -132,9 +154,13 @@
|
|||||||
"preferred_category": "Catégorie préférée",
|
"preferred_category": "Catégorie préférée",
|
||||||
"wounds": {
|
"wounds": {
|
||||||
"name": "Blessures",
|
"name": "Blessures",
|
||||||
"light": "Légères",
|
"threshold": "Seuil",
|
||||||
"heavy": "Graves",
|
"light": "Légère",
|
||||||
"deadly": "Mortelles"
|
"heavy": "Grave",
|
||||||
|
"deadly": "Mortelle",
|
||||||
|
"light_wounds": "Blessure légère",
|
||||||
|
"heavy_wounds": "Blessure grave",
|
||||||
|
"deadly_wounds": "Blessure mortelle"
|
||||||
},
|
},
|
||||||
"ability_category": {
|
"ability_category": {
|
||||||
"physical": "Physiques",
|
"physical": "Physiques",
|
||||||
@@ -153,6 +179,7 @@
|
|||||||
"rarity_0": "Commun",
|
"rarity_0": "Commun",
|
||||||
"rarity_1": "Peu commun",
|
"rarity_1": "Peu commun",
|
||||||
"rarity_2": "Rare",
|
"rarity_2": "Rare",
|
||||||
|
"rarity_3": "Très rare",
|
||||||
"test_of": "test de",
|
"test_of": "test de",
|
||||||
"rerolls_possible": "relances possibles",
|
"rerolls_possible": "relances possibles",
|
||||||
"grant_reroll": "accorder des relances",
|
"grant_reroll": "accorder des relances",
|
||||||
@@ -162,7 +189,7 @@
|
|||||||
"error_cannot_reroll": "Vous ne pouvez pas relancer un dés sur ce jet",
|
"error_cannot_reroll": "Vous ne pouvez pas relancer un dés sur ce jet",
|
||||||
"error_no_rerolls_left": "Plus de relances possibles",
|
"error_no_rerolls_left": "Plus de relances possibles",
|
||||||
"error_select_ability": "Veuillez sélectionner une caractéristique",
|
"error_select_ability": "Veuillez sélectionner une caractéristique",
|
||||||
"error_not_enough_self_control": "Pas assez de points de sang-froid",
|
"error_not_enough_self_control": "Vous n'avez pas assez de Sang-Froid",
|
||||||
"FightTool": "Outil de combat",
|
"FightTool": "Outil de combat",
|
||||||
"Roll4Fight": "Lancer pour le combat",
|
"Roll4Fight": "Lancer pour le combat",
|
||||||
"Selected": "Sélectionné",
|
"Selected": "Sélectionné",
|
||||||
@@ -174,7 +201,9 @@
|
|||||||
"group": "Groupe",
|
"group": "Groupe",
|
||||||
"abilities": "Caractéristiques",
|
"abilities": "Caractéristiques",
|
||||||
"ability": "Caractéristique",
|
"ability": "Caractéristique",
|
||||||
|
"skills": "Compétences",
|
||||||
"skills_title": "Compétences",
|
"skills_title": "Compétences",
|
||||||
|
"skill": "Compétence",
|
||||||
"skill_title": "Compétence",
|
"skill_title": "Compétence",
|
||||||
"skill_mastery": "Maîtrise",
|
"skill_mastery": "Maîtrise",
|
||||||
"bonus": "Bonus",
|
"bonus": "Bonus",
|
||||||
@@ -182,6 +211,8 @@
|
|||||||
"reroll": "Relance",
|
"reroll": "Relance",
|
||||||
"equipment": "Equipement",
|
"equipment": "Equipement",
|
||||||
"specialty": "Spécialité",
|
"specialty": "Spécialité",
|
||||||
|
"specificLevel": "Niveau spécifique",
|
||||||
|
"competence": "Compétence",
|
||||||
"technique": "Technique",
|
"technique": "Technique",
|
||||||
"techniques": "Techniques",
|
"techniques": "Techniques",
|
||||||
"difficulty": "Difficulté",
|
"difficulty": "Difficulté",
|
||||||
@@ -200,16 +231,6 @@
|
|||||||
"rarity_sm": "Rar.",
|
"rarity_sm": "Rar.",
|
||||||
"reliability": "Fiabilité",
|
"reliability": "Fiabilité",
|
||||||
"reliability_sm": "Fiab.",
|
"reliability_sm": "Fiab.",
|
||||||
"wounds": {
|
|
||||||
"name": "Blessures",
|
|
||||||
"threshold": "Seuil",
|
|
||||||
"light": "Légère",
|
|
||||||
"heavy": "Grave",
|
|
||||||
"deadly": "Mortelle",
|
|
||||||
"light_wounds": "Blessure légère",
|
|
||||||
"heavy_wounds": "Blessure grave",
|
|
||||||
"deadly_wounds": "Blessure mortelle"
|
|
||||||
},
|
|
||||||
"choose_ability": "Choisissez une caractéristique",
|
"choose_ability": "Choisissez une caractéristique",
|
||||||
"choose_skill": "Choisissez une compétence",
|
"choose_skill": "Choisissez une compétence",
|
||||||
"none": "Aucun",
|
"none": "Aucun",
|
||||||
@@ -220,8 +241,6 @@
|
|||||||
"totem_dice": "Dés de totem",
|
"totem_dice": "Dés de totem",
|
||||||
"keep_totem": "Garder le totem",
|
"keep_totem": "Garder le totem",
|
||||||
"totem_hint": "Cochez pour utiliser les dés de totem (double réussite possible)",
|
"totem_hint": "Cochez pour utiliser les dés de totem (double réussite possible)",
|
||||||
"error_not_enough_self_control": "Vous n'avez pas assez de Sang-Froid",
|
|
||||||
"error_select_ability": "Veuillez sélectionner une caractéristique",
|
|
||||||
"error_select_skill": "Veuillez sélectionner une compétence",
|
"error_select_skill": "Veuillez sélectionner une compétence",
|
||||||
"needed": "nécessaire",
|
"needed": "nécessaire",
|
||||||
"cost": "Coût",
|
"cost": "Coût",
|
||||||
@@ -243,11 +262,15 @@
|
|||||||
"ammo_sm": "Mun",
|
"ammo_sm": "Mun",
|
||||||
"trait": "Trait",
|
"trait": "Trait",
|
||||||
"traits": "Traits",
|
"traits": "Traits",
|
||||||
|
"traits_selector": "Sélecteur de traits",
|
||||||
"clew": "Indice",
|
"clew": "Indice",
|
||||||
"combat": "Combat",
|
"combat": "Combat",
|
||||||
"stories": "Histoires",
|
"stories": "Histoires",
|
||||||
"gear": "Matériel",
|
"gear": "Matériel",
|
||||||
"information": "Informations",
|
"information": "Informations",
|
||||||
|
"editMode": "Mode édition",
|
||||||
|
"playMode": "Mode jeu",
|
||||||
|
"modes": "Modes de jeu",
|
||||||
"boost": "boost",
|
"boost": "boost",
|
||||||
"group_abilities": "Capacités de groupe",
|
"group_abilities": "Capacités de groupe",
|
||||||
"totem_abilities": "Capacités de totem",
|
"totem_abilities": "Capacités de totem",
|
||||||
@@ -268,7 +291,6 @@
|
|||||||
"minor_objectives": "Objectifs mineurs",
|
"minor_objectives": "Objectifs mineurs",
|
||||||
"instincts": "Instincts",
|
"instincts": "Instincts",
|
||||||
"prohibits": "Interdictions",
|
"prohibits": "Interdictions",
|
||||||
"group_abilities": "Capacités de groupe",
|
|
||||||
"vote_reserve": "Vote pour utiliser la réserve",
|
"vote_reserve": "Vote pour utiliser la réserve",
|
||||||
"morale_high": "Haut",
|
"morale_high": "Haut",
|
||||||
"morale_normal": "Normal",
|
"morale_normal": "Normal",
|
||||||
@@ -286,7 +308,15 @@
|
|||||||
"base_values": "Valeurs de base",
|
"base_values": "Valeurs de base",
|
||||||
"total_attack": "Attaque totale",
|
"total_attack": "Attaque totale",
|
||||||
"total_damage": "Dégâts totaux",
|
"total_damage": "Dégâts totaux",
|
||||||
"wound_thresholds": "Seuils de blessures"
|
"wound_thresholds": "Seuils de blessures",
|
||||||
|
"CharacterNamePlaceholder": "Nom du personnage",
|
||||||
|
"sexes": {
|
||||||
|
"female": "Féminin",
|
||||||
|
"male": "Masculin"
|
||||||
|
},
|
||||||
|
"RollTool": "Outil de lancer de dés",
|
||||||
|
"roll": "Lancer",
|
||||||
|
"cancel": "Annuler"
|
||||||
},
|
},
|
||||||
"UI": {
|
"UI": {
|
||||||
"add": "Ajouter",
|
"add": "Ajouter",
|
||||||
@@ -304,6 +334,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ITEMS": {
|
"ITEMS": {
|
||||||
|
"item": "Objet",
|
||||||
|
"items": "Objets",
|
||||||
|
"new_item": "Nouvel objet",
|
||||||
"defense": "Protection",
|
"defense": "Protection",
|
||||||
"defenses": "Protections",
|
"defenses": "Protections",
|
||||||
"new_defense": "Nouvelle protection",
|
"new_defense": "Nouvelle protection",
|
||||||
@@ -322,6 +355,12 @@
|
|||||||
"abilities": "Capacités",
|
"abilities": "Capacités",
|
||||||
"specialties": "Spécialités",
|
"specialties": "Spécialités",
|
||||||
"new_specialty": "Nouvelle spécialité",
|
"new_specialty": "Nouvelle spécialité",
|
||||||
|
"target": "Cible",
|
||||||
|
"targets": "Cibles",
|
||||||
|
"new_target": "Nouvelle cible",
|
||||||
|
"rite": "Rite",
|
||||||
|
"rites": "Rites",
|
||||||
|
"new_rite": "Nouveau rite",
|
||||||
"shield": "Bouclier",
|
"shield": "Bouclier",
|
||||||
"evolution": "Adaptation",
|
"evolution": "Adaptation",
|
||||||
"new_evolution": "Nouvelle adaptation",
|
"new_evolution": "Nouvelle adaptation",
|
||||||
@@ -522,7 +561,7 @@
|
|||||||
"name": "Arts"
|
"name": "Arts"
|
||||||
},
|
},
|
||||||
"civilization": {
|
"civilization": {
|
||||||
"name": "Civilisations"
|
"name": "Civilisation"
|
||||||
},
|
},
|
||||||
"psychology": {
|
"psychology": {
|
||||||
"name": "Psychologie"
|
"name": "Psychologie"
|
||||||
@@ -539,14 +578,14 @@
|
|||||||
"dissection": {
|
"dissection": {
|
||||||
"name": "Dissection"
|
"name": "Dissection"
|
||||||
},
|
},
|
||||||
"wildlife": {
|
"fauna": {
|
||||||
"name": "Faune"
|
"name": "Faune"
|
||||||
},
|
},
|
||||||
"repulsion": {
|
"repulsion": {
|
||||||
"name": "Répulsion"
|
"name": "Répulsion"
|
||||||
},
|
},
|
||||||
"tracks": {
|
"tracks": {
|
||||||
"name": "Traces"
|
"name": "Pistage"
|
||||||
},
|
},
|
||||||
"crafting": {
|
"crafting": {
|
||||||
"name": "Artisanat"
|
"name": "Artisanat"
|
||||||
@@ -566,42 +605,39 @@
|
|||||||
"firearms": {
|
"firearms": {
|
||||||
"name": "Armes à feu"
|
"name": "Armes à feu"
|
||||||
},
|
},
|
||||||
|
"archery": {
|
||||||
|
"name": "Tir à l'arc"
|
||||||
|
},
|
||||||
"armory": {
|
"armory": {
|
||||||
"name": "Armurerie"
|
"name": "Armurerie"
|
||||||
},
|
},
|
||||||
"shield": {
|
|
||||||
"name": "Bouclier"
|
|
||||||
},
|
|
||||||
"close": {
|
|
||||||
"name": "Corps-à-corps"
|
|
||||||
},
|
|
||||||
"archery": {
|
|
||||||
"name": "Armes de tir"
|
|
||||||
},
|
|
||||||
"throwing": {
|
"throwing": {
|
||||||
"name": "Lancer"
|
"name": "Lancer"
|
||||||
},
|
},
|
||||||
"melee": {
|
"melee": {
|
||||||
"name": "Mêlée"
|
"name": "Mêlée"
|
||||||
},
|
},
|
||||||
"atletics": {
|
|
||||||
"name": "Atlétisme"
|
|
||||||
},
|
|
||||||
"stealth": {
|
|
||||||
"name": "Discrétion"
|
|
||||||
},
|
|
||||||
"alertness": {
|
"alertness": {
|
||||||
"name": "Vigilance"
|
"name": "Vigilance"
|
||||||
},
|
},
|
||||||
"flora": {
|
"athletics": {
|
||||||
"name": "Flore"
|
"name": "Athlétisme"
|
||||||
},
|
},
|
||||||
"food": {
|
"food": {
|
||||||
"name": "Alimentation"
|
"name": "Alimentation"
|
||||||
},
|
},
|
||||||
|
"stealth": {
|
||||||
|
"name": "Discrétion"
|
||||||
|
},
|
||||||
|
"close": {
|
||||||
|
"name": "Corps-à-corps"
|
||||||
|
},
|
||||||
"environment": {
|
"environment": {
|
||||||
"name": "Environnement"
|
"name": "Environnement"
|
||||||
},
|
},
|
||||||
|
"flora": {
|
||||||
|
"name": "Flore"
|
||||||
|
},
|
||||||
"road": {
|
"road": {
|
||||||
"name": "Route"
|
"name": "Route"
|
||||||
},
|
},
|
||||||
@@ -610,42 +646,28 @@
|
|||||||
},
|
},
|
||||||
"ruins": {
|
"ruins": {
|
||||||
"name": "Vestiges"
|
"name": "Vestiges"
|
||||||
|
},
|
||||||
|
"shield": {
|
||||||
|
"name": "Bouclier"
|
||||||
|
},
|
||||||
|
"wildlife": {
|
||||||
|
"name": "Faune"
|
||||||
|
},
|
||||||
|
"atletics": {
|
||||||
|
"name": "Athlétisme"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SEXES": {
|
"SEXES": {
|
||||||
"male": "Masculin",
|
"male": "Masculin",
|
||||||
"female": "Féminin"
|
"female": "Féminin"
|
||||||
},
|
},
|
||||||
"SKILLS": {
|
"COMBAT": {
|
||||||
"arts": "Arts",
|
"Begin": "Commencer",
|
||||||
"civilization": "Civilisation",
|
"Encounter": "Rencontre",
|
||||||
"psychology": "Psychologie",
|
"End": "Terminer",
|
||||||
"rumors": "Rumeurs",
|
"None": "Aucun",
|
||||||
"healing": "Soins",
|
"NotStarted": "Non commencé",
|
||||||
"animalism": "Animalisme",
|
"Round": "Tour",
|
||||||
"dissection": "Dissection",
|
"TurnEnd": "Fin de tour"
|
||||||
"fauna": "Faune",
|
|
||||||
"repulsion": "Répulsion",
|
|
||||||
"tracks": "Pistage",
|
|
||||||
"crafting": "Artisanat",
|
|
||||||
"diy": "Bricolage",
|
|
||||||
"mecanical": "Mécanique",
|
|
||||||
"piloting": "Pilotage",
|
|
||||||
"technology": "Technologie",
|
|
||||||
"firearms": "Armes à feu",
|
|
||||||
"archery": "Tir à l'arc",
|
|
||||||
"armory": "Armurerie",
|
|
||||||
"throwing": "Lancer",
|
|
||||||
"melee": "Mêlée",
|
|
||||||
"alertness": "Vigilance",
|
|
||||||
"athletics": "Athlétisme",
|
|
||||||
"food": "Alimentation",
|
|
||||||
"stealth": "Discrétion",
|
|
||||||
"close": "Corps-à-corps",
|
|
||||||
"environment": "Environnement",
|
|
||||||
"flora": "Flore",
|
|
||||||
"road": "Route",
|
|
||||||
"toxics": "Toxiques",
|
|
||||||
"ruins": "Vestiges"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,641 @@
|
|||||||
|
@import "totem";
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.actor {
|
||||||
|
|
||||||
|
// ── Fix contrast: dark text on light parchment bg ───────────────────
|
||||||
|
.window-content,
|
||||||
|
& {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
|
||||||
|
label, span, p, li, a:not(.active) {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4, h5 {
|
||||||
|
color: @color-text-dark-header;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: @theme-color-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:not([type="radio"]):not([type="checkbox"]),
|
||||||
|
select, textarea {
|
||||||
|
color: @color-text-light-0;
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 1px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-header {
|
||||||
|
h1 label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
.char-name-value {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
input:not([type="radio"]):not([type="checkbox"]) {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability input.hexa {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-label {
|
||||||
|
color: @color-text-dark-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability label {
|
||||||
|
color: @color-text-dark-header;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-header {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: @color-text-dark-header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hexa {
|
||||||
|
color: @color-text-dark-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-tabs a {
|
||||||
|
color: @color-text-dark-inactive;
|
||||||
|
&.active {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Two-column layout for character sheets ──────────────────────────
|
||||||
|
&.character {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
grid-template-rows: auto 1fr auto !important;
|
||||||
|
|
||||||
|
> header.window-header {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> section.window-content {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div.window-resize-handle {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.character .window-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 240px 1fr;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> .tab.main {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1 / -1;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
> nav.tabs {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .tab:not(.main) {
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 2;
|
||||||
|
display: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Stacked layout for NPC sheets (header + tabs + content) ────────
|
||||||
|
&.npc {
|
||||||
|
display: grid !important;
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
grid-template-rows: auto 1fr auto !important;
|
||||||
|
|
||||||
|
> header.window-header {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
> section.window-content {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
> div.window-resize-handle {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.npc .window-content {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto auto 1fr;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> .tab.main {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
> nav.tabs {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .tab:not(.main) {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 3;
|
||||||
|
display: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> .tab {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.charname input {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: 30px;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.hexa {
|
||||||
|
.hexa-style();
|
||||||
|
.transition();
|
||||||
|
margin: 0.2rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.hexa-style(rgba(255, 255, 255, 0.425), rgba(0, 0, 0, 0.288));
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
opacity: 1;
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
.hexa-style(rgb(0, 0, 0), rgba(0, 0, 0, 0.288));
|
||||||
|
&:hover { .hexa-style(rgb(43, 43, 43), rgba(0, 0, 0, 0.288)); }
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unavailable {
|
||||||
|
background: radial-gradient(circle, rgba(66, 15, 15, 0.664) 0%, rgba(131, 70, 70, 0.432) 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="number"] {
|
||||||
|
width: calc(100% - 2px);
|
||||||
|
height: calc(100% - 2px);
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 1px 0;
|
||||||
|
color: #333;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0);
|
||||||
|
|
||||||
|
&.hexa { .hexa-style(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
input[disabled],
|
||||||
|
select[disabled] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:hover:not(:disabled),
|
||||||
|
input[type="text"]:focus,
|
||||||
|
select:hover:not(:disabled),
|
||||||
|
select:focus,
|
||||||
|
input[type="number"]:hover:not(:disabled),
|
||||||
|
input[type="number"]:focus,
|
||||||
|
textarea:hover:not(:disabled),
|
||||||
|
textarea:focus {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
border: none;
|
||||||
|
appearance: none;
|
||||||
|
min-width: fit-content;
|
||||||
|
max-width: fit-content;
|
||||||
|
padding: 0 0.2rem;
|
||||||
|
margin: 0 0.2rem;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mce-panel span {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rollable:hover,
|
||||||
|
a:hover {
|
||||||
|
color: #000;
|
||||||
|
text-shadow: 0 5px 5px @theme-color-accent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chooseTotem {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-shadow: 0 0 10px @theme-color-highlight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-header-toggle {
|
||||||
|
text-align: right;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 0.2rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, cover, 50% 0%);
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.979) inset;
|
||||||
|
color: @theme-color-dark;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-wrapper {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 80%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.padding-with-frieze {
|
||||||
|
margin-left: 18% !important;
|
||||||
|
margin-right: 10% !important;
|
||||||
|
|
||||||
|
li {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.major-totem {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
position: absolute;
|
||||||
|
transform: rotate(-8deg);
|
||||||
|
opacity: 1;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.paper {
|
||||||
|
margin-top: 1rem;
|
||||||
|
height: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.second-paper {
|
||||||
|
margin-top: 4rem;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-header {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.char-name,
|
||||||
|
.char-vermine2047 {
|
||||||
|
border-bottom: none;
|
||||||
|
line-height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-vermine2047 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: @theme-color-light;
|
||||||
|
font-size: 1.7rem;
|
||||||
|
border-bottom: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: 1.4em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0 0 0.2rem;
|
||||||
|
|
||||||
|
&.characteristics {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-top: 0.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.totem &,
|
||||||
|
.tab.equipment &,
|
||||||
|
.tab.stories & {
|
||||||
|
margin-top: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.tabs[data-group="sheet"] {
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
height: 54px;
|
||||||
|
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, auto, right top);
|
||||||
|
background-size: 100% 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 4rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
height: 2.4rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
z-index: 1;
|
||||||
|
transition: all 0.1s ease-out;
|
||||||
|
color: #606060;
|
||||||
|
box-shadow: 0px 0px 0px rgba(0, 0, 0, 0.404);
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.active {
|
||||||
|
color: #000;
|
||||||
|
text-shadow: 0 5px 5px @theme-color-accent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-shadow: 0 5px 5px rgba(30, 82, 37, 0.6039215686);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability {
|
||||||
|
padding-right: 0.6rem;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
border-bottom: 1px solid rgba(170, 170, 152, 0.664);
|
||||||
|
box-shadow: 0px 0px 15px rgba(128, 128, 128, 0) inset;
|
||||||
|
transition: 0.2s;
|
||||||
|
position: relative;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
min-width: min-content;
|
||||||
|
container-type: inline-size;
|
||||||
|
container-name: ability-row;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px 0px 15px gray inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
min-width: 40%;
|
||||||
|
flex: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
max-width: fit-content;
|
||||||
|
margin: 0 1rem;
|
||||||
|
flex: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.specialties {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -0.2rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-dots {
|
||||||
|
height: 100%;
|
||||||
|
align-self: center;
|
||||||
|
flex: 1.5;
|
||||||
|
min-width: fit-content;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
max-width: 0.7rem;
|
||||||
|
height: 0.7rem;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 0.2rem;
|
||||||
|
font-style: oblique;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
|
&.dice-pool-dot {
|
||||||
|
.background-image(radial-gradient(circle, @dice-pool-color 25%, rgb(0, 0, 0) 100%), @dice-pool-color);
|
||||||
|
max-width: 0.7rem;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dice-reroll-dot {
|
||||||
|
background: radial-gradient(circle, @dice-reroll-color 25%, rgb(0, 0, 0) 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-category {
|
||||||
|
padding: 0.3rem;
|
||||||
|
|
||||||
|
&.preferred {
|
||||||
|
box-shadow: 0px 0px 30px rgba(0, 128, 0, 0.306) inset;
|
||||||
|
|
||||||
|
h4, label {
|
||||||
|
text-shadow: 0px 0px 5px rgba(0, 128, 0, 0.411);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#edit {
|
||||||
|
background-color: #000;
|
||||||
|
color: #fff;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reserve-grid {
|
||||||
|
line-height: 0.5rem;
|
||||||
|
transform-origin: 0% 50%;
|
||||||
|
max-width: fit-content;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
div.flexrow,
|
||||||
|
input,
|
||||||
|
.hexa {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
min-width: 1rem;
|
||||||
|
min-height: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .flexrow {
|
||||||
|
position: relative;
|
||||||
|
max-width: fit-content;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-list {
|
||||||
|
.item-name .item-image {
|
||||||
|
flex: 0 0 30px;
|
||||||
|
height: 30px;
|
||||||
|
background-size: 30px;
|
||||||
|
border: none;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-list .item.flexrow > div:not(.item-name):not(.item-controls) {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px;
|
||||||
|
border-bottom: 1px solid rgba(170, 170, 152, 0.664);
|
||||||
|
|
||||||
|
&:last-child { border-bottom: none; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── V2 tab navigation — chitin-plate style ─────────────────────────
|
||||||
|
nav.sheet-tabs[data-application-part="tabs"] {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: @color-chitin-dark;
|
||||||
|
border-bottom: 2px solid @color-amber;
|
||||||
|
min-height: 36px;
|
||||||
|
|
||||||
|
> a {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-11;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 16px;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
opacity: 1;
|
||||||
|
transition: background 0.15s;
|
||||||
|
position: relative;
|
||||||
|
border-right: 1px solid fade(@color-membrane, 12%);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: fade(@color-honeycomb, 10%);
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: linear-gradient(180deg, @color-amber 0%, @color-honeycomb 100%);
|
||||||
|
color: @color-chitin-dark;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 2px;
|
||||||
|
background: @color-acid-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> i {
|
||||||
|
font-size: @font-size-14;
|
||||||
|
width: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
> span, > i {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@container ability-row (max-width: 240px) {
|
||||||
|
.skill-dots,
|
||||||
|
span.hexa {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
@import "../variables";
|
||||||
|
@import "../utilities";
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.actor.creature {
|
||||||
|
.sheet-header {
|
||||||
|
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top);
|
||||||
|
padding: 10px;
|
||||||
|
max-height: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fields { flex: 1; }
|
||||||
|
.resources { margin-bottom: 10px; }
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
.card-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 60px;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
select {
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.charname {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
font-size: @font-size-18;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
.card-style();
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
label { font-weight: bold; }
|
||||||
|
span {
|
||||||
|
font-weight: bold;
|
||||||
|
color: @theme-color-light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mdb {
|
||||||
|
&:not(.row) {
|
||||||
|
.card-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
h4:first-child {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
margin-top: 0;
|
||||||
|
border-bottom: 1px solid @color-border-dark-3;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
text-align: center;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.unstyled {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 3px 0;
|
||||||
|
border-bottom: 1px solid @color-border-dark-3;
|
||||||
|
|
||||||
|
&:last-child { border-bottom: none; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-3col {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row.mdb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
@import "../variables";
|
||||||
|
@import "../utilities";
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.actor.group {
|
||||||
|
.char-header {
|
||||||
|
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top);
|
||||||
|
max-height: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char-details {
|
||||||
|
h1.char-name input,
|
||||||
|
.char-vermine2047 a.chooseTotem {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
}
|
||||||
|
|
||||||
|
.reserve-control,
|
||||||
|
.morale-control {
|
||||||
|
.card-style();
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select {
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
li.actor {
|
||||||
|
padding: 5px;
|
||||||
|
border-bottom: 1px solid @color-border-dark-3;
|
||||||
|
|
||||||
|
&:last-child { border-bottom: none; }
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actor-name, .item-name {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.objective-item {
|
||||||
|
padding: 5px;
|
||||||
|
border-bottom: 1px solid @color-border-dark-4;
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-display {
|
||||||
|
.card-style();
|
||||||
|
|
||||||
|
span { font-weight: bold; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.identity-field {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input { width: 100%; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.level input,
|
||||||
|
.reputation input {
|
||||||
|
width: 60px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
@import "../variables";
|
||||||
|
@import "../utilities";
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.actor.npc {
|
||||||
|
.sheet-header {
|
||||||
|
.background-image(url("@{ui-path}/barre_haut.webp"), no-repeat, 100% 100%, right top);
|
||||||
|
padding: 0.5rem;
|
||||||
|
max-height: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-value,
|
||||||
|
.resource-value {
|
||||||
|
min-width: 30px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card, .npc-card {
|
||||||
|
.card-style();
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
.card-title();
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
|
||||||
|
i { margin-right: 5px; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skills-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-category {
|
||||||
|
.card-style();
|
||||||
|
padding: 10px;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 5px;
|
||||||
|
border-bottom: 1px solid @color-border-dark-3;
|
||||||
|
|
||||||
|
&:last-child { border-bottom: none; }
|
||||||
|
|
||||||
|
label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-value {
|
||||||
|
min-width: 25px;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rarity-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 5px;
|
||||||
|
|
||||||
|
&.rarity-0 { .rarity-badge(@rarity-0-bg, @rarity-0-text); }
|
||||||
|
&.rarity-1 { .rarity-badge(@rarity-1-bg, @rarity-1-text); }
|
||||||
|
&.rarity-2 { .rarity-badge(@rarity-2-bg, @rarity-2-text); }
|
||||||
|
&.rarity-3 { .rarity-badge(@rarity-3-bg, @rarity-3-text); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.skill-category-selector {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
.card-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
.wounds-card {
|
||||||
|
margin-top: 15px;
|
||||||
|
.card-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
.wound-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wound-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
input { flex: 1; }
|
||||||
|
span {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
min-width: 30px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-section {
|
||||||
|
.card-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-category-header {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-bottom: 1px solid @color-border-dark-3;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 5px 0;
|
||||||
|
|
||||||
|
label {
|
||||||
|
flex: 1;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
padding: 10px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
.form-group-style();
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fields {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resources {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
.card-style();
|
||||||
|
|
||||||
|
label { font-weight: bold; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-label {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
font-weight: bold;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
padding: 3px 5px;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
min-height: 100px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.editor-content {
|
||||||
|
min-height: 80px;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hexa {
|
||||||
|
.hexa-style();
|
||||||
|
.transition();
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.hexa-style(rgba(255, 255, 255, 0.425), rgba(0, 0, 0, 0.288));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
@import "../variables";
|
||||||
|
@import "../utilities";
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.actor {
|
||||||
|
.totem-details {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img.img-totem {
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
filter: grayscale(1);
|
||||||
|
opacity: 0.15;
|
||||||
|
position: absolute;
|
||||||
|
width: 30%;
|
||||||
|
height: auto;
|
||||||
|
pointer-events: none;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
left: 35%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.minor-totems {
|
||||||
|
position: relative;
|
||||||
|
background-color: rgba(146, 156, 111, 0.5215686275);
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 2rem;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.human, &.adapted {
|
||||||
|
.transition(0.3s);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.human img.img-totem,
|
||||||
|
&.adapted img.img-totem {
|
||||||
|
filter: drop-shadow(0px 0px 20px rgb(0, 0, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
&.human.major,
|
||||||
|
&.adapted.major {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.human.major img,
|
||||||
|
&.adapted.major img {
|
||||||
|
filter: drop-shadow(0px 0px 10px red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-dice {
|
||||||
|
.human-dice,
|
||||||
|
.adapted-dice {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.human-dice i,
|
||||||
|
.adapted-dice i {
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
color: @totem-human-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.adapted-dice {
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: 2rem;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.adapted-dice i {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
color: @totem-adapted-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.human {
|
||||||
|
left: 0;
|
||||||
|
img { left: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.adapted {
|
||||||
|
right: 0;
|
||||||
|
img { right: 0; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,650 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
src: url("@{fonts-path}/dcc_sharp_distress_black_by_dccanim.otf");
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.system-vermine2047 {
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
color: @theme-color-secondary;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.unstyled {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx-auto {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-app {
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
.shadow(0px, 0px, 30px, rgba(106, 176, 76, 0.25));
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-vermine2047 {
|
||||||
|
.window-content,
|
||||||
|
form.application.sheet.vermine2047 {
|
||||||
|
.background-image(url("@{ui-path}/box_background.webp"), repeat);
|
||||||
|
padding: 10px;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog .window-content {
|
||||||
|
.background-image(url("@{ui-path}/fond_chat_box.webp"), repeat);
|
||||||
|
padding: 0.5rem;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.window-content .row {
|
||||||
|
&.smb { margin-bottom: 0.25rem; }
|
||||||
|
&.mdb { margin-bottom: 0.5rem; }
|
||||||
|
&.lgb { margin-bottom: 1rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.rollable:hover,
|
||||||
|
.rollable:focus {
|
||||||
|
color: #000;
|
||||||
|
text-shadow: 0 0 10px @color-acid-green;
|
||||||
|
cursor: pointer;
|
||||||
|
.transition();
|
||||||
|
}
|
||||||
|
|
||||||
|
.rollable {
|
||||||
|
.transition();
|
||||||
|
}
|
||||||
|
|
||||||
|
img.profile-img {
|
||||||
|
filter: drop-shadow(0px 0px 20px rgb(110, 133, 27));
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.system-vermine2047 img#logo {
|
||||||
|
content: url("@{assets-path}/images/ui/logo_vermine_foundry.webp");
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-log,
|
||||||
|
.chat-log {
|
||||||
|
.message {
|
||||||
|
.background-image(url("@{ui-path}/box_background.webp"), repeat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-column: span 2 / span 2;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
margin: 5px 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-2col { .grid(); }
|
||||||
|
.grid-3col { grid-column: span 3; grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||||
|
.grid-4col { grid-column: span 4; grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
||||||
|
.grid-5col { grid-column: span 5; grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
||||||
|
.grid-6col { grid-column: span 6; grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
||||||
|
.grid-7col { grid-column: span 7; grid-template-columns: repeat(7, minmax(0, 1fr)); }
|
||||||
|
.grid-8col { grid-column: span 8; grid-template-columns: repeat(8, minmax(0, 1fr)); }
|
||||||
|
.grid-9col { grid-column: span 9; grid-template-columns: repeat(9, minmax(0, 1fr)); }
|
||||||
|
.grid-10col { grid-column: span 10; grid-template-columns: repeat(10, minmax(0, 1fr)); }
|
||||||
|
.grid-11col { grid-column: span 11; grid-template-columns: repeat(11, minmax(0, 1fr)); }
|
||||||
|
.grid-12col { grid-column: span 12; grid-template-columns: repeat(12, minmax(0, 1fr)); }
|
||||||
|
|
||||||
|
.grid-start-2 { grid-column-start: 2; }
|
||||||
|
.grid-start-3 { grid-column-start: 3; }
|
||||||
|
.grid-start-4 { grid-column-start: 4; }
|
||||||
|
.grid-start-5 { grid-column-start: 5; }
|
||||||
|
.grid-start-6 { grid-column-start: 6; }
|
||||||
|
.grid-start-7 { grid-column-start: 7; }
|
||||||
|
.grid-start-8 { grid-column-start: 8; }
|
||||||
|
.grid-start-9 { grid-column-start: 9; }
|
||||||
|
.grid-start-10 { grid-column-start: 10; }
|
||||||
|
.grid-start-11 { grid-column-start: 11; }
|
||||||
|
.grid-start-12 { grid-column-start: 12; }
|
||||||
|
|
||||||
|
.grid-span-2 { grid-column-end: span 2; }
|
||||||
|
.grid-span-3 { grid-column-end: span 3; }
|
||||||
|
.grid-span-4 { grid-column-end: span 4; }
|
||||||
|
.grid-span-5 { grid-column-end: span 5; }
|
||||||
|
.grid-span-6 { grid-column-end: span 6; }
|
||||||
|
.grid-span-7 { grid-column-end: span 7; }
|
||||||
|
.grid-span-8 { grid-column-end: span 8; }
|
||||||
|
.grid-span-9 { grid-column-end: span 9; }
|
||||||
|
.grid-span-10 { grid-column-end: span 10; }
|
||||||
|
.grid-span-11 { grid-column-end: span 11; }
|
||||||
|
.grid-span-12 { grid-column-end: span 12; }
|
||||||
|
|
||||||
|
.flex-group-center,
|
||||||
|
.flex-group-left,
|
||||||
|
.flex-group-right {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-group-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-group-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-align-left { align-items: flex-start; }
|
||||||
|
.flex-align-right { align-items: flex-end; }
|
||||||
|
.gap-xs { gap: 2px; }
|
||||||
|
.gap-sm { gap: 4px; }
|
||||||
|
.gap-md { gap: 8px; }
|
||||||
|
.gap-lg { gap: 16px; }
|
||||||
|
.flexshrink { flex: 0; }
|
||||||
|
.flex-between { justify-content: space-between; }
|
||||||
|
.flexlarge { flex: 2; }
|
||||||
|
|
||||||
|
.align-left {
|
||||||
|
justify-content: flex-start;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
justify-content: flex-end;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-center {
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
outline: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #577822;
|
||||||
|
border: 1px solid var(--color-border-highlight);
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-vermine2047 {
|
||||||
|
.item-form {
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-header {
|
||||||
|
flex: 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.profile-img {
|
||||||
|
flex: 0 0 100px;
|
||||||
|
height: 100px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-fields {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.charname {
|
||||||
|
height: 50px;
|
||||||
|
padding: 0px;
|
||||||
|
margin: 5px 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-tabs {
|
||||||
|
flex: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sheet-body .tab,
|
||||||
|
.editor {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
min-height: 75px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
min-width: 100%;
|
||||||
|
|
||||||
|
.editor-content {
|
||||||
|
min-width: 100%;
|
||||||
|
min-height: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor:hover .editor-edit {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tox {
|
||||||
|
min-height: 25vh;
|
||||||
|
|
||||||
|
.tox-editor-container {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tox-edit-area {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-header {
|
||||||
|
height: 28px;
|
||||||
|
margin: 2px 0;
|
||||||
|
padding: 0;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border: 2px groove #eeede0;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
font-weight: bold;
|
||||||
|
padding-left: 5px;
|
||||||
|
text-align: left;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
color: #444;
|
||||||
|
|
||||||
|
.item-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
flex: 2;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: left;
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
h3, h4 {
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-controls {
|
||||||
|
display: flex;
|
||||||
|
flex: 0;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 2px;
|
||||||
|
border-bottom: 1px solid #c9c7b8;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
color: @theme-color-dark;
|
||||||
|
|
||||||
|
.item-image {
|
||||||
|
flex: 0 0 30px;
|
||||||
|
height: 30px;
|
||||||
|
background-size: 30px;
|
||||||
|
border: none;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-prop {
|
||||||
|
text-align: center;
|
||||||
|
border-left: 1px solid #c9c7b8;
|
||||||
|
border-right: 1px solid #c9c7b8;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.items-header {
|
||||||
|
height: 28px;
|
||||||
|
margin: 2px 0;
|
||||||
|
padding: 0;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border: 2px groove #eeede0;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-name {
|
||||||
|
padding-left: 5px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.effects .item {
|
||||||
|
.effect-source,
|
||||||
|
.effect-duration,
|
||||||
|
.effect-controls {
|
||||||
|
text-align: center;
|
||||||
|
border-left: 1px solid #c9c7b8;
|
||||||
|
border-right: 1px solid #c9c7b8;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.effect-controls {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-formula {
|
||||||
|
flex: 0 0 200px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
span.game-mode {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
position: absolute;
|
||||||
|
margin-left: auto;
|
||||||
|
color: rgba(0, 0, 0, 0);
|
||||||
|
top: 1rem;
|
||||||
|
z-index: 900;
|
||||||
|
width: 55%;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 900;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(255, 255, 255, 0.767) 0%,
|
||||||
|
rgba(0, 0, 0, 0.61) 17%,
|
||||||
|
rgba(0, 0, 0, 0.548) 19%,
|
||||||
|
rgba(222, 255, 221, 0.575) 24%,
|
||||||
|
rgba(255, 255, 255, 0.637) 43%,
|
||||||
|
rgba(0, 0, 0, 0.486) 47%,
|
||||||
|
rgba(254, 255, 254, 0.466) 50%,
|
||||||
|
rgba(0, 0, 0, 0.699) 63%,
|
||||||
|
rgba(134, 160, 137, 0.479) 64%,
|
||||||
|
rgba(213, 248, 210, 0.493) 100%
|
||||||
|
);
|
||||||
|
background-clip: text;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.game-mode#game-mode-1 { color: @game-mode-1-color; }
|
||||||
|
span.game-mode#game-mode-2 { color: @game-mode-2-color; }
|
||||||
|
span.game-mode#game-mode-3 { color: @game-mode-3-color; }
|
||||||
|
|
||||||
|
.shadow {
|
||||||
|
.shadow();
|
||||||
|
}
|
||||||
|
|
||||||
|
ol#chat-log,
|
||||||
|
ol.chat-log {
|
||||||
|
header.message-header {
|
||||||
|
background-color: #000;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vermine-roll-message {
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 4px 10px 10px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> h3 {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-14;
|
||||||
|
margin: 6px 0 8px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.3) inset;
|
||||||
|
font-weight: 900;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .flexrow:not(.roll-total):not(.roll-results) {
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 4px 0;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Reroll section ──────────────────────────────────────────────
|
||||||
|
.reroll-fromroll {
|
||||||
|
> h4 {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
margin: 6px 0 4px;
|
||||||
|
border-bottom: none;
|
||||||
|
font-weight: 900;
|
||||||
|
|
||||||
|
#allowed_reroll {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.reroll {
|
||||||
|
.transition(0.3s);
|
||||||
|
max-height: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
font-size: @font-size-12;
|
||||||
|
padding: 2px 12px;
|
||||||
|
max-width: fit-content;
|
||||||
|
box-shadow: 0px 0px 2px #000;
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, cover, 50% 0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.visible { max-height: 15rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Dice results ────────────────────────────────────────────────
|
||||||
|
ul.roll-results {
|
||||||
|
list-style: none;
|
||||||
|
padding: 6px 0;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 6px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
li.die {
|
||||||
|
position: relative;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
flex: none;
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
.transition();
|
||||||
|
border-bottom: 4px solid @color-hemolymph;
|
||||||
|
border-radius: 50%;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -1rem;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 100;
|
||||||
|
font-size: smaller;
|
||||||
|
text-align: center;
|
||||||
|
opacity: 0;
|
||||||
|
text-shadow: 0px 0px 5px #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover::after {
|
||||||
|
opacity: 1;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.human { border-top: 4px solid @totem-human-color; }
|
||||||
|
&.adapted { border-top: 4px solid @totem-adapted-color; }
|
||||||
|
&.rerollable {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover { transform: translateY(0.5rem); }
|
||||||
|
}
|
||||||
|
&.success { border-bottom-color: @color-acid-green; }
|
||||||
|
&.adapted::after { content: "adapté"; }
|
||||||
|
&.human::after { content: "humain"; }
|
||||||
|
&.rerolled { transform: translateY(0rem); }
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([data-result]) {
|
||||||
|
background-image: url("@{dice-path}/d10c-1.webp");
|
||||||
|
opacity: 0.5;
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-face dice background images
|
||||||
|
.generate-dice-faces(@i: 1) when (@i =< 10) {
|
||||||
|
li.die[data-result="@{i}"] {
|
||||||
|
background-image: url("@{dice-path}/d10c-@{i}.webp");
|
||||||
|
}
|
||||||
|
.generate-dice-faces(@i + 1);
|
||||||
|
}
|
||||||
|
.generate-dice-faces();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Total section ───────────────────────────────────────────────
|
||||||
|
div.roll-total {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.3) inset;
|
||||||
|
border-radius: 2px;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
margin: 0;
|
||||||
|
border-bottom: none;
|
||||||
|
font-weight: 900;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
font-size: @font-size-20;
|
||||||
|
}
|
||||||
|
|
||||||
|
#total {
|
||||||
|
color: @color-acid-green;
|
||||||
|
text-shadow: 0 0 4px fade(@color-acid-green, 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#required {
|
||||||
|
color: @color-amber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.item-card header img {
|
||||||
|
max-width: 30%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Padding supplémentaire pour les fiches groupe, npc et créature
|
||||||
|
.system-vermine2047 {
|
||||||
|
.group-content,
|
||||||
|
.npc-content,
|
||||||
|
.creature-content {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,475 @@
|
|||||||
|
@import "variables";
|
||||||
|
@import "utilities";
|
||||||
|
|
||||||
|
.window-app.vermineDialog {
|
||||||
|
max-width: 50vw;
|
||||||
|
height: fit-content;
|
||||||
|
|
||||||
|
.window-content {
|
||||||
|
.background-image(url("@{ui-path}/box_background.webp"), repeat);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
details > summary::after {
|
||||||
|
content: "▶️";
|
||||||
|
position: relative;
|
||||||
|
right: 40%;
|
||||||
|
}
|
||||||
|
|
||||||
|
details[open] > summary::after {
|
||||||
|
content: "🔽";
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
justify-content: space-around;
|
||||||
|
.shadow(0px, 1px, 10px, rgba(0, 0, 0, 0.555));
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem 0.2rem;
|
||||||
|
|
||||||
|
> * { margin: 0 0.3rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: larger;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
max-width: fit-content;
|
||||||
|
.custom-select-style();
|
||||||
|
option { max-width: fit-content; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
button {
|
||||||
|
display: block;
|
||||||
|
flex: 0.3;
|
||||||
|
color: @theme-color-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] { .custom-input-style(); }
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] { .custom-checkbox-radio(); }
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
width: 0.8rem;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
top: 5%;
|
||||||
|
left: 5%;
|
||||||
|
width: 90%;
|
||||||
|
height: 90%;
|
||||||
|
background-size: 30% 30%;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not([disabled]):hover::after { background-size: 90% 90%; }
|
||||||
|
|
||||||
|
&:checked::after {
|
||||||
|
content: "";
|
||||||
|
background-size: 70% 70%;
|
||||||
|
top: 5%;
|
||||||
|
left: 5%;
|
||||||
|
position: relative;
|
||||||
|
background-color: rgba(26, 1, 1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app.vermine2047.trait-selector {
|
||||||
|
.form-group {
|
||||||
|
.shadow(0, 0, 30px, gray);
|
||||||
|
padding: 0.3rem 0.5rem;
|
||||||
|
border: 3px solid #8e9010;
|
||||||
|
|
||||||
|
&:has(input[type="checkbox"]:checked) {
|
||||||
|
border: 3px solid green;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-around;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 2px solid #000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.app .actor.choose {
|
||||||
|
div.actor {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-radius: 50%;
|
||||||
|
.shadow(0px, 0px, 8px, #000);
|
||||||
|
}
|
||||||
|
|
||||||
|
span.actor-name {
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
background-color: rgba(255, 255, 255, 0.562);
|
||||||
|
border: 5px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
.shadow(0px, 0px, 8px, #000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
min-height: 500px;
|
||||||
|
|
||||||
|
.tabs.moods-headings {
|
||||||
|
max-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Roll Dialog V2 ─────────────────────────────────────────────────────
|
||||||
|
.application.vermine-roll {
|
||||||
|
.window-content {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.roll-dialog-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 10px;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
|
||||||
|
.dice-pool {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, select, textarea {
|
||||||
|
color: @color-chitin-dark;
|
||||||
|
background: @color-text-light-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
.custom-select-style();
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"] { .custom-input-style(); }
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] { .custom-checkbox-radio(); }
|
||||||
|
|
||||||
|
// ── Main roll section: ability + skill side-by-side ─────────────
|
||||||
|
.main-roll-section {
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
> .flexcol {
|
||||||
|
flex: 1;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
|
||||||
|
> label {
|
||||||
|
font-size: @font-size-13;
|
||||||
|
color: @color-amber;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
min-height: 28px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ability-score {
|
||||||
|
gap: 4px;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
opacity: 0.85;
|
||||||
|
|
||||||
|
#abilityScoreValue {
|
||||||
|
font-weight: bold;
|
||||||
|
color: @color-acid-green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Collapsible sections (specialties, difficulty, bonuses) ─────
|
||||||
|
details {
|
||||||
|
margin-top: 2px;
|
||||||
|
|
||||||
|
summary {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.5) inset;
|
||||||
|
cursor: pointer;
|
||||||
|
color: @theme-color-dark;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "▶";
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: @font-size-11;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.label {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-values,
|
||||||
|
.current-specialty,
|
||||||
|
.bonus-count {
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
font-size: @font-size-11;
|
||||||
|
text-transform: none;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[open] > summary::after {
|
||||||
|
content: "▼";
|
||||||
|
}
|
||||||
|
|
||||||
|
> div:not(summary) {
|
||||||
|
padding: 8px;
|
||||||
|
background: fade(@color-chitin-dark, 40%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Difficulty section ──────────────────────────────────────────
|
||||||
|
.difficulty-controls {
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
> .flexcol {
|
||||||
|
flex: 1;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: @font-size-11;
|
||||||
|
color: @color-amber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Specialties section ─────────────────────────────────────────
|
||||||
|
.specialty-options {
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
text-transform: none;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @color-amber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
flex: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Bonuses grid ────────────────────────────────────────────────
|
||||||
|
.bonus-grid {
|
||||||
|
margin: 0;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.bonus-item {
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 0;
|
||||||
|
|
||||||
|
> label.label {
|
||||||
|
font-size: @font-size-11;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
width: 40px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] + label {
|
||||||
|
font-size: @font-size-12;
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
text-transform: none;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-list {
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: @font-size-11;
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
text-transform: none;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @color-amber;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.full-width {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Totems sub-section
|
||||||
|
.totems-section {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
> label.label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-options {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
font-size: @font-size-12;
|
||||||
|
font-family: "Roboto", sans-serif;
|
||||||
|
text-transform: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: @color-amber;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-hint {
|
||||||
|
width: 100%;
|
||||||
|
font-size: @font-size-11;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
opacity: 0.6;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Total dice pool section ─────────────────────────────────────
|
||||||
|
.total-section {
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px;
|
||||||
|
border-top: 1px solid @color-border-dark-4;
|
||||||
|
margin-top: 4px;
|
||||||
|
gap: 10px;
|
||||||
|
background: fade(@color-chitin-dark, 50%);
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-16;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-value {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: @font-size-28;
|
||||||
|
color: @color-acid-green;
|
||||||
|
text-shadow: 0 0 8px fade(@color-acid-green, 40%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.totem-selector {
|
||||||
|
font-size: @font-size-11;
|
||||||
|
color: @color-text-light-0;
|
||||||
|
opacity: 0.7;
|
||||||
|
margin-left: auto;
|
||||||
|
|
||||||
|
select {
|
||||||
|
font-size: @font-size-11;
|
||||||
|
padding: 1px 4px;
|
||||||
|
width: auto;
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Footer buttons ───────────────────────────────────────────────
|
||||||
|
.sheet-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
margin-top: auto;
|
||||||
|
border-top: 1px solid @color-border-dark-4;
|
||||||
|
background: fade(@color-chitin-dark, 60%);
|
||||||
|
|
||||||
|
button {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: @font-size-13;
|
||||||
|
padding: 6px 20px;
|
||||||
|
border: none;
|
||||||
|
background: 50% 0% / cover no-repeat url("@{ui-path}/scotch.webp");
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.979) inset;
|
||||||
|
cursor: pointer;
|
||||||
|
color: @theme-color-dark;
|
||||||
|
.transition();
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 0 10px @theme-color-highlight inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: @font-size-14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
@import "variables";
|
||||||
|
@import "utilities";
|
||||||
|
|
||||||
|
.system-vermine2047 .item-formula {
|
||||||
|
flex: 0 0 200px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.system-vermine2047 .vermine2047.item .window-content {
|
||||||
|
padding: 0 1rem;
|
||||||
|
|
||||||
|
.flexrow { align-items: center; }
|
||||||
|
|
||||||
|
header, h1, h2, h3, h4, h5 {
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, cover, 50% 50%);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
margin-top: 1rem;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3, h4 { text-align: center; }
|
||||||
|
h5 { margin-bottom: 0; }
|
||||||
|
|
||||||
|
.resource-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: @color-text-light-2;
|
||||||
|
text-shadow: 0 0 3px rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource {
|
||||||
|
border: none;
|
||||||
|
border-left: 1px solid gray;
|
||||||
|
padding: 0.2rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.flexrow {
|
||||||
|
min-width: 5rem;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.damages-row {
|
||||||
|
margin: 0;
|
||||||
|
.radios {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.damage-pannes,
|
||||||
|
.damage-state,
|
||||||
|
.damage-effect {
|
||||||
|
text-align: center;
|
||||||
|
font-family: "DistressBlack";
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
color: @color-text-light-1;
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-align: center;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.979) inset;
|
||||||
|
text-shadow: 0 0 3px rgba(0, 0, 0, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: @color-text-light-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traits {
|
||||||
|
.shadow();
|
||||||
|
|
||||||
|
h3, h4 { margin: 0; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ol#chat-log div.item-card header img,
|
||||||
|
ol.chat-log div.item-card header img {
|
||||||
|
max-width: 30%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
.sans-font {
|
||||||
|
font-family: "DistressBlack", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-message .message-header {
|
||||||
|
line-height: 20px;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 0px 0px 5px black;
|
||||||
|
background: #1918135e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-center {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-around {
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flexsmall {
|
||||||
|
flex: 0.5;
|
||||||
|
}
|
||||||
@@ -0,0 +1,257 @@
|
|||||||
|
// ============================================
|
||||||
|
// Utilitaires LESS pour Vermine2047
|
||||||
|
// Mixins, fonctions et classes utilitaires
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Mixin pour les ombres standard
|
||||||
|
.shadow() {
|
||||||
|
box-shadow: 0 2rem @theme-color-shadow;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
.shadow(@color) {
|
||||||
|
box-shadow: 0 2rem @color;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
.shadow(@color, @blur) {
|
||||||
|
box-shadow: 0 @blur @color;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
.shadow(@h-offset, @v-offset, @blur, @color) {
|
||||||
|
box-shadow: @h-offset @v-offset @blur @color;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour le style des hexagones
|
||||||
|
.hexa-style(@bg-color: rgba(255, 255, 255, 0.425), @bg-color-end: rgba(0, 0, 0, 0.288)) {
|
||||||
|
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
|
||||||
|
background: radial-gradient(circle, @bg-color 0%, @bg-color-end 100%);
|
||||||
|
max-height: 1.5rem;
|
||||||
|
max-width: 1.5rem;
|
||||||
|
min-width: 1.5rem;
|
||||||
|
aspect-ratio: 1/1;
|
||||||
|
color: #000;
|
||||||
|
vertical-align: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les boutons rollable
|
||||||
|
.rollable-style() {
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
color: #000;
|
||||||
|
text-shadow: 0 0 10px red;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les inputs de type hexa
|
||||||
|
.input-hexa-style() {
|
||||||
|
.hexa-style();
|
||||||
|
input {
|
||||||
|
width: 1rem;
|
||||||
|
}
|
||||||
|
input[type="radio"] {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
input[type="radio"]::after,
|
||||||
|
input[type="radio"]::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les classes flex
|
||||||
|
.flex-container(@direction: row, @wrap: nowrap, @justify: flex-start, @align: stretch) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: @direction;
|
||||||
|
flex-wrap: @wrap;
|
||||||
|
justify-content: @justify;
|
||||||
|
align-items: @align;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour le style des cartes
|
||||||
|
.card-style(@bg-color: rgba(0, 0, 0, 0.1), @border-color: #444) {
|
||||||
|
border: 1px solid @border-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
background: @bg-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les titres de carte
|
||||||
|
.card-title(@border-color: #666) {
|
||||||
|
margin-top: 0;
|
||||||
|
border-bottom: 1px solid @border-color;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les éléments de liste avec bordure
|
||||||
|
.list-item-with-border(@border-color: #333) {
|
||||||
|
padding: 5px;
|
||||||
|
border-bottom: 1px solid @border-color;
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les groupes de formulaires
|
||||||
|
.form-group-style(@margin-bottom: 10px) {
|
||||||
|
margin-bottom: @margin-bottom;
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
input,
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les ressources
|
||||||
|
.resource-style(@bg-color: rgba(0, 0, 0, 0.05)) {
|
||||||
|
padding: 5px;
|
||||||
|
background: @bg-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 0 5px;
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 8px;
|
||||||
|
min-width: 60px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.resource-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les badges de rareté
|
||||||
|
.rarity-badge(@bg-color, @text-color) {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 1px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 5px;
|
||||||
|
background: @bg-color;
|
||||||
|
color: @text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour le fond avec image
|
||||||
|
.background-image(@url, @repeat: no-repeat, @size: auto, @position: center) {
|
||||||
|
background: @url @repeat @position / @size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les transitions
|
||||||
|
.transition(@property: all, @duration: 0.2s, @timing: ease-out) {
|
||||||
|
transition: @property @duration @timing;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les styles de l'éditeur TinyMCE
|
||||||
|
.tiny-mce-style() {
|
||||||
|
.tox {
|
||||||
|
min-height: 25vh;
|
||||||
|
.tox-editor-container {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
.tox-edit-area {
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les styles des inputs personnalisés
|
||||||
|
.custom-input-style() {
|
||||||
|
appearance: none;
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&::-webkit-slider-runnable-track {
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, auto, center);
|
||||||
|
background-size: 100% auto;
|
||||||
|
height: 0.4rem;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0px 0px 13px rgba(31, 26, 26, 0.979) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
appearance: none;
|
||||||
|
margin-top: -0.3rem;
|
||||||
|
height: 1rem;
|
||||||
|
width: 1rem;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
.background-image(url("@{totems-path}/human.webp"), no-repeat, cover);
|
||||||
|
filter: contrast(2);
|
||||||
|
box-shadow: 0px 0px 10px #000;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0px 0px 10px #ff0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les checkbox et radio boutons personnalisés
|
||||||
|
.custom-checkbox-radio() {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
box-shadow: 0px 0px 3px #85854e;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1rem;
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
.transition();
|
||||||
|
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
|
||||||
|
box-shadow: 0px 0px 6px #000 inset;
|
||||||
|
background-color: rgba(61, 11, 11, 0.658);
|
||||||
|
|
||||||
|
&[disabled=true] {
|
||||||
|
filter: grayscale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:after {
|
||||||
|
content: " ";
|
||||||
|
.background-image(url("@{totems-path}/human.webp"), no-repeat, auto, 50% 150%);
|
||||||
|
position: relative;
|
||||||
|
top: 10%;
|
||||||
|
left: 0%;
|
||||||
|
width: 100%;
|
||||||
|
height: 80%;
|
||||||
|
display: block;
|
||||||
|
border-radius: 0%;
|
||||||
|
padding: 0;
|
||||||
|
.transition();
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
background-color: rgba(26, 107, 12, 0.658);
|
||||||
|
&:after {
|
||||||
|
font-weight: 900;
|
||||||
|
background-color: rgba(26, 1, 1, 0);
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mixin pour les selects personnalisés
|
||||||
|
.custom-select-style() {
|
||||||
|
border: none;
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, auto, 100% 100%);
|
||||||
|
box-shadow: 0px 0px 3px rgba(31, 26, 26, 0.979) inset;
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
color: #000;
|
||||||
|
text-shadow: 0px 0px 15px #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
.background-image(url("@{ui-path}/scotch.webp"), no-repeat, auto, 100% 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
// ============================================
|
||||||
|
// Variables LESS pour Vermine2047
|
||||||
|
// Converti depuis les variables CSS du fichier vermine2047.css
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Couleurs de texte - clair
|
||||||
|
@color-text-light-highlight: #96d696;
|
||||||
|
@color-text-light-heading: #9fd8a8;
|
||||||
|
@color-text-light-primary: #a4b5b3;
|
||||||
|
|
||||||
|
// Couleurs de texte - foncé
|
||||||
|
@color-text-dark-primary: #131919;
|
||||||
|
@color-text-dark-secondary: #444b4a;
|
||||||
|
@color-text-dark-header: #1d2223;
|
||||||
|
@color-text-dark-inactive: #71797a;
|
||||||
|
|
||||||
|
// Couleurs de texte - autres
|
||||||
|
@color-text-hyperlink: #5aaf0a;
|
||||||
|
|
||||||
|
// Niveaux de gris pour le texte clair
|
||||||
|
@color-text-light-0: #fff;
|
||||||
|
@color-text-light-1: #e0f0f0;
|
||||||
|
@color-text-light-2: #c9e0c0;
|
||||||
|
@color-text-light-3: #90c4a4;
|
||||||
|
@color-text-light-4: #80c08b;
|
||||||
|
@color-text-light-5: #60b06b;
|
||||||
|
@color-text-light-6: #40a05d;
|
||||||
|
@color-text-light-7: #208028;
|
||||||
|
|
||||||
|
// Niveaux de gris pour le texte foncé
|
||||||
|
@color-text-dark-1: #111;
|
||||||
|
@color-text-dark-2: #222;
|
||||||
|
@color-text-dark-3: #444;
|
||||||
|
@color-text-dark-4: #555;
|
||||||
|
@color-text-dark-5: #666;
|
||||||
|
@color-text-dark-6: #777;
|
||||||
|
|
||||||
|
// Couleurs de bordure - clair
|
||||||
|
@color-border-light-1: #b0d9b0;
|
||||||
|
@color-border-light-2: #80c0c0;
|
||||||
|
|
||||||
|
// Couleurs de bordure - foncé
|
||||||
|
@color-border-dark-1: #131919;
|
||||||
|
@color-border-dark-2: #1d2223;
|
||||||
|
@color-border-dark-3: #2d3333;
|
||||||
|
@color-border-dark-4: #3d4444;
|
||||||
|
@color-border-dark-5: #668888;
|
||||||
|
|
||||||
|
// Couleurs d'ombre
|
||||||
|
@color-shadow-primary: #7bb60d;
|
||||||
|
@color-shadow-highlight: #85cc01d0;
|
||||||
|
@color-shadow-dark: #000;
|
||||||
|
|
||||||
|
// Couleurs de soulignement
|
||||||
|
@color-underline-inactive: #71797a;
|
||||||
|
@color-underline-active: #1a1944;
|
||||||
|
@color-underline-header: #228247;
|
||||||
|
|
||||||
|
// Couleurs de bordure - autres
|
||||||
|
@color-border-light-highlight: #b0d9b0;
|
||||||
|
@color-border-light-primary: #a4b5b3;
|
||||||
|
@color-border-light-secondary: #9fc7d8;
|
||||||
|
@color-border-light-tertiary: #71797a;
|
||||||
|
@color-border-dark: #000;
|
||||||
|
@color-border-dark-primary: #131919;
|
||||||
|
@color-border-dark-secondary: #1d2223;
|
||||||
|
@color-border-dark-tertiary: #444b4a;
|
||||||
|
@color-border-highlight: #85c019;
|
||||||
|
@color-border-highlight-alt: #70c008;
|
||||||
|
|
||||||
|
// Couleurs de fond
|
||||||
|
@color-bg-btn-minor-inactive: #9fc7d8;
|
||||||
|
@color-bg-btn-minor-active: #a4b5b3;
|
||||||
|
@color-bg-option: #ccdada;
|
||||||
|
|
||||||
|
// Autres couleurs
|
||||||
|
@color-checkbox-checked: #666;
|
||||||
|
@color-ownership-none: #00ff55;
|
||||||
|
@color-ownership-observer: #71797a;
|
||||||
|
@color-ownership-owner: #a4b5b3;
|
||||||
|
|
||||||
|
// Niveaux de log
|
||||||
|
@color-level-info: #b95c87;
|
||||||
|
@color-level-warning: #04b184;
|
||||||
|
@color-level-error: #03750;
|
||||||
|
@color-level-success: #3c266c;
|
||||||
|
|
||||||
|
// Z-index
|
||||||
|
@z-index-canvas: 0;
|
||||||
|
@z-index-app: 30;
|
||||||
|
@z-index-ui: 60;
|
||||||
|
@z-index-window: 100;
|
||||||
|
@z-index-tooltip: 9999;
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
@sidebar-width: 300px;
|
||||||
|
@sidebar-header-height: 32px;
|
||||||
|
@sidebar-item-height: 48px;
|
||||||
|
@hotbar-height: 52px;
|
||||||
|
@hotbar-width: 578px;
|
||||||
|
@macro-size: 50px;
|
||||||
|
@players-width: 200px;
|
||||||
|
@form-field-height: 26px;
|
||||||
|
|
||||||
|
// Polices
|
||||||
|
@font-mono: monospace;
|
||||||
|
|
||||||
|
// Tailles de police
|
||||||
|
@font-size-11: 0.6875rem;
|
||||||
|
@font-size-12: 0.75rem;
|
||||||
|
@font-size-13: 0.8125rem;
|
||||||
|
@font-size-14: 0.875rem;
|
||||||
|
@font-size-16: 1rem;
|
||||||
|
@font-size-18: 1.125rem;
|
||||||
|
@font-size-20: 1.25rem;
|
||||||
|
@font-size-24: 1.5rem;
|
||||||
|
@font-size-28: 1.75rem;
|
||||||
|
@font-size-32: 2rem;
|
||||||
|
@font-size-48: 3rem;
|
||||||
|
|
||||||
|
// Hauteurs de ligne
|
||||||
|
@line-height-12: 0.75rem;
|
||||||
|
@line-height-16: 1rem;
|
||||||
|
@line-height-20: 1.25rem;
|
||||||
|
@line-height-30: 1.875rem;
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Variables spécifiques Vermine2047
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Couleurs thématiques
|
||||||
|
@theme-color-primary: #7e7544;
|
||||||
|
@theme-color-secondary: #dfdfdf;
|
||||||
|
@theme-color-accent: #1fa832;
|
||||||
|
@theme-color-dark: #191813;
|
||||||
|
@theme-color-light: #4e564c;
|
||||||
|
@theme-color-shadow: rgba(0, 0, 0, 0.7098039216);
|
||||||
|
@theme-color-highlight: #005a3c;
|
||||||
|
|
||||||
|
// Couleurs insecte / organique
|
||||||
|
@color-acid-green: #6ab04c;
|
||||||
|
@color-amber: #e8b84b;
|
||||||
|
@color-chitin-dark: #2a2520;
|
||||||
|
@color-hemolymph: #8b4513;
|
||||||
|
@color-membrane: rgba(106, 176, 76, 0.1);
|
||||||
|
@color-honeycomb: #c9a84c;
|
||||||
|
|
||||||
|
// Couleurs pour les dés et totems
|
||||||
|
@dice-pool-color: rgb(94, 90, 77);
|
||||||
|
@dice-reroll-color: rgb(187, 182, 165);
|
||||||
|
@totem-human-color: #064930;
|
||||||
|
@totem-adapted-color: #553402;
|
||||||
|
|
||||||
|
// Couleurs pour les niveaux de rareté
|
||||||
|
@rarity-0-bg: #444;
|
||||||
|
@rarity-0-text: #aaa;
|
||||||
|
@rarity-1-bg: #5a7a5a;
|
||||||
|
@rarity-1-text: #fff;
|
||||||
|
@rarity-2-bg: #7a9a7a;
|
||||||
|
@rarity-2-text: #000;
|
||||||
|
@rarity-3-bg: #9a5a9a;
|
||||||
|
@rarity-3-text: #fff;
|
||||||
|
|
||||||
|
// Couleurs pour les modes de jeu
|
||||||
|
@game-mode-1-color: rgba(235, 218, 143, 0.8);
|
||||||
|
@game-mode-2-color: #83f883;
|
||||||
|
@game-mode-3-color: rgba(245, 124, 124, 0.8);
|
||||||
|
|
||||||
|
// Chemins des assets
|
||||||
|
@assets-path: "/systems/vermine2047/assets";
|
||||||
|
@images-path: "@{assets-path}/images";
|
||||||
|
@fonts-path: "@{assets-path}/fonts";
|
||||||
|
@ui-path: "@{images-path}/ui";
|
||||||
|
@totems-path: "@{images-path}/ui/totems";
|
||||||
|
@dice-path: "@{assets-path}/dice";
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// ============================================
|
||||||
|
// Fichier principal LESS pour Vermine2047
|
||||||
|
// Ce fichier importe tous les modules LESS
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// 1. Import de police Google (doit être en premier car c'est du CSS pur)
|
||||||
|
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");
|
||||||
|
|
||||||
|
// 2. Variables et utilitaires (doivent être importés avant tout le reste)
|
||||||
|
@import "variables";
|
||||||
|
@import "utilities";
|
||||||
|
|
||||||
|
// 3. Styles de base
|
||||||
|
@import "base";
|
||||||
|
|
||||||
|
// 4. Legacy SCSS converti en LESS
|
||||||
|
@import "legacy";
|
||||||
|
|
||||||
|
// 5. Styles des acteurs
|
||||||
|
@import "actor/actor";
|
||||||
|
@import "actor/totem";
|
||||||
|
@import "actor/npc";
|
||||||
|
@import "actor/group";
|
||||||
|
@import "actor/creature";
|
||||||
|
|
||||||
|
// 6. Styles des items
|
||||||
|
@import "items";
|
||||||
|
|
||||||
|
// 7. Styles des dialogs
|
||||||
|
@import "dialogs";
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Notes:
|
||||||
|
// - L'ordre des imports est important
|
||||||
|
// - Les variables doivent être définies avant d'être utilisées
|
||||||
|
// - Les mixins doivent être définis avant d'être appelés
|
||||||
|
// ============================================
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export { VermineBaseActorSheet } from "./base-actor-sheet.mjs"
|
||||||
|
export { VermineBaseItemSheet } from "./base-item-sheet.mjs"
|
||||||
|
export { VermineCharacterSheetV2 } from "./character-sheet.mjs"
|
||||||
|
export { VermineNpcSheetV2 } from "./npc-sheet.mjs"
|
||||||
|
export { VermineGroupSheetV2 } from "./group-sheet.mjs"
|
||||||
|
export { VermineCreatureSheetV2 } from "./creature-sheet.mjs"
|
||||||
|
export {
|
||||||
|
VermineItemSheetV2,
|
||||||
|
VermineWeaponSheetV2,
|
||||||
|
VermineDefenseSheetV2,
|
||||||
|
VermineVehicleSheetV2,
|
||||||
|
VermineAbilitySheetV2,
|
||||||
|
VermineSpecialtySheetV2,
|
||||||
|
VermineBackgroundSheetV2,
|
||||||
|
VermineTraumaSheetV2,
|
||||||
|
VermineEvolutionSheetV2,
|
||||||
|
VermineRumorSheetV2,
|
||||||
|
VermineTargetSheetV2,
|
||||||
|
VermineRiteSheetV2
|
||||||
|
} from "./item-sheets.mjs"
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
import { onManageActiveEffect } from "../../system/effects.mjs"
|
||||||
|
|
||||||
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fiche de base pour tous les acteurs Vermine 2047 (ApplicationV2).
|
||||||
|
* Remplace VermineActorSheet (AppV1).
|
||||||
|
*/
|
||||||
|
export class VermineBaseActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
||||||
|
|
||||||
|
// ── Mode édition / jeu ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
|
||||||
|
|
||||||
|
_sheetMode = this.constructor.SHEET_MODES.PLAY
|
||||||
|
|
||||||
|
get isPlayMode() { return this._sheetMode === this.constructor.SHEET_MODES.PLAY }
|
||||||
|
get isEditMode() { return this._sheetMode === this.constructor.SHEET_MODES.EDIT }
|
||||||
|
|
||||||
|
// ── Options par défaut ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["vermine2047", "actor"],
|
||||||
|
position: { width: 800, height: "auto" },
|
||||||
|
form: { submitOnChange: true },
|
||||||
|
window: { resizable: true },
|
||||||
|
dragDrop: [{ dragSelector: ".item", dropSelector: null }],
|
||||||
|
actions: {
|
||||||
|
editImage: VermineBaseActorSheet.#onEditImage,
|
||||||
|
toggleSheet: VermineBaseActorSheet.#onToggleSheet,
|
||||||
|
edit: VermineBaseActorSheet.#onItemEdit,
|
||||||
|
delete: VermineBaseActorSheet.#onItemDelete,
|
||||||
|
create: VermineBaseActorSheet.#onItemCreate,
|
||||||
|
roll: VermineBaseActorSheet.#onRollItem,
|
||||||
|
clickRadio: VermineBaseActorSheet.#onClickRadioHexa,
|
||||||
|
effectControl: VermineBaseActorSheet.#onEffectControl,
|
||||||
|
chooseTotem: VermineBaseActorSheet.#onChooseTotem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Drag & Drop ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#dragDrop
|
||||||
|
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options)
|
||||||
|
this.#dragDrop = this.#createDragDropHandlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
#createDragDropHandlers() {
|
||||||
|
return this.options.dragDrop.map(d => {
|
||||||
|
d.permissions = {
|
||||||
|
dragstart: this._canDragStart.bind(this),
|
||||||
|
drop: this._canDragDrop.bind(this)
|
||||||
|
}
|
||||||
|
d.callbacks = {
|
||||||
|
dragover: this._onDragOver.bind(this),
|
||||||
|
drop: this._onDrop.bind(this)
|
||||||
|
}
|
||||||
|
return new foundry.applications.ux.DragDrop.implementation(d)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_canDragStart() { return this.isEditable }
|
||||||
|
_canDragDrop() { return this.isEditable }
|
||||||
|
|
||||||
|
// ── Contexte commun ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
return {
|
||||||
|
fields: this.document.schema.fields,
|
||||||
|
systemFields: this.document.system.schema.fields,
|
||||||
|
actor: this.document,
|
||||||
|
system: this.document.system,
|
||||||
|
source: this.document.toObject(),
|
||||||
|
config: CONFIG.VERMINE,
|
||||||
|
rollData: this.document.getRollData(),
|
||||||
|
isGM: game.user.isGM,
|
||||||
|
isEditMode: this.isEditMode,
|
||||||
|
isPlayMode: this.isPlayMode,
|
||||||
|
isEditable: this.isEditable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rendu ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async _onRoll(event) {
|
||||||
|
event.preventDefault()
|
||||||
|
const el = event.currentTarget
|
||||||
|
const type = el.dataset.type
|
||||||
|
const label = el.dataset.label
|
||||||
|
if (!type || !label) return
|
||||||
|
const { default: RollDialog } = await import("../../system/dialogs/rollDialog.mjs")
|
||||||
|
const dialog = await RollDialog.create({
|
||||||
|
actorId: this.document.id,
|
||||||
|
rolltype: type,
|
||||||
|
label
|
||||||
|
})
|
||||||
|
if (dialog) dialog.render(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Actions ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_onRender(context, options) {
|
||||||
|
super._onRender(context, options)
|
||||||
|
this.#dragDrop.forEach(d => d.bind(this.element))
|
||||||
|
this.element.querySelectorAll(".rollable").forEach(el => {
|
||||||
|
el.addEventListener("click", this._onRoll.bind(this))
|
||||||
|
})
|
||||||
|
// Auto-fill empty number inputs on change to prevent validation errors
|
||||||
|
this.element.addEventListener("change", e => {
|
||||||
|
const input = e.target
|
||||||
|
if (input?.type === "number" && !input.value && input.name && input !== document.activeElement) {
|
||||||
|
input.value = "0"
|
||||||
|
}
|
||||||
|
}, { capture: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
async _onDropItem(item) {
|
||||||
|
const doc = item instanceof foundry.abstract.Document ? item : await fromUuid(item.uuid)
|
||||||
|
if (!doc) return
|
||||||
|
const itemData = doc.toObject()
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [itemData], { renderSheet: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onToggleSheet() {
|
||||||
|
const modes = this.constructor.SHEET_MODES
|
||||||
|
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onEditImage(event, target) {
|
||||||
|
const attr = target.dataset.edit ?? "img"
|
||||||
|
const current = foundry.utils.getProperty(this.document, attr)
|
||||||
|
const fp = new FilePicker({
|
||||||
|
current,
|
||||||
|
type: "image",
|
||||||
|
callback: (path) => this.document.update({ [attr]: path }),
|
||||||
|
top: this.position.top + 40,
|
||||||
|
left: this.position.left + 10
|
||||||
|
})
|
||||||
|
return fp.browse()
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onItemEdit(event, target) {
|
||||||
|
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
||||||
|
const uuid = target.closest("[data-item-uuid]")?.dataset?.itemUuid
|
||||||
|
let item
|
||||||
|
if (uuid) item = await fromUuid(uuid)
|
||||||
|
if (!item) item = this.document.items.get(id)
|
||||||
|
item?.sheet.render(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onItemDelete(event, target) {
|
||||||
|
const itemUuid = target.closest("[data-item-uuid]")?.dataset?.itemUuid
|
||||||
|
if (itemUuid) {
|
||||||
|
const item = await fromUuid(itemUuid)
|
||||||
|
await item?.deleteDialog()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
||||||
|
const item = this.document.items.get(id)
|
||||||
|
await item?.deleteDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onItemCreate(event, target) {
|
||||||
|
const type = target.dataset.type
|
||||||
|
if (!type) return
|
||||||
|
const name = game.i18n.localize("ITEMS.new_" + type)
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [{ name, type }])
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRollItem(event, target) {
|
||||||
|
const id = target.closest("[data-item-id]")?.dataset?.itemId
|
||||||
|
if (!id) return
|
||||||
|
const item = this.document.items.get(id)
|
||||||
|
item?.roll()
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onClickRadioHexa(event, target) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
const input = target
|
||||||
|
const update = {}
|
||||||
|
let current = this.document
|
||||||
|
const propTree = input.name.split(".")
|
||||||
|
for (const prop of propTree) {
|
||||||
|
current = current[prop]
|
||||||
|
}
|
||||||
|
if (current != input.value) {
|
||||||
|
update[input.name] = parseInt(input.value)
|
||||||
|
} else {
|
||||||
|
update[input.name] = parseInt(input.value) - 1
|
||||||
|
}
|
||||||
|
this.document.update(update)
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onEffectControl(event, target) {
|
||||||
|
onManageActiveEffect(event, this.document)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onChooseTotem(event, target) {
|
||||||
|
const { TotemPicker } = await import("../../system/applications.mjs")
|
||||||
|
new TotemPicker(target, this.document).render(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fiche de base pour tous les items Vermine 2047 (ApplicationV2).
|
||||||
|
*/
|
||||||
|
export class VermineBaseItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||||
|
|
||||||
|
// ── Mode édition ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
|
||||||
|
|
||||||
|
_sheetMode = this.constructor.SHEET_MODES.PLAY
|
||||||
|
|
||||||
|
get isPlayMode() { return this._sheetMode === this.constructor.SHEET_MODES.PLAY }
|
||||||
|
get isEditMode() { return this._sheetMode === this.constructor.SHEET_MODES.EDIT }
|
||||||
|
|
||||||
|
// ── Options par défaut ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["vermine2047", "item"],
|
||||||
|
position: { width: 560, height: "auto" },
|
||||||
|
form: { submitOnChange: true },
|
||||||
|
window: { resizable: true },
|
||||||
|
actions: {
|
||||||
|
editImage: VermineBaseItemSheet.#onEditImage,
|
||||||
|
toggleSheet: VermineBaseItemSheet.#onToggleSheet,
|
||||||
|
clickDamage: VermineBaseItemSheet.#onClickDamage,
|
||||||
|
openTraits: VermineBaseItemSheet.#onOpenTraits
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Drag & Drop ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#dragDrop
|
||||||
|
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options)
|
||||||
|
this.#dragDrop = this.#createDragDropHandlers()
|
||||||
|
}
|
||||||
|
|
||||||
|
#createDragDropHandlers() {
|
||||||
|
if (!this.options.dragDrop) return []
|
||||||
|
return this.options.dragDrop.map(d => {
|
||||||
|
d.permissions = {
|
||||||
|
dragstart: this._canDragStart.bind(this),
|
||||||
|
drop: this._canDragDrop.bind(this)
|
||||||
|
}
|
||||||
|
d.callbacks = {
|
||||||
|
dragover: this._onDragOver.bind(this),
|
||||||
|
drop: this._onDrop.bind(this)
|
||||||
|
}
|
||||||
|
return new foundry.applications.ux.DragDrop.implementation(d)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_canDragStart() { return this.isEditable }
|
||||||
|
_canDragDrop() { return this.isEditable }
|
||||||
|
|
||||||
|
// ── Contexte commun ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
return {
|
||||||
|
fields: this.document.schema.fields,
|
||||||
|
systemFields: this.document.system.schema.fields,
|
||||||
|
item: this.document,
|
||||||
|
system: this.document.system,
|
||||||
|
source: this.document.toObject(),
|
||||||
|
config: CONFIG.VERMINE,
|
||||||
|
isEditMode: this.isEditMode,
|
||||||
|
isPlayMode: this.isPlayMode,
|
||||||
|
isEditable: this.isEditable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rendu ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
_onRender(context, options) {
|
||||||
|
this.#dragDrop.forEach(d => d.bind(this.element))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Actions ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static #onToggleSheet() {
|
||||||
|
const modes = this.constructor.SHEET_MODES
|
||||||
|
this._sheetMode = this.isEditMode ? modes.PLAY : modes.EDIT
|
||||||
|
this.render()
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onEditImage(event, target) {
|
||||||
|
const attr = target.dataset.edit ?? "img"
|
||||||
|
const current = foundry.utils.getProperty(this.document, attr)
|
||||||
|
const fp = new FilePicker({
|
||||||
|
current,
|
||||||
|
type: "image",
|
||||||
|
callback: (path) => this.document.update({ [attr]: path }),
|
||||||
|
top: this.position.top + 40,
|
||||||
|
left: this.position.left + 10
|
||||||
|
})
|
||||||
|
return fp.browse()
|
||||||
|
}
|
||||||
|
|
||||||
|
static #onClickDamage(event, target) {
|
||||||
|
// Les radios de dégâts sont 1-based dans le template (value="{{@index}}" avec index 1..max)
|
||||||
|
// mais le stockage est 0-based. On soustrait 1 avant de sauvegarder.
|
||||||
|
const prop = target.name
|
||||||
|
const value = parseInt(target.value) - 1
|
||||||
|
this.document.update({ [prop]: value })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onOpenTraits(event, target) {
|
||||||
|
const { TraitSelector } = await import("../../system/applications.mjs")
|
||||||
|
new TraitSelector(this.document).render(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { VermineBaseActorSheet } from "./base-actor-sheet.mjs"
|
||||||
|
|
||||||
|
export class VermineCharacterSheetV2 extends VermineBaseActorSheet {
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["character"],
|
||||||
|
position: { width: 860, height: 720 },
|
||||||
|
window: { contentClasses: ["character-content"] },
|
||||||
|
actions: {
|
||||||
|
addSpecialty: VermineCharacterSheetV2.#onAddSpecialty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: "systems/vermine2047/templates/actor/appv2/character-main.hbs" },
|
||||||
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||||
|
abilities: { template: "systems/vermine2047/templates/actor/appv2/character-abilities.hbs" },
|
||||||
|
totem: { template: "systems/vermine2047/templates/actor/appv2/character-totem.hbs" },
|
||||||
|
equipment: { template: "systems/vermine2047/templates/actor/appv2/character-equipment.hbs" },
|
||||||
|
stories: { template: "systems/vermine2047/templates/actor/appv2/character-stories.hbs" },
|
||||||
|
combat: { template: "systems/vermine2047/templates/actor/appv2/character-combat.hbs" }
|
||||||
|
}
|
||||||
|
|
||||||
|
tabGroups = { sheet: "abilities" }
|
||||||
|
|
||||||
|
#getTabs() {
|
||||||
|
const tabs = {
|
||||||
|
abilities: { id: "abilities", group: "sheet", icon: "fas fa-address-card", label: "VERMINE.tabs.abilities" },
|
||||||
|
totem: { id: "totem", group: "sheet", icon: "fas fa-star", label: "VERMINE.tabs.totem" },
|
||||||
|
equipment: { id: "equipment", group: "sheet", icon: "fas fa-hammer", label: "VERMINE.tabs.equipment" },
|
||||||
|
stories: { id: "stories", group: "sheet", icon: "fas fa-book-open-reader", label: "VERMINE.tabs.stories" },
|
||||||
|
combat: { id: "combat", group: "sheet", icon: "fas fa-medal", label: "VERMINE.tabs.combat" }
|
||||||
|
}
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
|
v.cssClass = v.active ? "active" : ""
|
||||||
|
}
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.tabs = this.#getTabs()
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
const doc = this.document
|
||||||
|
context.systemFields = doc.system.schema.fields
|
||||||
|
switch (partId) {
|
||||||
|
case "main": break
|
||||||
|
case "abilities":
|
||||||
|
context.tab = context.tabs.abilities
|
||||||
|
break
|
||||||
|
case "totem":
|
||||||
|
context.tab = context.tabs.totem
|
||||||
|
context.abilities = doc.itemTypes.ability.filter(i => i.system.type !== "totem")
|
||||||
|
context.totem_abilities = doc.itemTypes.ability.filter(i => i.system.type === "totem")
|
||||||
|
context.specialties = doc.itemTypes.specialty
|
||||||
|
context.backgrounds = doc.itemTypes.background
|
||||||
|
context.traumas = doc.itemTypes.trauma
|
||||||
|
context.evolutions = doc.itemTypes.evolution
|
||||||
|
break
|
||||||
|
case "equipment":
|
||||||
|
context.tab = context.tabs.equipment
|
||||||
|
context.gear = doc.itemTypes.item
|
||||||
|
context.weapons = doc.itemTypes.weapon
|
||||||
|
context.defenses = doc.itemTypes.defense
|
||||||
|
context.vehicles = doc.itemTypes.vehicle
|
||||||
|
break
|
||||||
|
case "stories":
|
||||||
|
context.tab = context.tabs.stories
|
||||||
|
break
|
||||||
|
case "combat":
|
||||||
|
context.tab = context.tabs.combat
|
||||||
|
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
|
||||||
|
context.effects = prepareActiveEffectCategories(doc.effects)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
changeTab(tab, group, options = {}) {
|
||||||
|
super.changeTab(tab, group, options)
|
||||||
|
if (group === "sheet") {
|
||||||
|
const main = this.element?.querySelector('[data-group="sheet"][data-tab="main"]')
|
||||||
|
if (main) main.classList.add("active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onAddSpecialty(event, target) {
|
||||||
|
const skillKey = target.dataset.skill
|
||||||
|
const name = game.i18n.localize("ITEMS.new_specialty")
|
||||||
|
const itemData = { name, type: "specialty" }
|
||||||
|
if (skillKey) itemData.system = { skill: skillKey }
|
||||||
|
await this.document.createEmbeddedDocuments("Item", [itemData])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { VermineBaseActorSheet } from "./base-actor-sheet.mjs"
|
||||||
|
|
||||||
|
export class VermineCreatureSheetV2 extends VermineBaseActorSheet {
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["creature"],
|
||||||
|
position: { width: 700, height: 650 },
|
||||||
|
window: { contentClasses: ["creature-content"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: "systems/vermine2047/templates/actor/appv2/creature-main.hbs" },
|
||||||
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||||
|
info: { template: "systems/vermine2047/templates/actor/appv2/creature-info.hbs" },
|
||||||
|
stats: { template: "systems/vermine2047/templates/actor/appv2/creature-stats.hbs" },
|
||||||
|
combat: { template: "systems/vermine2047/templates/actor/appv2/creature-combat.hbs" },
|
||||||
|
effects: { template: "systems/vermine2047/templates/actor/appv2/creature-effects.hbs" }
|
||||||
|
}
|
||||||
|
|
||||||
|
tabGroups = { sheet: "info" }
|
||||||
|
|
||||||
|
#getTabs() {
|
||||||
|
const tabs = {
|
||||||
|
info: { id: "info", group: "sheet", icon: "fas fa-info-circle", label: "VERMINE.information" },
|
||||||
|
stats: { id: "stats", group: "sheet", icon: "fas fa-chart-bar", label: "VERMINE.stats" },
|
||||||
|
combat: { id: "combat", group: "sheet", icon: "fas fa-sword", label: "VERMINE.combat" },
|
||||||
|
effects: { id: "effects", group: "sheet", icon: "fas fa-magic", label: "UI.effects.name" }
|
||||||
|
}
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
|
v.cssClass = v.active ? "active" : ""
|
||||||
|
}
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.tabs = this.#getTabs()
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
const doc = this.document
|
||||||
|
switch (partId) {
|
||||||
|
case "main": break
|
||||||
|
case "info":
|
||||||
|
context.tab = context.tabs.info
|
||||||
|
break
|
||||||
|
case "stats":
|
||||||
|
context.tab = context.tabs.stats
|
||||||
|
context.patternLabel = doc.system.pattern?.value ? CONFIG.VERMINE.creaturePatternLevels[doc.system.pattern.value]?.label : ""
|
||||||
|
context.sizeLabel = doc.system.size?.value || ""
|
||||||
|
context.roleLabel = doc.system.role?.value ? CONFIG.VERMINE.creatureRoleLevels[doc.system.role.value]?.label : ""
|
||||||
|
context.packLabel = doc.system.pack?.value || game.i18n.localize("VERMINE.none")
|
||||||
|
break
|
||||||
|
case "combat":
|
||||||
|
context.tab = context.tabs.combat
|
||||||
|
break
|
||||||
|
case "effects":
|
||||||
|
context.tab = context.tabs.effects
|
||||||
|
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
|
||||||
|
context.effects = prepareActiveEffectCategories(doc.effects)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import { VermineBaseActorSheet } from "./base-actor-sheet.mjs"
|
||||||
|
|
||||||
|
export class VermineGroupSheetV2 extends VermineBaseActorSheet {
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["group"],
|
||||||
|
position: { width: 700, height: 600 },
|
||||||
|
window: { contentClasses: ["group-content"] },
|
||||||
|
actions: {
|
||||||
|
chooseTotem: VermineGroupSheetV2.#onChooseTotem,
|
||||||
|
chooseActor: VermineGroupSheetV2.#onChooseActor,
|
||||||
|
deleteMember: VermineGroupSheetV2.#onDeleteMember,
|
||||||
|
deleteEncounter: VermineGroupSheetV2.#onDeleteEncounter,
|
||||||
|
deleteObjective: VermineGroupSheetV2.#onDeleteObjective,
|
||||||
|
addObjective: VermineGroupSheetV2.#onAddObjective
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: "systems/vermine2047/templates/actor/appv2/group-main.hbs" },
|
||||||
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||||
|
info: { template: "systems/vermine2047/templates/actor/appv2/group-info.hbs" },
|
||||||
|
gear: { template: "systems/vermine2047/templates/actor/appv2/group-gear.hbs" },
|
||||||
|
road: { template: "systems/vermine2047/templates/actor/appv2/group-road.hbs" },
|
||||||
|
reserve: { template: "systems/vermine2047/templates/actor/appv2/group-reserve.hbs" }
|
||||||
|
}
|
||||||
|
|
||||||
|
tabGroups = { sheet: "info" }
|
||||||
|
|
||||||
|
#getTabs() {
|
||||||
|
const tabs = {
|
||||||
|
info: { id: "info", group: "sheet", icon: "fas fa-star", label: "VERMINE.information" },
|
||||||
|
gear: { id: "gear", group: "sheet", icon: "fas fa-gear", label: "VERMINE.gear" },
|
||||||
|
road: { id: "road", group: "sheet", icon: "fas fa-route", label: "VERMINE.road" },
|
||||||
|
reserve: { id: "reserve", group: "sheet", icon: "fas fa-users", label: "VERMINE.reserve" }
|
||||||
|
}
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
|
v.cssClass = v.active ? "active" : ""
|
||||||
|
}
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.tabs = this.#getTabs()
|
||||||
|
// Résoudre les IDs des membres/encounters en données acteur
|
||||||
|
context.resolvedMembers = {}
|
||||||
|
if (this.document.system.members?.length > 0) {
|
||||||
|
for (const memberId of this.document.system.members) {
|
||||||
|
const a = game.actors.get(memberId)
|
||||||
|
if (a) context.resolvedMembers[memberId] = { name: a.name, id: a.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.resolvedEncounters = {}
|
||||||
|
if (this.document.system.encounters?.length > 0) {
|
||||||
|
for (const encId of this.document.system.encounters) {
|
||||||
|
const a = game.actors.get(encId)
|
||||||
|
if (a) context.resolvedEncounters[encId] = { name: a.name, id: a.id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
const doc = this.document
|
||||||
|
switch (partId) {
|
||||||
|
case "main": break
|
||||||
|
case "info":
|
||||||
|
context.tab = context.tabs.info
|
||||||
|
context.abilities = doc.itemTypes.ability
|
||||||
|
context.specialties = doc.itemTypes.specialty
|
||||||
|
context.backgrounds = doc.itemTypes.background
|
||||||
|
context.traumas = doc.itemTypes.trauma
|
||||||
|
context.evolutions = doc.itemTypes.evolution
|
||||||
|
break
|
||||||
|
case "gear":
|
||||||
|
context.tab = context.tabs.gear
|
||||||
|
context.gear = doc.itemTypes.item
|
||||||
|
context.weapons = doc.itemTypes.weapon
|
||||||
|
context.defenses = doc.itemTypes.defense
|
||||||
|
break
|
||||||
|
case "road":
|
||||||
|
context.tab = context.tabs.road
|
||||||
|
context.vehicles = doc.itemTypes.vehicle
|
||||||
|
break
|
||||||
|
case "reserve":
|
||||||
|
context.tab = context.tabs.reserve
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions : délégation aux applications AppV1 existantes pour TotemPicker/ActorPicker
|
||||||
|
static async #onChooseTotem(event, target) {
|
||||||
|
const { TotemPicker } = await import("../../system/applications.mjs")
|
||||||
|
new TotemPicker(target, this.document).render(true)
|
||||||
|
}
|
||||||
|
static async #onChooseActor(event, target) {
|
||||||
|
const { ActorPicker } = await import("../../system/applications.mjs")
|
||||||
|
new ActorPicker(target, this.document).render(true)
|
||||||
|
}
|
||||||
|
static #onDeleteMember(event, target) {
|
||||||
|
const li = target.closest("li.actor")
|
||||||
|
if (!li) return
|
||||||
|
const actorId = li.dataset.actorId
|
||||||
|
const idx = this.document.system.members.indexOf(actorId)
|
||||||
|
if (idx !== -1) {
|
||||||
|
const members = [...this.document.system.members]
|
||||||
|
members.splice(idx, 1)
|
||||||
|
this.document.update({ "system.members": members })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static #onDeleteEncounter(event, target) {
|
||||||
|
const li = target.closest("li.actor")
|
||||||
|
if (!li) return
|
||||||
|
const actorId = li.dataset.actorId
|
||||||
|
const idx = this.document.system.encounters.indexOf(actorId)
|
||||||
|
if (idx !== -1) {
|
||||||
|
const encounters = [...this.document.system.encounters]
|
||||||
|
encounters.splice(idx, 1)
|
||||||
|
this.document.update({ "system.encounters": encounters })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static #onDeleteObjective(event, target) {
|
||||||
|
const type = target.dataset.type
|
||||||
|
const index = parseInt(target.dataset.index)
|
||||||
|
if (isNaN(index)) return
|
||||||
|
const objectives = foundry.utils.duplicate(this.document.system.objectives || { major: [], minor: [] })
|
||||||
|
objectives[type].splice(index, 1)
|
||||||
|
this.document.update({ "system.objectives": objectives })
|
||||||
|
}
|
||||||
|
static #onAddObjective(event, target) {
|
||||||
|
const type = target.dataset.type === "major_objective" ? "major" : "minor"
|
||||||
|
const objectives = foundry.utils.duplicate(this.document.system.objectives || { major: [], minor: [] })
|
||||||
|
objectives[type].push("")
|
||||||
|
this.document.update({ "system.objectives": objectives })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { VermineBaseItemSheet } from "./base-item-sheet.mjs"
|
||||||
|
|
||||||
|
// ── Item générique ────────────────────────────────────────────────────
|
||||||
|
export class VermineItemSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["item-gear"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-item-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Arme ──────────────────────────────────────────────────────────────
|
||||||
|
export class VermineWeaponSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["weapon"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-weapon-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Défense ───────────────────────────────────────────────────────────
|
||||||
|
export class VermineDefenseSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["defense"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-defense-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Véhicule ──────────────────────────────────────────────────────────
|
||||||
|
export class VermineVehicleSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["vehicle"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-vehicle-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Capacité ──────────────────────────────────────────────────────────
|
||||||
|
export class VermineAbilitySheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["ability"], position: { width: 560 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-ability-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Spécialité ────────────────────────────────────────────────────────
|
||||||
|
export class VermineSpecialtySheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["specialty"], position: { width: 400 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-specialty-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Historique ────────────────────────────────────────────────────────
|
||||||
|
export class VermineBackgroundSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["background"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-background-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Traumatisme ───────────────────────────────────────────────────────
|
||||||
|
export class VermineTraumaSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["trauma"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-trauma-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Évolution ─────────────────────────────────────────────────────────
|
||||||
|
export class VermineEvolutionSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["evolution"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-evolution-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rumeur ────────────────────────────────────────────────────────────
|
||||||
|
export class VermineRumorSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["rumor"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-rumor-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Cible ─────────────────────────────────────────────────────────────
|
||||||
|
export class VermineTargetSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["target"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-target-sheet.hbs" } }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Rite ──────────────────────────────────────────────────────────────
|
||||||
|
export class VermineRiteSheetV2 extends VermineBaseItemSheet {
|
||||||
|
static DEFAULT_OPTIONS = { classes: ["rite"], position: { width: 520 } }
|
||||||
|
static PARTS = { main: { template: "systems/vermine2047/templates/item/item-rite-sheet.hbs" } }
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { VermineBaseActorSheet } from "./base-actor-sheet.mjs"
|
||||||
|
|
||||||
|
export class VermineNpcSheetV2 extends VermineBaseActorSheet {
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
classes: ["npc"],
|
||||||
|
position: { width: 750, height: 680 },
|
||||||
|
window: { contentClasses: ["npc-content"] }
|
||||||
|
}
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
main: { template: "systems/vermine2047/templates/actor/appv2/npc-main.hbs" },
|
||||||
|
tabs: { template: "templates/generic/tab-navigation.hbs" },
|
||||||
|
characteristics: { template: "systems/vermine2047/templates/actor/appv2/npc-characteristics.hbs" },
|
||||||
|
skills: { template: "systems/vermine2047/templates/actor/appv2/npc-skills.hbs" },
|
||||||
|
threat: { template: "systems/vermine2047/templates/actor/appv2/npc-threat.hbs" },
|
||||||
|
combat: { template: "systems/vermine2047/templates/actor/appv2/npc-combat.hbs" },
|
||||||
|
notes: { template: "systems/vermine2047/templates/actor/appv2/npc-notes.hbs" }
|
||||||
|
}
|
||||||
|
|
||||||
|
tabGroups = { sheet: "characteristics" }
|
||||||
|
|
||||||
|
#getTabs() {
|
||||||
|
const tabs = {
|
||||||
|
characteristics: { id: "characteristics", group: "sheet", icon: "fas fa-dice", label: "VERMINE.abilities" },
|
||||||
|
skills: { id: "skills", group: "sheet", icon: "fas fa-brain", label: "VERMINE.skills" },
|
||||||
|
threat: { id: "threat", group: "sheet", icon: "fas fa-exclamation-triangle", label: "ADVERSITY.threat" },
|
||||||
|
combat: { id: "combat", group: "sheet", icon: "fas fa-sword", label: "VERMINE.combat" },
|
||||||
|
notes: { id: "notes", group: "sheet", icon: "fas fa-sticky-note", label: "IDENTITY.notes" }
|
||||||
|
}
|
||||||
|
for (const v of Object.values(tabs)) {
|
||||||
|
v.active = this.tabGroups[v.group] === v.id
|
||||||
|
v.cssClass = v.active ? "active" : ""
|
||||||
|
}
|
||||||
|
return tabs
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
const context = await super._prepareContext()
|
||||||
|
context.tabs = this.#getTabs()
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
changeTab(tab, group, options = {}) {
|
||||||
|
super.changeTab(tab, group, options)
|
||||||
|
if (group === "sheet") {
|
||||||
|
const main = this.element?.querySelector('[data-group="sheet"][data-tab="main"]')
|
||||||
|
if (main) main.classList.add("active")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _preparePartContext(partId, context) {
|
||||||
|
const doc = this.document
|
||||||
|
switch (partId) {
|
||||||
|
case "main": break
|
||||||
|
case "characteristics":
|
||||||
|
context.tab = context.tabs.characteristics
|
||||||
|
break
|
||||||
|
case "skills":
|
||||||
|
context.tab = context.tabs.skills
|
||||||
|
break
|
||||||
|
case "threat":
|
||||||
|
context.tab = context.tabs.threat
|
||||||
|
break
|
||||||
|
case "combat":
|
||||||
|
context.tab = context.tabs.combat
|
||||||
|
const { prepareActiveEffectCategories } = await import("../../system/effects.mjs")
|
||||||
|
context.effects = prepareActiveEffectCategories(doc.effects)
|
||||||
|
break
|
||||||
|
case "notes":
|
||||||
|
context.tab = context.tabs.notes
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Module de ré-export des classes de documents
|
||||||
|
* Compatible avec Foundry V2
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { default as VermineActor } from "./actor.mjs";
|
||||||
|
export { default as VermineItem } from "./item.mjs";
|
||||||
@@ -3,44 +3,7 @@
|
|||||||
* Extend the base Actor document by defining a custom roll data structure which is ideal for the Simple system.
|
* Extend the base Actor document by defining a custom roll data structure which is ideal for the Simple system.
|
||||||
* @extends {Actor}
|
* @extends {Actor}
|
||||||
*/
|
*/
|
||||||
export class VermineActor extends Actor {
|
export default class VermineActor extends Actor {
|
||||||
|
|
||||||
/** @override */
|
|
||||||
prepareBaseData() {
|
|
||||||
// Data modifications in this step occur before processing embedded
|
|
||||||
// documents or derived data.
|
|
||||||
|
|
||||||
// Initialize wound data to prevent undefined errors with active effects
|
|
||||||
if (!this.system.minorWound) this.system.minorWound = { value: 0, min: 0, max: 5, threshold: 1 };
|
|
||||||
if (!this.system.majorWound) this.system.majorWound = { value: 0, min: 0, max: 4, threshold: 4 };
|
|
||||||
if (!this.system.deadlyWound) this.system.deadlyWound = { value: 0, min: 0, max: 2, threshold: 8 };
|
|
||||||
|
|
||||||
// Initialize combatStatus to prevent errors
|
|
||||||
if (!this.system.combatStatus) {
|
|
||||||
this.system.combatStatus = { difficulty: "9", label: "Passif" };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.type == 'character') {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
prepareEmbeddedDocuments() {
|
|
||||||
// Check if effects are already being applied in this preparation cycle
|
|
||||||
// In Foundry V11+, the parent prepareEmbeddedDocuments() calls applyActiveEffects()
|
|
||||||
// If this is called multiple times in the same cycle, we get the "phase already completed" error
|
|
||||||
const phase = this.effects?.applicationPhase;
|
|
||||||
|
|
||||||
// If we're already in the middle of applying effects (initial or final phase),
|
|
||||||
// don't call super as it would try to apply effects again
|
|
||||||
if (phase === "initial" || phase === "final") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If effects haven't been applied yet, proceed normally
|
|
||||||
super.prepareEmbeddedDocuments();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* Extend the basic Item with some very simple modifications.
|
* Extend the basic Item with some very simple modifications.
|
||||||
* @extends {Item}
|
* @extends {Item}
|
||||||
*/
|
*/
|
||||||
export class VermineItem extends Item {
|
export default class VermineItem extends Item {
|
||||||
/**
|
/**
|
||||||
* Augment the basic Item data model with additional dynamic data.
|
* Augment the basic Item data model with additional dynamic data.
|
||||||
*/
|
*/
|
||||||
@@ -15,8 +15,7 @@ export class VermineItem extends Item {
|
|||||||
const actorType = (this.actor !== null) ? this.actor.type : 'character';
|
const actorType = (this.actor !== null) ? this.actor.type : 'character';
|
||||||
const itemType = this.type;
|
const itemType = this.type;
|
||||||
|
|
||||||
|
// Vérifie si une méthode spécifique au type existe
|
||||||
// Vérifie si une méthode spécifique au type existe// preparedData specifique au type
|
|
||||||
if (typeof this[`prepare${itemType.charAt(0).toUpperCase() + itemType.slice(1)}Data`] === 'function') {
|
if (typeof this[`prepare${itemType.charAt(0).toUpperCase() + itemType.slice(1)}Data`] === 'function') {
|
||||||
this[`prepare${itemType.charAt(0).toUpperCase() + itemType.slice(1)}Data`]();
|
this[`prepare${itemType.charAt(0).toUpperCase() + itemType.slice(1)}Data`]();
|
||||||
}
|
}
|
||||||
@@ -26,15 +25,14 @@ export class VermineItem extends Item {
|
|||||||
this.damagedLabel = this.system.damages.state[parseInt(this.system.damages?.value) - 1];
|
this.damagedLabel = this.system.damages.state[parseInt(this.system.damages?.value) - 1];
|
||||||
switch (this.damagedLabel) {
|
switch (this.damagedLabel) {
|
||||||
case "endommagé":
|
case "endommagé":
|
||||||
this.damagedIcon = '<i class="fas fa-exclamation-circle" style:"color="yellow"></i>';
|
this.damagedIcon = '<i class="fas fa-exclamation-circle" style="color=yellow"></i>';
|
||||||
break;
|
break;
|
||||||
case "défectueux":
|
case "défectueux":
|
||||||
this.damagedIcon = '<i class="fas fa-exclamation-triangle" style:"color="orange"></i>';
|
this.damagedIcon = '<i class="fas fa-exclamation-triangle" style="color=orange"></i>';
|
||||||
break;
|
break;
|
||||||
case "hors d'usage":
|
case "hors d'usage":
|
||||||
this.damagedIcon = '<i class="fas fa-star-exclamation" style:"color="red"></i>';
|
this.damagedIcon = '<i class="fas fa-star-exclamation" style="color=red"></i>';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,11 +42,9 @@ export class VermineItem extends Item {
|
|||||||
const actorType = (this.actor !== null) ? this.actor.type : 'character';
|
const actorType = (this.actor !== null) ? this.actor.type : 'character';
|
||||||
|
|
||||||
if (this.system.type == "") {
|
if (this.system.type == "") {
|
||||||
// console.log('je suis une capacité, avec pour sous-type', this.system.type, actorType);
|
|
||||||
this.system.type = actorType;
|
this.system.type = actorType;
|
||||||
}
|
}
|
||||||
if (this.system.totem == "" && this.actor !== null && this.actor.system.identity.totem != "") {
|
if (this.system.totem == "" && this.actor !== null && this.actor.system.identity.totem != "") {
|
||||||
// console.log('je suis une capacité, avec pour sous-type', this.system.type, actorType);
|
|
||||||
this.system.totem = this.actor.system.identity.totem;
|
this.system.totem = this.actor.system.identity.totem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,16 +75,13 @@ export class VermineItem extends Item {
|
|||||||
const rollMode = game.settings.get('core', 'rollMode');
|
const rollMode = game.settings.get('core', 'rollMode');
|
||||||
const label = `[${item.type}] ${item.name}`;
|
const label = `[${item.type}] ${item.name}`;
|
||||||
|
|
||||||
// If there's no roll data, send a chat message.
|
|
||||||
|
|
||||||
let mess = {
|
let mess = {
|
||||||
speaker: speaker,
|
speaker: speaker,
|
||||||
rollMode: rollMode,
|
rollMode: rollMode,
|
||||||
flavor: label,
|
flavor: label,
|
||||||
};
|
};
|
||||||
mess.content = await renderTemplate(`systems/vermine2047/templates/item/chatCards/${this.type}.hbs`, { item: this, message: mess }) ?? null;
|
mess.content = await foundry.applications.handlebars.renderTemplate(`systems/vermine2047/templates/item/chatCards/${this.type}.hbs`, { item: this, message: mess }) ?? null;
|
||||||
ChatMessage.create(mess)
|
ChatMessage.create(mess)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export { default as VermineCharacterData } from "./character.mjs"
|
||||||
|
export { default as VermineNpcData } from "./npc.mjs"
|
||||||
|
export { default as VermineGroupData } from "./group.mjs"
|
||||||
|
export { default as VermineCreatureData } from "./creature.mjs"
|
||||||
|
export { default as VermineItemData } from "./item.mjs"
|
||||||
|
export { default as VermineWeaponData } from "./weapon.mjs"
|
||||||
|
export { default as VermineDefenseData } from "./defense.mjs"
|
||||||
|
export { default as VermineVehicleData } from "./vehicle.mjs"
|
||||||
|
export { default as VermineAbilityData } from "./ability.mjs"
|
||||||
|
export { default as VermineSpecialtyData } from "./specialty.mjs"
|
||||||
|
export { default as VermineBackgroundData } from "./background.mjs"
|
||||||
|
export { default as VermineTraumaData } from "./trauma.mjs"
|
||||||
|
export { default as VermineEvolutionData } from "./evolution.mjs"
|
||||||
|
export { default as VermineRumorData } from "./rumor.mjs"
|
||||||
|
export { default as VermineTargetData } from "./target.mjs"
|
||||||
|
export { default as VermineRiteData } from "./rite.mjs"
|
||||||
@@ -0,0 +1,285 @@
|
|||||||
|
/**
|
||||||
|
* Schémas partagés pour les DataModels Vermine 2047.
|
||||||
|
* Fonctions factory retournant des objets SchemaField réutilisables.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retourne un schema pour une blessure (minor/major/deadly)
|
||||||
|
* @param {number} defaultThreshold
|
||||||
|
* @param {number} defaultMax
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
export function woundSchema(defaultThreshold = 1, defaultMax = 5) {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return {
|
||||||
|
threshold: new fields.NumberField({ ...reqInt, initial: defaultThreshold, min: 0 }),
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema des 3 types de blessures présents sur tous les acteurs.
|
||||||
|
*/
|
||||||
|
export function woundsSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
minorWound: new fields.SchemaField(woundSchema(1, 5)),
|
||||||
|
majorWound: new fields.SchemaField(woundSchema(4, 4)),
|
||||||
|
deadlyWound: new fields.SchemaField(woundSchema(8, 2))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Statut de combat (offensif/actif/passif).
|
||||||
|
*/
|
||||||
|
export function combatStatusSchema(defaultDifficulty = "7") {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
difficulty: new fields.StringField({ required: true, nullable: false, initial: defaultDifficulty })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Description d'équipement.
|
||||||
|
*/
|
||||||
|
export function equipmentSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
description: new fields.HTMLField({ required: true, initial: "", textSearch: true })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribut avec value/min/max.
|
||||||
|
* @param {number} defaultVal
|
||||||
|
* @param {number} defaultMin
|
||||||
|
* @param {number} defaultMax
|
||||||
|
*/
|
||||||
|
export function attributeSchema(defaultVal = 0, defaultMin = 0, defaultMax = 10) {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: defaultVal, min: defaultMin }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: defaultMin, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caractéristique (capacité) avec catégorie.
|
||||||
|
* @param {string} category - physical, manual, mental, social
|
||||||
|
*/
|
||||||
|
export function abilityField(category) {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: 1, min: 0, max: 5 }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||||
|
category: new fields.StringField({ required: true, nullable: false, initial: category })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Les 8 caractéristiques communes à character et npc.
|
||||||
|
*/
|
||||||
|
export function abilitiesSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
vigor: abilityField("physical"),
|
||||||
|
health: abilityField("physical"),
|
||||||
|
precision: abilityField("manual"),
|
||||||
|
reflexes: abilityField("manual"),
|
||||||
|
knowledge: abilityField("mental"),
|
||||||
|
perception: abilityField("mental"),
|
||||||
|
will: abilityField("social"),
|
||||||
|
empathy: abilityField("social")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Une compétence individuelle.
|
||||||
|
* @param {string} category
|
||||||
|
* @param {number} rarity - 0, 1, ou 2
|
||||||
|
*/
|
||||||
|
export function skillField(category, rarity = 0) {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 5 }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||||
|
category: new fields.StringField({ required: true, nullable: false, initial: category }),
|
||||||
|
rarity: new fields.NumberField({ ...reqInt, initial: rarity, min: 0, max: 2 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Les 30 compétences (character et npc).
|
||||||
|
*/
|
||||||
|
export function skillsSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
// man
|
||||||
|
arts: skillField("man", 1),
|
||||||
|
civilization: skillField("man", 2),
|
||||||
|
psychology: skillField("man", 1),
|
||||||
|
rumors: skillField("man", 0),
|
||||||
|
healing: skillField("man", 1),
|
||||||
|
// animal
|
||||||
|
animalism: skillField("animal", 1),
|
||||||
|
dissection: skillField("animal", 2),
|
||||||
|
wildlife: skillField("animal", 1),
|
||||||
|
repulsion: skillField("animal", 0),
|
||||||
|
tracks: skillField("animal", 0),
|
||||||
|
// tool
|
||||||
|
crafting: skillField("tool", 2),
|
||||||
|
diy: skillField("tool", 0),
|
||||||
|
mecanical: skillField("tool", 2),
|
||||||
|
piloting: skillField("tool", 1),
|
||||||
|
technology: skillField("tool", 2),
|
||||||
|
// weapon
|
||||||
|
firearms: skillField("weapon", 2),
|
||||||
|
archery: skillField("weapon", 0),
|
||||||
|
armory: skillField("weapon", 2),
|
||||||
|
throwing: skillField("weapon", 0),
|
||||||
|
melee: skillField("weapon", 0),
|
||||||
|
// survival
|
||||||
|
alertness: skillField("survival", 0),
|
||||||
|
atletics: skillField("survival", 0),
|
||||||
|
food: skillField("survival", 0),
|
||||||
|
stealth: skillField("survival", 0),
|
||||||
|
close: skillField("survival", 0),
|
||||||
|
// world
|
||||||
|
environment: skillField("world", 1),
|
||||||
|
flora: skillField("world", 1),
|
||||||
|
road: skillField("world", 0),
|
||||||
|
toxics: skillField("world", 2),
|
||||||
|
ruins: skillField("world", 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catégories de compétences avec domaine de prédilection.
|
||||||
|
*/
|
||||||
|
export function skillCategoriesSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
preferred: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
man: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.man" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
animal: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.animal" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
tool: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.tool" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
weapon: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.weapon" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
survival: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.survival" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
world: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "VERMINE.skill_category.world" }),
|
||||||
|
preferred: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Item shared schemas ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rareté avec handicap.
|
||||||
|
*/
|
||||||
|
export function raritySchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: 3, min: 1, max: 5 }),
|
||||||
|
handicap: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dégâts des items (hors arme).
|
||||||
|
*/
|
||||||
|
export function itemDamagesSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0, max: 5 }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||||
|
pannes: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||||
|
initial: ["mineure", "mineure", "grave", "grave", "critique"]
|
||||||
|
}),
|
||||||
|
state: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||||
|
initial: ["endommagé", "endommagé", "défectueux", "défectueux", "hors d'usage"]
|
||||||
|
}),
|
||||||
|
effect: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||||
|
initial: ["bonus annulé", "bonus annulé", "malus 1D", "malus 1D", "inutilisable"]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base commune à tous les items (template "base" dans l'ancien template.json).
|
||||||
|
*/
|
||||||
|
export function baseItemSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
description: new fields.HTMLField({ required: true, initial: "", textSearch: true }),
|
||||||
|
rarity: raritySchema(),
|
||||||
|
reliability: new fields.NumberField({ ...reqInt, initial: 3, min: 1, max: 5 }),
|
||||||
|
handicap: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
quantity: new fields.NumberField({ ...reqInt, initial: 1, min: 1 }),
|
||||||
|
weight: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
traits: new fields.ObjectField({ required: true, initial: {} }),
|
||||||
|
damages: itemDamagesSchema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Template "list" pour les items abstraits (ability, background, trauma, evolution, rumor, target).
|
||||||
|
* Version légère avec seulement description.
|
||||||
|
*/
|
||||||
|
export function listItemSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
description: new fields.HTMLField({ required: true, initial: "", textSearch: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schéma d'apprentissage pour les abilities.
|
||||||
|
*/
|
||||||
|
export function learnSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
threshold: new fields.NumberField({ ...reqInt, initial: 5, min: 0 }),
|
||||||
|
hindrance: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Niveau générique (value/min/max).
|
||||||
|
*/
|
||||||
|
export function levelSchema(defaultVal = 1, defaultMin = 1, defaultMax = 5) {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: defaultVal, min: defaultMin }),
|
||||||
|
min: new fields.NumberField({ ...reqInt, initial: defaultMin, min: 0 }),
|
||||||
|
max: new fields.NumberField({ ...reqInt, initial: defaultMax, min: 0 })
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { listItemSchema, learnSchema, levelSchema } from "./_shared.mjs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "ability" (capacités, pouvoirs).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineAbilityData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.ability"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...listItemSchema(),
|
||||||
|
type: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
totem: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
learn: learnSchema(),
|
||||||
|
level: levelSchema(1, 1, 3),
|
||||||
|
effects: new fields.ArrayField(new fields.StringField({ required: true, initial: "" }), {
|
||||||
|
initial: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { listItemSchema } from "./_shared.mjs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "background" (historiques, origines).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineBackgroundData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.background"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return {
|
||||||
|
...listItemSchema(),
|
||||||
|
cost: new fields.NumberField({ ...reqInt, initial: 1, min: 0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les acteurs de type "character" (personnage).
|
||||||
|
* Étend foundry.abstract.TypeDataModel.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
woundSchema,
|
||||||
|
combatStatusSchema,
|
||||||
|
equipmentSchema,
|
||||||
|
attributeSchema,
|
||||||
|
abilitiesSchema,
|
||||||
|
skillCategoriesSchema,
|
||||||
|
skillsSchema
|
||||||
|
} from "./_shared.mjs"
|
||||||
|
|
||||||
|
export default class VermineCharacterData extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.character"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Blessures (base)
|
||||||
|
minorWound: new fields.SchemaField(woundSchema(1, 5)),
|
||||||
|
majorWound: new fields.SchemaField(woundSchema(4, 4)),
|
||||||
|
deadlyWound: new fields.SchemaField(woundSchema(8, 2)),
|
||||||
|
|
||||||
|
// Statut de combat (base)
|
||||||
|
combatStatus: combatStatusSchema("7"),
|
||||||
|
|
||||||
|
// Adaptation (totems humain/adapté)
|
||||||
|
adaptation: new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 0, max: 5 }),
|
||||||
|
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 5, min: 0 }),
|
||||||
|
totems: new fields.SchemaField({
|
||||||
|
human: new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 0, max: 3 }),
|
||||||
|
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 3, min: 0 })
|
||||||
|
}),
|
||||||
|
adapted: new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1, min: 0, max: 3 }),
|
||||||
|
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 3, min: 0 })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Identité
|
||||||
|
identity: new fields.SchemaField({
|
||||||
|
height: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
weight: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
age: new fields.StringField({ required: true, nullable: false, initial: "15" }),
|
||||||
|
ageType: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 2 }),
|
||||||
|
profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
instincts: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
prohibits: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
objectives: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
relations: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
biography: new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Équipement
|
||||||
|
equipment: equipmentSchema(),
|
||||||
|
|
||||||
|
// Attributs (XP, réputation, sang-froid, effort)
|
||||||
|
attributes: new fields.SchemaField({
|
||||||
|
xp: attributeSchema(0, 0, 10),
|
||||||
|
reputation: attributeSchema(0, 0, 10),
|
||||||
|
self_control: attributeSchema(0, 0, 5),
|
||||||
|
effort: attributeSchema(0, 0, 5)
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Rencontres
|
||||||
|
encounters: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
|
||||||
|
|
||||||
|
// Caractéristiques (8)
|
||||||
|
abilities: abilitiesSchema(),
|
||||||
|
|
||||||
|
// Catégories de compétences
|
||||||
|
skill_categories: skillCategoriesSchema(),
|
||||||
|
|
||||||
|
// Compétences (30)
|
||||||
|
skills: skillsSchema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData()
|
||||||
|
|
||||||
|
// 1. Déterminer la tranche d'âge
|
||||||
|
this._setAgeType()
|
||||||
|
|
||||||
|
// 2. Calculer les modificateurs de caractéristiques
|
||||||
|
this._setAbilityModifiers()
|
||||||
|
|
||||||
|
// 3. Calculer les réserves (sang-froid et effort)
|
||||||
|
this._setSelfControlMax()
|
||||||
|
this._setEffortMax()
|
||||||
|
|
||||||
|
// 4. Calculer les seuils de blessures
|
||||||
|
this._setWoundThresholds()
|
||||||
|
|
||||||
|
// 5. Mettre à jour le statut de combat
|
||||||
|
this._updateCombatStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Détermine la tranche d'âge (1=jeune, 2=adulte, 3=vieux)
|
||||||
|
* à partir de l'âge et de la config VERMINE.AgeTypes.
|
||||||
|
*/
|
||||||
|
_setAgeType() {
|
||||||
|
const age = this.identity.age
|
||||||
|
const ageTypes = CONFIG.VERMINE.AgeTypes
|
||||||
|
for (const [type, cfg] of Object.entries(ageTypes)) {
|
||||||
|
if (age >= parseInt(cfg.beginning, 10)) {
|
||||||
|
this.identity.ageType = parseInt(type, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule les modificateurs de caractéristiques (règle d20).
|
||||||
|
*/
|
||||||
|
_setAbilityModifiers() {
|
||||||
|
for (const ability of Object.values(this.abilities)) {
|
||||||
|
ability.mod = Math.floor((ability.value - 10) / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule le max de sang-froid :
|
||||||
|
* somme des caractéristiques mentales + sociales + modificateur d'âge.
|
||||||
|
*/
|
||||||
|
_setSelfControlMax() {
|
||||||
|
const abilities = Object.values(this.abilities)
|
||||||
|
const modFromAge = this._getModFromAgeSelfControl()
|
||||||
|
const sum = abilities
|
||||||
|
.filter(a => a.category === "mental" || a.category === "social")
|
||||||
|
.reduce((acc, a) => acc + a.value, 0)
|
||||||
|
this.attributes.self_control.max = sum + modFromAge
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule le max d'effort :
|
||||||
|
* somme des caractéristiques physiques + manuelles + modificateur d'âge.
|
||||||
|
*/
|
||||||
|
_setEffortMax() {
|
||||||
|
const abilities = Object.values(this.abilities)
|
||||||
|
const modFromAge = this._getModFromAgeEffort()
|
||||||
|
const sum = abilities
|
||||||
|
.filter(a => a.category === "physical" || a.category === "manual")
|
||||||
|
.reduce((acc, a) => acc + a.value, 0)
|
||||||
|
this.attributes.effort.max = sum + modFromAge
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule les seuils de blessures à partir de la Santé.
|
||||||
|
*/
|
||||||
|
_setWoundThresholds() {
|
||||||
|
const health = this.abilities.health.value
|
||||||
|
const ageMods = this._getModFromAgeWounds()
|
||||||
|
|
||||||
|
this.minorWound.threshold = health
|
||||||
|
this.majorWound.threshold = health + 3
|
||||||
|
this.deadlyWound.threshold = (health + 7 < 11) ? health + 7 : 10
|
||||||
|
|
||||||
|
this.minorWound.max = 4 + ageMods.l
|
||||||
|
this.majorWound.max = 3 + ageMods.h
|
||||||
|
this.deadlyWound.max = 2 + ageMods.d
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le label du statut de combat en fonction de la difficulté.
|
||||||
|
*/
|
||||||
|
_updateCombatStatus() {
|
||||||
|
const difficulty = parseInt(this.combatStatus.difficulty) || 9
|
||||||
|
let newLabel = "Passif"
|
||||||
|
switch (difficulty) {
|
||||||
|
case 5: newLabel = "Offensif"; break
|
||||||
|
case 7: newLabel = "Actif"; break
|
||||||
|
case 9: newLabel = "Passif"; break
|
||||||
|
}
|
||||||
|
if (this.combatStatus.label !== newLabel) {
|
||||||
|
this.combatStatus.label = newLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Modificateurs liés à l'âge ──────────────────────────────────────
|
||||||
|
|
||||||
|
/** @returns {number} Modificateur de sang-froid selon l'âge. */
|
||||||
|
_getModFromAgeSelfControl() {
|
||||||
|
return this.identity.ageType === 1 ? -1 : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {number} Modificateur d'effort selon l'âge. */
|
||||||
|
_getModFromAgeEffort() {
|
||||||
|
if (this.identity.ageType === 1) return -1
|
||||||
|
if (this.identity.ageType === 3) return -2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {{l: number, h: number, d: number}} Modificateurs de blessures selon l'âge. */
|
||||||
|
_getModFromAgeWounds() {
|
||||||
|
if (this.identity.ageType === 1) return { l: 0, h: 0, d: -1 }
|
||||||
|
if (this.identity.ageType === 3) return { l: -1, h: -1, d: -1 }
|
||||||
|
return { l: 0, h: 0, d: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les acteurs de type "creature" (créature).
|
||||||
|
* Étend foundry.abstract.TypeDataModel.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
woundSchema,
|
||||||
|
combatStatusSchema,
|
||||||
|
equipmentSchema,
|
||||||
|
attributeSchema
|
||||||
|
} from "./_shared.mjs"
|
||||||
|
|
||||||
|
export default class VermineCreatureData extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.creature"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Blessures (base)
|
||||||
|
minorWound: new fields.SchemaField(woundSchema(1, 5)),
|
||||||
|
majorWound: new fields.SchemaField(woundSchema(4, 4)),
|
||||||
|
deadlyWound: new fields.SchemaField(woundSchema(8, 2)),
|
||||||
|
|
||||||
|
// Statut de combat (base)
|
||||||
|
combatStatus: combatStatusSchema(),
|
||||||
|
|
||||||
|
// Identité
|
||||||
|
identity: new fields.SchemaField({
|
||||||
|
profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
origin: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
notes: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
biography: new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Compétences (description libre)
|
||||||
|
skills: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
|
||||||
|
// Modes de jeu actifs
|
||||||
|
modes: new fields.SchemaField({
|
||||||
|
survival: new fields.BooleanField({ required: true, initial: true }),
|
||||||
|
nightmare: new fields.BooleanField({ required: true, initial: true }),
|
||||||
|
apocalypse: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Niveaux de créature (patron, taille, rôle, meute)
|
||||||
|
pattern: attributeSchema(1, 1, 4),
|
||||||
|
size: attributeSchema(1, 1, 3),
|
||||||
|
role: attributeSchema(1, 1, 4),
|
||||||
|
pack: attributeSchema(0, 0, 3),
|
||||||
|
|
||||||
|
// Valeurs calculées (dérivées de pattern/size/role/pack)
|
||||||
|
computed: new fields.SchemaField({
|
||||||
|
attack: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
damage: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
vigor: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
reaction: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
reactionBonus: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
pools: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
gear: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 9 }),
|
||||||
|
gearHindrance: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0 }),
|
||||||
|
protection: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 1 })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Équipement
|
||||||
|
equipment: equipmentSchema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData()
|
||||||
|
|
||||||
|
// 1. Calculer les valeurs dérivées (attaque, dégâts, vigueur, etc.)
|
||||||
|
this._calculateCreatureComputedValues()
|
||||||
|
|
||||||
|
// 2. Calculer les seuils de blessures
|
||||||
|
this._calculateCreatureWoundThresholds()
|
||||||
|
|
||||||
|
// 3. Mettre à jour le statut de combat
|
||||||
|
this._updateCombatStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule les valeurs dérivées à partir des niveaux de patron, taille, rôle et meute.
|
||||||
|
* Utilise les configs CONFIG.VERMINE.creaturePatternLevels, .creatureSizeLevels,
|
||||||
|
* .creatureRoleLevels, .creaturePackLevels.
|
||||||
|
*
|
||||||
|
* Règles :
|
||||||
|
* - Attaque = pattern.attack + size.attack + pack.attack + role.reaction
|
||||||
|
* - Dégâts = pattern.damage + size.vigor + pack.damage
|
||||||
|
* - Vigueur = size.vigor + pack.damage
|
||||||
|
* - Réaction = role.reaction + role.reaction_bonus
|
||||||
|
*/
|
||||||
|
_calculateCreatureComputedValues() {
|
||||||
|
const patternLevel = this.pattern?.value || 1
|
||||||
|
const sizeLevel = this.size?.value || 1
|
||||||
|
const roleLevel = this.role?.value || 1
|
||||||
|
const packLevel = this.pack?.value || 0
|
||||||
|
|
||||||
|
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
|
||||||
|
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}
|
||||||
|
const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel] || {}
|
||||||
|
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}
|
||||||
|
|
||||||
|
// Attaque : patron + taille + meute + réaction du rôle
|
||||||
|
this.computed.attack = (patternConfig.attack || 0)
|
||||||
|
+ (sizeConfig.attack || 0)
|
||||||
|
+ (packConfig.attack || 0)
|
||||||
|
+ (roleConfig.reaction || 0)
|
||||||
|
|
||||||
|
// Dégâts : patron + vigueur de taille + meute
|
||||||
|
this.computed.damage = (patternConfig.damage || 0)
|
||||||
|
+ (sizeConfig.vigor || 0)
|
||||||
|
+ (packConfig.damage || 0)
|
||||||
|
|
||||||
|
// Vigueur : taille + meute
|
||||||
|
this.computed.vigor = (sizeConfig.vigor || 0) + (packConfig.damage || 0)
|
||||||
|
|
||||||
|
// Réaction : rôle
|
||||||
|
this.computed.reaction = (roleConfig.reaction || 0) + (roleConfig.reaction_bonus || 0)
|
||||||
|
this.computed.reactionBonus = roleConfig.reaction_bonus || 0
|
||||||
|
|
||||||
|
// Réserves
|
||||||
|
this.computed.pools = roleConfig.pools || 0
|
||||||
|
|
||||||
|
// Équipement et handicap
|
||||||
|
this.computed.gear = roleConfig.gear || 9
|
||||||
|
this.computed.gearHindrance = roleConfig.gear_hindrance || 0
|
||||||
|
|
||||||
|
// Protection
|
||||||
|
this.computed.protection = roleConfig.protection || 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule les seuils de blessures à partir du patron, de la taille et de la meute.
|
||||||
|
* Les seuils sont la somme des valeurs correspondantes des trois sources.
|
||||||
|
*/
|
||||||
|
_calculateCreatureWoundThresholds() {
|
||||||
|
const patternLevel = this.pattern?.value || 1
|
||||||
|
const sizeLevel = this.size?.value || 1
|
||||||
|
const packLevel = this.pack?.value || 0
|
||||||
|
|
||||||
|
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel] || {}
|
||||||
|
const sizeConfig = CONFIG.VERMINE.creatureSizeLevels[sizeLevel] || {}
|
||||||
|
const packConfig = CONFIG.VERMINE.creaturePackLevels[packLevel] || {}
|
||||||
|
|
||||||
|
this.minorWound.threshold = (patternConfig.minorWound || 0)
|
||||||
|
+ (sizeConfig.minorWound || 0)
|
||||||
|
+ (packConfig.minorWound || 0)
|
||||||
|
this.majorWound.threshold = (patternConfig.majorWound || 0)
|
||||||
|
+ (sizeConfig.majorWound || 0)
|
||||||
|
+ (packConfig.majorWound || 0)
|
||||||
|
this.deadlyWound.threshold = (patternConfig.deadlyWound || 0)
|
||||||
|
+ (sizeConfig.deadlyWound || 0)
|
||||||
|
+ (packConfig.deadlyWound || 0)
|
||||||
|
|
||||||
|
// Max de blessures
|
||||||
|
this.minorWound.max = Math.min(5, this.minorWound.threshold + 2)
|
||||||
|
this.majorWound.max = Math.min(4, this.majorWound.threshold + 1)
|
||||||
|
this.deadlyWound.max = Math.min(2, this.deadlyWound.threshold)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le label du statut de combat en fonction de la difficulté.
|
||||||
|
*/
|
||||||
|
_updateCombatStatus() {
|
||||||
|
const difficulty = parseInt(this.combatStatus.difficulty) || 9
|
||||||
|
let newLabel = "Passif"
|
||||||
|
switch (difficulty) {
|
||||||
|
case 5: newLabel = "Offensif"; break
|
||||||
|
case 7: newLabel = "Actif"; break
|
||||||
|
case 9: newLabel = "Passif"; break
|
||||||
|
}
|
||||||
|
if (this.combatStatus.label !== newLabel) {
|
||||||
|
this.combatStatus.label = newLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { baseItemSchema } from "./_shared.mjs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "defense" (protections, armures, boucliers).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineDefenseData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.defense"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return {
|
||||||
|
...baseItemSchema(),
|
||||||
|
level: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
specificLevel: new fields.SchemaField({
|
||||||
|
label: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
level: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||||
|
}),
|
||||||
|
mobility: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
isShield: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { listItemSchema, levelSchema } from './_shared.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "evolution" (évolutions du personnage).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineEvolutionData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.evolution"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...listItemSchema(),
|
||||||
|
level: levelSchema(1, 1, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les acteurs de type "group" (groupe).
|
||||||
|
* Étend foundry.abstract.TypeDataModel.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
woundSchema,
|
||||||
|
combatStatusSchema,
|
||||||
|
equipmentSchema,
|
||||||
|
attributeSchema,
|
||||||
|
levelSchema
|
||||||
|
} from "./_shared.mjs"
|
||||||
|
|
||||||
|
export default class VermineGroupData extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.group"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Blessures (base)
|
||||||
|
minorWound: new fields.SchemaField(woundSchema(1, 5)),
|
||||||
|
majorWound: new fields.SchemaField(woundSchema(4, 4)),
|
||||||
|
deadlyWound: new fields.SchemaField(woundSchema(8, 2)),
|
||||||
|
|
||||||
|
// Statut de combat (base)
|
||||||
|
combatStatus: combatStatusSchema(),
|
||||||
|
|
||||||
|
// Identité du groupe
|
||||||
|
identity: new fields.SchemaField({
|
||||||
|
totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
origin: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
instincts: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
prohibits: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
notes: new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Équipement
|
||||||
|
equipment: equipmentSchema(),
|
||||||
|
|
||||||
|
// Niveau du groupe (1-10)
|
||||||
|
level: levelSchema(1, 1, 10),
|
||||||
|
|
||||||
|
// Réputation
|
||||||
|
reputation: new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ required: true, nullable: true, integer: true, initial: 10, min: 2 }),
|
||||||
|
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 2, min: 0 }),
|
||||||
|
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 10, min: 0 })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Moral
|
||||||
|
morale: new fields.SchemaField({
|
||||||
|
level: new fields.StringField({ required: true, nullable: false, initial: "high" }),
|
||||||
|
value: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 7, min: 0, max: 7 }),
|
||||||
|
min: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 0, min: 0 }),
|
||||||
|
max: new fields.NumberField({ required: true, nullable: false, integer: true, initial: 7, min: 0 })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Réserve
|
||||||
|
reserve: attributeSchema(0, 0, 10),
|
||||||
|
|
||||||
|
// Objectifs (majeurs et mineurs)
|
||||||
|
objectives: new fields.SchemaField({
|
||||||
|
major: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
|
||||||
|
minor: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" }))
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Capacités de groupe
|
||||||
|
groupAbilities: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
|
||||||
|
|
||||||
|
// Membres (IDs d'acteurs)
|
||||||
|
members: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" })),
|
||||||
|
|
||||||
|
// Rencontres
|
||||||
|
encounters: new fields.ArrayField(new fields.StringField({ required: true, nullable: false, initial: "" }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData()
|
||||||
|
|
||||||
|
// 1. Initialiser les données de groupe si absentes
|
||||||
|
this._initGroupData()
|
||||||
|
|
||||||
|
// 2. Calculer la réserve max selon le niveau
|
||||||
|
this._calculateGroupReserve()
|
||||||
|
|
||||||
|
// 3. Mettre à jour le moral selon la valeur de dés
|
||||||
|
this._updateGroupMorale()
|
||||||
|
|
||||||
|
// 4. Mettre à jour le statut de combat
|
||||||
|
this._updateCombatStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise les champs optionnels du groupe s'ils ne sont pas présents.
|
||||||
|
*/
|
||||||
|
_initGroupData() {
|
||||||
|
if (!this.objectives) {
|
||||||
|
this.objectives = { major: [], minor: [] }
|
||||||
|
}
|
||||||
|
if (!this.groupAbilities) {
|
||||||
|
this.groupAbilities = []
|
||||||
|
}
|
||||||
|
if (!this.reserve) {
|
||||||
|
this.reserve = { value: 0, min: 0, max: 10 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule la réserve max en fonction du niveau du groupe.
|
||||||
|
* Règle simplifiée : niveau × 2, plafonné à 10.
|
||||||
|
*/
|
||||||
|
_calculateGroupReserve() {
|
||||||
|
const level = this.level?.value || 1
|
||||||
|
this.reserve.max = Math.min(10, level * 2)
|
||||||
|
|
||||||
|
if (this.reserve.value > this.reserve.max) {
|
||||||
|
this.reserve.value = this.reserve.max
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le niveau de moral en fonction de la valeur de dés.
|
||||||
|
* Règles : 7D+ = Haut, 6-3D = Normal, 2-1D = Bas, 0D = Crise.
|
||||||
|
*/
|
||||||
|
_updateGroupMorale() {
|
||||||
|
const moraleValue = this.morale?.value || 0
|
||||||
|
|
||||||
|
// Ne pas écraser un niveau explicitement défini (sauf "high" qui est la valeur par défaut)
|
||||||
|
if (this.morale.level && this.morale.level !== "high") return
|
||||||
|
|
||||||
|
if (moraleValue >= 7) {
|
||||||
|
this.morale.level = "high"
|
||||||
|
} else if (moraleValue >= 3) {
|
||||||
|
this.morale.level = "normal"
|
||||||
|
} else if (moraleValue >= 1) {
|
||||||
|
this.morale.level = "low"
|
||||||
|
} else {
|
||||||
|
this.morale.level = "crisis"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le label du statut de combat en fonction de la difficulté.
|
||||||
|
*/
|
||||||
|
_updateCombatStatus() {
|
||||||
|
const difficulty = parseInt(this.combatStatus.difficulty) || 9
|
||||||
|
let newLabel = "Passif"
|
||||||
|
switch (difficulty) {
|
||||||
|
case 5: newLabel = "Offensif"; break
|
||||||
|
case 7: newLabel = "Actif"; break
|
||||||
|
case 9: newLabel = "Passif"; break
|
||||||
|
}
|
||||||
|
if (this.combatStatus.label !== newLabel) {
|
||||||
|
this.combatStatus.label = newLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les items de type "item" (équipement générique).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineItemData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.item"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...baseItemSchema(),
|
||||||
|
needSkill: new fields.SchemaField({
|
||||||
|
value: new fields.BooleanField({ required: true, initial: false }),
|
||||||
|
skill: new fields.StringField({ required: true, initial: "" })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import partagé — après la déclaration de classe car defineSchema est statique
|
||||||
|
import { baseItemSchema } from './_shared.mjs'
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les acteurs de type "npc" (PNJ).
|
||||||
|
* Étend foundry.abstract.TypeDataModel.
|
||||||
|
*
|
||||||
|
* Note : le champ libre de compétences (texte descriptif) est nommé "freeSkills"
|
||||||
|
* pour éviter le conflit avec le SchemaField "skills" qui contient les 30 compétences.
|
||||||
|
*/
|
||||||
|
import {
|
||||||
|
woundSchema,
|
||||||
|
combatStatusSchema,
|
||||||
|
equipmentSchema,
|
||||||
|
attributeSchema,
|
||||||
|
abilitiesSchema,
|
||||||
|
skillCategoriesSchema,
|
||||||
|
skillsSchema
|
||||||
|
} from "./_shared.mjs"
|
||||||
|
|
||||||
|
export default class VermineNpcData extends foundry.abstract.TypeDataModel {
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.npc"]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migration des données avant traitement par le schéma.
|
||||||
|
* Avant DataModel, template.json définissait "skills" comme un champ texte libre
|
||||||
|
* pour les PNJ. Le DataModel réserve "skills" pour les 30 compétences individuelles
|
||||||
|
* (SchemaField) et utilise "freeSkills" pour le texte libre.
|
||||||
|
* @param {Object} source Données brutes avant validation du schéma
|
||||||
|
* @returns {Object} Données migrées
|
||||||
|
*/
|
||||||
|
static migrateData(source) {
|
||||||
|
if (typeof source.skills === "string") {
|
||||||
|
source.freeSkills = source.skills
|
||||||
|
}
|
||||||
|
return super.migrateData(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
|
||||||
|
return {
|
||||||
|
// Blessures (base)
|
||||||
|
minorWound: new fields.SchemaField(woundSchema(1, 5)),
|
||||||
|
majorWound: new fields.SchemaField(woundSchema(4, 4)),
|
||||||
|
deadlyWound: new fields.SchemaField(woundSchema(8, 2)),
|
||||||
|
|
||||||
|
// Statut de combat (base, difficulté par défaut 9 pour PNJ)
|
||||||
|
combatStatus: combatStatusSchema("9"),
|
||||||
|
|
||||||
|
// Identité
|
||||||
|
identity: new fields.SchemaField({
|
||||||
|
name: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
profile: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
origin: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
totem: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
theme: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
notes: new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Attributs (XP, réputation, sang-froid, effort)
|
||||||
|
attributes: new fields.SchemaField({
|
||||||
|
xp: attributeSchema(0, 0, 10),
|
||||||
|
reputation: attributeSchema(0, 0, 10),
|
||||||
|
self_control: attributeSchema(0, 0, 5),
|
||||||
|
effort: attributeSchema(0, 0, 5)
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Niveaux PNJ (menace, expérience, rôle)
|
||||||
|
threat: attributeSchema(1, 1, 4),
|
||||||
|
experience: attributeSchema(1, 1, 4),
|
||||||
|
role: attributeSchema(1, 1, 4),
|
||||||
|
|
||||||
|
// Compétences (les 30 compétences individuelles)
|
||||||
|
skills: skillsSchema(),
|
||||||
|
|
||||||
|
// Description libre des compétences (champ texte PNJ)
|
||||||
|
freeSkills: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
|
||||||
|
// Catégories de compétences
|
||||||
|
skill_categories: skillCategoriesSchema(),
|
||||||
|
|
||||||
|
// Caractéristiques (8)
|
||||||
|
abilities: abilitiesSchema(),
|
||||||
|
|
||||||
|
// Équipement
|
||||||
|
equipment: equipmentSchema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
prepareDerivedData() {
|
||||||
|
super.prepareDerivedData()
|
||||||
|
|
||||||
|
// 1. Calculer les seuils de blessures selon le niveau de menace
|
||||||
|
this._setNpcWoundThresholds()
|
||||||
|
|
||||||
|
// 2. Calculer les réserves selon le niveau de rôle
|
||||||
|
this._setNpcAttributes()
|
||||||
|
|
||||||
|
// 3. Définir les libellés des caractéristiques
|
||||||
|
this._setAbilityLabels()
|
||||||
|
|
||||||
|
// 4. Mettre à jour le statut de combat
|
||||||
|
this._updateCombatStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calcule les seuils de blessures à partir du niveau de menace.
|
||||||
|
* Utilise CONFIG.VERMINE.npcThreatLevels.
|
||||||
|
*/
|
||||||
|
_setNpcWoundThresholds() {
|
||||||
|
const health = this.abilities?.health?.value || 1
|
||||||
|
const threatLevel = this.threat?.value || 1
|
||||||
|
const threatConfig = CONFIG.VERMINE.npcThreatLevels[threatLevel] || {}
|
||||||
|
|
||||||
|
this.minorWound.threshold = threatConfig.minorWound || health
|
||||||
|
this.majorWound.threshold = threatConfig.majorWound || (health + 3)
|
||||||
|
this.deadlyWound.threshold = threatConfig.deadlyWound || (health + 7 < 11 ? health + 7 : 10)
|
||||||
|
|
||||||
|
this.minorWound.max = threatConfig.minorWound || 4
|
||||||
|
this.majorWound.max = threatConfig.majorWound || 3
|
||||||
|
this.deadlyWound.max = threatConfig.deadlyWound || 2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Définit les attributs dérivés (effort, sang-froid) selon le niveau de rôle.
|
||||||
|
* Utilise CONFIG.VERMINE.npcRoleLevels.
|
||||||
|
*/
|
||||||
|
_setNpcAttributes() {
|
||||||
|
const roleLevel = this.role?.value || 1
|
||||||
|
const roleConfig = CONFIG.VERMINE.npcRoleLevels[roleLevel] || {}
|
||||||
|
|
||||||
|
this.attributes.effort.max = roleConfig.pools || 0
|
||||||
|
this.attributes.self_control.max = roleConfig.reaction_bonus || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Définit les libellés localisés des caractéristiques.
|
||||||
|
*/
|
||||||
|
_setAbilityLabels() {
|
||||||
|
for (const [k, v] of Object.entries(this.abilities)) {
|
||||||
|
v.label = game.i18n.localize(CONFIG.VERMINE.abilities[k]) ?? k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Met à jour le label du statut de combat en fonction de la difficulté.
|
||||||
|
*/
|
||||||
|
_updateCombatStatus() {
|
||||||
|
const difficulty = parseInt(this.combatStatus.difficulty) || 9
|
||||||
|
let newLabel = "Passif"
|
||||||
|
switch (difficulty) {
|
||||||
|
case 5: newLabel = "Offensif"; break
|
||||||
|
case 7: newLabel = "Actif"; break
|
||||||
|
case 9: newLabel = "Passif"; break
|
||||||
|
}
|
||||||
|
if (this.combatStatus.label !== newLabel) {
|
||||||
|
this.combatStatus.label = newLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { baseItemSchema } from './_shared.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "rite" (rites, rituels).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineRiteData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.rite"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...baseItemSchema(),
|
||||||
|
rituel: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
transe: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
ability: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
effect: new fields.StringField({ required: true, initial: "" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { listItemSchema } from "./_shared.mjs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "rumor" (rumeurs).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineRumorData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.rumor"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...listItemSchema()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* DataModel pour les items de type "specialty" (spécialités de compétence).
|
||||||
|
* Modèle minimal sans base partagée.
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineSpecialtyData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.specialty"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
name: new fields.StringField({ required: true, nullable: false, initial: "" }),
|
||||||
|
skill: new fields.StringField({ required: true, nullable: false, initial: "" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { listItemSchema } from './_shared.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "target" (cibles, objectifs).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineTargetData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.target"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...listItemSchema(),
|
||||||
|
level: new fields.StringField({ required: true, initial: "minor" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { listItemSchema } from './_shared.mjs'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "trauma" (traumatismes, séquelles).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineTraumaData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.trauma"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
return {
|
||||||
|
...listItemSchema(),
|
||||||
|
type: new fields.StringField({ required: true, initial: "" })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { baseItemSchema } from "./_shared.mjs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "vehicle" (véhicules).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineVehicleData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.vehicle"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return {
|
||||||
|
...baseItemSchema(),
|
||||||
|
mobility: new fields.NumberField({ ...reqInt, initial: 3, min: 0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { baseItemSchema } from "./_shared.mjs"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DataModel pour les items de type "weapon" (armes).
|
||||||
|
* @augments {foundry.abstract.TypeDataModel}
|
||||||
|
*/
|
||||||
|
export default class VermineWeaponData extends foundry.abstract.TypeDataModel {
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ["VERMINE.item.weapon"]
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const fields = foundry.data.fields
|
||||||
|
const reqInt = { required: true, nullable: false, integer: true }
|
||||||
|
return {
|
||||||
|
...baseItemSchema(),
|
||||||
|
min_range: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
max_range: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
damage: new fields.SchemaField({
|
||||||
|
value: new fields.NumberField({ ...reqInt, initial: 0, min: 0 }),
|
||||||
|
type: new fields.StringField({ required: true, initial: "" }),
|
||||||
|
addVigor: new fields.BooleanField({ required: true, initial: false })
|
||||||
|
}),
|
||||||
|
ammo: new fields.NumberField({ ...reqInt, initial: 0, min: 0 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
import { onManageActiveEffect, prepareActiveEffectCategories } from "../system/effects.mjs";
|
|
||||||
import { preloadHandlebarsTemplates } from "../system/handlebars-manager.mjs";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extend the basic ActorSheet with some very simple modifications
|
|
||||||
* @extends {ActorSheet}
|
|
||||||
*/
|
|
||||||
export class VermineActorSheet extends ActorSheet {
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static get defaultOptions() {
|
|
||||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
||||||
/*classes: ["vermine2047", "sheet", "actor"],
|
|
||||||
template: "systems/vermine2047/templates/actor/actor-sheet.hbs",
|
|
||||||
height: 800,
|
|
||||||
width: 690,
|
|
||||||
resizable: false,
|
|
||||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "features" }]*/
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
get template() {
|
|
||||||
return `systems/vermine2047/templates/actor/actor-${this.actor.type}-sheet.hbs`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
getData() {
|
|
||||||
// Retrieve the data structure from the base sheet. You can inspect or log
|
|
||||||
// the context variable to see the structure, but some key properties for
|
|
||||||
// sheets are the actor object, the data object, whether or not it's
|
|
||||||
// editable, the items array, and the effects array.
|
|
||||||
const context = super.getData();
|
|
||||||
|
|
||||||
// Use a safe clone of the actor data for further operations.
|
|
||||||
const actorData = this.actor.toObject(false);
|
|
||||||
|
|
||||||
// Add the actor's data to context.data for easier access, as well as flags.
|
|
||||||
context.system = actorData.system;
|
|
||||||
context.flags = actorData.flags;
|
|
||||||
|
|
||||||
//add system config for convenience use
|
|
||||||
context.config = CONFIG.VERMINE;
|
|
||||||
|
|
||||||
// Add roll data for TinyMCE editors.
|
|
||||||
context.rollData = context.actor.getRollData();
|
|
||||||
|
|
||||||
// Prepare active effects
|
|
||||||
context.effects = prepareActiveEffectCategories(this.actor.effects);
|
|
||||||
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
activateListeners(html) {
|
|
||||||
super.activateListeners(html);
|
|
||||||
// Render the item sheet for viewing/editing prior to the editable check.
|
|
||||||
html.find('.item-edit').click(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents(".item");
|
|
||||||
const item = this.actor.items.get(li.data("itemId"));
|
|
||||||
item.sheet.render(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
|
||||||
// Everything below here is only needed if the sheet is editable
|
|
||||||
if (!this.isEditable) return;
|
|
||||||
|
|
||||||
// Add Inventory Item
|
|
||||||
html.find('.item-create').click(this._onItemCreate.bind(this));
|
|
||||||
|
|
||||||
// Delete Inventory Item
|
|
||||||
html.find('.item-delete').click(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents(".item");
|
|
||||||
const item = this.actor.items.get(li.data("itemId"));
|
|
||||||
item.delete();
|
|
||||||
li.slideUp(200, () => this.render(false));
|
|
||||||
});
|
|
||||||
html.find(".item-roll").click(ev => {
|
|
||||||
this._onRollItem(ev)
|
|
||||||
})
|
|
||||||
// Active Effect management
|
|
||||||
html.find(".effect-control").click(ev => onManageActiveEffect(ev, this.actor));
|
|
||||||
|
|
||||||
|
|
||||||
// Drag events for macros.
|
|
||||||
if (this.actor.isOwner) {
|
|
||||||
let handler = ev => this._onDragStart(ev);
|
|
||||||
html.find('li.item').each((i, li) => {
|
|
||||||
if (li.classList.contains("inventory-header")) return;
|
|
||||||
li.setAttribute("draggable", true);
|
|
||||||
li.addEventListener("dragstart", handler, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//click on wound radio
|
|
||||||
html.find('.hexa [type="radio"]').click(ev => {
|
|
||||||
ev.preventDefault();
|
|
||||||
ev.stopPropagation();
|
|
||||||
|
|
||||||
return this._onClickRadioHexa(ev)
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async _onRollItem(ev) {
|
|
||||||
const li = $(ev.currentTarget).parents(".item");
|
|
||||||
const item = this.actor.items.get(li.data("itemId"));
|
|
||||||
item.roll();
|
|
||||||
}
|
|
||||||
_onClickRadioHexa(ev) {
|
|
||||||
let input = ev.currentTarget;
|
|
||||||
console.log(input.value, input.name);
|
|
||||||
let update = {};
|
|
||||||
update[input.name] = 0
|
|
||||||
let propTree = input.name.split('.')
|
|
||||||
let current = this.actor;
|
|
||||||
for (let prop of propTree) {
|
|
||||||
current = current[prop]
|
|
||||||
}
|
|
||||||
if (current != input.value) {
|
|
||||||
update[input.name] = parseInt(input.value)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
update[input.name] = parseInt(input.value) - 1;
|
|
||||||
}
|
|
||||||
this.actor.update(update)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
async _onItemCreate(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const header = event.currentTarget;
|
|
||||||
// Get the type of item to create.
|
|
||||||
const type = header.dataset.type;
|
|
||||||
// Grab any data associated with this control.
|
|
||||||
const data = foundry.utils.duplicate(header.dataset);
|
|
||||||
// Initialize a default name.
|
|
||||||
// const name = `New ${type.capitalize()}`;
|
|
||||||
const name = game.i18n.localize('ITEMS.new_' + type);
|
|
||||||
|
|
||||||
// Prepare the item object.
|
|
||||||
const itemData = {
|
|
||||||
name: name,
|
|
||||||
type: type,
|
|
||||||
system: data
|
|
||||||
};
|
|
||||||
// Remove the type from the dataset since it's in the itemData.type prop.
|
|
||||||
delete itemData.system["type"];
|
|
||||||
|
|
||||||
// Finally, create the item!
|
|
||||||
return await Item.create(itemData, { parent: this.actor });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
import { onManageActiveEffect, prepareActiveEffectCategories } from "../system/effects.mjs";
|
|
||||||
import { VermineActorSheet } from "./actor-sheet.mjs";
|
|
||||||
import RollDialog from "../system/dialogs/rollDialog.mjs";
|
|
||||||
import { TotemPicker } from "../system/applications.mjs";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extend the basic ActorSheet with some very simple modifications
|
|
||||||
* @extends {VermineActorSheet}
|
|
||||||
*/
|
|
||||||
export class VermineCharacterSheet extends VermineActorSheet {
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static get defaultOptions() {
|
|
||||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
||||||
classes: ["vermine2047", "sheet", "character", "actor"],
|
|
||||||
template: "systems/vermine2047/templates/actor/actor-sheet.hbs",
|
|
||||||
width: "fit-content",
|
|
||||||
height: "fit-content",
|
|
||||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "features" }]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
get template() {
|
|
||||||
return `systems/vermine2047/templates/actor/actor-character-sheet.hbs`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
getData() {
|
|
||||||
// Retrieve the data structure from the base sheet. You can inspect or log
|
|
||||||
// the context variable to see the structure, but some key properties for
|
|
||||||
// sheets are the actor object, the data object, whether or not it's
|
|
||||||
// editable, the items array, and the effects array.
|
|
||||||
const context = super.getData();
|
|
||||||
|
|
||||||
// Use a safe clone of the actor data for further operations.
|
|
||||||
const actorData = this.actor.toObject(false);
|
|
||||||
|
|
||||||
// Add the actor's data to context.data for easier access, as well as flags.
|
|
||||||
context.system = actorData.system;
|
|
||||||
context.flags = actorData.flags;
|
|
||||||
context.config = CONFIG.VERMINE;
|
|
||||||
|
|
||||||
// Prepare character data and items.
|
|
||||||
if (actorData.type == 'character') {
|
|
||||||
this._prepareItems(context);
|
|
||||||
this._prepareCharacterData(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare NPC data and items.
|
|
||||||
if (actorData.type == 'npc') {
|
|
||||||
this._prepareItems(context);
|
|
||||||
}
|
|
||||||
// Add roll data for TinyMCE editors.
|
|
||||||
context.rollData = context.actor.getRollData();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Prepare active effects
|
|
||||||
context.effects = prepareActiveEffectCategories(this.actor.effects);
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Organize and classify Items for Character sheets.
|
|
||||||
*
|
|
||||||
* @param {Object} actorData The actor to prepare.
|
|
||||||
*
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_prepareCharacterData(context) {
|
|
||||||
// Handle ability scores.
|
|
||||||
for (let [k, v] of Object.entries(context.system.abilities)) {
|
|
||||||
v.label = game.i18n.localize(context.system.abilities[k].label) ?? k;
|
|
||||||
}
|
|
||||||
for (let [k, v] of Object.entries(context.system.skills)) {
|
|
||||||
if (v.value >= 2) {
|
|
||||||
let spe = this.actor.items.filter(it => it.type == "specialty").filter(spec => spec.system.skill == k);
|
|
||||||
v.specialties = spe
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Organize and classify Items for Character sheets.
|
|
||||||
*
|
|
||||||
* @param {Object} actorData The actor to prepare.
|
|
||||||
*
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_prepareItems(context) {
|
|
||||||
context.gear = this.actor.itemTypes['item'];
|
|
||||||
context.weapons = this.actor.itemTypes['weapon'];
|
|
||||||
context.defenses = this.actor.itemTypes['defense'];
|
|
||||||
context.traits = this.actor.itemTypes['trait'];
|
|
||||||
context.specialties = this.actor.itemTypes['specialty'];
|
|
||||||
context.abilities = this.actor.itemTypes['ability'];
|
|
||||||
context.evolutions = this.actor.itemTypes['evolution'];
|
|
||||||
context.traumas = this.actor.itemTypes['trauma'];
|
|
||||||
context.backgrounds = this.actor.itemTypes['background'];
|
|
||||||
context.rumors = this.actor.itemTypes['rumor'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
activateListeners(html) {
|
|
||||||
super.activateListeners(html);
|
|
||||||
//desactiver les inputs si mode jeu
|
|
||||||
if (!this.actor.flags.world?.editMode) {
|
|
||||||
this.disableInputs(html)
|
|
||||||
}
|
|
||||||
// Choose Totem
|
|
||||||
html.find('.chooseTotem').click(this._onTotemButton.bind(this));
|
|
||||||
//activer lest jets
|
|
||||||
html.find('.ability .rollable').click(this._onRoll.bind(this));
|
|
||||||
//gérer les dés totems
|
|
||||||
html.find('[data-totem-name]').click(this._onClickTotemDice.bind(this));
|
|
||||||
//creation de specialités
|
|
||||||
html.find('i.add-specialty').click(this.addSpecialty.bind(this))
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//mode jeu/edit en mode jeu on bloque les selects et input
|
|
||||||
disableInputs(html) {
|
|
||||||
for (let input of html.find('input')) {
|
|
||||||
//préserver le toggle mode jeu/ mode edit
|
|
||||||
if (input.name != "flags.world.editMode") {
|
|
||||||
input.setAttribute('disabled', true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let select of html.find('select')) {
|
|
||||||
select.setAttribute('disabled', true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async addSpecialty(ev) {
|
|
||||||
let skillName = ev.target.closest('.ability').querySelector('label').dataset.label;
|
|
||||||
let itemData = {
|
|
||||||
name: `spécialité, ${skillName}`,
|
|
||||||
type: 'specialty',
|
|
||||||
system: {
|
|
||||||
skill: skillName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let spec = await this.actor.createEmbeddedDocuments("Item", [itemData]);
|
|
||||||
spec[0].sheet.render(true)
|
|
||||||
}
|
|
||||||
async _onClickTotemDice(ev) {
|
|
||||||
let el = ev.currentTarget;
|
|
||||||
let totem = el.dataset.totemName;
|
|
||||||
let value = parseInt(el.dataset.totemValue) || 0;
|
|
||||||
|
|
||||||
let oldValue = this.actor.system.adaptation.totems[totem].value;
|
|
||||||
if (value === oldValue) { value-- };
|
|
||||||
let updates = {};
|
|
||||||
updates[`system.adaptation.totems.${totem}.value`] = value;
|
|
||||||
//verifier le max des dés totems
|
|
||||||
let sum = value;
|
|
||||||
switch (totem) {
|
|
||||||
case "human":
|
|
||||||
sum += this.actor.system.adaptation.totems.adapted.value;
|
|
||||||
break;
|
|
||||||
case "adapted":
|
|
||||||
sum += this.actor.system.adaptation.totems.human.value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (sum > 5) { return ui.notifications.warn("pas plus de 5 dés totems") }
|
|
||||||
await this.actor.update(updates);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Handle clickable rolls.
|
|
||||||
* @param {Event} event The originating click event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
async _onRoll(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const element = event.currentTarget;
|
|
||||||
const dataset = element.dataset;
|
|
||||||
console.log("Ceci est un jet d'un personnage joueur", this.actor);
|
|
||||||
// Handle item rolls.
|
|
||||||
if (dataset.rollType) {
|
|
||||||
if (dataset.rollType == 'item') {
|
|
||||||
const itemId = element.closest('.item').dataset.itemId;
|
|
||||||
const item = this.actor.items.get(itemId);
|
|
||||||
if (item) return item.roll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle rolls that supply the formula directly.
|
|
||||||
if (dataset.label) {
|
|
||||||
dataset.rollType = dataset.type;
|
|
||||||
|
|
||||||
let data = {
|
|
||||||
actorId: this.object.id,
|
|
||||||
rollType: dataset.rollType,
|
|
||||||
labelKey: dataset.label,
|
|
||||||
|
|
||||||
label: game.i18n.localize(dataset.label)
|
|
||||||
};
|
|
||||||
|
|
||||||
let dial = await RollDialog.create(data);
|
|
||||||
console.log("from sheet", data, this)
|
|
||||||
return dial.render(true)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle totem pick
|
|
||||||
* @param {Event} event The originating click event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_onTotemButton(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const el = event.currentTarget;
|
|
||||||
// const dataset = el.dataset;
|
|
||||||
|
|
||||||
const totemPicker = new TotemPicker(el, this.actor);
|
|
||||||
totemPicker.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
import { onManageActiveEffect, prepareActiveEffectCategories } from "../system/effects.mjs";
|
|
||||||
import { VermineActorSheet } from "./actor-sheet.mjs";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extend the basic ActorSheet with some very simple modifications
|
|
||||||
* @extends {ActorSheet}
|
|
||||||
*/
|
|
||||||
export class VermineCreatureSheet extends VermineActorSheet {
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static get defaultOptions() {
|
|
||||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
||||||
classes: ["vermine2047", "sheet", "actor", "creature"],
|
|
||||||
template: "systems/vermine2047/templates/actor/actor-sheet.hbs",
|
|
||||||
width: 650,
|
|
||||||
height: 600,
|
|
||||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
get template() {
|
|
||||||
return `systems/vermine2047/templates/actor/actor-${this.actor.type}-sheet.hbs`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
getData() {
|
|
||||||
// Retrieve the data structure from the base sheet. You can inspect or log
|
|
||||||
// the context variable to see the structure, but some key properties for
|
|
||||||
// sheets are the actor object, the data object, whether or not it's
|
|
||||||
// editable, the items array, and the effects array.
|
|
||||||
const context = super.getData();
|
|
||||||
|
|
||||||
// Use a safe clone of the actor data for further operations.
|
|
||||||
const actorData = this.actor.toObject(false);
|
|
||||||
|
|
||||||
// Add the actor's data to context.data for easier access, as well as flags.
|
|
||||||
context.system = actorData.system;
|
|
||||||
context.flags = actorData.flags;
|
|
||||||
context.config = CONFIG.VERMINE;
|
|
||||||
|
|
||||||
// Prepare character data and items.
|
|
||||||
if (actorData.type == 'character') {
|
|
||||||
this._prepareItems(context);
|
|
||||||
this._prepareCharacterData(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare NPC data and items.
|
|
||||||
if (actorData.type == 'npc') {
|
|
||||||
this._prepareItems(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare Creature data and items.
|
|
||||||
if (actorData.type == 'creature') {
|
|
||||||
this._prepareItems(context);
|
|
||||||
this._prepareCreatureData(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add roll data for TinyMCE editors.
|
|
||||||
context.rollData = context.actor.getRollData();
|
|
||||||
|
|
||||||
// Prepare active effects
|
|
||||||
context.effects = prepareActiveEffectCategories(this.actor.effects);
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Organize and classify Items for Character sheets.
|
|
||||||
*
|
|
||||||
* @param {Object} actorData The actor to prepare.
|
|
||||||
*
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_prepareItems(context) {
|
|
||||||
context.gear = this.actor.itemTypes['item'];
|
|
||||||
context.traits = this.actor.itemTypes['trait'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare Character type specific data.
|
|
||||||
*
|
|
||||||
* @param {Object} actorData The actor to prepare.
|
|
||||||
*
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_prepareCharacterData(context) {
|
|
||||||
// Handle ability scores.
|
|
||||||
for (let [k, v] of Object.entries(context.system.abilities)) {
|
|
||||||
v.label = game.i18n.localize(context.system.abilities[k].label) ?? k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare Creature type specific data for the sheet.
|
|
||||||
*
|
|
||||||
* @param {Object} context The context data to prepare.
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_prepareCreatureData(context) {
|
|
||||||
if (this.actor.type !== 'creature') return;
|
|
||||||
|
|
||||||
// Add computed values to context
|
|
||||||
context.computed = context.system.computed || {};
|
|
||||||
|
|
||||||
// Get labels for pattern, size, role
|
|
||||||
const patternLevel = context.system.pattern?.value || 1;
|
|
||||||
const sizeLevel = context.system.size?.value || 1;
|
|
||||||
const roleLevel = context.system.role?.value || 1;
|
|
||||||
const packLevel = context.system.pack?.value || 0;
|
|
||||||
|
|
||||||
// Add pattern label
|
|
||||||
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel];
|
|
||||||
if (patternConfig) {
|
|
||||||
context.patternLabel = game.i18n.localize(patternConfig.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add size label (using numeric for now)
|
|
||||||
context.sizeLabel = sizeLevel;
|
|
||||||
|
|
||||||
// Add role label
|
|
||||||
const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel];
|
|
||||||
if (roleConfig) {
|
|
||||||
context.roleLabel = game.i18n.localize(roleConfig.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add pack label
|
|
||||||
context.packLabel = packLevel > 0 ? packLevel : game.i18n.localize('VERMINE.none');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
activateListeners(html) {
|
|
||||||
super.activateListeners(html);
|
|
||||||
|
|
||||||
html.find('.item-create').click(this._onItemCreate.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
async _onItemCreate(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const header = event.currentTarget;
|
|
||||||
// Get the type of item to create.
|
|
||||||
const type = header.dataset.type;
|
|
||||||
// Grab any data associated with this control.
|
|
||||||
const data = foundry.utils.duplicate(header.dataset);
|
|
||||||
// Initialize a default name.
|
|
||||||
// const name = `New ${type.capitalize()}`;
|
|
||||||
const name = game.i18n.localize('ITEMS.new_' + type);
|
|
||||||
|
|
||||||
console.log('onItemCreate child', data.type, this.actor.type);
|
|
||||||
// Prepare the item object.
|
|
||||||
const itemData = {
|
|
||||||
name: name,
|
|
||||||
type: type,
|
|
||||||
system: data
|
|
||||||
};
|
|
||||||
// Remove the type from the dataset since it's in the itemData.type prop.
|
|
||||||
delete itemData.system["type"];
|
|
||||||
|
|
||||||
// Finally, create the item!
|
|
||||||
return await Item.create(itemData, { parent: this.actor });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
import { onManageActiveEffect, prepareActiveEffectCategories } from "../system/effects.mjs";
|
|
||||||
import { VermineActorSheet } from "./actor-sheet.mjs";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extend the basic ActorSheet with some very simple modifications
|
|
||||||
* @extends {ActorSheet}
|
|
||||||
*/
|
|
||||||
export class VermineCreatureSheet extends VermineActorSheet {
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static get defaultOptions() {
|
|
||||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
||||||
classes: ["vermine2047", "sheet", "actor", "creature"],
|
|
||||||
template: "systems/vermine2047/templates/actor/actor-sheet.hbs",
|
|
||||||
width: 650,
|
|
||||||
height: 600,
|
|
||||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
get template() {
|
|
||||||
return `systems/vermine2047/templates/actor/actor-${this.actor.type}-sheet.hbs`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
getData() {
|
|
||||||
// Retrieve the data structure from the base sheet. You can inspect or log
|
|
||||||
// the context variable to see the structure, but some key properties for
|
|
||||||
// sheets are the actor object, the data object, whether or not it's
|
|
||||||
// editable, the items array, and the effects array.
|
|
||||||
const context = super.getData();
|
|
||||||
|
|
||||||
// Use a safe clone of the actor data for further operations.
|
|
||||||
const actorData = this.actor.toObject(false);
|
|
||||||
|
|
||||||
// Add the actor's data to context.data for easier access, as well as flags.
|
|
||||||
context.system = actorData.system;
|
|
||||||
context.flags = actorData.flags;
|
|
||||||
context.config = CONFIG.VERMINE;
|
|
||||||
|
|
||||||
// Prepare character data and items.
|
|
||||||
if (actorData.type == 'character') {
|
|
||||||
this._prepareItems(context);
|
|
||||||
this._prepareCharacterData(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare NPC data and items.
|
|
||||||
if (actorData.type == 'npc') {
|
|
||||||
this._prepareItems(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare Creature data and items.
|
|
||||||
if (actorData.type == 'creature') {
|
|
||||||
this._prepareItems(context);
|
|
||||||
this._prepareCreatureData(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add roll data for TinyMCE editors.
|
|
||||||
context.rollData = context.actor.getRollData();
|
|
||||||
|
|
||||||
// Prepare active effects
|
|
||||||
context.effects = prepareActiveEffectCategories(this.actor.effects);
|
|
||||||
|
|
||||||
return context;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare Creature type specific data for the sheet.
|
|
||||||
*
|
|
||||||
* @param {Object} context The context data to prepare.
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_prepareCreatureData(context) {
|
|
||||||
if (this.actor.type !== 'creature') return;
|
|
||||||
|
|
||||||
// Add computed values to context
|
|
||||||
context.computed = context.system.computed || {};
|
|
||||||
|
|
||||||
// Get labels for pattern, size, role
|
|
||||||
const patternLevel = context.system.pattern?.value || 1;
|
|
||||||
const sizeLevel = context.system.size?.value || 1;
|
|
||||||
const roleLevel = context.system.role?.value || 1;
|
|
||||||
const packLevel = context.system.pack?.value || 0;
|
|
||||||
|
|
||||||
// Add pattern label
|
|
||||||
const patternConfig = CONFIG.VERMINE.creaturePatternLevels[patternLevel];
|
|
||||||
if (patternConfig) {
|
|
||||||
context.patternLabel = game.i18n.localize(patternConfig.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add size label (using numeric for now)
|
|
||||||
context.sizeLabel = sizeLevel;
|
|
||||||
|
|
||||||
// Add role label
|
|
||||||
const roleConfig = CONFIG.VERMINE.creatureRoleLevels[roleLevel];
|
|
||||||
if (roleConfig) {
|
|
||||||
context.roleLabel = game.i18n.localize(roleConfig.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add pack label
|
|
||||||
context.packLabel = packLevel > 0 ? packLevel : game.i18n.localize('VERMINE.none');
|
|
||||||
}}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Organize and classify Items for Character sheets.
|
|
||||||
*
|
|
||||||
* @param {Object} actorData The actor to prepare.
|
|
||||||
*
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_prepareCharacterData(context) {
|
|
||||||
// Handle ability scores.
|
|
||||||
for (let [k, v] of Object.entries(context.system.abilities)) {
|
|
||||||
v.label = game.i18n.localize(context.system.abilities[k].label) ?? k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Organize and classify Items for Character sheets.
|
|
||||||
*
|
|
||||||
* @param {Object} actorData The actor to prepare.
|
|
||||||
*
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_prepareItems(context) {
|
|
||||||
context.gear = this.actor.itemTypes['item'];
|
|
||||||
context.traits = this.actor.itemTypes['trait'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
activateListeners(html) {
|
|
||||||
super.activateListeners(html);
|
|
||||||
|
|
||||||
html.find('.item-create').click(this._onItemCreate.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
async _onItemCreate(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const header = event.currentTarget;
|
|
||||||
// Get the type of item to create.
|
|
||||||
const type = header.dataset.type;
|
|
||||||
// Grab any data associated with this control.
|
|
||||||
const data = duplicate(header.dataset);
|
|
||||||
// Initialize a default name.
|
|
||||||
// const name = `New ${type.capitalize()}`;
|
|
||||||
const name = game.i18n.localize('ITEMS.new_' + type);
|
|
||||||
|
|
||||||
console.log('onItemCreate child', data.type, this.actor.type);
|
|
||||||
// Prepare the item object.
|
|
||||||
const itemData = {
|
|
||||||
name: name,
|
|
||||||
type: type,
|
|
||||||
system: data
|
|
||||||
};
|
|
||||||
// Remove the type from the dataset since it's in the itemData.type prop.
|
|
||||||
delete itemData.system["type"];
|
|
||||||
|
|
||||||
// Finally, create the item!
|
|
||||||
return await Item.create(itemData, { parent: this.actor });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import { TraitSelector } from "../system/applications.mjs";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extend the basic ItemSheet with some very simple modifications
|
|
||||||
* @extends {ItemSheet}
|
|
||||||
*/
|
|
||||||
export class VermineItemSheet extends ItemSheet {
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static get defaultOptions() {
|
|
||||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
||||||
classes: ["vermine2047", "sheet", "item"],
|
|
||||||
width: 450,
|
|
||||||
height: "max-content",
|
|
||||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
get template() {
|
|
||||||
const path = "systems/vermine2047/templates/item";
|
|
||||||
return `${path}/item-${this.item.type}-sheet.hbs`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
getData() {
|
|
||||||
// Retrieve base data structure.
|
|
||||||
const context = super.getData();
|
|
||||||
|
|
||||||
// Use a safe clone of the item data for further operations.
|
|
||||||
const itemData = context.item;
|
|
||||||
|
|
||||||
// Retrieve the roll data for TinyMCE editors.
|
|
||||||
context.rollData = {};
|
|
||||||
let actor = this.object?.parent ?? null;
|
|
||||||
if (actor) {
|
|
||||||
context.rollData = actor.getRollData();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the actor's data to context.data for easier access, as well as flags.
|
|
||||||
context.system = itemData.system;
|
|
||||||
context.flags = itemData.flags;
|
|
||||||
context.config = CONFIG.VERMINE;
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
activateListeners(html) {
|
|
||||||
super.activateListeners(html);
|
|
||||||
// Everything below here is only needed if the sheet is editable
|
|
||||||
if (!this.isEditable) return;
|
|
||||||
//click on wound radio
|
|
||||||
html.find('.damages-row [type="radio"]').click(ev => {
|
|
||||||
this._onClickDamage(ev)
|
|
||||||
})
|
|
||||||
|
|
||||||
html.find('.traits-selector').click(ev => {
|
|
||||||
this.openTraitSelector(ev)
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
async _onClickDamage(ev) {
|
|
||||||
if (!ev.currentTarget.checked) { return }
|
|
||||||
let prop = ev.currentTarget.name;
|
|
||||||
let update = {};
|
|
||||||
update[prop] = ev.currentTarget.value - 1
|
|
||||||
|
|
||||||
this.item.update(update)
|
|
||||||
}
|
|
||||||
|
|
||||||
async openTraitSelector(ev) {
|
|
||||||
let selector = new TraitSelector(this.item);
|
|
||||||
selector.render(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
import { onManageActiveEffect, prepareActiveEffectCategories } from "../system/effects.mjs";
|
|
||||||
import { VermineActorSheet } from "./actor-sheet.mjs";
|
|
||||||
import { TotemPicker, ActorPicker } from "../system/applications.mjs";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extend the basic ActorSheet with some very simple modifications
|
|
||||||
* @extends {VermineActorSheet}
|
|
||||||
*/
|
|
||||||
export class VermineGroupSheet extends VermineActorSheet {
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static get defaultOptions() {
|
|
||||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
||||||
classes: ["vermine2047", "sheet", "actor", "group"],
|
|
||||||
template: "systems/vermine2047/templates/actor/actor-sheet.hbs",
|
|
||||||
width: 700,
|
|
||||||
height: 600,
|
|
||||||
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
get template() {
|
|
||||||
return `systems/vermine2047/templates/actor/actor-${this.actor.type}-sheet.hbs`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
getData() {
|
|
||||||
// Retrieve the data structure from the base sheet. You can inspect or log
|
|
||||||
// the context variable to see the structure, but some key properties for
|
|
||||||
// sheets are the actor object, the data object, whether or not it's
|
|
||||||
// editable, the items array, and the effects array.
|
|
||||||
const context = super.getData();
|
|
||||||
|
|
||||||
// Use a safe clone of the actor data for further operations.
|
|
||||||
const actorData = this.actor.toObject(false);
|
|
||||||
|
|
||||||
// Add the actor's data to context.data for easier access, as well as flags.
|
|
||||||
context.system = actorData.system;
|
|
||||||
context.flags = actorData.flags;
|
|
||||||
context.config = CONFIG.VERMINE;
|
|
||||||
|
|
||||||
// Prepare character data and items.
|
|
||||||
if (actorData.type == 'character') {
|
|
||||||
this._prepareItems(context);
|
|
||||||
this._prepareCharacterData(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare NPC data and items.
|
|
||||||
if (actorData.type == 'npc') {
|
|
||||||
this._prepareItems(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (actorData.type == 'group') {
|
|
||||||
this._prepareItems(context);
|
|
||||||
this._prepareGroupData(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add roll data for TinyMCE editors.
|
|
||||||
context.rollData = this.actor.getRollData();
|
|
||||||
|
|
||||||
// Prepare active effects
|
|
||||||
context.effects = prepareActiveEffectCategories(this.actor.effects);
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Organize and classify Items for Character sheets.
|
|
||||||
*
|
|
||||||
* @param {Object} actorData The actor to prepare.
|
|
||||||
*
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_prepareCharacterData(context) {
|
|
||||||
// Handle ability scores.
|
|
||||||
for (let [k, v] of Object.entries(context.system.abilities)) {
|
|
||||||
v.label = game.i18n.localize(context.system.abilities[k].label) ?? k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare Group type specific data.
|
|
||||||
* Resolves member and encounter actor IDs to actual actor data.
|
|
||||||
*
|
|
||||||
* @param {Object} context The context data to prepare.
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_prepareGroupData(context) {
|
|
||||||
if (this.actor.type !== 'group') return;
|
|
||||||
|
|
||||||
// Resolve member IDs to actor data
|
|
||||||
context.resolvedMembers = {};
|
|
||||||
if (context.system.members && context.system.members.length > 0) {
|
|
||||||
context.system.members.forEach(memberId => {
|
|
||||||
const actor = game.actors.get(memberId);
|
|
||||||
if (actor) {
|
|
||||||
context.resolvedMembers[memberId] = {
|
|
||||||
name: actor.name,
|
|
||||||
id: actor.id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve encounter IDs to actor data
|
|
||||||
context.resolvedEncounters = {};
|
|
||||||
if (context.system.encounters && context.system.encounters.length > 0) {
|
|
||||||
context.system.encounters.forEach(encounterId => {
|
|
||||||
const actor = game.actors.get(encounterId);
|
|
||||||
if (actor) {
|
|
||||||
context.resolvedEncounters[encounterId] = {
|
|
||||||
name: actor.name,
|
|
||||||
id: actor.id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set morale level based on dice value (rules: p. 68-69)
|
|
||||||
this._updateMoraleLevel(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update morale level based on dice value.
|
|
||||||
* Rules: 7D+ = Haut, 6-3D = Normal, 2D- = Bas, 0D = Crise
|
|
||||||
*
|
|
||||||
* @param {Object} context The context data.
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_updateMoraleLevel(context) {
|
|
||||||
const moraleValue = context.system.morale.value || 0;
|
|
||||||
|
|
||||||
// If level is already set, keep it
|
|
||||||
if (context.system.morale.level) return;
|
|
||||||
|
|
||||||
// Determine morale level based on dice value
|
|
||||||
if (moraleValue >= 7) {
|
|
||||||
context.system.morale.level = "high";
|
|
||||||
} else if (moraleValue >= 3) {
|
|
||||||
context.system.morale.level = "normal";
|
|
||||||
} else if (moraleValue >= 1) {
|
|
||||||
context.system.morale.level = "low";
|
|
||||||
} else {
|
|
||||||
context.system.morale.level = "crisis";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Organize and classify Items for Character sheets.
|
|
||||||
*
|
|
||||||
* @param {Object} actorData The actor to prepare.
|
|
||||||
*
|
|
||||||
* @return {undefined}
|
|
||||||
*/
|
|
||||||
_prepareItems(context) {
|
|
||||||
context.specialties = this.actor.itemTypes['specialty'];
|
|
||||||
context.backgrounds = this.actor.itemTypes['background'];
|
|
||||||
context.evolutions = this.actor.itemTypes['evolution'];
|
|
||||||
context.traumas = this.actor.itemTypes['trauma'];
|
|
||||||
|
|
||||||
context.gear = this.actor.itemTypes['item'];
|
|
||||||
context.weapons = this.actor.itemTypes['weapon'];
|
|
||||||
context.defenses = this.actor.itemTypes['defense'];
|
|
||||||
context.vehicles = this.actor.itemTypes['vehicle'];
|
|
||||||
|
|
||||||
context.totem_abilities = this.actor.itemTypes['ability'].filter(i => i.system.type === 'totem');
|
|
||||||
context.abilities = this.actor.itemTypes['ability'].filter(i => i.system.type !== 'totem');
|
|
||||||
|
|
||||||
context.members = this.actor.system.members;
|
|
||||||
context.encounters = this.actor.system.encounters;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
activateListeners(html) {
|
|
||||||
super.activateListeners(html);
|
|
||||||
|
|
||||||
// Choose Totem
|
|
||||||
html.find('.chooseTotem').click(this._onTotemButton.bind(this));
|
|
||||||
|
|
||||||
// Choose Members / Encounters
|
|
||||||
html.find('.chooseActor').click(this._onRoadButton.bind(this));
|
|
||||||
html.find('.member-delete').click(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents("li.actor");
|
|
||||||
const actorId = li.data("actor-id");
|
|
||||||
const actorIdIndex = this.actor.system.members.indexOf(actorId);
|
|
||||||
if (actorIdIndex !== -1) {
|
|
||||||
this.actor.system.members.splice(actorIdIndex, 1);
|
|
||||||
}
|
|
||||||
this.actor.update({ "system.members": this.actor.system.members });
|
|
||||||
this.render(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
html.find('.encounter-delete').click(ev => {
|
|
||||||
const li = $(ev.currentTarget).parents("li.actor");
|
|
||||||
const actorId = li.data("actor-id");
|
|
||||||
const actorIdIndex = this.actor.system.encounters.indexOf(actorId);
|
|
||||||
if (actorIdIndex !== -1) {
|
|
||||||
this.actor.system.encounters.splice(actorIdIndex, 1);
|
|
||||||
}
|
|
||||||
this.actor.update({ "system.encounters": this.actor.system.encounters });
|
|
||||||
this.render(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle objective deletion
|
|
||||||
html.find('.objective-delete').click(ev => {
|
|
||||||
ev.preventDefault();
|
|
||||||
const btn = $(ev.currentTarget);
|
|
||||||
const type = btn.data("type"); // 'major' or 'minor'
|
|
||||||
const index = parseInt(btn.data("index"));
|
|
||||||
|
|
||||||
if (!isNaN(index)) {
|
|
||||||
const objectives = foundry.utils.duplicate(this.actor.system.objectives || { major: [], minor: [] });
|
|
||||||
objectives[type].splice(index, 1);
|
|
||||||
this.actor.update({ "system.objectives": objectives });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle adding new objectives
|
|
||||||
html.find('.item-create[data-type="major_objective"], .item-create[data-type="minor_objective"]').click(ev => {
|
|
||||||
ev.preventDefault();
|
|
||||||
const btn = $(ev.currentTarget);
|
|
||||||
const type = btn.data("type") === "major_objective" ? "major" : "minor";
|
|
||||||
|
|
||||||
const objectives = foundry.utils.duplicate(this.actor.system.objectives || { major: [], minor: [] });
|
|
||||||
objectives[type].push("");
|
|
||||||
this.actor.update({ "system.objectives": objectives });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle morale level change
|
|
||||||
html.find('select[name="system.morale.level"]').change(ev => {
|
|
||||||
const select = $(ev.currentTarget);
|
|
||||||
const level = select.val();
|
|
||||||
this.actor.update({ "system.morale.level": level });
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle totem pick
|
|
||||||
* @param {Event} event The originating click event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_onTotemButton(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const el = event.currentTarget;
|
|
||||||
// const dataset = el.dataset;
|
|
||||||
|
|
||||||
const totemPicker = new TotemPicker(el, this.actor);
|
|
||||||
totemPicker.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle actor pick
|
|
||||||
* @param {Event} event The originating click event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_onRoadButton(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const el = event.currentTarget;
|
|
||||||
// const dataset = el.dataset;
|
|
||||||
|
|
||||||
const actorPicker = new ActorPicker(el, this.actor);
|
|
||||||
actorPicker.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
import { onManageActiveEffect, prepareActiveEffectCategories } from "../system/effects.mjs";
|
|
||||||
import { VermineActorSheet } from "./actor-sheet.mjs";
|
|
||||||
import { TotemPicker } from "../system/applications.mjs";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extend the basic ActorSheet for NPC type
|
|
||||||
* @extends {VermineActorSheet}
|
|
||||||
*/
|
|
||||||
export class VermineNpcSheet extends VermineActorSheet {
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static get defaultOptions() {
|
|
||||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
||||||
classes: ["vermine2047", "sheet", "actor", "npc"],
|
|
||||||
template: "systems/vermine2047/templates/actor/actor-sheet.hbs",
|
|
||||||
width: 600,
|
|
||||||
height: 700,
|
|
||||||
tabs: [
|
|
||||||
{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "characteristics" }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
get template() {
|
|
||||||
return `systems/vermine2047/templates/actor/actor-${this.actor.type}-sheet.hbs`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
getData() {
|
|
||||||
// Retrieve the data structure from the base sheet.
|
|
||||||
const context = super.getData();
|
|
||||||
|
|
||||||
// Use a safe clone of the actor data for further operations.
|
|
||||||
const actorData = this.actor.toObject(false);
|
|
||||||
|
|
||||||
// Add the actor's data to context.data for easier access, as well as flags.
|
|
||||||
context.system = actorData.system;
|
|
||||||
context.flags = actorData.flags;
|
|
||||||
context.config = CONFIG.VERMINE;
|
|
||||||
|
|
||||||
// Prepare items for all actor types
|
|
||||||
this._prepareItems(context);
|
|
||||||
|
|
||||||
// Prepare NPC-specific data
|
|
||||||
if (actorData.type === 'npc') {
|
|
||||||
this._prepareNpcData(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add roll data for TinyMCE editors
|
|
||||||
context.rollData = this.actor.getRollData();
|
|
||||||
|
|
||||||
// Prepare active effects
|
|
||||||
context.effects = prepareActiveEffectCategories(this.actor.effects);
|
|
||||||
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare NPC specific data
|
|
||||||
*/
|
|
||||||
_prepareNpcData(context) {
|
|
||||||
// Calculate derived values from threat, experience, and role
|
|
||||||
const threat = CONFIG.VERMINE.npcThreatLevels[context.system.threat.value];
|
|
||||||
const experience = CONFIG.VERMINE.npcExperienceLevels[context.system.experience.value];
|
|
||||||
const role = CONFIG.VERMINE.npcRoleLevels[context.system.role.value];
|
|
||||||
|
|
||||||
// Add calculated values to context for easier access
|
|
||||||
context.threatData = threat;
|
|
||||||
context.experienceData = experience;
|
|
||||||
context.roleData = role;
|
|
||||||
|
|
||||||
// Set wound thresholds based on threat level
|
|
||||||
if (threat) {
|
|
||||||
context.system.minorWound.threshold = threat.minorWound || context.system.minorWound.threshold;
|
|
||||||
context.system.majorWound.threshold = threat.majorWound || context.system.majorWound.threshold;
|
|
||||||
context.system.deadlyWound.threshold = threat.deadlyWound || context.system.deadlyWound.threshold;
|
|
||||||
|
|
||||||
// Set max wounds
|
|
||||||
context.system.minorWound.max = threat.minorWound || context.system.minorWound.max;
|
|
||||||
context.system.majorWound.max = threat.majorWound || context.system.majorWound.max;
|
|
||||||
context.system.deadlyWound.max = threat.deadlyWound || context.system.deadlyWound.max;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set reserve max values based on role
|
|
||||||
if (role) {
|
|
||||||
context.system.attributes.effort.max = role.pools || context.system.attributes.effort.max;
|
|
||||||
context.system.attributes.self_control.max = role.reaction_bonus || context.system.attributes.self_control.max;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare abilities with labels
|
|
||||||
for (let [k, v] of Object.entries(context.system.abilities)) {
|
|
||||||
v.label = game.i18n.localize(CONFIG.VERMINE.abilities[k]) ?? k;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare skills with localized names
|
|
||||||
for (let [k, v] of Object.entries(context.system.skills)) {
|
|
||||||
const skillKey = `VERMINE.skill.${k}`;
|
|
||||||
v.name = game.i18n.localize(skillKey);
|
|
||||||
if (v.name === skillKey) {
|
|
||||||
// Fallback to key if no translation
|
|
||||||
v.name = k.charAt(0).toUpperCase() + k.slice(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare skill categories
|
|
||||||
for (let [k, v] of Object.entries(context.system.skill_categories)) {
|
|
||||||
if (k !== 'preferred') {
|
|
||||||
v.label = game.i18n.localize(v.label) ?? k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Organize and classify Items for NPC sheets.
|
|
||||||
*
|
|
||||||
* @param {Object} context - The context to prepare.
|
|
||||||
*/
|
|
||||||
_prepareItems(context) {
|
|
||||||
context.gear = this.actor.itemTypes['item'];
|
|
||||||
context.weapons = this.actor.itemTypes['weapon'];
|
|
||||||
context.defenses = this.actor.itemTypes['defense'];
|
|
||||||
context.vehicles = this.actor.itemTypes['vehicle'];
|
|
||||||
context.abilities = this.actor.itemTypes['ability'];
|
|
||||||
context.specialties = this.actor.itemTypes['specialty'];
|
|
||||||
context.backgrounds = this.actor.itemTypes['background'];
|
|
||||||
context.traumas = this.actor.itemTypes['trauma'];
|
|
||||||
context.evolutions = this.actor.itemTypes['evolution'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
activateListeners(html) {
|
|
||||||
super.activateListeners(html);
|
|
||||||
|
|
||||||
// Choose Totem
|
|
||||||
html.find('.chooseTotem').click(this._onTotemButton.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle totem pick
|
|
||||||
* @param {Event} event - The originating click event
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
_onTotemButton(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const el = event.currentTarget;
|
|
||||||
const totemPicker = new TotemPicker(el, this.actor);
|
|
||||||
totemPicker.render(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -251,7 +251,7 @@ VERMINE.skillCategories = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VERMINE.sexes = { "male": "VERMINE.sexes.male", "female": "VERMINE.sexes.female" };
|
VERMINE.sexes = { "male": "SEXES.male", "female": "SEXES.female" };
|
||||||
|
|
||||||
VERMINE.totems = {
|
VERMINE.totems = {
|
||||||
"human": "TOTEMS.human.name",
|
"human": "TOTEMS.human.name",
|
||||||
|
|||||||
@@ -1,596 +1,327 @@
|
|||||||
import { VermineUtils } from "../roll.mjs";
|
import { VermineUtils } from "../roll.mjs";
|
||||||
|
|
||||||
/**
|
const { HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
* Dialog for rolling dice in Vermine2047.
|
|
||||||
* Handles dice pool calculation, modifiers, and roll execution.
|
|
||||||
*/
|
|
||||||
export default class RollDialog extends Dialog {
|
|
||||||
|
|
||||||
/**
|
export default class RollDialog extends HandlebarsApplicationMixin(foundry.applications.api.ApplicationV2) {
|
||||||
* Creates a new RollDialog instance.
|
|
||||||
* @param {Object} data - The data for the dialog
|
#actor;
|
||||||
* @param {HTMLElement} html - The HTML content of the dialog
|
|
||||||
* @param {Object} options - The options for the dialog
|
get title() {
|
||||||
* @param {Function} [close] - The callback function for closing the dialog
|
return game.i18n.localize("VERMINE.roll");
|
||||||
*/
|
}
|
||||||
constructor(data, html, options, close = undefined) {
|
|
||||||
const conf = {
|
static DEFAULT_OPTIONS = {
|
||||||
title: "jet de dés",
|
classes: ["vermine-roll"],
|
||||||
content: html,
|
tag: "form",
|
||||||
buttons: {
|
window: {
|
||||||
roll: {
|
icon: "fas fa-dice-d10",
|
||||||
icon: '<i class="fas fa-check"></i>',
|
resizable: false
|
||||||
label: "Lancer !",
|
},
|
||||||
callback: () => this._onRoll()
|
position: {
|
||||||
},
|
width: 520,
|
||||||
cancel: {
|
height: 600
|
||||||
icon: '<i class="fas fa-times"></i>',
|
},
|
||||||
label: "Annuler",
|
actions: {
|
||||||
callback: () => this.close()
|
roll: RollDialog.#onRoll,
|
||||||
}
|
cancel: RollDialog.#onCancel
|
||||||
},
|
|
||||||
close: close
|
|
||||||
};
|
|
||||||
super({ ...conf, ...data }, options);
|
|
||||||
// Store reference to close callback
|
|
||||||
this._closeCallback = close;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
static PARTS = {
|
||||||
* Creates a new RollDialog instance.
|
main: { template: "systems/vermine2047/templates/dialogs/roll-dialog.hbs" }
|
||||||
* @param {Object} [data] - The data for the dialog
|
};
|
||||||
* @param {string} [data.label] - Roll label
|
|
||||||
* @param {string} [data.rolltype] - Roll type
|
|
||||||
* @param {number} [data.NoD=1] - Number of dice
|
|
||||||
* @param {boolean} [data.Reroll=false] - Allow rerolls
|
|
||||||
* @param {string} [data.actorId] - Actor ID for the roll
|
|
||||||
* @returns {Promise<RollDialog|null>} The RollDialog instance or null if creation failed
|
|
||||||
*/
|
|
||||||
static async create(data = {
|
|
||||||
label: null,
|
|
||||||
rolltype: null,
|
|
||||||
NoD: 1,
|
|
||||||
Reroll: false,
|
|
||||||
actorId: game.user.character?.id ?? canvas.tokens.controlled[0]?.actor?.id
|
|
||||||
}) {
|
|
||||||
// Validate actorId
|
|
||||||
const actorId = data.actorId;
|
|
||||||
if (!actorId || typeof actorId !== 'string') {
|
|
||||||
ui.notifications.warn(game.i18n.localize('VERMINE.error_no_actor_selected'));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the actor data based on the actorId
|
static async create(data = {}) {
|
||||||
data.actor = await game.actors.get(actorId);
|
const actorId = data.actorId ?? game.user.character?.id ?? canvas.tokens.controlled[0]?.actor?.id;
|
||||||
if (!data.actor) {
|
if (!actorId || typeof actorId !== "string") {
|
||||||
ui.notifications.warn(game.i18n.localize('VERMINE.error_no_actor_selected'));
|
ui.notifications.warn(game.i18n.localize("VERMINE.error_no_actor_selected"));
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
data.availableSpecialties = data.actor.items.filter(item => item.type === "specialty");
|
|
||||||
data.availableItems = data.actor.items.filter(item => item.type === "item");
|
|
||||||
data.config = CONFIG.VERMINE;
|
|
||||||
|
|
||||||
// Define options for the dialog
|
|
||||||
const options = {
|
|
||||||
classes: ["vermineDialog"],
|
|
||||||
width: "fit-content",
|
|
||||||
height: 'fit-content',
|
|
||||||
zIndex: 99999
|
|
||||||
};
|
|
||||||
|
|
||||||
// Render the HTML template for the dialog
|
|
||||||
const html = await renderTemplate('systems/vermine2047/templates/dialogs/roll-dialog.hbs', data);
|
|
||||||
|
|
||||||
// Return a new RollDialog instance with the provided data, HTML, and options
|
|
||||||
return new RollDialog(data, html, options);
|
|
||||||
}
|
}
|
||||||
|
const actor = await game.actors.get(actorId);
|
||||||
/**
|
if (!actor) {
|
||||||
* Retrieves the default options for the RollDialog.
|
ui.notifications.warn(game.i18n.localize("VERMINE.error_no_actor_selected"));
|
||||||
*/
|
return null;
|
||||||
static get defaultOptions() {
|
|
||||||
return foundry.utils.mergeObject(super.defaultOptions, {
|
|
||||||
focus: true,
|
|
||||||
classes: ["dialog vermine-roll"],
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
/**
|
return new RollDialog({ actor, label: data.label, rolltype: data.rolltype });
|
||||||
* Retrieves the data for the dialog.
|
}
|
||||||
* @returns {Object} The context data for the dialog
|
|
||||||
*/
|
|
||||||
getData() {
|
|
||||||
// Get the context data from the superclass
|
|
||||||
const context = super.getData();
|
|
||||||
context.data = this.data;
|
|
||||||
context.config = CONFIG.VERMINE;
|
|
||||||
|
|
||||||
return context;
|
constructor(options = {}) {
|
||||||
}
|
super(options);
|
||||||
|
this.#actor = options.actor;
|
||||||
/**
|
this.label = options.label ?? null;
|
||||||
* Prepares items for display.
|
this.rolltype = options.rolltype ?? null;
|
||||||
* @returns {Array} Filtered list of items
|
}
|
||||||
*/
|
|
||||||
prepareItems() {
|
|
||||||
return this.data.actor.items.filter(it => it.type === "item");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepares specialties for display.
|
|
||||||
* @returns {Array} Filtered list of specialties
|
|
||||||
*/
|
|
||||||
prepareSpecialties() {
|
|
||||||
return this.data.actor.items.filter(it => it.type === "specialty");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activates event listeners for the dialog.
|
|
||||||
* @param {HTMLElement} html - The HTML element of the dialog.
|
|
||||||
*/
|
|
||||||
async activateListeners(html) {
|
|
||||||
// Activate event listeners from the superclass
|
|
||||||
super.activateListeners(html);
|
|
||||||
|
|
||||||
// Initialize UI elements
|
|
||||||
this._html = html;
|
|
||||||
|
|
||||||
// Retrieve roll data and set up event listeners
|
|
||||||
await this.getRollData();
|
|
||||||
|
|
||||||
// Set up event listeners for all roll-related inputs
|
|
||||||
const rollInputs = html.find('[data-roll]');
|
|
||||||
for (const inp of rollInputs) {
|
|
||||||
inp.addEventListener('change', this._onRollInputChange.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.displaySpecialties();
|
|
||||||
|
|
||||||
const selectAbil = html.find('#ability')[0];
|
|
||||||
// Set the maximum value for self control based on ability value
|
|
||||||
html.find("#self_control")[0].max = selectAbil.value;
|
|
||||||
selectAbil.addEventListener('change', this._onChangeAbility.bind(this));
|
|
||||||
const selfControl = html.find('#self_control')[0];
|
|
||||||
// Add event listener for self control changes
|
|
||||||
selfControl.addEventListener('change', this._onChangeSelfControl.bind(this));
|
|
||||||
|
|
||||||
// Set up difficulty change listener
|
|
||||||
html.find('#difficulty')[0].addEventListener('change', this._onDifficultyChange.bind(this));
|
|
||||||
|
|
||||||
// Set up handicap change listener
|
|
||||||
html.find('#handicap')[0].addEventListener('change', this._onHandicapChange.bind(this));
|
|
||||||
|
|
||||||
// Set up totem checkbox listeners
|
|
||||||
html.find('#human-totem')[0]?.addEventListener('change', this._onTotemChange.bind(this));
|
|
||||||
html.find('#adapted-totem')[0]?.addEventListener('change', this._onTotemChange.bind(this));
|
|
||||||
|
|
||||||
// Initial update of all UI elements
|
|
||||||
this._updateUI();
|
|
||||||
|
|
||||||
|
async _prepareContext() {
|
||||||
|
const actor = this.#actor;
|
||||||
|
return {
|
||||||
|
actor,
|
||||||
|
system: actor.system,
|
||||||
|
config: CONFIG.VERMINE,
|
||||||
|
label: this.label,
|
||||||
|
rollType: this.rolltype,
|
||||||
|
labelKey: this.label,
|
||||||
|
speakerId: actor.id,
|
||||||
|
ability: null,
|
||||||
|
help: false,
|
||||||
|
specialty: false,
|
||||||
|
availableSpecialties: actor.items.filter(i => i.type === "specialty"),
|
||||||
|
availableItems: actor.items.filter(i => i.type === "item")
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
async _onRender(context, options) {
|
||||||
* Retrieves the roll data for the dialog.
|
this.element.dataset.actorId = this.#actor.id;
|
||||||
* @param {Event} _ev - The event triggering the roll data retrieval (unused).
|
|
||||||
*/
|
for (const inp of this.element.querySelectorAll("[data-roll]")) {
|
||||||
getRollData(_ev) {
|
inp.addEventListener("change", this.#onInputChange.bind(this));
|
||||||
// Calculate and store the roll data
|
}
|
||||||
this.rollData = {
|
|
||||||
actor: this.data.actor,
|
const ability = this.element.querySelector("#ability");
|
||||||
NoD: this.getDicePool(),
|
if (ability) {
|
||||||
Reroll: this.getReroll(),
|
ability.addEventListener("change", this.#onChangeAbility.bind(this));
|
||||||
difficulty: this.getDifficulty(),
|
const selfControl = this.element.querySelector("#self_control");
|
||||||
handicap: this.getHandicap(),
|
if (selfControl) selfControl.max = ability.value;
|
||||||
rollType: this.getRollType(),
|
}
|
||||||
rollLabel: this.getLabel(),
|
|
||||||
totems: this.getTotems(),
|
const selfControl = this.element.querySelector("#self_control");
|
||||||
self_control: this.getSelfControl(),
|
if (selfControl) {
|
||||||
max_effort: this.getMaxEffort(),
|
selfControl.addEventListener("change", this.#onChangeSelfControl.bind(this));
|
||||||
keepTotem: this.getKeepTotem(),
|
}
|
||||||
skillCategory: this.getSkillCategory()
|
|
||||||
};
|
this.element.querySelector("#difficulty")?.addEventListener("change", () => this.#updateUI());
|
||||||
this.displaySpecialties();
|
this.element.querySelector("#handicap")?.addEventListener("change", () => this.#updateUI());
|
||||||
this._updateUI();
|
this.element.querySelector("#human-totem")?.addEventListener("change", () => this.#updateUI());
|
||||||
|
this.element.querySelector("#adapted-totem")?.addEventListener("change", () => this.#updateUI());
|
||||||
|
|
||||||
|
this.#displaySpecialties();
|
||||||
|
this.#updateUI();
|
||||||
|
|
||||||
|
if (ability?.value !== "0") {
|
||||||
|
this.element.querySelector("#self_control")?.dispatchEvent(new Event("change"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Getters ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
get #el() { return this.element; }
|
||||||
|
|
||||||
|
#getAbility() { return this.#el.querySelector("#ability"); }
|
||||||
|
#getSkill() { return this.#el.querySelector("#skill"); }
|
||||||
|
#getDifficulty() { return this.#el.querySelector("#difficulty"); }
|
||||||
|
#getHandicap() { return this.#el.querySelector("#handicap"); }
|
||||||
|
#getSelfCtrl() { return this.#el.querySelector("#self_control"); }
|
||||||
|
|
||||||
|
getDicePool() {
|
||||||
|
const abil = this.#getAbility();
|
||||||
|
const abilVal = parseInt(abil?.options[abil?.selectedIndex]?.value, 10) || 0;
|
||||||
|
const skill = this.#getSkill();
|
||||||
|
const skillPool = parseInt(skill?.options[skill?.selectedIndex]?.dataset?.pool, 10) || 0;
|
||||||
|
const sc = parseInt(this.#getSelfCtrl()?.value, 10) || 0;
|
||||||
|
const specChecked = this.#el.querySelector("#usingSpecialization")?.checked;
|
||||||
|
const helped = this.#el.querySelector("#helped")?.checked;
|
||||||
|
const tools = this.#el.querySelector("input[name='usingTools']:checked")?.value !== "0";
|
||||||
|
const bonuses = (specChecked ? 1 : 0) + (helped ? 1 : 0) + (tools ? 1 : 0);
|
||||||
|
return (abilVal + sc + skillPool + bonuses) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDifficultySelect() {
|
||||||
|
const sel = this.#getDifficulty();
|
||||||
|
const idx = sel?.selectedIndex ?? 0;
|
||||||
|
return parseInt(sel?.options[idx]?.value, 10) || 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
getReroll() {
|
||||||
|
const sel = this.#getSkill();
|
||||||
|
const idx = sel?.selectedIndex ?? 0;
|
||||||
|
return parseInt(sel?.options[idx]?.dataset?.reroll, 10) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getHandicapSelect() {
|
||||||
|
const sel = this.#getHandicap();
|
||||||
|
return parseInt(sel?.value, 10) || 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSkillCategory() {
|
||||||
|
const sel = this.#getSkill();
|
||||||
|
const idx = sel?.selectedIndex ?? 0;
|
||||||
|
return sel?.options[idx]?.dataset?.category ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSkillLevel() {
|
||||||
|
const sel = this.#getSkill();
|
||||||
|
const idx = sel?.selectedIndex ?? 0;
|
||||||
|
const val = sel?.options[idx]?.value;
|
||||||
|
return val ? parseInt(val, 10) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasSpecialtySelected() {
|
||||||
|
const checked = this.#el.querySelector("input[name='usingSpecialization']:checked");
|
||||||
|
return checked && checked.value !== "aucune";
|
||||||
|
}
|
||||||
|
|
||||||
|
getRollType() {
|
||||||
|
const sel = this.#getSkill();
|
||||||
|
return sel?.value ? "skill" : "ability";
|
||||||
|
}
|
||||||
|
|
||||||
|
getLabel() {
|
||||||
|
const type = this.getRollType();
|
||||||
|
if (type === "skill") {
|
||||||
|
const sel = this.#getSkill();
|
||||||
|
const idx = sel?.selectedIndex ?? 0;
|
||||||
|
return sel?.options[idx]?.dataset?.label ?? "";
|
||||||
|
}
|
||||||
|
const sel = this.#getAbility();
|
||||||
|
const idx = sel?.selectedIndex ?? 0;
|
||||||
|
return sel?.options[idx]?.dataset?.label ?? "";
|
||||||
|
}
|
||||||
|
|
||||||
|
getSelfControl() {
|
||||||
|
return parseInt(this.#getSelfCtrl()?.value, 10) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMaxEffort() {
|
||||||
|
const sel = this.#getAbility();
|
||||||
|
return parseInt(sel?.value, 10) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTotems() {
|
||||||
|
return {
|
||||||
|
human: this.#el.querySelector("#human-totem")?.checked ?? false,
|
||||||
|
adapted: this.#el.querySelector("#adapted-totem")?.checked ?? false
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
getKeepTotem() {
|
||||||
* Gets the selected skill category
|
return this.#el.querySelector("#keep-totem-select")?.value ?? null;
|
||||||
* @returns {string|null} - The skill category
|
}
|
||||||
*/
|
|
||||||
getSkillCategory() {
|
// ── UI ───────────────────────────────────────────────────────────────
|
||||||
const html = this.element[0];
|
|
||||||
const skillSelect = html.querySelector('#skill');
|
#displaySpecialties() {
|
||||||
if (skillSelect && skillSelect.selectedIndex > 0) {
|
for (const el of this.#el.querySelectorAll("[data-spec-skill]")) {
|
||||||
const selectedOption = skillSelect.options[skillSelect.selectedIndex];
|
el.style.display = "inline";
|
||||||
return selectedOption.dataset.category || null;
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
#calculateBonusCount() {
|
||||||
|
let b = 0;
|
||||||
|
if (this.#el.querySelector("#helped")?.checked) b += 1;
|
||||||
|
b += parseInt(this.#el.querySelector("#group")?.value, 10) || 0;
|
||||||
|
b += parseInt(this.#getSelfCtrl()?.value, 10) || 0;
|
||||||
|
const tools = this.#el.querySelector("input[name='usingTools']:checked");
|
||||||
|
if (tools && tools.value !== "0") b += 1;
|
||||||
|
const human = this.#el.querySelector("#human-totem");
|
||||||
|
if (human?.checked) b += parseInt(this.#actor?.system?.adaptation?.totems?.human?.value, 10) || 0;
|
||||||
|
const adapted = this.#el.querySelector("#adapted-totem");
|
||||||
|
if (adapted?.checked) b += parseInt(this.#actor?.system?.adaptation?.totems?.adapted?.value, 10) || 0;
|
||||||
|
if (this.hasSpecialtySelected()) b += 1;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
#updateUI() {
|
||||||
|
const total = this.getDicePool();
|
||||||
|
const totalEl = this.#el.querySelector("#dice-pool-total");
|
||||||
|
if (totalEl) totalEl.textContent = `${total}D`;
|
||||||
|
|
||||||
|
const bonusEl = this.#el.querySelector("#total-bonus");
|
||||||
|
if (bonusEl) bonusEl.textContent = this.#calculateBonusCount();
|
||||||
|
|
||||||
|
const diffSel = this.#getDifficulty();
|
||||||
|
const diffEl = this.#el.querySelector("#current-difficulty");
|
||||||
|
if (diffEl && diffSel) {
|
||||||
|
const idx = diffSel.selectedIndex;
|
||||||
|
const val = diffSel.options[idx].value;
|
||||||
|
const lbl = diffSel.options[idx].text.split(" ")[0];
|
||||||
|
diffEl.textContent = `${lbl} (${val})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const handSel = this.#getHandicap();
|
||||||
* Gets the selected skill level
|
const handEl = this.#el.querySelector("#current-handicap");
|
||||||
* @returns {number|null} - The skill level
|
if (handEl && handSel) {
|
||||||
*/
|
handEl.textContent = handSel.options[handSel.selectedIndex].text;
|
||||||
getSkillLevel() {
|
|
||||||
const html = this.element[0];
|
|
||||||
const skillSelect = html.querySelector('#skill');
|
|
||||||
if (skillSelect && skillSelect.selectedIndex > 0) {
|
|
||||||
const selectedOption = skillSelect.options[skillSelect.selectedIndex];
|
|
||||||
return parseInt(selectedOption.value) || null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const abilSel = this.#getAbility();
|
||||||
* Checks if a specialty is selected
|
const abilValEl = this.#el.querySelector("#abilityScoreValue");
|
||||||
* @returns {boolean} - True if a specialty is selected
|
if (abilSel && abilValEl) {
|
||||||
*/
|
const idx = abilSel.selectedIndex;
|
||||||
hasSpecialtySelected() {
|
abilValEl.textContent = idx > 0 ? abilSel.options[idx].value : "0";
|
||||||
const html = this.element[0];
|
|
||||||
const specialtyRadio = html.querySelector('input[name="usingSpecialization"]:checked');
|
|
||||||
return specialtyRadio && specialtyRadio.value !== 'aucune';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const specChecked = this.#el.querySelector("input[name='usingSpecialization']:checked");
|
||||||
* Handles changes to roll inputs and updates UI.
|
const specEl = this.#el.querySelector(".current-specialty");
|
||||||
* @param {Event} ev - The change event.
|
if (specEl && specChecked) {
|
||||||
*/
|
specEl.textContent = specChecked.value === "aucune"
|
||||||
_onRollInputChange(ev) {
|
? game.i18n.localize("VERMINE.none")
|
||||||
this.getRollData(ev);
|
: specChecked.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Event handlers ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#onInputChange() {
|
||||||
|
this.#updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
#onChangeAbility(ev) {
|
||||||
|
const sel = ev.currentTarget;
|
||||||
|
const score = sel.options[sel.selectedIndex]?.value ?? "0";
|
||||||
|
const scoreEl = this.#el.querySelector("#abilityScore");
|
||||||
|
if (scoreEl) scoreEl.value = score;
|
||||||
|
const sc = this.#getSelfCtrl();
|
||||||
|
if (sc) sc.max = score;
|
||||||
|
this.#updateUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
#onChangeSelfControl(ev) {
|
||||||
|
const valEl = this.#el.querySelector("#self_control_value");
|
||||||
|
if (valEl) valEl.textContent = ev.currentTarget.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onCancel(event, target) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onRoll(event, target) {
|
||||||
|
const selfCtrl = this.getSelfControl();
|
||||||
|
if (selfCtrl > 0) {
|
||||||
|
const current = this.#actor?.system?.attributes?.self_control?.value ?? 0;
|
||||||
|
if (current < selfCtrl) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("VERMINE.error_not_enough_self_control"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const abilityVal = this.#el.querySelector('[name="ability"]')?.value;
|
||||||
* Updates all UI elements based on current roll data
|
if (!abilityVal || abilityVal === "0") {
|
||||||
*/
|
ui.notifications.warn(game.i18n.localize("VERMINE.error_select_ability"));
|
||||||
_updateUI() {
|
return;
|
||||||
if (!this._html) return;
|
|
||||||
|
|
||||||
const html = this._html[0];
|
|
||||||
|
|
||||||
// Update total dice pool display
|
|
||||||
const totalDice = this.getDicePool();
|
|
||||||
const totalEl = html.querySelector('#dice-pool-total');
|
|
||||||
if (totalEl) {
|
|
||||||
totalEl.textContent = `${totalDice}D`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update bonus count
|
|
||||||
const bonusCount = this._calculateBonusCount();
|
|
||||||
const bonusEl = html.querySelector('#total-bonus');
|
|
||||||
if (bonusEl) {
|
|
||||||
bonusEl.textContent = bonusCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update difficulty display
|
|
||||||
const difficultyEl = html.querySelector('#current-difficulty');
|
|
||||||
const difficultySelect = html.querySelector('#difficulty');
|
|
||||||
if (difficultyEl && difficultySelect) {
|
|
||||||
const selectedIndex = difficultySelect.selectedIndex;
|
|
||||||
const diffValue = parseInt(difficultySelect.options[selectedIndex].value);
|
|
||||||
const diffLabel = difficultySelect.options[selectedIndex].text.split(' ')[0];
|
|
||||||
difficultyEl.textContent = `${diffLabel} (${diffValue})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update handicap display
|
|
||||||
const handicapEl = html.querySelector('#current-handicap');
|
|
||||||
const handicapSelect = html.querySelector('#handicap');
|
|
||||||
if (handicapEl && handicapSelect) {
|
|
||||||
const selectedIndex = handicapSelect.selectedIndex;
|
|
||||||
handicapEl.textContent = handicapSelect.options[selectedIndex].text;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update ability score display
|
|
||||||
const abilSelect = html.querySelector('#ability');
|
|
||||||
const abilScoreEl = html.querySelector('#abilityScoreValue');
|
|
||||||
if (abilSelect && abilScoreEl) {
|
|
||||||
const selectedIndex = abilSelect.selectedIndex;
|
|
||||||
if (selectedIndex > 0) {
|
|
||||||
abilScoreEl.textContent = abilSelect.options[selectedIndex].value;
|
|
||||||
} else {
|
|
||||||
abilScoreEl.textContent = '0';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update specialty display
|
|
||||||
const specialtyRadios = html.querySelectorAll('input[name="usingSpecialization"]:checked');
|
|
||||||
const currentSpecEl = html.querySelector('.current-specialty');
|
|
||||||
if (currentSpecEl && specialtyRadios.length > 0) {
|
|
||||||
const checkedRadio = specialtyRadios[0];
|
|
||||||
currentSpecEl.textContent = checkedRadio.value === 'aucune' ? game.i18n.localize('VERMINE.none') : checkedRadio.value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (selfCtrl > 0) {
|
||||||
* Calculates the bonus count for display.
|
const newVal = this.#actor.system.attributes.self_control.value - selfCtrl;
|
||||||
* @returns {number} Total bonus dice.
|
await this.#actor.update({ "system.attributes.self_control.value": newVal });
|
||||||
*/
|
|
||||||
_calculateBonusCount() {
|
|
||||||
let bonus = 0;
|
|
||||||
|
|
||||||
// Help bonus
|
|
||||||
if (this._html?.find('#helped')[0]?.checked) {
|
|
||||||
bonus += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Group bonus
|
|
||||||
const groupValue = parseInt(this._html?.find('#group')[0]?.value, 10) || 0;
|
|
||||||
bonus += groupValue;
|
|
||||||
|
|
||||||
// Self control bonus
|
|
||||||
const selfControlValue = parseInt(this._html?.find('#self_control')[0]?.value, 10) || 0;
|
|
||||||
bonus += selfControlValue;
|
|
||||||
|
|
||||||
// Tools bonus
|
|
||||||
const toolsChecked = this._html?.find('input[name="usingTools"]:checked')[0]?.value !== '0';
|
|
||||||
if (toolsChecked) {
|
|
||||||
bonus += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Totems bonus
|
|
||||||
if (this._html?.find('#human-totem')[0]?.checked) {
|
|
||||||
bonus += parseInt(this.data.actor?.system?.adaptation?.totems?.human?.value, 10) || 0;
|
|
||||||
}
|
|
||||||
if (this._html?.find('#adapted-totem')[0]?.checked) {
|
|
||||||
bonus += parseInt(this.data.actor?.system?.adaptation?.totems?.adapted?.value, 10) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Specialty bonus
|
|
||||||
const specialtyChecked = this._html?.find('input[name="usingSpecialization"]:checked')[0]?.value !== 'aucune';
|
|
||||||
if (specialtyChecked) {
|
|
||||||
bonus += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bonus;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
await VermineUtils.roll({
|
||||||
* Handles difficulty change
|
actor: this.#actor,
|
||||||
* @param {Event} ev - The change event
|
NoD: this.getDicePool(),
|
||||||
*/
|
Reroll: this.getReroll(),
|
||||||
_onDifficultyChange(ev) {
|
difficulty: this.getDifficultySelect(),
|
||||||
this._updateUI();
|
handicap: this.getHandicapSelect(),
|
||||||
}
|
rollLabel: this.getLabel(),
|
||||||
|
totems: this.getTotems(),
|
||||||
|
self_control: selfCtrl,
|
||||||
|
max_effort: this.getMaxEffort(),
|
||||||
|
keepTotem: this.getKeepTotem(),
|
||||||
|
skillCategory: this.getSkillCategory(),
|
||||||
|
skillLevel: this.getSkillLevel(),
|
||||||
|
hasSpecialty: this.hasSpecialtySelected()
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
this.close();
|
||||||
* Handles handicap change
|
}
|
||||||
* @param {Event} ev - The change event
|
|
||||||
*/
|
|
||||||
_onHandicapChange(ev) {
|
|
||||||
this._updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles totem checkbox change
|
|
||||||
* @param {Event} ev - The change event
|
|
||||||
*/
|
|
||||||
_onTotemChange(ev) {
|
|
||||||
this._updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the selected totem to keep (for dual totem rolls)
|
|
||||||
* @returns {string|null} - The totem to keep ('human', 'adapted', or null)
|
|
||||||
*/
|
|
||||||
getKeepTotem() {
|
|
||||||
const keepTotemSelect = this._html?.find('#keep-totem-select')[0];
|
|
||||||
if (keepTotemSelect) {
|
|
||||||
return keepTotemSelect.value;
|
|
||||||
}
|
|
||||||
// Default to null (both totems used)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the change in self control value.
|
|
||||||
* @param {Event} ev - The event triggering the change in self control value.
|
|
||||||
*/
|
|
||||||
_onChangeSelfControl(ev) {
|
|
||||||
const html = this.element[0];
|
|
||||||
const selfControlValueElement = html.querySelector('#self_control_value');
|
|
||||||
if (selfControlValueElement) {
|
|
||||||
selfControlValueElement.innerText = ev.currentTarget.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Retrieves the handicap value from the HTML element.
|
|
||||||
* @returns {number} The handicap value.
|
|
||||||
*/
|
|
||||||
getHandicap() {
|
|
||||||
const html = this.element[0];
|
|
||||||
const handicapValue = html.querySelector('#handicap')?.value ?? '1';
|
|
||||||
return parseInt(handicapValue, 10);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Gets the roll type (ability or skill).
|
|
||||||
* @returns {string} The roll type: 'skill' or 'ability'.
|
|
||||||
*/
|
|
||||||
getRollType() {
|
|
||||||
const html = this.element[0];
|
|
||||||
return html.querySelector('select#skill')?.value ? "skill" : "ability";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the label for the roll.
|
|
||||||
* @returns {string} The roll label.
|
|
||||||
*/
|
|
||||||
getLabel() {
|
|
||||||
const html = this.element[0];
|
|
||||||
const rollType = this.getRollType();
|
|
||||||
|
|
||||||
if (rollType === "skill") {
|
|
||||||
const skillSelect = html.querySelector('select#skill');
|
|
||||||
const selectedIndex = skillSelect?.selectedIndex ?? 0;
|
|
||||||
return skillSelect?.options[selectedIndex]?.dataset?.label ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const abilitySelect = html.querySelector('select#ability');
|
|
||||||
const selectedIndex = abilitySelect?.selectedIndex ?? 0;
|
|
||||||
return abilitySelect?.options[selectedIndex]?.dataset?.label ?? "";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays specialties related to the selected skill.
|
|
||||||
*/
|
|
||||||
displaySpecialties() {
|
|
||||||
const specialties = this.element[0]?.querySelectorAll('[data-spec-skill]');
|
|
||||||
if (specialties) {
|
|
||||||
specialties.forEach(specEl => {
|
|
||||||
specEl.style.display = "inline";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the self control value from the HTML element.
|
|
||||||
* @returns {number} The self control value.
|
|
||||||
*/
|
|
||||||
getSelfControl() {
|
|
||||||
const html = this.element[0];
|
|
||||||
const selfControlValue = html.querySelector('#self_control')?.value ?? '0';
|
|
||||||
return parseInt(selfControlValue, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the maximum effort value from the HTML element.
|
|
||||||
* @returns {number} The maximum effort value.
|
|
||||||
*/
|
|
||||||
getMaxEffort() {
|
|
||||||
const html = this.element[0];
|
|
||||||
const abilityValue = html.querySelector('#ability')?.value ?? '0';
|
|
||||||
return parseInt(abilityValue, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the selected totems from the HTML element.
|
|
||||||
* @returns {Object} An object containing the selected totems {human: boolean, adapted: boolean}.
|
|
||||||
*/
|
|
||||||
getTotems() {
|
|
||||||
const html = this.element[0];
|
|
||||||
return {
|
|
||||||
human: html.querySelector('#human-totem')?.checked ?? false,
|
|
||||||
adapted: html.querySelector('#adapted-totem')?.checked ?? false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the change in ability value.
|
|
||||||
* @param {Event} ev - The event triggering the change in ability value.
|
|
||||||
*/
|
|
||||||
_onChangeAbility(ev) {
|
|
||||||
const html = this.element[0];
|
|
||||||
const abilitySelect = html.querySelector('#ability');
|
|
||||||
const selectedIndex = abilitySelect?.selectedIndex ?? 0;
|
|
||||||
const score = abilitySelect?.options[selectedIndex]?.value ?? '0';
|
|
||||||
|
|
||||||
const scoreElement = html.querySelector('#abilityScore');
|
|
||||||
if (scoreElement) {
|
|
||||||
scoreElement.value = score;
|
|
||||||
}
|
|
||||||
|
|
||||||
const selfControlElement = html.querySelector('#self_control');
|
|
||||||
if (selfControlElement) {
|
|
||||||
selfControlElement.max = score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the total dice pool based on various factors.
|
|
||||||
* @returns {number} The total dice pool value.
|
|
||||||
*/
|
|
||||||
getDicePool() {
|
|
||||||
// Retrieve the HTML element
|
|
||||||
const html = this.element[0];
|
|
||||||
|
|
||||||
// Safely get ability value
|
|
||||||
const abilitySelect = html.querySelector('#ability');
|
|
||||||
const abilValue = abilitySelect?.options[abilitySelect?.selectedIndex]?.value ?? 0;
|
|
||||||
|
|
||||||
// Safely get skill value and pool
|
|
||||||
const skillSelect = html.querySelector('#skill');
|
|
||||||
const skillOption = skillSelect?.options[skillSelect?.selectedIndex];
|
|
||||||
const skillValue = skillOption?.dataset?.pool ?? 0;
|
|
||||||
|
|
||||||
// Get the self control value
|
|
||||||
const selfControl = html.querySelector('#self_control')?.value ?? 0;
|
|
||||||
|
|
||||||
// Calculate bonuses based on certain conditions
|
|
||||||
const bonuses =
|
|
||||||
(html.querySelector('#usingSpecialization')?.checked ? 1 : 0) +
|
|
||||||
(html.querySelector('#helped')?.checked ? 1 : 0) +
|
|
||||||
(html.querySelector('#usingTools')?.checked ? 1 : 0);
|
|
||||||
|
|
||||||
// Calculate the total dice pool
|
|
||||||
const total = parseInt(abilValue, 10) + parseInt(selfControl, 10) + parseInt(skillValue, 10) + bonuses;
|
|
||||||
return total || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the reroll value based on selected skill.
|
|
||||||
* @returns {number} The reroll value.
|
|
||||||
*/
|
|
||||||
getReroll() {
|
|
||||||
const html = this.element[0];
|
|
||||||
const skillSelect = html.querySelector('#skill');
|
|
||||||
const selectedIndex = skillSelect?.selectedIndex ?? 0;
|
|
||||||
const rerollValue = skillSelect?.options[selectedIndex]?.dataset?.reroll ?? '0';
|
|
||||||
return parseInt(rerollValue, 10) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the difficulty value based on selected option.
|
|
||||||
* @returns {number} The difficulty value.
|
|
||||||
*/
|
|
||||||
getDifficulty() {
|
|
||||||
const html = this.element[0];
|
|
||||||
const difficultySelect = html.querySelector('#difficulty');
|
|
||||||
const selectedIndex = difficultySelect?.selectedIndex ?? 0;
|
|
||||||
const diffValue = difficultySelect?.options[selectedIndex]?.value ?? '0';
|
|
||||||
return parseInt(diffValue, 10) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs a dice roll based on the roll data and handles self control checks.
|
|
||||||
* @returns {Promise<Roll|false>} A promise that resolves with the Roll result or false if cancelled.
|
|
||||||
*/
|
|
||||||
async _onRoll() {
|
|
||||||
// Check if self control is required for the roll
|
|
||||||
if (this.rollData.self_control > 0) {
|
|
||||||
// Check if the actor has enough self control
|
|
||||||
const currentSelfControl = this.rollData.actor?.system?.attributes?.self_control?.value ?? 0;
|
|
||||||
if (currentSelfControl < this.rollData.self_control) {
|
|
||||||
// Display a warning message if self control is insufficient
|
|
||||||
ui.notifications.warn(game.i18n.localize('VERMINE.error_not_enough_self_control'));
|
|
||||||
// Re-render the dialog
|
|
||||||
this.render(true);
|
|
||||||
return false; // Exit the function if self control is insufficient
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const caracName = this.element[0]?.querySelector('[name="ability"]')?.value;
|
|
||||||
if (caracName === "0" || caracName === undefined) {
|
|
||||||
// Display a warning message if no ability selected
|
|
||||||
ui.notifications.warn(game.i18n.localize('VERMINE.error_select_ability'));
|
|
||||||
// Re-render the dialog
|
|
||||||
this.render(true);
|
|
||||||
return false; // Exit the function if no ability
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deduct self control points if necessary
|
|
||||||
if (this.rollData.self_control > 0) {
|
|
||||||
const newSelfControl = this.rollData.actor.system.attributes.self_control.value - this.rollData.self_control;
|
|
||||||
// Update the actor's self control value
|
|
||||||
await this.rollData.actor.update({
|
|
||||||
"system.attributes.self_control.value": newSelfControl
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the dice roll using VermineUtils
|
|
||||||
return VermineUtils.roll({
|
|
||||||
...this.rollData,
|
|
||||||
skillLevel: this.getSkillLevel(),
|
|
||||||
hasSpecialty: this.hasSpecialtySelected()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -291,7 +291,7 @@ export class VermineFight {
|
|||||||
console.log(data);
|
console.log(data);
|
||||||
|
|
||||||
// render template
|
// render template
|
||||||
let html = await renderTemplate(data._template, data);
|
let html = await foundry.applications.handlebars.renderTemplate(data._template, data);
|
||||||
|
|
||||||
let ui = new Dialog({
|
let ui = new Dialog({
|
||||||
title: game.i18n.localize("VERMINE.FightTool"),
|
title: game.i18n.localize("VERMINE.FightTool"),
|
||||||
@@ -415,7 +415,7 @@ export class VermineCombat extends Combat {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VermineCombatTracker extends CombatTracker {
|
export class VermineCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
|
||||||
|
|
||||||
get template() {
|
get template() {
|
||||||
return "systems/vermine2047/templates/combat-tracker.hbs";
|
return "systems/vermine2047/templates/combat-tracker.hbs";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
export const preloadHandlebarsTemplates = async function () {
|
export const preloadHandlebarsTemplates = async function () {
|
||||||
return loadTemplates([
|
return foundry.applications.handlebars.loadTemplates([
|
||||||
|
|
||||||
|
|
||||||
// Actor partials.
|
// Actor partials.
|
||||||
@@ -31,6 +31,7 @@ export const preloadHandlebarsTemplates = async function () {
|
|||||||
|
|
||||||
// npc partials
|
// npc partials
|
||||||
"systems/vermine2047/templates/actor/npc/npc-combat.hbs",
|
"systems/vermine2047/templates/actor/npc/npc-combat.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/parts/npc-skill-category.hbs",
|
||||||
|
|
||||||
// creature partials
|
// creature partials
|
||||||
"systems/vermine2047/templates/actor/creature/creature-combat.hbs",
|
"systems/vermine2047/templates/actor/creature/creature-combat.hbs",
|
||||||
|
|||||||
@@ -38,16 +38,16 @@ export const registerHooks = function () {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on('renderChatMessage', async (message, html, data) => {
|
Hooks.on('renderChatMessageHTML', async (message, html, data) => {
|
||||||
let rerollTitle = html[0].querySelector(".reroll-fromroll h4");
|
let rerollTitle = html.querySelector(".reroll-fromroll h4");
|
||||||
if (rerollTitle) {
|
if (rerollTitle) {
|
||||||
rerollTitle.addEventListener("click", () => { html[0].querySelector(".reroll").classList.toggle('visible') })
|
rerollTitle.addEventListener("click", () => { html.querySelector(".reroll").classList.toggle('visible') })
|
||||||
}
|
}
|
||||||
if (message.author?._id != game.user._id || !game.user.isGM) {
|
if (message.author?._id != game.user._id || !game.user.isGM) {
|
||||||
// désactiver les inputs pour les joueurs non-auteurs du message
|
// désactiver les inputs pour les joueurs non-auteurs du message
|
||||||
html[0].querySelectorAll("input").forEach(inp => inp.disabled = true);
|
html.querySelectorAll("input").forEach(inp => inp.disabled = true);
|
||||||
//cacher le boutton reroll
|
//cacher le boutton reroll
|
||||||
html[0].querySelectorAll("div.reroll-from-effort").forEach(el => el.style.display = "none")
|
html.querySelectorAll("div.reroll-from-effort").forEach(el => el.style.display = "none")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await VermineUtils.chatListenners(html)
|
await VermineUtils.chatListenners(html)
|
||||||
|
|||||||
@@ -468,7 +468,7 @@ export class VermineUtils {
|
|||||||
* @returns {Promise<ChatMessage>} The created chat message
|
* @returns {Promise<ChatMessage>} The created chat message
|
||||||
*/
|
*/
|
||||||
static async diplayChatRoll(roll, param) {
|
static async diplayChatRoll(roll, param) {
|
||||||
const content = await renderTemplate("systems/vermine2047/templates/roll-message.hbs", { roll, param });
|
const content = await foundry.applications.handlebars.renderTemplate("systems/vermine2047/templates/roll-message.hbs", { roll, param });
|
||||||
const chatData = {
|
const chatData = {
|
||||||
user: game.user?._id,
|
user: game.user?._id,
|
||||||
speaker: ChatMessage.getSpeaker(),
|
speaker: ChatMessage.getSpeaker(),
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class CreateActorDialog extends FormApplication {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class VermineTour extends Tour {
|
class VermineTour extends foundry.nue.Tour {
|
||||||
/** @override */
|
/** @override */
|
||||||
async _preStep() {
|
async _preStep() {
|
||||||
var _a2, _b, _c, _d, _e;
|
var _a2, _b, _c, _d, _e;
|
||||||
|
|||||||
@@ -3,19 +3,17 @@ import { registerSettings } from "./system/settings.mjs";
|
|||||||
import { GroupLink } from "./system/group-link.mjs";
|
import { GroupLink } from "./system/group-link.mjs";
|
||||||
|
|
||||||
// Import document classes.
|
// Import document classes.
|
||||||
import { VermineActor } from "./documents/actor.mjs";
|
import * as documents from "./documents/_module.mjs";
|
||||||
|
|
||||||
import { VermineCharacterSheet } from "./sheets/character-sheet.mjs";
|
|
||||||
import { VermineNpcSheet } from "./sheets/npc-sheet.mjs";
|
|
||||||
import { VermineGroupSheet } from "./sheets/npc-group.mjs";
|
|
||||||
import { VermineCreatureSheet } from "./sheets/creature-sheet.mjs";
|
|
||||||
|
|
||||||
import { VermineItem } from "./documents/item.mjs";
|
|
||||||
import { VermineItemSheet } from "./sheets/item-sheet.mjs";
|
|
||||||
|
|
||||||
import { VermineUtils } from "./system/roll.mjs";
|
import { VermineUtils } from "./system/roll.mjs";
|
||||||
import { VermineCombat, VermineCombatant, VermineCombatTracker } from "./system/fight.mjs";
|
import { VermineCombat, VermineCombatant, VermineCombatTracker } from "./system/fight.mjs";
|
||||||
|
|
||||||
|
// Import DataModels
|
||||||
|
import * as models from "./models/_module.mjs"
|
||||||
|
|
||||||
|
// Import ApplicationV2 sheets
|
||||||
|
import * as sheets from "./applications/sheets/_module.mjs"
|
||||||
|
|
||||||
// Import helper/utility classes and constants.
|
// Import helper/utility classes and constants.
|
||||||
import { preloadHandlebarsTemplates, registerHandlebarsHelpers } from "./system/handlebars-manager.mjs";
|
import { preloadHandlebarsTemplates, registerHandlebarsHelpers } from "./system/handlebars-manager.mjs";
|
||||||
import { VERMINE } from "./system/config.mjs";
|
import { VERMINE } from "./system/config.mjs";
|
||||||
@@ -26,51 +24,109 @@ import { VERMINE } from "./system/config.mjs";
|
|||||||
|
|
||||||
Hooks.once('init', async function () {
|
Hooks.once('init', async function () {
|
||||||
|
|
||||||
// Add utility classes to the global game object so that they're more easily
|
// System stylesheet is automatically loaded by Foundry from system.json
|
||||||
// accessible in global contexts.
|
// No need to manually inject it - this was causing MIME type issues
|
||||||
game.vermine2047 = {
|
// If you need to ensure fresh CSS, use cache-busting in the filename or system.json version
|
||||||
VermineActor,
|
|
||||||
VermineItem,
|
|
||||||
VermineUtils,
|
|
||||||
VermineCombat,
|
|
||||||
GroupLink
|
|
||||||
};
|
|
||||||
|
|
||||||
// Register GroupLink hooks for automatic synchronization
|
// Register GroupLink hooks for automatic synchronization
|
||||||
GroupLink.registerHooks();
|
GroupLink.registerHooks();
|
||||||
|
|
||||||
// Define custom Document classes
|
// Register ALL DataModels FIRST - this is crucial for Foundry V2
|
||||||
CONFIG.Actor.documentClass = VermineActor;
|
// Use individual assignments like Celestopol for compatibility
|
||||||
CONFIG.Item.documentClass = VermineItem;
|
CONFIG.Actor.dataModels.character = models.VermineCharacterData;
|
||||||
|
CONFIG.Actor.dataModels.npc = models.VermineNpcData;
|
||||||
|
CONFIG.Actor.dataModels.group = models.VermineGroupData;
|
||||||
|
CONFIG.Actor.dataModels.creature = models.VermineCreatureData;
|
||||||
|
|
||||||
|
CONFIG.Item.dataModels.item = models.VermineItemData;
|
||||||
|
CONFIG.Item.dataModels.weapon = models.VermineWeaponData;
|
||||||
|
CONFIG.Item.dataModels.defense = models.VermineDefenseData;
|
||||||
|
CONFIG.Item.dataModels.vehicle = models.VermineVehicleData;
|
||||||
|
CONFIG.Item.dataModels.ability = models.VermineAbilityData;
|
||||||
|
CONFIG.Item.dataModels.specialty = models.VermineSpecialtyData;
|
||||||
|
CONFIG.Item.dataModels.background = models.VermineBackgroundData;
|
||||||
|
CONFIG.Item.dataModels.trauma = models.VermineTraumaData;
|
||||||
|
CONFIG.Item.dataModels.evolution = models.VermineEvolutionData;
|
||||||
|
CONFIG.Item.dataModels.rumor = models.VermineRumorData;
|
||||||
|
CONFIG.Item.dataModels.target = models.VermineTargetData;
|
||||||
|
CONFIG.Item.dataModels.rite = models.VermineRiteData;
|
||||||
|
|
||||||
|
// Define custom Document classes AFTER ALL DataModels are registered
|
||||||
|
CONFIG.Actor.documentClass = documents.VermineActor;
|
||||||
|
CONFIG.Item.documentClass = documents.VermineItem;
|
||||||
|
|
||||||
|
// Add utility classes to the global game object so that they're more easily
|
||||||
|
// accessible in global contexts.
|
||||||
|
// Note: Do NOT expose Document classes here as it can cause issues with DataModel initialization
|
||||||
|
game.vermine2047 = {
|
||||||
|
VermineUtils,
|
||||||
|
VermineCombat,
|
||||||
|
GroupLink
|
||||||
|
};
|
||||||
|
|
||||||
CONFIG.ui.combat = VermineCombatTracker;
|
CONFIG.ui.combat = VermineCombatTracker;
|
||||||
CONFIG.Combatant.documentClass = VermineCombatant;
|
CONFIG.Combatant.documentClass = VermineCombatant;
|
||||||
CONFIG.Combat.documentClass = VermineCombat;
|
CONFIG.Combat.documentClass = VermineCombat;
|
||||||
|
|
||||||
|
|
||||||
// Register sheet application classes
|
// Register sheet application classes (ApplicationV2)
|
||||||
Actors.unregisterSheet("core", ActorSheet);
|
// Unregister core sheets
|
||||||
Actors.registerSheet('vermine2047', VermineCharacterSheet, {
|
foundry.applications.sheets.ActorSheetV2?.unregisterSheet?.("core", "Actor", {
|
||||||
types: ['character'],
|
types: ["character", "npc", "group", "creature"]
|
||||||
makeDefault: true,
|
})
|
||||||
});
|
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1?.sheets?.ItemSheet)
|
||||||
|
|
||||||
Actors.registerSheet('vermine2047', VermineNpcSheet, {
|
// Actor sheets
|
||||||
types: ['npc'],
|
foundry.documents.collections.Actors.registerSheet("vermine2047", sheets.VermineCharacterSheetV2, {
|
||||||
makeDefault: true,
|
types: ["character"], makeDefault: true, label: "VERMINE.Sheet.character"
|
||||||
});
|
})
|
||||||
|
foundry.documents.collections.Actors.registerSheet("vermine2047", sheets.VermineNpcSheetV2, {
|
||||||
|
types: ["npc"], makeDefault: true, label: "VERMINE.Sheet.npc"
|
||||||
|
})
|
||||||
|
foundry.documents.collections.Actors.registerSheet("vermine2047", sheets.VermineCreatureSheetV2, {
|
||||||
|
types: ["creature"], makeDefault: true, label: "VERMINE.Sheet.creature"
|
||||||
|
})
|
||||||
|
foundry.documents.collections.Actors.registerSheet("vermine2047", sheets.VermineGroupSheetV2, {
|
||||||
|
types: ["group"], makeDefault: true, label: "VERMINE.Sheet.group"
|
||||||
|
})
|
||||||
|
|
||||||
Actors.registerSheet('vermine2047', VermineCreatureSheet, {
|
// Item sheets — un par type
|
||||||
types: ['creature'],
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineItemSheetV2, {
|
||||||
makeDefault: true,
|
types: ["item"], makeDefault: true
|
||||||
});
|
})
|
||||||
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineWeaponSheetV2, {
|
||||||
Actors.registerSheet('vermine2047', VermineGroupSheet, {
|
types: ["weapon"], makeDefault: true
|
||||||
types: ['group'],
|
})
|
||||||
makeDefault: true,
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineDefenseSheetV2, {
|
||||||
});
|
types: ["defense"], makeDefault: true
|
||||||
Items.unregisterSheet("core", ItemSheet);
|
})
|
||||||
Items.registerSheet("vermine2047", VermineItemSheet, { makeDefault: true });
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineVehicleSheetV2, {
|
||||||
|
types: ["vehicle"], makeDefault: true
|
||||||
|
})
|
||||||
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineAbilitySheetV2, {
|
||||||
|
types: ["ability"], makeDefault: true
|
||||||
|
})
|
||||||
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineSpecialtySheetV2, {
|
||||||
|
types: ["specialty"], makeDefault: true
|
||||||
|
})
|
||||||
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineBackgroundSheetV2, {
|
||||||
|
types: ["background"], makeDefault: true
|
||||||
|
})
|
||||||
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineTraumaSheetV2, {
|
||||||
|
types: ["trauma"], makeDefault: true
|
||||||
|
})
|
||||||
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineEvolutionSheetV2, {
|
||||||
|
types: ["evolution"], makeDefault: true
|
||||||
|
})
|
||||||
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineRumorSheetV2, {
|
||||||
|
types: ["rumor"], makeDefault: true
|
||||||
|
})
|
||||||
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineTargetSheetV2, {
|
||||||
|
types: ["target"], makeDefault: true
|
||||||
|
})
|
||||||
|
foundry.documents.collections.Items.registerSheet("vermine2047", sheets.VermineRiteSheetV2, {
|
||||||
|
types: ["rite"], makeDefault: true
|
||||||
|
})
|
||||||
|
|
||||||
registerHandlebarsHelpers(); // Register Handlebars helpers
|
registerHandlebarsHelpers(); // Register Handlebars helpers
|
||||||
registerHooks(); // register Hooks
|
registerHooks(); // register Hooks
|
||||||
@@ -79,13 +135,8 @@ Hooks.once('init', async function () {
|
|||||||
// Add custom constants for configuration.
|
// Add custom constants for configuration.
|
||||||
CONFIG.VERMINE = VERMINE;
|
CONFIG.VERMINE = VERMINE;
|
||||||
|
|
||||||
// Set up model templates - must be done after system templates are loaded
|
// Les DataModels sont déjà enregistrés dans CONFIG.Actor.dataModels et
|
||||||
if (game.system?.template?.Actor && game.system?.template?.Item) {
|
// CONFIG.Item.dataModels. On expose leurs définitions pour compatibilité.
|
||||||
CONFIG.VERMINE.model = {
|
|
||||||
Actor: game.system.template.Actor,
|
|
||||||
Item: game.system.template.Item
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set an initiative formula for the system
|
* Set an initiative formula for the system
|
||||||
@@ -116,8 +167,33 @@ Hooks.once('init', async function () {
|
|||||||
document.querySelector('#ui-left').prepend(el);
|
document.querySelector('#ui-left').prepend(el);
|
||||||
|
|
||||||
|
|
||||||
// Preload Handlebars templates.
|
// Preload templates (ApplicationV2 + legacy)
|
||||||
return preloadHandlebarsTemplates();
|
await preloadHandlebarsTemplates();
|
||||||
|
await foundry.applications.handlebars.loadTemplates([
|
||||||
|
"systems/vermine2047/templates/actor/appv2/character-header.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/character-main.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/character-abilities.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/character-totem.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/character-equipment.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/character-stories.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/character-combat.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/npc-main.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/npc-characteristics.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/npc-skills.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/npc-threat.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/npc-combat.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/npc-notes.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/group-main.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/group-info.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/group-gear.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/group-road.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/group-reserve.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/creature-main.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/creature-info.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/creature-stats.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/creature-combat.hbs",
|
||||||
|
"systems/vermine2047/templates/actor/appv2/creature-effects.hbs"
|
||||||
|
]);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,18 +7,34 @@
|
|||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"launch_Foundry12": "sudo node /home/rwan/foundry/foundry_software/FoundryVTT-12.331/resources/app/main.js",
|
"launch_Foundry12": "sudo node /home/rwan/foundry/foundry_software/FoundryVTT-12.331/resources/app/main.js",
|
||||||
"watch": "gulp watch",
|
"watch": "gulp watch",
|
||||||
"buildStyle": "gulp buildStyles",
|
"build:less": "gulp buildLess",
|
||||||
|
"build:less:dev": "gulp buildLessDev",
|
||||||
|
"build:css": "gulp buildCSS",
|
||||||
|
"build:all-css": "gulp buildAllCSS",
|
||||||
|
"build": "npm run build:less",
|
||||||
"dev": "npm run launch_Foundry12 & xdg-open http://localhost:30000/",
|
"dev": "npm run launch_Foundry12 & xdg-open http://localhost:30000/",
|
||||||
|
"clean:css": "rm -f css/vermine2047.*.css",
|
||||||
|
"rebuild:less": "npm run clean:css && npm run build:less",
|
||||||
|
"rebuild:css": "npm run clean:css && npm run build:all-css",
|
||||||
"pushLDBtoYAML": "node ./tools/pushLDBtoYAML.mjs",
|
"pushLDBtoYAML": "node ./tools/pushLDBtoYAML.mjs",
|
||||||
"pullYAMLtoLDB": "node ./tools/pullYAMLtoLDB.mjs"
|
"pullYAMLtoLDB": "node ./tools/pullYAMLtoLDB.mjs",
|
||||||
|
"lint:less": "lesshint less/**/*.less"
|
||||||
},
|
},
|
||||||
"author": "Rwan",
|
"author": "Rwan",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@foundryvtt/foundryvtt-cli": "^1.0.2",
|
"@foundryvtt/foundryvtt-cli": "^1.0.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.60.1",
|
||||||
|
"@typescript-eslint/parser": "^8.60.1",
|
||||||
"@typhonjs-fvtt/eslint-config-foundry.js": "^0.8.0",
|
"@typhonjs-fvtt/eslint-config-foundry.js": "^0.8.0",
|
||||||
"browser-sync": "^3.0.3",
|
"browser-sync": "^3.0.3",
|
||||||
|
"eslint": "^8.57.1",
|
||||||
|
"eslint-plugin-html": "^8.1.4",
|
||||||
|
"eslint-plugin-import": "^2.32.0",
|
||||||
"gulp": "^5.0.0",
|
"gulp": "^5.0.0",
|
||||||
"gulp-sass": "^5.1.0",
|
"gulp-autoprefixer": "^8.0.0",
|
||||||
"sass": "^1.55.0"
|
"gulp-clean-css": "^4.3.0",
|
||||||
|
"gulp-less": "^5.0.0",
|
||||||
|
"gulp-rename": "^2.0.0",
|
||||||
|
"less": "^4.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 329 KiB |
|
After Width: | Height: | Size: 347 KiB |
@@ -4,11 +4,13 @@
|
|||||||
"description": "The Vermine 2047 system for FoundryVTT!",
|
"description": "The Vermine 2047 system for FoundryVTT!",
|
||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": "11",
|
"minimum": "14",
|
||||||
"verified": "14.0",
|
"verified": "14"
|
||||||
"maximum": "14"
|
|
||||||
},
|
},
|
||||||
"authors": [
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "LeRatierBretonnien/Uberwald"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "François-Xavier Guillois"
|
"name": "François-Xavier Guillois"
|
||||||
},
|
},
|
||||||
@@ -25,8 +27,30 @@
|
|||||||
"module/vermine2047.mjs"
|
"module/vermine2047.mjs"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"css/vermine2047.css"
|
"css/vermine2047.min.css"
|
||||||
],
|
],
|
||||||
|
"documentTypes": {
|
||||||
|
"Actor": {
|
||||||
|
"character": { "htmlFields": ["equipment.description", "identity.biography", "identity.relations"] },
|
||||||
|
"npc": { "htmlFields": ["equipment.description", "identity.notes"] },
|
||||||
|
"group": { "htmlFields": ["equipment.description", "identity.notes"] },
|
||||||
|
"creature": { "htmlFields": ["equipment.description", "identity.notes", "identity.biography"] }
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"item": { "htmlFields": ["description"] },
|
||||||
|
"weapon": { "htmlFields": ["description"] },
|
||||||
|
"defense": { "htmlFields": ["description"] },
|
||||||
|
"vehicle": { "htmlFields": ["description"] },
|
||||||
|
"ability": { "htmlFields": ["description"] },
|
||||||
|
"specialty": {},
|
||||||
|
"background": { "htmlFields": ["description"] },
|
||||||
|
"trauma": { "htmlFields": ["description"] },
|
||||||
|
"evolution": { "htmlFields": ["description"] },
|
||||||
|
"rumor": { "htmlFields": ["description"] },
|
||||||
|
"target": { "htmlFields": ["description"] },
|
||||||
|
"rite": { "htmlFields": ["description"] }
|
||||||
|
}
|
||||||
|
},
|
||||||
"languages": [
|
"languages": [
|
||||||
{
|
{
|
||||||
"lang": "en",
|
"lang": "en",
|
||||||
@@ -144,9 +168,9 @@
|
|||||||
},
|
},
|
||||||
"primaryTokenAttribute": "health",
|
"primaryTokenAttribute": "health",
|
||||||
"secondaryTokenAttribute": "power",
|
"secondaryTokenAttribute": "power",
|
||||||
"url": "https://github.com/rwanoux/vermine2047/tree/main",
|
"url": "https://www.uberwald.me/gitea/uberwald/vermine2047",
|
||||||
"manifest": "https://raw.githubusercontent.com/rwanoux/vermine2047/refs/heads/main/system.json",
|
"manifest": "https://www.uberwald.me/gitea/uberwald/vermine2047/raw/refs/heads/main/system.json",
|
||||||
"download": "https://github.com/rwanoux/vermine2047/archive/refs/heads/main.zip",
|
"download": "https://www.uberwald.me/gitea/uberwald/vermine2047/archive/refs/heads/main.zip",
|
||||||
"license": "LICENSE.txt",
|
"license": "LICENSE.txt",
|
||||||
"changelog": "CHANGELOG.md",
|
"changelog": "CHANGELOG.md",
|
||||||
"flags": {
|
"flags": {
|
||||||
|
|||||||