11 Commits

Author SHA1 Message Date
uberwald f4ba85bcb9 Ajout de la gestion correcte calendrier+notes+sauvegarde position 2026-07-25 17:42:16 +02:00
uberwald 47442be3ef feat: notes de bord et sauvegarde position calendrier
- bouton crayon sur la fenêtre calendrier (MJ) → dialogue de note
- date impériale + date réelle préremplies
- append au journal configuré (créé auto si inexistant)
- appendJournalNote : labels passés depuis l'ouverture (pas de re-lecture)
- échappement XSS via jQuery .text().html()
- onSave async + await + try/catch
- sauvegarde position calendrier (client scope, debounced 300ms)
- restore position au constructeur
- close() async avec await settings.set
- boutons note et config groupés dans .mgt2-cal-actions (flex row)
2026-07-25 17:08:54 +02:00
uberwald 8882cf8ee2 Compendiums+calendrier 2026-07-23 23:29:19 +02:00
uberwald 00bcc775fe feat: calendrier impérial et conversions calendaires
- calendrier impérial : fenêtre compacte (260px), affichage date
- commande /cal, auto-ouverture paramétrable dans les settings
- socket calendarSync : mise à jour temps réel pour les joueurs
- config MJ : année, jour (label live), heure:minute, planète
- boutons +1 jour et +1 heure (report 24h→jour, 365→1)
- conversions : Solomani, Vilani, Zhodani (Olympiade), Aslan, K'Kree, Hiver
- formules wiki.travellerrpg.com/Date_Conversion, section repliable
- fix: renderChatInput /cal trop large → exact match
- fix: fenêtre refermable (onClose nullifie la référence)
- fix: socket joueur lit data direct au lieu de readCalendar()
2026-07-23 23:28:49 +02:00
uberwald 5a4d4c45dc Sync compendiums
Release Creation / build (release) Successful in 1m5s
2026-07-09 07:53:44 +02:00
uberwald 7fc8d2c09c feat: batch NPC import, ally/enemy gen, map sync, UI shortcuts
- batch NPC creator: import images from dir → actors (dedup, folder)
- ally/enemy generator: data tables, D66 specials, tests (39 pass)
- sector map sync: DialogV2 player view, socket v13/v14, sync click pos
- actor/scene directory: shortcut buttons (Commerce, PNJ, Map, Sync vue)
- closeImagePopout: GM closes player portraits via socket
- canvasSync: force same canvas view on all players
- FilePicker: v14 folder picker support with v13 fallback
- cleanup: debug logs, template dedup, return guards
2026-07-09 07:49:12 +02:00
uberwald bfe24812bd Auto-import PNJ3 2026-07-07 21:01:57 +02:00
uberwald 746f9e23bb Auto-import PNJ3 2026-07-07 18:59:34 +02:00
uberwald a53c7ace53 Ready for release
Release Creation / build (release) Successful in 43s
2026-06-12 20:53:44 +02:00
uberwald efe37b8a96 MAp management and helpers 2026-06-02 00:16:08 +02:00
uberwald 49423f40f5 AJout gestion map 2026-06-01 22:51:48 +02:00
115 changed files with 4838 additions and 328 deletions
+73
View File
@@ -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.
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
testMatch: ['**/scripts/tests/*.test.js'],
transform: {},
};
+77 -2
View File
@@ -7,23 +7,87 @@
"verified": "13", "verified": "13",
"maximum": "14" "maximum": "14"
}, },
"socket": "true",
"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.", "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/batchNpcCreator.js",
"scripts/commerce.js", "scripts/commerce.js",
"scripts/npc.js", "scripts/npc.js",
"scripts/sector.js",
"scripts/sectorSocket.js",
"scripts/utils/travellerNpcUtils.js", "scripts/utils/travellerNpcUtils.js",
"scripts/data/travellerNpcGenerator.js", "scripts/data/travellerNpcGenerator.js",
"scripts/travellerNpcGenerator.js", "scripts/travellerNpcGenerator.js",
"scripts/TravellerNpcDialog.js", "scripts/TravellerNpcDialog.js",
"scripts/mgt2eMigration.js", "scripts/mgt2eMigration.js",
"scripts/npcRollTableSync.js", "scripts/npcRollTableSync.js",
"scripts/mgt2eSkills.js" "scripts/mgt2eSkills.js",
"scripts/imperialCalendar.js"
], ],
"styles": [ "styles": [
"styles/commerce.css", "styles/commerce.css",
"styles/npc.css", "styles/npc.css",
"styles/traveller-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",
@@ -115,7 +179,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",
@@ -167,6 +231,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"
}
} }
] ]
} }
+4
View File
@@ -0,0 +1,4 @@
{
"type": "module",
"private": true
}
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000084 MANIFEST-000180
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-23:11:58.414164 7f3e6f7fe6c0 Recovering log #82 2026/07/25-16:54:40.235374 7fb397fff6c0 Recovering log #178
2026/05/27-23:11:58.424222 7f3e6f7fe6c0 Delete type=3 #80 2026/07/25-16:54:40.246219 7fb397fff6c0 Delete type=3 #176
2026/05/27-23:11:58.424274 7f3e6f7fe6c0 Delete type=0 #82 2026/07/25-16:54:40.246321 7fb397fff6c0 Delete type=0 #178
2026/05/28-01:07:03.306861 7f3e6effd6c0 Level-0 table #87: started 2026/07/25-17:22:36.365586 7fb394b4f6c0 Level-0 table #183: started
2026/05/28-01:07:03.306872 7f3e6effd6c0 Level-0 table #87: 0 bytes OK 2026/07/25-17:22:36.365618 7fb394b4f6c0 Level-0 table #183: 0 bytes OK
2026/05/28-01:07:03.314015 7f3e6effd6c0 Delete type=0 #85 2026/07/25-17:22:36.372300 7fb394b4f6c0 Delete type=0 #181
2026/05/28-01:07:03.314175 7f3e6effd6c0 Manual compaction at level-0 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.379902 7fb394b4f6c0 Manual compaction at level-0 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end)
2026/05/28-01:07:03.314198 7f3e6effd6c0 Manual compaction at level-1 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.380052 7fb394b4f6c0 Manual compaction at level-1 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-14:31:25.639035 7f3e6f7fe6c0 Recovering log #77 2026/07/25-16:21:45.460294 7fb3977fe6c0 Recovering log #174
2026/05/27-14:31:25.648169 7f3e6f7fe6c0 Delete type=3 #75 2026/07/25-16:21:45.470482 7fb3977fe6c0 Delete type=3 #172
2026/05/27-14:31:25.648196 7f3e6f7fe6c0 Delete type=0 #77 2026/07/25-16:21:45.470569 7fb3977fe6c0 Delete type=0 #174
2026/05/27-14:33:21.786747 7f3e6effd6c0 Level-0 table #83: started 2026/07/25-16:34:16.350463 7fb394b4f6c0 Level-0 table #179: started
2026/05/27-14:33:21.786766 7f3e6effd6c0 Level-0 table #83: 0 bytes OK 2026/07/25-16:34:16.350531 7fb394b4f6c0 Level-0 table #179: 0 bytes OK
2026/05/27-14:33:21.793932 7f3e6effd6c0 Delete type=0 #81 2026/07/25-16:34:16.357551 7fb394b4f6c0 Delete type=0 #177
2026/05/27-14:33:21.800554 7f3e6effd6c0 Manual compaction at level-0 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.510151 7fb394b4f6c0 Manual compaction at level-0 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end)
2026/05/27-14:33:21.800579 7f3e6effd6c0 Manual compaction at level-1 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.680246 7fb394b4f6c0 Manual compaction at level-1 from '!folders!673DRfEBYUliGnKJ' @ 72057594037927935 : 1 .. '!items!yoIqL0RQEnzNVJB6' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000183 MANIFEST-000279
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-23:11:58.290859 7f3e6f7fe6c0 Recovering log #181 2026/07/25-16:54:40.104032 7fb3e4fff6c0 Recovering log #277
2026/05/27-23:11:58.307573 7f3e6f7fe6c0 Delete type=3 #179 2026/07/25-16:54:40.114576 7fb3e4fff6c0 Delete type=3 #275
2026/05/27-23:11:58.307626 7f3e6f7fe6c0 Delete type=0 #181 2026/07/25-16:54:40.114680 7fb3e4fff6c0 Delete type=0 #277
2026/05/28-01:07:03.251844 7f3e6effd6c0 Level-0 table #186: started 2026/07/25-17:22:36.288710 7fb394b4f6c0 Level-0 table #282: started
2026/05/28-01:07:03.251857 7f3e6effd6c0 Level-0 table #186: 0 bytes OK 2026/07/25-17:22:36.288757 7fb394b4f6c0 Level-0 table #282: 0 bytes OK
2026/05/28-01:07:03.257706 7f3e6effd6c0 Delete type=0 #184 2026/07/25-17:22:36.295586 7fb394b4f6c0 Delete type=0 #280
2026/05/28-01:07:03.263618 7f3e6effd6c0 Manual compaction at level-0 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.302495 7fb394b4f6c0 Manual compaction at level-0 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end)
2026/05/28-01:07:03.275995 7f3e6effd6c0 Manual compaction at level-1 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.302790 7fb394b4f6c0 Manual compaction at level-1 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-14:31:25.531736 7f3ebd7ff6c0 Recovering log #176 2026/07/25-16:21:45.335501 7fb397fff6c0 Recovering log #273
2026/05/27-14:31:25.541455 7f3ebd7ff6c0 Delete type=3 #174 2026/07/25-16:21:45.346461 7fb397fff6c0 Delete type=3 #271
2026/05/27-14:31:25.541507 7f3ebd7ff6c0 Delete type=0 #176 2026/07/25-16:21:45.346548 7fb397fff6c0 Delete type=0 #273
2026/05/27-14:33:21.722465 7f3e6effd6c0 Level-0 table #182: started 2026/07/25-16:34:16.287513 7fb394b4f6c0 Level-0 table #278: started
2026/05/27-14:33:21.722495 7f3e6effd6c0 Level-0 table #182: 0 bytes OK 2026/07/25-16:34:16.287557 7fb394b4f6c0 Level-0 table #278: 0 bytes OK
2026/05/27-14:33:21.728923 7f3e6effd6c0 Delete type=0 #180 2026/07/25-16:34:16.294033 7fb394b4f6c0 Delete type=0 #276
2026/05/27-14:33:21.735721 7f3e6effd6c0 Manual compaction at level-0 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.294225 7fb394b4f6c0 Manual compaction at level-0 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end)
2026/05/27-14:33:21.735741 7f3e6effd6c0 Manual compaction at level-1 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.301494 7fb394b4f6c0 Manual compaction at level-1 from '!items!8xqChkoKK7i0c9M1' @ 72057594037927935 : 1 .. '!items!wpBopoosZiWXjlKD' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000067 MANIFEST-000163
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-23:11:58.442577 7f3e6f7fe6c0 Recovering log #65 2026/07/25-16:54:40.265755 7fb397fff6c0 Recovering log #161
2026/05/27-23:11:58.452097 7f3e6f7fe6c0 Delete type=3 #63 2026/07/25-16:54:40.276688 7fb397fff6c0 Delete type=3 #159
2026/05/27-23:11:58.452144 7f3e6f7fe6c0 Delete type=0 #65 2026/07/25-16:54:40.276786 7fb397fff6c0 Delete type=0 #161
2026/05/28-01:07:03.320241 7f3e6effd6c0 Level-0 table #70: started 2026/07/25-17:22:36.380101 7fb394b4f6c0 Level-0 table #166: started
2026/05/28-01:07:03.320254 7f3e6effd6c0 Level-0 table #70: 0 bytes OK 2026/07/25-17:22:36.380168 7fb394b4f6c0 Level-0 table #166: 0 bytes OK
2026/05/28-01:07:03.326319 7f3e6effd6c0 Delete type=0 #68 2026/07/25-17:22:36.386681 7fb394b4f6c0 Delete type=0 #164
2026/05/28-01:07:03.356879 7f3e6effd6c0 Manual compaction at level-0 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.535741 7fb394b4f6c0 Manual compaction at level-0 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end)
2026/05/28-01:07:03.381785 7f3e6effd6c0 Manual compaction at level-1 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.806569 7fb394b4f6c0 Manual compaction at level-1 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-14:31:25.663213 7f3e6f7fe6c0 Recovering log #60 2026/07/25-16:21:45.488403 7fb397fff6c0 Recovering log #157
2026/05/27-14:31:25.672282 7f3e6f7fe6c0 Delete type=3 #58 2026/07/25-16:21:45.499075 7fb397fff6c0 Delete type=3 #155
2026/05/27-14:31:25.672315 7f3e6f7fe6c0 Delete type=0 #60 2026/07/25-16:21:45.499160 7fb397fff6c0 Delete type=0 #157
2026/05/27-14:33:21.794049 7f3e6effd6c0 Level-0 table #66: started 2026/07/25-16:34:16.357718 7fb394b4f6c0 Level-0 table #162: started
2026/05/27-14:33:21.794078 7f3e6effd6c0 Level-0 table #66: 0 bytes OK 2026/07/25-16:34:16.357749 7fb394b4f6c0 Level-0 table #162: 0 bytes OK
2026/05/27-14:33:21.800426 7f3e6effd6c0 Delete type=0 #64 2026/07/25-16:34:16.364285 7fb394b4f6c0 Delete type=0 #160
2026/05/27-14:33:21.808266 7f3e6effd6c0 Manual compaction at level-0 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.510175 7fb394b4f6c0 Manual compaction at level-0 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end)
2026/05/27-14:33:21.831733 7f3e6effd6c0 Manual compaction at level-1 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.680277 7fb394b4f6c0 Manual compaction at level-1 from '!items!57vgsVVCy9MRKM2M' @ 72057594037927935 : 1 .. '!items!vJInnoigCTJzuY2S' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000186 MANIFEST-000282
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-23:11:58.309946 7f3ebcffe6c0 Recovering log #184 2026/07/25-16:54:40.118657 7fb3977fe6c0 Recovering log #280
2026/05/27-23:11:58.320486 7f3ebcffe6c0 Delete type=3 #182 2026/07/25-16:54:40.129567 7fb3977fe6c0 Delete type=3 #278
2026/05/27-23:11:58.320525 7f3ebcffe6c0 Delete type=0 #184 2026/07/25-16:54:40.129662 7fb3977fe6c0 Delete type=0 #280
2026/05/28-01:07:03.257742 7f3e6effd6c0 Level-0 table #189: started 2026/07/25-17:22:36.295712 7fb394b4f6c0 Level-0 table #285: started
2026/05/28-01:07:03.257754 7f3e6effd6c0 Level-0 table #189: 0 bytes OK 2026/07/25-17:22:36.295740 7fb394b4f6c0 Level-0 table #285: 0 bytes OK
2026/05/28-01:07:03.263574 7f3e6effd6c0 Delete type=0 #187 2026/07/25-17:22:36.302356 7fb394b4f6c0 Delete type=0 #283
2026/05/28-01:07:03.263715 7f3e6effd6c0 Manual compaction at level-0 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.302757 7fb394b4f6c0 Manual compaction at level-0 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end)
2026/05/28-01:07:03.275990 7f3e6effd6c0 Manual compaction at level-1 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.317403 7fb394b4f6c0 Manual compaction at level-1 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-14:31:25.544106 7f3ebd7ff6c0 Recovering log #179 2026/07/25-16:21:45.349559 7fb396ffd6c0 Recovering log #276
2026/05/27-14:31:25.554048 7f3ebd7ff6c0 Delete type=3 #177 2026/07/25-16:21:45.359598 7fb396ffd6c0 Delete type=3 #274
2026/05/27-14:31:25.554071 7f3ebd7ff6c0 Delete type=0 #179 2026/07/25-16:21:45.359698 7fb396ffd6c0 Delete type=0 #276
2026/05/27-14:33:21.729016 7f3e6effd6c0 Level-0 table #185: started 2026/07/25-16:34:16.308405 7fb394b4f6c0 Level-0 table #281: started
2026/05/27-14:33:21.729040 7f3e6effd6c0 Level-0 table #185: 0 bytes OK 2026/07/25-16:34:16.308432 7fb394b4f6c0 Level-0 table #281: 0 bytes OK
2026/05/27-14:33:21.735577 7f3e6effd6c0 Delete type=0 #183 2026/07/25-16:34:16.316009 7fb394b4f6c0 Delete type=0 #279
2026/05/27-14:33:21.735798 7f3e6effd6c0 Manual compaction at level-0 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.322893 7fb394b4f6c0 Manual compaction at level-0 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end)
2026/05/27-14:33:21.735816 7f3e6effd6c0 Manual compaction at level-1 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.323046 7fb394b4f6c0 Manual compaction at level-1 from '!items!04MdBSzwkYWUMJBC' @ 72057594037927935 : 1 .. '!items!yqjKyTCgpclCuHyK' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000120 MANIFEST-000216
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/27-23:11:58.375862 7f3e6ffff6c0 Recovering log #118 2026/07/25-16:54:40.192611 7fb3e4fff6c0 Recovering log #214
2026/05/27-23:11:58.385912 7f3e6ffff6c0 Delete type=3 #116 2026/07/25-16:54:40.203144 7fb3e4fff6c0 Delete type=3 #212
2026/05/27-23:11:58.385947 7f3e6ffff6c0 Delete type=0 #118 2026/07/25-16:54:40.203218 7fb3e4fff6c0 Delete type=0 #214
2026/05/28-01:07:03.287852 7f3e6effd6c0 Level-0 table #123: started 2026/07/25-17:22:36.334207 7fb394b4f6c0 Level-0 table #219: started
2026/05/28-01:07:03.287863 7f3e6effd6c0 Level-0 table #123: 0 bytes OK 2026/07/25-17:22:36.334237 7fb394b4f6c0 Level-0 table #219: 0 bytes OK
2026/05/28-01:07:03.294442 7f3e6effd6c0 Delete type=0 #121 2026/07/25-17:22:36.344490 7fb394b4f6c0 Delete type=0 #217
2026/05/28-01:07:03.300889 7f3e6effd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.358562 7fb394b4f6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/27-14:31:25.604521 7f3ebd7ff6c0 Recovering log #114 2026/07/25-16:21:45.418689 7fb397fff6c0 Recovering log #210
2026/05/27-14:31:25.613630 7f3ebd7ff6c0 Delete type=3 #112 2026/07/25-16:21:45.429568 7fb397fff6c0 Delete type=3 #208
2026/05/27-14:31:25.613651 7f3ebd7ff6c0 Delete type=0 #114 2026/07/25-16:21:45.429651 7fb397fff6c0 Delete type=0 #210
2026/05/27-14:33:21.767043 7f3e6effd6c0 Level-0 table #119: started 2026/07/25-16:34:16.329833 7fb394b4f6c0 Level-0 table #215: started
2026/05/27-14:33:21.767067 7f3e6effd6c0 Level-0 table #119: 0 bytes OK 2026/07/25-16:34:16.329859 7fb394b4f6c0 Level-0 table #215: 0 bytes OK
2026/05/27-14:33:21.773608 7f3e6effd6c0 Delete type=0 #117 2026/07/25-16:34:16.336695 7fb394b4f6c0 Delete type=0 #213
2026/05/27-14:33:21.780095 7f3e6effd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.350122 7fb394b4f6c0 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
View File
@@ -1 +1 @@
MANIFEST-000128 MANIFEST-000224
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-23:11:58.349736 7f3e6ffff6c0 Recovering log #126 2026/07/25-16:54:40.162927 7fb3e4fff6c0 Recovering log #222
2026/05/27-23:11:58.359183 7f3e6ffff6c0 Delete type=3 #124 2026/07/25-16:54:40.173541 7fb3e4fff6c0 Delete type=3 #220
2026/05/27-23:11:58.359211 7f3e6ffff6c0 Delete type=0 #126 2026/07/25-16:54:40.173643 7fb3e4fff6c0 Delete type=0 #222
2026/05/28-01:07:03.276071 7f3e6effd6c0 Level-0 table #131: started 2026/07/25-17:22:36.324212 7fb394b4f6c0 Level-0 table #227: started
2026/05/28-01:07:03.276086 7f3e6effd6c0 Level-0 table #131: 0 bytes OK 2026/07/25-17:22:36.324245 7fb394b4f6c0 Level-0 table #227: 0 bytes OK
2026/05/28-01:07:03.281962 7f3e6effd6c0 Delete type=0 #129 2026/07/25-17:22:36.333994 7fb394b4f6c0 Delete type=0 #225
2026/05/28-01:07:03.300877 7f3e6effd6c0 Manual compaction at level-0 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.358543 7fb394b4f6c0 Manual compaction at level-0 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end)
2026/05/28-01:07:03.300894 7f3e6effd6c0 Manual compaction at level-1 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.358932 7fb394b4f6c0 Manual compaction at level-1 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-14:31:25.580201 7f3ebd7ff6c0 Recovering log #121 2026/07/25-16:21:45.391004 7fb397fff6c0 Recovering log #218
2026/05/27-14:31:25.589450 7f3ebd7ff6c0 Delete type=3 #119 2026/07/25-16:21:45.401154 7fb397fff6c0 Delete type=3 #216
2026/05/27-14:31:25.589494 7f3ebd7ff6c0 Delete type=0 #121 2026/07/25-16:21:45.401258 7fb397fff6c0 Delete type=0 #218
2026/05/27-14:33:21.747566 7f3e6effd6c0 Level-0 table #127: started 2026/07/25-16:34:16.316171 7fb394b4f6c0 Level-0 table #223: started
2026/05/27-14:33:21.747585 7f3e6effd6c0 Level-0 table #127: 0 bytes OK 2026/07/25-16:34:16.316199 7fb394b4f6c0 Level-0 table #223: 0 bytes OK
2026/05/27-14:33:21.754474 7f3e6effd6c0 Delete type=0 #125 2026/07/25-16:34:16.322746 7fb394b4f6c0 Delete type=0 #221
2026/05/27-14:33:21.760899 7f3e6effd6c0 Manual compaction at level-0 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.323022 7fb394b4f6c0 Manual compaction at level-0 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end)
2026/05/27-14:33:21.760915 7f3e6effd6c0 Manual compaction at level-1 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.329820 7fb394b4f6c0 Manual compaction at level-1 from '!folders!8swFcTr6RH7BnGiu' @ 72057594037927935 : 1 .. '!items!zRJfxioYBRq4iSBR' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000120 MANIFEST-000216
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/27-23:11:58.388239 7f3e6f7fe6c0 Recovering log #118 2026/07/25-16:54:40.207018 7fb396ffd6c0 Recovering log #214
2026/05/27-23:11:58.398684 7f3e6f7fe6c0 Delete type=3 #116 2026/07/25-16:54:40.218111 7fb396ffd6c0 Delete type=3 #212
2026/05/27-23:11:58.398724 7f3e6f7fe6c0 Delete type=0 #118 2026/07/25-16:54:40.218206 7fb396ffd6c0 Delete type=0 #214
2026/05/28-01:07:03.294489 7f3e6effd6c0 Level-0 table #123: started 2026/07/25-17:22:36.344663 7fb394b4f6c0 Level-0 table #219: started
2026/05/28-01:07:03.294501 7f3e6effd6c0 Level-0 table #123: 0 bytes OK 2026/07/25-17:22:36.344695 7fb394b4f6c0 Level-0 table #219: 0 bytes OK
2026/05/28-01:07:03.300827 7f3e6effd6c0 Delete type=0 #121 2026/07/25-17:22:36.351019 7fb394b4f6c0 Delete type=0 #217
2026/05/28-01:07:03.314060 7f3e6effd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.358578 7fb394b4f6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/27-14:31:25.615623 7f3ebd7ff6c0 Recovering log #114 2026/07/25-16:21:45.432370 7fb3e4fff6c0 Recovering log #210
2026/05/27-14:31:25.625348 7f3ebd7ff6c0 Delete type=3 #112 2026/07/25-16:21:45.442645 7fb3e4fff6c0 Delete type=3 #208
2026/05/27-14:31:25.625369 7f3ebd7ff6c0 Delete type=0 #114 2026/07/25-16:21:45.442704 7fb3e4fff6c0 Delete type=0 #210
2026/05/27-14:33:21.761003 7f3e6effd6c0 Level-0 table #119: started 2026/07/25-16:34:16.336838 7fb394b4f6c0 Level-0 table #215: started
2026/05/27-14:33:21.761022 7f3e6effd6c0 Level-0 table #119: 0 bytes OK 2026/07/25-16:34:16.336867 7fb394b4f6c0 Level-0 table #215: 0 bytes OK
2026/05/27-14:33:21.766953 7f3e6effd6c0 Delete type=0 #117 2026/07/25-16:34:16.343246 7fb394b4f6c0 Delete type=0 #213
2026/05/27-14:33:21.780083 7f3e6effd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.350151 7fb394b4f6c0 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
View File
@@ -1 +1 @@
MANIFEST-000101 MANIFEST-000197
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-23:11:58.400455 7f3ebd7ff6c0 Recovering log #99 2026/07/25-16:54:40.221457 7fb3e4fff6c0 Recovering log #195
2026/05/27-23:11:58.410587 7f3ebd7ff6c0 Delete type=3 #97 2026/07/25-16:54:40.231541 7fb3e4fff6c0 Delete type=3 #193
2026/05/27-23:11:58.410645 7f3ebd7ff6c0 Delete type=0 #99 2026/07/25-16:54:40.231630 7fb3e4fff6c0 Delete type=0 #195
2026/05/28-01:07:03.300932 7f3e6effd6c0 Level-0 table #104: started 2026/07/25-17:22:36.358974 7fb394b4f6c0 Level-0 table #200: started
2026/05/28-01:07:03.300945 7f3e6effd6c0 Level-0 table #104: 0 bytes OK 2026/07/25-17:22:36.359013 7fb394b4f6c0 Level-0 table #200: 0 bytes OK
2026/05/28-01:07:03.306823 7f3e6effd6c0 Delete type=0 #102 2026/07/25-17:22:36.365407 7fb394b4f6c0 Delete type=0 #198
2026/05/28-01:07:03.314070 7f3e6effd6c0 Manual compaction at level-0 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.379885 7fb394b4f6c0 Manual compaction at level-0 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end)
2026/05/28-01:07:03.314229 7f3e6effd6c0 Manual compaction at level-1 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.380062 7fb394b4f6c0 Manual compaction at level-1 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-14:31:25.627132 7f3ebd7ff6c0 Recovering log #95 2026/07/25-16:21:45.445936 7fb397fff6c0 Recovering log #191
2026/05/27-14:31:25.636967 7f3ebd7ff6c0 Delete type=3 #93 2026/07/25-16:21:45.456628 7fb397fff6c0 Delete type=3 #189
2026/05/27-14:31:25.637002 7f3ebd7ff6c0 Delete type=0 #95 2026/07/25-16:21:45.456706 7fb397fff6c0 Delete type=0 #191
2026/05/27-14:33:21.773684 7f3e6effd6c0 Level-0 table #100: started 2026/07/25-16:34:16.343456 7fb394b4f6c0 Level-0 table #196: started
2026/05/27-14:33:21.773700 7f3e6effd6c0 Level-0 table #100: 0 bytes OK 2026/07/25-16:34:16.343486 7fb394b4f6c0 Level-0 table #196: 0 bytes OK
2026/05/27-14:33:21.779958 7f3e6effd6c0 Delete type=0 #98 2026/07/25-16:34:16.349952 7fb394b4f6c0 Delete type=0 #194
2026/05/27-14:33:21.786739 7f3e6effd6c0 Manual compaction at level-0 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.350230 7fb394b4f6c0 Manual compaction at level-0 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end)
2026/05/27-14:33:21.800533 7f3e6effd6c0 Manual compaction at level-1 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.350280 7fb394b4f6c0 Manual compaction at level-1 from '!journal!26ZqV9BvS47hrLmV' @ 72057594037927935 : 1 .. '!journal.pages!26ZqV9BvS47hrLmV.ZS4936SEQUT9IA8i' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000128 MANIFEST-000224
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-23:11:58.323722 7f3e6f7fe6c0 Recovering log #126 2026/07/25-16:54:40.134268 7fb3e4fff6c0 Recovering log #222
2026/05/27-23:11:58.334612 7f3e6f7fe6c0 Delete type=3 #124 2026/07/25-16:54:40.144763 7fb3e4fff6c0 Delete type=3 #220
2026/05/27-23:11:58.334668 7f3e6f7fe6c0 Delete type=0 #126 2026/07/25-16:54:40.144868 7fb3e4fff6c0 Delete type=0 #222
2026/05/28-01:07:03.263720 7f3e6effd6c0 Level-0 table #131: started 2026/07/25-17:22:36.302800 7fb394b4f6c0 Level-0 table #227: started
2026/05/28-01:07:03.263735 7f3e6effd6c0 Level-0 table #131: 0 bytes OK 2026/07/25-17:22:36.302838 7fb394b4f6c0 Level-0 table #227: 0 bytes OK
2026/05/28-01:07:03.269766 7f3e6effd6c0 Delete type=0 #129 2026/07/25-17:22:36.309558 7fb394b4f6c0 Delete type=0 #225
2026/05/28-01:07:03.275999 7f3e6effd6c0 Manual compaction at level-0 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.324180 7fb394b4f6c0 Manual compaction at level-0 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end)
2026/05/28-01:07:03.276022 7f3e6effd6c0 Manual compaction at level-1 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.334192 7fb394b4f6c0 Manual compaction at level-1 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-14:31:25.557570 7f3ebd7ff6c0 Recovering log #121 2026/07/25-16:21:45.362950 7fb397fff6c0 Recovering log #218
2026/05/27-14:31:25.567130 7f3ebd7ff6c0 Delete type=3 #119 2026/07/25-16:21:45.374396 7fb397fff6c0 Delete type=3 #216
2026/05/27-14:31:25.567150 7f3ebd7ff6c0 Delete type=0 #121 2026/07/25-16:21:45.374487 7fb397fff6c0 Delete type=0 #218
2026/05/27-14:33:21.735841 7f3e6effd6c0 Level-0 table #127: started 2026/07/25-16:34:16.294555 7fb394b4f6c0 Level-0 table #223: started
2026/05/27-14:33:21.735866 7f3e6effd6c0 Level-0 table #127: 0 bytes OK 2026/07/25-16:34:16.294610 7fb394b4f6c0 Level-0 table #223: 0 bytes OK
2026/05/27-14:33:21.741712 7f3e6effd6c0 Delete type=0 #125 2026/07/25-16:34:16.301357 7fb394b4f6c0 Delete type=0 #221
2026/05/27-14:33:21.760883 7f3e6effd6c0 Manual compaction at level-0 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.316155 7fb394b4f6c0 Manual compaction at level-0 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end)
2026/05/27-14:33:21.760906 7f3e6effd6c0 Manual compaction at level-1 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.322906 7fb394b4f6c0 Manual compaction at level-1 from '!items!QHovFMj93BC7bqBu' @ 72057594037927935 : 1 .. '!items!yleVHgRqGoYLvzxT' @ 0 : 0; will stop at (end)
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000120 MANIFEST-000216
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/27-23:11:58.337311 7f3ebcffe6c0 Recovering log #118 2026/07/25-16:54:40.148506 7fb3977fe6c0 Recovering log #214
2026/05/27-23:11:58.347349 7f3ebcffe6c0 Delete type=3 #116 2026/07/25-16:54:40.159742 7fb3977fe6c0 Delete type=3 #212
2026/05/27-23:11:58.347392 7f3ebcffe6c0 Delete type=0 #118 2026/07/25-16:54:40.159842 7fb3977fe6c0 Delete type=0 #214
2026/05/28-01:07:03.269795 7f3e6effd6c0 Level-0 table #123: started 2026/07/25-17:22:36.317424 7fb394b4f6c0 Level-0 table #219: started
2026/05/28-01:07:03.269808 7f3e6effd6c0 Level-0 table #123: 0 bytes OK 2026/07/25-17:22:36.317453 7fb394b4f6c0 Level-0 table #219: 0 bytes OK
2026/05/28-01:07:03.275958 7f3e6effd6c0 Delete type=0 #121 2026/07/25-17:22:36.324008 7fb394b4f6c0 Delete type=0 #217
2026/05/28-01:07:03.276003 7f3e6effd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.334172 7fb394b4f6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
+7 -7
View File
@@ -1,7 +1,7 @@
2026/05/27-14:31:25.569405 7f3ebd7ff6c0 Recovering log #114 2026/07/25-16:21:45.377631 7fb3e4fff6c0 Recovering log #210
2026/05/27-14:31:25.578632 7f3ebd7ff6c0 Delete type=3 #112 2026/07/25-16:21:45.388254 7fb3e4fff6c0 Delete type=3 #208
2026/05/27-14:31:25.578657 7f3ebd7ff6c0 Delete type=0 #114 2026/07/25-16:21:45.388347 7fb3e4fff6c0 Delete type=0 #210
2026/05/27-14:33:21.741780 7f3e6effd6c0 Level-0 table #119: started 2026/07/25-16:34:16.301508 7fb394b4f6c0 Level-0 table #215: started
2026/05/27-14:33:21.741799 7f3e6effd6c0 Level-0 table #119: 0 bytes OK 2026/07/25-16:34:16.301533 7fb394b4f6c0 Level-0 table #215: 0 bytes OK
2026/05/27-14:33:21.747507 7f3e6effd6c0 Delete type=0 #117 2026/07/25-16:34:16.308270 7fb394b4f6c0 Delete type=0 #213
2026/05/27-14:33:21.760892 7f3e6effd6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.322878 7fb394b4f6c0 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
View File
@@ -1 +1 @@
MANIFEST-000128 MANIFEST-000224
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-23:11:58.362926 7f3ebd7ff6c0 Recovering log #126 2026/07/25-16:54:40.177967 7fb397fff6c0 Recovering log #222
2026/05/27-23:11:58.372894 7f3ebd7ff6c0 Delete type=3 #124 2026/07/25-16:54:40.189174 7fb397fff6c0 Delete type=3 #220
2026/05/27-23:11:58.372934 7f3ebd7ff6c0 Delete type=0 #126 2026/07/25-16:54:40.189264 7fb397fff6c0 Delete type=0 #222
2026/05/28-01:07:03.281993 7f3e6effd6c0 Level-0 table #131: started 2026/07/25-17:22:36.351191 7fb394b4f6c0 Level-0 table #227: started
2026/05/28-01:07:03.282003 7f3e6effd6c0 Level-0 table #131: 0 bytes OK 2026/07/25-17:22:36.351222 7fb394b4f6c0 Level-0 table #227: 0 bytes OK
2026/05/28-01:07:03.287823 7f3e6effd6c0 Delete type=0 #129 2026/07/25-17:22:36.358358 7fb394b4f6c0 Delete type=0 #225
2026/05/28-01:07:03.300883 7f3e6effd6c0 Manual compaction at level-0 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.358960 7fb394b4f6c0 Manual compaction at level-0 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end)
2026/05/28-01:07:03.314065 7f3e6effd6c0 Manual compaction at level-1 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.379865 7fb394b4f6c0 Manual compaction at level-1 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-14:31:25.591969 7f3ebd7ff6c0 Recovering log #121 2026/07/25-16:21:45.404647 7fb3e4fff6c0 Recovering log #218
2026/05/27-14:31:25.601963 7f3ebd7ff6c0 Delete type=3 #119 2026/07/25-16:21:45.415371 7fb3e4fff6c0 Delete type=3 #216
2026/05/27-14:31:25.601983 7f3ebd7ff6c0 Delete type=0 #121 2026/07/25-16:21:45.415456 7fb3e4fff6c0 Delete type=0 #218
2026/05/27-14:33:21.754559 7f3e6effd6c0 Level-0 table #127: started 2026/07/25-16:34:16.323056 7fb394b4f6c0 Level-0 table #223: started
2026/05/27-14:33:21.754583 7f3e6effd6c0 Level-0 table #127: 0 bytes OK 2026/07/25-16:34:16.323083 7fb394b4f6c0 Level-0 table #223: 0 bytes OK
2026/05/27-14:33:21.760810 7f3e6effd6c0 Delete type=0 #125 2026/07/25-16:34:16.329677 7fb394b4f6c0 Delete type=0 #221
2026/05/27-14:33:21.760994 7f3e6effd6c0 Manual compaction at level-0 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.343438 7fb394b4f6c0 Manual compaction at level-0 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end)
2026/05/27-14:33:21.773676 7f3e6effd6c0 Manual compaction at level-1 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.350140 7fb394b4f6c0 Manual compaction at level-1 from '!folders!qrqRBmTP6UuS30DF' @ 72057594037927935 : 1 .. '!items!yFvuDyV00NdojxGt' @ 0 : 0; will stop at (end)
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
MANIFEST-000062
+8
View File
@@ -0,0 +1,8 @@
2026/07/25-16:54:40.546836 7fb3e4fff6c0 Recovering log #60
2026/07/25-16:54:40.559140 7fb3e4fff6c0 Delete type=3 #58
2026/07/25-16:54:40.559230 7fb3e4fff6c0 Delete type=0 #60
2026/07/25-17:22:36.806586 7fb394b4f6c0 Level-0 table #65: started
2026/07/25-17:22:36.806624 7fb394b4f6c0 Level-0 table #65: 0 bytes OK
2026/07/25-17:22:36.813457 7fb394b4f6c0 Delete type=0 #63
2026/07/25-17:22:36.832126 7fb394b4f6c0 Manual compaction at level-0 from '!actors!DL3zXfXxWMOQhXJY' @ 72057594037927935 : 1 .. '!actors.items!JAHYXFedTxNlGYX8.nmIgA3CQkOSEpCSP' @ 0 : 0; will stop at (end)
2026/07/25-17:22:36.880816 7fb394b4f6c0 Manual compaction at level-1 from '!actors!DL3zXfXxWMOQhXJY' @ 72057594037927935 : 1 .. '!actors.items!JAHYXFedTxNlGYX8.nmIgA3CQkOSEpCSP' @ 0 : 0; will stop at (end)
+8
View File
@@ -0,0 +1,8 @@
2026/07/25-16:21:45.739454 7fb397fff6c0 Recovering log #56
2026/07/25-16:21:45.751415 7fb397fff6c0 Delete type=3 #54
2026/07/25-16:21:45.751507 7fb397fff6c0 Delete type=0 #56
2026/07/25-16:34:16.722283 7fb394b4f6c0 Level-0 table #61: started
2026/07/25-16:34:16.722319 7fb394b4f6c0 Level-0 table #61: 0 bytes OK
2026/07/25-16:34:16.729649 7fb394b4f6c0 Delete type=0 #59
2026/07/25-16:34:16.800931 7fb394b4f6c0 Manual compaction at level-0 from '!actors!DL3zXfXxWMOQhXJY' @ 72057594037927935 : 1 .. '!actors.items!JAHYXFedTxNlGYX8.nmIgA3CQkOSEpCSP' @ 0 : 0; will stop at (end)
2026/07/25-16:34:16.860353 7fb394b4f6c0 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.
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000048 MANIFEST-000530
+63 -92
View File
@@ -1,92 +1,63 @@
2026/05/27-23:11:58.454344 7f3ebcffe6c0 Recovering log #44 2026/07/25-16:54:40.280606 7fb3e4fff6c0 Recovering log #517
2026/05/27-23:11:58.463285 7f3ebcffe6c0 Delete type=3 #40 2026/07/25-16:54:40.291187 7fb3e4fff6c0 Delete type=3 #513
2026/05/27-23:11:58.463300 7f3ebcffe6c0 Delete type=0 #44 2026/07/25-16:54:40.291276 7fb3e4fff6c0 Delete type=0 #517
2026/05/27-23:37:44.587645 7f3e6effd6c0 Level-0 table #51: started 2026/07/25-16:55:19.321188 7fb394b4f6c0 Level-0 table #533: started
2026/05/27-23:37:44.605872 7f3e6effd6c0 Level-0 table #51: 1343154 bytes OK 2026/07/25-16:55:19.444275 7fb394b4f6c0 Level-0 table #533: 8047048 bytes OK
2026/05/27-23:37:44.612030 7f3e6effd6c0 Delete type=0 #49 2026/07/25-16:55:19.451577 7fb394b4f6c0 Delete type=0 #531
2026/05/28-00:04:39.168329 7f3e6effd6c0 Level-0 table #53: started 2026/07/25-17:21:46.121006 7fb394b4f6c0 Level-0 table #535: started
2026/05/28-00:04:39.185209 7f3e6effd6c0 Level-0 table #53: 1393674 bytes OK 2026/07/25-17:21:46.222937 7fb394b4f6c0 Level-0 table #535: 8120620 bytes OK
2026/05/28-00:04:39.191603 7f3e6effd6c0 Delete type=0 #50 2026/07/25-17:21:46.230171 7fb394b4f6c0 Delete type=0 #532
2026/05/28-00:18:55.273857 7f3e6effd6c0 Level-0 table #55: started 2026/07/25-17:22:03.264081 7fb394b4f6c0 Level-0 table #537: started
2026/05/28-00:18:55.294346 7f3e6effd6c0 Level-0 table #55: 1451564 bytes OK 2026/07/25-17:22:03.358383 7fb394b4f6c0 Level-0 table #537: 8159715 bytes OK
2026/05/28-00:18:55.300308 7f3e6effd6c0 Delete type=0 #52 2026/07/25-17:22:03.366011 7fb394b4f6c0 Delete type=0 #534
2026/05/28-00:20:15.021651 7f3e6effd6c0 Level-0 table #57: started 2026/07/25-17:22:36.394386 7fb394b4f6c0 Level-0 table #539: started
2026/05/28-00:20:15.040525 7f3e6effd6c0 Level-0 table #57: 1496314 bytes OK 2026/07/25-17:22:36.518263 7fb394b4f6c0 Level-0 table #539: 8197558 bytes OK
2026/05/28-00:20:15.046886 7f3e6effd6c0 Delete type=0 #54 2026/07/25-17:22:36.525099 7fb394b4f6c0 Delete type=0 #536
2026/05/28-00:20:43.720521 7f3e6effd6c0 Level-0 table #59: started 2026/07/25-17:22:36.535793 7fb394b4f6c0 Manual compaction at level-0 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 4024266 : 1
2026/05/28-00:20:43.742455 7f3e6effd6c0 Level-0 table #59: 1546947 bytes OK 2026/07/25-17:22:36.535803 7fb394b4f6c0 Compacting 3@0 + 1@1 files
2026/05/28-00:20:43.748835 7f3e6effd6c0 Delete type=0 #56 2026/07/25-17:22:36.589302 7fb394b4f6c0 Generated table #540@0: 7833 keys, 2132558 bytes
2026/05/28-00:20:43.749387 7f3e6effd6c0 Compacting 4@0 + 1@1 files 2026/07/25-17:22:36.667506 7fb394b4f6c0 Generated table #541@0: 15560 keys, 2160278 bytes
2026/05/28-00:20:43.771709 7f3e6effd6c0 Generated table #60@0: 10302 keys, 1546947 bytes 2026/07/25-17:22:36.735222 7fb394b4f6c0 Generated table #542@0: 16090 keys, 2162299 bytes
2026/05/28-00:20:43.771721 7f3e6effd6c0 Compacted 4@0 + 1@1 files => 1546947 bytes 2026/07/25-17:22:36.791787 7fb394b4f6c0 Generated table #543@0: 12983 keys, 1738102 bytes
2026/05/28-00:20:43.777946 7f3e6effd6c0 compacted to: files[ 0 1 1 0 0 0 0 ] 2026/07/25-17:22:36.791824 7fb394b4f6c0 Compacted 3@0 + 1@1 files => 8193237 bytes
2026/05/28-00:20:43.778171 7f3e6effd6c0 Delete type=2 #51 2026/07/25-17:22:36.798979 7fb394b4f6c0 compacted to: files[ 0 4 4 0 0 0 0 ]
2026/05/28-00:20:43.778444 7f3e6effd6c0 Delete type=2 #53 2026/07/25-17:22:36.799753 7fb394b4f6c0 Delete type=2 #533
2026/05/28-00:20:43.778777 7f3e6effd6c0 Delete type=2 #55 2026/07/25-17:22:36.801735 7fb394b4f6c0 Delete type=2 #535
2026/05/28-00:20:43.778977 7f3e6effd6c0 Delete type=2 #57 2026/07/25-17:22:36.803482 7fb394b4f6c0 Delete type=2 #537
2026/05/28-00:20:43.779160 7f3e6effd6c0 Delete type=2 #59 2026/07/25-17:22:36.805232 7fb394b4f6c0 Delete type=2 #539
2026/05/28-00:21:51.457842 7f3e6effd6c0 Level-0 table #62: started 2026/07/25-17:22:36.813620 7fb394b4f6c0 Manual compaction at level-0 from '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 4024266 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at (end)
2026/05/28-00:21:51.479532 7f3e6effd6c0 Level-0 table #62: 1604425 bytes OK 2026/07/25-17:22:36.832161 7fb394b4f6c0 Manual compaction at level-1 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!Eg3PdOfCx9R54Tm6.NHK48nIoycLq56X4' @ 4160142 : 1
2026/05/28-00:21:51.485724 7f3e6effd6c0 Delete type=0 #58 2026/07/25-17:22:36.832171 7fb394b4f6c0 Compacting 1@1 + 1@2 files
2026/05/28-00:30:27.802783 7f3e6effd6c0 Level-0 table #64: started 2026/07/25-17:22:36.867333 7fb394b4f6c0 Generated table #544@1: 7833 keys, 2132558 bytes
2026/05/28-00:30:27.823947 7f3e6effd6c0 Level-0 table #64: 1651065 bytes OK 2026/07/25-17:22:36.872505 7fb394b4f6c0 Generated table #545@1: 338 keys, 55239 bytes
2026/05/28-00:30:27.829721 7f3e6effd6c0 Delete type=0 #61 2026/07/25-17:22:36.872544 7fb394b4f6c0 Compacted 1@1 + 1@2 files => 2187797 bytes
2026/05/28-00:35:01.919877 7f3e6effd6c0 Level-0 table #66: started 2026/07/25-17:22:36.879490 7fb394b4f6c0 compacted to: files[ 0 3 5 0 0 0 0 ]
2026/05/28-00:35:01.987012 7f3e6effd6c0 Level-0 table #66: 1703060 bytes OK 2026/07/25-17:22:36.879931 7fb394b4f6c0 Delete type=2 #523
2026/05/28-00:35:02.043369 7f3e6effd6c0 Delete type=0 #63 2026/07/25-17:22:36.880474 7fb394b4f6c0 Delete type=2 #540
2026/05/28-00:37:26.052331 7f3e6effd6c0 Level-0 table #68: started 2026/07/25-17:22:36.880920 7fb394b4f6c0 Manual compaction at level-1 from '!tables.results!Eg3PdOfCx9R54Tm6.NHK48nIoycLq56X4' @ 4160142 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!bQ64721XzgOetD2v.6a2Kk3rl0H7Rij2U' @ 4171077 : 1
2026/05/28-00:37:26.076959 7f3e6effd6c0 Level-0 table #68: 1753179 bytes OK 2026/07/25-17:22:36.880928 7fb394b4f6c0 Compacting 1@1 + 2@2 files
2026/05/28-00:37:26.083119 7f3e6effd6c0 Delete type=0 #65 2026/07/25-17:22:36.931840 7fb394b4f6c0 Generated table #546@1: 15560 keys, 2160278 bytes
2026/05/28-00:37:26.083369 7f3e6effd6c0 Compacting 4@0 + 1@1 files 2026/07/25-17:22:36.938177 7fb394b4f6c0 Generated table #547@1: 737 keys, 105748 bytes
2026/05/28-00:37:26.103213 7f3e6effd6c0 Generated table #69@0: 11630 keys, 1753179 bytes 2026/07/25-17:22:36.938217 7fb394b4f6c0 Compacted 1@1 + 2@2 files => 2266026 bytes
2026/05/28-00:37:26.103227 7f3e6effd6c0 Compacted 4@0 + 1@1 files => 1753179 bytes 2026/07/25-17:22:36.945137 7fb394b4f6c0 compacted to: files[ 0 2 5 0 0 0 0 ]
2026/05/28-00:37:26.109075 7f3e6effd6c0 compacted to: files[ 0 1 1 0 0 0 0 ] 2026/07/25-17:22:36.945519 7fb394b4f6c0 Delete type=2 #525
2026/05/28-00:37:26.109152 7f3e6effd6c0 Delete type=2 #60 2026/07/25-17:22:36.946090 7fb394b4f6c0 Delete type=2 #541
2026/05/28-00:37:26.109336 7f3e6effd6c0 Delete type=2 #62 2026/07/25-17:22:36.946379 7fb394b4f6c0 Delete type=2 #545
2026/05/28-00:37:26.109453 7f3e6effd6c0 Delete type=2 #64 2026/07/25-17:22:36.965952 7fb394b4f6c0 Manual compaction at level-1 from '!tables.results!bQ64721XzgOetD2v.6a2Kk3rl0H7Rij2U' @ 4171077 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!gXa1mHCjJYOtPiRx.w274XsrKfEGnYr0a' @ 4145219 : 1
2026/05/28-00:37:26.109722 7f3e6effd6c0 Delete type=2 #66 2026/07/25-17:22:36.965988 7fb394b4f6c0 Compacting 1@1 + 2@2 files
2026/05/28-00:37:26.109828 7f3e6effd6c0 Delete type=2 #68 2026/07/25-17:22:37.021652 7fb394b4f6c0 Generated table #548@1: 16090 keys, 2162299 bytes
2026/05/28-00:48:03.393850 7f3e6effd6c0 Level-0 table #71: started 2026/07/25-17:22:37.028936 7fb394b4f6c0 Generated table #549@1: 1170 keys, 156119 bytes
2026/05/28-00:48:03.411976 7f3e6effd6c0 Level-0 table #71: 1806724 bytes OK 2026/07/25-17:22:37.028974 7fb394b4f6c0 Compacted 1@1 + 2@2 files => 2318418 bytes
2026/05/28-00:48:03.419030 7f3e6effd6c0 Delete type=0 #67 2026/07/25-17:22:37.035841 7fb394b4f6c0 compacted to: files[ 0 1 5 0 0 0 0 ]
2026/05/28-00:52:05.605289 7f3e6effd6c0 Level-0 table #73: started 2026/07/25-17:22:37.036188 7fb394b4f6c0 Delete type=2 #527
2026/05/28-00:52:05.629208 7f3e6effd6c0 Level-0 table #73: 1856591 bytes OK 2026/07/25-17:22:37.036784 7fb394b4f6c0 Delete type=2 #542
2026/05/28-00:52:05.635297 7f3e6effd6c0 Delete type=0 #70 2026/07/25-17:22:37.037237 7fb394b4f6c0 Delete type=2 #547
2026/05/28-00:53:23.184161 7f3e6effd6c0 Level-0 table #75: started 2026/07/25-17:22:37.044891 7fb394b4f6c0 Manual compaction at level-1 from '!tables.results!gXa1mHCjJYOtPiRx.w274XsrKfEGnYr0a' @ 4145219 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 4128290 : 1
2026/05/28-00:53:23.202618 7f3e6effd6c0 Level-0 table #75: 1901920 bytes OK 2026/07/25-17:22:37.044907 7fb394b4f6c0 Compacting 1@1 + 2@2 files
2026/05/28-00:53:23.208730 7f3e6effd6c0 Delete type=0 #72 2026/07/25-17:22:37.084375 7fb394b4f6c0 Generated table #550@1: 12983 keys, 1738102 bytes
2026/05/28-01:02:57.320650 7f3e6effd6c0 Level-0 table #77: started 2026/07/25-17:22:37.084414 7fb394b4f6c0 Compacted 1@1 + 2@2 files => 1738102 bytes
2026/05/28-01:02:57.341951 7f3e6effd6c0 Level-0 table #77: 1962797 bytes OK 2026/07/25-17:22:37.091282 7fb394b4f6c0 compacted to: files[ 0 0 4 0 0 0 0 ]
2026/05/28-01:02:57.347793 7f3e6effd6c0 Delete type=0 #74 2026/07/25-17:22:37.091499 7fb394b4f6c0 Delete type=2 #529
2026/05/28-01:02:57.348132 7f3e6effd6c0 Compacting 4@0 + 1@1 files 2026/07/25-17:22:37.091903 7fb394b4f6c0 Delete type=2 #543
2026/05/28-01:02:57.369982 7f3e6effd6c0 Generated table #78@0: 12958 keys, 1962797 bytes 2026/07/25-17:22:37.092142 7fb394b4f6c0 Delete type=2 #549
2026/05/28-01:02:57.370013 7f3e6effd6c0 Compacted 4@0 + 1@1 files => 1962797 bytes 2026/07/25-17:22:37.106687 7fb394b4f6c0 Manual compaction at level-1 from '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 4128290 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at (end)
2026/05/28-01:02:57.376098 7f3e6effd6c0 compacted to: files[ 0 1 1 0 0 0 0 ]
2026/05/28-01:02:57.376330 7f3e6effd6c0 Delete type=2 #69
2026/05/28-01:02:57.376716 7f3e6effd6c0 Delete type=2 #71
2026/05/28-01:02:57.377097 7f3e6effd6c0 Delete type=2 #73
2026/05/28-01:02:57.377594 7f3e6effd6c0 Delete type=2 #75
2026/05/28-01:02:57.377833 7f3e6effd6c0 Delete type=2 #77
2026/05/28-01:06:35.052396 7f3e6effd6c0 Level-0 table #80: started
2026/05/28-01:06:35.067526 7f3e6effd6c0 Level-0 table #80: 2007084 bytes OK
2026/05/28-01:06:35.074274 7f3e6effd6c0 Delete type=0 #76
2026/05/28-01:07:03.326355 7f3e6effd6c0 Level-0 table #82: started
2026/05/28-01:07:03.344521 7f3e6effd6c0 Level-0 table #82: 2055614 bytes OK
2026/05/28-01:07:03.350350 7f3e6effd6c0 Delete type=0 #79
2026/05/28-01:07:03.356890 7f3e6effd6c0 Manual compaction at level-0 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 261156 : 1
2026/05/28-01:07:03.356894 7f3e6effd6c0 Compacting 2@0 + 1@1 files
2026/05/28-01:07:03.375384 7f3e6effd6c0 Generated table #83@0: 13622 keys, 2055614 bytes
2026/05/28-01:07:03.375400 7f3e6effd6c0 Compacted 2@0 + 1@1 files => 2055614 bytes
2026/05/28-01:07:03.381064 7f3e6effd6c0 compacted to: files[ 0 1 1 0 0 0 0 ]
2026/05/28-01:07:03.381439 7f3e6effd6c0 Delete type=2 #78
2026/05/28-01:07:03.381593 7f3e6effd6c0 Delete type=2 #80
2026/05/28-01:07:03.381689 7f3e6effd6c0 Delete type=2 #82
2026/05/28-01:07:03.381868 7f3e6effd6c0 Manual compaction at level-0 from '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 261156 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at (end)
2026/05/28-01:07:03.416170 7f3e6effd6c0 Manual compaction at level-1 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 274490 : 1
2026/05/28-01:07:03.416174 7f3e6effd6c0 Compacting 1@1 + 1@2 files
2026/05/28-01:07:03.432983 7f3e6effd6c0 Generated table #84@1: 13622 keys, 2055614 bytes
2026/05/28-01:07:03.432994 7f3e6effd6c0 Compacted 1@1 + 1@2 files => 2055614 bytes
2026/05/28-01:07:03.438860 7f3e6effd6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
2026/05/28-01:07:03.438913 7f3e6effd6c0 Delete type=2 #47
2026/05/28-01:07:03.439160 7f3e6effd6c0 Delete type=2 #83
2026/05/28-01:07:03.445861 7f3e6effd6c0 Manual compaction at level-1 from '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 274490 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at (end)
+55 -25
View File
@@ -1,25 +1,55 @@
2026/05/27-14:31:25.674162 7f3e6f7fe6c0 Recovering log #36 2026/07/25-16:21:45.501792 7fb3e4fff6c0 Recovering log #500
2026/05/27-14:31:25.684284 7f3e6f7fe6c0 Delete type=3 #21 2026/07/25-16:21:45.512008 7fb3e4fff6c0 Delete type=3 #496
2026/05/27-14:31:25.684327 7f3e6f7fe6c0 Delete type=0 #36 2026/07/25-16:21:45.512073 7fb3e4fff6c0 Delete type=0 #500
2026/05/27-14:32:13.276652 7f3e6effd6c0 Level-0 table #43: started 2026/07/25-16:24:19.128277 7fb394b4f6c0 Level-0 table #516: started
2026/05/27-14:32:13.292995 7f3e6effd6c0 Level-0 table #43: 1244894 bytes OK 2026/07/25-16:24:19.244728 7fb394b4f6c0 Level-0 table #516: 7956529 bytes OK
2026/05/27-14:32:13.298790 7f3e6effd6c0 Delete type=0 #41 2026/07/25-16:24:19.251153 7fb394b4f6c0 Delete type=0 #514
2026/05/27-14:33:21.808275 7f3e6effd6c0 Level-0 table #45: started 2026/07/25-16:34:16.371434 7fb394b4f6c0 Level-0 table #518: started
2026/05/27-14:33:21.824612 7f3e6effd6c0 Level-0 table #45: 1294793 bytes OK 2026/07/25-16:34:16.500326 7fb394b4f6c0 Level-0 table #518: 7998817 bytes OK
2026/05/27-14:33:21.831380 7f3e6effd6c0 Delete type=0 #42 2026/07/25-16:34:16.508005 7fb394b4f6c0 Delete type=0 #515
2026/05/27-14:33:21.838976 7f3e6effd6c0 Manual compaction at level-0 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 109340 : 1 2026/07/25-16:34:16.510322 7fb394b4f6c0 Manual compaction at level-0 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 3921570 : 1
2026/05/27-14:33:21.838980 7f3e6effd6c0 Compacting 1@0 + 1@1 files 2026/07/25-16:34:16.510331 7fb394b4f6c0 Compacting 1@0 + 1@1 files
2026/05/27-14:33:21.852317 7f3e6effd6c0 Generated table #46@0: 8642 keys, 1294793 bytes 2026/07/25-16:34:16.542074 7fb394b4f6c0 Generated table #519@0: 7972 keys, 2131647 bytes
2026/05/27-14:33:21.852331 7f3e6effd6c0 Compacted 1@0 + 1@1 files => 1294793 bytes 2026/07/25-16:34:16.588781 7fb394b4f6c0 Generated table #520@0: 15567 keys, 2160383 bytes
2026/05/27-14:33:21.859689 7f3e6effd6c0 compacted to: files[ 0 1 1 0 0 0 0 ] 2026/07/25-16:34:16.635707 7fb394b4f6c0 Generated table #521@0: 16112 keys, 2162117 bytes
2026/05/27-14:33:21.859935 7f3e6effd6c0 Delete type=2 #43 2026/07/25-16:34:16.671030 7fb394b4f6c0 Generated table #522@0: 11487 keys, 1540844 bytes
2026/05/27-14:33:21.860058 7f3e6effd6c0 Delete type=2 #45 2026/07/25-16:34:16.671069 7fb394b4f6c0 Compacted 1@0 + 1@1 files => 7994991 bytes
2026/05/27-14:33:21.877111 7f3e6effd6c0 Manual compaction at level-0 from '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 109340 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.677382 7fb394b4f6c0 compacted to: files[ 0 4 4 0 0 0 0 ]
2026/05/27-14:33:21.877144 7f3e6effd6c0 Manual compaction at level-1 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 109340 : 1 2026/07/25-16:34:16.678276 7fb394b4f6c0 Delete type=2 #516
2026/05/27-14:33:21.877150 7f3e6effd6c0 Compacting 1@1 + 1@2 files 2026/07/25-16:34:16.679572 7fb394b4f6c0 Delete type=2 #518
2026/05/27-14:33:21.895679 7f3e6effd6c0 Generated table #47@1: 8642 keys, 1294793 bytes 2026/07/25-16:34:16.680429 7fb394b4f6c0 Manual compaction at level-0 from '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 3921570 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at (end)
2026/05/27-14:33:21.895699 7f3e6effd6c0 Compacted 1@1 + 1@2 files => 1294793 bytes 2026/07/25-16:34:16.680597 7fb394b4f6c0 Manual compaction at level-1 from '!tables!BbXMbmHKcLJrBCmk' @ 72057594037927935 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!Eg3PdOfCx9R54Tm6.QmTmqRtZ2zqUbNDK' @ 3951925 : 1
2026/05/27-14:33:21.901922 7f3e6effd6c0 compacted to: files[ 0 0 1 0 0 0 0 ] 2026/07/25-16:34:16.680618 7fb394b4f6c0 Compacting 1@1 + 1@2 files
2026/05/27-14:33:21.902059 7f3e6effd6c0 Delete type=2 #39 2026/07/25-16:34:16.710701 7fb394b4f6c0 Generated table #523@1: 7972 keys, 2131647 bytes
2026/05/27-14:33:21.902514 7f3e6effd6c0 Delete type=2 #46 2026/07/25-16:34:16.714887 7fb394b4f6c0 Generated table #524@1: 166 keys, 27091 bytes
2026/05/27-14:33:21.908609 7f3e6effd6c0 Manual compaction at level-1 from '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 109340 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zyJ49IY9JAmeIucJ' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.714917 7fb394b4f6c0 Compacted 1@1 + 1@2 files => 2158738 bytes
2026/07/25-16:34:16.721082 7fb394b4f6c0 compacted to: files[ 0 3 5 0 0 0 0 ]
2026/07/25-16:34:16.721504 7fb394b4f6c0 Delete type=2 #506
2026/07/25-16:34:16.722019 7fb394b4f6c0 Delete type=2 #519
2026/07/25-16:34:16.743211 7fb394b4f6c0 Manual compaction at level-1 from '!tables.results!Eg3PdOfCx9R54Tm6.QmTmqRtZ2zqUbNDK' @ 3951925 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!bQ64721XzgOetD2v.EReJqu3wyM6vwWR7' @ 3963654 : 1
2026/07/25-16:34:16.743231 7fb394b4f6c0 Compacting 1@1 + 2@2 files
2026/07/25-16:34:16.788469 7fb394b4f6c0 Generated table #525@1: 15567 keys, 2160383 bytes
2026/07/25-16:34:16.793007 7fb394b4f6c0 Generated table #526@1: 370 keys, 53494 bytes
2026/07/25-16:34:16.793036 7fb394b4f6c0 Compacted 1@1 + 2@2 files => 2213877 bytes
2026/07/25-16:34:16.799791 7fb394b4f6c0 compacted to: files[ 0 2 5 0 0 0 0 ]
2026/07/25-16:34:16.800112 7fb394b4f6c0 Delete type=2 #508
2026/07/25-16:34:16.800586 7fb394b4f6c0 Delete type=2 #520
2026/07/25-16:34:16.800854 7fb394b4f6c0 Delete type=2 #524
2026/07/25-16:34:16.801124 7fb394b4f6c0 Manual compaction at level-1 from '!tables.results!bQ64721XzgOetD2v.EReJqu3wyM6vwWR7' @ 3963654 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!jup6vzXHR8WYariQ.9MLFLfBZxah8tU5d' @ 3930763 : 1
2026/07/25-16:34:16.801134 7fb394b4f6c0 Compacting 1@1 + 2@2 files
2026/07/25-16:34:16.847533 7fb394b4f6c0 Generated table #527@1: 16112 keys, 2162117 bytes
2026/07/25-16:34:16.852860 7fb394b4f6c0 Generated table #528@1: 669 keys, 88745 bytes
2026/07/25-16:34:16.852890 7fb394b4f6c0 Compacted 1@1 + 2@2 files => 2250862 bytes
2026/07/25-16:34:16.859163 7fb394b4f6c0 compacted to: files[ 0 1 5 0 0 0 0 ]
2026/07/25-16:34:16.859513 7fb394b4f6c0 Delete type=2 #510
2026/07/25-16:34:16.859993 7fb394b4f6c0 Delete type=2 #521
2026/07/25-16:34:16.860259 7fb394b4f6c0 Delete type=2 #526
2026/07/25-16:34:16.868258 7fb394b4f6c0 Manual compaction at level-1 from '!tables.results!jup6vzXHR8WYariQ.9MLFLfBZxah8tU5d' @ 3930763 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 3921570 : 1
2026/07/25-16:34:16.868275 7fb394b4f6c0 Compacting 1@1 + 2@2 files
2026/07/25-16:34:16.902985 7fb394b4f6c0 Generated table #529@1: 11487 keys, 1540844 bytes
2026/07/25-16:34:16.903023 7fb394b4f6c0 Compacted 1@1 + 2@2 files => 1540844 bytes
2026/07/25-16:34:16.910466 7fb394b4f6c0 compacted to: files[ 0 0 4 0 0 0 0 ]
2026/07/25-16:34:16.910669 7fb394b4f6c0 Delete type=2 #512
2026/07/25-16:34:16.911042 7fb394b4f6c0 Delete type=2 #522
2026/07/25-16:34:16.911269 7fb394b4f6c0 Delete type=2 #528
2026/07/25-16:34:16.928914 7fb394b4f6c0 Manual compaction at level-1 from '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 3921570 : 1 .. '!tables.results!xe7x4qufBpzLaEby.zzqLG98O4eFQZHp0' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
View File
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000074 MANIFEST-000170
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-23:11:58.427900 7f3ebd7ff6c0 Recovering log #72 2026/07/25-16:54:40.250077 7fb3977fe6c0 Recovering log #168
2026/05/27-23:11:58.438233 7f3ebd7ff6c0 Delete type=3 #70 2026/07/25-16:54:40.261282 7fb3977fe6c0 Delete type=3 #166
2026/05/27-23:11:58.438292 7f3ebd7ff6c0 Delete type=0 #72 2026/07/25-16:54:40.261388 7fb3977fe6c0 Delete type=0 #168
2026/05/28-01:07:03.314279 7f3e6effd6c0 Level-0 table #77: started 2026/07/25-17:22:36.372487 7fb394b4f6c0 Level-0 table #173: started
2026/05/28-01:07:03.314328 7f3e6effd6c0 Level-0 table #77: 0 bytes OK 2026/07/25-17:22:36.372517 7fb394b4f6c0 Level-0 table #173: 0 bytes OK
2026/05/28-01:07:03.320204 7f3e6effd6c0 Delete type=0 #75 2026/07/25-17:22:36.379686 7fb394b4f6c0 Delete type=0 #171
2026/05/28-01:07:03.356871 7f3e6effd6c0 Manual compaction at level-0 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.379949 7fb394b4f6c0 Manual compaction at level-0 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end)
2026/05/28-01:07:03.381781 7f3e6effd6c0 Manual compaction at level-1 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end) 2026/07/25-17:22:36.379970 7fb394b4f6c0 Manual compaction at level-1 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end)
+8 -8
View File
@@ -1,8 +1,8 @@
2026/05/27-14:31:25.650849 7f3e6f7fe6c0 Recovering log #67 2026/07/25-16:21:45.473663 7fb3e4fff6c0 Recovering log #164
2026/05/27-14:31:25.660624 7f3e6f7fe6c0 Delete type=3 #65 2026/07/25-16:21:45.484922 7fb3e4fff6c0 Delete type=3 #162
2026/05/27-14:31:25.660648 7f3e6f7fe6c0 Delete type=0 #67 2026/07/25-16:21:45.484999 7fb3e4fff6c0 Delete type=0 #164
2026/05/27-14:33:21.780104 7f3e6effd6c0 Level-0 table #73: started 2026/07/25-16:34:16.364464 7fb394b4f6c0 Level-0 table #169: started
2026/05/27-14:33:21.780129 7f3e6effd6c0 Level-0 table #73: 0 bytes OK 2026/07/25-16:34:16.364495 7fb394b4f6c0 Level-0 table #169: 0 bytes OK
2026/05/27-14:33:21.786659 7f3e6effd6c0 Delete type=0 #71 2026/07/25-16:34:16.371260 7fb394b4f6c0 Delete type=0 #167
2026/05/27-14:33:21.800546 7f3e6effd6c0 Manual compaction at level-0 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.510187 7fb394b4f6c0 Manual compaction at level-0 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end)
2026/05/27-14:33:21.808253 7f3e6effd6c0 Manual compaction at level-1 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end) 2026/07/25-16:34:16.680263 7fb394b4f6c0 Manual compaction at level-1 from '!items!0ZfAXacF6oWS120o' @ 72057594037927935 : 1 .. '!items!xFUyR7XECD8QJcIw' @ 0 : 0; will stop at (end)
+95
View File
@@ -0,0 +1,95 @@
import { formatImperialDate } from './calendarDate.js';
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
export class CalendarApp extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
id: 'mgt2-calendar',
classes: ['mgt2-calendar'],
position: { width: 260, height: 'auto' },
window: {
icon: 'fas fa-calendar-alt',
title: 'Calendrier Impérial',
resizable: false,
},
};
static PARTS = {
main: {
template: `modules/${MODULE_ID}/templates/calendar-app.hbs`,
root: true,
},
};
constructor({ onConfig, onClose, onNote } = {}) {
super();
const saved = game.settings?.get(MODULE_ID, 'calendarPosition');
if (saved?.left != null && saved?.top != null) {
this.position.left = saved.left;
this.position.top = saved.top;
}
this._onConfig = onConfig;
this._onClose = onClose;
this._onNote = onNote;
this._data = { year: 1116, day: 1, hour: 12, minute: 0, planet: '' };
}
get title() {
return 'Calendrier Impérial';
}
async _prepareContext() {
return {
dateLabel: formatImperialDate(this._data),
isGM: game.user?.isGM ?? false,
};
}
async _onRender(context, options) {
await super._onRender(context, options);
const html = $(this.element);
html.find('.mgt2-cal-config-btn').on('click', () => {
this._onConfig?.();
});
html.find('.mgt2-cal-note-btn').on('click', () => {
this._onNote?.();
});
}
setPosition(position) {
const result = super.setPosition(position);
this._debouncedSavePos();
return result;
}
_debouncedSavePos() {
if (this._savePosTimeout) clearTimeout(this._savePosTimeout);
this._savePosTimeout = setTimeout(() => {
game.settings?.set(MODULE_ID, 'calendarPosition', {
left: this.position.left,
top: this.position.top,
});
this._savePosTimeout = null;
}, 300);
}
updateDisplay(data) {
this._data = { ...this._data, ...data };
this.render();
}
async close() {
if (game.settings) {
await game.settings.set(MODULE_ID, 'calendarPosition', {
left: this.position.left,
top: this.position.top,
});
}
this._onConfig = null;
this._onNote = null;
this._onClose?.();
this._onClose = null;
return super.close();
}
}
+116
View File
@@ -0,0 +1,116 @@
import { getDayLabel, computeConversions } from './calendarDate.js';
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
export class CalendarConfigDialog extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
id: 'mgt2-calendar-config',
classes: ['mgt2-calendar'],
position: { width: 300, height: 'auto' },
window: {
icon: 'fas fa-cog',
title: 'Configurer le calendrier',
resizable: false,
},
};
static PARTS = {
main: {
template: `modules/${MODULE_ID}/templates/calendar-config.hbs`,
root: true,
},
};
constructor({ data, onApply } = {}) {
super();
this._data = { ...data };
this._onApply = onApply;
}
get title() {
return 'Configurer le calendrier';
}
async _prepareContext() {
return {
year: this._data.year,
day: this._data.day,
hour: this._data.hour,
minute: this._data.minute,
planet: this._data.planet ?? '',
dayLabel: getDayLabel(this._data.day),
conversions: computeConversions(this._data.year),
};
}
async _onRender(context, options) {
await super._onRender(context, options);
const html = $(this.element);
html.find('[name="day"]').on('input', () => {
const day = parseInt(html.find('[name="day"]').val(), 10);
if (day >= 1 && day <= 365) {
html.find('.mgt2-cal-day-label').text(getDayLabel(day));
}
});
html.find('[name="year"]').on('input', () => {
const year = parseInt(html.find('[name="year"]').val(), 10) || 0;
const conv = computeConversions(year);
html.find('.mgt2-cal-conv-val[data-cal="solomani"]').text(conv.solomani);
html.find('.mgt2-cal-conv-val[data-cal="vilani"]').text(conv.vilani);
html.find('.mgt2-cal-conv-val[data-cal="zhodani"]').text(conv.zhodani);
html.find('.mgt2-cal-conv-val[data-cal="aslan"]').text(conv.aslan);
html.find('.mgt2-cal-conv-val[data-cal="kkree"]').text(conv.kkree);
html.find('.mgt2-cal-conv-val[data-cal="hiver"]').text(conv.hiver);
});
html.find('[data-action="day-inc"]').on('click', () => {
const input = html.find('[name="day"]');
let day = parseInt(input.val(), 10) || 1;
day = day >= 365 ? 1 : day + 1;
input.val(day);
html.find('.mgt2-cal-day-label').text(getDayLabel(day));
});
html.find('[data-action="hour-inc"]').on('click', () => {
const dayInput = html.find('[name="day"]');
const hourInput = html.find('[name="hour"]');
const minuteInput = html.find('[name="minute"]');
let day = parseInt(dayInput.val(), 10) || 1;
let hour = parseInt(hourInput.val(), 10) || 0;
let minute = parseInt(minuteInput.val(), 10) || 0;
hour += 1;
if (hour >= 24) {
hour = 0;
day = day >= 365 ? 1 : day + 1;
}
hourInput.val(hour);
dayInput.val(day);
html.find('.mgt2-cal-day-label').text(getDayLabel(day));
});
html.find('[data-action="apply"]').on('click', () => {
this._readAndApply(html);
});
}
_readAndApply(html) {
const year = parseInt(html.find('[name="year"]').val(), 10) || 1116;
const day = Math.min(365, Math.max(1, parseInt(html.find('[name="day"]').val(), 10) || 1));
const hour = Math.min(23, Math.max(0, parseInt(html.find('[name="hour"]').val(), 10) || 0));
const minute = Math.min(59, Math.max(0, parseInt(html.find('[name="minute"]').val(), 10) || 0));
const planet = html.find('[name="planet"]').val()?.trim() ?? '';
const data = { year, day, hour, minute, planet };
this._onApply?.(data);
}
close() {
this._onApply = null;
return super.close();
}
}
+36 -1
View File
@@ -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');
+59
View File
@@ -0,0 +1,59 @@
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
export class JournalNoteDialog extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
id: 'mgt2-journal-note',
classes: ['mgt2-calendar'],
position: { width: 320, height: 'auto' },
window: {
icon: 'fas fa-pen',
title: 'Note de bord',
resizable: false,
},
};
static PARTS = {
main: {
template: `modules/${MODULE_ID}/templates/journal-note-dialog.hbs`,
root: true,
},
};
constructor({ imperialDate, realDate, onSave } = {}) {
super();
this._imperialDate = imperialDate;
this._realDate = realDate;
this._onSave = onSave;
}
get title() {
return 'Note de bord';
}
async _prepareContext() {
return {
imperialDate: this._imperialDate,
realDate: this._realDate,
};
}
async _onRender(context, options) {
await super._onRender(context, options);
const html = $(this.element);
html.find('[data-action="save"]').on('click', async () => {
const content = html.find('[name="content"]').val()?.trim();
try {
await this._onSave?.(content);
} catch (err) {
console.error(`${MODULE_ID} | Erreur sauvegarde note:`, err);
}
});
}
close() {
this._onSave = null;
return super.close();
}
}
+88 -2
View File
@@ -1,5 +1,6 @@
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 { generateAndCreateTravellerNpc } from './travellerNpcGenerator.js';
import { generateRandomName } from './data/travellerNpcGenerator.js'; import { generateRandomName } from './data/travellerNpcGenerator.js';
@@ -68,6 +69,13 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
actorName: '', actorName: '',
openCreatedActor: DEFAULT_OPTIONS.openCreatedActor, openCreatedActor: DEFAULT_OPTIONS.openCreatedActor,
}, },
ae: {
relation: options.relation ?? 'contact',
includeSpecial: true,
createActor: false,
actorName: '',
openCreatedActor: true,
},
}; };
} }
@@ -138,6 +146,12 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
await this._handleTravellerNpc(); 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) => { html.find('[data-action="randomize-name"]').on('click', (event) => {
event.preventDefault(); event.preventDefault();
this._randomizeTravellerName(html); this._randomizeTravellerName(html);
@@ -205,6 +219,13 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
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) // Données pour l'onglet PNJ Détaillé (Traveller)
this._formData.traveller.citizenCategory = html.find('[name="traveller.citizenCategory"]').val(); this._formData.traveller.citizenCategory = html.find('[name="traveller.citizenCategory"]').val();
this._formData.traveller.experience = html.find('[name="traveller.experience"]').val(); this._formData.traveller.experience = html.find('[name="traveller.experience"]').val();
@@ -281,6 +302,60 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
} }
} }
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) { _randomizeTravellerName(html) {
const name = generateRandomName(this._formData.traveller.gender); const name = generateRandomName(this._formData.traveller.gender);
html.find('[name="traveller.firstName"]').val(name.firstName); html.find('[name="traveller.firstName"]').val(name.firstName);
@@ -295,13 +370,15 @@ export class NpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
async _postToChatResult(data) { async _postToChatResult(data) {
registerHandlebarsHelpers(); registerHandlebarsHelpers();
// Déterminer quel template utiliser en fonction du type de données
let template = `modules/${MODULE_ID}/templates/npc-result.hbs`; let template = `modules/${MODULE_ID}/templates/npc-result.hbs`;
let resultType = 'npc-result'; let resultType = 'npc-result';
if (data.type === 'traveller-npc' || data?.flags?.[MODULE_ID]?.type === 'traveller-npc-result') { if (data.type === 'traveller-npc' || data?.flags?.[MODULE_ID]?.type === 'traveller-npc-result') {
template = `modules/${MODULE_ID}/templates/traveller-npc-result.hbs`; template = `modules/${MODULE_ID}/templates/traveller-npc-result.hbs`;
resultType = 'traveller-npc-result'; 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); const html = await foundry.applications.handlebars.renderTemplate(template, data);
@@ -369,4 +446,13 @@ function registerHandlebarsHelpers() {
if (!obj || !key) return ''; if (!obj || !key) return '';
return obj[key] !== undefined ? obj[key] : ''; 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));
} }
+647
View File
@@ -0,0 +1,647 @@
/**
* 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, { readOnly = false } = {}) {
super();
this._sector = sector;
this._subsector = subsector;
this._readOnly = readOnly;
this._handler = null;
this._mapHex = null;
this._mapCenter = null;
this._mapScale = 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';
const cb = this._readOnly ? '' : '&callback=postmessage';
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${cb}`;
}
let url = `${base}/?sector=${encodeURIComponent(this._sector)}&style=mongoose&hideui=1${cb}`;
if (this._mapHex) {
url += `&hex=${this._mapHex}&scale=512`;
} else if (this._mapCenter && this._mapScale) {
url += `&center=${this._mapCenter}&scale=${this._mapScale}`;
}
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() {
if (this._readOnly) {
return `<div class="mgt2-sector-map-outer">
<div class="mgt2-sector-map-toolbar mgt2-sector-map-toolbar--ro">
<span class="mgt2-sector-map-label">${SectorMapApp._escapeAttr(this.title)}</span>
<span class="mgt2-sector-map-hint">Carte partagée par le MJ</span>
</div>
<iframe
src="${this._mapUrl}"
class="mgt2-sector-map-frame"
allow="clipboard-write"
referrerpolicy="no-referrer">
</iframe>
</div>`;
}
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-sync" disabled title="Cliquez d'abord sur un hex pour définir la position à synchroniser"><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) {
if (this._readOnly) {
this._listen();
return;
}
this._listen();
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) => {
const d = event.data || {};
if (d.type === 'viewport') {
this._sector = d.sector || this._sector;
this._subsector = d.subsector || this._subsector;
this._mapHex = d.hex || null;
this._mapCenter = (d.x != null && d.y != null) ? `${d.x},${d.y}` : null;
this._mapScale = d.scale || null;
return;
}
// Click on map
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;
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; }
// Store click position for sync
this._sector = sectorName;
this._subsector = null;
this._mapHex = String(hx).padStart(2, '0') + String(hy).padStart(2, '0');
this._mapCenter = `${wx},${wy}`;
this._mapScale = loc.scale || 512;
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._enableSyncButton(world.Name || hex);
this._postWorldCard(world);
}
_enableSyncButton(worldName) {
const btn = this.element?.querySelector('.mgt2-sector-map-sync');
if (btn) {
btn.disabled = false;
btn.title = worldName ? `Synchroniser la vue sur ${worldName}` : 'Ouvrir la carte interactive chez tous les joueurs';
}
}
/* ───── 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 = [ '05% (désert)','615%','1625%','2635%','3645%','4655%','5665%','6675%','7685%','8695%','96100%' ];
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 dimportance é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)}&nbsp;milliards`
: total >= 1e6 ? `${(total / 1e6).toFixed(1)}&nbsp;millions`
: total >= 1e3 ? `${(total / 1e3).toFixed(0)}&nbsp;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 dastéroïdes : <b>${belts ?? '?'}</b> &nbsp;|&nbsp; 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, '&amp;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
}
_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;
this._mapCenter = null;
this._mapScale = null;
this._enableSyncButton(name);
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 ───── */
async _syncAll() {
if (!game.socket?.emit) {
ui.notifications.warn('Socket indisponible — impossible de synchroniser');
return;
}
try {
// Tentative de récupération du zoom via getViewport
const viewport = await new Promise((resolve) => {
const handler = (event) => {
if (event.data?.type === 'viewport') {
window.removeEventListener('message', handler);
resolve(event.data);
}
};
window.addEventListener('message', handler);
const iframe = this.element?.querySelector('.mgt2-sector-map-frame');
if (iframe?.contentWindow) {
iframe.contentWindow.postMessage(
{ source: 'travellermap', type: 'getViewport' },
'*'
);
}
setTimeout(() => {
window.removeEventListener('message', handler);
resolve(null);
}, 1000);
});
if (viewport) {
this._sector = viewport.sector || this._sector;
this._subsector = viewport.subsector || this._subsector;
this._mapHex = viewport.hex || this._mapHex;
this._mapCenter = (viewport.x != null && viewport.y != null) ? `${viewport.x},${viewport.y}` : this._mapCenter;
this._mapScale = viewport.scale || this._mapScale;
}
const payload = {
_type: 'sectorMapSync',
sector: this._sector,
subsector: this._subsector,
mapHex: this._mapHex,
mapCenter: this._mapCenter,
mapScale: this._mapScale,
};
const players = game.users.filter(u => !u.isGM && u.active);
for (const player of players) {
game.socket.emit(`module.${MODULE_ID}`, payload);
}
ui.notifications.info(`Vue synchronisée chez ${players.length} joueur(s)`);
} catch (err) {
console.error(`${MODULE_ID} | Erreur socket sync:`, err);
ui.notifications.error('Erreur lors de la synchronisation');
}
}
updatePosition(sector, subsector, mapHex, mapUrl) {
this._sector = sector;
this._subsector = subsector;
this._mapHex = mapHex || null;
const iframe = this.element?.querySelector('.mgt2-sector-map-frame');
if (iframe) iframe.src = mapUrl || this._mapUrl;
}
_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();
}
}
+277
View File
@@ -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,
};
}
+300
View File
@@ -0,0 +1,300 @@
import { CommerceDialog } from './CommerceDialog.js';
import { NpcDialog } from './NpcDialog.js';
import { SectorMapApp } from './SectorMapApp.js';
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
const IMAGE_EXTENSIONS = new Set(['png', 'webp', 'jpg', 'jpeg', 'gif', 'svg', 'avif', 'bmp', 'tiff', 'tif']);
class BatchNpcDialog extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
id: 'mgt2-batch-npc',
classes: ['mgt2-npc-dialog'],
position: { width: 540, height: 'auto' },
window: { title: 'Création de PNJs par lots', resizable: true },
form: { submitOnClose: false, closeOnSubmit: false },
};
static PARTS = {
main: {
template: `modules/${MODULE_ID}/templates/batch-npc-dialog.hbs`,
root: true,
},
};
constructor(options = {}) {
super(options);
this._formData = {
batch: {
directory: '',
folderName: 'PNJs importés',
openSheets: false,
},
};
}
async _prepareContext() {
return { ...this._formData };
}
async _onRender(context, options) {
await super._onRender(context, options);
const html = $('#' + this.id).find('.window-content');
this._applyTheme(html);
html.find('[data-action="browse-directory"]').on('click', async () => {
// Foundry v14: FilePicker avec pickFolders pour sélectionner un répertoire
try {
const fp = new FilePicker({
type: 'folder',
current: this._formData.batch.directory || '',
callback: (path) => {
this._formData.batch.directory = path;
html.find('[name="batch.directory"]').val(path);
},
pickFolders: true,
field: null,
button: null,
});
fp.render(true);
return;
} catch (_e) { /* fallback v13 */ }
// Fallback v13: selection d'un fichier → extraction du dossier parent
const fp = new FilePicker({
type: 'image',
current: this._formData.batch.directory || '',
callback: (path) => {
const dir = path.split('/').slice(0, -1).join('/');
this._formData.batch.directory = dir;
html.find('[name="batch.directory"]').val(dir);
},
});
fp.render(true);
});
html.find('[data-action="batch-create"]').on('click', async () => {
this._readForm(html);
await this._executeBatch(html);
});
}
_readForm(html) {
this._formData.batch.directory = (html.find('[name="batch.directory"]').val()?.trim() || '').replace(/\/+$/, '');
this._formData.batch.folderName = html.find('[name="batch.folderName"]').val()?.trim() || 'PNJs importés';
this._formData.batch.openSheets = html.find('[name="batch.openSheets"]').is(':checked');
}
_applyTheme(html) {
html.find('h3').css({ color: '#5f4300', 'border-bottom-color': '#b78f26', 'text-shadow': 'none' });
html.find('legend').css({ color: '#7a5c00', 'text-shadow': 'none' });
}
async _executeBatch(html) {
const { directory, folderName, openSheets } = this._formData.batch;
if (!directory) {
ui.notifications.warn('Veuillez spécifier un répertoire source.');
return;
}
const $progress = html.find('#batch-progress');
const $fill = html.find('#batch-progress-fill');
const $text = html.find('#batch-progress-text');
$progress.show();
try {
$text.text('Analyse du répertoire...');
let files;
try {
const result = await FilePicker.browse('data', directory);
files = result.files || [];
} catch (e) {
$progress.hide();
ui.notifications.error(`Impossible de lire le répertoire « ${directory} » : ${e.message}`);
return;
}
const imageFiles = files.filter(f => {
const ext = f.split('.').pop()?.toLowerCase();
return ext && IMAGE_EXTENSIONS.has(ext);
});
if (imageFiles.length === 0) {
$progress.hide();
ui.notifications.warn(`Aucune image trouvée dans « ${directory} ».`);
return;
}
$text.text(`${imageFiles.length} image(s) trouvée(s). Vérification des doublons...`);
const normalizePath = (p) => p.replace(/^[\\/]+/, '').replace(/[\\/]+/g, '/').toLowerCase();
const existingImages = new Set();
for (const actor of game.actors) {
if (actor.img && actor.img !== 'icons/svg/mystery-man.svg') {
existingImages.add(normalizePath(actor.img));
}
}
const toCreate = imageFiles.filter(f => !existingImages.has(normalizePath(f)));
if (toCreate.length === 0) {
$progress.hide();
ui.notifications.info('Tous les PNJs existent déjà (aucune nouvelle image à traiter).');
return;
}
let folder = null;
if (folderName) {
folder = game.folders.find(f => f.name === folderName && f.type === 'Actor');
if (!folder) {
folder = await Folder.create({ name: folderName, type: 'Actor', parent: null });
}
}
$text.text(`Création de ${toCreate.length} PNJ(s)...`);
const chunkSize = 10;
let created = 0;
const allCreated = [];
for (let i = 0; i < toCreate.length; i += chunkSize) {
const chunk = toCreate.slice(i, i + chunkSize);
const actorsData = chunk.map(path => {
const name = path.split('/').pop().replace(/\.[^/.]+$/, '');
return {
name,
type: 'npc',
img: path,
folder: folder?.id || null,
token: {
name,
img: path,
actorLink: true,
disposition: 0,
sight: { enabled: false },
},
system: {
settings: { hideUntrained: true, lockCharacteristics: true },
characteristics: {},
hits: {},
skills: {},
},
flags: {
[MODULE_ID]: { batchImported: true, sourceImage: path },
},
};
});
const actors = await Actor.create(actorsData, { renderSheet: false });
allCreated.push(...actors);
created += actors.length;
const pct = Math.min(100, Math.round((i + chunkSize) / toCreate.length * 100));
$fill.css('width', pct + '%');
$text.text(`${created}/${toCreate.length} PNJ(s) créé(s)...`);
}
ui.actors?.render(false);
$fill.css('width', '100%');
$text.text(`${created} PNJ(s) créé(s) avec succès dans le dossier « ${folderName} » !`);
ui.notifications.info(`${created} PNJ(s) créé(s) à partir des images.`);
if (openSheets && allCreated.length > 0) {
const lastActor = allCreated[allCreated.length - 1];
if (lastActor) lastActor.sheet?.render(true);
}
} catch (error) {
console.error(`${MODULE_ID} | Batch NPC creation error:`, error);
$text.text(`Erreur : ${error.message}`);
ui.notifications.error(`Erreur lors de la création : ${error.message}`);
}
}
}
Hooks.on('renderActorDirectory', (app, html, data) => {
if (!game.user?.isGM) return;
const $html = html instanceof jQuery ? html : $(html);
if ($html.find('.mgt2-batch-npc-btn').length) return;
const btn = $(`<button class="mgt2-batch-npc-btn" style="margin:4px 8px;flex:0;background:#2c2c3e;color:#d9b24c;border:1px solid #c9a227;">
<i class="fas fa-users"></i> Importer PNJs par lots
</button>`);
const shortcuts = [
{ icon: 'fa-balance-scale', label: 'Commerce', abbr: 'Commerce', action: 'commerce', cls: 'mgt2-shortcut-commerce', title: 'Ouvrir le commerce' },
{ icon: 'fa-user', label: 'PNJ rapide', abbr: 'PNJ', action: 'npc', cls: 'mgt2-shortcut-pnj', title: 'Générer un PNJ rapide' },
{ icon: 'fa-random', label: 'Rencontre', abbr: 'Rencontre', action: 'encounter', cls: 'mgt2-shortcut-rencontre', title: 'Générer une rencontre' },
{ icon: 'fa-briefcase', label: 'Mission', abbr: 'Mission', action: 'mission', cls: 'mgt2-shortcut-mission', title: 'Générer une mission' },
{ icon: 'fa-image', label: 'Fermer portraits', abbr: 'Fermer img', action: 'closePortraits', cls: 'mgt2-shortcut-closeimg', title: 'Fermer les portraits affichés aux joueurs' },
];
const grid = $(`<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:4px 8px;"></div>`);
shortcuts.forEach(s => {
const b = $(`<button class="${s.cls}" title="${s.title}" style="background:#2c2c3e;color:#d9b24c;border:1px solid #c9a227;padding:4px 6px;font-size:0.78em;cursor:pointer;border-radius:3px;text-align:center;">
<i class="fas ${s.icon}"></i> ${s.abbr}
</button>`).on('click', () => {
if (s.action === 'commerce') new CommerceDialog().render({ force: true });
else if (s.action === 'closePortraits') {
game.socket.emit(`module.${MODULE_ID}`, { _type: 'closeImagePopout' });
ui.notifications.info('Fermeture des portraits envoyée aux joueurs');
}
else new NpcDialog({ initialTab: s.action }).render({ force: true });
});
grid.append(b);
});
const footer = $html.find('.directory-footer');
if (footer.length) {
footer.append(btn);
footer.append(grid);
} else {
$html.find('.directory-header').after(btn);
$html.find('.directory-header').after(grid);
}
btn.on('click', () => {
new BatchNpcDialog().render({ force: true });
});
});
Hooks.on('renderSceneDirectory', (app, html, data) => {
if (!game.user?.isGM) return;
const $html = html instanceof jQuery ? html : $(html);
if ($html.find('.mgt2-sector-btn').length) return;
const sectorBtn = $(`<button class="mgt2-sector-btn" style="margin:4px 8px;flex:0;background:#2c2c3e;color:#d9b24c;border:1px solid #c9a227;cursor:pointer;">
<i class="fas fa-map"></i> Traveller Map
</button>`).on('click', () => {
new SectorMapApp().render({ force: true });
});
const canvasBtn = $(`<button class="mgt2-canvas-sync-btn" style="margin:4px 8px;flex:0;background:#2c2c3e;color:#d9b24c;border:1px solid #c9a227;cursor:pointer;">
<i class="fas fa-eye"></i> Sync vue MJ
</button>`).on('click', () => {
const pivot = canvas.stage?.pivot;
const scale = canvas.stage?.scale?.x;
if (!pivot || !scale) { ui.notifications.warn('Canvas pas encore prêt'); return; }
const players = game.users.filter(u => !u.isGM && u.active).map(u => u.id);
if (!players.length) { ui.notifications.warn('Aucun joueur connecté'); return; }
game.socket.emit(`module.${MODULE_ID}`, {
_type: 'canvasSync',
x: pivot.x,
y: pivot.y,
scale,
});
ui.notifications.info(`Vue synchronisée chez ${players.length} joueur(s)`);
});
const grid = $(`<div style="display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:4px 8px;"></div>`);
grid.append(sectorBtn, canvasBtn);
const footer = $html.find('.directory-footer');
if (footer.length) footer.append(grid);
else $html.find('.directory-header').after(grid);
});
+105
View File
@@ -0,0 +1,105 @@
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
export function imperialToSolomani(impYear) {
return Math.round(impYear * 1.0006644 + 4521);
}
export function imperialToVilani(impYear) {
return Math.round((impYear + 4045) / 1.312);
}
export function imperialToZhodani(impYear) {
const raw = (impYear + 6731) / 2.26;
const olympiad = Math.floor(raw);
const frac = raw - olympiad;
const year = Math.min(3, Math.max(1, Math.round(frac * 3)));
return { olympiad, year, label: `${olympiad}.${year}` };
}
export function imperialToAslan(impYear) {
return Math.round((impYear + 2083) / 0.877);
}
export function imperialToKkree(impYear) {
return Math.round((impYear + 8145) / 1.2);
}
export function imperialToHiver(impYear) {
return Math.round((impYear - 410) * 2);
}
export function computeConversions(impYear) {
const y = impYear ?? 1116;
const z = imperialToZhodani(y);
return {
solomani: `${imperialToSolomani(y)} AD`,
vilani: `${imperialToVilani(y)} VI`,
zhodani: z.label,
aslan: `${imperialToAslan(y)}`,
kkree: `${imperialToKkree(y)}`,
hiver: `${imperialToHiver(y)} HF`,
};
}
export const DAY_NAMES = [
'Holiday',
'Wonday',
'Tuday',
'Thirday',
'Forday',
'Fiday',
'Sixday',
'Senday',
];
export const DAY_LABELS_FR = {
Holiday: 'Holiday',
Wonday: 'Wonday',
Tuday: 'Tuday',
Thirday: 'Thirday',
Forday: 'Forday',
Fiday: 'Fiday',
Sixday: 'Sixday',
Senday: 'Senday',
};
export function getDayLabel(day) {
if (day < 1 || day > 365) return '?';
if (day === 1) return DAY_NAMES[0];
return DAY_NAMES[((day - 2) % 7) + 1];
}
export function getWeekday(day) {
if (day < 1 || day > 365) return -1;
if (day === 1) return -1;
return (day - 2) % 7;
}
export function formatImperialDate({ year, day, hour, minute, planet } = {}) {
const y = year ?? 1116;
const d = day ?? 1;
const dayLabel = getDayLabel(d);
const dayStr = String(d).padStart(3, '0');
let label = `${dayLabel}, ${dayStr}-${y} IC`;
if (hour != null && minute != null) {
const h = String(Math.round(hour)).padStart(2, '0');
const m = String(Math.round(minute)).padStart(2, '0');
label += `${h}:${m}`;
if (planet) label += ` (${planet})`;
}
return label;
}
export function getDefaultCalendar() {
return { year: 1116, day: 1, hour: 12, minute: 0, planet: '' };
}
export function formatRealDate() {
const d = new Date();
const y = d.getFullYear();
const m = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
const h = String(d.getHours()).padStart(2, '0');
const min = String(d.getMinutes()).padStart(2, '0');
return `${day}/${m}/${y} ${h}:${min}`;
}
+95
View File
@@ -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 } },
];
+274
View File
@@ -0,0 +1,274 @@
import { CalendarApp } from './CalendarApp.js';
import { CalendarConfigDialog } from './CalendarConfigDialog.js';
import { JournalNoteDialog } from './JournalNoteDialog.js';
import { getDefaultCalendar, formatImperialDate, formatRealDate } from './calendarDate.js';
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
const ChatLogV2 = foundry.applications.sidebar.tabs.ChatLog;
let _calendarApp = null;
function readCalendar() {
return {
year: game.settings.get(MODULE_ID, 'calendarYear'),
day: game.settings.get(MODULE_ID, 'calendarDay'),
hour: game.settings.get(MODULE_ID, 'calendarHour'),
minute: game.settings.get(MODULE_ID, 'calendarMinute'),
planet: game.settings.get(MODULE_ID, 'calendarPlanet'),
};
}
async function writeCalendar(data) {
await game.settings.set(MODULE_ID, 'calendarYear', data.year);
await game.settings.set(MODULE_ID, 'calendarDay', data.day);
await game.settings.set(MODULE_ID, 'calendarHour', data.hour);
await game.settings.set(MODULE_ID, 'calendarMinute', data.minute);
await game.settings.set(MODULE_ID, 'calendarPlanet', data.planet);
}
function applyCalendar(data) {
if (_calendarApp) {
_calendarApp.updateDisplay(data);
}
}
async function broadcastCalendar(data) {
if (!game.socket?.emit) return;
game.socket.emit(`module.${MODULE_ID}`, {
_type: 'calendarSync',
...data,
});
}
function openCalendarApp() {
if (_calendarApp) {
_calendarApp.bringToTop();
return;
}
const data = readCalendar();
_calendarApp = new CalendarApp({
onConfig: () => openCalendarConfig(),
onNote: () => openJournalNote(),
onClose: () => { _calendarApp = null; },
});
_calendarApp.updateDisplay(data);
_calendarApp.render({ force: true });
}
function openCalendarConfig() {
const data = readCalendar();
const dialog = new CalendarConfigDialog({
data,
onApply: async (newData) => {
await writeCalendar(newData);
applyCalendar(newData);
broadcastCalendar(newData);
dialog.close();
},
});
dialog.render({ force: true });
}
async function appendJournalNote(content, { dateLabel, realLabel } = {}) {
if (!content) return false;
const journalName = game.settings.get(MODULE_ID, 'calendarJournalName') || 'Journal de bord';
let journal = game.journal.getName(journalName);
if (!journal) {
journal = await JournalEntry.create({ name: journalName });
if (!journal) {
ui.notifications.error('Impossible de créer le journal');
return false;
}
}
if (!dateLabel || !realLabel) {
const c = readCalendar();
dateLabel = formatImperialDate(c);
realLabel = formatRealDate();
}
const safeContent = $('<div>').text(content).html().replace(/\n/g, '<br>');
const noteHtml = `<hr>
<h3>${realLabel}${dateLabel}</h3>
<p>${safeContent}</p>`;
let page = journal.pages.contents[0];
if (!page) {
[page] = await journal.createEmbeddedDocuments('JournalEntryPage', [{ name: 'Notes', text: { content: '' } }]);
}
if (!page) {
ui.notifications.error('Impossible de créer une page dans le journal');
return false;
}
const existing = page.text?.content || '';
await page.update({ 'text.content': existing + '\n' + noteHtml });
ui.notifications.info(`Note ajoutée au journal « ${journalName} »`);
return true;
}
function openJournalNote() {
const calendar = readCalendar();
const dateLabel = formatImperialDate(calendar);
const realLabel = formatRealDate();
const dialog = new JournalNoteDialog({
imperialDate: dateLabel,
realDate: realLabel,
onSave: async (content) => {
const ok = await appendJournalNote(content, { dateLabel, realLabel });
if (ok) dialog.close();
},
});
dialog.render({ force: true });
}
function registerCalCommand() {
if (!ChatLogV2?.CHAT_COMMANDS) return;
ChatLogV2.CHAT_COMMANDS.cal = {
rgx: /^\/cal(?:\s+(.*))?$/i,
fn: () => {
openCalendarApp();
return false;
},
};
console.log(`${MODULE_ID} | Commande /cal enregistrée via ChatLog.CHAT_COMMANDS`);
}
Hooks.once('init', () => {
const def = getDefaultCalendar();
game.settings.register(MODULE_ID, 'calendarYear', {
name: 'Année impériale',
scope: 'world',
config: false,
type: Number,
default: def.year,
});
game.settings.register(MODULE_ID, 'calendarDay', {
name: 'Jour impérial (1-365)',
scope: 'world',
config: false,
type: Number,
default: def.day,
});
game.settings.register(MODULE_ID, 'calendarHour', {
name: 'Heure locale',
scope: 'world',
config: false,
type: Number,
default: def.hour,
});
game.settings.register(MODULE_ID, 'calendarMinute', {
name: 'Minute locale',
scope: 'world',
config: false,
type: Number,
default: def.minute,
});
game.settings.register(MODULE_ID, 'calendarPlanet', {
name: 'Planète',
scope: 'world',
config: false,
type: String,
default: def.planet,
});
game.settings.register(MODULE_ID, 'calendarAutoOpen', {
name: 'Calendrier impérial : auto-ouverture',
hint: 'Ouvre automatiquement la fenêtre du calendrier au démarrage',
scope: 'world',
config: true,
type: Boolean,
default: true,
});
game.settings.register(MODULE_ID, 'calendarJournalName', {
name: 'Journal de bord',
hint: 'Nom du journal utilisé pour les notes de session. Créé automatiquement s\'il n\'existe pas.',
scope: 'world',
config: true,
type: String,
default: 'Journal de bord',
});
game.settings.register(MODULE_ID, 'calendarPosition', {
scope: 'client',
config: false,
type: Object,
default: {},
});
const loadTemplatesFn = foundry.applications?.handlebars?.loadTemplates || loadTemplates;
if (loadTemplatesFn) {
loadTemplatesFn([
`modules/${MODULE_ID}/templates/calendar-app.hbs`,
`modules/${MODULE_ID}/templates/calendar-config.hbs`,
`modules/${MODULE_ID}/templates/journal-note-dialog.hbs`,
]);
}
registerCalCommand();
console.log(`${MODULE_ID} | Calendrier impérial initialisé`);
});
Hooks.once('ready', () => {
game.socket.on(`module.${MODULE_ID}`, (data) => {
if (game.user?.isGM) return;
if (data?._type !== 'calendarSync') return;
if (!_calendarApp) {
const { year, day, hour, minute, planet } = data;
_calendarApp = new CalendarApp({
onClose: () => { _calendarApp = null; },
});
_calendarApp.updateDisplay({ year, day, hour, minute, planet });
_calendarApp.render({ force: true });
return;
}
_calendarApp.updateDisplay(data);
});
if (game.settings.get(MODULE_ID, 'calendarAutoOpen')) {
openCalendarApp();
}
});
Hooks.on('preCreateChatMessage', (message, data, options) => {
const content = message.content?.trim()?.toLowerCase();
if (content === '/cal' || content?.startsWith('/cal ')) {
openCalendarApp();
return false;
}
});
Hooks.on('chatMessage', (...args) => {
let message;
if (args[0]?.content !== undefined) {
message = args[0].content;
} else if (typeof args[1] === 'string') {
message = args[1];
} else {
return;
}
const trimmed = message?.trim()?.toLowerCase();
if (trimmed === '/cal' || trimmed?.startsWith('/cal ')) {
openCalendarApp();
return false;
}
});
Hooks.on('renderChatInput', (app, html, data) => {
let input;
if (html?.element) {
input = $(html.element).find('textarea[name="content"]');
} else if (html?.find) {
input = html.find('textarea[name="content"]');
} else {
input = $(html).find('textarea[name="content"]');
}
if (input.data('mgt2-cal-listener')) return;
input.data('mgt2-cal-listener', true);
input.on('keydown', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
const content = input.val()?.trim();
if (content === '/cal' || content?.startsWith('/cal ')) {
event.preventDefault();
event.stopImmediatePropagation();
openCalendarApp();
input.val('');
}
}
});
});
+3
View File
@@ -37,6 +37,9 @@ Hooks.once('init', () => {
`modules/${MODULE_ID}/templates/npc-result.hbs`, `modules/${MODULE_ID}/templates/npc-result.hbs`,
`modules/${MODULE_ID}/templates/traveller-npc-dialog.hbs`, `modules/${MODULE_ID}/templates/traveller-npc-dialog.hbs`,
`modules/${MODULE_ID}/templates/traveller-npc-result.hbs`, `modules/${MODULE_ID}/templates/traveller-npc-result.hbs`,
`modules/${MODULE_ID}/templates/ally-enemy-result.hbs`,
`modules/${MODULE_ID}/templates/batch-npc-dialog.hbs`,
`modules/${MODULE_ID}/templates/player-map.hbs`,
]); ]);
} }
+202
View File
@@ -0,0 +1,202 @@
/**
* MGT2 Commandes /sector et /subsector
*
* Ouvre l'application interactive SectorMapApp (IFRAME Traveller Map).
* Compatible Foundry VTT v13 et v14
*/
import { SectorMapApp } from './SectorMapApp.js';
import { postWorldCardToChat } from './worldCard.js';
import { CommerceDialog } from './CommerceDialog.js';
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
const ChatLogV2 = foundry.applications?.sidebar?.tabs?.ChatLog;
let _pendingHandle = false;
/* ───── Fonctions partagées ───── */
async function openMap(sector, subsector, { readOnly = false } = {}) {
const app = new SectorMapApp(sector, subsector, { readOnly });
await app.render({ force: true });
return app;
}
async function handleSectorCommand(sector, subsector) {
if (_pendingHandle) return;
_pendingHandle = true;
try {
if (!game.user?.isGM) {
ui.notifications.error('Seul le MJ peut utiliser cette commande');
return;
}
await openMap(sector?.trim());
} catch (err) {
console.error(`${MODULE_ID} | Erreur /sector :`, err);
ui.notifications.error(`Erreur : ${err.message}`);
} finally {
_pendingHandle = false;
}
}
async function handleSystemCommand(sector, hex) {
if (!sector || !hex) {
ui.notifications.warn('Usage : /system <secteur> <hex> (ex: /system "Spinward Marches" 1910)');
return;
}
await postWorldCardToChat(sector, hex);
}
async function handleSubsectorCommand(raw) {
if (_pendingHandle) return;
_pendingHandle = true;
try {
const parts = raw?.trim().split(/\s+/);
if (!parts || parts.length < 2) {
ui.notifications.warn('Usage : /subsector <secteur> <sous-secteur> (ex: /subsector "Spinward Marches" C)');
return;
}
const sub = parts.pop();
const sector = parts.join(' ');
if (!sector || !sub) {
ui.notifications.warn('Usage : /subsector <secteur> <sous-secteur>');
return;
}
if (!game.user?.isGM) {
ui.notifications.error('Seul le MJ peut utiliser cette commande');
return;
}
await openMap(sector.trim(), sub.trim());
} catch (err) {
console.error(`${MODULE_ID} | Erreur /subsector :`, err);
ui.notifications.error(`Erreur : ${err.message}`);
} finally {
_pendingHandle = false;
}
}
/* ───── Commande /sector ───── */
if (ChatLogV2?.CHAT_COMMANDS) {
ChatLogV2.CHAT_COMMANDS['sector'] = {
rgx: /^\/sector(?:\s+(.*))?$/i,
fn: function() {
const raw = arguments[1]?.[1]?.trim?.();
handleSectorCommand(raw || undefined);
return false;
},
};
console.log(`${MODULE_ID} | Commande /sector enregistrée`);
ChatLogV2.CHAT_COMMANDS['subsector'] = {
rgx: /^\/subsector(?:\s+(.*))?$/i,
fn: function() {
const raw = arguments[1]?.[1]?.trim?.();
if (raw) {
handleSubsectorCommand(raw);
return false;
}
return true;
},
};
console.log(`${MODULE_ID} | Commande /subsector enregistrée`);
ChatLogV2.CHAT_COMMANDS['system'] = {
rgx: /^\/system\s+(.+?)\s+(\d{4})\s*$/i,
fn: function() {
const sector = arguments[1]?.[1]?.trim?.();
const hex = arguments[1]?.[2]?.trim?.();
if (sector && hex) {
handleSystemCommand(sector, hex);
return false;
}
return true;
},
};
console.log(`${MODULE_ID} | Commande /system enregistrée`);
}
/* ───── Hooks de secours (v13 / fallback v14) ───── */
Hooks.on('preCreateChatMessage', (message, data, options) => {
const c = message.content?.trim();
let m = c?.match(/^\/sector(?:\s+(.*))?$/i);
if (m) {
handleSectorCommand(m[1]?.trim());
return false;
}
m = c?.match(/^\/subsector(?:\s+(.*))?$/i);
if (m) {
handleSubsectorCommand(m[1]?.trim());
return false;
}
m = c?.match(/^\/system\s+(.+?)\s+(\d{4})\s*$/i);
if (m) {
handleSystemCommand(m[1]?.trim(), m[2]?.trim());
return false;
}
});
/* ───── Socket (synchronisation MJ → joueurs) ───── voir sectorSocket.js ───── */
Hooks.once('ready', () => {
document.addEventListener('click', (event) => {
const link = event.target.closest('.mgt2-world-link');
if (!link) return;
event.preventDefault();
const sector = link.dataset.sector;
const hex = link.dataset.hex;
if (sector && hex) {
handleSystemCommand(sector, hex);
}
});
// Clics sur le bouton Commerce dans les cartes de monde
document.addEventListener('click', (event) => {
const btn = event.target.closest('.mgt2-world-commerce');
if (!btn) return;
event.preventDefault();
const uwp = btn.dataset.uwp;
const zone = btn.dataset.zone;
const name = btn.dataset.name;
const sector = btn.dataset.sector;
const hex = btn.dataset.hex;
if (uwp) {
const existing = Object.values(ui.windows).find(w => w.id === 'mgt2-commerce');
if (existing) { existing.bringToTop(); return; }
const dialog = new CommerceDialog({
defaultWorld: { uwp, zone, name, sector, hex },
initialTab: 'trade',
});
dialog.render({ force: true });
}
});
});
Hooks.on('chatMessage', (...args) => {
let msg;
if (args[0]?.content !== undefined) msg = args[0].content;
else if (typeof args[1] === 'string') msg = args[1];
else return;
let m = msg?.trim()?.match(/^\/sector(?:\s+(.*))?$/i);
if (m) {
handleSectorCommand(m[1]?.trim());
return false;
}
m = msg?.trim()?.match(/^\/subsector(?:\s+(.*))?$/i);
if (m) {
handleSubsectorCommand(m[1]?.trim());
return false;
}
m = msg?.trim()?.match(/^\/system\s+(.+?)\s+(\d{4})\s*$/i);
if (m) {
handleSystemCommand(m[1]?.trim(), m[2]?.trim());
return false;
}
});
+108
View File
@@ -0,0 +1,108 @@
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
let _playerDialog = null;
class PlayerMapDialog extends HandlebarsApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = {
id: 'mgt2-sector-map-player',
classes: ['mgt2-sector-map'],
position: { width: 960, height: 720 },
window: { icon: 'fas fa-map', resizable: true },
};
static PARTS = {
main: {
template: `modules/${MODULE_ID}/templates/player-map.hbs`,
},
};
constructor(url, sector) {
super();
this._mapUrl = url;
this._sectorLabel = sector ? `Secteur ${sector}` : 'Carte stellaire';
}
get title() { return this._sectorLabel; }
async _prepareContext() {
return { url: this._mapUrl };
}
updateMap(url) {
this._mapUrl = url;
const iframe = this.element?.querySelector('iframe');
if (iframe) iframe.src = url;
}
}
function buildMapUrl(sector, subsector, mapHex, mapCenter, mapScale) {
const base = 'https://travellermap.com';
if (!sector) return `${base}/?style=mongoose&hideui=1`;
if (subsector) {
let url = `${base}/?sector=${encodeURIComponent(sector)}&subsector=${encodeURIComponent(subsector)}&style=mongoose&hideui=1`;
if (mapCenter && mapScale) url += `&center=${mapCenter}&scale=${mapScale}`;
return url;
}
let url = `${base}/?sector=${encodeURIComponent(sector)}&style=mongoose&hideui=1`;
if (mapHex) {
url += `&hex=${mapHex}&scale=512`;
} else if (mapCenter && mapScale) {
url += `&center=${mapCenter}&scale=${mapScale}`;
}
return url;
}
Hooks.once('ready', () => {
console.log(`${MODULE_ID} | sectorSocket pret`);
game.socket.on(`module.${MODULE_ID}`, (data) => {
if (game.user?.isGM) return;
if (data?._type === 'sectorMapSync') {
try {
const url = buildMapUrl(data.sector, data.subsector, data.mapHex, data.mapCenter, data.mapScale);
if (_playerDialog) {
_playerDialog.updateMap(url);
_playerDialog.bringToTop?.();
console.log(`${MODULE_ID} | Carte mise a jour`);
return;
}
_playerDialog = new PlayerMapDialog(url, data.sector);
_playerDialog.render({ force: true }).then(() => {
console.log(`${MODULE_ID} | Dialog affiche`);
}).catch(err => {
console.error(`${MODULE_ID} | Erreur render:`, err);
});
} catch (err) {
console.error(`${MODULE_ID} | Erreur:`, err);
}
return;
}
if (data?._type === 'canvasSync') {
try {
const { x, y, scale } = data;
console.log(`${MODULE_ID} | Canvas sync recu:`, x, y, scale);
canvas.animatePan({ x, y, scale, duration: 0 });
} catch (err) {
console.error(`${MODULE_ID} | Erreur canvas sync:`, err);
}
return;
}
if (data?._type === 'closeImagePopout') {
try {
let closed = 0;
document.querySelectorAll('.image-popout, [class*="image-popout"], [id*="image-popout"]').forEach(el => {
el.remove(); closed++;
});
console.log(`${MODULE_ID} | ${closed} portrait(s) fermé(s)`);
} catch (err) {
console.error(`${MODULE_ID} | Erreur close portraits:`, err);
}
return;
}
});
});
+1 -1
View File
@@ -12,7 +12,7 @@ function buildStartupNoticeHtml() {
<div style="padding:6px 8px;border-left:4px solid #c9a227;background:#f5f0e8;color:#2c2c3e;line-height:1.45;"> <div style="padding:6px 8px;border-left:4px solid #c9a227;background:#f5f0e8;color:#2c2c3e;line-height:1.45;">
<strong>MgT2e outils</strong> <strong>MgT2e outils</strong>
<span style="color:#6a5422;">·</span> <span style="color:#6a5422;">·</span>
<code>/commerce</code> <code>/pnj</code> <code>/rencontre</code> <code>/mission</code> <code>/cal</code> <code>/commerce</code> <code>/pnj</code> <code>/rencontre</code> <code>/mission</code> <code>/sector</code>
<div style="margin-top:2px;font-size:0.92em;color:#6a5422;"> <div style="margin-top:2px;font-size:0.92em;color:#6a5422;">
par <a href="https://www.uberwald.me" target="_blank" rel="noopener noreferrer">LeRatierBretonnien / Uberwald</a> par <a href="https://www.uberwald.me" target="_blank" rel="noopener noreferrer">LeRatierBretonnien / Uberwald</a>
</div> </div>

Some files were not shown because too many files have changed in this diff Show More