Compare commits
7 Commits
c3cf8f176d
...
9d96ec5543
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d96ec5543 | |||
| b6a4148829 | |||
| d8c61458ea | |||
| 9453c15d58 | |||
| 76870c27bf | |||
| ef7fe6e2bd | |||
| 4f53d903eb |
+12
-4
@@ -1,20 +1,28 @@
|
||||
{
|
||||
"id": "mgt2-compendium-amiral-denisov",
|
||||
"title": "MgT2e - Compendium Amiral Denisov",
|
||||
"version": "1.2.1",
|
||||
"version": "1.3.0",
|
||||
"compatibility": {
|
||||
"minimum": "13",
|
||||
"verified": "13",
|
||||
"maximum": "14"
|
||||
},
|
||||
"description": "Module de compendium et d'outils Mongoose Traveller 2e pour FoundryVTT écrit par JdR.Ninja.\nInclut les commandes /commerce, /pnj, /rencontre et /mission pour automatiser le commerce, les PNJ rapides, les rencontres et les contrats aléatoires au-dessus du système mgt2e, en s'appuyant sur les compétences natives des fiches.",
|
||||
"description": "Module de compendium et d'outils Mongoose Traveller 2e pour FoundryVTT écrit par JdR.Ninja.\nInclut les commandes /commerce, /pnj, /rencontre et /mission pour automatiser le commerce, les PNJ rapides, les rencontres et les contrats aléatoires. La fenêtre /pnj inclut un onglet 'PNJ Détaillé' pour la génération de PNJ Traveller selon les règles du générateur officiel, en s'appuyant sur les compétences natives des fiches.",
|
||||
"esmodules": [
|
||||
"scripts/commerce.js",
|
||||
"scripts/npc.js"
|
||||
"scripts/npc.js",
|
||||
"scripts/utils/travellerNpcUtils.js",
|
||||
"scripts/data/travellerNpcGenerator.js",
|
||||
"scripts/travellerNpcGenerator.js",
|
||||
"scripts/TravellerNpcDialog.js",
|
||||
"scripts/mgt2eMigration.js",
|
||||
"scripts/npcRollTableSync.js",
|
||||
"scripts/mgt2eSkills.js"
|
||||
],
|
||||
"styles": [
|
||||
"styles/commerce.css",
|
||||
"styles/npc.css"
|
||||
"styles/npc.css",
|
||||
"styles/traveller-npc.css"
|
||||
],
|
||||
"packs": [
|
||||
{
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
MANIFEST-000075
|
||||
MANIFEST-000084
|
||||
|
||||
+3
-15
@@ -1,15 +1,3 @@
|
||||
2026/05/24-16:50:54.659414 7fdf5b7fe6c0 Recovering log #73
|
||||
2026/05/24-16:50:54.670747 7fdf5b7fe6c0 Delete type=3 #71
|
||||
2026/05/24-16:50:54.670783 7fdf5b7fe6c0 Delete type=0 #73
|
||||
2026/05/24-17:30:55.121088 7fdf5affd6c0 Level-0 table #78: started
|
||||
2026/05/24-17:30:55.125303 7fdf5affd6c0 Level-0 table #78: 9864 bytes OK
|
||||
2026/05/24-17:30:55.131248 7fdf5affd6c0 Delete type=0 #76
|
||||
2026/05/24-17:30:55.131416 7fdf5affd6c0 Manual compaction at level-0 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end)
|
||||
2026/05/24-17:30:55.131452 7fdf5affd6c0 Manual compaction at level-1 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at '!items!yoIqL0RQEnzNVJB6' @ 161 : 1
|
||||
2026/05/24-17:30:55.131457 7fdf5affd6c0 Compacting 1@1 + 1@2 files
|
||||
2026/05/24-17:30:55.134712 7fdf5affd6c0 Generated table #79@1: 29 keys, 10438 bytes
|
||||
2026/05/24-17:30:55.134726 7fdf5affd6c0 Compacted 1@1 + 1@2 files => 10438 bytes
|
||||
2026/05/24-17:30:55.141487 7fdf5affd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2026/05/24-17:30:55.141544 7fdf5affd6c0 Delete type=2 #70
|
||||
2026/05/24-17:30:55.141639 7fdf5affd6c0 Delete type=2 #78
|
||||
2026/05/24-17:30:55.187693 7fdf5affd6c0 Manual compaction at level-1 from '!items!yoIqL0RQEnzNVJB6' @ 161 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.414164 7f3e6f7fe6c0 Recovering log #82
|
||||
2026/05/27-23:11:58.424222 7f3e6f7fe6c0 Delete type=3 #80
|
||||
2026/05/27-23:11:58.424274 7f3e6f7fe6c0 Delete type=0 #82
|
||||
|
||||
+8
-8
@@ -1,8 +1,8 @@
|
||||
2026/05/18-20:27:59.998100 7f5a46ffd6c0 Recovering log #68
|
||||
2026/05/18-20:28:00.055719 7f5a46ffd6c0 Delete type=3 #66
|
||||
2026/05/18-20:28:00.055824 7f5a46ffd6c0 Delete type=0 #68
|
||||
2026/05/18-20:29:47.499128 7f5a467fc6c0 Level-0 table #74: started
|
||||
2026/05/18-20:29:47.499199 7f5a467fc6c0 Level-0 table #74: 0 bytes OK
|
||||
2026/05/18-20:29:47.505640 7f5a467fc6c0 Delete type=0 #72
|
||||
2026/05/18-20:29:47.505916 7f5a467fc6c0 Manual compaction at level-0 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end)
|
||||
2026/05/18-20:29:47.505957 7f5a467fc6c0 Manual compaction at level-1 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.639035 7f3e6f7fe6c0 Recovering log #77
|
||||
2026/05/27-14:31:25.648169 7f3e6f7fe6c0 Delete type=3 #75
|
||||
2026/05/27-14:31:25.648196 7f3e6f7fe6c0 Delete type=0 #77
|
||||
2026/05/27-14:33:21.786747 7f3e6effd6c0 Level-0 table #83: started
|
||||
2026/05/27-14:33:21.786766 7f3e6effd6c0 Level-0 table #83: 0 bytes OK
|
||||
2026/05/27-14:33:21.793932 7f3e6effd6c0 Delete type=0 #81
|
||||
2026/05/27-14:33:21.800554 7f3e6effd6c0 Manual compaction at level-0 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:33:21.800579 7f3e6effd6c0 Manual compaction at level-1 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000174
|
||||
MANIFEST-000183
|
||||
|
||||
+3
-15
@@ -1,15 +1,3 @@
|
||||
2026/05/24-16:50:54.546518 7fdf5b7fe6c0 Recovering log #172
|
||||
2026/05/24-16:50:54.556572 7fdf5b7fe6c0 Delete type=3 #170
|
||||
2026/05/24-16:50:54.556614 7fdf5b7fe6c0 Delete type=0 #172
|
||||
2026/05/24-17:30:54.977322 7fdf5affd6c0 Level-0 table #177: started
|
||||
2026/05/24-17:30:54.980526 7fdf5affd6c0 Level-0 table #177: 3210 bytes OK
|
||||
2026/05/24-17:30:54.986935 7fdf5affd6c0 Delete type=0 #175
|
||||
2026/05/24-17:30:55.016293 7fdf5affd6c0 Manual compaction at level-0 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end)
|
||||
2026/05/24-17:30:55.016335 7fdf5affd6c0 Manual compaction at level-1 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at '!items!wpBopoosZiWXjlKD' @ 78 : 1
|
||||
2026/05/24-17:30:55.016340 7fdf5affd6c0 Compacting 1@1 + 1@2 files
|
||||
2026/05/24-17:30:55.019483 7fdf5affd6c0 Generated table #178@1: 6 keys, 3210 bytes
|
||||
2026/05/24-17:30:55.019496 7fdf5affd6c0 Compacted 1@1 + 1@2 files => 3210 bytes
|
||||
2026/05/24-17:30:55.026070 7fdf5affd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2026/05/24-17:30:55.026126 7fdf5affd6c0 Delete type=2 #169
|
||||
2026/05/24-17:30:55.026250 7fdf5affd6c0 Delete type=2 #177
|
||||
2026/05/24-17:30:55.052180 7fdf5affd6c0 Manual compaction at level-1 from '!items!wpBopoosZiWXjlKD' @ 78 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.290859 7f3e6f7fe6c0 Recovering log #181
|
||||
2026/05/27-23:11:58.307573 7f3e6f7fe6c0 Delete type=3 #179
|
||||
2026/05/27-23:11:58.307626 7f3e6f7fe6c0 Delete type=0 #181
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
2026/05/18-20:27:59.309876 7f5a477fe6c0 Recovering log #167
|
||||
2026/05/18-20:27:59.371762 7f5a477fe6c0 Delete type=3 #165
|
||||
2026/05/18-20:27:59.371874 7f5a477fe6c0 Delete type=0 #167
|
||||
2026/05/18-20:29:47.416279 7f5a467fc6c0 Level-0 table #173: started
|
||||
2026/05/18-20:29:47.416343 7f5a467fc6c0 Level-0 table #173: 0 bytes OK
|
||||
2026/05/18-20:29:47.425598 7f5a467fc6c0 Delete type=0 #171
|
||||
2026/05/18-20:29:47.425873 7f5a467fc6c0 Manual compaction at level-0 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end)
|
||||
2026/05/18-20:29:47.425920 7f5a467fc6c0 Manual compaction at level-1 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.531736 7f3ebd7ff6c0 Recovering log #176
|
||||
2026/05/27-14:31:25.541455 7f3ebd7ff6c0 Delete type=3 #174
|
||||
2026/05/27-14:31:25.541507 7f3ebd7ff6c0 Delete type=0 #176
|
||||
2026/05/27-14:33:21.722465 7f3e6effd6c0 Level-0 table #182: started
|
||||
2026/05/27-14:33:21.722495 7f3e6effd6c0 Level-0 table #182: 0 bytes OK
|
||||
2026/05/27-14:33:21.728923 7f3e6effd6c0 Delete type=0 #180
|
||||
2026/05/27-14:33:21.735721 7f3e6effd6c0 Manual compaction at level-0 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:33:21.735741 7f3e6effd6c0 Manual compaction at level-1 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000058
|
||||
MANIFEST-000067
|
||||
|
||||
+3
-15
@@ -1,15 +1,3 @@
|
||||
2026/05/24-16:50:54.686699 7fdf5bfff6c0 Recovering log #56
|
||||
2026/05/24-16:50:54.695982 7fdf5bfff6c0 Delete type=3 #54
|
||||
2026/05/24-16:50:54.696016 7fdf5bfff6c0 Delete type=0 #56
|
||||
2026/05/24-17:30:55.150957 7fdf5affd6c0 Level-0 table #61: started
|
||||
2026/05/24-17:30:55.154345 7fdf5affd6c0 Level-0 table #61: 4273 bytes OK
|
||||
2026/05/24-17:30:55.160772 7fdf5affd6c0 Delete type=0 #59
|
||||
2026/05/24-17:30:55.187706 7fdf5affd6c0 Manual compaction at level-0 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end)
|
||||
2026/05/24-17:30:55.210353 7fdf5affd6c0 Manual compaction at level-1 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at '!items!vJInnoigCTJzuY2S' @ 126 : 1
|
||||
2026/05/24-17:30:55.210359 7fdf5affd6c0 Compacting 1@1 + 1@2 files
|
||||
2026/05/24-17:30:55.213753 7fdf5affd6c0 Generated table #62@1: 16 keys, 4273 bytes
|
||||
2026/05/24-17:30:55.213776 7fdf5affd6c0 Compacted 1@1 + 1@2 files => 4273 bytes
|
||||
2026/05/24-17:30:55.220661 7fdf5affd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2026/05/24-17:30:55.220738 7fdf5affd6c0 Delete type=2 #53
|
||||
2026/05/24-17:30:55.220857 7fdf5affd6c0 Delete type=2 #61
|
||||
2026/05/24-17:30:55.237555 7fdf5affd6c0 Manual compaction at level-1 from '!items!vJInnoigCTJzuY2S' @ 126 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.442577 7f3e6f7fe6c0 Recovering log #65
|
||||
2026/05/27-23:11:58.452097 7f3e6f7fe6c0 Delete type=3 #63
|
||||
2026/05/27-23:11:58.452144 7f3e6f7fe6c0 Delete type=0 #65
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
2026/05/18-20:28:00.170623 7f5a477fe6c0 Recovering log #51
|
||||
2026/05/18-20:28:00.231017 7f5a477fe6c0 Delete type=3 #49
|
||||
2026/05/18-20:28:00.231120 7f5a477fe6c0 Delete type=0 #51
|
||||
2026/05/18-20:29:47.515216 7f5a467fc6c0 Level-0 table #57: started
|
||||
2026/05/18-20:29:47.515280 7f5a467fc6c0 Level-0 table #57: 0 bytes OK
|
||||
2026/05/18-20:29:47.522604 7f5a467fc6c0 Delete type=0 #55
|
||||
2026/05/18-20:29:47.522854 7f5a467fc6c0 Manual compaction at level-0 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end)
|
||||
2026/05/18-20:29:47.522882 7f5a467fc6c0 Manual compaction at level-1 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.663213 7f3e6f7fe6c0 Recovering log #60
|
||||
2026/05/27-14:31:25.672282 7f3e6f7fe6c0 Delete type=3 #58
|
||||
2026/05/27-14:31:25.672315 7f3e6f7fe6c0 Delete type=0 #60
|
||||
2026/05/27-14:33:21.794049 7f3e6effd6c0 Level-0 table #66: started
|
||||
2026/05/27-14:33:21.794078 7f3e6effd6c0 Level-0 table #66: 0 bytes OK
|
||||
2026/05/27-14:33:21.800426 7f3e6effd6c0 Delete type=0 #64
|
||||
2026/05/27-14:33:21.808266 7f3e6effd6c0 Manual compaction at level-0 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:33:21.831733 7f3e6effd6c0 Manual compaction at level-1 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000177
|
||||
MANIFEST-000186
|
||||
|
||||
+3
-15
@@ -1,15 +1,3 @@
|
||||
2026/05/24-16:50:54.559278 7fdf5bfff6c0 Recovering log #175
|
||||
2026/05/24-16:50:54.569235 7fdf5bfff6c0 Delete type=3 #173
|
||||
2026/05/24-16:50:54.569273 7fdf5bfff6c0 Delete type=0 #175
|
||||
2026/05/24-17:30:54.996524 7fdf5affd6c0 Level-0 table #180: started
|
||||
2026/05/24-17:30:55.000122 7fdf5affd6c0 Level-0 table #180: 30255 bytes OK
|
||||
2026/05/24-17:30:55.006363 7fdf5affd6c0 Delete type=0 #178
|
||||
2026/05/24-17:30:55.016315 7fdf5affd6c0 Manual compaction at level-0 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end)
|
||||
2026/05/24-17:30:55.041863 7fdf5affd6c0 Manual compaction at level-1 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at '!items!yqjKyTCgpclCuHyK' @ 544 : 1
|
||||
2026/05/24-17:30:55.041874 7fdf5affd6c0 Compacting 1@1 + 1@2 files
|
||||
2026/05/24-17:30:55.045796 7fdf5affd6c0 Generated table #181@1: 39 keys, 30255 bytes
|
||||
2026/05/24-17:30:55.045823 7fdf5affd6c0 Compacted 1@1 + 1@2 files => 30255 bytes
|
||||
2026/05/24-17:30:55.051904 7fdf5affd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2026/05/24-17:30:55.051997 7fdf5affd6c0 Delete type=2 #172
|
||||
2026/05/24-17:30:55.052110 7fdf5affd6c0 Delete type=2 #180
|
||||
2026/05/24-17:30:55.062783 7fdf5affd6c0 Manual compaction at level-1 from '!items!yqjKyTCgpclCuHyK' @ 544 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.309946 7f3ebcffe6c0 Recovering log #184
|
||||
2026/05/27-23:11:58.320486 7f3ebcffe6c0 Delete type=3 #182
|
||||
2026/05/27-23:11:58.320525 7f3ebcffe6c0 Delete type=0 #184
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
2026/05/18-20:27:59.380990 7f5a47fff6c0 Recovering log #170
|
||||
2026/05/18-20:27:59.439837 7f5a47fff6c0 Delete type=3 #168
|
||||
2026/05/18-20:27:59.439975 7f5a47fff6c0 Delete type=0 #170
|
||||
2026/05/18-20:29:47.426912 7f5a467fc6c0 Level-0 table #176: started
|
||||
2026/05/18-20:29:47.426974 7f5a467fc6c0 Level-0 table #176: 0 bytes OK
|
||||
2026/05/18-20:29:47.437378 7f5a467fc6c0 Delete type=0 #174
|
||||
2026/05/18-20:29:47.437633 7f5a467fc6c0 Manual compaction at level-0 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end)
|
||||
2026/05/18-20:29:47.437658 7f5a467fc6c0 Manual compaction at level-1 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.544106 7f3ebd7ff6c0 Recovering log #179
|
||||
2026/05/27-14:31:25.554048 7f3ebd7ff6c0 Delete type=3 #177
|
||||
2026/05/27-14:31:25.554071 7f3ebd7ff6c0 Delete type=0 #179
|
||||
2026/05/27-14:33:21.729016 7f3e6effd6c0 Level-0 table #185: started
|
||||
2026/05/27-14:33:21.729040 7f3e6effd6c0 Level-0 table #185: 0 bytes OK
|
||||
2026/05/27-14:33:21.735577 7f3e6effd6c0 Delete type=0 #183
|
||||
2026/05/27-14:33:21.735798 7f3e6effd6c0 Manual compaction at level-0 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:33:21.735816 7f3e6effd6c0 Manual compaction at level-1 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000112
|
||||
MANIFEST-000120
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
2026/05/24-16:50:54.623242 7fdf5bfff6c0 Recovering log #110
|
||||
2026/05/24-16:50:54.633193 7fdf5bfff6c0 Delete type=3 #108
|
||||
2026/05/24-16:50:54.633252 7fdf5bfff6c0 Delete type=0 #110
|
||||
2026/05/24-17:30:55.081463 7fdf5affd6c0 Level-0 table #115: started
|
||||
2026/05/24-17:30:55.081487 7fdf5affd6c0 Level-0 table #115: 0 bytes OK
|
||||
2026/05/24-17:30:55.087560 7fdf5affd6c0 Delete type=0 #113
|
||||
2026/05/24-17:30:55.097957 7fdf5affd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.375862 7f3e6ffff6c0 Recovering log #118
|
||||
2026/05/27-23:11:58.385912 7f3e6ffff6c0 Delete type=3 #116
|
||||
2026/05/27-23:11:58.385947 7f3e6ffff6c0 Delete type=0 #118
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/05/18-20:27:59.766699 7f5a47fff6c0 Recovering log #106
|
||||
2026/05/18-20:27:59.824574 7f5a47fff6c0 Delete type=3 #104
|
||||
2026/05/18-20:27:59.824666 7f5a47fff6c0 Delete type=0 #106
|
||||
2026/05/18-20:29:47.472932 7f5a467fc6c0 Level-0 table #111: started
|
||||
2026/05/18-20:29:47.473002 7f5a467fc6c0 Level-0 table #111: 0 bytes OK
|
||||
2026/05/18-20:29:47.479740 7f5a467fc6c0 Delete type=0 #109
|
||||
2026/05/18-20:29:47.479998 7f5a467fc6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.604521 7f3ebd7ff6c0 Recovering log #114
|
||||
2026/05/27-14:31:25.613630 7f3ebd7ff6c0 Delete type=3 #112
|
||||
2026/05/27-14:31:25.613651 7f3ebd7ff6c0 Delete type=0 #114
|
||||
2026/05/27-14:33:21.767043 7f3e6effd6c0 Level-0 table #119: started
|
||||
2026/05/27-14:33:21.767067 7f3e6effd6c0 Level-0 table #119: 0 bytes OK
|
||||
2026/05/27-14:33:21.773608 7f3e6effd6c0 Delete type=0 #117
|
||||
2026/05/27-14:33:21.780095 7f3e6effd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000119
|
||||
MANIFEST-000128
|
||||
|
||||
+3
-15
@@ -1,15 +1,3 @@
|
||||
2026/05/24-16:50:54.597446 7fdf5b7fe6c0 Recovering log #117
|
||||
2026/05/24-16:50:54.607335 7fdf5b7fe6c0 Delete type=3 #115
|
||||
2026/05/24-16:50:54.607379 7fdf5b7fe6c0 Delete type=0 #117
|
||||
2026/05/24-17:30:55.052370 7fdf5affd6c0 Level-0 table #122: started
|
||||
2026/05/24-17:30:55.056355 7fdf5affd6c0 Level-0 table #122: 20248 bytes OK
|
||||
2026/05/24-17:30:55.062721 7fdf5affd6c0 Delete type=0 #120
|
||||
2026/05/24-17:30:55.081449 7fdf5affd6c0 Manual compaction at level-0 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end)
|
||||
2026/05/24-17:30:55.087647 7fdf5affd6c0 Manual compaction at level-1 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at '!items!zRJfxioYBRq4iSBR' @ 258 : 1
|
||||
2026/05/24-17:30:55.087653 7fdf5affd6c0 Compacting 1@1 + 1@2 files
|
||||
2026/05/24-17:30:55.091676 7fdf5affd6c0 Generated table #123@1: 42 keys, 19863 bytes
|
||||
2026/05/24-17:30:55.091690 7fdf5affd6c0 Compacted 1@1 + 1@2 files => 19863 bytes
|
||||
2026/05/24-17:30:55.097740 7fdf5affd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2026/05/24-17:30:55.097812 7fdf5affd6c0 Delete type=2 #114
|
||||
2026/05/24-17:30:55.097896 7fdf5affd6c0 Delete type=2 #122
|
||||
2026/05/24-17:30:55.104450 7fdf5affd6c0 Manual compaction at level-1 from '!items!zRJfxioYBRq4iSBR' @ 258 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.349736 7f3e6ffff6c0 Recovering log #126
|
||||
2026/05/27-23:11:58.359183 7f3e6ffff6c0 Delete type=3 #124
|
||||
2026/05/27-23:11:58.359211 7f3e6ffff6c0 Delete type=0 #126
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
2026/05/18-20:27:59.596317 7f5a47fff6c0 Recovering log #112
|
||||
2026/05/18-20:27:59.649784 7f5a47fff6c0 Delete type=3 #110
|
||||
2026/05/18-20:27:59.649914 7f5a47fff6c0 Delete type=0 #112
|
||||
2026/05/18-20:29:47.456954 7f5a467fc6c0 Level-0 table #118: started
|
||||
2026/05/18-20:29:47.457019 7f5a467fc6c0 Level-0 table #118: 0 bytes OK
|
||||
2026/05/18-20:29:47.463638 7f5a467fc6c0 Delete type=0 #116
|
||||
2026/05/18-20:29:47.463868 7f5a467fc6c0 Manual compaction at level-0 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end)
|
||||
2026/05/18-20:29:47.463950 7f5a467fc6c0 Manual compaction at level-1 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.580201 7f3ebd7ff6c0 Recovering log #121
|
||||
2026/05/27-14:31:25.589450 7f3ebd7ff6c0 Delete type=3 #119
|
||||
2026/05/27-14:31:25.589494 7f3ebd7ff6c0 Delete type=0 #121
|
||||
2026/05/27-14:33:21.747566 7f3e6effd6c0 Level-0 table #127: started
|
||||
2026/05/27-14:33:21.747585 7f3e6effd6c0 Level-0 table #127: 0 bytes OK
|
||||
2026/05/27-14:33:21.754474 7f3e6effd6c0 Delete type=0 #125
|
||||
2026/05/27-14:33:21.760899 7f3e6effd6c0 Manual compaction at level-0 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:33:21.760915 7f3e6effd6c0 Manual compaction at level-1 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000112
|
||||
MANIFEST-000120
|
||||
|
||||
+3
-7
@@ -1,7 +1,3 @@
|
||||
2026/05/24-16:50:54.634756 7fdfa8dfe6c0 Recovering log #110
|
||||
2026/05/24-16:50:54.645375 7fdfa8dfe6c0 Delete type=3 #108
|
||||
2026/05/24-16:50:54.645440 7fdfa8dfe6c0 Delete type=0 #110
|
||||
2026/05/24-17:30:55.097965 7fdf5affd6c0 Level-0 table #115: started
|
||||
2026/05/24-17:30:55.097985 7fdf5affd6c0 Level-0 table #115: 0 bytes OK
|
||||
2026/05/24-17:30:55.104346 7fdf5affd6c0 Delete type=0 #113
|
||||
2026/05/24-17:30:55.131378 7fdf5affd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.388239 7f3e6f7fe6c0 Recovering log #118
|
||||
2026/05/27-23:11:58.398684 7f3e6f7fe6c0 Delete type=3 #116
|
||||
2026/05/27-23:11:58.398724 7f3e6f7fe6c0 Delete type=0 #118
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
2026/05/18-20:27:59.838862 7f5a46ffd6c0 Recovering log #106
|
||||
2026/05/18-20:27:59.893943 7f5a46ffd6c0 Delete type=3 #104
|
||||
2026/05/18-20:27:59.894068 7f5a46ffd6c0 Delete type=0 #106
|
||||
2026/05/18-20:29:47.480953 7f5a467fc6c0 Level-0 table #111: started
|
||||
2026/05/18-20:29:47.481012 7f5a467fc6c0 Level-0 table #111: 0 bytes OK
|
||||
2026/05/18-20:29:47.487588 7f5a467fc6c0 Delete type=0 #109
|
||||
2026/05/18-20:29:47.487817 7f5a467fc6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.615623 7f3ebd7ff6c0 Recovering log #114
|
||||
2026/05/27-14:31:25.625348 7f3ebd7ff6c0 Delete type=3 #112
|
||||
2026/05/27-14:31:25.625369 7f3ebd7ff6c0 Delete type=0 #114
|
||||
2026/05/27-14:33:21.761003 7f3e6effd6c0 Level-0 table #119: started
|
||||
2026/05/27-14:33:21.761022 7f3e6effd6c0 Level-0 table #119: 0 bytes OK
|
||||
2026/05/27-14:33:21.766953 7f3e6effd6c0 Delete type=0 #117
|
||||
2026/05/27-14:33:21.780083 7f3e6effd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000093
|
||||
MANIFEST-000101
|
||||
|
||||
+3
-8
@@ -1,8 +1,3 @@
|
||||
2026/05/24-16:50:54.647193 7fdf5b7fe6c0 Recovering log #91
|
||||
2026/05/24-16:50:54.656827 7fdf5b7fe6c0 Delete type=3 #89
|
||||
2026/05/24-16:50:54.656870 7fdf5b7fe6c0 Delete type=0 #91
|
||||
2026/05/24-17:30:55.114778 7fdf5affd6c0 Level-0 table #96: started
|
||||
2026/05/24-17:30:55.114807 7fdf5affd6c0 Level-0 table #96: 0 bytes OK
|
||||
2026/05/24-17:30:55.121001 7fdf5affd6c0 Delete type=0 #94
|
||||
2026/05/24-17:30:55.131407 7fdf5affd6c0 Manual compaction at level-0 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end)
|
||||
2026/05/24-17:30:55.131437 7fdf5affd6c0 Manual compaction at level-1 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.400455 7f3ebd7ff6c0 Recovering log #99
|
||||
2026/05/27-23:11:58.410587 7f3ebd7ff6c0 Delete type=3 #97
|
||||
2026/05/27-23:11:58.410645 7f3ebd7ff6c0 Delete type=0 #99
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
2026/05/18-20:27:59.909714 7f5a477fe6c0 Recovering log #87
|
||||
2026/05/18-20:27:59.963841 7f5a477fe6c0 Delete type=3 #85
|
||||
2026/05/18-20:27:59.963963 7f5a477fe6c0 Delete type=0 #87
|
||||
2026/05/18-20:29:47.488790 7f5a467fc6c0 Level-0 table #92: started
|
||||
2026/05/18-20:29:47.488852 7f5a467fc6c0 Level-0 table #92: 0 bytes OK
|
||||
2026/05/18-20:29:47.496720 7f5a467fc6c0 Delete type=0 #90
|
||||
2026/05/18-20:29:47.497943 7f5a467fc6c0 Manual compaction at level-0 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end)
|
||||
2026/05/18-20:29:47.498034 7f5a467fc6c0 Manual compaction at level-1 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.627132 7f3ebd7ff6c0 Recovering log #95
|
||||
2026/05/27-14:31:25.636967 7f3ebd7ff6c0 Delete type=3 #93
|
||||
2026/05/27-14:31:25.637002 7f3ebd7ff6c0 Delete type=0 #95
|
||||
2026/05/27-14:33:21.773684 7f3e6effd6c0 Level-0 table #100: started
|
||||
2026/05/27-14:33:21.773700 7f3e6effd6c0 Level-0 table #100: 0 bytes OK
|
||||
2026/05/27-14:33:21.779958 7f3e6effd6c0 Delete type=0 #98
|
||||
2026/05/27-14:33:21.786739 7f3e6effd6c0 Manual compaction at level-0 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:33:21.800533 7f3e6effd6c0 Manual compaction at level-1 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000119
|
||||
MANIFEST-000128
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
2026/05/24-16:50:54.573746 7fdf5bfff6c0 Recovering log #117
|
||||
2026/05/24-16:50:54.583876 7fdf5bfff6c0 Delete type=3 #115
|
||||
2026/05/24-16:50:54.583933 7fdf5bfff6c0 Delete type=0 #117
|
||||
2026/05/24-17:30:55.026316 7fdf5affd6c0 Level-0 table #122: started
|
||||
2026/05/24-17:30:55.029368 7fdf5affd6c0 Level-0 table #122: 1988 bytes OK
|
||||
2026/05/24-17:30:55.035351 7fdf5affd6c0 Delete type=0 #120
|
||||
2026/05/24-17:30:55.052192 7fdf5affd6c0 Manual compaction at level-0 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end)
|
||||
2026/05/24-17:30:55.062793 7fdf5affd6c0 Manual compaction at level-1 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at '!items!yleVHgRqGoYLvzxT' @ 56 : 1
|
||||
2026/05/24-17:30:55.062796 7fdf5affd6c0 Compacting 1@1 + 1@2 files
|
||||
2026/05/24-17:30:55.065855 7fdf5affd6c0 Generated table #123@1: 8 keys, 1988 bytes
|
||||
2026/05/24-17:30:55.065865 7fdf5affd6c0 Compacted 1@1 + 1@2 files => 1988 bytes
|
||||
2026/05/24-17:30:55.071780 7fdf5affd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2026/05/24-17:30:55.071820 7fdf5affd6c0 Delete type=2 #114
|
||||
2026/05/24-17:30:55.071877 7fdf5affd6c0 Delete type=2 #122
|
||||
2026/05/24-17:30:55.087630 7fdf5affd6c0 Manual compaction at level-1 from '!items!yleVHgRqGoYLvzxT' @ 56 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.323722 7f3e6f7fe6c0 Recovering log #126
|
||||
2026/05/27-23:11:58.334612 7f3e6f7fe6c0 Delete type=3 #124
|
||||
2026/05/27-23:11:58.334668 7f3e6f7fe6c0 Delete type=0 #126
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
2026/05/18-20:27:59.461881 7f5a46ffd6c0 Recovering log #112
|
||||
2026/05/18-20:27:59.517849 7f5a46ffd6c0 Delete type=3 #110
|
||||
2026/05/18-20:27:59.517983 7f5a46ffd6c0 Delete type=0 #112
|
||||
2026/05/18-20:29:47.438640 7f5a467fc6c0 Level-0 table #118: started
|
||||
2026/05/18-20:29:47.438693 7f5a467fc6c0 Level-0 table #118: 0 bytes OK
|
||||
2026/05/18-20:29:47.445247 7f5a467fc6c0 Delete type=0 #116
|
||||
2026/05/18-20:29:47.445955 7f5a467fc6c0 Manual compaction at level-0 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end)
|
||||
2026/05/18-20:29:47.446012 7f5a467fc6c0 Manual compaction at level-1 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.557570 7f3ebd7ff6c0 Recovering log #121
|
||||
2026/05/27-14:31:25.567130 7f3ebd7ff6c0 Delete type=3 #119
|
||||
2026/05/27-14:31:25.567150 7f3ebd7ff6c0 Delete type=0 #121
|
||||
2026/05/27-14:33:21.735841 7f3e6effd6c0 Level-0 table #127: started
|
||||
2026/05/27-14:33:21.735866 7f3e6effd6c0 Level-0 table #127: 0 bytes OK
|
||||
2026/05/27-14:33:21.741712 7f3e6effd6c0 Delete type=0 #125
|
||||
2026/05/27-14:33:21.760883 7f3e6effd6c0 Manual compaction at level-0 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:33:21.760906 7f3e6effd6c0 Manual compaction at level-1 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+1
-1
@@ -1 +1 @@
|
||||
MANIFEST-000112
|
||||
MANIFEST-000120
|
||||
|
||||
+3
-7
@@ -1,7 +1,3 @@
|
||||
2026/05/24-16:50:54.586100 7fdfa8dfe6c0 Recovering log #110
|
||||
2026/05/24-16:50:54.595631 7fdfa8dfe6c0 Delete type=3 #108
|
||||
2026/05/24-16:50:54.595694 7fdfa8dfe6c0 Delete type=0 #110
|
||||
2026/05/24-17:30:55.035470 7fdf5affd6c0 Level-0 table #115: started
|
||||
2026/05/24-17:30:55.035483 7fdf5affd6c0 Level-0 table #115: 0 bytes OK
|
||||
2026/05/24-17:30:55.041734 7fdf5affd6c0 Delete type=0 #113
|
||||
2026/05/24-17:30:55.052200 7fdf5affd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.337311 7f3ebcffe6c0 Recovering log #118
|
||||
2026/05/27-23:11:58.347349 7f3ebcffe6c0 Delete type=3 #116
|
||||
2026/05/27-23:11:58.347392 7f3ebcffe6c0 Delete type=0 #118
|
||||
|
||||
+7
-7
@@ -1,7 +1,7 @@
|
||||
2026/05/18-20:27:59.526598 7f5a94bff6c0 Recovering log #106
|
||||
2026/05/18-20:27:59.589070 7f5a94bff6c0 Delete type=3 #104
|
||||
2026/05/18-20:27:59.589252 7f5a94bff6c0 Delete type=0 #106
|
||||
2026/05/18-20:29:47.448116 7f5a467fc6c0 Level-0 table #111: started
|
||||
2026/05/18-20:29:47.448187 7f5a467fc6c0 Level-0 table #111: 0 bytes OK
|
||||
2026/05/18-20:29:47.455433 7f5a467fc6c0 Delete type=0 #109
|
||||
2026/05/18-20:29:47.455684 7f5a467fc6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.569405 7f3ebd7ff6c0 Recovering log #114
|
||||
2026/05/27-14:31:25.578632 7f3ebd7ff6c0 Delete type=3 #112
|
||||
2026/05/27-14:31:25.578657 7f3ebd7ff6c0 Delete type=0 #114
|
||||
2026/05/27-14:33:21.741780 7f3e6effd6c0 Level-0 table #119: started
|
||||
2026/05/27-14:33:21.741799 7f3e6effd6c0 Level-0 table #119: 0 bytes OK
|
||||
2026/05/27-14:33:21.747507 7f3e6effd6c0 Delete type=0 #117
|
||||
2026/05/27-14:33:21.760892 7f3e6effd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000119
|
||||
MANIFEST-000128
|
||||
|
||||
+3
-15
@@ -1,15 +1,3 @@
|
||||
2026/05/24-16:50:54.610807 7fdfa95ff6c0 Recovering log #117
|
||||
2026/05/24-16:50:54.620344 7fdfa95ff6c0 Delete type=3 #115
|
||||
2026/05/24-16:50:54.620394 7fdfa95ff6c0 Delete type=0 #117
|
||||
2026/05/24-17:30:55.071908 7fdf5affd6c0 Level-0 table #122: started
|
||||
2026/05/24-17:30:55.075519 7fdf5affd6c0 Level-0 table #122: 5396 bytes OK
|
||||
2026/05/24-17:30:55.081331 7fdf5affd6c0 Delete type=0 #120
|
||||
2026/05/24-17:30:55.097948 7fdf5affd6c0 Manual compaction at level-0 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end)
|
||||
2026/05/24-17:30:55.104470 7fdf5affd6c0 Manual compaction at level-1 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at '!items!yFvuDyV00NdojxGt' @ 93 : 1
|
||||
2026/05/24-17:30:55.104477 7fdf5affd6c0 Compacting 1@1 + 1@2 files
|
||||
2026/05/24-17:30:55.108502 7fdf5affd6c0 Generated table #123@1: 15 keys, 5574 bytes
|
||||
2026/05/24-17:30:55.108520 7fdf5affd6c0 Compacted 1@1 + 1@2 files => 5574 bytes
|
||||
2026/05/24-17:30:55.114469 7fdf5affd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2026/05/24-17:30:55.114575 7fdf5affd6c0 Delete type=2 #114
|
||||
2026/05/24-17:30:55.114702 7fdf5affd6c0 Delete type=2 #122
|
||||
2026/05/24-17:30:55.131396 7fdf5affd6c0 Manual compaction at level-1 from '!items!yFvuDyV00NdojxGt' @ 93 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.362926 7f3ebd7ff6c0 Recovering log #126
|
||||
2026/05/27-23:11:58.372894 7f3ebd7ff6c0 Delete type=3 #124
|
||||
2026/05/27-23:11:58.372934 7f3ebd7ff6c0 Delete type=0 #126
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
2026/05/18-20:27:59.682456 7f5a46ffd6c0 Recovering log #112
|
||||
2026/05/18-20:27:59.750017 7f5a46ffd6c0 Delete type=3 #110
|
||||
2026/05/18-20:27:59.750123 7f5a46ffd6c0 Delete type=0 #112
|
||||
2026/05/18-20:29:47.464929 7f5a467fc6c0 Level-0 table #118: started
|
||||
2026/05/18-20:29:47.465002 7f5a467fc6c0 Level-0 table #118: 0 bytes OK
|
||||
2026/05/18-20:29:47.471758 7f5a467fc6c0 Delete type=0 #116
|
||||
2026/05/18-20:29:47.472004 7f5a467fc6c0 Manual compaction at level-0 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end)
|
||||
2026/05/18-20:29:47.472035 7f5a467fc6c0 Manual compaction at level-1 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.591969 7f3ebd7ff6c0 Recovering log #121
|
||||
2026/05/27-14:31:25.601963 7f3ebd7ff6c0 Delete type=3 #119
|
||||
2026/05/27-14:31:25.601983 7f3ebd7ff6c0 Delete type=0 #121
|
||||
2026/05/27-14:33:21.754559 7f3e6effd6c0 Level-0 table #127: started
|
||||
2026/05/27-14:33:21.754583 7f3e6effd6c0 Level-0 table #127: 0 bytes OK
|
||||
2026/05/27-14:33:21.760810 7f3e6effd6c0 Delete type=0 #125
|
||||
2026/05/27-14:33:21.760994 7f3e6effd6c0 Manual compaction at level-0 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:33:21.773676 7f3e6effd6c0 Manual compaction at level-1 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000021
|
||||
MANIFEST-000048
|
||||
|
||||
+69
-50
@@ -1,50 +1,69 @@
|
||||
2026/05/24-16:50:54.697724 7fdfa8dfe6c0 Recovering log #18
|
||||
2026/05/24-16:50:54.707598 7fdfa8dfe6c0 Delete type=3 #16
|
||||
2026/05/24-16:50:54.707634 7fdfa8dfe6c0 Delete type=0 #18
|
||||
2026/05/24-17:00:32.390913 7fdf5affd6c0 Level-0 table #24: started
|
||||
2026/05/24-17:00:32.409568 7fdf5affd6c0 Level-0 table #24: 1225651 bytes OK
|
||||
2026/05/24-17:00:32.416341 7fdf5affd6c0 Delete type=0 #22
|
||||
2026/05/24-17:03:56.805332 7fdf5affd6c0 Level-0 table #26: started
|
||||
2026/05/24-17:03:56.824851 7fdf5affd6c0 Level-0 table #26: 1099794 bytes OK
|
||||
2026/05/24-17:03:56.831981 7fdf5affd6c0 Delete type=0 #23
|
||||
2026/05/24-17:12:00.516549 7fdf5affd6c0 Level-0 table #28: started
|
||||
2026/05/24-17:12:00.550743 7fdf5affd6c0 Level-0 table #28: 1268131 bytes OK
|
||||
2026/05/24-17:12:00.582730 7fdf5affd6c0 Delete type=0 #25
|
||||
2026/05/24-17:16:09.186372 7fdf5affd6c0 Level-0 table #30: started
|
||||
2026/05/24-17:16:09.219830 7fdf5affd6c0 Level-0 table #30: 1432074 bytes OK
|
||||
2026/05/24-17:16:09.258561 7fdf5affd6c0 Delete type=0 #27
|
||||
2026/05/24-17:18:30.349891 7fdf5affd6c0 Level-0 table #32: started
|
||||
2026/05/24-17:18:30.379065 7fdf5affd6c0 Level-0 table #32: 1608171 bytes OK
|
||||
2026/05/24-17:18:30.388404 7fdf5affd6c0 Delete type=0 #29
|
||||
2026/05/24-17:18:30.388776 7fdf5affd6c0 Compacting 4@0 + 1@1 files
|
||||
2026/05/24-17:18:30.411581 7fdf5affd6c0 Generated table #33@0: 6650 keys, 986248 bytes
|
||||
2026/05/24-17:18:30.411611 7fdf5affd6c0 Compacted 4@0 + 1@1 files => 986248 bytes
|
||||
2026/05/24-17:18:30.420963 7fdf5affd6c0 compacted to: files[ 0 1 1 0 0 0 0 ]
|
||||
2026/05/24-17:18:30.421191 7fdf5affd6c0 Delete type=2 #24
|
||||
2026/05/24-17:18:30.421480 7fdf5affd6c0 Delete type=2 #26
|
||||
2026/05/24-17:18:30.421601 7fdf5affd6c0 Delete type=2 #28
|
||||
2026/05/24-17:18:30.421786 7fdf5affd6c0 Delete type=2 #30
|
||||
2026/05/24-17:18:30.422133 7fdf5affd6c0 Delete type=2 #32
|
||||
2026/05/24-17:21:12.979159 7fdf5affd6c0 Level-0 table #35: started
|
||||
2026/05/24-17:21:13.003011 7fdf5affd6c0 Level-0 table #35: 1764850 bytes OK
|
||||
2026/05/24-17:21:13.009270 7fdf5affd6c0 Delete type=0 #31
|
||||
2026/05/24-17:30:55.160858 7fdf5affd6c0 Level-0 table #37: started
|
||||
2026/05/24-17:30:55.180790 7fdf5affd6c0 Level-0 table #37: 1929143 bytes OK
|
||||
2026/05/24-17:30:55.187168 7fdf5affd6c0 Delete type=0 #34
|
||||
2026/05/24-17:30:55.187713 7fdf5affd6c0 Manual compaction at level-0 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 70890 : 1
|
||||
2026/05/24-17:30:55.187715 7fdf5affd6c0 Compacting 2@0 + 1@1 files
|
||||
2026/05/24-17:30:55.203256 7fdf5affd6c0 Generated table #38@0: 7978 keys, 1186513 bytes
|
||||
2026/05/24-17:30:55.203267 7fdf5affd6c0 Compacted 2@0 + 1@1 files => 1186513 bytes
|
||||
2026/05/24-17:30:55.209485 7fdf5affd6c0 compacted to: files[ 0 1 1 0 0 0 0 ]
|
||||
2026/05/24-17:30:55.209584 7fdf5affd6c0 Delete type=2 #33
|
||||
2026/05/24-17:30:55.209874 7fdf5affd6c0 Delete type=2 #35
|
||||
2026/05/24-17:30:55.210245 7fdf5affd6c0 Delete type=2 #37
|
||||
2026/05/24-17:30:55.237542 7fdf5affd6c0 Manual compaction at level-0 from '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 70890 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at (end)
|
||||
2026/05/24-17:30:55.237576 7fdf5affd6c0 Manual compaction at level-1 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 92964 : 1
|
||||
2026/05/24-17:30:55.237582 7fdf5affd6c0 Compacting 1@1 + 1@2 files
|
||||
2026/05/24-17:30:55.258486 7fdf5affd6c0 Generated table #39@1: 7978 keys, 1186513 bytes
|
||||
2026/05/24-17:30:55.258511 7fdf5affd6c0 Compacted 1@1 + 1@2 files => 1186513 bytes
|
||||
2026/05/24-17:30:55.264533 7fdf5affd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2026/05/24-17:30:55.264684 7fdf5affd6c0 Delete type=2 #20
|
||||
2026/05/24-17:30:55.264949 7fdf5affd6c0 Delete type=2 #38
|
||||
2026/05/24-17:30:55.283063 7fdf5affd6c0 Manual compaction at level-1 from '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 92964 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.454344 7f3ebcffe6c0 Recovering log #44
|
||||
2026/05/27-23:11:58.463285 7f3ebcffe6c0 Delete type=3 #40
|
||||
2026/05/27-23:11:58.463300 7f3ebcffe6c0 Delete type=0 #44
|
||||
2026/05/27-23:37:44.587645 7f3e6effd6c0 Level-0 table #51: started
|
||||
2026/05/27-23:37:44.605872 7f3e6effd6c0 Level-0 table #51: 1343154 bytes OK
|
||||
2026/05/27-23:37:44.612030 7f3e6effd6c0 Delete type=0 #49
|
||||
2026/05/28-00:04:39.168329 7f3e6effd6c0 Level-0 table #53: started
|
||||
2026/05/28-00:04:39.185209 7f3e6effd6c0 Level-0 table #53: 1393674 bytes OK
|
||||
2026/05/28-00:04:39.191603 7f3e6effd6c0 Delete type=0 #50
|
||||
2026/05/28-00:18:55.273857 7f3e6effd6c0 Level-0 table #55: started
|
||||
2026/05/28-00:18:55.294346 7f3e6effd6c0 Level-0 table #55: 1451564 bytes OK
|
||||
2026/05/28-00:18:55.300308 7f3e6effd6c0 Delete type=0 #52
|
||||
2026/05/28-00:20:15.021651 7f3e6effd6c0 Level-0 table #57: started
|
||||
2026/05/28-00:20:15.040525 7f3e6effd6c0 Level-0 table #57: 1496314 bytes OK
|
||||
2026/05/28-00:20:15.046886 7f3e6effd6c0 Delete type=0 #54
|
||||
2026/05/28-00:20:43.720521 7f3e6effd6c0 Level-0 table #59: started
|
||||
2026/05/28-00:20:43.742455 7f3e6effd6c0 Level-0 table #59: 1546947 bytes OK
|
||||
2026/05/28-00:20:43.748835 7f3e6effd6c0 Delete type=0 #56
|
||||
2026/05/28-00:20:43.749387 7f3e6effd6c0 Compacting 4@0 + 1@1 files
|
||||
2026/05/28-00:20:43.771709 7f3e6effd6c0 Generated table #60@0: 10302 keys, 1546947 bytes
|
||||
2026/05/28-00:20:43.771721 7f3e6effd6c0 Compacted 4@0 + 1@1 files => 1546947 bytes
|
||||
2026/05/28-00:20:43.777946 7f3e6effd6c0 compacted to: files[ 0 1 1 0 0 0 0 ]
|
||||
2026/05/28-00:20:43.778171 7f3e6effd6c0 Delete type=2 #51
|
||||
2026/05/28-00:20:43.778444 7f3e6effd6c0 Delete type=2 #53
|
||||
2026/05/28-00:20:43.778777 7f3e6effd6c0 Delete type=2 #55
|
||||
2026/05/28-00:20:43.778977 7f3e6effd6c0 Delete type=2 #57
|
||||
2026/05/28-00:20:43.779160 7f3e6effd6c0 Delete type=2 #59
|
||||
2026/05/28-00:21:51.457842 7f3e6effd6c0 Level-0 table #62: started
|
||||
2026/05/28-00:21:51.479532 7f3e6effd6c0 Level-0 table #62: 1604425 bytes OK
|
||||
2026/05/28-00:21:51.485724 7f3e6effd6c0 Delete type=0 #58
|
||||
2026/05/28-00:30:27.802783 7f3e6effd6c0 Level-0 table #64: started
|
||||
2026/05/28-00:30:27.823947 7f3e6effd6c0 Level-0 table #64: 1651065 bytes OK
|
||||
2026/05/28-00:30:27.829721 7f3e6effd6c0 Delete type=0 #61
|
||||
2026/05/28-00:35:01.919877 7f3e6effd6c0 Level-0 table #66: started
|
||||
2026/05/28-00:35:01.987012 7f3e6effd6c0 Level-0 table #66: 1703060 bytes OK
|
||||
2026/05/28-00:35:02.043369 7f3e6effd6c0 Delete type=0 #63
|
||||
2026/05/28-00:37:26.052331 7f3e6effd6c0 Level-0 table #68: started
|
||||
2026/05/28-00:37:26.076959 7f3e6effd6c0 Level-0 table #68: 1753179 bytes OK
|
||||
2026/05/28-00:37:26.083119 7f3e6effd6c0 Delete type=0 #65
|
||||
2026/05/28-00:37:26.083369 7f3e6effd6c0 Compacting 4@0 + 1@1 files
|
||||
2026/05/28-00:37:26.103213 7f3e6effd6c0 Generated table #69@0: 11630 keys, 1753179 bytes
|
||||
2026/05/28-00:37:26.103227 7f3e6effd6c0 Compacted 4@0 + 1@1 files => 1753179 bytes
|
||||
2026/05/28-00:37:26.109075 7f3e6effd6c0 compacted to: files[ 0 1 1 0 0 0 0 ]
|
||||
2026/05/28-00:37:26.109152 7f3e6effd6c0 Delete type=2 #60
|
||||
2026/05/28-00:37:26.109336 7f3e6effd6c0 Delete type=2 #62
|
||||
2026/05/28-00:37:26.109453 7f3e6effd6c0 Delete type=2 #64
|
||||
2026/05/28-00:37:26.109722 7f3e6effd6c0 Delete type=2 #66
|
||||
2026/05/28-00:37:26.109828 7f3e6effd6c0 Delete type=2 #68
|
||||
2026/05/28-00:48:03.393850 7f3e6effd6c0 Level-0 table #71: started
|
||||
2026/05/28-00:48:03.411976 7f3e6effd6c0 Level-0 table #71: 1806724 bytes OK
|
||||
2026/05/28-00:48:03.419030 7f3e6effd6c0 Delete type=0 #67
|
||||
2026/05/28-00:52:05.605289 7f3e6effd6c0 Level-0 table #73: started
|
||||
2026/05/28-00:52:05.629208 7f3e6effd6c0 Level-0 table #73: 1856591 bytes OK
|
||||
2026/05/28-00:52:05.635297 7f3e6effd6c0 Delete type=0 #70
|
||||
2026/05/28-00:53:23.184161 7f3e6effd6c0 Level-0 table #75: started
|
||||
2026/05/28-00:53:23.202618 7f3e6effd6c0 Level-0 table #75: 1901920 bytes OK
|
||||
2026/05/28-00:53:23.208730 7f3e6effd6c0 Delete type=0 #72
|
||||
2026/05/28-01:02:57.320650 7f3e6effd6c0 Level-0 table #77: started
|
||||
2026/05/28-01:02:57.341951 7f3e6effd6c0 Level-0 table #77: 1962797 bytes OK
|
||||
2026/05/28-01:02:57.347793 7f3e6effd6c0 Delete type=0 #74
|
||||
2026/05/28-01:02:57.348132 7f3e6effd6c0 Compacting 4@0 + 1@1 files
|
||||
2026/05/28-01:02:57.369982 7f3e6effd6c0 Generated table #78@0: 12958 keys, 1962797 bytes
|
||||
2026/05/28-01:02:57.370013 7f3e6effd6c0 Compacted 4@0 + 1@1 files => 1962797 bytes
|
||||
2026/05/28-01:02:57.376098 7f3e6effd6c0 compacted to: files[ 0 1 1 0 0 0 0 ]
|
||||
2026/05/28-01:02:57.376330 7f3e6effd6c0 Delete type=2 #69
|
||||
2026/05/28-01:02:57.376716 7f3e6effd6c0 Delete type=2 #71
|
||||
2026/05/28-01:02:57.377097 7f3e6effd6c0 Delete type=2 #73
|
||||
2026/05/28-01:02:57.377594 7f3e6effd6c0 Delete type=2 #75
|
||||
2026/05/28-01:02:57.377833 7f3e6effd6c0 Delete type=2 #77
|
||||
|
||||
+25
-15
@@ -1,15 +1,25 @@
|
||||
2026/05/18-20:28:00.247967 7f5a94bff6c0 Recovering log #13
|
||||
2026/05/18-20:28:00.322431 7f5a94bff6c0 Delete type=3 #11
|
||||
2026/05/18-20:28:00.322531 7f5a94bff6c0 Delete type=0 #13
|
||||
2026/05/18-20:29:47.524056 7f5a467fc6c0 Level-0 table #19: started
|
||||
2026/05/18-20:29:47.536717 7f5a467fc6c0 Level-0 table #19: 417426 bytes OK
|
||||
2026/05/18-20:29:47.544323 7f5a467fc6c0 Delete type=0 #17
|
||||
2026/05/18-20:29:47.545004 7f5a467fc6c0 Manual compaction at level-0 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at (end)
|
||||
2026/05/18-20:29:47.545055 7f5a467fc6c0 Manual compaction at level-1 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 12474 : 1
|
||||
2026/05/18-20:29:47.545064 7f5a467fc6c0 Compacting 1@1 + 1@2 files
|
||||
2026/05/18-20:29:47.560513 7f5a467fc6c0 Generated table #20@1: 2998 keys, 417426 bytes
|
||||
2026/05/18-20:29:47.560554 7f5a467fc6c0 Compacted 1@1 + 1@2 files => 417426 bytes
|
||||
2026/05/18-20:29:47.567200 7f5a467fc6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2026/05/18-20:29:47.567433 7f5a467fc6c0 Delete type=2 #15
|
||||
2026/05/18-20:29:47.567841 7f5a467fc6c0 Delete type=2 #19
|
||||
2026/05/18-20:29:47.568160 7f5a467fc6c0 Manual compaction at level-1 from '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 12474 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.674162 7f3e6f7fe6c0 Recovering log #36
|
||||
2026/05/27-14:31:25.684284 7f3e6f7fe6c0 Delete type=3 #21
|
||||
2026/05/27-14:31:25.684327 7f3e6f7fe6c0 Delete type=0 #36
|
||||
2026/05/27-14:32:13.276652 7f3e6effd6c0 Level-0 table #43: started
|
||||
2026/05/27-14:32:13.292995 7f3e6effd6c0 Level-0 table #43: 1244894 bytes OK
|
||||
2026/05/27-14:32:13.298790 7f3e6effd6c0 Delete type=0 #41
|
||||
2026/05/27-14:33:21.808275 7f3e6effd6c0 Level-0 table #45: started
|
||||
2026/05/27-14:33:21.824612 7f3e6effd6c0 Level-0 table #45: 1294793 bytes OK
|
||||
2026/05/27-14:33:21.831380 7f3e6effd6c0 Delete type=0 #42
|
||||
2026/05/27-14:33:21.838976 7f3e6effd6c0 Manual compaction at level-0 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 109340 : 1
|
||||
2026/05/27-14:33:21.838980 7f3e6effd6c0 Compacting 1@0 + 1@1 files
|
||||
2026/05/27-14:33:21.852317 7f3e6effd6c0 Generated table #46@0: 8642 keys, 1294793 bytes
|
||||
2026/05/27-14:33:21.852331 7f3e6effd6c0 Compacted 1@0 + 1@1 files => 1294793 bytes
|
||||
2026/05/27-14:33:21.859689 7f3e6effd6c0 compacted to: files[ 0 1 1 0 0 0 0 ]
|
||||
2026/05/27-14:33:21.859935 7f3e6effd6c0 Delete type=2 #43
|
||||
2026/05/27-14:33:21.860058 7f3e6effd6c0 Delete type=2 #45
|
||||
2026/05/27-14:33:21.877111 7f3e6effd6c0 Manual compaction at level-0 from '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 109340 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:33:21.877144 7f3e6effd6c0 Manual compaction at level-1 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 109340 : 1
|
||||
2026/05/27-14:33:21.877150 7f3e6effd6c0 Compacting 1@1 + 1@2 files
|
||||
2026/05/27-14:33:21.895679 7f3e6effd6c0 Generated table #47@1: 8642 keys, 1294793 bytes
|
||||
2026/05/27-14:33:21.895699 7f3e6effd6c0 Compacted 1@1 + 1@2 files => 1294793 bytes
|
||||
2026/05/27-14:33:21.901922 7f3e6effd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2026/05/27-14:33:21.902059 7f3e6effd6c0 Delete type=2 #39
|
||||
2026/05/27-14:33:21.902514 7f3e6effd6c0 Delete type=2 #46
|
||||
2026/05/27-14:33:21.908609 7f3e6effd6c0 Manual compaction at level-1 from '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 109340 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
||||
MANIFEST-000065
|
||||
MANIFEST-000074
|
||||
|
||||
@@ -1,15 +1,3 @@
|
||||
2026/05/24-16:50:54.673462 7fdfa95ff6c0 Recovering log #63
|
||||
2026/05/24-16:50:54.683865 7fdfa95ff6c0 Delete type=3 #61
|
||||
2026/05/24-16:50:54.683923 7fdfa95ff6c0 Delete type=0 #63
|
||||
2026/05/24-17:30:55.141699 7fdf5affd6c0 Level-0 table #68: started
|
||||
2026/05/24-17:30:55.144973 7fdf5affd6c0 Level-0 table #68: 18651 bytes OK
|
||||
2026/05/24-17:30:55.150867 7fdf5affd6c0 Delete type=0 #66
|
||||
2026/05/24-17:30:55.187701 7fdf5affd6c0 Manual compaction at level-0 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end)
|
||||
2026/05/24-17:30:55.227041 7fdf5affd6c0 Manual compaction at level-1 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at '!items!xFUyR7XECD8QJcIw' @ 231 : 1
|
||||
2026/05/24-17:30:55.227053 7fdf5affd6c0 Compacting 1@1 + 1@2 files
|
||||
2026/05/24-17:30:55.230678 7fdf5affd6c0 Generated table #69@1: 33 keys, 18651 bytes
|
||||
2026/05/24-17:30:55.230696 7fdf5affd6c0 Compacted 1@1 + 1@2 files => 18651 bytes
|
||||
2026/05/24-17:30:55.237297 7fdf5affd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
||||
2026/05/24-17:30:55.237359 7fdf5affd6c0 Delete type=2 #60
|
||||
2026/05/24-17:30:55.237471 7fdf5affd6c0 Delete type=2 #68
|
||||
2026/05/24-17:30:55.265203 7fdf5affd6c0 Manual compaction at level-1 from '!items!xFUyR7XECD8QJcIw' @ 231 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-23:11:58.427900 7f3ebd7ff6c0 Recovering log #72
|
||||
2026/05/27-23:11:58.438233 7f3ebd7ff6c0 Delete type=3 #70
|
||||
2026/05/27-23:11:58.438292 7f3ebd7ff6c0 Delete type=0 #72
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
2026/05/18-20:28:00.088714 7f5a94bff6c0 Recovering log #58
|
||||
2026/05/18-20:28:00.147839 7f5a94bff6c0 Delete type=3 #56
|
||||
2026/05/18-20:28:00.147945 7f5a94bff6c0 Delete type=0 #58
|
||||
2026/05/18-20:29:47.507007 7f5a467fc6c0 Level-0 table #64: started
|
||||
2026/05/18-20:29:47.507077 7f5a467fc6c0 Level-0 table #64: 0 bytes OK
|
||||
2026/05/18-20:29:47.513818 7f5a467fc6c0 Delete type=0 #62
|
||||
2026/05/18-20:29:47.514115 7f5a467fc6c0 Manual compaction at level-0 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end)
|
||||
2026/05/18-20:29:47.514158 7f5a467fc6c0 Manual compaction at level-1 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:31:25.650849 7f3e6f7fe6c0 Recovering log #67
|
||||
2026/05/27-14:31:25.660624 7f3e6f7fe6c0 Delete type=3 #65
|
||||
2026/05/27-14:31:25.660648 7f3e6f7fe6c0 Delete type=0 #67
|
||||
2026/05/27-14:33:21.780104 7f3e6effd6c0 Level-0 table #73: started
|
||||
2026/05/27-14:33:21.780129 7f3e6effd6c0 Level-0 table #73: 0 bytes OK
|
||||
2026/05/27-14:33:21.786659 7f3e6effd6c0 Delete type=0 #71
|
||||
2026/05/27-14:33:21.800546 7f3e6effd6c0 Manual compaction at level-0 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end)
|
||||
2026/05/27-14:33:21.808253 7f3e6effd6c0 Manual compaction at level-1 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+187
-2
@@ -1,6 +1,20 @@
|
||||
import { formatCredits } from './tradeHelper.js';
|
||||
import { createNpcActor, generateClientMission, generateEncounter, generateQuickNpc } from './npcHelper.js';
|
||||
import { NPC_RELATIONS } from './data/npcTables.js';
|
||||
import { generateAndCreateTravellerNpc } from './travellerNpcGenerator.js';
|
||||
import { generateRandomName } from './data/travellerNpcGenerator.js';
|
||||
import { localizeSkill } from './mgt2eSkills.js';
|
||||
import {
|
||||
CITIZEN_CATEGORY_LIST,
|
||||
EXPERIENCE_LEVEL_LIST,
|
||||
ROLE_LIST,
|
||||
GENDER_LIST,
|
||||
DEFAULT_OPTIONS,
|
||||
CITIZEN_CATEGORY_LABELS_FR,
|
||||
EXPERIENCE_LEVEL_LABELS_FR,
|
||||
ROLE_LABELS_FR,
|
||||
GENDER_LABELS_FR
|
||||
} from './data/travellerNpcGenerator.js';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
@@ -41,6 +55,19 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
context: options.context ?? 'starport',
|
||||
includeFollowUp: true,
|
||||
},
|
||||
mission: {},
|
||||
traveller: {
|
||||
citizenCategory: DEFAULT_OPTIONS.citizenCategory,
|
||||
experience: DEFAULT_OPTIONS.experience,
|
||||
role: DEFAULT_OPTIONS.role,
|
||||
gender: DEFAULT_OPTIONS.gender,
|
||||
firstName: '',
|
||||
surname: '',
|
||||
useRandomName: true,
|
||||
createActor: DEFAULT_OPTIONS.createActor,
|
||||
actorName: '',
|
||||
openCreatedActor: DEFAULT_OPTIONS.openCreatedActor,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -50,6 +77,25 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
...this._formData,
|
||||
activeTab: this._activeTab,
|
||||
relations: Object.entries(NPC_RELATIONS).map(([key, value]) => ({ key, label: value.label })),
|
||||
citizenCategories: CITIZEN_CATEGORY_LIST.map(c => ({
|
||||
key: c.key,
|
||||
label: CITIZEN_CATEGORY_LABELS_FR[c.key] || c.label,
|
||||
description: c.description
|
||||
})),
|
||||
experienceLevels: EXPERIENCE_LEVEL_LIST.map(e => ({
|
||||
key: e.key,
|
||||
label: EXPERIENCE_LEVEL_LABELS_FR[e.key] || e.label,
|
||||
description: e.description
|
||||
})),
|
||||
roles: ROLE_LIST.map(r => ({
|
||||
key: r.key,
|
||||
label: ROLE_LABELS_FR[r.key] || r.label,
|
||||
description: r.description
|
||||
})),
|
||||
genders: GENDER_LIST.map(g => ({
|
||||
key: g.key,
|
||||
label: GENDER_LABELS_FR[g.key] || g.label
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,6 +130,27 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
this._readForm(html);
|
||||
this._activateTab($(event.currentTarget).data('tab'));
|
||||
});
|
||||
|
||||
// Gestion des événements pour l'onglet PNJ Détaillé (Traveller)
|
||||
html.find('[data-action="generate-traveller-npc"]').on('click', async (event) => {
|
||||
event.preventDefault();
|
||||
this._readForm(html);
|
||||
await this._handleTravellerNpc();
|
||||
});
|
||||
|
||||
html.find('[data-action="randomize-name"]').on('click', (event) => {
|
||||
event.preventDefault();
|
||||
this._randomizeTravellerName(html);
|
||||
});
|
||||
|
||||
html.find('[name="traveller.useRandomName"]').on('change', (event) => {
|
||||
const useRandom = event.target.checked;
|
||||
this._formData.traveller.useRandomName = useRandom;
|
||||
html.find('.traveller-name-fields').toggleClass('hidden', useRandom);
|
||||
});
|
||||
|
||||
// Initialiser l'affichage des champs de nom pour l'onglet Traveller
|
||||
html.find('.traveller-name-fields').toggleClass('hidden', this._formData.traveller.useRandomName);
|
||||
}
|
||||
|
||||
_getForm() {
|
||||
@@ -137,6 +204,18 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
this._formData.npc.openCreatedActor = html.find('[name="npc.openCreatedActor"]').is(':checked');
|
||||
this._formData.encounter.context = html.find('[name="encounter.context"]').val();
|
||||
this._formData.encounter.includeFollowUp = html.find('[name="encounter.includeFollowUp"]').is(':checked');
|
||||
|
||||
// Données pour l'onglet PNJ Détaillé (Traveller)
|
||||
this._formData.traveller.citizenCategory = html.find('[name="traveller.citizenCategory"]').val();
|
||||
this._formData.traveller.experience = html.find('[name="traveller.experience"]').val();
|
||||
this._formData.traveller.role = html.find('[name="traveller.role"]').val();
|
||||
this._formData.traveller.gender = html.find('[name="traveller.gender"]').val();
|
||||
this._formData.traveller.firstName = html.find('[name="traveller.firstName"]').val();
|
||||
this._formData.traveller.surname = html.find('[name="traveller.surname"]').val();
|
||||
this._formData.traveller.useRandomName = html.find('[name="traveller.useRandomName"]').is(':checked');
|
||||
this._formData.traveller.createActor = html.find('[name="traveller.createActor"]').is(':checked');
|
||||
this._formData.traveller.actorName = html.find('[name="traveller.actorName"]').val();
|
||||
this._formData.traveller.openCreatedActor = html.find('[name="traveller.openCreatedActor"]').is(':checked');
|
||||
}
|
||||
|
||||
async _handleNpc() {
|
||||
@@ -162,14 +241,75 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
await this._postToChatResult(result);
|
||||
}
|
||||
|
||||
async _handleTravellerNpc() {
|
||||
const button = $(this.element).find('[data-action="generate-traveller-npc"]');
|
||||
const originalLabel = button.html();
|
||||
|
||||
try {
|
||||
button.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Génération...');
|
||||
|
||||
const generateOptions = {
|
||||
citizenCategory: this._formData.traveller.citizenCategory,
|
||||
experience: this._formData.traveller.experience,
|
||||
role: this._formData.traveller.role,
|
||||
gender: this._formData.traveller.gender,
|
||||
createActor: this._formData.traveller.createActor,
|
||||
actorName: this._formData.traveller.actorName,
|
||||
openCreatedActor: this._formData.traveller.openCreatedActor
|
||||
};
|
||||
|
||||
if (!this._formData.traveller.useRandomName && this._formData.traveller.firstName && this._formData.traveller.surname) {
|
||||
generateOptions.firstName = this._formData.traveller.firstName;
|
||||
generateOptions.surname = this._formData.traveller.surname;
|
||||
}
|
||||
|
||||
const result = await generateAndCreateTravellerNpc(generateOptions);
|
||||
|
||||
if (result.success) {
|
||||
await this._postToChatResult(result);
|
||||
if (result.createdActor) {
|
||||
ui.notifications.info(`Fiche PNJ Traveller créée : ${result.createdActor.name}`);
|
||||
}
|
||||
} else {
|
||||
ui.notifications.error('Erreur lors de la génération du PNJ Traveller');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`${MODULE_ID} | Erreur lors de la génération du PNJ Traveller:`, error);
|
||||
ui.notifications.error(`Erreur: ${error.message}`);
|
||||
} finally {
|
||||
button.prop('disabled', false).html(originalLabel);
|
||||
}
|
||||
}
|
||||
|
||||
_randomizeTravellerName(html) {
|
||||
const name = generateRandomName(this._formData.traveller.gender);
|
||||
html.find('[name="traveller.firstName"]').val(name.firstName);
|
||||
html.find('[name="traveller.surname"]').val(name.surname);
|
||||
this._formData.traveller.firstName = name.firstName;
|
||||
this._formData.traveller.surname = name.surname;
|
||||
this._formData.traveller.useRandomName = false;
|
||||
html.find('[name="traveller.useRandomName"]').prop('checked', false);
|
||||
html.find('.traveller-name-fields').removeClass('hidden');
|
||||
}
|
||||
|
||||
async _postToChatResult(data) {
|
||||
registerHandlebarsHelpers();
|
||||
const html = await foundry.applications.handlebars.renderTemplate(`modules/${MODULE_ID}/templates/npc-result.hbs`, data);
|
||||
|
||||
// Déterminer quel template utiliser en fonction du type de données
|
||||
let template = `modules/${MODULE_ID}/templates/npc-result.hbs`;
|
||||
let resultType = 'npc-result';
|
||||
|
||||
if (data.type === 'traveller-npc' || data?.flags?.[MODULE_ID]?.type === 'traveller-npc-result') {
|
||||
template = `modules/${MODULE_ID}/templates/traveller-npc-result.hbs`;
|
||||
resultType = 'traveller-npc-result';
|
||||
}
|
||||
|
||||
const html = await foundry.applications.handlebars.renderTemplate(template, data);
|
||||
|
||||
await ChatMessage.create({
|
||||
content: html,
|
||||
speaker: ChatMessage.getSpeaker(),
|
||||
flags: { [MODULE_ID]: { type: 'npc-result' } },
|
||||
flags: { [MODULE_ID]: { type: resultType } },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -180,8 +320,53 @@ function registerHandlebarsHelpers() {
|
||||
if (helpersRegistered) return;
|
||||
helpersRegistered = true;
|
||||
|
||||
// Helpers existants pour NPC
|
||||
Handlebars.registerHelper('eq', (a, b) => a === b);
|
||||
Handlebars.registerHelper('join', (arr, sep) => (Array.isArray(arr) ? arr.join(sep) : ''));
|
||||
Handlebars.registerHelper('formatCredits', (amount) => formatCredits(amount));
|
||||
Handlebars.registerHelper('contains', (text, search) => String(text ?? '').includes(search));
|
||||
|
||||
// Helper pour localiser une compétence (ex: 'pilot' -> 'Pilote')
|
||||
Handlebars.registerHelper('localizeSkill', (skillFqn) => {
|
||||
if (!skillFqn) return '';
|
||||
return localizeSkill(String(skillFqn));
|
||||
});
|
||||
|
||||
// Helper pour joindre un tableau de compétences en les localisant
|
||||
Handlebars.registerHelper('joinLocalizedSkills', (arr, sep = ', ') => {
|
||||
if (!Array.isArray(arr)) return '';
|
||||
return arr.map(skill => localizeSkill(String(skill))).join(sep);
|
||||
});
|
||||
|
||||
// Helpers pour Traveller NPC
|
||||
Handlebars.registerHelper('gt', (a, b) => a > b);
|
||||
|
||||
// Helper pour afficher le niveau de compétence avec un symbole
|
||||
Handlebars.registerHelper('skillLevelSymbol', (level) => {
|
||||
if (level === 0) return '';
|
||||
if (level === 1) return '★';
|
||||
if (level === 2) return '★★';
|
||||
if (level === 3) return '★★★';
|
||||
return `+${level}`;
|
||||
});
|
||||
|
||||
// Helper pour formater le DM (Difficulté Modificateur)
|
||||
Handlebars.registerHelper('formatDm', (value) => {
|
||||
const dm = Math.floor((value - 6) / 3);
|
||||
return dm >= 0 ? `+${dm}` : `${dm}`;
|
||||
});
|
||||
|
||||
// Helper pour obtenir la classe CSS du niveau de compétence
|
||||
Handlebars.registerHelper('skillLevelClass', (level) => {
|
||||
if (level === 3) return 'skill-level-3';
|
||||
if (level === 2) return 'skill-level-2';
|
||||
if (level === 1) return 'skill-level-1';
|
||||
return 'skill-level-0';
|
||||
});
|
||||
|
||||
// Helper pour lookup dans un objet
|
||||
Handlebars.registerHelper('lookup', (obj, key) => {
|
||||
if (!obj || !key) return '';
|
||||
return obj[key] !== undefined ? obj[key] : '';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* Traveller NPC Generator - Dialogue de génération
|
||||
*
|
||||
* Ce fichier contient le dialogue pour générer des PNJ Traveller.
|
||||
*/
|
||||
|
||||
import { generateAndCreateTravellerNpc } from './travellerNpcGenerator.js';
|
||||
import {
|
||||
CITIZEN_CATEGORY_LIST,
|
||||
EXPERIENCE_LEVEL_LIST,
|
||||
ROLE_LIST,
|
||||
GENDER_LIST,
|
||||
DEFAULT_OPTIONS,
|
||||
generateRandomName,
|
||||
CITIZEN_CATEGORY_LABELS_FR,
|
||||
EXPERIENCE_LEVEL_LABELS_FR,
|
||||
ROLE_LABELS_FR,
|
||||
GENDER_LABELS_FR
|
||||
} from './data/travellerNpcGenerator.js';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
|
||||
export class TravellerNpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'mgt2-traveller-npc',
|
||||
classes: ['mgt2-npc-dialog', 'mgt2-traveller-npc-dialog'],
|
||||
position: {
|
||||
width: 700,
|
||||
height: 'auto',
|
||||
},
|
||||
window: {
|
||||
title: 'Générateur de PNJ Traveller – MgT2e',
|
||||
resizable: true,
|
||||
},
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
main: {
|
||||
template: `modules/${MODULE_ID}/templates/traveller-npc-dialog.hbs`,
|
||||
root: true,
|
||||
},
|
||||
};
|
||||
|
||||
constructor(options = {}) {
|
||||
super(options);
|
||||
|
||||
// Form data avec valeurs par défaut
|
||||
this._formData = {
|
||||
citizenCategory: options.citizenCategory || DEFAULT_OPTIONS.citizenCategory,
|
||||
experience: options.experience || DEFAULT_OPTIONS.experience,
|
||||
role: options.role || DEFAULT_OPTIONS.role,
|
||||
gender: options.gender || DEFAULT_OPTIONS.gender,
|
||||
firstName: options.firstName || '',
|
||||
surname: options.surname || '',
|
||||
useRandomName: options.useRandomName !== false,
|
||||
createActor: options.createActor !== undefined ? options.createActor : DEFAULT_OPTIONS.createActor,
|
||||
actorName: options.actorName || '',
|
||||
openCreatedActor: options.openCreatedActor !== undefined ? options.openCreatedActor : DEFAULT_OPTIONS.openCreatedActor,
|
||||
};
|
||||
|
||||
// Bind les méthodes pour éviter les problèmes de contexte
|
||||
this._readForm = this._readForm.bind(this);
|
||||
this._handleGenerate = this._handleGenerate.bind(this);
|
||||
this._randomizeName = this._randomizeName.bind(this);
|
||||
this._applyThemeStyles = this._applyThemeStyles.bind(this);
|
||||
this._getForm = this._getForm.bind(this);
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
registerHandlebarsHelpers();
|
||||
return {
|
||||
...this._formData,
|
||||
citizenCategories: CITIZEN_CATEGORY_LIST.map(c => ({
|
||||
key: c.key,
|
||||
label: CITIZEN_CATEGORY_LABELS_FR[c.key] || c.label,
|
||||
description: c.description
|
||||
})),
|
||||
experienceLevels: EXPERIENCE_LEVEL_LIST.map(e => ({
|
||||
key: e.key,
|
||||
label: EXPERIENCE_LEVEL_LABELS_FR[e.key] || e.label,
|
||||
description: e.description
|
||||
})),
|
||||
roles: ROLE_LIST.map(r => ({
|
||||
key: r.key,
|
||||
label: ROLE_LABELS_FR[r.key] || r.label,
|
||||
description: r.description
|
||||
})),
|
||||
genders: GENDER_LIST.map(g => ({
|
||||
key: g.key,
|
||||
label: GENDER_LABELS_FR[g.key] || g.label
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
async _onRender(context, options) {
|
||||
await super._onRender(context, options);
|
||||
const html = this._getForm();
|
||||
if (!html?.length) return;
|
||||
|
||||
html.addClass('mgt2-traveller-npc-form');
|
||||
this._applyThemeStyles(html);
|
||||
|
||||
// Gestion des événements
|
||||
html.find('[data-action="generate-traveller-npc"]').on('click', async (event) => {
|
||||
event.preventDefault();
|
||||
this._readForm(html);
|
||||
await this._handleGenerate();
|
||||
});
|
||||
|
||||
html.find('[data-action="randomize-name"]').on('click', (event) => {
|
||||
event.preventDefault();
|
||||
this._randomizeName(html);
|
||||
});
|
||||
|
||||
// Gestion du basculement entre nom aléatoire et nom personnalisé
|
||||
html.find('[name="useRandomName"]').on('change', (event) => {
|
||||
const useRandom = event.target.checked;
|
||||
this._formData.useRandomName = useRandom;
|
||||
html.find('.name-fields').toggleClass('hidden', useRandom);
|
||||
});
|
||||
|
||||
// Initialiser l'affichage des champs de nom
|
||||
html.find('.name-fields').toggleClass('hidden', this._formData.useRandomName);
|
||||
}
|
||||
|
||||
_getForm() {
|
||||
return $(this.element).find('.window-content');
|
||||
}
|
||||
|
||||
_applyThemeStyles(html) {
|
||||
// Les styles sont maintenant gérés par CSS, cette méthode peut être vide
|
||||
// ou utilisée pour des ajustements spécifiques si nécessaire
|
||||
// Les styles de base sont cohérents avec mgt2-npc-dialog et mgt2-commerce-dialog
|
||||
}
|
||||
|
||||
_readForm(html) {
|
||||
this._formData.citizenCategory = html.find('[name="citizenCategory"]').val();
|
||||
this._formData.experience = html.find('[name="experience"]').val();
|
||||
this._formData.role = html.find('[name="role"]').val();
|
||||
this._formData.gender = html.find('[name="gender"]').val();
|
||||
this._formData.firstName = html.find('[name="firstName"]').val();
|
||||
this._formData.surname = html.find('[name="surname"]').val();
|
||||
this._formData.useRandomName = html.find('[name="useRandomName"]').is(':checked');
|
||||
this._formData.createActor = html.find('[name="createActor"]').is(':checked');
|
||||
this._formData.actorName = html.find('[name="actorName"]').val();
|
||||
this._formData.openCreatedActor = html.find('[name="openCreatedActor"]').is(':checked');
|
||||
}
|
||||
|
||||
_randomizeName(html) {
|
||||
const name = generateRandomName(this._formData.gender);
|
||||
html.find('[name="firstName"]').val(name.firstName);
|
||||
html.find('[name="surname"]').val(name.surname);
|
||||
this._formData.firstName = name.firstName;
|
||||
this._formData.surname = name.surname;
|
||||
this._formData.useRandomName = false;
|
||||
html.find('[name="useRandomName"]').prop('checked', false);
|
||||
html.find('.name-fields').removeClass('hidden');
|
||||
}
|
||||
|
||||
async _handleGenerate() {
|
||||
const button = $(this.element).find('[data-action="generate-traveller-npc"]');
|
||||
const originalLabel = button.html();
|
||||
|
||||
try {
|
||||
// Désactiver le bouton pendant la génération
|
||||
button.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Génération...');
|
||||
|
||||
// Préparer les options de génération
|
||||
const generateOptions = {
|
||||
citizenCategory: this._formData.citizenCategory,
|
||||
experience: this._formData.experience,
|
||||
role: this._formData.role,
|
||||
gender: this._formData.gender,
|
||||
createActor: this._formData.createActor,
|
||||
actorName: this._formData.actorName,
|
||||
openCreatedActor: this._formData.openCreatedActor
|
||||
};
|
||||
|
||||
// Si on n'utilise pas de nom aléatoire, passer le nom personnalisé
|
||||
if (!this._formData.useRandomName && this._formData.firstName && this._formData.surname) {
|
||||
generateOptions.firstName = this._formData.firstName;
|
||||
generateOptions.surname = this._formData.surname;
|
||||
}
|
||||
|
||||
// Générer le PNJ
|
||||
const result = await generateAndCreateTravellerNpc(generateOptions);
|
||||
|
||||
if (result.success) {
|
||||
// Afficher le résultat dans le chat
|
||||
await this._postToChatResult(result);
|
||||
|
||||
if (result.createdActor) {
|
||||
ui.notifications.info(`Fiche PNJ Traveller créée : ${result.createdActor.name}`);
|
||||
}
|
||||
} else {
|
||||
ui.notifications.error('Erreur lors de la génération du PNJ Traveller');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`${MODULE_ID} | Erreur lors de la génération du PNJ Traveller:`, error);
|
||||
ui.notifications.error(`Erreur: ${error.message}`);
|
||||
} finally {
|
||||
// Réactiver le bouton
|
||||
button.prop('disabled', false).html(originalLabel);
|
||||
}
|
||||
}
|
||||
|
||||
async _postToChatResult(data) {
|
||||
registerHandlebarsHelpers();
|
||||
const html = await foundry.applications.handlebars.renderTemplate(
|
||||
`modules/${MODULE_ID}/templates/traveller-npc-result.hbs`,
|
||||
data
|
||||
);
|
||||
|
||||
await ChatMessage.create({
|
||||
content: html,
|
||||
speaker: ChatMessage.getSpeaker(),
|
||||
flags: { [MODULE_ID]: { type: 'traveller-npc-result' } },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper functions
|
||||
// ============================================================================
|
||||
|
||||
// Import des données pour les helpers
|
||||
import { CHARACTERISTIC_LIST, UPP_ORDER } from './data/travellerNpcGenerator.js';
|
||||
|
||||
let helpersRegistered = false;
|
||||
|
||||
function registerHandlebarsHelpers() {
|
||||
if (helpersRegistered) return;
|
||||
helpersRegistered = true;
|
||||
|
||||
// Helper pour comparer deux valeurs
|
||||
Handlebars.registerHelper('eq', (a, b) => a === b);
|
||||
|
||||
// Helper pour rejoindre un tableau
|
||||
Handlebars.registerHelper('join', (arr, sep) => (Array.isArray(arr) ? arr.join(sep) : ''));
|
||||
|
||||
// Helper pour vérifier si une valeur contient du texte
|
||||
Handlebars.registerHelper('contains', (text, search) => String(text ?? '').includes(search));
|
||||
|
||||
// Helper pour vérifier si a > b
|
||||
Handlebars.registerHelper('gt', (a, b) => a > b);
|
||||
|
||||
// Helper pour afficher le niveau de compétence avec un symbole
|
||||
Handlebars.registerHelper('skillLevelSymbol', (level) => {
|
||||
if (level === 0) return '';
|
||||
if (level === 1) return '★';
|
||||
if (level === 2) return '★★';
|
||||
if (level === 3) return '★★★';
|
||||
return `+${level}`;
|
||||
});
|
||||
|
||||
// Helper pour formater le DM
|
||||
Handlebars.registerHelper('formatDm', (value) => {
|
||||
const dm = Math.floor((value - 6) / 3);
|
||||
return dm >= 0 ? `+${dm}` : `${dm}`;
|
||||
});
|
||||
|
||||
// Helper pour obtenir la classe CSS du niveau de compétence
|
||||
Handlebars.registerHelper('skillLevelClass', (level) => {
|
||||
if (level === 3) return 'skill-level-3';
|
||||
if (level === 2) return 'skill-level-2';
|
||||
if (level === 1) return 'skill-level-1';
|
||||
return 'skill-level-0';
|
||||
});
|
||||
|
||||
// Helper pour lookup dans un objet
|
||||
Handlebars.registerHelper('lookup', (obj, key) => {
|
||||
if (!obj || !key) return '';
|
||||
return obj[key] !== undefined ? obj[key] : '';
|
||||
});
|
||||
}
|
||||
|
||||
// Exporter pour pouvoir l'ouvrir depuis d'autres modules
|
||||
export function openTravellerNpcDialog(options = {}) {
|
||||
new TravellerNpcDialog(options).render({ force: true });
|
||||
}
|
||||
@@ -0,0 +1,880 @@
|
||||
/**
|
||||
* Traveller NPC Generator - Données de configuration
|
||||
* Basé sur : https://github.com/carloscasalar/traveller-npc-generator
|
||||
*
|
||||
* Ce fichier contient toutes les données nécessaires pour générer des PNJ
|
||||
* selon les règles du générateur Traveller.
|
||||
*/
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
|
||||
// ============================================================================
|
||||
// Catégories de citoyens
|
||||
// ============================================================================
|
||||
|
||||
export const CITIZEN_CATEGORY = {
|
||||
BELOW_AVERAGE: {
|
||||
key: 'belowAverage',
|
||||
label: 'En dessous de la moyenne',
|
||||
value: 0,
|
||||
characteristicArray: [8, 7, 6, 6, 5, 4],
|
||||
description: 'Citoyen avec des capacités inférieures à la moyenne'
|
||||
},
|
||||
AVERAGE: {
|
||||
key: 'average',
|
||||
label: 'Moyenne',
|
||||
value: 1,
|
||||
characteristicArray: [9, 8, 7, 7, 6, 5],
|
||||
description: 'Citoyen moyen'
|
||||
},
|
||||
ABOVE_AVERAGE: {
|
||||
key: 'aboveAverage',
|
||||
label: 'Au-dessus de la moyenne',
|
||||
value: 2,
|
||||
characteristicArray: [10, 9, 8, 8, 7, 6],
|
||||
description: 'Citoyen avec des capacités supérieures à la moyenne'
|
||||
},
|
||||
EXCEPTIONAL: {
|
||||
key: 'exceptional',
|
||||
label: 'Exceptionnel',
|
||||
value: 3,
|
||||
characteristicArray: [11, 10, 9, 9, 8, 7],
|
||||
description: 'Citoyen exceptionnel'
|
||||
}
|
||||
};
|
||||
|
||||
export const CITIZEN_CATEGORY_LIST = [
|
||||
CITIZEN_CATEGORY.BELOW_AVERAGE,
|
||||
CITIZEN_CATEGORY.AVERAGE,
|
||||
CITIZEN_CATEGORY.ABOVE_AVERAGE,
|
||||
CITIZEN_CATEGORY.EXCEPTIONAL
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// Niveaux d'expérience
|
||||
// ============================================================================
|
||||
|
||||
export const EXPERIENCE_LEVEL = {
|
||||
RECRUIT: {
|
||||
key: 'recruit',
|
||||
label: 'Recrue',
|
||||
value: 0,
|
||||
skillDistribution: {
|
||||
level0: 4,
|
||||
level1: 0,
|
||||
level2: 0,
|
||||
level3: 0
|
||||
},
|
||||
description: 'Nouveau, sans expérience'
|
||||
},
|
||||
ROOKIE: {
|
||||
key: 'rookie',
|
||||
label: 'Débutant',
|
||||
value: 1,
|
||||
skillDistribution: {
|
||||
level0: 4,
|
||||
level1: 2,
|
||||
level2: 0,
|
||||
level3: 0
|
||||
},
|
||||
description: 'Débutant avec un peu d\'expérience'
|
||||
},
|
||||
INTERMEDIATE: {
|
||||
key: 'intermediate',
|
||||
label: 'Intermédiaire',
|
||||
value: 2,
|
||||
skillDistribution: {
|
||||
level0: 4,
|
||||
level1: 2,
|
||||
level2: 1,
|
||||
level3: 0
|
||||
},
|
||||
description: 'Niveau intermédiaire'
|
||||
},
|
||||
REGULAR: {
|
||||
key: 'regular',
|
||||
label: 'Régulier',
|
||||
value: 3,
|
||||
skillDistribution: {
|
||||
level0: 5,
|
||||
level1: 2,
|
||||
level2: 2,
|
||||
level3: 0
|
||||
},
|
||||
description: 'Expérience régulière'
|
||||
},
|
||||
VETERAN: {
|
||||
key: 'veteran',
|
||||
label: 'Vétéran',
|
||||
value: 4,
|
||||
skillDistribution: {
|
||||
level0: 5,
|
||||
level1: 2,
|
||||
level2: 3,
|
||||
level3: 0
|
||||
},
|
||||
description: 'Vétéran expérimenté'
|
||||
},
|
||||
ELITE: {
|
||||
key: 'elite',
|
||||
label: 'Élite',
|
||||
value: 5,
|
||||
skillDistribution: {
|
||||
level0: 6,
|
||||
level1: 3,
|
||||
level2: 2,
|
||||
level3: 1
|
||||
},
|
||||
description: 'Élite, très expérimenté'
|
||||
}
|
||||
};
|
||||
|
||||
export const EXPERIENCE_LEVEL_LIST = [
|
||||
EXPERIENCE_LEVEL.RECRUIT,
|
||||
EXPERIENCE_LEVEL.ROOKIE,
|
||||
EXPERIENCE_LEVEL.INTERMEDIATE,
|
||||
EXPERIENCE_LEVEL.REGULAR,
|
||||
EXPERIENCE_LEVEL.VETERAN,
|
||||
EXPERIENCE_LEVEL.ELITE
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// Rôles (Crew roles in a starship)
|
||||
// ============================================================================
|
||||
|
||||
// Caractéristiques par rôle - définit quelles caractéristiques sont prioritaires
|
||||
// pour chaque rôle (High, Medium, Low)
|
||||
export const CHARACTERISTIC_PRIORITIES = {
|
||||
pilot: {
|
||||
high: ['DEX', 'INT'],
|
||||
medium: ['EDU', 'STR'],
|
||||
low: ['END', 'SOC']
|
||||
},
|
||||
navigator: {
|
||||
high: ['INT', 'EDU'],
|
||||
medium: ['DEX', 'SOC'],
|
||||
low: ['STR', 'END']
|
||||
},
|
||||
engineer: {
|
||||
high: ['INT', 'EDU'],
|
||||
medium: ['DEX', 'END'],
|
||||
low: ['STR', 'SOC']
|
||||
},
|
||||
steward: {
|
||||
high: ['INT', 'SOC'],
|
||||
medium: ['DEX', 'EDU'],
|
||||
low: ['STR', 'END']
|
||||
},
|
||||
medic: {
|
||||
high: ['INT', 'EDU'],
|
||||
medium: ['DEX', 'SOC'],
|
||||
low: ['STR', 'END']
|
||||
},
|
||||
marine: {
|
||||
high: ['STR', 'END'],
|
||||
medium: ['DEX', 'INT'],
|
||||
low: ['EDU', 'SOC']
|
||||
},
|
||||
gunner: {
|
||||
high: ['DEX', 'INT'],
|
||||
medium: ['END', 'EDU'],
|
||||
low: ['STR', 'SOC']
|
||||
},
|
||||
scout: {
|
||||
high: ['DEX', 'INT'],
|
||||
medium: ['END', 'EDU'],
|
||||
low: ['STR', 'SOC']
|
||||
},
|
||||
technician: {
|
||||
high: ['INT', 'EDU'],
|
||||
medium: ['DEX', 'END'],
|
||||
low: ['STR', 'SOC']
|
||||
},
|
||||
leader: {
|
||||
high: ['INT', 'SOC'],
|
||||
medium: ['EDU', 'END'],
|
||||
low: ['DEX', 'STR']
|
||||
},
|
||||
diplomat: {
|
||||
high: ['INT', 'SOC'],
|
||||
medium: ['EDU', 'DEX'],
|
||||
low: ['STR', 'END']
|
||||
},
|
||||
entertainer: {
|
||||
high: ['DEX', 'SOC'],
|
||||
medium: ['INT', 'EDU'],
|
||||
low: ['STR', 'END']
|
||||
},
|
||||
trader: {
|
||||
high: ['INT', 'SOC'],
|
||||
medium: ['EDU', 'DEX'],
|
||||
low: ['STR', 'END']
|
||||
},
|
||||
thug: {
|
||||
high: ['STR', 'END'],
|
||||
medium: ['DEX', 'INT'],
|
||||
low: ['EDU', 'SOC']
|
||||
}
|
||||
};
|
||||
|
||||
// Compétences pertinentes pour chaque rôle
|
||||
export const ROLE_SKILLS = {
|
||||
pilot: [
|
||||
'Pilot-Spacecraft',
|
||||
'Astrogation',
|
||||
'Electronics-Sensors',
|
||||
'Gunner',
|
||||
'Mechanic',
|
||||
'Pilot-Small Craft',
|
||||
'Leadership',
|
||||
'Vacc Suit',
|
||||
'Communications',
|
||||
'Drive-Grav',
|
||||
'Survival',
|
||||
'Recon',
|
||||
'Flyer'
|
||||
],
|
||||
navigator: [
|
||||
'Astrogation',
|
||||
'Electronics-Sensors',
|
||||
'Pilot-Spacecraft',
|
||||
'Computers',
|
||||
'Survival',
|
||||
'Navigation',
|
||||
'Mechanic',
|
||||
'Leadership',
|
||||
'Tactics',
|
||||
'Engineer',
|
||||
'Vacc Suit',
|
||||
'Recon'
|
||||
],
|
||||
engineer: [
|
||||
'Engineer-MDrive',
|
||||
'Mechanic',
|
||||
'Engineer-Power',
|
||||
'Computers',
|
||||
'Engineer-JDrive',
|
||||
'Engineer-Life Support',
|
||||
'Electronics-Sensors',
|
||||
'Survival',
|
||||
'Pilot-Small Craft',
|
||||
'Leadership',
|
||||
'Vacc Suit',
|
||||
'Recon',
|
||||
'Drive'
|
||||
],
|
||||
steward: [
|
||||
'Steward',
|
||||
'Carouse',
|
||||
'Persuade',
|
||||
'Broker',
|
||||
'Admin',
|
||||
'Computers',
|
||||
'Language',
|
||||
'Advocate',
|
||||
'Leadership',
|
||||
'Medic',
|
||||
'Streetwise',
|
||||
'Diplomat'
|
||||
],
|
||||
medic: [
|
||||
'Medic',
|
||||
'Science-Biology',
|
||||
'Science-Chemistry',
|
||||
'Deception',
|
||||
'Investigate',
|
||||
'Diplomat',
|
||||
'Computers',
|
||||
'Persuade',
|
||||
'Admin',
|
||||
'Broker',
|
||||
'Electronics-Sensors',
|
||||
'Drive',
|
||||
'Leadership'
|
||||
],
|
||||
marine: [
|
||||
'Gun Combat',
|
||||
'Survival',
|
||||
'Athletics-Strength',
|
||||
'Melee-Unarmed',
|
||||
'Heavy Weapons',
|
||||
'Tactics',
|
||||
'Recon',
|
||||
'Electronics-Sensors',
|
||||
'Leadership',
|
||||
'Medic',
|
||||
'Drive-Grav',
|
||||
'Communications',
|
||||
'Stealth'
|
||||
],
|
||||
gunner: [
|
||||
'Gunner-Turrets',
|
||||
'Electronics-Sensors',
|
||||
'Gunner-Screens',
|
||||
'Tactics',
|
||||
'Gun Combat',
|
||||
'Leadership',
|
||||
'Mechanic',
|
||||
'Heavy Weapons',
|
||||
'Explosives',
|
||||
'Computers',
|
||||
'Pilot-Small Craft',
|
||||
'Athletics-Dexterity',
|
||||
'Melee-Blade'
|
||||
],
|
||||
scout: [
|
||||
'Survival',
|
||||
'Recon',
|
||||
'Pilot-Small Craft',
|
||||
'Astrogation',
|
||||
'Electronics-Sensors',
|
||||
'Stealth',
|
||||
'Gunner',
|
||||
'Medic',
|
||||
'Tactics',
|
||||
'Gun Combat',
|
||||
'Navigation',
|
||||
'Leadership'
|
||||
],
|
||||
technician: [
|
||||
'Mechanic',
|
||||
'Computers',
|
||||
'Electronics-Sensors',
|
||||
'Engineer-Power',
|
||||
'Engineer-MDrive',
|
||||
'Drive',
|
||||
'Pilot',
|
||||
'Vacc Suit',
|
||||
'Recon',
|
||||
'Athletics-Dexterity',
|
||||
'Survival',
|
||||
'Explosives'
|
||||
],
|
||||
leader: [
|
||||
'Leadership',
|
||||
'Tactics',
|
||||
'Admin',
|
||||
'Diplomat',
|
||||
'Persuade',
|
||||
'Advocate',
|
||||
'Electronics-Sensors',
|
||||
'Computers',
|
||||
'Deception',
|
||||
'Pilot-Spacecraft',
|
||||
'Engineer',
|
||||
'Recon',
|
||||
'Medic'
|
||||
],
|
||||
diplomat: [
|
||||
'Diplomat',
|
||||
'Persuade',
|
||||
'Advocate',
|
||||
'Admin',
|
||||
'Carouse',
|
||||
'Steward',
|
||||
'Streetwise',
|
||||
'Language',
|
||||
'Broker',
|
||||
'Leadership',
|
||||
'Communications',
|
||||
'Tactics'
|
||||
],
|
||||
entertainer: [
|
||||
'Carouse',
|
||||
'Streetwise',
|
||||
'Art-Instrument',
|
||||
'Persuade',
|
||||
'Stealth',
|
||||
'Deception',
|
||||
'Diplomat',
|
||||
'Art-Acting',
|
||||
'Computers',
|
||||
'Electronics-Sensors',
|
||||
'Leadership',
|
||||
'Broker',
|
||||
'Melee-Blade',
|
||||
'Admin'
|
||||
],
|
||||
trader: [
|
||||
'Broker',
|
||||
'Persuade',
|
||||
'Admin',
|
||||
'Advocate',
|
||||
'Computers',
|
||||
'Streetwise',
|
||||
'Gun Combat',
|
||||
'Diplomat',
|
||||
'Deception',
|
||||
'Carouse',
|
||||
'Communications',
|
||||
'Mechanic',
|
||||
'Electronics-Sensors',
|
||||
'Leadership'
|
||||
],
|
||||
thug: [
|
||||
'Melee-Unarmed',
|
||||
'Gun Combat',
|
||||
'Melee-Blade',
|
||||
'Athletics-Strength',
|
||||
'Stealth',
|
||||
'Streetwise',
|
||||
'Carouse',
|
||||
'Tactics',
|
||||
'Survival',
|
||||
'Persuade',
|
||||
'Explosives',
|
||||
'Computers'
|
||||
]
|
||||
};
|
||||
|
||||
// Liste des rôles avec libellés en français
|
||||
export const ROLE = {
|
||||
PILOT: { key: 'pilot', label: 'Pilote', description: 'Pilote de vaisseau spatial' },
|
||||
NAVIGATOR: { key: 'navigator', label: 'Navigateur', description: 'Navigateur spatial' },
|
||||
ENGINEER: { key: 'engineer', label: 'Ingénieur', description: 'Ingénieur de bord' },
|
||||
STEWARD: { key: 'steward', label: 'Intendant', description: 'Intendant / steward' },
|
||||
MEDIC: { key: 'medic', label: 'Médecin', description: 'Médecin de bord' },
|
||||
MARINE: { key: 'marine', label: 'Marine', description: 'Marine / soldat' },
|
||||
GUNNER: { key: 'gunner', label: 'Artilleur', description: 'Artilleur / canonnier' },
|
||||
SCOUT: { key: 'scout', label: 'Éclaireur', description: 'Éclaireur' },
|
||||
TECHNICIAN: { key: 'technician', label: 'Technicien', description: 'Technicien' },
|
||||
LEADER: { key: 'leader', label: 'Chef', description: 'Chef / leader' },
|
||||
DIPLOMAT: { key: 'diplomat', label: 'Diplomate', description: 'Diplomate' },
|
||||
ENTERTAINER: { key: 'entertainer', label: 'Artiste', description: 'Artiste / divertisseur' },
|
||||
TRADER: { key: 'trader', label: 'Marchand', description: 'Marchand / commerçant' },
|
||||
THUG: { key: 'thug', label: 'Brute', description: 'Brute / voyou' }
|
||||
};
|
||||
|
||||
export const ROLE_LIST = [
|
||||
ROLE.PILOT,
|
||||
ROLE.NAVIGATOR,
|
||||
ROLE.ENGINEER,
|
||||
ROLE.STEWARD,
|
||||
ROLE.MEDIC,
|
||||
ROLE.MARINE,
|
||||
ROLE.GUNNER,
|
||||
ROLE.SCOUT,
|
||||
ROLE.TECHNICIAN,
|
||||
ROLE.LEADER,
|
||||
ROLE.DIPLOMAT,
|
||||
ROLE.ENTERTAINER,
|
||||
ROLE.TRADER,
|
||||
ROLE.THUG
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// Genre
|
||||
// ============================================================================
|
||||
|
||||
export const GENDER = {
|
||||
UNSPECIFIED: { key: 'unspecified', label: 'Non spécifié', value: 0 },
|
||||
FEMALE: { key: 'female', label: 'Féminin', value: 1 },
|
||||
MALE: { key: 'male', label: 'Masculin', value: 2 }
|
||||
};
|
||||
|
||||
export const GENDER_LIST = [
|
||||
GENDER.UNSPECIFIED,
|
||||
GENDER.FEMALE,
|
||||
GENDER.MALE
|
||||
];
|
||||
|
||||
// ============================================================================
|
||||
// Catalogues de noms
|
||||
// ============================================================================
|
||||
|
||||
export const NAME_CATALOGS = {
|
||||
surnames: [
|
||||
'Anderson', 'Berezovsky', 'Brown', 'Chen', 'Clark', 'Davis', 'Fujita', 'Garcia',
|
||||
'Gupta', 'Harris', 'Hicks', 'Ito', 'Ivanov', 'Jackson', 'Johnson', 'Jones',
|
||||
'Kim', 'Kobayashi', 'Kowalski', 'Kumar', 'Kuznetsoff', 'Kuznetsov', 'Kuznetsova',
|
||||
'Lee', 'Martin', 'Martinez', 'Miller', 'Moore', 'Nakamura', 'Nguyen', 'Nowak',
|
||||
"O'Brien", "O'Callaghan", "O'Connell", "O'Connor", "O'Keefe", "O'Leary",
|
||||
"O'Malley", "O'Neil", "O'Reilly", "O'Sullivan", 'Park', 'Patel', 'Pierzynski',
|
||||
'Pietrzykowski', 'Pisarski', 'Reshevsky', 'Robinson', 'Rumkowska', 'Saito',
|
||||
'Singh', 'Smith', 'Tanaka', 'Taylor', 'Thomas', 'Thompson', 'Vasquez',
|
||||
'Watanabe', 'White', 'Williams', 'Wilson', 'Wong', 'Yamamoto', 'Yang'
|
||||
],
|
||||
nonGenderedNames: [
|
||||
'Arrow', 'Artemis', 'Ash', 'Aster', 'Avery', 'Basil', 'Ever', 'Fig', 'Finch',
|
||||
'Indigo', 'Jett', 'Juniper', 'Kavi', 'Kaviya', 'Kaviyan', 'Kaviyanan', 'Kay',
|
||||
'Lark', 'Noah', 'Ocean', 'Phoenix', 'Riley', 'River', 'Rory', 'Rowan', 'Sage',
|
||||
'Sawyer', 'Shiloh', 'Sparrow', 'Sutton', 'Tavi', 'Uli', 'Veer', 'Vesper',
|
||||
'Winter', 'Wren', 'Zen', 'Zenith', 'Zephyr', 'Zephyrus'
|
||||
],
|
||||
femaleNames: [
|
||||
'Aarohi', 'Aarushi', 'Abigail', 'Amelia', 'Ananya', 'Anika', 'Anjali',
|
||||
'Anushka', 'Aria', 'Ava', 'Chloe', 'Devi', 'Elina', 'Elizabeth', 'Emily',
|
||||
'Emma', 'Esha', 'Evelyn', 'Grace', 'Harper', 'Isabella', 'Ishani', 'Ishika',
|
||||
'Ishita', 'Layla', 'Lily', 'Madison', 'Margarita', 'Maria', 'Mia', 'Nisha',
|
||||
'Nora', 'Nosheen', 'Olivia', 'Penelope', 'Priya', 'Qadira', 'Riley', 'Scarlett',
|
||||
'Sofia', 'Sophia', 'Tala', 'Ulka', 'Uthra', 'Uthraa', 'Victoria', 'Yasmin',
|
||||
'Zara', 'Zoey', 'Zoya', 'Zulaikha'
|
||||
],
|
||||
maleNames: [
|
||||
'Ahmed', 'Aiden', 'Alexander', 'Benjamin', 'Callum', 'Carter', 'Daniel',
|
||||
'David', 'Dylan', 'Elias', 'Elijah', 'Ethan', 'Ewan', 'Ezra', 'Gavin',
|
||||
'Henry', 'Hudson', 'Jackson', 'Jacob', 'Jaden', 'James', 'Jaxon', 'Jayden',
|
||||
'Jordan', 'Joseph', 'Julian', 'Kian', 'Kianan', 'Kianu', 'Lachlan', 'Leo',
|
||||
'Levi', 'Liam', 'Lincoln', 'Logan', 'Lucas', 'Mason', 'Mateo', 'Matthew',
|
||||
'Michael', 'Mohammed', 'Noah', 'Nolan', 'Oliver', 'Pol', 'Samuel',
|
||||
'Santiago', 'Sebastian', 'Theodore', 'Ulric', 'Wallid', 'William', 'Wyatt'
|
||||
]
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Caractéristiques
|
||||
// ============================================================================
|
||||
|
||||
export const CHARACTERISTIC = {
|
||||
STR: { key: 'STR', label: 'Force', mgt2eKey: 'STR' },
|
||||
DEX: { key: 'DEX', label: 'Dextérité', mgt2eKey: 'DEX' },
|
||||
END: { key: 'END', label: 'Endurance', mgt2eKey: 'END' },
|
||||
INT: { key: 'INT', label: 'Intellect', mgt2eKey: 'INT' },
|
||||
EDU: { key: 'EDU', label: 'Éducation', mgt2eKey: 'EDU' },
|
||||
SOC: { key: 'SOC', label: 'Statut Social', mgt2eKey: 'SOC' }
|
||||
};
|
||||
|
||||
export const CHARACTERISTIC_LIST = [
|
||||
CHARACTERISTIC.STR,
|
||||
CHARACTERISTIC.DEX,
|
||||
CHARACTERISTIC.END,
|
||||
CHARACTERISTIC.INT,
|
||||
CHARACTERISTIC.EDU,
|
||||
CHARACTERISTIC.SOC
|
||||
];
|
||||
|
||||
// Ordre des caractéristiques pour l'UPP
|
||||
export const UPP_ORDER = ['STR', 'DEX', 'END', 'INT', 'EDU', 'SOC'];
|
||||
|
||||
// ============================================================================
|
||||
// Codes d'erreur
|
||||
// ============================================================================
|
||||
|
||||
export const ERROR_CODES = {
|
||||
INVALID_OPTIONS: 'INVALID_OPTIONS',
|
||||
INVALID_ROLE: 'INVALID_ROLE',
|
||||
INVALID_CATEGORY: 'INVALID_CATEGORY',
|
||||
INVALID_EXPERIENCE: 'INVALID_EXPERIENCE',
|
||||
INVALID_GENDER: 'INVALID_GENDER',
|
||||
ACTOR_CREATION_FAILED: 'ACTOR_CREATION_FAILED',
|
||||
MGT2E_NOT_ACTIVE: 'MGT2E_NOT_ACTIVE',
|
||||
BASE_ACTOR_NOT_FOUND: 'BASE_ACTOR_NOT_FOUND'
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Fonctions utilitaires
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Convertit une valeur de caractéristique en code hexadécimal pour UPP
|
||||
* @param {number} value - Valeur de la caractéristique (0-15)
|
||||
* @returns {string} - Code hexadécimal
|
||||
*/
|
||||
export function toHex(value) {
|
||||
return Math.max(0, Math.min(15, Math.floor(value))).toString(16).toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcule le DM (Damage Modifier) à partir d'une valeur de caractéristique
|
||||
* @param {number} value - Valeur de la caractéristique
|
||||
* @returns {number} - Modificateur de dégâts
|
||||
*/
|
||||
export function calculateDm(value) {
|
||||
return Math.floor((value - 6) / 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère un nom aléatoire à partir des catalogues
|
||||
* @param {string} genderKey - Clé du genre ('unspecified', 'female', 'male')
|
||||
* @returns {{firstName: string, surname: string, fullName: string}}
|
||||
*/
|
||||
export function generateRandomName(genderKey = 'unspecified') {
|
||||
const genderMap = {
|
||||
unspecified: NAME_CATALOGS.nonGenderedNames,
|
||||
female: NAME_CATALOGS.femaleNames,
|
||||
male: NAME_CATALOGS.maleNames
|
||||
};
|
||||
|
||||
const firstNames = genderMap[genderKey] || NAME_CATALOGS.nonGenderedNames;
|
||||
const surnames = NAME_CATALOGS.surnames;
|
||||
|
||||
const firstName = pickRandomItem(firstNames);
|
||||
const surname = pickRandomItem(surnames);
|
||||
|
||||
return {
|
||||
firstName,
|
||||
surname,
|
||||
fullName: `${firstName} ${surname}`
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sélectionne un élément aléatoire dans un tableau
|
||||
* @template T
|
||||
* @param {T[]} items - Tableau d'éléments
|
||||
* @returns {T} - Élément sélectionné
|
||||
*/
|
||||
export function pickRandomItem(items) {
|
||||
if (!items || items.length === 0) {
|
||||
throw new Error('Cannot pick from empty array');
|
||||
}
|
||||
const index = Math.floor(Math.random() * items.length);
|
||||
return items[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Mélange un tableau (algorithme de Fisher-Yates)
|
||||
* @template T
|
||||
* @param {T[]} array - Tableau à mélanger
|
||||
* @returns {T[]} - Nouveau tableau mélangé
|
||||
*/
|
||||
export function shuffleArray(array) {
|
||||
const newArray = [...array];
|
||||
for (let i = newArray.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
[newArray[i], newArray[j]] = [newArray[j], newArray[i]];
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extraire les N premiers éléments d'un tableau et retourner les deux parties
|
||||
* @template T
|
||||
* @param {T[]} array - Tableau source
|
||||
* @param {number} count - Nombre d'éléments à extraire
|
||||
* @returns {[T[], T[]]} - [éléments extraits, éléments restants]
|
||||
*/
|
||||
export function popRandomItems(array, count) {
|
||||
if (!array || array.length === 0 || count <= 0) {
|
||||
return [[], [...array]];
|
||||
}
|
||||
|
||||
const shuffled = shuffleArray(array);
|
||||
const taken = shuffled.slice(0, Math.min(count, shuffled.length));
|
||||
const remaining = shuffled.slice(Math.min(count, shuffled.length));
|
||||
|
||||
return [taken, remaining];
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un rôle par sa clé
|
||||
* @param {string} key - Clé du rôle
|
||||
* @returns {Object} - Objet rôle
|
||||
*/
|
||||
export function getRoleByKey(key) {
|
||||
const found = ROLE_LIST.find(r => r.key === key);
|
||||
return found || ROLE.PILOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve une catégorie de citoyen par sa clé
|
||||
* @param {string} key - Clé de la catégorie
|
||||
* @returns {Object} - Objet catégorie
|
||||
*/
|
||||
export function getCitizenCategoryByKey(key) {
|
||||
const found = CITIZEN_CATEGORY_LIST.find(c => c.key === key);
|
||||
return found || CITIZEN_CATEGORY.AVERAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un niveau d'expérience par sa clé
|
||||
* @param {string} key - Clé du niveau
|
||||
* @returns {Object} - Objet niveau d'expérience
|
||||
*/
|
||||
export function getExperienceLevelByKey(key) {
|
||||
const found = EXPERIENCE_LEVEL_LIST.find(e => e.key === key);
|
||||
return found || EXPERIENCE_LEVEL.REGULAR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trouve un genre par sa clé
|
||||
* @param {string} key - Clé du genre
|
||||
* @returns {Object} - Objet genre
|
||||
*/
|
||||
export function getGenderByKey(key) {
|
||||
const found = GENDER_LIST.find(g => g.key === key);
|
||||
return found || GENDER.UNSPECIFIED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les compétences pour un rôle
|
||||
* @param {string} roleKey - Clé du rôle
|
||||
* @returns {string[]} - Tableau de compétences
|
||||
*/
|
||||
export function getSkillsForRole(roleKey) {
|
||||
return ROLE_SKILLS[roleKey] || ROLE_SKILLS.pilot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient les priorités de caractéristiques pour un rôle
|
||||
* @param {string} roleKey - Clé du rôle
|
||||
* @returns {Object} - Priorités de caractéristiques
|
||||
*/
|
||||
export function getCharacteristicPrioritiesForRole(roleKey) {
|
||||
return CHARACTERISTIC_PRIORITIES[roleKey] || CHARACTERISTIC_PRIORITIES.pilot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide les options de génération
|
||||
* @param {Object} options - Options à valider
|
||||
* @returns {Object} - Options validées
|
||||
*/
|
||||
export function validateOptions(options = {}) {
|
||||
const errors = [];
|
||||
const validated = { ...options };
|
||||
|
||||
if (validated.citizenCategory && !getCitizenCategoryByKey(validated.citizenCategory)) {
|
||||
errors.push(`Catégorie de citoyen invalide: ${validated.citizenCategory}`);
|
||||
validated.citizenCategory = DEFAULT_OPTIONS.citizenCategory;
|
||||
}
|
||||
|
||||
if (validated.experience && !getExperienceLevelByKey(validated.experience)) {
|
||||
errors.push(`Niveau d'expérience invalide: ${validated.experience}`);
|
||||
validated.experience = DEFAULT_OPTIONS.experience;
|
||||
}
|
||||
|
||||
if (validated.role && !getRoleByKey(validated.role)) {
|
||||
errors.push(`Rôle invalide: ${validated.role}`);
|
||||
validated.role = DEFAULT_OPTIONS.role;
|
||||
}
|
||||
|
||||
if (validated.gender && !getGenderByKey(validated.gender)) {
|
||||
errors.push(`Genre invalide: ${validated.gender}`);
|
||||
validated.gender = DEFAULT_OPTIONS.gender;
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.warn(`${MODULE_ID} | Options de génération invalides:`, errors);
|
||||
}
|
||||
|
||||
return validated;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Données par défaut
|
||||
// ============================================================================
|
||||
|
||||
export const DEFAULT_OPTIONS = {
|
||||
citizenCategory: CITIZEN_CATEGORY.AVERAGE.key,
|
||||
experience: EXPERIENCE_LEVEL.REGULAR.key,
|
||||
role: ROLE.PILOT.key,
|
||||
gender: GENDER.UNSPECIFIED.key,
|
||||
createActor: false,
|
||||
actorName: '',
|
||||
openCreatedActor: true
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Traductions françaises des compétences Traveller
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Libellés français des compétences Traveller
|
||||
* Basé sur les traductions du système mgt2e et les standards Traveller FR
|
||||
*/
|
||||
export const SKILL_LABELS_FR = {
|
||||
'Pilot-Spacecraft': 'Pilote – Vaisseau spatial',
|
||||
'Pilot-Small Craft': 'Pilote – Petits vaisseaux',
|
||||
'Pilot': 'Pilote',
|
||||
'Flyer': 'Pilote – Aéronef atmosphérique',
|
||||
'Astrogation': 'Astrogation',
|
||||
'Navigation': 'Navigation',
|
||||
'Electronics-Sensors': 'Électronique – Capteurs',
|
||||
'Electronics-Communications': 'Électronique – Communications',
|
||||
'Electronics-Computers': 'Électronique – Informatique',
|
||||
'Electronics': 'Électronique',
|
||||
'Computers': 'Informatique',
|
||||
'Gunner-Turrets': 'Artilleur – Tourelles',
|
||||
'Gunner-Screens': 'Artilleur – Boucliers',
|
||||
'Gunner': 'Artilleur',
|
||||
'Gun Combat': 'Combat aux armes à feu',
|
||||
'Heavy Weapons': 'Armes lourdes',
|
||||
'Explosives': 'Explosifs',
|
||||
'Mechanic': 'Mécanique',
|
||||
'Engineer-MDrive': 'Ingénieur – Propulsion manœuvre',
|
||||
'Engineer-Power': 'Ingénieur – Énergie',
|
||||
'Engineer-JDrive': 'Ingénieur – Propulsion saut',
|
||||
'Engineer-Life Support': 'Ingénieur – Support vie',
|
||||
'Engineer': 'Ingénieur',
|
||||
'Steward': 'Intendant',
|
||||
'Carouse': 'Festoyer',
|
||||
'Persuade': 'Persuasion',
|
||||
'Broker': 'Courtage',
|
||||
'Admin': 'Administration',
|
||||
'Advocate': 'Plaidoyer',
|
||||
'Diplomat': 'Diplomatie',
|
||||
'Streetwise': 'Rues',
|
||||
'Leadership': 'Leadership',
|
||||
'Science-Biology': 'Science – Biologie',
|
||||
'Science-Chemistry': 'Science – Chimie',
|
||||
'Science': 'Science',
|
||||
'Medic': 'Médecine',
|
||||
'Deception': 'Tromperie',
|
||||
'Investigate': 'Investigation',
|
||||
'Melee-Unarmed': 'Mêlée – Sans arme',
|
||||
'Melee-Blade': 'Mêlée – Arme blanche',
|
||||
'Melee': 'Mêlée',
|
||||
'Athletics-Strength': 'Athlétisme – Force',
|
||||
'Athletics-Dexterity': 'Athlétisme – Dextérité',
|
||||
'Athletics': 'Athlétisme',
|
||||
'Tactics': 'Tactiques',
|
||||
'Recon': 'Reconnaissance',
|
||||
'Survival': 'Survie',
|
||||
'Stealth': 'Discrétion',
|
||||
'Communications': 'Communications',
|
||||
'Drive-Grav': 'Conduite – Gravité',
|
||||
'Drive': 'Conduite',
|
||||
'Vacc Suit': 'Combinaison spatiale',
|
||||
'Language': 'Langue',
|
||||
'Art-Acting': 'Art – Jeu d\'acteur',
|
||||
'Art-Instrument': 'Art – Instrument',
|
||||
'Art': 'Art'
|
||||
};
|
||||
|
||||
export const CHARACTERISTIC_LABELS_FR = {
|
||||
'STR': 'Force',
|
||||
'DEX': 'Dextérité',
|
||||
'END': 'Endurance',
|
||||
'INT': 'Intellect',
|
||||
'EDU': 'Éducation',
|
||||
'SOC': 'Statut Social'
|
||||
};
|
||||
|
||||
export const CITIZEN_CATEGORY_LABELS_FR = {
|
||||
'belowAverage': 'En dessous de la moyenne',
|
||||
'average': 'Moyenne',
|
||||
'aboveAverage': 'Au-dessus de la moyenne',
|
||||
'exceptional': 'Exceptionnel'
|
||||
};
|
||||
|
||||
export const EXPERIENCE_LEVEL_LABELS_FR = {
|
||||
'recruit': 'Recrue',
|
||||
'rookie': 'Débutant',
|
||||
'intermediate': 'Intermédiaire',
|
||||
'regular': 'Régulier',
|
||||
'veteran': 'Vétéran',
|
||||
'elite': 'Élite'
|
||||
};
|
||||
|
||||
export const ROLE_LABELS_FR = {
|
||||
'pilot': 'Pilote',
|
||||
'navigator': 'Navigateur',
|
||||
'engineer': 'Ingénieur',
|
||||
'steward': 'Intendant',
|
||||
'medic': 'Médecin',
|
||||
'marine': 'Marine',
|
||||
'gunner': 'Artilleur',
|
||||
'scout': 'Éclaireur',
|
||||
'technician': 'Technicien',
|
||||
'leader': 'Chef',
|
||||
'diplomat': 'Diplomate',
|
||||
'entertainer': 'Artiste',
|
||||
'trader': 'Marchand',
|
||||
'thug': 'Brute'
|
||||
};
|
||||
|
||||
export const GENDER_LABELS_FR = {
|
||||
'unspecified': 'Non spécifié',
|
||||
'female': 'Féminin',
|
||||
'male': 'Masculin'
|
||||
};
|
||||
+26
-5
@@ -112,13 +112,34 @@ export function setSkillLevel(skills, skillFqn, level) {
|
||||
if (!skillId || !skills?.[skillId]) return skills;
|
||||
|
||||
const numericLevel = Number(level ?? 0);
|
||||
const skill = foundry.utils.mergeObject(skills[skillId], { trained: numericLevel > 0 || skills[skillId].trained });
|
||||
let skill = foundry.utils.deepClone(skills[skillId]);
|
||||
|
||||
if (specialityId && skill.specialities?.[specialityId]) {
|
||||
skill.specialities[specialityId] = foundry.utils.mergeObject(skill.specialities[specialityId], {
|
||||
value: Math.max(Number(skill.specialities[specialityId].value ?? 0), numericLevel),
|
||||
});
|
||||
// Marquer la compétence comme entraînée si niveau > 0
|
||||
skill.trained = numericLevel > 0 || skill.trained;
|
||||
|
||||
if (specialityId) {
|
||||
// Créer l'objet specialities s'il n'existe pas
|
||||
if (!skill.specialities) {
|
||||
skill.specialities = {};
|
||||
}
|
||||
// Créer la spécialité si elle n'existe pas
|
||||
if (!skill.specialities[specialityId]) {
|
||||
skill.specialities[specialityId] = {
|
||||
value: 0,
|
||||
trained: false,
|
||||
default: skill.default || null
|
||||
};
|
||||
}
|
||||
// Appliquer le niveau à la spécialité
|
||||
skill.specialities[specialityId] = foundry.utils.mergeObject(
|
||||
skill.specialities[specialityId],
|
||||
{
|
||||
value: Math.max(Number(skill.specialities[specialityId].value ?? 0), numericLevel),
|
||||
trained: true
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// Compétence sans spécialité
|
||||
skill.value = Math.max(Number(skill.value ?? 0), numericLevel);
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -35,6 +35,8 @@ Hooks.once('init', () => {
|
||||
loadTemplatesFn([
|
||||
`modules/${MODULE_ID}/templates/npc-dialog.hbs`,
|
||||
`modules/${MODULE_ID}/templates/npc-result.hbs`,
|
||||
`modules/${MODULE_ID}/templates/traveller-npc-dialog.hbs`,
|
||||
`modules/${MODULE_ID}/templates/traveller-npc-result.hbs`,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -97,7 +99,7 @@ Hooks.on('renderChatInput', (app, html, data) => {
|
||||
});
|
||||
|
||||
/**
|
||||
* Intercepte les messages de chat pour /pnj, /rencontre, /mission
|
||||
* Intercepte les messages de chat pour /pnj, /rencontre, /mission, /gennpc
|
||||
* Utilise preCreateChatMessage pour Foundry v14+ (avant que le message ne soit validé)
|
||||
* Compatible avec Foundry v13 et v14
|
||||
*/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,730 @@
|
||||
/**
|
||||
* Traveller NPC Generator - Logique métier
|
||||
* Basé sur : https://github.com/carloscasalar/traveller-npc-generator
|
||||
*
|
||||
* Ce fichier contient la logique de génération des PNJ Traveller.
|
||||
*/
|
||||
|
||||
import {
|
||||
CITIZEN_CATEGORY,
|
||||
EXPERIENCE_LEVEL,
|
||||
ROLE,
|
||||
ROLE_SKILLS,
|
||||
CHARACTERISTIC_PRIORITIES,
|
||||
GENDER,
|
||||
CHARACTERISTIC,
|
||||
CHARACTERISTIC_LIST,
|
||||
UPP_ORDER,
|
||||
toHex,
|
||||
calculateDm,
|
||||
pickRandomItem,
|
||||
shuffleArray,
|
||||
getRoleByKey,
|
||||
getCitizenCategoryByKey,
|
||||
getExperienceLevelByKey,
|
||||
getGenderByKey,
|
||||
getSkillsForRole,
|
||||
getCharacteristicPrioritiesForRole,
|
||||
validateOptions,
|
||||
DEFAULT_OPTIONS,
|
||||
NAME_CATALOGS,
|
||||
SKILL_LABELS_FR,
|
||||
CHARACTERISTIC_LABELS_FR,
|
||||
CITIZEN_CATEGORY_LABELS_FR,
|
||||
EXPERIENCE_LEVEL_LABELS_FR,
|
||||
ROLE_LABELS_FR,
|
||||
GENDER_LABELS_FR
|
||||
} from './data/travellerNpcGenerator.js';
|
||||
|
||||
import { setSkillLevel, localizeSkill } from './mgt2eSkills.js';
|
||||
import { travellerNpcCache, TravellerNpcError, ERROR_CODES } from './utils/travellerNpcUtils.js';
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
|
||||
// ============================================================================
|
||||
// Conversion des compétences
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Mapping des compétences Traveller vers mgt2e
|
||||
* @type {Object<string, string>}
|
||||
*/
|
||||
// Mapping des compétences Traveller vers MgT2e
|
||||
// IMPORTANT: Utiliser les clés internes EXACTES du système mgt2e (ex: pilot, spacecraft, smallCraft)
|
||||
// Basé sur le fichier fr.json du système : /home/morr/work/foundryvtt/traveller-foundryvtt/mgt2e/lang/fr.json
|
||||
// Format: 'Compétence-Traveller' -> 'competence_mgt2e.specialite_mgt2e' (en camelCase, sans accents)
|
||||
// Les clés sont en anglais, les libellés français sont gérés par la localisation
|
||||
const SKILL_MAPPING = {
|
||||
// Pilotage
|
||||
'Pilot-Spacecraft': 'pilot.spacecraft',
|
||||
'Pilot-Small Craft': 'pilot.smallCraft',
|
||||
'Pilot': 'pilot',
|
||||
'Flyer': 'flyer',
|
||||
|
||||
// Astrogation et Navigation
|
||||
'Astrogation': 'astrogation',
|
||||
'Navigation': 'navigation',
|
||||
|
||||
// Électronique
|
||||
'Electronics-Sensors': 'electronics.sensors',
|
||||
'Electronics-Communications': 'electronics.comms',
|
||||
'Electronics-Computers': 'electronics.computers',
|
||||
'Electronics': 'electronics',
|
||||
'Computers': 'electronics',
|
||||
'Communications': 'electronics',
|
||||
|
||||
// Artillerie
|
||||
'Gunner-Turrets': 'gunner.turret',
|
||||
'Gunner-Screens': 'gunner.screen',
|
||||
'Gunner': 'gunner',
|
||||
|
||||
// Mécanique
|
||||
'Mechanic': 'mechanic',
|
||||
|
||||
// Ingénierie
|
||||
'Engineer-MDrive': 'engineer.mDrive',
|
||||
'Engineer-Power': 'engineer.power',
|
||||
'Engineer-JDrive': 'engineer.jDrive',
|
||||
'Engineer-Life Support': 'engineer.lifeSupport',
|
||||
'Engineer': 'engineer',
|
||||
|
||||
// Social et Administration
|
||||
'Steward': 'steward',
|
||||
'Carouse': 'carouse',
|
||||
'Persuade': 'persuade',
|
||||
'Broker': 'broker',
|
||||
'Admin': 'admin',
|
||||
'Language': 'language',
|
||||
'Advocate': 'advocate',
|
||||
'Leadership': 'leadership',
|
||||
'Medic': 'medic',
|
||||
'Diplomat': 'diplomat',
|
||||
|
||||
// Sciences
|
||||
'Science-Biology': 'science.biology',
|
||||
'Science-Chemistry': 'science.chemistry',
|
||||
'Science': 'science',
|
||||
|
||||
// Combat
|
||||
'Gun Combat': 'guncombat',
|
||||
'Heavy Weapons': 'heavyweapons',
|
||||
|
||||
// Mêlée
|
||||
'Melee-Unarmed': 'melee.unarmed',
|
||||
'Melee-Blade': 'melee.blade',
|
||||
'Melee': 'melee',
|
||||
|
||||
// Athlétisme
|
||||
'Athletics-Strength': 'athletics.strength',
|
||||
'Athletics-Dexterity': 'athletics.dexterity',
|
||||
'Athletics': 'athletics',
|
||||
|
||||
// Tactique et Exploration
|
||||
'Tactics': 'tactics',
|
||||
'Recon': 'recon',
|
||||
'Survival': 'survival',
|
||||
'Stealth': 'stealth',
|
||||
'Explosives': 'explosives',
|
||||
'Deception': 'deception',
|
||||
'Investigate': 'investigate',
|
||||
|
||||
// Conduite
|
||||
'Drive-Grav': 'drive.grav',
|
||||
'Drive': 'drive',
|
||||
|
||||
// Équipement
|
||||
'Vacc Suit': 'vaccsuit',
|
||||
|
||||
// Art
|
||||
'Art-Acting': 'art.performer',
|
||||
'Art-Instrument': 'art.instrument',
|
||||
'Art': 'art'
|
||||
};
|
||||
|
||||
/**
|
||||
* Convertit un nom de compétence du format Traveller vers le format mgt2e
|
||||
* @param {string} skillName - Nom de la compétence
|
||||
* @returns {string} - Nom au format mgt2e
|
||||
*/
|
||||
function convertSkillToMgt2eFormat(skillName) {
|
||||
// Vérifier d'abord dans le mapping explicite
|
||||
if (SKILL_MAPPING[skillName]) {
|
||||
return SKILL_MAPPING[skillName];
|
||||
}
|
||||
|
||||
// Si pas dans le mapping, essayer de deviner
|
||||
// MgT2e utilise des noms en minuscules (ex: pilot, electronics, guncombat)
|
||||
// 1. Remplacer les tirets et espaces par des points
|
||||
// 2. Tout mettre en minuscules
|
||||
return skillName.toLowerCase().replace(/-| /g, '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le libellé français d'une compétence Traveller
|
||||
* @param {string} skillName - Nom de la compétence (ex: 'Pilot-Spacecraft')
|
||||
* @returns {string} - Libellé français
|
||||
*/
|
||||
function getSkillLabelFr(skillName) {
|
||||
return SKILL_LABELS_FR[skillName] || skillName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtient le libellé français d'une caractéristique
|
||||
* @param {string} charKey - Clé de la caractéristique (ex: 'STR')
|
||||
* @returns {string} - Libellé français
|
||||
*/
|
||||
function getCharacteristicLabelFr(charKey) {
|
||||
return CHARACTERISTIC_LABELS_FR[charKey] || charKey;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Génération des caractéristiques
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Génère les caractéristiques d'un PNJ en fonction de sa catégorie et de son rôle
|
||||
*
|
||||
* @param {string} citizenCategoryKey - Clé de la catégorie de citoyen
|
||||
* @param {string} roleKey - Clé du rôle
|
||||
* @returns {Object} - Objet contenant les caractéristiques et l'UPP
|
||||
*/
|
||||
export function generateCharacteristics(citizenCategoryKey, roleKey) {
|
||||
const category = getCitizenCategoryByKey(citizenCategoryKey);
|
||||
const priorities = getCharacteristicPrioritiesForRole(roleKey);
|
||||
|
||||
// Cloner et mélanger l'array de base de la catégorie
|
||||
let characteristicArray = shuffleArray([...category.characteristicArray]);
|
||||
|
||||
const characteristics = {};
|
||||
|
||||
// Créer un tableau de priorités avec leur ordre
|
||||
const priorityGroups = [
|
||||
{ keys: priorities.high, count: priorities.high.length },
|
||||
{ keys: priorities.medium, count: priorities.medium.length },
|
||||
{ keys: priorities.low, count: priorities.low.length }
|
||||
];
|
||||
|
||||
// Attribuer les valeurs dans l'ordre de priorité
|
||||
for (const group of priorityGroups) {
|
||||
const values = characteristicArray.slice(0, group.count);
|
||||
characteristicArray = characteristicArray.slice(group.count);
|
||||
|
||||
group.keys.forEach((charKey, i) => {
|
||||
characteristics[charKey] = values[i] ?? 7;
|
||||
});
|
||||
}
|
||||
|
||||
// Remplir les valeurs manquantes avec 7
|
||||
for (const charKey of UPP_ORDER) {
|
||||
if (characteristics[charKey] === undefined) {
|
||||
characteristics[charKey] = 7;
|
||||
}
|
||||
}
|
||||
|
||||
// Construire l'UPP
|
||||
const upp = UPP_ORDER.map(charKey => toHex(characteristics[charKey])).join('');
|
||||
|
||||
return {
|
||||
characteristics,
|
||||
upp,
|
||||
category
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Génération des compétences
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Distribue les niveaux de compétence sur une liste de compétences
|
||||
*
|
||||
* @param {string[]} roleSkills - Liste des compétences du rôle
|
||||
* @param {Object} distribution - Distribution des niveaux
|
||||
* @returns {Array<{name: string, level: number}>} - Compétences avec niveaux
|
||||
*/
|
||||
function distributeSkillLevels(roleSkills, distribution) {
|
||||
// Supprimer les doublons EXACTS (même nom complet de compétence)
|
||||
// mais conserver les spécialisations comme Pilot-Spacecraft et Pilot-Small Craft
|
||||
const uniqueSkills = [];
|
||||
const seen = new Set();
|
||||
|
||||
for (const skill of roleSkills) {
|
||||
if (!seen.has(skill)) {
|
||||
seen.add(skill);
|
||||
uniqueSkills.push(skill);
|
||||
}
|
||||
}
|
||||
|
||||
// Mélanger les compétences une seule fois
|
||||
const shuffledSkills = shuffleArray([...uniqueSkills]);
|
||||
|
||||
// Créer un tableau de niveaux, initialisé à 0
|
||||
const levels = new Array(shuffledSkills.length).fill(0);
|
||||
|
||||
// Distribuer les niveaux de manière séquentielle
|
||||
let index = 0;
|
||||
|
||||
// Level 3 (les plus rares, en premier)
|
||||
const level3Count = Math.min(distribution.level3, shuffledSkills.length);
|
||||
for (let i = 0; i < level3Count; i++) {
|
||||
if (index < levels.length) levels[index++] = 3;
|
||||
}
|
||||
|
||||
// Level 2
|
||||
const level2Count = Math.min(distribution.level2, shuffledSkills.length - level3Count);
|
||||
for (let i = 0; i < level2Count; i++) {
|
||||
if (index < levels.length) levels[index++] = 2;
|
||||
}
|
||||
|
||||
// Level 1
|
||||
const level1Count = Math.min(distribution.level1, shuffledSkills.length - level3Count - level2Count);
|
||||
for (let i = 0; i < level1Count; i++) {
|
||||
if (index < levels.length) levels[index++] = 1;
|
||||
}
|
||||
|
||||
// Level 0 (déjà initialisé)
|
||||
|
||||
// Créer le résultat trié par niveau (descendant) puis par nom
|
||||
return shuffledSkills
|
||||
.map((skill, i) => ({ name: skill, level: levels[i] }))
|
||||
.sort((a, b) => b.level - a.level || a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère les compétences d'un PNJ en fonction de son rôle et de son expérience
|
||||
*
|
||||
* @param {string} roleKey - Clé du rôle
|
||||
* @param {string} experienceKey - Clé du niveau d'expérience
|
||||
* @returns {Array<{name: string, level: number}>} - Liste des compétences avec niveaux
|
||||
*/
|
||||
export function generateSkills(roleKey, experienceKey) {
|
||||
const roleSkills = getSkillsForRole(roleKey);
|
||||
const experience = getExperienceLevelByKey(experienceKey);
|
||||
const distribution = experience.skillDistribution;
|
||||
|
||||
return distributeSkillLevels(roleSkills, distribution);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Génération complète du PNJ
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Génère un PNJ Traveller complet
|
||||
*
|
||||
* @typedef {Object} TravellerNpcOptions
|
||||
* @property {string} [citizenCategory] - Catégorie de citoyen
|
||||
* @property {string} [experience] - Niveau d'expérience
|
||||
* @property {string} [role] - Rôle
|
||||
* @property {string} [gender] - Genre
|
||||
* @property {string} [firstName] - Prénom forcé
|
||||
* @property {string} [surname] - Nom de famille forcé
|
||||
*
|
||||
* @typedef {Object} TravellerNpcResult
|
||||
* @property {boolean} success - Succès de la génération
|
||||
* @property {string} type - Type de résultat
|
||||
* @property {Object} name - Nom du PNJ
|
||||
* @property {Object} role - Rôle
|
||||
* @property {Object} citizenCategory - Catégorie
|
||||
* @property {Object} experience - Expérience
|
||||
* @property {Object} gender - Genre
|
||||
* @property {Object} characteristics - Caractéristiques
|
||||
* @property {string} upp - Code UPP
|
||||
* @property {Array<{name: string, level: number}>} skills - Compétences
|
||||
* @property {Array<{name: string, level: number}>} skillsForActor - Compétences pour mgt2e
|
||||
* @property {Object} display - Métadonnées pour l'affichage
|
||||
*
|
||||
* @param {TravellerNpcOptions} [options={}]
|
||||
* @returns {TravellerNpcResult}
|
||||
*/
|
||||
export function generateTravellerNpc(options = {}) {
|
||||
// Valider et fusionner avec les options par défaut
|
||||
const opts = validateOptions({
|
||||
...DEFAULT_OPTIONS,
|
||||
...options
|
||||
});
|
||||
|
||||
// Générer le nom
|
||||
let name;
|
||||
if (opts.firstName && opts.surname) {
|
||||
name = {
|
||||
firstName: opts.firstName,
|
||||
surname: opts.surname,
|
||||
fullName: `${opts.firstName} ${opts.surname}`
|
||||
};
|
||||
} else {
|
||||
const firstName = pickRandomItem(
|
||||
opts.gender === 'female' ? NAME_CATALOGS.femaleNames :
|
||||
opts.gender === 'male' ? NAME_CATALOGS.maleNames :
|
||||
NAME_CATALOGS.nonGenderedNames
|
||||
);
|
||||
const surname = pickRandomItem(NAME_CATALOGS.surnames);
|
||||
name = {
|
||||
firstName,
|
||||
surname,
|
||||
fullName: `${firstName} ${surname}`
|
||||
};
|
||||
}
|
||||
|
||||
// Générer les caractéristiques
|
||||
const { characteristics, upp, category } = generateCharacteristics(
|
||||
opts.citizenCategory,
|
||||
opts.role
|
||||
);
|
||||
|
||||
// Générer les compétences
|
||||
const skills = generateSkills(opts.role, opts.experience);
|
||||
|
||||
// Convertir les compétences au format mgt2e pour la création de fiche
|
||||
const skillsForActor = skills.map(s => ({
|
||||
name: convertSkillToMgt2eFormat(s.name),
|
||||
level: s.level
|
||||
}));
|
||||
|
||||
// Récupérer les objets complets pour les références
|
||||
const citizenCategory = getCitizenCategoryByKey(opts.citizenCategory);
|
||||
const experience = getExperienceLevelByKey(opts.experience);
|
||||
const role = getRoleByKey(opts.role);
|
||||
const gender = getGenderByKey(opts.gender);
|
||||
|
||||
// Libellés des caractéristiques pour l'affichage (en français)
|
||||
const characteristicLabels = {};
|
||||
CHARACTERISTIC_LIST.forEach(char => {
|
||||
characteristicLabels[char.key] = getCharacteristicLabelFr(char.key);
|
||||
});
|
||||
|
||||
// Ajouter les libellés français aux compétences pour l'affichage
|
||||
const skillsWithLabels = skills.map(skill => ({
|
||||
...skill,
|
||||
labelFr: getSkillLabelFr(skill.name)
|
||||
}));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
type: 'traveller-npc',
|
||||
name,
|
||||
role,
|
||||
citizenCategory,
|
||||
experience,
|
||||
gender,
|
||||
characteristics,
|
||||
upp,
|
||||
skills: skillsWithLabels,
|
||||
skillsForActor,
|
||||
MODULE_ID,
|
||||
UPP_ORDER,
|
||||
display: {
|
||||
roleLabel: ROLE_LABELS_FR[role.key] || role.label,
|
||||
categoryLabel: CITIZEN_CATEGORY_LABELS_FR[citizenCategory.key] || citizenCategory.label,
|
||||
experienceLabel: EXPERIENCE_LEVEL_LABELS_FR[experience.key] || experience.label,
|
||||
genderLabel: GENDER_LABELS_FR[gender.key] || gender.label,
|
||||
characteristicLabels
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Récupération du système de base mgt2e
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Récupère le système de base des acteurs mgt2e
|
||||
* @returns {Promise<Object|null>} - Système de base ou null
|
||||
*/
|
||||
async function fetchMgt2eBaseActorSystem() {
|
||||
try {
|
||||
// Vérifier que le système mgt2e est actif
|
||||
if (game.system?.id !== 'mgt2e') {
|
||||
console.warn(`${MODULE_ID} | Le système mgt2e n'est pas actif`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const pack = game.packs.get('mgt2e.base-actors');
|
||||
if (!pack) {
|
||||
console.warn(`${MODULE_ID} | Le compendium mgt2e.base-actors n'est pas disponible`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const index = Array.from(await pack.getIndex({ fields: ['name', 'type'] }));
|
||||
const entry = index.find((document) => document.name === 'DEFAULT TRAVELLER')
|
||||
?? index.find((document) => document.type === 'traveller')
|
||||
?? index[0];
|
||||
|
||||
if (!entry?._id) {
|
||||
console.warn(`${MODULE_ID} | Aucun acteur de base trouvé dans mgt2e.base-actors`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const document = await pack.getDocument(entry._id);
|
||||
const system = document?.toObject()?.system;
|
||||
|
||||
if (!system) {
|
||||
console.warn(`${MODULE_ID} | Le système de l'acteur de base est vide`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return system;
|
||||
} catch (error) {
|
||||
console.error(`${MODULE_ID} | Erreur lors de la récupération du système de base mgt2e:`, error);
|
||||
throw new TravellerNpcError(
|
||||
'Erreur lors de la récupération du système de base mgt2e',
|
||||
ERROR_CODES.BASE_ACTOR_NOT_FOUND,
|
||||
{ error: error.message }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le système de base des acteurs mgt2e avec cache
|
||||
* @param {Object} [options={}] - Options
|
||||
* @param {boolean} [options.forceRefresh=false] - Forcer le rafraîchissement
|
||||
* @returns {Promise<Object|null>}
|
||||
*/
|
||||
export async function getMgt2eBaseActorSystem(options = {}) {
|
||||
const { forceRefresh = false } = options;
|
||||
const cacheKey = 'baseActorSystem';
|
||||
|
||||
return travellerNpcCache.getOrFetch(
|
||||
cacheKey,
|
||||
fetchMgt2eBaseActorSystem,
|
||||
{ forceRefresh }
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Construction des données pour l'acteur
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Construit les caractéristiques au format mgt2e
|
||||
*
|
||||
* @param {Object} existingCharacteristics - Caractéristiques existantes
|
||||
* @param {Object} characteristics - Caractéristiques générées
|
||||
* @returns {Object} - Caractéristiques au format mgt2e
|
||||
*/
|
||||
export function buildMgt2eCharacteristics(existingCharacteristics = {}, characteristics) {
|
||||
const result = foundry.utils.deepClone(existingCharacteristics);
|
||||
|
||||
for (const char of CHARACTERISTIC_LIST) {
|
||||
const value = characteristics[char.key] || 7;
|
||||
result[char.mgt2eKey] = foundry.utils.mergeObject(result[char.mgt2eKey] ?? {}, {
|
||||
value,
|
||||
current: value,
|
||||
dm: calculateDm(value),
|
||||
show: true,
|
||||
default: false,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit les compétences au format mgt2e
|
||||
*
|
||||
* @param {Object} existingSkills - Compétences existantes
|
||||
* @param {Array<{name: string, level: number}>} skills - Compétences générées
|
||||
* @param {boolean} [alreadyMapped=false] - Les compétences sont déjà au format mgt2e
|
||||
* @returns {Object} - Compétences au format mgt2e
|
||||
*/
|
||||
export function buildMgt2eSkills(existingSkills = {}, skills, alreadyMapped = false) {
|
||||
const result = foundry.utils.deepClone(existingSkills);
|
||||
|
||||
for (const { name, level } of skills) {
|
||||
const skillName = alreadyMapped ? name : convertSkillToMgt2eFormat(name);
|
||||
setSkillLevel(result, skillName, level);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit la description de l'acteur
|
||||
*
|
||||
* @param {Object} npcData - Données du PNJ généré
|
||||
* @param {string} actorName - Nom de l'acteur
|
||||
* @returns {string} - Description formatée
|
||||
*/
|
||||
function buildActorDescription(npcData, actorName) {
|
||||
const notableSkills = npcData.skills
|
||||
.filter(s => s.level > 0)
|
||||
.map(s => {
|
||||
try {
|
||||
return localizeSkill(s.name) || s.name;
|
||||
} catch (e) {
|
||||
return s.name;
|
||||
}
|
||||
})
|
||||
.join(', ');
|
||||
|
||||
const lines = [
|
||||
`${actorName} — ${npcData.role.label}`,
|
||||
`Catégorie : ${npcData.citizenCategory.label}`,
|
||||
`Expérience : ${npcData.experience.label}`,
|
||||
`UPP : ${npcData.upp}`,
|
||||
`Genre : ${npcData.gender.label}`
|
||||
];
|
||||
|
||||
if (notableSkills) {
|
||||
lines.push(`Compétences : ${notableSkills}`);
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Création de la fiche d'acteur
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Crée une fiche d'acteur pour un PNJ Traveller
|
||||
*
|
||||
* @param {Object} npcData - Données du PNJ généré
|
||||
* @param {Object} options - Options de création
|
||||
* @param {string} [options.name] - Nom de l'acteur
|
||||
* @param {boolean} [options.openSheet=true] - Ouvrir la fiche après création
|
||||
* @returns {Promise<Actor|null>} - Acteur créé ou null
|
||||
*/
|
||||
export async function createTravellerNpcActor(npcData, options = {}) {
|
||||
try {
|
||||
const requestedName = options.name?.trim();
|
||||
const actorName = requestedName || npcData.name.fullName || `PNJ — ${npcData.role.label}`;
|
||||
|
||||
// Vérifier que mgt2e est actif
|
||||
if (game.system?.id !== 'mgt2e') {
|
||||
throw new TravellerNpcError(
|
||||
'Le système mgt2e doit être actif pour créer des fiches PNJ Traveller',
|
||||
ERROR_CODES.MGT2E_NOT_ACTIVE
|
||||
);
|
||||
}
|
||||
|
||||
// Récupérer le système de base
|
||||
const baseActorSystem = await getMgt2eBaseActorSystem();
|
||||
|
||||
// Construire la description (avec <br> pour HTML)
|
||||
const description = buildActorDescription(npcData, actorName).replace(/\n/g, '<br>');
|
||||
|
||||
const actorData = {
|
||||
name: actorName,
|
||||
type: 'npc',
|
||||
img: 'systems/mgt2e/icons/cargo/passenger-middle.svg',
|
||||
system: {
|
||||
settings: foundry.utils.mergeObject(
|
||||
foundry.utils.deepClone(baseActorSystem?.settings ?? {}),
|
||||
{
|
||||
hideUntrained: true,
|
||||
lockCharacteristics: true,
|
||||
}
|
||||
),
|
||||
sophont: foundry.utils.mergeObject(
|
||||
foundry.utils.deepClone(baseActorSystem?.sophont ?? {}),
|
||||
{
|
||||
age: 18 + Math.floor(Math.random() * 40),
|
||||
homeworld: '',
|
||||
profession: npcData.role.label,
|
||||
}
|
||||
),
|
||||
characteristics: buildMgt2eCharacteristics(
|
||||
foundry.utils.deepClone(baseActorSystem?.characteristics ?? {}),
|
||||
npcData.characteristics
|
||||
),
|
||||
hits: foundry.utils.deepClone(baseActorSystem?.hits ?? {}),
|
||||
skills: buildMgt2eSkills(
|
||||
foundry.utils.deepClone(baseActorSystem?.skills ?? {}),
|
||||
npcData.skillsForActor,
|
||||
true
|
||||
),
|
||||
description,
|
||||
},
|
||||
flags: {
|
||||
[MODULE_ID]: {
|
||||
generatedTravellerNpc: {
|
||||
version: 1,
|
||||
role: npcData.role.key,
|
||||
citizenCategory: npcData.citizenCategory.key,
|
||||
experience: npcData.experience.key,
|
||||
gender: npcData.gender.key,
|
||||
upp: npcData.upp,
|
||||
generatedAt: new Date().toISOString()
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const actor = await Actor.create(actorData, { renderSheet: false });
|
||||
|
||||
if (options.openSheet !== false) {
|
||||
actor.sheet?.render(true);
|
||||
}
|
||||
|
||||
return actor;
|
||||
} catch (error) {
|
||||
const npcError = TravellerNpcError.from(error, ERROR_CODES.ACTOR_CREATION_FAILED);
|
||||
npcError.notify();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Fonction principale
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Fonction principale pour générer un PNJ Traveller
|
||||
* Peut créer une fiche d'acteur si demandé
|
||||
*
|
||||
* @param {TravellerNpcOptions} [options={}]
|
||||
* @returns {Promise<TravellerNpcResult>}
|
||||
*/
|
||||
export async function generateAndCreateTravellerNpc(options = {}) {
|
||||
const npcData = generateTravellerNpc(options);
|
||||
|
||||
if (options.createActor) {
|
||||
const actor = await createTravellerNpcActor(npcData, {
|
||||
name: options.actorName,
|
||||
openSheet: options.openCreatedActor !== false
|
||||
});
|
||||
|
||||
if (actor) {
|
||||
npcData.createdActor = {
|
||||
id: actor.id,
|
||||
name: actor.name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return npcData;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Export des types et données pour compatibilité
|
||||
// ============================================================================
|
||||
|
||||
// Ré-exporter les données pour facilitier les imports
|
||||
export {
|
||||
CITIZEN_CATEGORY,
|
||||
CITIZEN_CATEGORY_LIST,
|
||||
EXPERIENCE_LEVEL,
|
||||
EXPERIENCE_LEVEL_LIST,
|
||||
ROLE,
|
||||
ROLE_LIST,
|
||||
ROLE_SKILLS,
|
||||
CHARACTERISTIC_PRIORITIES,
|
||||
GENDER,
|
||||
GENDER_LIST,
|
||||
CHARACTERISTIC,
|
||||
CHARACTERISTIC_LIST,
|
||||
UPP_ORDER,
|
||||
toHex,
|
||||
calculateDm,
|
||||
pickRandomItem,
|
||||
shuffleArray,
|
||||
getRoleByKey,
|
||||
getCitizenCategoryByKey,
|
||||
getExperienceLevelByKey,
|
||||
getGenderByKey,
|
||||
getSkillsForRole,
|
||||
getCharacteristicPrioritiesForRole,
|
||||
validateOptions,
|
||||
DEFAULT_OPTIONS
|
||||
} from './data/travellerNpcGenerator.js';
|
||||
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* Traveller NPC Generator - Utilitaires
|
||||
*
|
||||
* Ce fichier contient les classes et fonctions utilitaires pour le générateur.
|
||||
*/
|
||||
|
||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||
|
||||
// ============================================================================
|
||||
// Classe de gestion des erreurs
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Erreur spécifique au générateur de PNJ Traveller
|
||||
*/
|
||||
export class TravellerNpcError extends Error {
|
||||
/**
|
||||
* @param {string} message - Message d'erreur
|
||||
* @param {string} code - Code d'erreur
|
||||
* @param {Object} [details={}] - Détails supplémentaires
|
||||
*/
|
||||
constructor(message, code, details = {}) {
|
||||
super(message);
|
||||
this.name = 'TravellerNpcError';
|
||||
this.code = code;
|
||||
this.details = details;
|
||||
this.isTravellerNpcError = true;
|
||||
this.timestamp = new Date().toISOString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une erreur à partir d'une erreur existante
|
||||
* @param {Error} error - Erreur originale
|
||||
* @param {string} code - Code d'erreur
|
||||
* @returns {TravellerNpcError}
|
||||
*/
|
||||
static from(error, code = 'UNKNOWN') {
|
||||
if (error?.isTravellerNpcError) {
|
||||
return error;
|
||||
}
|
||||
return new TravellerNpcError(
|
||||
error?.message || 'Erreur inconnue',
|
||||
code,
|
||||
{ originalError: error }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Log l'erreur dans la console
|
||||
*/
|
||||
log() {
|
||||
console.error(`${MODULE_ID} | [${this.code}] ${this.message}`, {
|
||||
details: this.details,
|
||||
timestamp: this.timestamp
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Affiche une notification à l'utilisateur et log l'erreur
|
||||
*/
|
||||
notify() {
|
||||
ui.notifications?.error(`${this.code}: ${this.message}`);
|
||||
this.log();
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée une notification sans lancer d'erreur
|
||||
* @param {string} message - Message
|
||||
* @param {string} code - Code
|
||||
*/
|
||||
static warn(message, code) {
|
||||
const error = new TravellerNpcError(message, code);
|
||||
error.log();
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Classe de cache pour le module
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Système de cache générique pour le module
|
||||
*/
|
||||
export class ModuleCache {
|
||||
/**
|
||||
* @param {string} moduleId - ID du module
|
||||
* @param {number} [defaultTTL=300000] - Durée de vie par défaut (5 min)
|
||||
*/
|
||||
constructor(moduleId, defaultTTL = 300000) {
|
||||
this.moduleId = moduleId;
|
||||
this.defaultTTL = defaultTTL;
|
||||
this.cache = new Map();
|
||||
this.pending = new Map();
|
||||
this.timestamps = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une valeur du cache ou la fetch
|
||||
* @template T
|
||||
* @param {string} key - Clé de cache
|
||||
* @param {Function} fetchFn - Fonction de récupération
|
||||
* @param {Object} [options={}] - Options
|
||||
* @param {boolean} [options.forceRefresh=false] - Forcer le rafraîchissement
|
||||
* @param {number} [options.ttl] - TTL spécifique
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
async getOrFetch(key, fetchFn, options = {}) {
|
||||
const { forceRefresh = false, ttl = this.defaultTTL } = options;
|
||||
|
||||
// Si déjà en cache et non expiré
|
||||
if (!forceRefresh && this.cache.has(key)) {
|
||||
const timestamp = this.timestamps.get(key);
|
||||
if (Date.now() - timestamp < ttl) {
|
||||
return foundry.utils.deepClone(this.cache.get(key));
|
||||
}
|
||||
// Expiré, on le supprime
|
||||
this.clear(key);
|
||||
}
|
||||
|
||||
// Si déjà en cours de fetch pour cette clé
|
||||
if (this.pending.has(key)) {
|
||||
return this.pending.get(key);
|
||||
}
|
||||
|
||||
// Nouveau fetch
|
||||
const promise = (async () => {
|
||||
try {
|
||||
const result = await fetchFn();
|
||||
const cachedResult = result ? foundry.utils.deepClone(result) : null;
|
||||
this.cache.set(key, cachedResult);
|
||||
this.timestamps.set(key, Date.now());
|
||||
this.pending.delete(key);
|
||||
return foundry.utils.deepClone(cachedResult);
|
||||
} catch (error) {
|
||||
console.warn(`${this.moduleId} | Erreur de cache pour ${key}:`, error);
|
||||
this.pending.delete(key);
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
|
||||
this.pending.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide une entrée du cache
|
||||
* @param {string} key - Clé à supprimer
|
||||
*/
|
||||
clear(key) {
|
||||
this.cache.delete(key);
|
||||
this.pending.delete(key);
|
||||
this.timestamps.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vide tout le cache
|
||||
*/
|
||||
clearAll() {
|
||||
this.cache.clear();
|
||||
this.pending.clear();
|
||||
this.timestamps.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si une clé est en cache
|
||||
* @param {string} key - Clé à vérifier
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(key) {
|
||||
if (!this.cache.has(key)) return false;
|
||||
const timestamp = this.timestamps.get(key);
|
||||
return Date.now() - timestamp < this.defaultTTL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère une valeur du cache sans vérification d'expiration
|
||||
* @template T
|
||||
* @param {string} key - Clé
|
||||
* @returns {T|null}
|
||||
*/
|
||||
get(key) {
|
||||
return this.cache.get(key) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Instance de cache pour le module
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Instance de cache partagée pour le générateur de PNJ Traveller
|
||||
* @type {ModuleCache}
|
||||
*/
|
||||
export const travellerNpcCache = new ModuleCache(MODULE_ID, 300000); // 5 min TTL
|
||||
|
||||
// ============================================================================
|
||||
// Codes d'erreur
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Codes d'erreur standard pour le module
|
||||
*/
|
||||
export const ERROR_CODES = {
|
||||
INVALID_OPTIONS: 'INVALID_OPTIONS',
|
||||
INVALID_ROLE: 'INVALID_ROLE',
|
||||
INVALID_CATEGORY: 'INVALID_CATEGORY',
|
||||
INVALID_EXPERIENCE: 'INVALID_EXPERIENCE',
|
||||
INVALID_GENDER: 'INVALID_GENDER',
|
||||
ACTOR_CREATION_FAILED: 'ACTOR_CREATION_FAILED',
|
||||
MGT2E_NOT_ACTIVE: 'MGT2E_NOT_ACTIVE',
|
||||
BASE_ACTOR_NOT_FOUND: 'BASE_ACTOR_NOT_FOUND',
|
||||
CACHE_ERROR: 'CACHE_ERROR',
|
||||
GENERATION_ERROR: 'GENERATION_ERROR'
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Fonctions utilitaires générales
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Formate un message de debug
|
||||
* @param {string} message - Message
|
||||
* @param {Object} [data={}] - Données supplémentaires
|
||||
*/
|
||||
export function debug(message, data = {}) {
|
||||
if (game.settings.get(MODULE_ID, 'debug') || game.user?.isGM) {
|
||||
console.debug(`${MODULE_ID} | ${message}`, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formate un message de log
|
||||
* @param {string} message - Message
|
||||
* @param {Object} [data={}] - Données supplémentaires
|
||||
*/
|
||||
export function log(message, data = {}) {
|
||||
console.log(`${MODULE_ID} | ${message}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formate un message d'avertissement
|
||||
* @param {string} message - Message
|
||||
* @param {Object} [data={}] - Données supplémentaires
|
||||
*/
|
||||
export function warn(message, data = {}) {
|
||||
console.warn(`${MODULE_ID} | ${message}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formate un message d'erreur
|
||||
* @param {string} message - Message
|
||||
* @param {Object} [data={}] - Données supplémentaires
|
||||
*/
|
||||
export function error(message, data = {}) {
|
||||
console.error(`${MODULE_ID} | ${message}`, data);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Export par défaut
|
||||
// ============================================================================
|
||||
|
||||
export default {
|
||||
TravellerNpcError,
|
||||
ModuleCache,
|
||||
travellerNpcCache,
|
||||
ERROR_CODES,
|
||||
debug,
|
||||
log,
|
||||
warn,
|
||||
error
|
||||
};
|
||||
@@ -0,0 +1,614 @@
|
||||
/**
|
||||
* Styles pour le générateur de PNJ Traveller
|
||||
* Aligné avec les styles des dialogues /commerce et /pnj du module
|
||||
* Compatible avec Foundry VTT v13 et v14
|
||||
*/
|
||||
|
||||
/* ==========================================================================
|
||||
Conteneur principal - Aligné sur mgt2-npc-dialog et mgt2-commerce-dialog
|
||||
======================================================================== */
|
||||
|
||||
.mgt2-traveller-npc-dialog .window-header {
|
||||
background: linear-gradient(180deg, rgba(44, 44, 62, 0.96) 0%, rgba(30, 30, 43, 0.96) 100%);
|
||||
border-bottom: 1px solid #c9a227;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-dialog .window-title {
|
||||
color: #d9b24c;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-dialog .window-content,
|
||||
#mgt2-traveller-npc .window-content {
|
||||
padding: 0;
|
||||
overflow-y: auto;
|
||||
background: #f5f0e8;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Formulaire principal - Aligné sur mgt2-npc-form et mgt2-commerce-form
|
||||
======================================================================== */
|
||||
|
||||
.mgt2-traveller-npc-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f5f0e8;
|
||||
color: #222;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form h3,
|
||||
.mgt2-npc-dialog .mgt2-traveller-npc-form h3 {
|
||||
margin: 0 0 12px;
|
||||
color: #5f4300 !important;
|
||||
font-size: 1em;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #b78f26 !important;
|
||||
padding-bottom: 5px;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form h3 i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .traveller-npc-intro {
|
||||
margin: 0 0 10px;
|
||||
color: #555;
|
||||
font-size: 0.87em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Champs de formulaire - Aligné sur mgt2-npc-form
|
||||
======================================================================== */
|
||||
|
||||
.mgt2-traveller-npc-form .form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .form-group-row {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .form-group-row .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form label {
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
display: block;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form input[type="text"],
|
||||
.mgt2-traveller-npc-form select {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 5px 7px;
|
||||
font-size: 0.85em;
|
||||
background: #fff;
|
||||
color: #222;
|
||||
border: 1px solid #bbb;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.08);
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form input[type="text"]:focus,
|
||||
.mgt2-traveller-npc-form select:focus {
|
||||
border-color: #c9a227;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(201, 162, 39, 0.22);
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form select option {
|
||||
background: #fff;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Fieldset - Aligné sur mgt2-npc-form
|
||||
======================================================================== */
|
||||
|
||||
.mgt2-traveller-npc-form fieldset {
|
||||
border: 1px solid #c9a227;
|
||||
border-radius: 5px;
|
||||
padding: 10px 12px 8px;
|
||||
margin: 10px 0;
|
||||
background: rgba(201, 162, 39, 0.04);
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-dialog .mgt2-traveller-npc-form legend,
|
||||
.mgt2-traveller-npc-form legend {
|
||||
color: #7a5c00 !important;
|
||||
font-size: 0.78em;
|
||||
font-weight: bold;
|
||||
padding: 0 5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Champs de nom
|
||||
======================================================================== */
|
||||
|
||||
.mgt2-traveller-npc-form .name-fields {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .name-fields.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .name-fields .form-group {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .name-fields .btn-small {
|
||||
padding: 5px 10px;
|
||||
background: #2c2c3e;
|
||||
color: #e1bc57;
|
||||
border: 1px solid #c9a227;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.85em;
|
||||
min-width: 36px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .name-fields .btn-small:hover {
|
||||
background: #243852;
|
||||
color: #f2d27a;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Checkbox
|
||||
======================================================================== */
|
||||
|
||||
.mgt2-traveller-npc-form .checkbox-group {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .checkbox-group label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
font-size: 0.85em;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .checkbox-group input[type="checkbox"] {
|
||||
accent-color: #c9a227;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Hint
|
||||
======================================================================== */
|
||||
|
||||
.mgt2-traveller-npc-form .hint {
|
||||
font-weight: normal;
|
||||
font-size: 0.85em;
|
||||
color: #777;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Required field indicator
|
||||
======================================================================== */
|
||||
|
||||
.mgt2-traveller-npc-form .required {
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Pied de formulaire - Aligné sur mgt2-npc-form
|
||||
======================================================================== */
|
||||
|
||||
.mgt2-traveller-npc-form .form-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
button.btn-calculate,
|
||||
.mgt2-traveller-npc-form .btn-calculate {
|
||||
background: #2c2c3e;
|
||||
color: #e1bc57;
|
||||
border: 1px solid #c9a227;
|
||||
border-radius: 4px;
|
||||
padding: 7px 18px;
|
||||
font-size: 0.85em;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
button.btn-calculate:hover,
|
||||
.mgt2-traveller-npc-form .btn-calculate:hover {
|
||||
background: #243852;
|
||||
color: #f2d27a;
|
||||
}
|
||||
|
||||
button.btn-calculate:disabled,
|
||||
.mgt2-traveller-npc-form .btn-calculate:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .btn-calculate i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Résultat - Aligné sur mgt2-npc-result
|
||||
======================================================================== */
|
||||
|
||||
.mgt2-npc-result,
|
||||
.traveller-npc-result {
|
||||
font-size: 0.85em;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-header {
|
||||
text-align: center;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #c9a227;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-header h3 {
|
||||
color: #5f4300 !important;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 8px;
|
||||
font-size: 1em;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-header h3 i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-name {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-notice {
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-notice.success {
|
||||
background: #eef8ee;
|
||||
border: 1px solid #a9d0a9;
|
||||
color: #2a6a2a;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-notice.success i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
||||
gap: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-detail {
|
||||
background: #fbf8f1;
|
||||
padding: 6px 8px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
border: 1px solid #d7ccb0;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-detail-label {
|
||||
font-size: 0.75em;
|
||||
color: #7a5c00;
|
||||
margin-bottom: 3px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-detail-value {
|
||||
font-weight: bold;
|
||||
color: #222;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-section h4 {
|
||||
color: #5f4300 !important;
|
||||
font-size: 0.85em;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid #d7ccb0;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-section h4 i {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-characteristics {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-characteristic {
|
||||
background: #fbf8f1;
|
||||
padding: 5px 8px;
|
||||
border-radius: 3px;
|
||||
min-width: 70px;
|
||||
text-align: center;
|
||||
border: 1px solid #d7ccb0;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-char-key {
|
||||
font-size: 0.7em;
|
||||
color: #7a5c00;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 2px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-char-value {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-char-dm {
|
||||
font-size: 0.7em;
|
||||
color: #c9a227;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-skills {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-skill {
|
||||
background: #fbf8f1;
|
||||
padding: 4px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.85em;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border: 1px solid #d7ccb0;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-skill-name {
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-skill-level {
|
||||
color: #c9a227;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-footer {
|
||||
margin-top: 15px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #d7ccb0;
|
||||
text-align: center;
|
||||
font-size: 0.75em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Niveaux de compétence
|
||||
======================================================================== */
|
||||
|
||||
.traveller-npc-result .skillLevelSymbol {
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Onglets personnalisés pour le dialogue
|
||||
======================================================================== */
|
||||
|
||||
.mgt2-traveller-npc-dialog .mgt2-traveller-npc-form .tabs {
|
||||
display: flex;
|
||||
background: #2c2c3e;
|
||||
border-bottom: 3px solid #c9a227;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-dialog .mgt2-traveller-npc-form .tabs .item {
|
||||
flex: 1;
|
||||
padding: 9px 8px;
|
||||
text-align: center;
|
||||
color: #d8c79a !important;
|
||||
font-size: 0.82em;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
border-bottom: 3px solid transparent;
|
||||
margin-bottom: -3px;
|
||||
transition: color 0.18s, border-color 0.18s, background 0.18s;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-dialog .mgt2-traveller-npc-form .tabs .item:hover {
|
||||
color: #f3e3b1 !important;
|
||||
background: rgba(201, 162, 39, 0.16) !important;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-dialog .mgt2-traveller-npc-form .tabs .item.active {
|
||||
color: #d9b24c !important;
|
||||
border-bottom-color: #c9a227 !important;
|
||||
background: rgba(201, 162, 39, 0.18) !important;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Accessibilité
|
||||
======================================================================== */
|
||||
|
||||
/* Focus visible pour la navigation clavier */
|
||||
.mgt2-traveller-npc-form select:focus-visible,
|
||||
.mgt2-traveller-npc-form input:focus-visible,
|
||||
.mgt2-traveller-npc-form button:focus-visible {
|
||||
outline: 2px solid #c9a227;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Contraste amélioré pour l'accessibilité */
|
||||
@media (prefers-contrast: high) {
|
||||
.mgt2-traveller-npc-form label {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form input,
|
||||
.mgt2-traveller-npc-form select {
|
||||
background: #fff;
|
||||
border-width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Design réactif
|
||||
======================================================================== */
|
||||
|
||||
/* Écran large */
|
||||
@media (min-width: 900px) {
|
||||
.mgt2-traveller-npc-dialog {
|
||||
min-width: 700px;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .form-group-row {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-details-grid {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Écran moyen */
|
||||
@media (max-width: 899px) {
|
||||
.mgt2-traveller-npc-dialog {
|
||||
width: 90vw;
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-details-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
/* Mobile */
|
||||
@media (max-width: 600px) {
|
||||
.mgt2-traveller-npc-dialog {
|
||||
width: 95vw;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .form-group-row {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form .name-fields {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-characteristics {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-characteristic {
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-skills {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* Très petit écran */
|
||||
@media (max-width: 400px) {
|
||||
.mgt2-traveller-npc-form .btn-calculate {
|
||||
width: 100%;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-details-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Impression
|
||||
======================================================================== */
|
||||
|
||||
@media print {
|
||||
.mgt2-traveller-npc-dialog .window-header,
|
||||
.mgt2-traveller-npc-dialog .window-content {
|
||||
background: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form {
|
||||
color: black;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.mgt2-traveller-npc-form input,
|
||||
.mgt2-traveller-npc-form select {
|
||||
background: white;
|
||||
color: black;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.traveller-npc-result {
|
||||
background: white;
|
||||
color: black;
|
||||
border: 1px solid #ccc;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.traveller-npc-result .npc-detail,
|
||||
.traveller-npc-result .npc-characteristic,
|
||||
.traveller-npc-result .npc-skill {
|
||||
background: #f9f9f9;
|
||||
border-color: #ccc;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@
|
||||
<a class="item {{#if (eq activeTab "mission")}}active{{/if}}" data-tab="mission">
|
||||
<i class="fas fa-briefcase"></i> Client & mission
|
||||
</a>
|
||||
<a class="item {{#if (eq activeTab "traveller")}}active{{/if}}" data-tab="traveller">
|
||||
<i class="fas fa-user-astronaut"></i> PNJ Détaillé
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<section class="tab-content">
|
||||
@@ -119,6 +122,116 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab {{#if (eq activeTab "traveller")}}active{{/if}}" data-tab="traveller">
|
||||
<h3><i class="fas fa-user-astronaut"></i> Générateur de PNJ Traveller</h3>
|
||||
<p class="traveller-npc-intro">
|
||||
Génère un personnage non-joueur selon les règles du générateur Traveller,
|
||||
avec caractéristiques, compétences et rôle aléatoires ou personnalisés.
|
||||
</p>
|
||||
|
||||
<fieldset>
|
||||
<legend>Identité du PNJ</legend>
|
||||
|
||||
<div class="form-group checkbox-group">
|
||||
<label>
|
||||
<input type="checkbox" name="traveller.useRandomName" {{#if traveller.useRandomName}}checked{{/if}}>
|
||||
Utiliser un nom aléatoire
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group-row traveller-name-fields {{#if traveller.useRandomName}}hidden{{/if}}">
|
||||
<div class="form-group">
|
||||
<label for="traveller-firstName">Prénom</label>
|
||||
<input id="traveller-firstName" name="traveller.firstName" type="text" value="{{traveller.firstName}}" placeholder="John">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="traveller-surname">Nom de famille</label>
|
||||
<input id="traveller-surname" name="traveller.surname" type="text" value="{{traveller.surname}}" placeholder="Smith">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn-small" data-action="randomize-name" title="Générer un nom aléatoire">
|
||||
<i class="fas fa-dice-d6"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group-row">
|
||||
<div class="form-group">
|
||||
<label for="traveller-gender">Genre</label>
|
||||
<select id="traveller-gender" name="traveller.gender">
|
||||
{{#each genders}}
|
||||
<option value="{{key}}" {{#if (eq ../traveller.gender key)}}selected{{/if}}>{{label}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="traveller-role">Rôle <span class="required">*</span></label>
|
||||
<select id="traveller-role" name="traveller.role" required>
|
||||
{{#each roles}}
|
||||
<option value="{{key}}" {{#if (eq ../traveller.role key)}}selected{{/if}}>{{label}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Caractéristiques et Expérience</legend>
|
||||
|
||||
<div class="form-group-row">
|
||||
<div class="form-group">
|
||||
<label for="traveller-citizenCategory">Catégorie de citoyen</label>
|
||||
<select id="traveller-citizenCategory" name="traveller.citizenCategory">
|
||||
{{#each citizenCategories}}
|
||||
<option value="{{key}}" {{#if (eq ../traveller.citizenCategory key)}}selected{{/if}}>{{label}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
<div class="hint">{{description}}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="traveller-experience">Niveau d'expérience</label>
|
||||
<select id="traveller-experience" name="traveller.experience">
|
||||
{{#each experienceLevels}}
|
||||
<option value="{{key}}" {{#if (eq ../traveller.experience key)}}selected{{/if}}>{{label}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
<div class="hint">{{description}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Création de fiche d'acteur</legend>
|
||||
|
||||
<div class="form-group checkbox-group">
|
||||
<label>
|
||||
<input type="checkbox" name="traveller.createActor" {{#if traveller.createActor}}checked{{/if}}>
|
||||
Créer une fiche PNJ dans les Acteurs
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group-row">
|
||||
<div class="form-group">
|
||||
<label for="traveller-actorName">Nom de la fiche <span class="hint">(facultatif)</span></label>
|
||||
<input id="traveller-actorName" name="traveller.actorName" type="text" value="{{traveller.actorName}}" placeholder="PNJ — Pilote">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group checkbox-group">
|
||||
<label>
|
||||
<input type="checkbox" name="traveller.openCreatedActor" {{#if traveller.openCreatedActor}}checked{{/if}}>
|
||||
Ouvrir automatiquement la fiche créée
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-footer">
|
||||
<button type="button" class="btn-calculate" data-action="generate-traveller-npc">
|
||||
<i class="fas fa-dice-d6"></i> Générer le PNJ Traveller
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</form>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="npc-section">
|
||||
<div class="npc-section-title">Résumé jouable</div>
|
||||
<p><strong>Relation :</strong> {{relation.summary}}</p>
|
||||
<p><strong>Compétences-types :</strong> {{join experience.profile.skills ", "}}</p>
|
||||
<p><strong>Compétences-types :</strong> {{joinLocalizedSkills experience.profile.skills ", "}}</p>
|
||||
<p><strong>Niveau moyen de compétence :</strong> {{experience.profile.skillLevel}}</p>
|
||||
<p><strong>Bonus de caractéristiques :</strong>
|
||||
{{#if experience.profile.characteristicBonuses.length}}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<form class="mgt2-traveller-npc-form">
|
||||
<h3><i class="fas fa-user-astronaut"></i> Générateur de PNJ Traveller</h3>
|
||||
<p class="traveller-npc-intro">
|
||||
Génère un personnage non-joueur selon les règles du générateur Traveller,
|
||||
avec caractéristiques, compétences et rôle aléatoires ou personnalisés.
|
||||
</p>
|
||||
|
||||
<fieldset>
|
||||
<legend>Identité du PNJ</legend>
|
||||
|
||||
<div class="form-group checkbox-group">
|
||||
<label>
|
||||
<input type="checkbox" name="useRandomName" {{#if useRandomName}}checked{{/if}}>
|
||||
Utiliser un nom aléatoire
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group-row name-fields {{#if useRandomName}}hidden{{/if}}">
|
||||
<div class="form-group">
|
||||
<label for="firstName">Prénom</label>
|
||||
<input id="firstName" name="firstName" type="text" value="{{firstName}}" placeholder="John">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="surname">Nom de famille</label>
|
||||
<input id="surname" name="surname" type="text" value="{{surname}}" placeholder="Smith">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="button" class="btn-small" data-action="randomize-name" title="Générer un nom aléatoire">
|
||||
<i class="fas fa-dice-d6"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group-row">
|
||||
<div class="form-group">
|
||||
<label for="gender">Genre</label>
|
||||
<select id="gender" name="gender">
|
||||
{{#each genders}}
|
||||
<option value="{{key}}" {{#if (eq ../gender key)}}selected{{/if}}>{{label}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="role">Rôle <span class="required">*</span></label>
|
||||
<select id="role" name="role" required>
|
||||
{{#each roles}}
|
||||
<option value="{{key}}" {{#if (eq ../role key)}}selected{{/if}}>{{label}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Caractéristiques et Expérience</legend>
|
||||
|
||||
<div class="form-group-row">
|
||||
<div class="form-group">
|
||||
<label for="citizenCategory">Catégorie de citoyen</label>
|
||||
<select id="citizenCategory" name="citizenCategory">
|
||||
{{#each citizenCategories}}
|
||||
<option value="{{key}}" {{#if (eq ../citizenCategory key)}}selected{{/if}}>{{label}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
<div class="hint">{{description}}</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="experience">Niveau d'expérience</label>
|
||||
<select id="experience" name="experience">
|
||||
{{#each experienceLevels}}
|
||||
<option value="{{key}}" {{#if (eq ../experience key)}}selected{{/if}}>{{label}}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
<div class="hint">{{description}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>Création de fiche d'acteur</legend>
|
||||
|
||||
<div class="form-group checkbox-group">
|
||||
<label>
|
||||
<input type="checkbox" name="createActor" {{#if createActor}}checked{{/if}}>
|
||||
Créer une fiche PNJ dans les Acteurs
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group-row">
|
||||
<div class="form-group">
|
||||
<label for="actorName">Nom de la fiche <span class="hint">(facultatif)</span></label>
|
||||
<input id="actorName" name="actorName" type="text" value="{{actorName}}" placeholder="PNJ — Pilote">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group checkbox-group">
|
||||
<label>
|
||||
<input type="checkbox" name="openCreatedActor" {{#if openCreatedActor}}checked{{/if}}>
|
||||
Ouvrir automatiquement la fiche créée
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-footer">
|
||||
<button type="button" class="btn-calculate" data-action="generate-traveller-npc">
|
||||
<i class="fas fa-dice-d6"></i> Générer le PNJ Traveller
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -0,0 +1,65 @@
|
||||
<div class="mgt2-npc-result traveller-npc-result">
|
||||
<div class="npc-header">
|
||||
<h3><i class="fas fa-user-astronaut"></i> PNJ Traveller généré</h3>
|
||||
<div class="npc-name">{{name.fullName}}</div>
|
||||
</div>
|
||||
|
||||
{{#if createdActor}}
|
||||
<div class="npc-notice success">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
Fiche d'acteur créée : {{createdActor.name}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="npc-details-grid">
|
||||
<div class="npc-detail">
|
||||
<div class="npc-detail-label">Rôle</div>
|
||||
<div class="npc-detail-value">{{display.roleLabel}}</div>
|
||||
</div>
|
||||
<div class="npc-detail">
|
||||
<div class="npc-detail-label">Catégorie</div>
|
||||
<div class="npc-detail-value">{{display.categoryLabel}}</div>
|
||||
</div>
|
||||
<div class="npc-detail">
|
||||
<div class="npc-detail-label">Expérience</div>
|
||||
<div class="npc-detail-value">{{display.experienceLabel}}</div>
|
||||
</div>
|
||||
<div class="npc-detail">
|
||||
<div class="npc-detail-label">Genre</div>
|
||||
<div class="npc-detail-value">{{display.genderLabel}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="npc-section">
|
||||
<h4><i class="fas fa-chart-bar"></i> Caractéristiques (UPP: {{upp}})</h4>
|
||||
<div class="npc-characteristics">
|
||||
{{#each UPP_ORDER}}
|
||||
<div class="npc-characteristic">
|
||||
<div class="npc-char-key">{{lookup ../display.characteristicLabels this}}</div>
|
||||
<div class="npc-char-value">{{lookup ../characteristics this}}</div>
|
||||
<div class="npc-char-dm">{{formatDm (lookup ../characteristics this)}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if skills}}
|
||||
<div class="npc-section">
|
||||
<h4><i class="fas fa-graduation-cap"></i> Compétences</h4>
|
||||
<div class="npc-skills">
|
||||
{{#each skills}}
|
||||
{{#if (gt level 0)}}
|
||||
<div class="npc-skill {{skillLevelClass level}}" title="{{labelFr}}">
|
||||
<span class="npc-skill-name">{{labelFr}} {{level}}</span>
|
||||
<span class="npc-skill-level">{{skillLevelSymbol level}}</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="npc-footer">
|
||||
<small>Généré par le module {{MODULE_ID}}</small>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user