Compare commits
11 Commits
c3cf8f176d
..
2.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
| a53c7ace53 | |||
| efe37b8a96 | |||
| 49423f40f5 | |||
| 9abc2a8b19 | |||
| 9d96ec5543 | |||
| b6a4148829 | |||
| d8c61458ea | |||
| 9453c15d58 | |||
| 76870c27bf | |||
| ef7fe6e2bd | |||
| 4f53d903eb |
@@ -0,0 +1,73 @@
|
|||||||
|
# AGENTS.md — mgt2-compendium-amiral-denisov
|
||||||
|
|
||||||
|
## First read
|
||||||
|
|
||||||
|
- `.github/copilot-instructions.md` — packs, icons, module.json conventions (this file builds on it, don't repeat it)
|
||||||
|
|
||||||
|
## No package manager / no build
|
||||||
|
|
||||||
|
There is **no** `package.json`, no lockfile, no bundler. JavaScript is plain ESM loaded directly by FoundryVTT. Do not run `npm install` or any build command.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
5 chat commands registered at runtime (not declared in `module.json`):
|
||||||
|
|
||||||
|
| Command | Entrypoint | Dialog |
|
||||||
|
|---|---|---|
|
||||||
|
| `/commerce` | `scripts/commerce.js` | `CommerceDialog` (3 tabs) |
|
||||||
|
| `/pnj` | `scripts/npc.js` | `NpcDialog` (4 tabs) |
|
||||||
|
| `/rencontre` | `scripts/npc.js` | same dialog (tab 2) |
|
||||||
|
| `/mission` | `scripts/npc.js` | same dialog (tab 3) |
|
||||||
|
| `/sector` | `scripts/sector.js` | `SectorMapApp` (IFRAME Traveller Map, clics→chat) |
|
||||||
|
| `/subsector` | `scripts/sector.js` | `SectorMapApp` (IFRAME) |
|
||||||
|
|
||||||
|
Commands are registered via `ChatLogV2.CHAT_COMMANDS` — not Hooks.on("chatMessage") as the copilot-instructions say (that file is stale). See `commerce.js:15` and `npc.js:15` for the actual pattern.
|
||||||
|
|
||||||
|
## Framework quirks
|
||||||
|
|
||||||
|
- **ApplicationV2** + `HandlebarsApplicationMixin` for all dialogs.
|
||||||
|
- Foundry v13/v14 dual code paths: some hooks must handle both jQuery (v13) and DOM (v14) APIs.
|
||||||
|
- Dice rolls: `await new Roll(formula).evaluate()` — always async.
|
||||||
|
- Skill FQN format for `game.i18n`: e.g. `pilot.spacecraft`, `electronics.computers`, `gunner.turret`.
|
||||||
|
|
||||||
|
## Runtime systems (run at `ready` hook, GM only)
|
||||||
|
|
||||||
|
1. **Migration** (`mgt2eMigration.js`): converts legacy item types to mgt2e (armor→armour, equipment→item/augment, computer→hardware, etc.). Tracked via `game.settings` version flag. Forced re-run requires deleting the setting.
|
||||||
|
|
||||||
|
2. **NPC RollTable sync** (`npcRollTableSync.js`): writes D66 tables from `scripts/data/npcTables.js` into the `tables-pnj` compendium pack. Checksums to avoid redundant writes.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# standalone, no npm needed — install jest globally or use npx
|
||||||
|
npx jest scripts/tests/travellerNpcGenerator.test.js
|
||||||
|
```
|
||||||
|
|
||||||
|
- Single file, 1096 lines, hand-rolled FoundryVTT mocks.
|
||||||
|
- Covers: utilities, lookups, validation, characteristic/skill generation, skill conversion, full NPC gen, ModuleCache, errors, config validation.
|
||||||
|
- No CI test step — you must run manually.
|
||||||
|
- No Jest config file — relies on defaults.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
- `module.json` → `"version"` is the single source of truth.
|
||||||
|
- Git tag format: `v1.3.0` (CI strips `v`).
|
||||||
|
- No changelog file.
|
||||||
|
|
||||||
|
## CI/CD (Gitea)
|
||||||
|
|
||||||
|
- Only triggers on **`release: [published]`** — not on push/PR.
|
||||||
|
- Builds a zip archive of `module.json + assets/ + packs/ + scripts/ + styles/ + templates/` and uploads to the release.
|
||||||
|
- No test step in CI.
|
||||||
|
|
||||||
|
## Deprecated packs (still on disk, NOT in module.json)
|
||||||
|
|
||||||
|
- `packs/arme/` → superseded by `armes`
|
||||||
|
- `packs/carriere/` → superseded by `carrieres`
|
||||||
|
- `packs/talent-psy/` → superseded by `talents-psioniques`
|
||||||
|
|
||||||
|
Do not re-add them to `module.json`. They remain for historical data recovery only.
|
||||||
|
|
||||||
|
## All content is in French
|
||||||
|
|
||||||
|
Labels, comments, commit messages, UI strings, icon file names, rule references — everything. Check French naming before searching/grepping.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
testMatch: ['**/scripts/tests/*.test.js'],
|
||||||
|
transform: {},
|
||||||
|
};
|
||||||
+84
-5
@@ -1,21 +1,89 @@
|
|||||||
{
|
{
|
||||||
"id": "mgt2-compendium-amiral-denisov",
|
"id": "mgt2-compendium-amiral-denisov",
|
||||||
"title": "MgT2e - Compendium Amiral Denisov",
|
"title": "MgT2e - Compendium Amiral Denisov",
|
||||||
"version": "1.2.1",
|
"version": "1.3.0",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": "13",
|
"minimum": "13",
|
||||||
"verified": "13",
|
"verified": "13",
|
||||||
"maximum": "14"
|
"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": [
|
"esmodules": [
|
||||||
"scripts/commerce.js",
|
"scripts/commerce.js",
|
||||||
"scripts/npc.js"
|
"scripts/npc.js",
|
||||||
|
"scripts/sector.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": [
|
||||||
"styles/commerce.css",
|
"styles/commerce.css",
|
||||||
"styles/npc.css"
|
"styles/npc.css",
|
||||||
|
"styles/traveller-npc.css"
|
||||||
],
|
],
|
||||||
|
"packFolders": {
|
||||||
|
"name": "Amiral Denisov",
|
||||||
|
"sorting": "m",
|
||||||
|
"color": "#00435c",
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"name": "Equipements",
|
||||||
|
"sorting": "a",
|
||||||
|
"color": "#00435c",
|
||||||
|
"packs": [
|
||||||
|
"armures",
|
||||||
|
"objet",
|
||||||
|
"equipement",
|
||||||
|
"ordinateur",
|
||||||
|
"contenant-sac-coffre",
|
||||||
|
"armes"
|
||||||
|
],
|
||||||
|
"folders": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Références",
|
||||||
|
"sorting": "b",
|
||||||
|
"color": "#00435c",
|
||||||
|
"packs": [
|
||||||
|
"competences",
|
||||||
|
"maladie-poison-and-blessure",
|
||||||
|
"espece",
|
||||||
|
"talents-psioniques",
|
||||||
|
"carrieres"
|
||||||
|
],
|
||||||
|
"folders": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PNJ & Items",
|
||||||
|
"sorting": "c",
|
||||||
|
"color": "#00435c",
|
||||||
|
"packs": [
|
||||||
|
"tables-pnj",
|
||||||
|
"pnj",
|
||||||
|
"competences",
|
||||||
|
"maladie-poison-and-blessure",
|
||||||
|
"espece",
|
||||||
|
"talents-psioniques",
|
||||||
|
"carrieres"
|
||||||
|
],
|
||||||
|
"folders": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Journaux",
|
||||||
|
"sorting": "d",
|
||||||
|
"color": "#00435c",
|
||||||
|
"packs": [
|
||||||
|
"journal"
|
||||||
|
],
|
||||||
|
"folders": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packs": []
|
||||||
|
},
|
||||||
"packs": [
|
"packs": [
|
||||||
{
|
{
|
||||||
"name": "armures",
|
"name": "armures",
|
||||||
@@ -107,7 +175,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "journal",
|
"name": "journal",
|
||||||
"label": "Journal Psioniques",
|
"label": "Journal Psioniques",
|
||||||
"path": "packs/journal",
|
"path": "packs/journal",
|
||||||
"type": "JournalEntry",
|
"type": "JournalEntry",
|
||||||
"system": "mgt2e",
|
"system": "mgt2e",
|
||||||
@@ -159,6 +227,17 @@
|
|||||||
"PLAYER": "OBSERVER",
|
"PLAYER": "OBSERVER",
|
||||||
"ASSISTANT": "OWNER"
|
"ASSISTANT": "OWNER"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pnj",
|
||||||
|
"label": "PNJs",
|
||||||
|
"path": "packs/pnj",
|
||||||
|
"type": "Actor",
|
||||||
|
"system": "mgt2e",
|
||||||
|
"ownership": {
|
||||||
|
"PLAYER": "OBSERVER",
|
||||||
|
"ASSISTANT": "OWNER"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"type": "module",
|
||||||
|
"private": true
|
||||||
|
}
|
||||||
+1
-1
@@ -1 +1 @@
|
|||||||
MANIFEST-000075
|
MANIFEST-000132
|
||||||
|
|||||||
+8
-15
@@ -1,15 +1,8 @@
|
|||||||
2026/05/24-16:50:54.659414 7fdf5b7fe6c0 Recovering log #73
|
2026/06/12-20:52:58.023135 7fc410ffe6c0 Recovering log #130
|
||||||
2026/05/24-16:50:54.670747 7fdf5b7fe6c0 Delete type=3 #71
|
2026/06/12-20:52:58.033792 7fc410ffe6c0 Delete type=3 #128
|
||||||
2026/05/24-16:50:54.670783 7fdf5b7fe6c0 Delete type=0 #73
|
2026/06/12-20:52:58.033845 7fc410ffe6c0 Delete type=0 #130
|
||||||
2026/05/24-17:30:55.121088 7fdf5affd6c0 Level-0 table #78: started
|
2026/06/12-20:53:22.703483 7fc3c11be6c0 Level-0 table #135: started
|
||||||
2026/05/24-17:30:55.125303 7fdf5affd6c0 Level-0 table #78: 9864 bytes OK
|
2026/06/12-20:53:22.703533 7fc3c11be6c0 Level-0 table #135: 0 bytes OK
|
||||||
2026/05/24-17:30:55.131248 7fdf5affd6c0 Delete type=0 #76
|
2026/06/12-20:53:22.710309 7fc3c11be6c0 Delete type=0 #133
|
||||||
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/06/12-20:53:22.717089 7fc3c11be6c0 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/06/12-20:53:22.724385 7fc3c11be6c0 Manual compaction at level-1 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end)
|
||||||
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)
|
|
||||||
|
|||||||
+8
-8
@@ -1,8 +1,8 @@
|
|||||||
2026/05/18-20:27:59.998100 7f5a46ffd6c0 Recovering log #68
|
2026/06/12-20:51:44.594732 7fc3c3fff6c0 Recovering log #126
|
||||||
2026/05/18-20:28:00.055719 7f5a46ffd6c0 Delete type=3 #66
|
2026/06/12-20:51:44.605104 7fc3c3fff6c0 Delete type=3 #124
|
||||||
2026/05/18-20:28:00.055824 7f5a46ffd6c0 Delete type=0 #68
|
2026/06/12-20:51:44.605163 7fc3c3fff6c0 Delete type=0 #126
|
||||||
2026/05/18-20:29:47.499128 7f5a467fc6c0 Level-0 table #74: started
|
2026/06/12-20:52:05.597166 7fc3c11be6c0 Level-0 table #131: started
|
||||||
2026/05/18-20:29:47.499199 7f5a467fc6c0 Level-0 table #74: 0 bytes OK
|
2026/06/12-20:52:05.597184 7fc3c11be6c0 Level-0 table #131: 0 bytes OK
|
||||||
2026/05/18-20:29:47.505640 7f5a467fc6c0 Delete type=0 #72
|
2026/06/12-20:52:05.604422 7fc3c11be6c0 Delete type=0 #129
|
||||||
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/06/12-20:52:05.618860 7fc3c11be6c0 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/06/12-20:52:05.619120 7fc3c11be6c0 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-000231
|
||||||
|
|||||||
+8
-15
@@ -1,15 +1,8 @@
|
|||||||
2026/05/24-16:50:54.546518 7fdf5b7fe6c0 Recovering log #172
|
2026/06/12-20:52:57.899810 7fc3c37fe6c0 Recovering log #229
|
||||||
2026/05/24-16:50:54.556572 7fdf5b7fe6c0 Delete type=3 #170
|
2026/06/12-20:52:57.911135 7fc3c37fe6c0 Delete type=3 #227
|
||||||
2026/05/24-16:50:54.556614 7fdf5b7fe6c0 Delete type=0 #172
|
2026/06/12-20:52:57.911180 7fc3c37fe6c0 Delete type=0 #229
|
||||||
2026/05/24-17:30:54.977322 7fdf5affd6c0 Level-0 table #177: started
|
2026/06/12-20:53:22.642554 7fc3c11be6c0 Level-0 table #234: started
|
||||||
2026/05/24-17:30:54.980526 7fdf5affd6c0 Level-0 table #177: 3210 bytes OK
|
2026/06/12-20:53:22.642590 7fc3c11be6c0 Level-0 table #234: 0 bytes OK
|
||||||
2026/05/24-17:30:54.986935 7fdf5affd6c0 Delete type=0 #175
|
2026/06/12-20:53:22.648801 7fc3c11be6c0 Delete type=0 #232
|
||||||
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/06/12-20:53:22.649072 7fc3c11be6c0 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/06/12-20:53:22.649091 7fc3c11be6c0 Manual compaction at level-1 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end)
|
||||||
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)
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/18-20:27:59.309876 7f5a477fe6c0 Recovering log #167
|
2026/06/12-20:51:44.470883 7fc3c37fe6c0 Recovering log #225
|
||||||
2026/05/18-20:27:59.371762 7f5a477fe6c0 Delete type=3 #165
|
2026/06/12-20:51:44.481326 7fc3c37fe6c0 Delete type=3 #223
|
||||||
2026/05/18-20:27:59.371874 7f5a477fe6c0 Delete type=0 #167
|
2026/06/12-20:51:44.481382 7fc3c37fe6c0 Delete type=0 #225
|
||||||
2026/05/18-20:29:47.416279 7f5a467fc6c0 Level-0 table #173: started
|
2026/06/12-20:52:05.534179 7fc3c11be6c0 Level-0 table #230: started
|
||||||
2026/05/18-20:29:47.416343 7f5a467fc6c0 Level-0 table #173: 0 bytes OK
|
2026/06/12-20:52:05.534214 7fc3c11be6c0 Level-0 table #230: 0 bytes OK
|
||||||
2026/05/18-20:29:47.425598 7f5a467fc6c0 Delete type=0 #171
|
2026/06/12-20:52:05.540979 7fc3c11be6c0 Delete type=0 #228
|
||||||
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/06/12-20:52:05.541333 7fc3c11be6c0 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/06/12-20:52:05.562210 7fc3c11be6c0 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-000115
|
||||||
|
|||||||
+8
-15
@@ -1,15 +1,8 @@
|
|||||||
2026/05/24-16:50:54.686699 7fdf5bfff6c0 Recovering log #56
|
2026/06/12-20:52:58.051634 7fc410ffe6c0 Recovering log #113
|
||||||
2026/05/24-16:50:54.695982 7fdf5bfff6c0 Delete type=3 #54
|
2026/06/12-20:52:58.062421 7fc410ffe6c0 Delete type=3 #111
|
||||||
2026/05/24-16:50:54.696016 7fdf5bfff6c0 Delete type=0 #56
|
2026/06/12-20:52:58.062480 7fc410ffe6c0 Delete type=0 #113
|
||||||
2026/05/24-17:30:55.150957 7fdf5affd6c0 Level-0 table #61: started
|
2026/06/12-20:53:22.717098 7fc3c11be6c0 Level-0 table #118: started
|
||||||
2026/05/24-17:30:55.154345 7fdf5affd6c0 Level-0 table #61: 4273 bytes OK
|
2026/06/12-20:53:22.717122 7fc3c11be6c0 Level-0 table #118: 0 bytes OK
|
||||||
2026/05/24-17:30:55.160772 7fdf5affd6c0 Delete type=0 #59
|
2026/06/12-20:53:22.724248 7fc3c11be6c0 Delete type=0 #116
|
||||||
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/06/12-20:53:22.822698 7fc3c11be6c0 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/06/12-20:53:22.829756 7fc3c11be6c0 Manual compaction at level-1 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end)
|
||||||
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)
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/18-20:28:00.170623 7f5a477fe6c0 Recovering log #51
|
2026/06/12-20:51:44.625465 7fc3c37fe6c0 Recovering log #109
|
||||||
2026/05/18-20:28:00.231017 7f5a477fe6c0 Delete type=3 #49
|
2026/06/12-20:51:44.635539 7fc3c37fe6c0 Delete type=3 #107
|
||||||
2026/05/18-20:28:00.231120 7f5a477fe6c0 Delete type=0 #51
|
2026/06/12-20:51:44.635595 7fc3c37fe6c0 Delete type=0 #109
|
||||||
2026/05/18-20:29:47.515216 7f5a467fc6c0 Level-0 table #57: started
|
2026/06/12-20:52:05.611307 7fc3c11be6c0 Level-0 table #114: started
|
||||||
2026/05/18-20:29:47.515280 7f5a467fc6c0 Level-0 table #57: 0 bytes OK
|
2026/06/12-20:52:05.611325 7fc3c11be6c0 Level-0 table #114: 0 bytes OK
|
||||||
2026/05/18-20:29:47.522604 7f5a467fc6c0 Delete type=0 #55
|
2026/06/12-20:52:05.618732 7fc3c11be6c0 Delete type=0 #112
|
||||||
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/06/12-20:52:05.619090 7fc3c11be6c0 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/06/12-20:52:05.619279 7fc3c11be6c0 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-000234
|
||||||
|
|||||||
+8
-15
@@ -1,15 +1,8 @@
|
|||||||
2026/05/24-16:50:54.559278 7fdf5bfff6c0 Recovering log #175
|
2026/06/12-20:52:57.913632 7fc410ffe6c0 Recovering log #232
|
||||||
2026/05/24-16:50:54.569235 7fdf5bfff6c0 Delete type=3 #173
|
2026/06/12-20:52:57.923055 7fc410ffe6c0 Delete type=3 #230
|
||||||
2026/05/24-16:50:54.569273 7fdf5bfff6c0 Delete type=0 #175
|
2026/06/12-20:52:57.923103 7fc410ffe6c0 Delete type=0 #232
|
||||||
2026/05/24-17:30:54.996524 7fdf5affd6c0 Level-0 table #180: started
|
2026/06/12-20:53:22.662427 7fc3c11be6c0 Level-0 table #237: started
|
||||||
2026/05/24-17:30:55.000122 7fdf5affd6c0 Level-0 table #180: 30255 bytes OK
|
2026/06/12-20:53:22.662456 7fc3c11be6c0 Level-0 table #237: 0 bytes OK
|
||||||
2026/05/24-17:30:55.006363 7fdf5affd6c0 Delete type=0 #178
|
2026/06/12-20:53:22.669591 7fc3c11be6c0 Delete type=0 #235
|
||||||
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/06/12-20:53:22.676441 7fc3c11be6c0 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/06/12-20:53:22.676637 7fc3c11be6c0 Manual compaction at level-1 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end)
|
||||||
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)
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/18-20:27:59.380990 7f5a47fff6c0 Recovering log #170
|
2026/06/12-20:51:44.484787 7fc410ffe6c0 Recovering log #228
|
||||||
2026/05/18-20:27:59.439837 7f5a47fff6c0 Delete type=3 #168
|
2026/06/12-20:51:44.494717 7fc410ffe6c0 Delete type=3 #226
|
||||||
2026/05/18-20:27:59.439975 7f5a47fff6c0 Delete type=0 #170
|
2026/06/12-20:51:44.494769 7fc410ffe6c0 Delete type=0 #228
|
||||||
2026/05/18-20:29:47.426912 7f5a467fc6c0 Level-0 table #176: started
|
2026/06/12-20:52:05.541367 7fc3c11be6c0 Level-0 table #233: started
|
||||||
2026/05/18-20:29:47.426974 7f5a467fc6c0 Level-0 table #176: 0 bytes OK
|
2026/06/12-20:52:05.541402 7fc3c11be6c0 Level-0 table #233: 0 bytes OK
|
||||||
2026/05/18-20:29:47.437378 7f5a467fc6c0 Delete type=0 #174
|
2026/06/12-20:52:05.548343 7fc3c11be6c0 Delete type=0 #231
|
||||||
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/06/12-20:52:05.562225 7fc3c11be6c0 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/06/12-20:52:05.562411 7fc3c11be6c0 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-000168
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2026/05/24-16:50:54.623242 7fdf5bfff6c0 Recovering log #110
|
2026/06/12-20:52:57.981469 7fc410ffe6c0 Recovering log #166
|
||||||
2026/05/24-16:50:54.633193 7fdf5bfff6c0 Delete type=3 #108
|
2026/06/12-20:52:57.992240 7fc410ffe6c0 Delete type=3 #164
|
||||||
2026/05/24-16:50:54.633252 7fdf5bfff6c0 Delete type=0 #110
|
2026/06/12-20:52:57.992286 7fc410ffe6c0 Delete type=0 #166
|
||||||
2026/05/24-17:30:55.081463 7fdf5affd6c0 Level-0 table #115: started
|
2026/06/12-20:53:22.676645 7fc3c11be6c0 Level-0 table #171: started
|
||||||
2026/05/24-17:30:55.081487 7fdf5affd6c0 Level-0 table #115: 0 bytes OK
|
2026/06/12-20:53:22.676670 7fc3c11be6c0 Level-0 table #171: 0 bytes OK
|
||||||
2026/05/24-17:30:55.087560 7fdf5affd6c0 Delete type=0 #113
|
2026/06/12-20:53:22.683046 7fc3c11be6c0 Delete type=0 #169
|
||||||
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/06/12-20:53:22.703339 7fc3c11be6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2026/05/18-20:27:59.766699 7f5a47fff6c0 Recovering log #106
|
2026/06/12-20:51:44.553830 7fc3c37fe6c0 Recovering log #162
|
||||||
2026/05/18-20:27:59.824574 7f5a47fff6c0 Delete type=3 #104
|
2026/06/12-20:51:44.563465 7fc3c37fe6c0 Delete type=3 #160
|
||||||
2026/05/18-20:27:59.824666 7f5a47fff6c0 Delete type=0 #106
|
2026/06/12-20:51:44.563519 7fc3c37fe6c0 Delete type=0 #162
|
||||||
2026/05/18-20:29:47.472932 7f5a467fc6c0 Level-0 table #111: started
|
2026/06/12-20:52:05.576474 7fc3c11be6c0 Level-0 table #167: started
|
||||||
2026/05/18-20:29:47.473002 7f5a467fc6c0 Level-0 table #111: 0 bytes OK
|
2026/06/12-20:52:05.576496 7fc3c11be6c0 Level-0 table #167: 0 bytes OK
|
||||||
2026/05/18-20:29:47.479740 7f5a467fc6c0 Delete type=0 #109
|
2026/06/12-20:52:05.583147 7fc3c11be6c0 Delete type=0 #165
|
||||||
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/06/12-20:52:05.590265 7fc3c11be6c0 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-000176
|
||||||
|
|||||||
+8
-15
@@ -1,15 +1,8 @@
|
|||||||
2026/05/24-16:50:54.597446 7fdf5b7fe6c0 Recovering log #117
|
2026/06/12-20:52:57.954396 7fc410ffe6c0 Recovering log #174
|
||||||
2026/05/24-16:50:54.607335 7fdf5b7fe6c0 Delete type=3 #115
|
2026/06/12-20:52:57.964106 7fc410ffe6c0 Delete type=3 #172
|
||||||
2026/05/24-16:50:54.607379 7fdf5b7fe6c0 Delete type=0 #117
|
2026/06/12-20:52:57.964146 7fc410ffe6c0 Delete type=0 #174
|
||||||
2026/05/24-17:30:55.052370 7fdf5affd6c0 Level-0 table #122: started
|
2026/06/12-20:53:22.669694 7fc3c11be6c0 Level-0 table #179: started
|
||||||
2026/05/24-17:30:55.056355 7fdf5affd6c0 Level-0 table #122: 20248 bytes OK
|
2026/06/12-20:53:22.669720 7fc3c11be6c0 Level-0 table #179: 0 bytes OK
|
||||||
2026/05/24-17:30:55.062721 7fdf5affd6c0 Delete type=0 #120
|
2026/06/12-20:53:22.676299 7fc3c11be6c0 Delete type=0 #177
|
||||||
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/06/12-20:53:22.676452 7fc3c11be6c0 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/06/12-20:53:22.676473 7fc3c11be6c0 Manual compaction at level-1 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end)
|
||||||
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)
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/18-20:27:59.596317 7f5a47fff6c0 Recovering log #112
|
2026/06/12-20:51:44.524826 7fc3c37fe6c0 Recovering log #170
|
||||||
2026/05/18-20:27:59.649784 7f5a47fff6c0 Delete type=3 #110
|
2026/06/12-20:51:44.535451 7fc3c37fe6c0 Delete type=3 #168
|
||||||
2026/05/18-20:27:59.649914 7f5a47fff6c0 Delete type=0 #112
|
2026/06/12-20:51:44.535506 7fc3c37fe6c0 Delete type=0 #170
|
||||||
2026/05/18-20:29:47.456954 7f5a467fc6c0 Level-0 table #118: started
|
2026/06/12-20:52:05.562430 7fc3c11be6c0 Level-0 table #175: started
|
||||||
2026/05/18-20:29:47.457019 7f5a467fc6c0 Level-0 table #118: 0 bytes OK
|
2026/06/12-20:52:05.562454 7fc3c11be6c0 Level-0 table #175: 0 bytes OK
|
||||||
2026/05/18-20:29:47.463638 7f5a467fc6c0 Delete type=0 #116
|
2026/06/12-20:52:05.569802 7fc3c11be6c0 Delete type=0 #173
|
||||||
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/06/12-20:52:05.590244 7fc3c11be6c0 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/06/12-20:52:05.590432 7fc3c11be6c0 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-000168
|
||||||
|
|||||||
+7
-7
@@ -1,7 +1,7 @@
|
|||||||
2026/05/24-16:50:54.634756 7fdfa8dfe6c0 Recovering log #110
|
2026/06/12-20:52:57.995403 7fc3c37fe6c0 Recovering log #166
|
||||||
2026/05/24-16:50:54.645375 7fdfa8dfe6c0 Delete type=3 #108
|
2026/06/12-20:52:58.005793 7fc3c37fe6c0 Delete type=3 #164
|
||||||
2026/05/24-16:50:54.645440 7fdfa8dfe6c0 Delete type=0 #110
|
2026/06/12-20:52:58.005851 7fc3c37fe6c0 Delete type=0 #166
|
||||||
2026/05/24-17:30:55.097965 7fdf5affd6c0 Level-0 table #115: started
|
2026/06/12-20:53:22.683153 7fc3c11be6c0 Level-0 table #171: started
|
||||||
2026/05/24-17:30:55.097985 7fdf5affd6c0 Level-0 table #115: 0 bytes OK
|
2026/06/12-20:53:22.683178 7fc3c11be6c0 Level-0 table #171: 0 bytes OK
|
||||||
2026/05/24-17:30:55.104346 7fdf5affd6c0 Delete type=0 #113
|
2026/06/12-20:53:22.690078 7fc3c11be6c0 Delete type=0 #169
|
||||||
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/06/12-20:53:22.703354 7fc3c11be6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
2026/05/18-20:27:59.838862 7f5a46ffd6c0 Recovering log #106
|
2026/06/12-20:51:44.566595 7fc410ffe6c0 Recovering log #162
|
||||||
2026/05/18-20:27:59.893943 7f5a46ffd6c0 Delete type=3 #104
|
2026/06/12-20:51:44.577675 7fc410ffe6c0 Delete type=3 #160
|
||||||
2026/05/18-20:27:59.894068 7f5a46ffd6c0 Delete type=0 #106
|
2026/06/12-20:51:44.577730 7fc410ffe6c0 Delete type=0 #162
|
||||||
2026/05/18-20:29:47.480953 7f5a467fc6c0 Level-0 table #111: started
|
2026/06/12-20:52:05.583251 7fc3c11be6c0 Level-0 table #167: started
|
||||||
2026/05/18-20:29:47.481012 7f5a467fc6c0 Level-0 table #111: 0 bytes OK
|
2026/06/12-20:52:05.583269 7fc3c11be6c0 Level-0 table #167: 0 bytes OK
|
||||||
2026/05/18-20:29:47.487588 7f5a467fc6c0 Delete type=0 #109
|
2026/06/12-20:52:05.590142 7fc3c11be6c0 Delete type=0 #165
|
||||||
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/06/12-20:52:05.590369 7fc3c11be6c0 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-000149
|
||||||
|
|||||||
+8
-8
@@ -1,8 +1,8 @@
|
|||||||
2026/05/24-16:50:54.647193 7fdf5b7fe6c0 Recovering log #91
|
2026/06/12-20:52:58.008917 7fc3c3fff6c0 Recovering log #147
|
||||||
2026/05/24-16:50:54.656827 7fdf5b7fe6c0 Delete type=3 #89
|
2026/06/12-20:52:58.019530 7fc3c3fff6c0 Delete type=3 #145
|
||||||
2026/05/24-16:50:54.656870 7fdf5b7fe6c0 Delete type=0 #91
|
2026/06/12-20:52:58.019585 7fc3c3fff6c0 Delete type=0 #147
|
||||||
2026/05/24-17:30:55.114778 7fdf5affd6c0 Level-0 table #96: started
|
2026/06/12-20:53:22.690192 7fc3c11be6c0 Level-0 table #152: started
|
||||||
2026/05/24-17:30:55.114807 7fdf5affd6c0 Level-0 table #96: 0 bytes OK
|
2026/06/12-20:53:22.690216 7fc3c11be6c0 Level-0 table #152: 0 bytes OK
|
||||||
2026/05/24-17:30:55.121001 7fdf5affd6c0 Delete type=0 #94
|
2026/06/12-20:53:22.696652 7fc3c11be6c0 Delete type=0 #150
|
||||||
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/06/12-20:53:22.703367 7fc3c11be6c0 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/06/12-20:53:22.717074 7fc3c11be6c0 Manual compaction at level-1 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/18-20:27:59.909714 7f5a477fe6c0 Recovering log #87
|
2026/06/12-20:51:44.581216 7fc3c37fe6c0 Recovering log #143
|
||||||
2026/05/18-20:27:59.963841 7f5a477fe6c0 Delete type=3 #85
|
2026/06/12-20:51:44.591542 7fc3c37fe6c0 Delete type=3 #141
|
||||||
2026/05/18-20:27:59.963963 7f5a477fe6c0 Delete type=0 #87
|
2026/06/12-20:51:44.591593 7fc3c37fe6c0 Delete type=0 #143
|
||||||
2026/05/18-20:29:47.488790 7f5a467fc6c0 Level-0 table #92: started
|
2026/06/12-20:52:05.590481 7fc3c11be6c0 Level-0 table #148: started
|
||||||
2026/05/18-20:29:47.488852 7f5a467fc6c0 Level-0 table #92: 0 bytes OK
|
2026/06/12-20:52:05.590505 7fc3c11be6c0 Level-0 table #148: 0 bytes OK
|
||||||
2026/05/18-20:29:47.496720 7f5a467fc6c0 Delete type=0 #90
|
2026/06/12-20:52:05.597069 7fc3c11be6c0 Delete type=0 #146
|
||||||
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/06/12-20:52:05.618846 7fc3c11be6c0 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/06/12-20:52:05.619137 7fc3c11be6c0 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-000176
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
2026/05/24-16:50:54.573746 7fdf5bfff6c0 Recovering log #117
|
2026/06/12-20:52:57.926432 7fc3c37fe6c0 Recovering log #174
|
||||||
2026/05/24-16:50:54.583876 7fdf5bfff6c0 Delete type=3 #115
|
2026/06/12-20:52:57.936438 7fc3c37fe6c0 Delete type=3 #172
|
||||||
2026/05/24-16:50:54.583933 7fdf5bfff6c0 Delete type=0 #117
|
2026/06/12-20:52:57.936485 7fc3c37fe6c0 Delete type=0 #174
|
||||||
2026/05/24-17:30:55.026316 7fdf5affd6c0 Level-0 table #122: started
|
2026/06/12-20:53:22.649158 7fc3c11be6c0 Level-0 table #179: started
|
||||||
2026/05/24-17:30:55.029368 7fdf5affd6c0 Level-0 table #122: 1988 bytes OK
|
2026/06/12-20:53:22.649178 7fc3c11be6c0 Level-0 table #179: 0 bytes OK
|
||||||
2026/05/24-17:30:55.035351 7fdf5affd6c0 Delete type=0 #120
|
2026/06/12-20:53:22.655504 7fc3c11be6c0 Delete type=0 #177
|
||||||
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/06/12-20:53:22.676414 7fc3c11be6c0 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/06/12-20:53:22.676616 7fc3c11be6c0 Manual compaction at level-1 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end)
|
||||||
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)
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/18-20:27:59.461881 7f5a46ffd6c0 Recovering log #112
|
2026/06/12-20:51:44.497744 7fc3c3fff6c0 Recovering log #170
|
||||||
2026/05/18-20:27:59.517849 7f5a46ffd6c0 Delete type=3 #110
|
2026/06/12-20:51:44.508610 7fc3c3fff6c0 Delete type=3 #168
|
||||||
2026/05/18-20:27:59.517983 7f5a46ffd6c0 Delete type=0 #112
|
2026/06/12-20:51:44.508662 7fc3c3fff6c0 Delete type=0 #170
|
||||||
2026/05/18-20:29:47.438640 7f5a467fc6c0 Level-0 table #118: started
|
2026/06/12-20:52:05.548467 7fc3c11be6c0 Level-0 table #175: started
|
||||||
2026/05/18-20:29:47.438693 7f5a467fc6c0 Level-0 table #118: 0 bytes OK
|
2026/06/12-20:52:05.548492 7fc3c11be6c0 Level-0 table #175: 0 bytes OK
|
||||||
2026/05/18-20:29:47.445247 7f5a467fc6c0 Delete type=0 #116
|
2026/06/12-20:52:05.555571 7fc3c11be6c0 Delete type=0 #173
|
||||||
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/06/12-20:52:05.562235 7fc3c11be6c0 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/06/12-20:52:05.562402 7fc3c11be6c0 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-000168
|
||||||
|
|||||||
+7
-7
@@ -1,7 +1,7 @@
|
|||||||
2026/05/24-16:50:54.586100 7fdfa8dfe6c0 Recovering log #110
|
2026/06/12-20:52:57.939847 7fc4117ff6c0 Recovering log #166
|
||||||
2026/05/24-16:50:54.595631 7fdfa8dfe6c0 Delete type=3 #108
|
2026/06/12-20:52:57.951112 7fc4117ff6c0 Delete type=3 #164
|
||||||
2026/05/24-16:50:54.595694 7fdfa8dfe6c0 Delete type=0 #110
|
2026/06/12-20:52:57.951166 7fc4117ff6c0 Delete type=0 #166
|
||||||
2026/05/24-17:30:55.035470 7fdf5affd6c0 Level-0 table #115: started
|
2026/06/12-20:53:22.655606 7fc3c11be6c0 Level-0 table #171: started
|
||||||
2026/05/24-17:30:55.035483 7fdf5affd6c0 Level-0 table #115: 0 bytes OK
|
2026/06/12-20:53:22.655631 7fc3c11be6c0 Level-0 table #171: 0 bytes OK
|
||||||
2026/05/24-17:30:55.041734 7fdf5affd6c0 Delete type=0 #113
|
2026/06/12-20:53:22.662292 7fc3c11be6c0 Delete type=0 #169
|
||||||
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/06/12-20:53:22.676430 7fc3c11be6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
+7
-7
@@ -1,7 +1,7 @@
|
|||||||
2026/05/18-20:27:59.526598 7f5a94bff6c0 Recovering log #106
|
2026/06/12-20:51:44.511682 7fc410ffe6c0 Recovering log #162
|
||||||
2026/05/18-20:27:59.589070 7f5a94bff6c0 Delete type=3 #104
|
2026/06/12-20:51:44.521666 7fc410ffe6c0 Delete type=3 #160
|
||||||
2026/05/18-20:27:59.589252 7f5a94bff6c0 Delete type=0 #106
|
2026/06/12-20:51:44.521720 7fc410ffe6c0 Delete type=0 #162
|
||||||
2026/05/18-20:29:47.448116 7f5a467fc6c0 Level-0 table #111: started
|
2026/06/12-20:52:05.555691 7fc3c11be6c0 Level-0 table #167: started
|
||||||
2026/05/18-20:29:47.448187 7f5a467fc6c0 Level-0 table #111: 0 bytes OK
|
2026/06/12-20:52:05.555716 7fc3c11be6c0 Level-0 table #167: 0 bytes OK
|
||||||
2026/05/18-20:29:47.455433 7f5a467fc6c0 Delete type=0 #109
|
2026/06/12-20:52:05.562097 7fc3c11be6c0 Delete type=0 #165
|
||||||
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/06/12-20:52:05.562305 7fc3c11be6c0 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-000176
|
||||||
|
|||||||
+8
-15
@@ -1,15 +1,8 @@
|
|||||||
2026/05/24-16:50:54.610807 7fdfa95ff6c0 Recovering log #117
|
2026/06/12-20:52:57.967451 7fc4117ff6c0 Recovering log #174
|
||||||
2026/05/24-16:50:54.620344 7fdfa95ff6c0 Delete type=3 #115
|
2026/06/12-20:52:57.978196 7fc4117ff6c0 Delete type=3 #172
|
||||||
2026/05/24-16:50:54.620394 7fdfa95ff6c0 Delete type=0 #117
|
2026/06/12-20:52:57.978251 7fc4117ff6c0 Delete type=0 #174
|
||||||
2026/05/24-17:30:55.071908 7fdf5affd6c0 Level-0 table #122: started
|
2026/06/12-20:53:22.696764 7fc3c11be6c0 Level-0 table #179: started
|
||||||
2026/05/24-17:30:55.075519 7fdf5affd6c0 Level-0 table #122: 5396 bytes OK
|
2026/06/12-20:53:22.696789 7fc3c11be6c0 Level-0 table #179: 0 bytes OK
|
||||||
2026/05/24-17:30:55.081331 7fdf5affd6c0 Delete type=0 #120
|
2026/06/12-20:53:22.703214 7fc3c11be6c0 Delete type=0 #177
|
||||||
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/06/12-20:53:22.703379 7fc3c11be6c0 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/06/12-20:53:22.710437 7fc3c11be6c0 Manual compaction at level-1 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end)
|
||||||
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)
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/18-20:27:59.682456 7f5a46ffd6c0 Recovering log #112
|
2026/06/12-20:51:44.538990 7fc410ffe6c0 Recovering log #170
|
||||||
2026/05/18-20:27:59.750017 7f5a46ffd6c0 Delete type=3 #110
|
2026/06/12-20:51:44.550083 7fc410ffe6c0 Delete type=3 #168
|
||||||
2026/05/18-20:27:59.750123 7f5a46ffd6c0 Delete type=0 #112
|
2026/06/12-20:51:44.550141 7fc410ffe6c0 Delete type=0 #170
|
||||||
2026/05/18-20:29:47.464929 7f5a467fc6c0 Level-0 table #118: started
|
2026/06/12-20:52:05.569908 7fc3c11be6c0 Level-0 table #175: started
|
||||||
2026/05/18-20:29:47.465002 7f5a467fc6c0 Level-0 table #118: 0 bytes OK
|
2026/06/12-20:52:05.569929 7fc3c11be6c0 Level-0 table #175: 0 bytes OK
|
||||||
2026/05/18-20:29:47.471758 7f5a467fc6c0 Delete type=0 #116
|
2026/06/12-20:52:05.576375 7fc3c11be6c0 Delete type=0 #173
|
||||||
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/06/12-20:52:05.590256 7fc3c11be6c0 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/06/12-20:52:05.590425 7fc3c11be6c0 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.
@@ -0,0 +1 @@
|
|||||||
|
MANIFEST-000014
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
2026/06/12-20:52:58.209985 7fc3c3fff6c0 Recovering log #12
|
||||||
|
2026/06/12-20:52:58.222276 7fc3c3fff6c0 Delete type=3 #10
|
||||||
|
2026/06/12-20:52:58.222315 7fc3c3fff6c0 Delete type=0 #12
|
||||||
|
2026/06/12-20:53:22.822733 7fc3c11be6c0 Level-0 table #17: started
|
||||||
|
2026/06/12-20:53:22.822761 7fc3c11be6c0 Level-0 table #17: 0 bytes OK
|
||||||
|
2026/06/12-20:53:22.829641 7fc3c11be6c0 Delete type=0 #15
|
||||||
|
2026/06/12-20:53:22.836426 7fc3c11be6c0 Manual compaction at level-0 from '!actors!DL3zXfXxWMOQhXJY' @ 72057594037927935 : 1 .. '!actors.items!JAHYXFedTxNlGYX8.nmIgA3CQkOSEpCSP' @ 0 : 0; will stop at (end)
|
||||||
|
2026/06/12-20:53:22.960032 7fc3c11be6c0 Manual compaction at level-1 from '!actors!DL3zXfXxWMOQhXJY' @ 72057594037927935 : 1 .. '!actors.items!JAHYXFedTxNlGYX8.nmIgA3CQkOSEpCSP' @ 0 : 0; will stop at (end)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
2026/06/12-20:51:44.772467 7fc3c37fe6c0 Recovering log #8
|
||||||
|
2026/06/12-20:51:44.784739 7fc3c37fe6c0 Delete type=3 #6
|
||||||
|
2026/06/12-20:51:44.784799 7fc3c37fe6c0 Delete type=0 #8
|
||||||
|
2026/06/12-20:52:05.700099 7fc3c11be6c0 Level-0 table #13: started
|
||||||
|
2026/06/12-20:52:05.700122 7fc3c11be6c0 Level-0 table #13: 0 bytes OK
|
||||||
|
2026/06/12-20:52:05.707051 7fc3c11be6c0 Delete type=0 #11
|
||||||
|
2026/06/12-20:52:05.714022 7fc3c11be6c0 Manual compaction at level-0 from '!actors!DL3zXfXxWMOQhXJY' @ 72057594037927935 : 1 .. '!actors.items!JAHYXFedTxNlGYX8.nmIgA3CQkOSEpCSP' @ 0 : 0; will stop at (end)
|
||||||
|
2026/06/12-20:52:05.714257 7fc3c11be6c0 Manual compaction at level-1 from '!actors!DL3zXfXxWMOQhXJY' @ 72057594037927935 : 1 .. '!actors.items!JAHYXFedTxNlGYX8.nmIgA3CQkOSEpCSP' @ 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.
@@ -1 +1 @@
|
|||||||
MANIFEST-000021
|
MANIFEST-000291
|
||||||
|
|||||||
+19
-50
@@ -1,50 +1,19 @@
|
|||||||
2026/05/24-16:50:54.697724 7fdfa8dfe6c0 Recovering log #18
|
2026/06/12-20:52:58.065673 7fc3c3fff6c0 Recovering log #286
|
||||||
2026/05/24-16:50:54.707598 7fdfa8dfe6c0 Delete type=3 #16
|
2026/06/12-20:52:58.076152 7fc3c3fff6c0 Delete type=3 #284
|
||||||
2026/05/24-16:50:54.707634 7fdfa8dfe6c0 Delete type=0 #18
|
2026/06/12-20:52:58.076196 7fc3c3fff6c0 Delete type=0 #286
|
||||||
2026/05/24-17:00:32.390913 7fdf5affd6c0 Level-0 table #24: started
|
2026/06/12-20:53:22.724394 7fc3c11be6c0 Level-0 table #294: started
|
||||||
2026/05/24-17:00:32.409568 7fdf5affd6c0 Level-0 table #24: 1225651 bytes OK
|
2026/06/12-20:53:22.813324 7fc3c11be6c0 Level-0 table #294: 5029605 bytes OK
|
||||||
2026/05/24-17:00:32.416341 7fdf5affd6c0 Delete type=0 #22
|
2026/06/12-20:53:22.820890 7fc3c11be6c0 Delete type=0 #292
|
||||||
2026/05/24-17:03:56.805332 7fdf5affd6c0 Level-0 table #26: started
|
2026/06/12-20:53:22.829771 7fc3c11be6c0 Manual compaction at level-0 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at (end)
|
||||||
2026/05/24-17:03:56.824851 7fdf5affd6c0 Level-0 table #26: 1099794 bytes OK
|
2026/06/12-20:53:22.836453 7fc3c11be6c0 Manual compaction at level-1 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 1617840 : 1
|
||||||
2026/05/24-17:03:56.831981 7fdf5affd6c0 Delete type=0 #23
|
2026/06/12-20:53:22.836460 7fc3c11be6c0 Compacting 1@1 + 3@2 files
|
||||||
2026/05/24-17:12:00.516549 7fdf5affd6c0 Level-0 table #28: started
|
2026/06/12-20:53:22.873874 7fc3c11be6c0 Generated table #295@1: 10562 keys, 2142429 bytes
|
||||||
2026/05/24-17:12:00.550743 7fdf5affd6c0 Level-0 table #28: 1268131 bytes OK
|
2026/06/12-20:53:22.924542 7fc3c11be6c0 Generated table #296@1: 16816 keys, 2164017 bytes
|
||||||
2026/05/24-17:12:00.582730 7fdf5affd6c0 Delete type=0 #25
|
2026/06/12-20:53:22.944331 7fc3c11be6c0 Generated table #297@1: 5500 keys, 721026 bytes
|
||||||
2026/05/24-17:16:09.186372 7fdf5affd6c0 Level-0 table #30: started
|
2026/06/12-20:53:22.944361 7fc3c11be6c0 Compacted 1@1 + 3@2 files => 5027472 bytes
|
||||||
2026/05/24-17:16:09.219830 7fdf5affd6c0 Level-0 table #30: 1432074 bytes OK
|
2026/06/12-20:53:22.951445 7fc3c11be6c0 compacted to: files[ 0 0 3 0 0 0 0 ]
|
||||||
2026/05/24-17:16:09.258561 7fdf5affd6c0 Delete type=0 #27
|
2026/06/12-20:53:22.951933 7fc3c11be6c0 Delete type=2 #288
|
||||||
2026/05/24-17:18:30.349891 7fdf5affd6c0 Level-0 table #32: started
|
2026/06/12-20:53:22.952420 7fc3c11be6c0 Delete type=2 #289
|
||||||
2026/05/24-17:18:30.379065 7fdf5affd6c0 Level-0 table #32: 1608171 bytes OK
|
2026/06/12-20:53:22.952708 7fc3c11be6c0 Delete type=2 #290
|
||||||
2026/05/24-17:18:30.388404 7fdf5affd6c0 Delete type=0 #29
|
2026/06/12-20:53:22.953103 7fc3c11be6c0 Delete type=2 #294
|
||||||
2026/05/24-17:18:30.388776 7fdf5affd6c0 Compacting 4@0 + 1@1 files
|
2026/06/12-20:53:22.960065 7fc3c11be6c0 Manual compaction at level-1 from '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 1617840 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at (end)
|
||||||
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)
|
|
||||||
|
|||||||
+19
-15
@@ -1,15 +1,19 @@
|
|||||||
2026/05/18-20:28:00.247967 7f5a94bff6c0 Recovering log #13
|
2026/06/12-20:51:44.638713 7fc410ffe6c0 Recovering log #279
|
||||||
2026/05/18-20:28:00.322431 7f5a94bff6c0 Delete type=3 #11
|
2026/06/12-20:51:44.649235 7fc410ffe6c0 Delete type=3 #277
|
||||||
2026/05/18-20:28:00.322531 7f5a94bff6c0 Delete type=0 #13
|
2026/06/12-20:51:44.649296 7fc410ffe6c0 Delete type=0 #279
|
||||||
2026/05/18-20:29:47.524056 7f5a467fc6c0 Level-0 table #19: started
|
2026/06/12-20:52:05.627084 7fc3c11be6c0 Level-0 table #287: started
|
||||||
2026/05/18-20:29:47.536717 7f5a467fc6c0 Level-0 table #19: 417426 bytes OK
|
2026/06/12-20:52:05.691136 7fc3c11be6c0 Level-0 table #287: 4971536 bytes OK
|
||||||
2026/05/18-20:29:47.544323 7f5a467fc6c0 Delete type=0 #17
|
2026/06/12-20:52:05.698145 7fc3c11be6c0 Delete type=0 #285
|
||||||
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/06/12-20:52:05.714008 7fc3c11be6c0 Manual compaction at level-0 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 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/06/12-20:52:05.714299 7fc3c11be6c0 Manual compaction at level-1 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 1585250 : 1
|
||||||
2026/05/18-20:29:47.545064 7f5a467fc6c0 Compacting 1@1 + 1@2 files
|
2026/06/12-20:52:05.714307 7fc3c11be6c0 Compacting 1@1 + 3@2 files
|
||||||
2026/05/18-20:29:47.560513 7f5a467fc6c0 Generated table #20@1: 2998 keys, 417426 bytes
|
2026/06/12-20:52:05.744344 7fc3c11be6c0 Generated table #288@1: 10660 keys, 2142130 bytes
|
||||||
2026/05/18-20:29:47.560554 7f5a467fc6c0 Compacted 1@1 + 1@2 files => 417426 bytes
|
2026/06/12-20:52:05.781422 7fc3c11be6c0 Generated table #289@1: 16841 keys, 2165292 bytes
|
||||||
2026/05/18-20:29:47.567200 7f5a467fc6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
|
2026/06/12-20:52:05.794598 7fc3c11be6c0 Generated table #290@1: 5045 keys, 662072 bytes
|
||||||
2026/05/18-20:29:47.567433 7f5a467fc6c0 Delete type=2 #15
|
2026/06/12-20:52:05.794618 7fc3c11be6c0 Compacted 1@1 + 3@2 files => 4969494 bytes
|
||||||
2026/05/18-20:29:47.567841 7f5a467fc6c0 Delete type=2 #19
|
2026/06/12-20:52:05.801998 7fc3c11be6c0 compacted to: files[ 0 0 3 0 0 0 0 ]
|
||||||
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/06/12-20:52:05.802346 7fc3c11be6c0 Delete type=2 #281
|
||||||
|
2026/06/12-20:52:05.802678 7fc3c11be6c0 Delete type=2 #282
|
||||||
|
2026/06/12-20:52:05.802871 7fc3c11be6c0 Delete type=2 #283
|
||||||
|
2026/06/12-20:52:05.803095 7fc3c11be6c0 Delete type=2 #287
|
||||||
|
2026/06/12-20:52:05.821244 7fc3c11be6c0 Manual compaction at level-1 from '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 1585250 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at (end)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
MANIFEST-000065
|
MANIFEST-000122
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
2026/05/24-16:50:54.673462 7fdfa95ff6c0 Recovering log #63
|
2026/06/12-20:52:58.037556 7fc3c3fff6c0 Recovering log #120
|
||||||
2026/05/24-16:50:54.683865 7fdfa95ff6c0 Delete type=3 #61
|
2026/06/12-20:52:58.047938 7fc3c3fff6c0 Delete type=3 #118
|
||||||
2026/05/24-16:50:54.683923 7fdfa95ff6c0 Delete type=0 #63
|
2026/06/12-20:52:58.048005 7fc3c3fff6c0 Delete type=0 #120
|
||||||
2026/05/24-17:30:55.141699 7fdf5affd6c0 Level-0 table #68: started
|
2026/06/12-20:53:22.710451 7fc3c11be6c0 Level-0 table #125: started
|
||||||
2026/05/24-17:30:55.144973 7fdf5affd6c0 Level-0 table #68: 18651 bytes OK
|
2026/06/12-20:53:22.710474 7fc3c11be6c0 Level-0 table #125: 0 bytes OK
|
||||||
2026/05/24-17:30:55.150867 7fdf5affd6c0 Delete type=0 #66
|
2026/06/12-20:53:22.716951 7fc3c11be6c0 Delete type=0 #123
|
||||||
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/06/12-20:53:22.724368 7fc3c11be6c0 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/06/12-20:53:22.822717 7fc3c11be6c0 Manual compaction at level-1 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end)
|
||||||
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)
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
2026/05/18-20:28:00.088714 7f5a94bff6c0 Recovering log #58
|
2026/06/12-20:51:44.607543 7fc410ffe6c0 Recovering log #116
|
||||||
2026/05/18-20:28:00.147839 7f5a94bff6c0 Delete type=3 #56
|
2026/06/12-20:51:44.622157 7fc410ffe6c0 Delete type=3 #114
|
||||||
2026/05/18-20:28:00.147945 7f5a94bff6c0 Delete type=0 #58
|
2026/06/12-20:51:44.622215 7fc410ffe6c0 Delete type=0 #116
|
||||||
2026/05/18-20:29:47.507007 7f5a467fc6c0 Level-0 table #64: started
|
2026/06/12-20:52:05.604539 7fc3c11be6c0 Level-0 table #121: started
|
||||||
2026/05/18-20:29:47.507077 7f5a467fc6c0 Level-0 table #64: 0 bytes OK
|
2026/06/12-20:52:05.604563 7fc3c11be6c0 Level-0 table #121: 0 bytes OK
|
||||||
2026/05/18-20:29:47.513818 7f5a467fc6c0 Delete type=0 #62
|
2026/06/12-20:52:05.611212 7fc3c11be6c0 Delete type=0 #119
|
||||||
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/06/12-20:52:05.618870 7fc3c11be6c0 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/06/12-20:52:05.619129 7fc3c11be6c0 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.
@@ -51,15 +51,36 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
this._tradeGoods = null;
|
this._tradeGoods = null;
|
||||||
|
this._worldNames = {};
|
||||||
|
|
||||||
|
if (options.defaultWorld) {
|
||||||
|
const w = options.defaultWorld;
|
||||||
|
this._defaultWorld = w;
|
||||||
|
this._worldNames['pax.uwpDep'] = w.name || '';
|
||||||
|
this._worldNames['cargo.uwpDep'] = w.name || '';
|
||||||
|
this._worldNames['trade.uwp'] = w.name || '';
|
||||||
|
if (w.uwp) this._formData.pax.uwpDep = w.uwp;
|
||||||
|
if (w.zone) this._formData.pax.zoneDep = w.zone;
|
||||||
|
if (w.uwp) this._formData.cargo.uwpDep = w.uwp;
|
||||||
|
if (w.zone) this._formData.cargo.zoneDep = w.zone;
|
||||||
|
if (w.uwp) this._formData.trade.uwp = w.uwp;
|
||||||
|
if (w.zone) this._formData.trade.zone = w.zone;
|
||||||
|
this._activeTab = 'trade';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async _prepareContext() {
|
async _prepareContext() {
|
||||||
_registerHandlebarsHelpers();
|
_registerHandlebarsHelpers();
|
||||||
return {
|
const ctx = {
|
||||||
...this._formData,
|
...this._formData,
|
||||||
activeActor: buildActiveActorContext(),
|
activeActor: buildActiveActorContext(),
|
||||||
activeTab: this._activeTab,
|
activeTab: this._activeTab,
|
||||||
};
|
};
|
||||||
|
if (this._defaultWorld) {
|
||||||
|
ctx.defaultWorldName = this._defaultWorld.name;
|
||||||
|
ctx.defaultWorldLoc = `${this._defaultWorld.sector || ''} ${this._defaultWorld.hex || ''}`.trim();
|
||||||
|
}
|
||||||
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _onRender(context, options) {
|
async _onRender(context, options) {
|
||||||
@@ -128,6 +149,12 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
|
|
||||||
this._bindWorldSearch(html);
|
this._bindWorldSearch(html);
|
||||||
|
|
||||||
|
// Pré-remplir les champs recherche avec le nom du monde
|
||||||
|
if (this._defaultWorld?.name) {
|
||||||
|
html.find('.world-search-widget[data-role="dep"] .world-search-input, .world-block-full .world-search-input')
|
||||||
|
.val(this._defaultWorld.name);
|
||||||
|
}
|
||||||
|
|
||||||
html.on('click', (ev) => {
|
html.on('click', (ev) => {
|
||||||
if (!$(ev.target).closest('.world-search-widget').length) {
|
if (!$(ev.target).closest('.world-search-widget').length) {
|
||||||
html.find('.world-search-results').empty();
|
html.find('.world-search-results').empty();
|
||||||
@@ -204,6 +231,7 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
$li.on('click', async () => {
|
$li.on('click', async () => {
|
||||||
$results.empty();
|
$results.empty();
|
||||||
$input.val(w.name);
|
$input.val(w.name);
|
||||||
|
if (uwpTarget) this._worldNames[uwpTarget] = w.name;
|
||||||
const [detail, coords] = await Promise.all([
|
const [detail, coords] = await Promise.all([
|
||||||
fetchWorldDetail(w.sector, w.hex).catch(() => null),
|
fetchWorldDetail(w.sector, w.hex).catch(() => null),
|
||||||
fetchWorldCoordinates(w.sector, w.hex).catch(() => null),
|
fetchWorldCoordinates(w.sector, w.hex).catch(() => null),
|
||||||
@@ -222,11 +250,13 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
html.find('[name="cargo.uwpDep"]').val(resolvedUwp);
|
html.find('[name="cargo.uwpDep"]').val(resolvedUwp);
|
||||||
html.find('[name="cargo.zoneDep"]').val(resolvedZone);
|
html.find('[name="cargo.zoneDep"]').val(resolvedZone);
|
||||||
$cargoDep.data('coords', coords);
|
$cargoDep.data('coords', coords);
|
||||||
|
this._worldNames['cargo.uwpDep'] = w.name;
|
||||||
|
|
||||||
const $tradeWorld = html.find('.world-search-widget[data-uwp-target="trade.uwp"]');
|
const $tradeWorld = html.find('.world-search-widget[data-uwp-target="trade.uwp"]');
|
||||||
$tradeWorld.find('.world-search-input').val(w.name);
|
$tradeWorld.find('.world-search-input').val(w.name);
|
||||||
html.find('[name="trade.uwp"]').val(resolvedUwp);
|
html.find('[name="trade.uwp"]').val(resolvedUwp);
|
||||||
html.find('[name="trade.zone"]').val(resolvedZone);
|
html.find('[name="trade.zone"]').val(resolvedZone);
|
||||||
|
this._worldNames['trade.uwp'] = w.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsecsTarget) {
|
if (parsecsTarget) {
|
||||||
@@ -370,6 +400,8 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!result.success) return ui.notifications.error(result.errors.join(' | '));
|
if (!result.success) return ui.notifications.error(result.errors.join(' | '));
|
||||||
|
result.dep = { ...result.dep, name: this._worldNames['pax.uwpDep'] || '' };
|
||||||
|
result.dest = { ...result.dest, name: this._worldNames['pax.uwpDest'] || '' };
|
||||||
await this._postToChatResult(result);
|
await this._postToChatResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,6 +426,8 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
|
|
||||||
if (!result.success) return ui.notifications.error(result.errors.join(' | '));
|
if (!result.success) return ui.notifications.error(result.errors.join(' | '));
|
||||||
result.cargoRevenue = result.lots.reduce((s, l) => s + l.revenue, 0);
|
result.cargoRevenue = result.lots.reduce((s, l) => s + l.revenue, 0);
|
||||||
|
result.dep = { ...result.dep, name: this._worldNames['cargo.uwpDep'] || '' };
|
||||||
|
result.dest = { ...result.dest, name: this._worldNames['cargo.uwpDest'] || '' };
|
||||||
await this._postToChatResult(result);
|
await this._postToChatResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,6 +447,7 @@ export class CommerceDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
|
|
||||||
if (!result.success) return ui.notifications.error(result.errors.join(' | '));
|
if (!result.success) return ui.notifications.error(result.errors.join(' | '));
|
||||||
|
|
||||||
|
result.world = { ...result.world, name: this._worldNames['trade.uwp'] || '' };
|
||||||
this._tradeGoods = result;
|
this._tradeGoods = result;
|
||||||
const goodsDiv = html.find('.trade-goods-result');
|
const goodsDiv = html.find('.trade-goods-result');
|
||||||
const listDiv = html.find('.trade-goods-list');
|
const listDiv = html.find('.trade-goods-list');
|
||||||
|
|||||||
+274
-3
@@ -1,6 +1,21 @@
|
|||||||
import { formatCredits } from './tradeHelper.js';
|
import { formatCredits } from './tradeHelper.js';
|
||||||
import { createNpcActor, generateClientMission, generateEncounter, generateQuickNpc } from './npcHelper.js';
|
import { createNpcActor, generateClientMission, generateEncounter, generateQuickNpc, formatSigned } from './npcHelper.js';
|
||||||
|
import { generateAllyEnemy } from './allyEnemyGenerator.js';
|
||||||
import { NPC_RELATIONS } from './data/npcTables.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 { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||||
@@ -41,6 +56,26 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
context: options.context ?? 'starport',
|
context: options.context ?? 'starport',
|
||||||
includeFollowUp: true,
|
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,
|
||||||
|
},
|
||||||
|
ae: {
|
||||||
|
relation: options.relation ?? 'contact',
|
||||||
|
includeSpecial: true,
|
||||||
|
createActor: false,
|
||||||
|
actorName: '',
|
||||||
|
openCreatedActor: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +85,25 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
...this._formData,
|
...this._formData,
|
||||||
activeTab: this._activeTab,
|
activeTab: this._activeTab,
|
||||||
relations: Object.entries(NPC_RELATIONS).map(([key, value]) => ({ key, label: value.label })),
|
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 +138,33 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
this._readForm(html);
|
this._readForm(html);
|
||||||
this._activateTab($(event.currentTarget).data('tab'));
|
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="generate-ally-enemy"]').on('click', async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this._readForm(html);
|
||||||
|
await this._handleAllyEnemy();
|
||||||
|
});
|
||||||
|
|
||||||
|
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() {
|
_getForm() {
|
||||||
@@ -137,6 +218,25 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
this._formData.npc.openCreatedActor = html.find('[name="npc.openCreatedActor"]').is(':checked');
|
this._formData.npc.openCreatedActor = html.find('[name="npc.openCreatedActor"]').is(':checked');
|
||||||
this._formData.encounter.context = html.find('[name="encounter.context"]').val();
|
this._formData.encounter.context = html.find('[name="encounter.context"]').val();
|
||||||
this._formData.encounter.includeFollowUp = html.find('[name="encounter.includeFollowUp"]').is(':checked');
|
this._formData.encounter.includeFollowUp = html.find('[name="encounter.includeFollowUp"]').is(':checked');
|
||||||
|
|
||||||
|
// Données pour l'onglet Alliés/Ennemis
|
||||||
|
this._formData.ae.relation = html.find('[name="ae.relation"]').val();
|
||||||
|
this._formData.ae.includeSpecial = html.find('[name="ae.includeSpecial"]').is(':checked');
|
||||||
|
this._formData.ae.createActor = html.find('[name="ae.createActor"]').is(':checked');
|
||||||
|
this._formData.ae.actorName = html.find('[name="ae.actorName"]').val();
|
||||||
|
this._formData.ae.openCreatedActor = html.find('[name="ae.openCreatedActor"]').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() {
|
async _handleNpc() {
|
||||||
@@ -162,14 +262,131 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||||||
await this._postToChatResult(result);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _handleAllyEnemy() {
|
||||||
|
const button = $(this.element).find('[data-action="generate-ally-enemy"]');
|
||||||
|
const originalLabel = button.html();
|
||||||
|
|
||||||
|
try {
|
||||||
|
button.prop('disabled', true).html('<i class="fas fa-spinner fa-spin"></i> Génération...');
|
||||||
|
|
||||||
|
const result = await generateAllyEnemy(this._formData.ae.relation, {
|
||||||
|
includeSpecial: this._formData.ae.includeSpecial,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
if (this._formData.ae.createActor) {
|
||||||
|
const ae = this._formData.ae;
|
||||||
|
const actorName = ae.actorName?.trim() || `PNJ — ${result.relation.label}`;
|
||||||
|
const baseActorSystem = game.system?.id === 'mgt2e'
|
||||||
|
? await (await import('./travellerNpcGenerator.js')).getMgt2eBaseActorSystem()
|
||||||
|
: null;
|
||||||
|
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, homeworld: '', profession: result.relation.label,
|
||||||
|
}),
|
||||||
|
characteristics: foundry.utils.deepClone(baseActorSystem?.characteristics ?? {}),
|
||||||
|
hits: foundry.utils.deepClone(baseActorSystem?.hits ?? {}),
|
||||||
|
skills: foundry.utils.deepClone(baseActorSystem?.skills ?? {}),
|
||||||
|
},
|
||||||
|
flags: {
|
||||||
|
[MODULE_ID]: { generatedAllyEnemy: { relation: result.relation.key } },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const actor = await Actor.create(actorData, { renderSheet: false });
|
||||||
|
result.createdActor = { id: actor.id, name: actor.name };
|
||||||
|
if (ae.openCreatedActor) actor.sheet?.render(true);
|
||||||
|
ui.notifications.info(`Fiche PNJ créée : ${actor.name}`);
|
||||||
|
}
|
||||||
|
await this._postToChatResult(result);
|
||||||
|
} else {
|
||||||
|
ui.notifications.error('Erreur lors de la génération de la relation');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`${MODULE_ID} | Erreur AE:`, 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) {
|
async _postToChatResult(data) {
|
||||||
registerHandlebarsHelpers();
|
registerHandlebarsHelpers();
|
||||||
const html = await foundry.applications.handlebars.renderTemplate(`modules/${MODULE_ID}/templates/npc-result.hbs`, data);
|
|
||||||
|
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';
|
||||||
|
} else if (data.type === 'ally-enemy') {
|
||||||
|
template = `modules/${MODULE_ID}/templates/ally-enemy-result.hbs`;
|
||||||
|
resultType = 'ally-enemy-result';
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = await foundry.applications.handlebars.renderTemplate(template, data);
|
||||||
|
|
||||||
await ChatMessage.create({
|
await ChatMessage.create({
|
||||||
content: html,
|
content: html,
|
||||||
speaker: ChatMessage.getSpeaker(),
|
speaker: ChatMessage.getSpeaker(),
|
||||||
flags: { [MODULE_ID]: { type: 'npc-result' } },
|
flags: { [MODULE_ID]: { type: resultType } },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -180,8 +397,62 @@ function registerHandlebarsHelpers() {
|
|||||||
if (helpersRegistered) return;
|
if (helpersRegistered) return;
|
||||||
helpersRegistered = true;
|
helpersRegistered = true;
|
||||||
|
|
||||||
|
// Helpers existants pour NPC
|
||||||
Handlebars.registerHelper('eq', (a, b) => a === b);
|
Handlebars.registerHelper('eq', (a, b) => a === b);
|
||||||
Handlebars.registerHelper('join', (arr, sep) => (Array.isArray(arr) ? arr.join(sep) : ''));
|
Handlebars.registerHelper('join', (arr, sep) => (Array.isArray(arr) ? arr.join(sep) : ''));
|
||||||
Handlebars.registerHelper('formatCredits', (amount) => formatCredits(amount));
|
Handlebars.registerHelper('formatCredits', (amount) => formatCredits(amount));
|
||||||
Handlebars.registerHelper('contains', (text, search) => String(text ?? '').includes(search));
|
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] : '';
|
||||||
|
});
|
||||||
|
|
||||||
|
const RELATION_LABELS = Object.entries(NPC_RELATIONS).reduce((acc, [key, val]) => {
|
||||||
|
acc[key] = val.label;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('lookupRelationKey', (key) => RELATION_LABELS[key] || key);
|
||||||
|
|
||||||
|
Handlebars.registerHelper('formatSigned', (value) => formatSigned(value));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,573 @@
|
|||||||
|
/**
|
||||||
|
* MGT2 – SectorMapApp
|
||||||
|
*
|
||||||
|
* Application interactive affichant une carte Traveller Map dans un IFRAME.
|
||||||
|
* Les clics sur la carte affichent les détails du monde dans le chat.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { searchWorlds } from './travellerMapApi.js';
|
||||||
|
import { TravelDialog } from './travelDialog.js';
|
||||||
|
|
||||||
|
const { ApplicationV2 } = foundry.applications.api;
|
||||||
|
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
|
||||||
|
|
||||||
|
export class SectorMapApp extends ApplicationV2 {
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
id: 'mgt2-sector-map',
|
||||||
|
classes: ['mgt2-sector-map'],
|
||||||
|
position: { width: 960, height: 720 },
|
||||||
|
window: { icon: 'fas fa-map', resizable: true, controls: [] },
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(sector, subsector) {
|
||||||
|
super();
|
||||||
|
this._sector = sector;
|
||||||
|
this._subsector = subsector;
|
||||||
|
this._handler = null;
|
||||||
|
this._mapHex = null;
|
||||||
|
this._searchTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get title() {
|
||||||
|
if (!this._sector) return 'Carte stellaire — Traveller Map';
|
||||||
|
return this._subsector
|
||||||
|
? `Sous-secteur ${this._subsector} — ${this._sector}`
|
||||||
|
: `Secteur ${this._sector}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
get _mapUrl() {
|
||||||
|
const base = 'https://travellermap.com';
|
||||||
|
if (!this._sector) return `${base}/?style=mongoose&hideui=1`;
|
||||||
|
if (this._subsector) {
|
||||||
|
return `${base}/?sector=${encodeURIComponent(this._sector)}&subsector=${encodeURIComponent(this._subsector)}&style=mongoose&hideui=1`;
|
||||||
|
}
|
||||||
|
let url = `${base}/go/${encodeURIComponent(this._sector)}?style=mongoose`;
|
||||||
|
if (this._mapHex) {
|
||||||
|
url += `&hex=${this._mapHex}&scale=512`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── Rendu ───── */
|
||||||
|
|
||||||
|
_replaceHTML(result, config) {
|
||||||
|
const content = this.element?.querySelector('.window-content');
|
||||||
|
if (!content) return;
|
||||||
|
const html = typeof result === 'string' ? result : this._lastHTML;
|
||||||
|
content.innerHTML = typeof html === 'string' ? html : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderHTML() {
|
||||||
|
return `<div class="mgt2-sector-map-outer">
|
||||||
|
<div class="mgt2-sector-map-toolbar">
|
||||||
|
<div class="mgt2-sector-map-search">
|
||||||
|
<input type="text" class="mgt2-sector-map-input" placeholder="Rechercher un monde…" autocomplete="off">
|
||||||
|
<ul class="mgt2-sector-map-results"></ul>
|
||||||
|
</div>
|
||||||
|
<span class="mgt2-sector-map-label">${this._sector ? `${this._sector}${this._subsector ? ` — ss.${this._subsector}` : ''}` : 'Toute la carte'}</span>
|
||||||
|
<span class="mgt2-sector-map-hint">Cliquez sur un hex pour voir les détails du monde</span>
|
||||||
|
<button type="button" class="mgt2-sector-map-share" title="Partager une image fixe dans le chat"><i class="fas fa-image"></i> Partager</button>
|
||||||
|
<button type="button" class="mgt2-sector-map-sync" title="Ouvrir la carte interactive chez tous les joueurs"><i class="fas fa-users"></i> Synchroniser</button>
|
||||||
|
<button type="button" class="mgt2-sector-map-travel" title="Planifier un voyage"><i class="fas fa-route"></i> Voyage</button>
|
||||||
|
</div>
|
||||||
|
<iframe
|
||||||
|
src="${this._mapUrl}"
|
||||||
|
class="mgt2-sector-map-frame"
|
||||||
|
allow="clipboard-write"
|
||||||
|
referrerpolicy="no-referrer">
|
||||||
|
</iframe>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onRender(context, options) {
|
||||||
|
this._listen();
|
||||||
|
this.element?.querySelector('.mgt2-sector-map-share')?.addEventListener('click', () => {
|
||||||
|
this._shareMap();
|
||||||
|
});
|
||||||
|
this.element?.querySelector('.mgt2-sector-map-sync')?.addEventListener('click', () => {
|
||||||
|
this._syncAll();
|
||||||
|
});
|
||||||
|
this.element?.querySelector('.mgt2-sector-map-travel')?.addEventListener('click', () => {
|
||||||
|
this._openTravelDialog();
|
||||||
|
});
|
||||||
|
|
||||||
|
const input = this.element?.querySelector('.mgt2-sector-map-input');
|
||||||
|
const results = this.element?.querySelector('.mgt2-sector-map-results');
|
||||||
|
if (input && results) {
|
||||||
|
input.addEventListener('input', () => {
|
||||||
|
if (this._searchTimeout) clearTimeout(this._searchTimeout);
|
||||||
|
this._searchTimeout = setTimeout(() => this._doSearch(input, results), 300);
|
||||||
|
});
|
||||||
|
input.addEventListener('blur', () => {
|
||||||
|
setTimeout(() => { results.innerHTML = ''; }, 200);
|
||||||
|
});
|
||||||
|
input.addEventListener('focus', () => {
|
||||||
|
if (input.value.trim().length >= 2) this._doSearch(input, results);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── Écoute des clics IFRAME ───── */
|
||||||
|
|
||||||
|
_listen() {
|
||||||
|
if (this._handler) return;
|
||||||
|
this._handler = (event) => {
|
||||||
|
// Accept messages from travellermap or any origin (for testing)
|
||||||
|
const d = event.data || {};
|
||||||
|
const wx = d.x ?? d.location?.x;
|
||||||
|
const wy = d.y ?? d.location?.y;
|
||||||
|
if (wx == null || wy == null) return;
|
||||||
|
const x = Number(wx);
|
||||||
|
const y = Number(wy);
|
||||||
|
if (isNaN(x) || isNaN(y)) return;
|
||||||
|
console.log('SectorMapApp | click at', x, y, 'from', event.origin);
|
||||||
|
this._onMapClick({x, y}).catch(err => {
|
||||||
|
console.error('SectorMapApp | click handler failed:', err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
window.addEventListener('message', this._handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _onMapClick(loc) {
|
||||||
|
const wx = loc?.x;
|
||||||
|
const wy = loc?.y;
|
||||||
|
if (wx == null || wy == null) return;
|
||||||
|
|
||||||
|
const coordResp = await fetch(
|
||||||
|
`https://travellermap.com/api/coordinates?x=${wx}&y=${wy}`
|
||||||
|
);
|
||||||
|
if (!coordResp.ok) { console.error('SectorMapApp | /api/coordinates failed', coordResp.status); return; }
|
||||||
|
const coord = await coordResp.json();
|
||||||
|
const { sx, sy, hx, hy } = coord;
|
||||||
|
if (sx == null || hx == null || hy == null) { console.error('SectorMapApp | no sx/hx/hy in', coord); return; }
|
||||||
|
|
||||||
|
const metaResp = await fetch(
|
||||||
|
`https://travellermap.com/api/metadata?sx=${sx}&sy=${sy}`
|
||||||
|
);
|
||||||
|
if (!metaResp.ok) { console.error('SectorMapApp | /api/metadata failed', metaResp.status); return; }
|
||||||
|
const meta = await metaResp.json();
|
||||||
|
const sectorName = meta.Names?.[0]?.Text;
|
||||||
|
if (!sectorName) { console.error('SectorMapApp | no Names[0].Text in metadata', meta); return; }
|
||||||
|
|
||||||
|
const hex = String(hx).padStart(2, '0') + String(hy).padStart(2, '0');
|
||||||
|
const resp = await fetch(
|
||||||
|
`https://travellermap.com/data/${encodeURIComponent(sectorName)}/${hex}`
|
||||||
|
);
|
||||||
|
if (!resp.ok) { console.error('SectorMapApp | /data failed', resp.status, sectorName, hex); return; }
|
||||||
|
|
||||||
|
const data = await resp.json();
|
||||||
|
const world = data.Worlds?.[0];
|
||||||
|
if (!world) { console.error('SectorMapApp | no Worlds in data', data); return; }
|
||||||
|
|
||||||
|
this._postWorldCard(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── Carte de chat ───── */
|
||||||
|
|
||||||
|
static _STARPORT = { A:'Excellent', B:'Bon', C:'Routinier', D:'Médiocre', E:'Frontière', X:'Aucun' };
|
||||||
|
static _SIZE = ['Aucun (Astéroïde)','1 600 km','3 200 km','4 800 km','6 400 km','8 000 km','9 600 km','11 200 km','12 800 km','14 400 km','16 000 km'];
|
||||||
|
static _ATMO = [
|
||||||
|
'Aucune (vide)','Trace','Très ténue (polluée)','Très ténue','Ténue (polluée)','Ténue','Standard','Standard (polluée)','Dense','Dense (polluée)',
|
||||||
|
'Exotique','Corrosive','Insidieuse','','',''];
|
||||||
|
static _HYDRO = [ '0–5% (désert)','6–15%','16–25%','26–35%','36–45%','46–55%','56–65%','66–75%','76–85%','86–95%','96–100%' ];
|
||||||
|
static _POP = ['','Dizaines','Centaines','Milliers','Dizaines de milliers','Centaines de milliers','Millions','Dizaines de millions','Centaines de millions','Milliards','Dizaines de milliards','','','','','',''];
|
||||||
|
static _GOV = [
|
||||||
|
'Aucun','Compagnie / Corporation','Démocratie participative','Oligarchie auto-perpétuée',
|
||||||
|
'Démocratie représentative','Technocratie féodale','Gouvernement captif / Colonie',
|
||||||
|
'Balkanisation','Bureaucratie de service civil','Bureaucratie impersonnelle',
|
||||||
|
'Dictature charismatique','Dictature non-charismatique','Oligarchie charismatique',
|
||||||
|
'Dictature religieuse','Oligarchie religieuse','Gouvernement tribal'];
|
||||||
|
static _LAW = [
|
||||||
|
'Aucune', 'Armes de poing, explosifs, poison','Armes à énergie portatives','Mitrailleuses, armes auto',
|
||||||
|
'Armes d\'assaut légères, PM','Armes de poing individuelles','Toutes les armes à feu sauf neutralisateur',
|
||||||
|
'Fusils, neutralisateur','Armes blanches, neutralisateur','Armes hors du domicile','Armes interdites',
|
||||||
|
'Contrôle rigide','Aucune arme','Contrôle militariste sévère'];
|
||||||
|
static _TL = [
|
||||||
|
'Âge de pierre','Âge du bronze/fer','Médiéval','Grandes découvertes','Révolution industrielle',
|
||||||
|
'Production mécanisée','Ère nucléaire','Pré-stellaire (ère de l\'information)','Propulsion à saut (1re gen)',
|
||||||
|
'Propulsion à saut-2','Propulsion à saut-3','Propulsion à saut-4','Propulsion à saut-5',
|
||||||
|
'Propulsion à saut-6','Transporteur','Moyenne stellaire'];
|
||||||
|
|
||||||
|
static _hexVal(ch) {
|
||||||
|
const n = parseInt(ch, 36);
|
||||||
|
if (isNaN(n)) return -1;
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _uwpDigit(desc, val) {
|
||||||
|
return `<span class="uwp-dig" title="${desc}">${val}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _uwpBreakdown(uwp) {
|
||||||
|
if (!uwp || uwp.length < 2) return '';
|
||||||
|
const d = SectorMapApp._hexVal;
|
||||||
|
const s = d(uwp[0]), sz = d(uwp[1]), a = d(uwp[2]), h = d(uwp[3]);
|
||||||
|
const p = d(uwp[4]), g = d(uwp[5]), l = d(uwp[6]);
|
||||||
|
const t = uwp.length > 8 ? d(uwp[8]) : -1;
|
||||||
|
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
|
const starport = SectorMapApp._STARPORT[uwp[0]];
|
||||||
|
lines.push(`<tr><td>${uwp[0]}</td><td>Starport</td><td>${starport ?? '—'}</td></tr>`);
|
||||||
|
|
||||||
|
if (uwp[1] === 'F' || uwp[1] === 'f') {
|
||||||
|
lines.push(`<tr><td>${uwp[1]}</td><td>Taille</td><td>Gaz géant</td></tr>`);
|
||||||
|
} else if (sz >= 0 && sz <= 10) {
|
||||||
|
const km = SectorMapApp._SIZE[sz];
|
||||||
|
const grav = sz === 0 ? '0g' : (sz < 10 ? `0.${sz}g` : '1.0g+');
|
||||||
|
lines.push(`<tr><td>${uwp[1]}</td><td>Taille</td><td>${km} (${grav})</td></tr>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a >= 0 && a <= 15) {
|
||||||
|
const atmo = SectorMapApp._ATMO[a] ?? '—';
|
||||||
|
lines.push(`<tr><td>${uwp[2]}</td><td>Atmosphère</td><td>${atmo}</td></tr>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (h >= 0 && h <= 10) {
|
||||||
|
lines.push(`<tr><td>${uwp[3]}</td><td>Hydrosphère</td><td>${SectorMapApp._HYDRO[h]}</td></tr>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p >= 0 && p <= 15) {
|
||||||
|
lines.push(`<tr><td>${uwp[4]}</td><td>Population</td><td>${SectorMapApp._POP[p] ?? '—'}</td></tr>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g >= 0 && g <= 15) {
|
||||||
|
lines.push(`<tr><td>${uwp[5]}</td><td>Gouvernement</td><td>${SectorMapApp._GOV[g] ?? '—'}</td></tr>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l >= 0 && l <= 15) {
|
||||||
|
lines.push(`<tr><td>${uwp[6]}</td><td>Niveau légal</td><td>${SectorMapApp._LAW[l] ?? '—'}</td></tr>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t >= 0 && t <= 15) {
|
||||||
|
lines.push(`<tr><td>${uwp[8]}</td><td>Technologie</td><td>${SectorMapApp._TL[t] ?? '—'}</td></tr>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<table class="uwp-breakdown"><tbody>${lines.join('')}</tbody></table>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _REMARKS_HELP = {
|
||||||
|
AB:'Anneau (ceinture)', AG:'Agricole', AN:'Site ancien', AS:'Astéroïde',
|
||||||
|
BA:'Bande astéroïdale', CP:'Sous-secteur capitale', CS:'Colonie',
|
||||||
|
CX:'Chasseur (Croiseur)', CY:'Colonie', DA:'Déchu', DE:'Désertique',
|
||||||
|
DI:'Interdit (Diebar)', FL:'Fluides Lo', FO:'Interdit (Forbidden)',
|
||||||
|
FR:'Gelé (Frozen)', GA:'Jardin (Garden)', HE:'Helios', HI:'Haute population',
|
||||||
|
HT:'Haute technologie', IC:'Mondes gelés (Ice)', IN:'Industrialisé',
|
||||||
|
LI:'Faible population', LO:'Faible population (Low)', LT:'Basse technologie (Low Tech)',
|
||||||
|
MI:'Militaire', MR:'Mine (ressources)', NA:'Non-agricole',
|
||||||
|
NI:'Non-industrialisé', OC:'Océanique', OX:'Oxydant',
|
||||||
|
PA:'Pré-agricole (Pre-Agricultural)', PH:'Phosphore',
|
||||||
|
PO:'Pauvre (Poor)', PR:'Pré-industriel (Pre-Industrial)',
|
||||||
|
PX:'Prisonnier (exil)', PZ:'Puzzle (énigmatique)',
|
||||||
|
RE:'Religieux (Religious)', RI:'Riche (Rich)',
|
||||||
|
SA:'Bande d\'astéroïdes (Satellite)', SC:'Sainte (colonie)',
|
||||||
|
SL:'Esclavage (Slave)', SO:'Soleil (Sol)', SP:'Désert (Despoiled)',
|
||||||
|
SR:'Réserve (Reserve)', ST:'Base stellaire', SU:'Secteur capitale',
|
||||||
|
TR:'Traces (Trace)', TU:'Tucannides', TZ:'Mondes Tz',
|
||||||
|
UN:'Inhabité (Uninhabited)', VA:'Vide (Vacuum)',
|
||||||
|
WA:'Monde aquatique (Water)', WT:'Monde d\'eau (Watery)',
|
||||||
|
};
|
||||||
|
|
||||||
|
static _STAR_TYPES = { O:'Bleu (hypergéante)', B:'Bleu-blanc', A:'Blanc', F:'Blanc-jaune', G:'Jaune (naine)', K:'Orange (naine)', M:'Rouge (naine)', L:'Brune', T:'Brune', Y:'Brune' };
|
||||||
|
static _STAR_CLASS = { 'I':'Supergéante', 'II':'Géante brillante', 'III':'Géante', 'IV':'Sous-géante', 'V':'Naine (séquence principale)', 'VI':'Sous-naine', 'VII':'Naine blanche' };
|
||||||
|
|
||||||
|
static _BASES_HELP = {
|
||||||
|
N:'Base navale', S:'Base scout', W:'Relais', D:'Dépôt naval',
|
||||||
|
T:'Base TAS', C:'Consulat', P:'Base pirate', R:'Base de réparation',
|
||||||
|
K:'Base navale (K.)', X:'Relais Xboat',
|
||||||
|
};
|
||||||
|
|
||||||
|
static _NOBILITY = {
|
||||||
|
B:'Chevalier (Baronet)', C:'Baron', D:'Marquis', E:'Comte', F:'Duc', G:'Archiduc', H:'Empereur',
|
||||||
|
};
|
||||||
|
|
||||||
|
static _IMPORTANCE = {
|
||||||
|
'-5':'Très mineur', '-4':'Mineur', '-3':'Mineur', '-2':'Très secondaire', '-1':'Secondaire',
|
||||||
|
'0':'Ordinaire', '1':'Important', '2':'Important', '3':'Très important', '4':'Majeure',
|
||||||
|
'5':'Majeure', '6':'Capitale',
|
||||||
|
};
|
||||||
|
|
||||||
|
static _foldRow(label, value, detail, titleAttr) {
|
||||||
|
const valAttr = titleAttr ? ` title="${titleAttr}"` : '';
|
||||||
|
return `<tr><td colspan="2">
|
||||||
|
<details>
|
||||||
|
<summary><span class="fold-label">${label}</span><span class="fold-value"${valAttr}>${value}</span></summary>
|
||||||
|
<div class="fold-content">${detail}</div>
|
||||||
|
</details>
|
||||||
|
</td></tr>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _decodeImportance(ix) {
|
||||||
|
if (!ix) return '';
|
||||||
|
const m = String(ix).match(/\{?\s*(-?\d+)\s*\}?/);
|
||||||
|
if (!m) return SectorMapApp._foldRow('Importance', ix, '');
|
||||||
|
const val = m[1];
|
||||||
|
const desc = SectorMapApp._IMPORTANCE[val] || '—';
|
||||||
|
const detail = `<div class="fold-desc">Valeur d’importance économique et stratégique du monde.<br>${val} = ${desc}</div>`;
|
||||||
|
return SectorMapApp._foldRow('Importance', `{ ${val} } ${desc}`, detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
static _decodeEconomics(ex) {
|
||||||
|
if (!ex) return '';
|
||||||
|
let s = String(ex).replace(/[()\s]/g, '');
|
||||||
|
const m = s.match(/^([\dA-F])([\dA-F])([\dA-F])([+-]\d+)$/i);
|
||||||
|
if (!m) return SectorMapApp._foldRow('Économie', ex, '');
|
||||||
|
const res = m[1], lab = m[2], inf = m[3], eff = m[4];
|
||||||
|
const detail = `<table class="fold-subtable">
|
||||||
|
<tr><td>Ressources</td><td>${res}</td></tr>
|
||||||
|
<tr><td>Main-d’œuvre</td><td>${lab}</td></tr>
|
||||||
|
<tr><td>Infrastructure</td><td>${inf}</td></tr>
|
||||||
|
<tr><td>Efficacité</td><td>${eff}</td></tr>
|
||||||
|
</table>`;
|
||||||
|
return SectorMapApp._foldRow('Économie', `( ${res} ${lab} ${inf} ${eff} )`, detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
static _decodeCulture(cx) {
|
||||||
|
if (!cx) return '';
|
||||||
|
const s = String(cx).replace(/[\[\]\s]/g, '');
|
||||||
|
if (s.length < 4) return SectorMapApp._foldRow('Culture', cx, '');
|
||||||
|
const h = s[0].toUpperCase(), t = s[1].toUpperCase(), p = s[2].toUpperCase(), a = s[3].toUpperCase();
|
||||||
|
const detail = `<table class="fold-subtable">
|
||||||
|
<tr><td>Hétérogénéité</td><td>${h}</td></tr>
|
||||||
|
<tr><td>Traditionalisme</td><td>${t}</td></tr>
|
||||||
|
<tr><td>Progressisme</td><td>${p}</td></tr>
|
||||||
|
<tr><td>Agressivité</td><td>${a}</td></tr>
|
||||||
|
</table>`;
|
||||||
|
return SectorMapApp._foldRow('Culture', `[ ${h} ${t} ${p} ${a} ]`, detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
static _decodePopulation(uwp, pbg) {
|
||||||
|
if (!uwp || uwp.length < 5) return '';
|
||||||
|
const popUwp = SectorMapApp._hexVal(uwp[4]);
|
||||||
|
if (popUwp < 0) return '';
|
||||||
|
const popPbg = pbg ? parseInt(pbg[0], 10) : null;
|
||||||
|
const multiplier = popPbg != null && !isNaN(popPbg) ? popPbg : 1;
|
||||||
|
const base = Math.pow(10, popUwp);
|
||||||
|
const total = multiplier * base;
|
||||||
|
const fmtBase = `10<sup>${popUwp}</sup>`;
|
||||||
|
const fmtMult = multiplier;
|
||||||
|
const fmtTotal = total >= 1e9 ? `${(total / 1e9).toFixed(1)} milliards`
|
||||||
|
: total >= 1e6 ? `${(total / 1e6).toFixed(1)} millions`
|
||||||
|
: total >= 1e3 ? `${(total / 1e3).toFixed(0)} 000`
|
||||||
|
: String(total);
|
||||||
|
const belts = pbg ? parseInt(pbg[1], 10) : null;
|
||||||
|
const gas = pbg ? parseInt(pbg[2], 10) : null;
|
||||||
|
const detail = `<div class="fold-desc">Population = multiplicateur (PBG: <b>${popPbg}</b>) × 10<sup>chiffre UWP (${uwp[4]})</sup><br>
|
||||||
|
Ceintures d’astéroïdes : <b>${belts ?? '?'}</b> | Géantes gazeuses : <b>${gas ?? '?'}</b></div>`;
|
||||||
|
return SectorMapApp._foldRow('Population', `${fmtMult} × ${fmtBase} = ${fmtTotal}`, detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
static _decodeNobility(nob) {
|
||||||
|
if (!nob) return '';
|
||||||
|
const titles = [];
|
||||||
|
for (const ch of nob) {
|
||||||
|
const desc = SectorMapApp._NOBILITY[ch.toUpperCase()];
|
||||||
|
if (desc) titles.push(`${ch} (${desc})`);
|
||||||
|
}
|
||||||
|
if (!titles.length) return SectorMapApp._foldRow('Noblesse', nob, '');
|
||||||
|
const detail = `<div class="fold-desc">Titres de noblesse impériale présents sur ce monde.</div>`;
|
||||||
|
return SectorMapApp._foldRow('Noblesse', titles.join(', '), detail);
|
||||||
|
}
|
||||||
|
|
||||||
|
static _buildWorldCardHTML(w) {
|
||||||
|
const sector = w.Sector || '';
|
||||||
|
const hex = w.Hex || '';
|
||||||
|
const name = w.Name || '—';
|
||||||
|
const uwp = w.UWP || '???????-?';
|
||||||
|
const bases = w.Bases || '';
|
||||||
|
const remarks = w.Remarks || '';
|
||||||
|
const allegiance = w.Allegiance || '';
|
||||||
|
const stellar = w.Stellar || '';
|
||||||
|
const zone = w.Zone || '';
|
||||||
|
const pbg = w.PBG || '';
|
||||||
|
|
||||||
|
const zoneLabel = zone === 'R' ? 'Rouge'
|
||||||
|
: zone === 'A' ? 'Ambre'
|
||||||
|
: 'Verte';
|
||||||
|
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
|
const uwpDetail = SectorMapApp._uwpBreakdown(uwp);
|
||||||
|
lines.push(SectorMapApp._foldRow('UWP', `<span class="mono">${uwp}</span>`, uwpDetail));
|
||||||
|
|
||||||
|
const ixRow = SectorMapApp._decodeImportance(w.Ix);
|
||||||
|
if (ixRow) lines.push(ixRow);
|
||||||
|
const exRow = SectorMapApp._decodeEconomics(w.Ex);
|
||||||
|
if (exRow) lines.push(exRow);
|
||||||
|
const cxRow = SectorMapApp._decodeCulture(w.Cx);
|
||||||
|
if (cxRow) lines.push(cxRow);
|
||||||
|
const popRow = SectorMapApp._decodePopulation(uwp, pbg);
|
||||||
|
if (popRow) lines.push(popRow);
|
||||||
|
const nobRow = SectorMapApp._decodeNobility(w.Nobility);
|
||||||
|
if (nobRow) lines.push(nobRow);
|
||||||
|
|
||||||
|
if (bases) {
|
||||||
|
const bCodes = bases.split(/[\s,;]+/);
|
||||||
|
const bList = bCodes.map(c => {
|
||||||
|
const desc = SectorMapApp._BASES_HELP[c.toUpperCase()];
|
||||||
|
return `<tr><td class="mono">${c}</td><td>${desc || '—'}</td></tr>`;
|
||||||
|
}).join('');
|
||||||
|
const bDetail = `<table class="fold-subtable"><tbody>${bList}</tbody></table>`;
|
||||||
|
lines.push(SectorMapApp._foldRow('Bases', bases, bDetail));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remarks) {
|
||||||
|
const rCodes = remarks.split(/[\s,;]+/);
|
||||||
|
const rList = rCodes.map(c => {
|
||||||
|
const desc = SectorMapApp._REMARKS_HELP[c.toUpperCase()];
|
||||||
|
return `<tr><td class="mono">${c}</td><td>${desc || '—'}</td></tr>`;
|
||||||
|
}).join('');
|
||||||
|
const rDetail = `<table class="fold-subtable"><tbody>${rList}</tbody></table>`;
|
||||||
|
lines.push(SectorMapApp._foldRow('Remarques', remarks, rDetail));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allegiance) {
|
||||||
|
const allegFull = w.AllegianceName || '';
|
||||||
|
const aDetail = `<div class="fold-desc">${allegFull || allegiance}</div>`;
|
||||||
|
lines.push(SectorMapApp._foldRow('Allégeance', allegFull ? `${allegiance} (${allegFull})` : allegiance, aDetail));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stellar) {
|
||||||
|
const sList = [];
|
||||||
|
let remaining = stellar.trim();
|
||||||
|
const reStar = /^([OBAFGKMLTY])(\d)\s*(VII|VI|V|IV|III|II|I)\s*/i;
|
||||||
|
while (remaining) {
|
||||||
|
const m = remaining.match(reStar);
|
||||||
|
if (m) {
|
||||||
|
const type = SectorMapApp._STAR_TYPES[m[1].toUpperCase()] || m[1];
|
||||||
|
const cls = SectorMapApp._STAR_CLASS[m[3]] || m[3];
|
||||||
|
sList.push(`<tr><td class="mono">${m[1]}${m[2]} ${m[3]}</td><td>${type} · ${cls}</td></tr>`);
|
||||||
|
remaining = remaining.slice(m[0].length).trimStart();
|
||||||
|
} else {
|
||||||
|
const next = remaining.indexOf(' ');
|
||||||
|
if (next < 0) break;
|
||||||
|
remaining = remaining.slice(next + 1).trimStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const sDetail = sList.length ? `<table class="fold-subtable"><tbody>${sList.join('')}</tbody></table>` : `<div class="fold-desc">${stellar}</div>`;
|
||||||
|
lines.push(SectorMapApp._foldRow('Étoile', `<span class="mono">${stellar}</span>`, sDetail));
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<section class="mgt2-world-card">
|
||||||
|
<div class="mgt2-world-card-header">
|
||||||
|
<span class="mgt2-world-name">${name}</span>
|
||||||
|
<span class="mgt2-world-hex">${sector} ${hex}</span>
|
||||||
|
<span class="mgt2-world-zone zone-${zone.toLowerCase() || 'g'}">${zoneLabel}</span>
|
||||||
|
</div>
|
||||||
|
<table class="mgt2-world-card-body"><tbody>${lines.join('')}</tbody></table>
|
||||||
|
<div class="mgt2-world-card-actions">
|
||||||
|
<a class="mgt2-world-commerce" data-sector="${this._escapeAttr(sector)}" data-hex="${hex}" data-uwp="${uwp}" data-zone="${zone || 'normal'}" data-name="${this._escapeAttr(name)}">
|
||||||
|
<i class="fas fa-balance-scale"></i> Commerce
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _escapeAttr(str) {
|
||||||
|
if (!str) return '';
|
||||||
|
return String(str).replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
_postWorldCard(w) {
|
||||||
|
const html = SectorMapApp._buildWorldCardHTML(w);
|
||||||
|
ChatMessage.create({
|
||||||
|
content: html,
|
||||||
|
whisper: [game.user.id],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── Recherche de monde ───── */
|
||||||
|
|
||||||
|
async _doSearch(input, results) {
|
||||||
|
const query = input.value.trim();
|
||||||
|
if (query.length < 2) { results.innerHTML = ''; return; }
|
||||||
|
const worlds = await searchWorlds(query);
|
||||||
|
if (!worlds.length) {
|
||||||
|
results.innerHTML = '<li class="no-result">Aucun monde trouvé</li>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
results.innerHTML = worlds.slice(0, 12).map(w =>
|
||||||
|
`<li data-sector="${w.sector}" data-hex="${w.hex}" data-name="${w.name}">
|
||||||
|
<span class="world-name">${w.name}</span>
|
||||||
|
<span class="world-uwp">${w.uwp}</span>
|
||||||
|
<span class="world-sector">${w.sector}</span>
|
||||||
|
</li>`
|
||||||
|
).join('');
|
||||||
|
results.querySelectorAll('li[data-sector]').forEach(li => {
|
||||||
|
li.addEventListener('click', () => this._selectWorld(li, input, results));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _selectWorld(li, input, results) {
|
||||||
|
const sector = li.dataset.sector;
|
||||||
|
const hex = li.dataset.hex;
|
||||||
|
const name = li.dataset.name;
|
||||||
|
results.innerHTML = '';
|
||||||
|
input.value = name;
|
||||||
|
|
||||||
|
this._sector = sector;
|
||||||
|
this._subsector = null;
|
||||||
|
this._mapHex = hex;
|
||||||
|
|
||||||
|
const iframe = this.element?.querySelector('.mgt2-sector-map-frame');
|
||||||
|
if (iframe) iframe.src = this._mapUrl;
|
||||||
|
|
||||||
|
ui.notifications.info(`Carte centrée sur ${name} (${sector} ${hex})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── Partage ───── */
|
||||||
|
|
||||||
|
_shareMap() {
|
||||||
|
if (!this._sector) {
|
||||||
|
ui.notifications.warn('Aucun secteur sélectionné — utilisez la recherche pour centrer sur un secteur');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const posterUrl = `https://travellermap.com/api/poster?sector=${encodeURIComponent(this._sector)}${this._subsector ? `&subsector=${encodeURIComponent(this._subsector)}` : ''}&style=mongoose&scale=128&dpr=2`;
|
||||||
|
const label = this._subsector
|
||||||
|
? `Sous-secteur ${this._subsector} — ${this._sector}`
|
||||||
|
: `Secteur ${this._sector}`;
|
||||||
|
|
||||||
|
const html = `<section class="mgt2-shared-map">
|
||||||
|
<div class="mgt2-world-card-header">
|
||||||
|
<span class="mgt2-world-name">${label}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mgt2-shared-map-image">
|
||||||
|
<img src="${posterUrl}" alt="${label}">
|
||||||
|
</div>
|
||||||
|
<div class="mgt2-shared-map-footer">
|
||||||
|
<a href="https://travellermap.com/go/${encodeURIComponent(this._sector)}" target="_blank" rel="noopener">
|
||||||
|
Ouvrir sur Traveller Map <i class="fas fa-external-link-alt"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>`;
|
||||||
|
|
||||||
|
ChatMessage.create({ content: html, rollMode: 'public' });
|
||||||
|
ui.notifications.info(`Carte partagée avec les joueurs`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_syncAll() {
|
||||||
|
game.socket.emit(`module.${MODULE_ID}`, {
|
||||||
|
type: 'sectorMapSync',
|
||||||
|
sector: this._sector,
|
||||||
|
subsector: this._subsector,
|
||||||
|
});
|
||||||
|
ui.notifications.info(`Carte synchronisée chez tous les joueurs`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_openTravelDialog() {
|
||||||
|
const existing = Object.values(ui.windows).find(w => w.id === 'mgt2-travel-dialog');
|
||||||
|
if (existing) { existing.bringToTop(); return; }
|
||||||
|
const dialog = new TravelDialog();
|
||||||
|
dialog.render({ force: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ───── Nettoyage ───── */
|
||||||
|
|
||||||
|
close() {
|
||||||
|
if (this._handler) {
|
||||||
|
window.removeEventListener('message', this._handler);
|
||||||
|
this._handler = null;
|
||||||
|
}
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,277 @@
|
|||||||
|
import { NPC_RELATIONS } from './data/npcTables.js';
|
||||||
|
import {
|
||||||
|
RELATION_FORMULAS,
|
||||||
|
AFFINITY_INIMITY_MAP,
|
||||||
|
POWER_INFLUENCE_MAP,
|
||||||
|
AFFINITY_LABELS,
|
||||||
|
INIMITY_LABELS,
|
||||||
|
POWER_LABELS,
|
||||||
|
INFLUENCE_LABELS,
|
||||||
|
SPECIAL_CHARACTERISTICS_TABLE,
|
||||||
|
} from './data/allyEnemyTables.js';
|
||||||
|
|
||||||
|
export function mapRollToValue(roll, mapping) {
|
||||||
|
return mapping[roll] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getLabel(value, labels) {
|
||||||
|
return labels.find(l => l.value === Math.abs(value)) ?? labels[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clamp(value, min, max) {
|
||||||
|
return Math.max(min, Math.min(max, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rollFormula(formula) {
|
||||||
|
const roll = await new Roll(formula).evaluate();
|
||||||
|
return { formula, total: roll.total };
|
||||||
|
}
|
||||||
|
|
||||||
|
function getD66Entry(entries, total) {
|
||||||
|
return entries.find(e => e.d66 === total) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rollD66(entries) {
|
||||||
|
const tens = await rollFormula('1d6');
|
||||||
|
const ones = await rollFormula('1d6');
|
||||||
|
const total = (tens.total * 10) + ones.total;
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
tens: tens.total,
|
||||||
|
ones: ones.total,
|
||||||
|
entry: getD66Entry(entries, total),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rollAffinityInimity(relationKey) {
|
||||||
|
const formulas = RELATION_FORMULAS[relationKey];
|
||||||
|
let affinityRoll = null;
|
||||||
|
let inimityRoll = null;
|
||||||
|
|
||||||
|
if (formulas.affinity !== '0') {
|
||||||
|
affinityRoll = await rollFormula(formulas.affinity);
|
||||||
|
}
|
||||||
|
if (formulas.inimity !== '0') {
|
||||||
|
inimityRoll = await rollFormula(formulas.inimity);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
affinityValue: affinityRoll ? mapRollToValue(affinityRoll.total, AFFINITY_INIMITY_MAP) : 0,
|
||||||
|
inimityValue: inimityRoll ? mapRollToValue(inimityRoll.total, AFFINITY_INIMITY_MAP) : 0,
|
||||||
|
affinityRoll,
|
||||||
|
inimityRoll,
|
||||||
|
formulas,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveSpecialCharacteristics(currentRelationKey, depth = 0) {
|
||||||
|
if (depth > 5) return [];
|
||||||
|
|
||||||
|
const d66Result = await rollD66(SPECIAL_CHARACTERISTICS_TABLE);
|
||||||
|
if (!d66Result.entry) return [];
|
||||||
|
|
||||||
|
const entry = d66Result.entry;
|
||||||
|
const result = {
|
||||||
|
d66: d66Result.total,
|
||||||
|
text: entry.text,
|
||||||
|
effects: entry.effects,
|
||||||
|
appliedDeltas: { affinity: 0, inimity: 0, power: 0, influence: 0 },
|
||||||
|
rerollNote: null,
|
||||||
|
swapNote: null,
|
||||||
|
narrativeText: entry.effects.action === 'narrativeOnly' ? entry.text : null,
|
||||||
|
newRelationKey: null,
|
||||||
|
subCharacteristics: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (entry.effects.affinityMod) result.appliedDeltas.affinity = entry.effects.affinityMod;
|
||||||
|
if (entry.effects.inimityMod) result.appliedDeltas.inimity = entry.effects.inimityMod;
|
||||||
|
if (entry.effects.powerMod) result.appliedDeltas.power = entry.effects.powerMod;
|
||||||
|
if (entry.effects.influenceMod) result.appliedDeltas.influence = entry.effects.influenceMod;
|
||||||
|
|
||||||
|
if (entry.effects.action === 'extraRolls') {
|
||||||
|
const count = entry.effects.actionValue || 1;
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const extra = await resolveSpecialCharacteristics(currentRelationKey, depth + 1);
|
||||||
|
result.subCharacteristics.push(...extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function generateAllyEnemy(relationKey = 'contact', options = {}) {
|
||||||
|
const relation = NPC_RELATIONS[relationKey];
|
||||||
|
let currentRelationKey = relationKey;
|
||||||
|
|
||||||
|
const initial = await rollAffinityInimity(relationKey);
|
||||||
|
let affinityValue = initial.affinityValue;
|
||||||
|
let inimityValue = initial.inimityValue;
|
||||||
|
let affinityRoll = initial.affinityRoll;
|
||||||
|
let inimityRoll = initial.inimityRoll;
|
||||||
|
let currentFormulas = initial.formulas;
|
||||||
|
|
||||||
|
const powerRoll = await rollFormula('2d6');
|
||||||
|
const influenceRoll = await rollFormula('2d6');
|
||||||
|
let powerValue = mapRollToValue(powerRoll.total, POWER_INFLUENCE_MAP);
|
||||||
|
let influenceValue = mapRollToValue(influenceRoll.total, POWER_INFLUENCE_MAP);
|
||||||
|
|
||||||
|
let specialRoll = null;
|
||||||
|
let specialCharacteristics = [];
|
||||||
|
|
||||||
|
if (options.includeSpecial !== false) {
|
||||||
|
specialRoll = await rollFormula('2d6');
|
||||||
|
|
||||||
|
if (specialRoll.total >= 8) {
|
||||||
|
let queue = await resolveSpecialCharacteristics(currentRelationKey);
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const sc = queue.shift();
|
||||||
|
|
||||||
|
if (sc.effects.action === 'extraRolls') {
|
||||||
|
queue.push(...sc.subCharacteristics);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc.effects.action === 'moderateRelation') {
|
||||||
|
if (currentRelationKey === 'enemy') {
|
||||||
|
currentRelationKey = 'rival';
|
||||||
|
const rerolled = await rollAffinityInimity(currentRelationKey);
|
||||||
|
affinityValue = rerolled.affinityValue;
|
||||||
|
inimityValue = rerolled.inimityValue;
|
||||||
|
affinityRoll = rerolled.affinityRoll;
|
||||||
|
inimityRoll = rerolled.inimityRoll;
|
||||||
|
currentFormulas = rerolled.formulas;
|
||||||
|
sc.newRelationKey = currentRelationKey;
|
||||||
|
} else if (currentRelationKey === 'ally') {
|
||||||
|
currentRelationKey = 'contact';
|
||||||
|
const rerolled = await rollAffinityInimity(currentRelationKey);
|
||||||
|
affinityValue = rerolled.affinityValue;
|
||||||
|
inimityValue = rerolled.inimityValue;
|
||||||
|
affinityRoll = rerolled.affinityRoll;
|
||||||
|
inimityRoll = rerolled.inimityRoll;
|
||||||
|
currentFormulas = rerolled.formulas;
|
||||||
|
sc.newRelationKey = currentRelationKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc.effects.action === 'intensifyRelation') {
|
||||||
|
if (currentRelationKey === 'rival') {
|
||||||
|
currentRelationKey = 'enemy';
|
||||||
|
const rerolled = await rollAffinityInimity(currentRelationKey);
|
||||||
|
affinityValue = rerolled.affinityValue;
|
||||||
|
inimityValue = rerolled.inimityValue;
|
||||||
|
affinityRoll = rerolled.affinityRoll;
|
||||||
|
inimityRoll = rerolled.inimityRoll;
|
||||||
|
currentFormulas = rerolled.formulas;
|
||||||
|
sc.newRelationKey = currentRelationKey;
|
||||||
|
} else if (currentRelationKey === 'contact') {
|
||||||
|
currentRelationKey = 'ally';
|
||||||
|
const rerolled = await rollAffinityInimity(currentRelationKey);
|
||||||
|
affinityValue = rerolled.affinityValue;
|
||||||
|
inimityValue = rerolled.inimityValue;
|
||||||
|
affinityRoll = rerolled.affinityRoll;
|
||||||
|
inimityRoll = rerolled.inimityRoll;
|
||||||
|
currentFormulas = rerolled.formulas;
|
||||||
|
sc.newRelationKey = currentRelationKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc.effects.action === 'reRollAffinity') {
|
||||||
|
const reroll = await rollFormula('2d6');
|
||||||
|
const rerolledValue = mapRollToValue(reroll.total, AFFINITY_INIMITY_MAP);
|
||||||
|
if (rerolledValue > affinityValue) {
|
||||||
|
sc.rerollNote = `Affinité relancée : ${reroll.total} → ${rerolledValue} (était ${affinityValue})`;
|
||||||
|
sc.appliedDeltas.affinity = rerolledValue - affinityValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc.effects.action === 'reRollInimity') {
|
||||||
|
const reroll = await rollFormula('2d6');
|
||||||
|
const rerolledValue = mapRollToValue(reroll.total, AFFINITY_INIMITY_MAP);
|
||||||
|
if (rerolledValue > inimityValue) {
|
||||||
|
sc.rerollNote = `Inimitié relancée : ${reroll.total} → ${rerolledValue} (était ${inimityValue})`;
|
||||||
|
sc.appliedDeltas.inimity = rerolledValue - inimityValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc.effects.action === 'swapAffinityInimity') {
|
||||||
|
sc.swapNote = 'Affinité et Inimitié échangées';
|
||||||
|
const tmpAff = affinityValue;
|
||||||
|
const tmpInim = inimityValue;
|
||||||
|
sc.appliedDeltas.affinity = tmpInim - affinityValue;
|
||||||
|
sc.appliedDeltas.inimity = tmpAff - inimityValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc.effects.action === 'setPowerToZero') {
|
||||||
|
sc.appliedDeltas.power = -powerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc.effects.action === 'createEnemy') {
|
||||||
|
sc.narrativeText = 'Un nouvel Ennemi commun au Voyageur et à cet individu est créé.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sc.effects.action === 'createContactOrRival') {
|
||||||
|
const net = affinityValue - inimityValue;
|
||||||
|
sc.narrativeText = net > 0
|
||||||
|
? 'Un nouveau Contact est créé (Affinité supérieure à l\'Inimitié).'
|
||||||
|
: 'Un nouveau Rival est créé (Inimitié supérieure à l\'Affinité).';
|
||||||
|
}
|
||||||
|
|
||||||
|
let newAffinity = affinityValue + (sc.appliedDeltas.affinity || 0);
|
||||||
|
let newInimity = inimityValue + (sc.appliedDeltas.inimity || 0);
|
||||||
|
let newPower = powerValue + (sc.appliedDeltas.power || 0);
|
||||||
|
let newInfluence = influenceValue + (sc.appliedDeltas.influence || 0);
|
||||||
|
|
||||||
|
affinityValue = clamp(newAffinity, 0, 6);
|
||||||
|
inimityValue = clamp(newInimity, 0, 6);
|
||||||
|
powerValue = clamp(newPower, 0, 6);
|
||||||
|
influenceValue = clamp(newInfluence, 0, 6);
|
||||||
|
|
||||||
|
specialCharacteristics.push(sc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalRelation = currentRelationKey !== relationKey
|
||||||
|
? NPC_RELATIONS[currentRelationKey]
|
||||||
|
: relation;
|
||||||
|
|
||||||
|
const netScore = affinityValue - inimityValue;
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
type: 'ally-enemy',
|
||||||
|
relation: { key: currentRelationKey, label: finalRelation.label, summary: finalRelation.summary },
|
||||||
|
originalRelationKey: relationKey,
|
||||||
|
relationChanged: currentRelationKey !== relationKey,
|
||||||
|
|
||||||
|
affinity: {
|
||||||
|
formula: currentFormulas.affinity,
|
||||||
|
roll: affinityRoll?.total ?? 0,
|
||||||
|
value: affinityValue,
|
||||||
|
label: getLabel(affinityValue, AFFINITY_LABELS).label,
|
||||||
|
description: getLabel(affinityValue, AFFINITY_LABELS).description,
|
||||||
|
},
|
||||||
|
inimity: {
|
||||||
|
formula: currentFormulas.inimity,
|
||||||
|
roll: inimityRoll?.total ?? 0,
|
||||||
|
value: inimityValue,
|
||||||
|
label: getLabel(inimityValue, INIMITY_LABELS).label,
|
||||||
|
description: getLabel(inimityValue, INIMITY_LABELS).description,
|
||||||
|
},
|
||||||
|
netScore,
|
||||||
|
|
||||||
|
power: {
|
||||||
|
value: powerValue,
|
||||||
|
label: getLabel(powerValue, POWER_LABELS).label,
|
||||||
|
description: getLabel(powerValue, POWER_LABELS).description,
|
||||||
|
},
|
||||||
|
influence: {
|
||||||
|
value: influenceValue,
|
||||||
|
label: getLabel(influenceValue, INFLUENCE_LABELS).label,
|
||||||
|
description: getLabel(influenceValue, INFLUENCE_LABELS).description,
|
||||||
|
},
|
||||||
|
|
||||||
|
specialRoll: specialRoll ? { roll: specialRoll.total, triggered: specialRoll.total >= 8 } : null,
|
||||||
|
specialCharacteristics,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
export const RELATION_FORMULAS = {
|
||||||
|
ally: { affinity: '2d6', inimity: '0' },
|
||||||
|
contact: { affinity: '1d6+1', inimity: '1d6-1' },
|
||||||
|
rival: { affinity: '1d6-1', inimity: '1d6+1' },
|
||||||
|
enemy: { affinity: '0', inimity: '2d6' },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AFFINITY_INIMITY_MAP = {
|
||||||
|
2: 0, 3: 1, 4: 1, 5: 2, 6: 2,
|
||||||
|
7: 3, 8: 3, 9: 4, 10: 4, 11: 5, 12: 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POWER_INFLUENCE_MAP = {
|
||||||
|
2: 0, 3: 0, 4: 0, 5: 0,
|
||||||
|
6: 1, 7: 1, 8: 2, 9: 3, 10: 4, 11: 5, 12: 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AFFINITY_LABELS = [
|
||||||
|
{ value: 0, label: 'Aucune', description: 'Aucune affinité envers le Voyageur. Peut être un ennemi ou quelqu\'un d\'indifférent selon son Inimitié.' },
|
||||||
|
{ value: 1, label: 'Vaguement bienveillant', description: 'Bienveillance comparable à celle d\'un inconnu ordinaire. Petits gestes d\'entraide par courtoisie.' },
|
||||||
|
{ value: 2, label: 'Bienveillant', description: 'Aidera probablement le Voyageur si simple et sans danger, même sans récompense.' },
|
||||||
|
{ value: 3, label: 'Très bienveillant', description: 'N\'hésitera pas à prendre des risques modérés ou à offrir son aide de son propre chef.' },
|
||||||
|
{ value: 4, label: 'Ami loyal', description: 'Fera presque tout son possible pour aider, mais peut être retenu par d\'autres loyautés.' },
|
||||||
|
{ value: 5, label: 'Amour', description: 'Passera très probablement les intérêts du Voyageur avant les siens ou ceux d\'autrui.' },
|
||||||
|
{ value: 6, label: 'Fanatique', description: 'Fera tout ce que le Voyageur exige, quels que soient les risques.' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const INIMITY_LABELS = [
|
||||||
|
{ value: 0, label: 'Aucune', description: 'Aucune inimitié envers le Voyageur.' },
|
||||||
|
{ value: 1, label: 'Méfiant', description: 'Vaguement mal disposé mais ne fera pas d\'efforts particuliers pour faire obstacle.' },
|
||||||
|
{ value: 2, label: 'Malveillant', description: 'Peut commettre des actes de malveillance mineurs par pure mesquinerie.' },
|
||||||
|
{ value: 3, label: 'Très malveillant', description: 'Se donnera du mal pour faire obstacle au Voyageur par simple rancune.' },
|
||||||
|
{ value: 4, label: 'Haine', description: 'Fera presque tout pour avoir le dessus sur le Voyageur.' },
|
||||||
|
{ value: 5, label: 'Haine farouche', description: 'Complotera activement ou prendra de grands risques pour nuire au Voyageur.' },
|
||||||
|
{ value: 6, label: 'Haine aveugle', description: 'Peut s\'engager dans des actions autodestructrices pour nuire au Voyageur.' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const POWER_LABELS = [
|
||||||
|
{ value: 0, label: 'Négligeable', description: 'Ne dispose pratiquement d\'aucune ressource mobilisable en dehors de ses possessions personnelles.' },
|
||||||
|
{ value: 1, label: 'Faible', description: 'Quelques amis ou contacts. Équivalent d\'un groupe de Voyageurs typique.' },
|
||||||
|
{ value: 2, label: 'Utile', description: 'Possède un atout majeur : petit vaisseau, unité de mercenaires, équipe d\'avocats.' },
|
||||||
|
{ value: 3, label: 'Modérément puissant', description: 'Ressources très importantes : unité de mercenaires ou entreprise de taille moyenne.' },
|
||||||
|
{ value: 4, label: 'Puissant', description: 'Atouts majeurs : compagnie de transport marchand ou grand groupe commercial.' },
|
||||||
|
{ value: 5, label: 'Très puissant', description: 'Pouvoir colossal : haute sphère gouvernementale ou PDG d\'une grande compagnie.' },
|
||||||
|
{ value: 6, label: 'Acteur majeur', description: 'Pèse sur la politique interstellaire : amiral ou haut dignitaire.' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const INFLUENCE_LABELS = [
|
||||||
|
{ value: 0, label: 'Aucune influence', description: 'N\'a pratiquement aucune influence sur qui que ce soit.' },
|
||||||
|
{ value: 1, label: 'Faible influence', description: 'Peut faire jouer quelques faveurs auprès de fonctionnaires mineurs.' },
|
||||||
|
{ value: 2, label: 'Influence modérée', description: 'A un ou plusieurs notables locaux « dans la poche ».' },
|
||||||
|
{ value: 3, label: 'Influent', description: 'Exerce une influence sur des gens de pouvoir (fonctionnaires, négociants).' },
|
||||||
|
{ value: 4, label: 'Très influent', description: 'Influence interplanétaire, personnalités gouvernementales ou figures de la pègre.' },
|
||||||
|
{ value: 5, label: 'Extrêmement influent', description: 'Influence interstellaire, pression sur les législateurs.' },
|
||||||
|
{ value: 6, label: 'Incontournable', description: 'A l\'oreille de personnes extrêmement puissantes (noble dirigeant le sous-secteur).' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const SPECIAL_CHARACTERISTICS_TABLE = [
|
||||||
|
{ d66: 11, text: 'Cet individu a des raisons de pardonner au Voyageur ou de l\'apprécier plus que d\'ordinaire.', effects: { affinityMod: 1 } },
|
||||||
|
{ d66: 12, text: 'Les relations entre le Voyageur et cet individu se sont particulièrement détériorées.', effects: { inimityMod: 1, affinityMod: -1 } },
|
||||||
|
{ d66: 13, text: 'Un événement a altéré la relation entre le Voyageur et cet associé.', effects: { affinityMod: 1, inimityMod: -1 } },
|
||||||
|
{ d66: 14, text: 'Un incident augmente l\'Inimitié entre le Voyageur et cet individu.', effects: { inimityMod: 1 } },
|
||||||
|
{ d66: 15, text: 'La relation devient plus modérée. Un Ennemi devient un Rival et un Allié devient un Contact. Relancez l\'Affinité et l\'Inimitié.', effects: { action: 'moderateRelation' } },
|
||||||
|
{ d66: 16, text: 'La relation s\'intensifie. Un Rival devient un Ennemi et un Contact devient un Allié. Relancez l\'Affinité et l\'Inimitié.', effects: { action: 'intensifyRelation' } },
|
||||||
|
{ d66: 21, text: 'Cet individu gagne en pouvoir.', effects: { powerMod: 1 } },
|
||||||
|
{ d66: 22, text: 'Cet individu perd une partie de sa base de pouvoir.', effects: { powerMod: -1 } },
|
||||||
|
{ d66: 23, text: 'Cet individu gagne en influence.', effects: { influenceMod: 1 } },
|
||||||
|
{ d66: 24, text: 'L\'influence de cet individu diminue.', effects: { influenceMod: -1 } },
|
||||||
|
{ d66: 25, text: 'Cet individu gagne à la fois en pouvoir et en influence.', effects: { powerMod: 1, influenceMod: 1 } },
|
||||||
|
{ d66: 26, text: 'Cet individu perd à la fois en pouvoir et en influence.', effects: { powerMod: -1, influenceMod: -1 } },
|
||||||
|
{ d66: 31, text: 'Cet individu appartient à un groupe culturel ou religieux inhabituel.', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 32, text: 'Cet individu appartient à une xéno-espèce rare.', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 33, text: 'Cet individu est particulièrement atypique (intelligence artificielle ou entité profondément xéno).', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 34, text: 'Cet individu représente en réalité une organisation (mouvement politique, entreprise).', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 35, text: 'Cet individu est membre d\'une organisation dont la vision est généralement opposée à celle du Voyageur.', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 36, text: 'Cet individu est une figure douteuse (criminel, pirate ou noble déchu). Le Voyageur sera jugé par association.', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 41, text: 'Le Voyageur et cet individu se sont violemment brouillés. Relancez l\'Inimitié sur 2D et utilisez le nouveau résultat s\'il est supérieur.', effects: { action: 'reRollInimity' } },
|
||||||
|
{ d66: 42, text: 'Le Voyageur et cet individu se sont réconciliés. Relancez l\'Affinité sur 2D et appliquez le nouveau résultat s\'il est supérieur.', effects: { action: 'reRollAffinity' } },
|
||||||
|
{ d66: 43, text: 'Cet individu traverse une période difficile.', effects: { powerMod: -1 } },
|
||||||
|
{ d66: 44, text: 'Cet individu a été ruiné par un malheur causé par le Voyageur.', effects: { action: 'setPowerToZero', inimityMod: 1 } },
|
||||||
|
{ d66: 45, text: 'Cet individu a gagné en influence grâce à l\'aide du Voyageur.', effects: { influenceMod: 1, affinityMod: 1 } },
|
||||||
|
{ d66: 46, text: 'Cet individu a gagné du pouvoir aux dépens d\'un tiers qui blâme désormais le Voyageur.', effects: { powerMod: 1, action: 'createEnemy' } },
|
||||||
|
{ d66: 51, text: 'Cet individu a disparu dans des circonstances suspectes.', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 52, text: 'Cet individu est injoignable, occupé à quelque chose d\'intéressant mais sans caractère suspect.', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 53, text: 'Cet individu est en grave difficulté et aurait bien besoin de l\'aide du Voyageur.', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 54, text: 'Cet individu a récemment bénéficié d\'une chance insolente.', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 55, text: 'Cet individu est incarcéré ou piégé quelque part.', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 56, text: 'Cet individu est retrouvé ou déclaré mort. Ce n\'est peut-être pas toute la vérité…', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 61, text: 'Cet individu s\'est récemment marié ou a vécu un événement bouleversant sa vie.', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 62, text: 'Cet individu a été renié par sa famille, a divorcé ou a vécu un événement tragique.', effects: { action: 'narrativeOnly' } },
|
||||||
|
{ d66: 63, text: 'Les relations de cet individu commencent à affecter le Voyageur. Créez un nouveau Contact si son Affinité est supérieure à son Inimitié, ou un Rival si l\'Inimitié est supérieure.', effects: { action: 'createContactOrRival' } },
|
||||||
|
{ d66: 64, text: 'La relation entre le Voyageur et cet associé est complètement redéfinie. Alliés↔Ennemis, Rivaux↔Contacts. Échangez les valeurs d\'Affinité et d\'Inimitié.', effects: { action: 'swapAffinityInimity' } },
|
||||||
|
{ d66: 65, text: 'Tirez deux autres caractéristiques spéciales.', effects: { action: 'extraRolls', actionValue: 2 } },
|
||||||
|
{ d66: 66, text: 'Tirez trois autres caractéristiques spéciales.', effects: { action: 'extraRolls', actionValue: 3 } },
|
||||||
|
];
|
||||||
@@ -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;
|
if (!skillId || !skills?.[skillId]) return skills;
|
||||||
|
|
||||||
const numericLevel = Number(level ?? 0);
|
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]) {
|
// Marquer la compétence comme entraînée si niveau > 0
|
||||||
skill.specialities[specialityId] = foundry.utils.mergeObject(skill.specialities[specialityId], {
|
skill.trained = numericLevel > 0 || skill.trained;
|
||||||
value: Math.max(Number(skill.specialities[specialityId].value ?? 0), numericLevel),
|
|
||||||
});
|
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 {
|
} else {
|
||||||
|
// Compétence sans spécialité
|
||||||
skill.value = Math.max(Number(skill.value ?? 0), numericLevel);
|
skill.value = Math.max(Number(skill.value ?? 0), numericLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user