Compare commits

..

33 Commits

Author SHA1 Message Date
7adc1b3f07 Auto-release
All checks were successful
Release Creation / build (release) Successful in 2m22s
2026-03-01 09:41:06 +01:00
6c70dc147c DataModels + Appv2 migration : OK 2026-03-01 01:12:00 +01:00
1ffb8b08fc DataModels + Appv2 migration : OK 2026-02-28 21:00:06 +01:00
8017bb207d Datamodel + Appv2 migration, WIP 2026-01-13 08:10:04 +01:00
364278527d Datamodel + Appv2 migration, WIP 2026-01-13 08:09:11 +01:00
93d35abde2 Datamodel + Appv2 migration, WIP 2026-01-12 23:33:35 +01:00
0409be64eb Datamodel + Appv2 migration, WIP 2026-01-12 23:33:14 +01:00
ed76a49e7d Datamodel + Appv2 migration, WIP 2026-01-12 23:32:51 +01:00
2abd2c881a Sync bol compendiums 2026-01-12 23:01:53 +01:00
3fa5ca66d1 Minor fix for chronicles v1 2025-12-04 18:26:26 +01:00
6de15eeda7 Ajout alignement Mythic+
Some checks failed
Release Creation / build (release) Failing after 43s
2025-09-28 20:52:09 +02:00
724c096743 Ajout alignement Mythic+
Some checks failed
Release Creation / build (release) Failing after 39s
2025-09-28 20:47:31 +02:00
3e3a4b9ec1 Ajout alignement Mythic+
Some checks failed
Release Creation / build (release) Failing after 41s
2025-09-28 20:42:01 +02:00
425a2a1fc0 Fix odd difficulties
Some checks failed
Release Creation / build (release) Failing after 45s
2025-08-24 22:18:39 +02:00
203d7add66 FIx journals color
Some checks failed
Release Creation / build (release) Failing after 1m38s
2025-07-08 22:34:13 +02:00
8ae193dc34 Fix items sheets
Some checks failed
Release Creation / build (release) Failing after 1m0s
2025-05-15 18:18:40 +02:00
3904f595b5 Add repo
Some checks failed
Release Creation / build (release) Failing after 1m1s
2025-05-15 15:29:49 +02:00
1bf21fae7f Add repo 2025-05-15 14:29:11 +02:00
22579c21bc Update for Foundry v13
Some checks failed
Release Creation / build (release) Failing after 58s
2025-05-09 21:58:37 +02:00
50c5c31e7b Update for Foundry v13 2025-05-09 21:56:03 +02:00
a8c05cd4de Update for Foundry v13 2025-05-09 21:54:29 +02:00
e1cea78059 Update for Foundry v13 2025-05-09 21:54:10 +02:00
c2f9934f5f Fix welcome message 2025-03-31 07:21:30 +02:00
313c8a85fa Fix welcome message 2025-03-31 07:15:38 +02:00
74f6d4d54a Fix empty lands, Fix #33 2025-02-12 07:02:24 +01:00
2eb153ce14 Various fixes for translations 2025-02-11 21:55:04 +01:00
fe513e821a Fix AIS from ARESIA inputs 2025-02-11 21:45:30 +01:00
1b72c9c467 Various fixes for rulebook module 2025-02-10 20:28:13 +01:00
3c3a0901da Fix issues #30 and #31 2025-02-09 21:08:20 +01:00
3fc6d3e3df Fix issues #30 and #31 2025-02-09 21:08:02 +01:00
69b34669b0 Sync bol for module releas 2025-01-25 20:12:00 +01:00
89bcc47b11 Ready for module !! 2024-12-12 21:50:28 +01:00
dda70636a1 New translalation 2024-12-02 15:26:59 +01:00
262 changed files with 9309 additions and 2377 deletions

View File

@@ -8,47 +8,56 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: echo "💡 The ${{ gitea.repository }} repository will cloned to the runner." - run: echo "💡 The ${{ gitea.repository }} repository will cloned to the runner."
#- uses: actions/checkout@v3 #- uses: actions/checkout@v3
- uses: RouxAntoine/checkout@v3.5.4 - uses: RouxAntoine/checkout@v3.5.4
with:
ref: 'v10'
# get part of the tag after the `v` # get part of the tag after the `v`
- name: Extract tag version number - name: Extract tag version number
id: get_version id: get_version
uses: battila7/get-version-action@v2 uses: battila7/get-version-action@v2
# Substitute the Manifest and Download URLs in the module.json # Substitute the Manifest and Download URLs in the system.json
- name: Substitute Manifest and Download Links For Versioned Ones - name: Substitute Manifest and Download Links For Versioned Ones
id: sub_manifest_link_version id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1 uses: microsoft/variable-substitution@v1
with: with:
files: 'system.json' files: "system.json"
env: env:
version: ${{steps.get_version.outputs.version-without-v}} version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/public/bol url: https://www.uberwald.me/gitea/${{gitea.repository}}
manifest: https://www.uberwald.me/gitea/public/bol/releases/latest/system.json manifest: https://www.uberwald.me/gitea/public/bol/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/public/bol/releases/download/${{github.event.release.tag_name}}/bol.zip download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/bol.zip
# Create a zip file with all files required by the module to add to the release # Create a zip file with all files required by the module to add to the release
- run: | - run: |
apt update -y apt update -y
apt install -y zip apt install -y zip
- run: zip -r ./bol.zip system.json template.json README.md LICENSE assets/ css/ fonts/ images/ lang/ module/ packs/ styles/ templates/ ui/ - run: zip -r ./bol.zip system.json template.json README.md LICENSE assets/ compendiums/ css/ fonts/ images/ lang/ module/ packs/ styles/ templates/ ui/
- name: setup go - name: setup go
uses: https://github.com/actions/setup-go@v4 uses: https://github.com/actions/setup-go@v4
with: with:
go-version: '>=1.20.1' go-version: ">=1.20.1"
- name: Use Go Action - name: Use Go Action
id: use-go-action id: use-go-action
uses: https://gitea.com/actions/release-action@main uses: https://gitea.com/actions/release-action@main
with: with:
files: |- files: |-
./bol.zip ./bol.zip
system.json system.json
api_key: '${{secrets.RELEASE_TOKEN_UBERWALD}}' api_key: "${{secrets.ALLOW_PUSH_RELEASE}}"
- name: Publish to Foundry server
uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
with:
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
id: "bol"
version: ${{github.event.release.tag_name}}
manifest: "https://www.uberwald.me/gitea/public/bol/releases/download/latest/system.json"
notes: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/bol.zip"
compatibility-minimum: "13"
compatibility-verified: "13"

286
APPV2_ITEMS_MIGRATION.md Normal file
View File

@@ -0,0 +1,286 @@
# Migration AppV2 - Item Sheets
## Vue d'ensemble
Migration des feuilles d'items de BoL vers l'architecture **ApplicationV2** de Foundry VTT v12+.
## Fichiers créés
### Classes AppV2 (module/applications/sheets/)
1. **base-item-sheet.mjs** - Classe de base pour tous les item sheets
- Hérite de `foundry.applications.sheets.ItemSheetV2`
- Utilise `HandlebarsApplicationMixin`
- Gère les tabs, drag & drop, actions communes
- 272 lignes de code
2. **item-sheet.mjs** - Sheet pour le type "item"
- Équipements, armes, protections, sorts, etc.
- Hérite de `BoLBaseItemSheet`
- Contexte spécifique aux items
3. **feature-sheet.mjs** - Sheet pour le type "feature"
- Boons, flaws, careers, origins, races, etc.
- Hérite de `BoLBaseItemSheet`
- Contexte spécifique aux features
4. **_module.mjs** - Export des sheets
### Templates (templates/item/)
1. **item-sheet.hbs** - Template pour les items
2. **feature-sheet.hbs** - Template pour les features
3. **parts/item-header.hbs** - Header mis à jour pour AppV2
### Backups
- `templates/item.backup/` - Backup des templates originaux
- `module/item.backup/` - Backup de l'ancienne classe ItemSheet
- `templates/item/parts/item-header.hbs.backup` - Backup du header
## Architecture AppV2
### Différences avec AppV1
| Aspect | AppV1 (avant) | AppV2 (après) |
|--------|---------------|---------------|
| Classe de base | `ItemSheet` | `ItemSheetV2` |
| Options | `static get defaultOptions()` | `static DEFAULT_OPTIONS` |
| getData | `async getData()` | `async _prepareContext()` |
| Template | Unique | Peut utiliser PARTS |
| Tabs | Automatiques | Gestion manuelle |
| Actions | Event listeners | `actions:` dans OPTIONS |
| Render | `activateListeners()` | `_onRender()` |
### Structure de BoLBaseItemSheet
```javascript
class BoLBaseItemSheet extends ItemSheetV2 {
// Options statiques
static DEFAULT_OPTIONS = {
classes, position, form, window,
actions, dragDrop, tabs
}
// State
tabGroups = { primary: "description" }
// Méthodes principales
async _prepareContext() // Prépare les données
_onRender() // Après le render
_activateTabs() // Active les tabs
_activateListeners() // Event listeners
// Actions
static #onEditImage() // data-action="editImage"
static #onPostItem() // data-action="postItem"
// Drag & Drop
#createDragDropHandlers()
_canDragStart()
_onDragStart()
}
```
## Fonctionnalités migrées
### ✅ Depuis l'ancien BoLItemSheet
1. **getData****_prepareContext**
- Enrichissement de la description
- Configuration des données dynamiques
- Gestion des catégories
- Propriétés des items
- Careers depuis l'actor
2. **Tabs**
- Navigation entre Description et Properties
- State persistant avec `tabGroups`
- Activation manuelle des tabs
3. **Actions**
- Edit Image (FilePicker)
- Post Item (chat)
4. **Listeners spécifiques**
- Armor quality → soak formula
5. **Dynamic defaults**
- Category par défaut
- Spell conditions (mandatory/optional)
- Equipment slots
## Utilisation dans les templates
### Actions (data-action)
```handlebars
{{!-- Éditer l'image --}}
<img data-action="editImage" src="{{item.img}}" />
{{!-- Poster au chat --}}
<button data-action="postItem">
<i class="fas fa-comment"></i>
</button>
```
### Tabs
```handlebars
{{!-- Navigation --}}
<nav class="tabs" data-group="primary">
{{#each tabs}}
<a data-tab="{{this.id}}" class="{{#if (eq ../activeTab this.id)}}active{{/if}}">
{{localize this.label}}
</a>
{{/each}}
</nav>
{{!-- Contenu --}}
<div class="tab {{#if (eq activeTab 'description')}}active{{/if}}"
data-tab="description">
...
</div>
```
### Editor
```handlebars
{{!-- AppV2 avec ProseMirror --}}
{{editor enrichedDescription
target="system.description"
button=true
editable=isEditable
engine="prosemirror"}}
```
## Context disponible dans les templates
```javascript
{
// Document & system
fields: schema.fields,
systemFields: system.schema.fields,
item: document,
system: document.system,
source: document.toObject(),
// Content
enrichedDescription: "HTML enrichi",
category: "equipment|weapon|spell|...",
itemProperties: ["prop1", "prop2"],
// Config
config: game.bol.config,
isGM: boolean,
isEditable: boolean,
// Tabs
tabs: [{id, label, icon}],
activeTab: "description|properties",
// Type-specific (item-sheet.mjs)
isItem: true,
isEquipment: boolean,
isWeapon: boolean,
isProtection: boolean,
isSpell: boolean,
// Type-specific (feature-sheet.mjs)
isFeature: true,
isBoon: boolean,
isFlaw: boolean,
isCareer: boolean,
isOrigin: boolean,
isRace: boolean,
// Optional
careers: actor.careers // Si item sur actor
}
```
## Configuration dans bol.js
```javascript
// Import
import * as sheets from "./applications/sheets/_module.mjs"
// Enregistrement
foundry.documents.collections.Items.unregisterSheet("core", ...)
foundry.documents.collections.Items.registerSheet("bol",
sheets.BoLItemSheet,
{ types: ["item"], makeDefault: true }
)
foundry.documents.collections.Items.registerSheet("bol",
sheets.BoLFeatureSheet,
{ types: ["feature"], makeDefault: true }
)
```
## Avantages de AppV2
1. **Performance** : Meilleur rendu et gestion des updates
2. **Structure** : Code plus organisé et maintenable
3. **Actions** : Système d'actions déclaratif
4. **Context** : Préparation des données séparée du template
5. **Standard** : Aligné sur Foundry VTT v12+
6. **Future-proof** : Architecture pérenne
## Points d'attention
### Compatibilité
- Les données restent 100% compatibles
- Seule l'interface de sheet change
- Pas de migration de données nécessaire
### Tabs
Les tabs ne sont plus automatiques dans AppV2 :
- Navigation manuelle avec `_activateTabs()`
- State persistant avec `tabGroups`
- CSS `.active` géré manuellement
### Editor
ProseMirror est maintenant le moteur par défaut :
```handlebars
{{editor content engine="prosemirror"}}
```
### Actions
Système déclaratif :
```javascript
actions: {
myAction: ClassName.#onMyAction
}
```
Dans le template :
```handlebars
<button data-action="myAction">Click</button>
```
## Prochaines étapes
### Court terme
1. Tester les sheets dans Foundry
2. Vérifier toutes les fonctionnalités
3. Ajuster les CSS si nécessaire
### Moyen terme
4. Migrer les actor sheets vers AppV2
5. Ajouter des features AppV2 (parts, etc.)
6. Optimiser les templates
### Long terme
7. Utiliser PARTS pour modularité
8. Ajouter des actions avancées
9. Améliorer la UX
## Références
- [Foundry AppV2 Documentation](https://foundryvtt.com/api/classes/foundry.applications.api.ApplicationV2.html)
- [ItemSheetV2 API](https://foundryvtt.com/api/classes/foundry.applications.sheets.ItemSheetV2.html)
- Exemples : fvtt-cthulhu-eternal, fvtt-mournblade

View File

@@ -1,18 +1,26 @@
Changes :
# v12.1.0 # v13.0.0
- Foundry v13 only
# v12.1.7
- Enhance welcome message
# v12.1.1
## French ## French
- Correction sur les conditions des sorts - Correction sur les conditions des sorts
- Jet d'armures correctement affichés - Jet d'armures correctement affichés
- Dégat à 0 possibles sur les armes/capacités - Dégat à 0 possibles sur les armes/capacités
## English ## English
- Corrected spell conditions - Corrected spell conditions
- Armor rolls are now correctly displayed - Armor rolls are now correctly displayed
- Weapon/capacity damage can now be set to 0 - Weapon/capacity damage can now be set to 0
# v12.1.0 # v12.1.0
- Gestion des Hordes - Gestion des Hordes
@@ -39,20 +47,19 @@ Changes :
# v11.0.8 # v11.0.8
- Correction sur les malus de bouclier (blocage) - Correction sur les malus de bouclier (blocage)
- Corrrection sur le malus d'init des boucliers qui était mal affiché dans la fiche d'item - Corrrection sur le malus d'init des boucliers qui était mal affiché dans la fiche d'item
# v10.4.0 # v10.4.0
- Ajout de la gestion d'effets - Ajout de la gestion d'effets
- Aide intégré - Aide intégré
- Intégration du PDF de la bougette - Intégration du PDF de la bougette
# v10.3.3 # v10.3.3
- Nouvelles clés de traduction - Nouvelles clés de traduction
- Lorsqu'une arme a un dé bonus, prise en compte plus claire du dé bonus et affichage de l'information dans la fenêtre de jet - Lorsqu'une arme a un dé bonus, prise en compte plus claire du dé bonus et affichage de l'information dans la fenêtre de jet
- Lorsqu'une arme relance les 1 sur ses dégats, l'information est affichée dans le tchat - Lorsqu'une arme relance les 1 sur ses dégats, l'information est affichée dans le tchat
- Termes corrects pour les PNJs (ie rival) - Termes corrects pour les PNJs (ie rival)
- Nouveaux équipements issus du Dieu Voilé - Nouveaux équipements issus du Dieu Voilé

View File

@@ -1,108 +1,108 @@
{ {
"label": "Gods&Traits", "label": "Gods&Traits",
"mapping": {
"description": "system.description"
},
"folders": { "folders": {
"Dieux": "Gods", "Dieux": "Gods",
"Pouvoirs démoniaques": "Demonic Powers" "Pouvoirs Démoniaques": "Demonic Powers"
},
"mapping": {
"description": "system.description"
}, },
"entries": { "entries": {
"Afyra, Deesse de la Vie": { "Afyra, Deesse de la Vie": {
"name": "Afyra, Goddess of Life", "name": "Afyra, Goddess of Life",
"description": "" "description": "The Lady of Healing is worshipped by many physicians, and women everywhere who want children. She is the daughter of Hurm and Piandra. She is usually depicted as a young woman dressed in a sort shift and sandals with a garland of flowers about her neck."
}, },
"Charkond, Dieu de la Guerre et des Batailles": { "Charkond, Dieu de la Guerre et des Batailles": {
"name": "Charkond, God of War, Lord of Battle", "name": "Charkond, God of War, Lord of Battle",
"description": "" "description": "The son of Hurm and Lilandra is depicted as a black-skinned warrior, with four arms and four andrak legs. He is clad in a golden breastplate, wears helm and shield and is armed with a spear although he is skilled with any weapon. He generally represents the chaos of battle, rather than the skill of military strategy. As such he is worshipped mainly by mercenaries, berserkers, mariners, and raiders."
}, },
"Chiomalla, Deesse des Grains et des Moissons": { "Chiomalla, Deesse des Grains et des Moissons": {
"name": "Chiomalla, Goddess of Crops, Lady of the Harvest", "name": "Chiomalla, Goddess of Crops, Lady of the Harvest",
"description": "" "description": "She is depicted as a mature but nonetheless attractive woman, usually dressed in a brown and green robe, carrying a sheaf of wheat and a staff. She is worshipped by farmers and anyone reliant upon grain or other crops for their livelihoods, such as innkeepers, bakers, and so on."
}, },
"Dyr, Dieu des Tempêtes et du Tonnerre": { "Dyr, Dieu des Tempêtes et du Tonnerre": {
"name": "Dyr, God of Storms, the Thunder Lord", "name": "Dyr, God of Storms, the Thunder Lord",
"description": "" "description": "A powerful god, Dyr is known to fly into rages for the most trivial of reasons. Dyr is depicted as a tall, muscular man wearing a mail shirt and a purple mantle. In battle he uses his halberd Jarnost (which was made by Yrzlak). Dyr is worshipped by just about everyone who is affected by stormy weather."
}, },
"Fillana, déesse des étoiles": { "Fillana, déesse des étoiles": {
"name": "Fillana, Goddess of the Stars", "name": "Fillana, Goddess of the Stars",
"description": "" "description": "The Lady of the Night Skies is worshipped by many who conduct their business at night and also by anyone reliant on the stars for navigation, such as whores, assassins, burglars, sailors, and sky-pilots."
}, },
"Fyrzon la Sentinelle, lInébranlable": { "Fyrzon la Sentinelle, lInébranlable": {
"name": "Fyrzon the Sentinel, the Steadfast", "name": "Fyrzon the Sentinel, the Steadfast",
"description": "" "description": "Also known as the Gate Keeper, Watcher of the Stairs. Fyrzon is often depicted as a man with the head of a bouphon, wielding a great iron mace named Mulkandrar. He is stalwart and true being able to stand for years without moving if needed. Once his feet are grounded, nothing can make him move. If you arent desired in Mezzechesh, you will not get past Fyrzon."
}, },
"Grondil, le dieu bâtisseur": { "Grondil, le dieu bâtisseur": {
"name": "Grondil the Builder God", "name": "Grondil the Builder God",
"description": "" "description": "The god of Bricks and Buildings, the Stoneworker, Grondil is depicted as a muscular, bearded man, either carrying a hod of bricks or chiselling a lump of stone. He built the villa of Mezzechesh and is worshipped by architects, builders, masons, and by anyone who owns or rents their own building (almost all)."
}, },
"Hadron le Noir , seigneur du Néant": { "Hadron le Noir , seigneur du Néant": {
"name": "Hadron, Lord of the Void, the Dark One", "name": "Hadron, Lord of the Void, the Dark One",
"description": "" "description": "Worshipped by the Black Druids and the Sorcerer-Kings, he is usually depicted as an androgynous human-shaped patch of night sky with stars scattered throughout his form. However, he can assume any form he likes. Hadron has the power of destruction at his fingertips. He is the ruler of the Void, where the Dark Gods and demons are said to dwell."
}, },
"Hurm, le père des dieux, seigneur du ciel": { "Hurm, le père des dieux, seigneur du ciel": {
"name": "Hurm, Father-God, God of the Sky", "name": "Hurm, Father-God, God of the Sky",
"description": "" "description": "The King of the Gods is depicted as a regal, mature man with a sturdy frame and dark beard, usually just wearing a belted leather kilt. He carries his royal sceptre in one hand and a bronze bell in the other. The bell is used to call all the other gods and goddesses to order, and has powers over men and beasts too. Hurm is worshipped by all."
}, },
"Iondal le débauché": { "Iondal le débauché": {
"name": "Iondal the Carouser", "name": "Iondal the Carouser",
"description": "" "description": "The Lord of Song and Debauchery is depicted as a pot-bellied man with a goblet in one hand and a haunch of meat in the other. There are usually naked women at his feet. His followers tend to be minstrels and bards, tavern-keepers, and decadent nobles."
}, },
"Karyzon, le seigneur des vents": { "Karyzon, le seigneur des vents": {
"name": "Karyzon the Wind Lord", "name": "Karyzon the Wind Lord",
"description": "" "description": "The Messenger of the Gods is depicted as a handsome and athletic-looking youth, sometimes with feathered wings sprouting from his back, sometimes without, and often naked. Karyzon is worshipped by the Winged Folk, as well as by sailors and sky-pilots. Karyzon is the son of Nemmereth and Lilandra."
}, },
"Knothakon, dieu de la sagesse, seigneur des âges": { "Knothakon, dieu de la sagesse, seigneur des âges": {
"name": "Knothakon, God of Wisdom, Lord of Aeons", "name": "Knothakon, God of Wisdom, Lord of Aeons",
"description": "" "description": "Knothakon is depicted as a man in a white robe with a massive head that is far too big for his shrivelled body. His head grows as he stores more knowledge, but shrinks a little when he imparts some of this knowledge to others. He is worshipped by scribes and scholars everywhere."
}, },
"Lilandra la séductrice": { "Lilandra la séductrice": {
"name": "Lilandra the Seductress", "name": "Lilandra the Seductress",
"description": "" "description": "The goddess of Love and Beauty is worshipped by many especially by courtesans and dancers. Lilandras temples tend to resemble very high- class brothels. She is depicted as a beautiful woman, and of all the goddesses most likely to appear nude or semi-nude. Poets praise the radiance of her smile and her laughter. Others appreciate her physical features rather more."
}, },
"Morgazzon, roi-démon de la folie": { "Morgazzon, roi-démon de la folie": {
"name": "Morgazzon, The Demonlord of Madness", "name": "Morgazzon, The Demonlord of Madness",
"description": "" "description": "Also, the Insane God. Worshipped by the Yellow Druids, Morgazzon is said to have one of his homes in the Festrel Swamp. Morgazzon often appears as a wild-eyed, long-bearded hermit in brown robes, although he sometimes appears as a shapeless mass of writhing tentacles with a yellow orb-like eye in the centre. Morgazzons followers are becoming highly ambitious of late, and are starting to appear in some of the cities of Lemuria."
}, },
"Nemmereth du long sommeil, dieu de la mort": { "Nemmereth du long sommeil, dieu de la mort": {
"name": "Nemmereth of the Long Sleep, The Death God", "name": "Nemmereth of the Long Sleep, The Death God",
"description": "" "description": "Worshipped by Lemurian priests in his guise as one of the Twenty Gods, Nemmereth is considered by Grey Druids to be a Dark God. He is brother to Hurm, with whom he had a falling out over Piandra, and was defeated in combat. He generally appears as a tall and kindly clean- shaven man wearing grey robes, although his other aspect (as a Dark Lord) has a skeletal form with maggots coming out of his eyes and mouth."
}, },
"Piandra, déesse de la chance, Dame Fortune": { "Piandra, déesse de la chance, Dame Fortune": {
"name": "Piandra, The Goddess of Luck, Lady of Fortune", "name": "Piandra, The Goddess of Luck, Lady of Fortune",
"description": "" "description": "She wears colourful patchwork garb and has dice in one hand and cards in the other. Piandra is worshipped by gamblers and anyone who needs a slice of luck when all other gods have failed them."
}, },
"Quathoomar, dieu du voyage": { "Quathoomar, dieu du voyage": {
"name": "Quathoomar, Lord of Travel, The Pathfinder", "name": "Quathoomar, Lord of Travel, The Pathfinder",
"description": "" "description": "Originally a god of the Blue Giants, but now worshipped by many other travellers of all kinds. He appears as a huge Blue Giant that can stride over hills and mountains as if they are anthills. He carries a great wooden maul and wears a behemathon-hide tunic."
}, },
"SaTel, dieu des étoiles": { "SaTel, dieu des étoiles": {
"name": "SaTel, The God of Stars", "name": "SaTel, The God of Stars",
"description": "" "description": "SaTel created the Star-Orb and sent it to earth for Yrzlak to forge the blade that he gave to Hrangarth to defeat the Sorcerer-Kings. He is usually depicted as a bright light or star, although he is sometimes pictured as a slim naked man of indeterminate age with deep black eyes."
}, },
"Shazzadion, dieu des mers": { "Shazzadion, dieu des mers": {
"name": "Shazzadion the Sea Lord", "name": "Shazzadion the Sea Lord",
"description": "" "description": "God of the Oceans, Ruler of the Waves. He is depicted as an older ebony-skinned man with a grey beard and bald head. He wears a shimmering green tunic and carries a great spear. Shazzadion has a great sea-serpent called Fulca that he rides at great speed over the oceans, or that sometimes pulls his war galley. Shazzadions followers are sailors and pirates everywhere, and anyone who has to travel across the sea."
}, },
"Tharungozoth le massacreur, seigneur des souffrances": { "Tharungozoth le massacreur, seigneur des souffrances": {
"name": "Tharungozoth the Slaughterer, the Pain Lord", "name": "Tharungozoth the Slaughterer, the Pain Lord",
"description": "" "description": "Tharungozoth is worshipped by the Black Druids and some torturers, gladiators, and slavers. He appears as a solid, broad-shouldered man with a black hood over his head and a black studded leather jerkin, wielding either a whip or an executioners axe sometimes both. He often has long iron nails sticking through his neck, shoulders, knees, elbows, and ankles."
}, },
"Yrzlak le dieu forgeron": { "Yrzlak le dieu forgeron": {
"name": "Yrzlak the Godsmith", "name": "Yrzlak the Godsmith",
"description": "" "description": "The god that forged the Orb-Blade. His forge is within the volcano of Mount Kolvis. He is usually depicted as a great bearded man wearing a leather apron and with hammer, tongs and anvil the tools of a smith. He is sometimes shown riding an enormous black bouphon. Yrzlak is worshipped by blacksmiths, armourers, soldiers, and even some alchemists (who, as men of science, generally do not worship the gods)."
}, },
"Zaggath, seigneur du Feu": { "Zaggath, seigneur du Feu": {
"name": "Zaggath, Lord of Fire", "name": "Zaggath, Lord of Fire",
"description": "" "description": "He is worshipped by the Red Druids, and is said to reside on the Fire Coast where his devoted agent is Methyn Sarr, the Witch Queen. He appears as a man-form, immolated in bright red and orange fire."
}, },
"Zalkyr le Lumineux, dieu du soleil": { "Zalkyr le Lumineux, dieu du soleil": {
"name": "Zalkyr the Bright, Lord of Light, Sun God", "name": "Zalkyr the Bright, Lord of Light, Sun God",
"description": "" "description": "Worshipped by all as he represents light, warmth and growth. He travels across the sky on either a chariot or a sky-boat (the latter being more common in Satarlan temples). In the Crimson Edda, Zalkyr is described as an aging king with golden flesh, silver bones, and hair of sunlight."
}, },
"Zylidith, seigneur du sang, dieu du sacrifice": { "Zylidith, seigneur du sang, dieu du sacrifice": {
"name": "Zylidith, Lord of Blood, God of the Sacrifice", "name": "Zylidith, Lord of Blood, God of the Sacrifice",
"description": "" "description": "Worshipped by the Crimson Druids, Zylidith always requires his Druids to perform a human sacrifice to receive their spell benefits. He can appear as a gaunt naked man weeping blood, and sometimes as a monstrous and shapeless scarlet mass."
}, },
"Armes Améliorées": { "Armes Améliorées": {
"name": "Enhanced Weaponry", "name": "Enhanced Weaponry",

View File

@@ -18,7 +18,7 @@
"description": "<h1>Medium Armour</h1><p>Combining some of the mobility of the lighter armour and better protection of heavy armour, this is the armour worn by the typical adventurer expecting to get into combat situations on a regular basis. Medium armour could represent fairly extensive coverage of leather armour, with stiffened leather in some of the more vital areas, or partial coverage of mail with leather in other areas. It could even represent a steel breastplate and minimal/no other armour.</p><p>Someone in medium armour can often remove portions of their armour to reduce it to light armour, as necessary.</p><h2>Medium armour effects</h2><ul><li>Roll a d6-2 (0-4 points of damage reduction).</li><li>If you prefer static numbers, medium armour absorbs 2 points of damage.</li><li>Wearing medium armour restricts magicians and imposes an extra 2 Arcane Points cost on a spell casting.</li></ul>" "description": "<h1>Medium Armour</h1><p>Combining some of the mobility of the lighter armour and better protection of heavy armour, this is the armour worn by the typical adventurer expecting to get into combat situations on a regular basis. Medium armour could represent fairly extensive coverage of leather armour, with stiffened leather in some of the more vital areas, or partial coverage of mail with leather in other areas. It could even represent a steel breastplate and minimal/no other armour.</p><p>Someone in medium armour can often remove portions of their armour to reduce it to light armour, as necessary.</p><h2>Medium armour effects</h2><ul><li>Roll a d6-2 (0-4 points of damage reduction).</li><li>If you prefer static numbers, medium armour absorbs 2 points of damage.</li><li>Wearing medium armour restricts magicians and imposes an extra 2 Arcane Points cost on a spell casting.</li></ul>"
}, },
"Casque": { "Casque": {
"name": "Casque", "name": "Helmet",
"description": "<h1>Helmet</h1><p>If you are wearing a helmet, this adds +1 to your protection if already wearing armour. Therefore, if in light armour and helmet, youd roll d6-2. In medium armour, roll d6-1. In heavy armour, roll d6.</p><p>Helmets give you a penalty in social situations and to your initiative (as its harder to notice things whilst wearing a helmet).</p><p>Most Heroes take their helmets off, unless preparing for battle.</p>" "description": "<h1>Helmet</h1><p>If you are wearing a helmet, this adds +1 to your protection if already wearing armour. Therefore, if in light armour and helmet, youd roll d6-2. In medium armour, roll d6-1. In heavy armour, roll d6.</p><p>Helmets give you a penalty in social situations and to your initiative (as its harder to notice things whilst wearing a helmet).</p><p>Most Heroes take their helmets off, unless preparing for battle.</p>"
}, },
"Grand bouclier": { "Grand bouclier": {

View File

@@ -14,7 +14,7 @@
"description": "" "description": ""
}, },
"Esprit -1": { "Esprit -1": {
"name": "Miond -1", "name": "Mind -1",
"description": "" "description": ""
}, },
"Vigueur -1": { "Vigueur -1": {

View File

@@ -175,7 +175,7 @@
}, },
"Traqué": { "Traqué": {
"name": "Hunted", "name": "Hunted",
"description": "<h1>Hunted</h1><p>Your character is ridiculously tight- lipped. Its a rare day on which he uses a sentence of more than three words, and its virtually unheard of for him to initiate conversation. His extreme reluctance to speak unfortunately means he never volunteers information without being asked. Take a penalty die in social situations.</p>" "description": "<h1>Hunted</h1><p>Perhaps you are wanted by the authorities, or have offended some powerful noble or pirate king. Regardless of whom, you constantly have to evade agents intent on capturing or even killing you. Roll a d6 whenever you enter a new city. On a 1, agents of your enemy (or your enemy himself, if you choose) will spot you and make your life unpleasant.</p>"
} }
} }
} }

View File

@@ -74,8 +74,8 @@
"description": "<h1>Shamballah</h1><p>Shamballahns are generally a dark-skinned and dark-haired people with purple, mauve, scarlet, and indigo eye colouring. They are very perceptive and make good hunters.</p>" "description": "<h1>Shamballah</h1><p>Shamballahns are generally a dark-skinned and dark-haired people with purple, mauve, scarlet, and indigo eye colouring. They are very perceptive and make good hunters.</p>"
}, },
"Terres Désolées": { "Terres Désolées": {
"name": "Terres Désolées", "name": "Empty Lands",
"description": "<h1>Terres Désolées</h1><p>Personne ne sait grand-chose au sujet des Terres Désolées, et moins encore sur les contrées qui sétendraient au-delà. Des gens vivent assurément dans cette région, sil faut en croire les récits de chasseurs et dexplorateurs qui ont relevé des traces de présence. Mais à ce jour, personne na découvert le moindre village ou hameau, et on ignore tout de lapparence de leurs habitants.</p><p>Si votre héros est originaire des Terres Désolées, il vous faudra travailler avec le MJ pour déterminer à quoi ressemble votre personnage. Mais attendezvous à ce quil soit toujours considéré comme un étranger, où quil se rende en Lémurie.</p>" "description": "<h1>Empty Lands</h1><p>No one knows what exists in and beyond the Empty Lands. People do live there, because hunters and explorers have seen evidence of them. However, no towns or even villages have been discovered, and no one has any idea of what the people are like. If you come from the Empty Lands, you will need to work with the GM to determine what your character is like. He or she will always be treated as an outsider.</p>"
}, },
"Tyrus": { "Tyrus": {
"name": "Tyrus", "name": "Tyrus",

View File

@@ -42,8 +42,8 @@
"description": "<h1>Staff Sling</h1><p>The staff sling is a two-handed version of the sling, with a longer range.</p>" "description": "<h1>Staff Sling</h1><p>The staff sling is a two-handed version of the sling, with a longer range.</p>"
}, },
"Cimeterre": { "Cimeterre": {
"name": "Cimeterre", "name": "Scimitar",
"description": "<h1>Épée</h1><p>larme favorite des héros. Elle se décline en différents modèles utilisés un peu partout en Lémurie, comme les sabres dabordage, les tulwars, les cimeterres ou les épées longues. Inscrivez le nom de larme que vous voulez sur la fiche de votre personnage en fonction de limage que vous vous faites de ce dernier. Mais au final, une épée reste une longue lame utilisée pour tuer lennemi.</p>" "description": "<h1>Scimitar (Sword)</h1><p>This weapon is a favourite amongst Heroes. This is the catch-all description for all manner of long-bladed, one-handed weapons used all over Lemuria, such as cutlasses, tulwars, scimitars, rapiers, broadswords and longswords. Call it what you want on your character sheet, because that will add flavour to your character, but at the end of the day a sword is a long blade used for killing.</p>"
}, },
"Dague": { "Dague": {
"name": "Dagger", "name": "Dagger",
@@ -123,19 +123,19 @@
}, },
"Masse darmes": { "Masse darmes": {
"name": "Mace", "name": "Mace",
"description": "<h1>Mace</h1><p>cA mace is similar to a club, but with a metal head, often with spikes or flanges. One-handed maces can be thrown at increments of 5 as they arent very effective used this way.</p>" "description": "<h1>Mace</h1><p>A mace is similar to a club, but with a metal head, often with spikes or flanges. One-handed maces can be thrown at increments of 5 as they arent very effective used this way.</p>"
}, },
"Masse darmes (Lancer)": { "Masse darmes (Lancer)": {
"name": "Masse darmes (Lancer)", "name": "Mace (Thrown)",
"description": "<h1>Masse darmes</h1><p>cette arme a la même forme quun gourdin, mais possède une tête en métal, souvent agrémentée de pointes ou dailettes pour plus defficacité. Les masses darmes à une main peuvent se lancer, mais à courte distance, car elles ne sont pas vraiment prévues pour cet usage.</p>" "description": "<h1>Mace</h1><p>A mace is similar to a club, but with a metal head, often with spikes or flanges. One-handed maces can be thrown at increments of 5 as they arent very effective used this way.</p>"
}, },
"Massue": { "Massue": {
"name": "Massue", "name": "Club",
"description": "<h1>Massue</h1><p>la version lourde du gourdin. Une massue consiste en un solide manche en bois dont lextrémité, plus volumineuse, sert à fracasser le crâne de ses adversaires, doù son autre nom de casse-tête.</p>" "description": "<h1>Club</h1><p>This is a larger version of the cudgel. It is a stout length of wood, used in one hand to bludgeon and batter your opponent. Clubs usually have a wider or knobbed head and can be called war-clubs, knobkerries, or shillelaghs.</p>"
}, },
"Massue (Lancer)": { "Massue (Lancer)": {
"name": "Massue (Lancer)", "name": "Club (Thrown)",
"description": "<h1>Massue</h1><p>la version lourde du gourdin. Une massue consiste en un solide manche en bois dont lextrémité, plus volumineuse, sert à fracasser le crâne de ses adversaires, doù son autre nom de casse-tête.</p>" "description": "<h1>Club</h1><p>This is a larger version of the cudgel. It is a stout length of wood, used in one hand to bludgeon and batter your opponent. Clubs usually have a wider or knobbed head and can be called war-clubs, knobkerries, or shillelaghs.</p>"
}, },
"Morgenstern": { "Morgenstern": {
"name": "Morning Star", "name": "Morning Star",

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
[Dolphin]
Timestamp=2024,11,2,20,30,2.2800000000000002
Version=4
ViewMode=1
VisibleRoles=Details_text,Details_size,Details_modificationtime,Details_creationtime,CustomizedDetails

View File

@@ -290,6 +290,7 @@
"BOL.itemProperty.difficulty": "Schwierigkeit", "BOL.itemProperty.difficulty": "Schwierigkeit",
"BOL.itemProperty.natural": "Natürliche Waffe", "BOL.itemProperty.natural": "Natürliche Waffe",
"BOL.itemProperty.onlymodifier": "Nur Modifikator (d.h. Angriffe von Kreaturen)", "BOL.itemProperty.onlymodifier": "Nur Modifikator (d.h. Angriffe von Kreaturen)",
"BOL.itemProperty.attackMalusDice": "Angriffsmalus (Würfel)",
"BOL.itemStat.quantity": "Anzahl", "BOL.itemStat.quantity": "Anzahl",
"BOL.itemStat.weight": "Gewicht", "BOL.itemStat.weight": "Gewicht",
@@ -468,6 +469,7 @@
"BOL.chat.welcome4": "Im Discord findet ihr Support für die FoundryVTT-Implementierung dieses Systems: https://discord.gg/pPSDNJk", "BOL.chat.welcome4": "Im Discord findet ihr Support für die FoundryVTT-Implementierung dieses Systems: https://discord.gg/pPSDNJk",
"BOL.chat.welcome5": "Auf ein gutes Spiel in Lemuria!", "BOL.chat.welcome5": "Auf ein gutes Spiel in Lemuria!",
"BOL.chat.welcome6": "", "BOL.chat.welcome6": "",
"BOL.chat.bolRulebookMessage": "Don't miss the full Rulebook module (including Sagas) available at : https://www.ludospherik-editions.com/en_gb/ !",
"BOL.settings.rollArmor": "Roll for armor", "BOL.settings.rollArmor": "Roll for armor",
"BOL.settings.rollArmorTooltip": "Roll for armor value, fixed value if unchecked", "BOL.settings.rollArmorTooltip": "Roll for armor value, fixed value if unchecked",

View File

@@ -365,6 +365,7 @@
"BOL.itemProperty.difficulty": "Difficulty", "BOL.itemProperty.difficulty": "Difficulty",
"BOL.itemProperty.natural": "Natural weapon", "BOL.itemProperty.natural": "Natural weapon",
"BOL.itemProperty.onlymodifier": "Modifier only (ie creatures attacks)", "BOL.itemProperty.onlymodifier": "Modifier only (ie creatures attacks)",
"BOL.itemProperty.attackMalusDice": "Attack Malus (Dice)",
"BOL.itemStat.quantity": "Quantity", "BOL.itemStat.quantity": "Quantity",
"BOL.itemStat.weight": "Weight", "BOL.itemStat.weight": "Weight",
@@ -543,17 +544,24 @@
"BOL.chat.criticalbuttonjournal": "Legendary/Heroic Success", "BOL.chat.criticalbuttonjournal": "Legendary/Heroic Success",
"BOL.chat.nodamage": "Do not apply damages", "BOL.chat.nodamage": "Do not apply damages",
"BOL.chat.armorRoll": "Armor roll", "BOL.chat.armorRoll": "Armor roll",
"BOL.chat.bolRulebookMessage": "Don't miss the full Rulebook module (including Sagas) available at : https://www.ludospherik-editions.com/en_gb/ !",
"BOL.dialog.soeasy": "So easy (+4)", "BOL.dialog.soeasy": "So easy (+4)",
"BOL.dialog.soeasy3": "So easy (+3)",
"BOL.dialog.veryeasy": "Very easy (+2)", "BOL.dialog.veryeasy": "Very easy (+2)",
"BOL.dialog.easy": "Easy (+1)", "BOL.dialog.easy": "Easy (+1)",
"BOL.dialog.moderate": "Moderate (0)", "BOL.dialog.moderate": "Moderate (0)",
"BOL.dialog.hard": "Hard (-1)", "BOL.dialog.hard": "Hard (-1)",
"BOL.dialog.tough": "Tough (-2)", "BOL.dialog.tough": "Tough (-2)",
"BOL.dialog.tough3": "Tough (-3)",
"BOL.dialog.demanding": "Demanding (-4)", "BOL.dialog.demanding": "Demanding (-4)",
"BOL.dialog.demanding5": "Demanding (-5)",
"BOL.dialog.formidable": "Formidable (-6)", "BOL.dialog.formidable": "Formidable (-6)",
"BOL.dialog.formidable7": "Formidable (-7)",
"BOL.dialog.heroic": "Heroic (-8)", "BOL.dialog.heroic": "Heroic (-8)",
"BOL.dialog.heroic9": "Heroic (-9)",
"BOL.dialog.mythic": "Mythic (-10)", "BOL.dialog.mythic": "Mythic (-10)",
"BOL.dialog.mythic11": "Mythic (-11)",
"BOL.dialog.divine": "Divine (-12)", "BOL.dialog.divine": "Divine (-12)",
"BOL.dialog.pointblank": "Point blank (+1)", "BOL.dialog.pointblank": "Point blank (+1)",
@@ -578,11 +586,11 @@
"BOL.ui.bionotes": "Notes", "BOL.ui.bionotes": "Notes",
"BOL.chat.welcome1": "Welcome to Barbarians of Lemuria (Ludospherik version)", "BOL.chat.welcome1": "Welcome to Barbarians of Lemuria (Ludospherik version)",
"BOL.chat.welcome2": "Books are necessary to play, and ca be found here : http://www.ludospherik.fr/content/14-barbarians-of-lemuria", "BOL.chat.welcome2": "Books are necessary to play, and <a href='http://www.ludospherik.fr/content/14-barbarians-of-lemuria'>can be found here.</a> ",
"BOL.chat.welcome3": "The integrated maps are authorized by Guillaume Tavernier and Ludospherik. Thanks to them !.", "BOL.chat.welcome3": "The integrated maps are authorized by Emmanuel Roudier and Ludospherik. Thanks to them !.",
"BOL.chat.welcome4": "All support for this system is available on this Discord server : https://discord.gg/pPSDNJk", "BOL.chat.welcome4": "All support for this system is available on this Discord server : https://discord.gg/pPSDNJk",
"BOL.chat.welcome5": "Good game in Lemuria !", "BOL.chat.welcome5": "<strong>In order to see compendiums in English, you must install and enable the Babele module.</strong>",
"BOL.chat.welcome6": "", "BOL.chat.welcome6": "Good game in Lemuria !",
"BOL.settings.rollArmor": "Roll for armor", "BOL.settings.rollArmor": "Roll for armor",
"BOL.settings.rollArmorTooltip": "Roll for armor value, fixed value if unchecked", "BOL.settings.rollArmorTooltip": "Roll for armor value, fixed value if unchecked",
@@ -601,5 +609,10 @@
"BOL.settings.defaultLogoActorSheetPath" : "Path for Actor sheet logo", "BOL.settings.defaultLogoActorSheetPath" : "Path for Actor sheet logo",
"BOL.settings.defaultLogoPathActorSheetTooltip": "Path of the Actor sheet logo (346 x 200, default : /systems/bol/ui/logo.webp)", "BOL.settings.defaultLogoPathActorSheetTooltip": "Path of the Actor sheet logo (346 x 200, default : /systems/bol/ui/logo.webp)",
"BOL.settings.defaultLogoTopLeftPath" : "Path for main top left logo", "BOL.settings.defaultLogoTopLeftPath" : "Path for main top left logo",
"BOL.settings.defaultLogoTopLeftPathTooltip": "Path of the logo in the top left window (718 x 416, default : /systems/bol/ui/logo2.webp)" "BOL.settings.defaultLogoTopLeftPathTooltip": "Path of the logo in the top left window (718 x 416, default : /systems/bol/ui/logo2.webp)",
"BOL.ui.charSummaryTitle": "Character Summary",
"BOL.ui.colGroupAttributes": "Attributes",
"BOL.ui.colGroupAptitudes": "Aptitudes",
"BOL.ui.colGroupResources": "Resources"
} }

View File

@@ -359,6 +359,7 @@
"BOL.itemProperty.difficulty": "Dificultad", "BOL.itemProperty.difficulty": "Dificultad",
"BOL.itemProperty.natural": "Arma natural", "BOL.itemProperty.natural": "Arma natural",
"BOL.itemProperty.onlymodifier": "Sólo modificador (ej criatura)", "BOL.itemProperty.onlymodifier": "Sólo modificador (ej criatura)",
"BOL.itemProperty.attackMalusDice": "Dado Desventaja Ataque",
"BOL.itemStat.quantity": "Cantidad", "BOL.itemStat.quantity": "Cantidad",
"BOL.itemStat.weight": "Peso", "BOL.itemStat.weight": "Peso",
@@ -534,6 +535,7 @@
"BOL.chat.criticalinfo": "¡Esto es un éxito Asombroso o Legendario! Escoge tus opciones y efectos", "BOL.chat.criticalinfo": "¡Esto es un éxito Asombroso o Legendario! Escoge tus opciones y efectos",
"BOL.chat.criticalbuttonjournal": "Éxito Asombroso/Legendario", "BOL.chat.criticalbuttonjournal": "Éxito Asombroso/Legendario",
"BOL.chat.armorRoll": "Tirada de Armadura", "BOL.chat.armorRoll": "Tirada de Armadura",
"BOL.chat.bolRulebookMessage": "Don't miss the full Rulebook module (including Sagas) available at : https://www.ludospherik-editions.com/en_gb/ !",
"BOL.dialog.soeasy": "Demasiado fácil (+4)", "BOL.dialog.soeasy": "Demasiado fácil (+4)",
"BOL.dialog.veryeasy": "Muy fácil (+2)", "BOL.dialog.veryeasy": "Muy fácil (+2)",

View File

@@ -394,6 +394,7 @@
"BOL.itemProperty.isboarding": "Abordage", "BOL.itemProperty.isboarding": "Abordage",
"BOL.itemProperty.isspur": "Eperonnage", "BOL.itemProperty.isspur": "Eperonnage",
"BOL.itemProperty.isbreakrow": "Briser les rames", "BOL.itemProperty.isbreakrow": "Briser les rames",
"BOL.itemProperty.attackMalusDice": "Malus d'attaque (Dés)",
"BOL.itemStat.quantity": "Quantité", "BOL.itemStat.quantity": "Quantité",
"BOL.itemStat.weight": "Poids", "BOL.itemStat.weight": "Poids",
@@ -569,21 +570,29 @@
"BOL.chat.criticalinfo": "C'est un succès Héroïque ! Choisissez vos options et effets !", "BOL.chat.criticalinfo": "C'est un succès Héroïque ! Choisissez vos options et effets !",
"BOL.chat.criticallegendaryinfo": "C'est un succès Légendaire ! Choisissez vos options et effets !", "BOL.chat.criticallegendaryinfo": "C'est un succès Légendaire ! Choisissez vos options et effets !",
"BOL.chat.criticalbuttonjournal": "Succès Héroïque/Légendaire", "BOL.chat.criticalbuttonjournal": "Succès Héroïque/Légendaire",
"BOL.chat.bolRulebookMessage": "N'oubliez pas le module complet du Livre de Règle et des Sagas disponible ici : https://www.ludospherik-editions.com !",
"BOL.chat.losshp": "{name} a perdu {lossHP} points de Vitalité. Si il se repose quelques minutes, il peut récupérer {recupHP} points de Vitalité.", "BOL.chat.losshp": "{name} a perdu {lossHP} points de Vitalité. Si il se repose quelques minutes, il peut récupérer {recupHP} points de Vitalité.",
"BOL.chat.applyrecup": "Récupérer pendant quelques minutes (+{recupHP} Vitalité)", "BOL.chat.applyrecup": "Récupérer pendant quelques minutes (+{recupHP} Vitalité)",
"BOL.chat.inforecup": "{name} vient de récupérer {recupHP} points de Vitalité après quelques minutes de repos.", "BOL.chat.inforecup": "{name} vient de récupérer {recupHP} points de Vitalité après quelques minutes de repos.",
"BOL.chat.defenseReduceDamage2": "Vous pouvez également dépenser 1 Point d'Héroisme/Vilainie pour regagner [[/r 2d6KH(1D6B)]] points de vitalité, cela vous coutera votre prochaine action.",
"BOL.chat.armorRoll": "Jet d'armure ",
"BOL.dialog.soeasy": "Inmanquable (+4)", "BOL.dialog.soeasy": "Inmanquable (+4)",
"BOL.dialog.soeasy3": "Inmanquable (+3)",
"BOL.dialog.veryeasy": "Trés Facile (+2)", "BOL.dialog.veryeasy": "Trés Facile (+2)",
"BOL.dialog.easy": "Facile (+1)", "BOL.dialog.easy": "Facile (+1)",
"BOL.dialog.moderate": "Moyenne (0)", "BOL.dialog.moderate": "Moyenne (0)",
"BOL.dialog.hard": "Ardue (-1)", "BOL.dialog.hard": "Ardue (-1)",
"BOL.dialog.tough": "Difficile (-2)", "BOL.dialog.tough": "Difficile (-2)",
"BOL.dialog.tough3": "Difficile (-3)",
"BOL.dialog.demanding": "Très Difficile (-4)", "BOL.dialog.demanding": "Très Difficile (-4)",
"BOL.dialog.demanding5": "Très Difficile (-5)",
"BOL.dialog.formidable": "Impossible (-6)", "BOL.dialog.formidable": "Impossible (-6)",
"BOL.dialog.formidable7": "Impossible (-7)",
"BOL.dialog.heroic": "Héroïque (-8)", "BOL.dialog.heroic": "Héroïque (-8)",
"BOL.dialog.heroic9": "Héroïque (-9)",
"BOL.dialog.mythic": "Mythique (-10)", "BOL.dialog.mythic": "Mythique (-10)",
"BOL.dialog.mythic11": "Mythique (-11)",
"BOL.dialog.divine": "Divine (-12)", "BOL.dialog.divine": "Divine (-12)",
"BOL.dialog.pointblank": "Bout portant (+1)", "BOL.dialog.pointblank": "Bout portant (+1)",
@@ -611,16 +620,15 @@
"BOL.chat.welcome1": "Bienvenue dans Barbarians of Lemuria (Ludospherik version)", "BOL.chat.welcome1": "Bienvenue dans Barbarians of Lemuria (Ludospherik version)",
"BOL.chat.welcome2": "Les livres nécessaires pour jouer sont disponibles sur le site de <a href='http://www.ludospherik.fr/content/14-barbarians-of-lemuria'>l'éditeur Ludospherik.</a>", "BOL.chat.welcome2": "Les livres nécessaires pour jouer sont disponibles sur le site de <a href='http://www.ludospherik.fr/content/14-barbarians-of-lemuria'>l'éditeur Ludospherik.</a>",
"BOL.chat.welcome3": "Les cartes intégrées au système le sont grace à l'aimable autorisation de leur auteur Guillaume Tavernier et des éditions Ludospherik. Merci à eux !.", "BOL.chat.welcome3": "Les cartes intégrées au système le sont grace à l'aimable autorisation d'Emmanuel Roudier et des éditions Ludospherik. Merci à eux !.",
"BOL.chat.welcome4": "Tout le support et le suivi de ce système est disponible via le <a href='https://discord.gg/pPSDNJk'>Discord Foundry FR</a>.", "BOL.chat.welcome4": "Tout le support et le suivi de ce système est disponible via le <a href='https://discord.gg/pPSDNJk'>Discord Foundry FR</a>.",
"BOL.chat.welcome5": "Consulter l'aide en ligne pour plus d'informations : @UUID[Compendium.bol.aides-de-jeu.JournalEntry.8ihDiCxC47fcdKVA]{Aide du Jeu}.", "BOL.chat.welcome5": "Consulter l'aide en ligne pour plus d'informations : @UUID[Compendium.bol.aides-de-jeu.JournalEntry.8ihDiCxC47fcdKVA]{Aide du Jeu}.<br>Si vous souhaitez jouer en anglais, n'oubliez pas d'activer le module Babele.",
"BOL.chat.welcome6": "Bon jeu en Lemurie !", "BOL.chat.welcome6": "Bon jeu en Lemurie !",
"BOL.chat.nodamage": "Ne pas appliquer les dommages", "BOL.chat.nodamage": "Ne pas appliquer les dommages",
"BOL.chat.pcwarning": "Attention ! Aucun personnage n'est relié au joueur !", "BOL.chat.pcwarning": "Attention ! Aucun personnage n'est relié au joueur !",
"BOL.chat.pcwarningmsg": "<b>ATTENTION</b> Le joueur n'est relié à aucun personnage !", "BOL.chat.pcwarningmsg": "<b>ATTENTION</b> Le joueur n'est relié à aucun personnage !",
"BOL.chat.pcnotlinked": "Le token du personnage joueur n'est pas relié à l'acteur", "BOL.chat.pcnotlinked": "Le token du personnage joueur n'est pas relié à l'acteur",
"BOL.chat.pcnotlinkedmsg": "<b>ATTENTION</b> Le token du personnage joueur n'est pas relié à l'acteur !", "BOL.chat.pcnotlinkedmsg": "<b>ATTENTION</b> Le token du personnage joueur n'est pas relié à l'acteur !",
"BOL.chat.armorRoll": "Jet d'armure",
"BOL.settings.rollArmor": "Effectuer des jets pour les armures", "BOL.settings.rollArmor": "Effectuer des jets pour les armures",
"BOL.settings.rollArmorTooltip": "Effectue un jet de dés pour les armures (valeur fixe si désactivé)", "BOL.settings.rollArmorTooltip": "Effectue un jet de dés pour les armures (valeur fixe si désactivé)",
@@ -642,5 +650,10 @@
"BOL.settings.defaultLogoTopLeftPathTooltip": "Vous pouvez changer le logo BoL en haut à gauche de chaque écran (idéalement 718 x 416, défaut : /systems/bol/ui/logo2.webp)", "BOL.settings.defaultLogoTopLeftPathTooltip": "Vous pouvez changer le logo BoL en haut à gauche de chaque écran (idéalement 718 x 416, défaut : /systems/bol/ui/logo2.webp)",
"EFFECT.StatusProne": "A terre", "EFFECT.StatusProne": "A terre",
"EFFECT.StatusDead": "Mort" "EFFECT.StatusDead": "Mort",
"BOL.ui.charSummaryTitle": "Résumé des Personnages",
"BOL.ui.colGroupAttributes": "Attributs",
"BOL.ui.colGroupAptitudes": "Aptitudes",
"BOL.ui.colGroupResources": "Ressources"
} }

View File

@@ -5,7 +5,7 @@
import { BoLRoll } from "../controllers/bol-rolls.js"; import { BoLRoll } from "../controllers/bol-rolls.js";
import { BoLUtility } from "../system/bol-utility.js"; import { BoLUtility } from "../system/bol-utility.js";
export class BoLActorSheet extends ActorSheet { export class BoLActorSheet extends foundry.appv1.sheets.ActorSheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
@@ -170,8 +170,8 @@ export class BoLActorSheet extends ActorSheet {
formData.charType = this.actor.getCharType() formData.charType = this.actor.getCharType()
formData.villainy = this.actor.getVillainy() formData.villainy = this.actor.getVillainy()
formData.isUndead = this.actor.isUndead() formData.isUndead = this.actor.isUndead()
formData.biography = await TextEditor.enrichHTML(this.object.system.details?.biography || "", { async: true }) formData.biography = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.details?.biography || "", { async: true })
formData.notes = await TextEditor.enrichHTML(this.object.system.details.notes || "", { async: true }) formData.notes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.details.notes || "", { async: true })
formData.isSorcerer = this.actor.isSorcerer() formData.isSorcerer = this.actor.isSorcerer()
formData.isAlchemist = this.actor.isAlchemist() formData.isAlchemist = this.actor.isAlchemist()
formData.isAstrologer = this.actor.isAstrologer() formData.isAstrologer = this.actor.isAstrologer()

View File

@@ -7,6 +7,8 @@ import { BoLUtility } from "../system/bol-utility.js";
*/ */
export class BoLActor extends Actor { export class BoLActor extends Actor {
static _healthLock = new Set()
static async create(data, options) { static async create(data, options) {
// Case of compendium global import // Case of compendium global import
@@ -22,6 +24,9 @@ export class BoLActor extends Actor {
if (data.type == 'encounter') { if (data.type == 'encounter') {
data.system = { resources: { hero : { max : 0, value: 0 } } } data.system = { resources: { hero : { max : 0, value: 0 } } }
} }
if (data.type == 'character') {
data.system = { resources: { hp : { max : 10, value: 10 } } }
}
if (data.type == 'horde') { if (data.type == 'horde') {
let weapon = { let weapon = {
@@ -161,12 +166,16 @@ export class BoLActor extends Actor {
let newVitality = 10 + this.system.attributes.vigor.value + this.system.resources.hp.bonus let newVitality = 10 + this.system.attributes.vigor.value + this.system.resources.hp.bonus
if (this.system.resources.hp.max != newVitality) { if (this.system.resources.hp.max != newVitality) {
let actor = this let actor = this
setTimeout(function () { actor.update({ 'system.resources.hp.max': newVitality }) }, 800) let newHP = foundry.utils.duplicate(this.system.resources.hp)
newHP.max = newVitality
setTimeout(function () { actor.update({ 'system.resources.hp': newHP }) }, 800)
} }
let newPower = 10 + this.system.attributes.mind.value + this.system.resources.power.bonus let maxPower = 10 + this.system.attributes.mind.value + this.system.resources.power.bonus
if (this.system.resources.power.max != newPower) { if (this.system.resources.power.max != maxPower) {
let actor = this let actor = this
setTimeout(function () { actor.update({ 'system.resources.power.max': newPower }) }, 800) let newPower = foundry.utils.duplicate(this.system.resources.power)
newPower.max = maxPower
setTimeout(function () { actor.update({ 'system.resources.power': newPower }) }, 800)
} }
} }
} }
@@ -350,7 +359,7 @@ export class BoLActor extends Actor {
ChatMessage.create({ ChatMessage.create({
alias: this.name, alias: this.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name), whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
content: await renderTemplate('systems/bol/templates/chat/chat-activate-fight-option.hbs', { name: this.name, img: fightOption.img, foName: fightOption.name, state: state }) content: await foundry.applications.handlebars.foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/chat-activate-fight-option.hbs', { name: this.name, img: fightOption.img, foName: fightOption.name, state: state })
}) })
} }
@@ -845,36 +854,42 @@ export class BoLActor extends Actor {
/*-------------------------------------------- */ /*-------------------------------------------- */
async manageHealthState() { async manageHealthState() {
let hpID = "lastHP" + this.id if (BoLActor._healthLock.has(this.id)) return
let lastHP = await this.getFlag("world", hpID) BoLActor._healthLock.add(this.id)
if (lastHP != this.system.resources.hp.value && game.user.isGM) { // Only GM sends this try {
await this.setFlag("world", hpID, this.system.resources.hp.value) let hpID = "lastHP" + this.id
let prone = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusProne")) let lastHP = await this.getFlag("world", hpID)
let dead = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusDead")) if (lastHP != this.system.resources.hp.value && game.user.isGM) { // Only GM sends this
if (this.system.resources.hp.value <= 0) { await this.setFlag("world", hpID, this.system.resources.hp.value)
if (!prone) { let prone = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusProne"))
await this.createEmbeddedDocuments("ActiveEffect", [ let dead = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusDead"))
{ name: game.i18n.localize('EFFECT.StatusProne'), icon: 'icons/svg/falling.svg', statuses: 'prone' } if (this.system.resources.hp.value <= 0) {
]) if (!prone) {
} await this.createEmbeddedDocuments("ActiveEffect", [
if (this.system.resources.hp.value < -5 && !dead) { { name: game.i18n.localize('EFFECT.StatusProne'), icon: 'icons/svg/falling.svg', statuses: 'prone' }
await this.createEmbeddedDocuments("ActiveEffect", [ ])
{ name: game.i18n.localize('EFFECT.StatusDead'), icon: 'icons/svg/skull.svg', statuses: 'dead' } }
]) if (this.system.resources.hp.value < -5 && !dead) {
} await this.createEmbeddedDocuments("ActiveEffect", [
ChatMessage.create({ { name: game.i18n.localize('EFFECT.StatusDead'), icon: 'icons/svg/skull.svg', statuses: 'dead' }
alias: this.name, ])
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name), }
content: await renderTemplate('systems/bol/templates/chat/chat-vitality-zero.hbs', { name: this.name, img: this.img, hp: this.system.resources.hp.value, isHeroAdversary: this.isHeroAdversary() }) ChatMessage.create({
}) alias: this.name,
} else { whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
if (prone) { content: await foundry.applications.handlebars.foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/chat-vitality-zero.hbs', { name: this.name, img: this.img, hp: this.system.resources.hp.value, isHeroAdversary: this.isHeroAdversary() })
await this.deleteEmbeddedDocuments("ActiveEffect", [prone.id]) })
} } else {
if (dead) { if (prone) {
await this.deleteEmbeddedDocuments("ActiveEffect", [dead.id]) await this.deleteEmbeddedDocuments("ActiveEffect", [prone.id])
}
if (dead) {
await this.deleteEmbeddedDocuments("ActiveEffect", [dead.id])
}
} }
} }
} finally {
BoLActor._healthLock.delete(this.id)
} }
} }
@@ -897,7 +912,7 @@ export class BoLActor extends Actor {
let msg = await ChatMessage.create({ let msg = await ChatMessage.create({
alias: this.name, alias: this.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name), whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
content: await renderTemplate('systems/bol/templates/chat/chat-recup-information.hbs', { content: await foundry.applications.handlebars.foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/chat-recup-information.hbs', {
name: this.name, name: this.name,
img: this.img, img: this.img,
actorId: this.id, actorId: this.id,

View File

@@ -5,7 +5,7 @@
import { BoLRoll } from "../controllers/bol-rolls.js"; import { BoLRoll } from "../controllers/bol-rolls.js";
import { BoLUtility } from "../system/bol-utility.js"; import { BoLUtility } from "../system/bol-utility.js";
export class BoLHordeSheet extends ActorSheet { export class BoLHordeSheet extends foundry.appv1.sheets.ActorSheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
@@ -116,7 +116,7 @@ export class BoLHordeSheet extends ActorSheet {
formData.options = this.options formData.options = this.options
formData.owner = this.document.isOwner formData.owner = this.document.isOwner
formData.editScore = this.options.editScore formData.editScore = this.options.editScore
formData.description = await TextEditor.enrichHTML(this.actor.system.description, {async: true}) formData.description = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.actor.system.description, {async: true})
formData.isGM = game.user.isGM formData.isGM = game.user.isGM

View File

@@ -5,7 +5,7 @@
import { BoLRoll } from "../controllers/bol-rolls.js"; import { BoLRoll } from "../controllers/bol-rolls.js";
import { BoLUtility } from "../system/bol-utility.js"; import { BoLUtility } from "../system/bol-utility.js";
export class BoLVehicleSheet extends ActorSheet { export class BoLVehicleSheet extends foundry.appv1.sheets.ActorSheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
@@ -127,7 +127,7 @@ export class BoLVehicleSheet extends ActorSheet {
formData.options = this.options formData.options = this.options
formData.owner = this.document.isOwner formData.owner = this.document.isOwner
formData.editScore = this.options.editScore formData.editScore = this.options.editScore
formData.description = await TextEditor.enrichHTML(this.actor.system.description, {async: true}) formData.description = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.actor.system.description, {async: true})
formData.isGM = game.user.isGM formData.isGM = game.user.isGM

View File

@@ -0,0 +1,7 @@
export { default as BoLBaseItemSheet } from "./base-item-sheet.mjs"
export { default as BoLItemSheet } from "./item-sheet.mjs"
export { default as BoLFeatureSheet } from "./feature-sheet.mjs"
export { default as BoLBaseActorSheet } from "./base-actor-sheet.mjs"
export { default as BoLActorSheet } from "./actor-sheet.mjs"
export { default as BoLHordeSheet } from "./horde-sheet.mjs"
export { default as BoLVehicleSheet } from "./vehicle-sheet.mjs"

View File

@@ -0,0 +1,144 @@
import BoLBaseActorSheet from "./base-actor-sheet.mjs"
import { BoLUtility } from "../../system/bol-utility.js"
/**
* Actor Sheet for BoL characters and encounters using AppV2
*/
export default class BoLActorSheet extends BoLBaseActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes],
actions: {
...super.DEFAULT_OPTIONS.actions,
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/bol/templates/actor/actor-sheet.hbs",
},
}
/** @override */
tabGroups = { primary: "stats" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
context.data = actor.toObject()
context.details = actor.details
context.attributes = actor.attributes
context.aptitudes = actor.aptitudes
context.resources = actor.getResourcesFromType()
context.xp = actor.system.xp
context.equipment = actor.equipment
context.equipmentCreature = actor.equipmentCreature
context.weapons = actor.weapons
context.protections = actor.protections
context.spells = actor.spells
context.alchemy = actor.alchemy
context.containers = actor.containers
context.treasure = actor.treasure
context.boleffects = actor.boleffects
context.alchemyrecipe = actor.alchemyrecipe
context.horoscopes = actor.horoscopes
context.vehicles = actor.vehicles
context.fightoptions = actor.fightoptions
context.ammos = actor.ammos
context.misc = actor.misc
context.xplog = actor.xplog
context.combat = actor.buildCombat()
context.initiativeRank = actor.getInitiativeRank()
context.features = actor.buildFeatures()
context.options = this.options
context.editScore = this.options.editScore
context.useBougette = (actor.type === "character" && BoLUtility.getUseBougette()) || false
context.bougette = actor.getBougette()
context.charType = actor.getCharType()
context.villainy = actor.getVillainy()
context.isUndead = actor.isUndead()
context.biography = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.details?.biography || "", { async: true }
)
context.notes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.details?.notes || "", { async: true }
)
context.isSorcerer = actor.isSorcerer()
context.isAlchemist = actor.isAlchemist()
context.isAstrologer = actor.isAstrologer()
context.isMysteries = context.isSorcerer || context.isAlchemist || context.isAstrologer
context.isPriest = actor.isPriest()
context.horoscopeGroupList = game.settings.get("bol", "horoscope-group")
console.log("ACTORDATA (AppV2)", context)
return context
}
/** @override */
_activateListeners() {
super._activateListeners()
if (!this.isEditable) return
// Create generic item
this.element.querySelectorAll(".create-item").forEach((el) => {
el.addEventListener("click", () => {
this.actor.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("BOL.ui.newEquipment"), type: "item" }], { renderSheet: true })
})
})
// Create natural weapon
this.element.querySelectorAll(".create-natural-weapon").forEach((el) => {
el.addEventListener("click", () => {
const system = foundry.utils.duplicate(game.bol.config.defaultNaturalWeapon)
this.actor.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("BOL.ui.newNaturalWeapon"), type: "item", system }], { renderSheet: true })
})
})
// Create natural protection
this.element.querySelectorAll(".create-natural-protection").forEach((el) => {
el.addEventListener("click", () => {
const system = foundry.utils.duplicate(game.bol.config.defaultNaturalProtection)
this.actor.createEmbeddedDocuments("Item", [{ name: game.i18n.localize("BOL.ui.newNaturalProtection"), type: "item", system }], { renderSheet: true })
})
})
// Item create via header dataset (type-based creation)
this.element.querySelectorAll(".item-create").forEach((el) => {
el.addEventListener("click", (ev) => {
ev.preventDefault()
const header = ev.currentTarget
const type = header.dataset.type
const data = foundry.utils.duplicate(header.dataset)
const name = `New ${type}`
delete data.type
this.actor.createEmbeddedDocuments("Item", [{ name, type, data }])
})
})
// Add XP Log entry
this.element.querySelectorAll(".xplog-add").forEach((el) => {
el.addEventListener("click", (ev) => {
ev.preventDefault()
this.actor.addXPLog("other", "Nouveau", 0, 0)
})
})
// Add item by category/subtype (equipment tab headers)
this.element.querySelectorAll(".item-add").forEach((el) => {
el.addEventListener("click", (ev) => {
ev.preventDefault()
const { itemType, category, subtype } = ev.currentTarget.dataset
const system = { category, subtype }
this.actor.createEmbeddedDocuments("Item", [{
name: game.i18n.localize("BOL.ui.newEquipment"),
type: itemType || "item",
system,
}], { renderSheet: true })
})
})
}
}

View File

@@ -0,0 +1,307 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { BoLRoll } from "../../controllers/bol-rolls.js"
import { BoLUtility } from "../../system/bol-utility.js"
/**
* Base Actor Sheet for BoL system using AppV2
* @extends {ActorSheetV2}
*/
export default class BoLBaseActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["bol", "sheet", "actor"],
position: {
width: 836,
height: 807,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: "nav[data-group=\"primary\"]",
contentSelector: "section.sheet-body",
initial: "stats",
},
],
dragDrop: [{ dragSelector: ".items-list .item", dropSelector: null }],
actions: {},
}
/** Tab groups state */
tabGroups = { primary: "stats" }
/** @override */
async _prepareContext() {
const actor = this.document
return {
actor,
system: actor.system,
config: game.bol.config,
isGM: game.user.isGM,
isEditable: this.isEditable,
owner: this.document.isOwner,
cssClass: this.options.classes.join(" "),
}
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
this._activateTabs()
this._activateListeners()
this._applyBackgroundImage()
this._activateImageEdit()
}
/**
* Apply background image to the actor form
* @private
*/
_applyBackgroundImage() {
const logoUrl = BoLUtility.getLogoActorSheet()
const form = this.element.querySelector(".bol-actor-form")
if (form) form.style.backgroundImage = `url(${logoUrl})`
}
/**
* Activate image editing via FilePicker
* @private
*/
_activateImageEdit() {
const img = this.element.querySelector('[data-edit="img"]')
if (img && this.isEditable) {
img.style.cursor = "pointer"
img.addEventListener("click", () => {
new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => this.document.update({ img: path }),
}).browse()
})
}
}
/**
* Activate tab navigation
* @private
*/
_activateTabs() {
const nav = this.element.querySelector("nav[data-group]")
if (!nav) return
const group = nav.dataset.group
const activeTab = this.tabGroups[group] || "stats"
nav.querySelectorAll("[data-tab]").forEach((link) => {
const tab = link.dataset.tab
link.classList.toggle("active", tab === activeTab)
link.addEventListener("click", (event) => {
event.preventDefault()
this.tabGroups[group] = tab
this.render()
})
})
this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach((content) => {
content.classList.toggle("active", content.dataset.tab === activeTab)
})
}
/**
* Activate event listeners (replaces activateListeners with vanilla DOM)
* @private
*/
_activateListeners() {
if (!this.isEditable) return
// Item edit
this.element.querySelectorAll(".item-edit").forEach((el) => {
el.addEventListener("click", (ev) => {
const li = ev.currentTarget.closest(".item")
const item = this.actor.items.get(li?.dataset.itemId)
item?.sheet.render(true)
})
})
// Item delete
this.element.querySelectorAll(".item-delete").forEach((el) => {
el.addEventListener("click", (ev) => {
const li = ev.currentTarget.closest(".item")
const itemId = li?.dataset.itemId
Dialog.confirm({
title: game.i18n.localize("BOL.ui.deletetitle"),
content: game.i18n.localize("BOL.ui.confirmdelete"),
yes: () => {
this.actor.deleteEmbeddedDocuments("Item", [itemId])
li?.remove()
},
no: () => {},
defaultYes: false,
})
})
})
// Item equip/unequip
this.element.querySelectorAll(".item-equip").forEach((el) => {
el.addEventListener("click", (ev) => {
const li = ev.currentTarget.closest(".item")
const item = this.actor.items.get(li?.dataset.itemId)
if (item) this.actor.toggleEquipItem(item)
})
})
// Toggle fight option
this.element.querySelectorAll(".toggle-fight-option").forEach((el) => {
el.addEventListener("click", (ev) => {
const li = ev.currentTarget.closest(".item")
this.actor.toggleFightOption(li?.dataset.itemId)
})
})
// Inc/dec alchemy points
this.element.querySelectorAll(".inc-dec-btns-alchemy").forEach((el) => {
el.addEventListener("click", (ev) => {
const li = ev.currentTarget.closest(".item")
this.actor.spendAlchemyPoint(li?.dataset.itemId, 1)
})
})
// Inc/dec resource buttons
this.element.querySelectorAll(".inc-dec-btns-resource").forEach((el) => {
el.addEventListener("click", (ev) => {
const dataset = ev.currentTarget.dataset
this.actor.incDecResources(dataset.target, parseInt(dataset.incr))
})
})
// Generic inc/dec buttons for item fields
this.element.querySelectorAll(".inc-dec-btns").forEach((el) => {
el.addEventListener("click", (ev) => {
const li = ev.currentTarget.closest(".item")
if (!li) return
const item = this.actor.items.get(li.dataset.itemId)
if (!item) return
const dataset = ev.currentTarget.dataset
const operator = dataset.operator
const target = dataset.target
const incr = parseInt(dataset.incr)
const min = parseInt(dataset.min)
const max = parseInt(dataset.max) || 10000
// eslint-disable-next-line no-eval
let value = eval("item." + target) || 0
if (operator === "minus") value = value >= min + incr ? value - incr : min
if (operator === "plus") value = value <= max - incr ? value + incr : max
item.update({ [target]: value })
})
})
// Rollable elements
this.element.querySelectorAll(".rollable").forEach((el) => {
el.addEventListener("click", (ev) => this._onRoll(ev))
})
}
/**
* Handle clickable rolls (replaces _onRoll with vanilla DOM)
* @param {Event} event
* @private
*/
_onRoll(event) {
event.preventDefault()
const element = event.currentTarget
const dataset = element.dataset
const rollType = dataset.rollType
const li = element.closest(".item")
const itemId = li?.dataset.itemId
switch (rollType) {
case "attribute":
BoLRoll.attributeCheck(this.actor, dataset.key, event)
break
case "aptitude":
BoLRoll.aptitudeCheck(this.actor, dataset.key, event)
break
case "weapon":
BoLRoll.weaponCheck(this.actor, event)
break
case "spell":
BoLRoll.spellCheck(this.actor, event)
break
case "alchemy":
BoLRoll.alchemyCheck(this.actor, event)
break
case "protection":
this.actor.rollProtection(itemId)
break
case "damage":
this.actor.rollWeaponDamage(itemId)
break
case "aptitudexp":
this.actor.incAptitudeXP(dataset.key)
break
case "attributexp":
this.actor.incAttributeXP(dataset.key)
break
case "careerxp":
this.actor.incCareerXP(itemId)
break
case "horoscope-minor":
BoLRoll.horoscopeCheck(this.actor, event, "minor")
break
case "horoscope-major":
BoLRoll.horoscopeCheck(this.actor, event, "major")
break
case "horoscope-major-group":
BoLRoll.horoscopeCheck(this.actor, event, "majorgroup")
break
case "bougette":
this.actor.rollBougette()
break
default:
break
}
}
// #region Drag-and-Drop
#createDragDropHandlers() {
return (this.options.dragDrop || []).map((dragDrop) =>
new foundry.applications.ux.DragDrop.implementation({
...dragDrop,
permissions: {
dragstart: this._canDragStart.bind(this),
drop: this._canDragDrop.bind(this),
},
callbacks: {
dragstart: this._onDragStart.bind(this),
dragover: this._onDragOver.bind(this),
drop: this._onDrop.bind(this),
},
})
)
}
_canDragStart(selector) {
return this.isEditable
}
_canDragDrop(selector) {
return this.isEditable
}
// #endregion
}

View File

@@ -0,0 +1,255 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
/**
* Base Item Sheet for BoL system using AppV2
* @extends {ItemSheetV2}
*/
export default class BoLBaseItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["bol", "sheet", "item"],
position: {
width: 650,
height: 780,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: 'nav[data-group="primary"]',
contentSelector: "section.sheet-body",
initial: "description",
},
],
actions: {
editImage: BoLBaseItemSheet.#onEditImage,
postItem: BoLBaseItemSheet.#onPostItem,
},
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" }
/** @override */
async _prepareContext() {
const context = {
// Document & system
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
source: this.document.toObject(),
// Enriched content
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(
this.document.system.description,
{ async: true }
),
// Properties
category: this.document.system.category,
itemProperties: this.document.itemProperties,
// Config & permissions
config: game.bol.config,
isGM: game.user.isGM,
isEditable: this.isEditable,
// CSS classes for template
cssClass: this.options.classes.join(" "),
// Tab state
tabs: this._getTabs(),
activeTab: this.tabGroups.primary || "description"
}
// Add careers if item is on an actor
if (this.document.actor) {
context.careers = this.document.actor.careers
}
// Apply dynamic defaults based on item type
this._applyDynamicDefaults(context)
return context
}
/**
* Get tabs configuration
* @returns {object[]}
* @private
*/
_getTabs() {
return [
{ id: "description", label: "BOL.ui.tab.description", icon: "fa-solid fa-book" },
{ id: "properties", label: "BOL.ui.tab.details", icon: "fa-solid fa-cog" }
]
}
/**
* Apply dynamic defaults to context based on item type and category
* @param {object} context
* @private
*/
_applyDynamicDefaults(context) {
const itemData = context.item
if (itemData.type === "item") {
// Set default category
if (!itemData.system.category) {
itemData.system.category = "equipment"
}
// Handle equipment slot
if (itemData.system.category === "equipment" && itemData.system.properties.equipable) {
if (!itemData.system.properties.slot) {
itemData.system.properties.slot = "-"
}
}
// Handle spell conditions
if (itemData.system.category === 'spell') {
if (!itemData.system.properties.mandatoryconditions) {
itemData.system.properties.mandatoryconditions = []
}
if (!itemData.system.properties.optionnalconditions) {
itemData.system.properties.optionnalconditions = []
}
for (let i = 0; i < 4; i++) {
itemData.system.properties.mandatoryconditions[i] = itemData.system.properties.mandatoryconditions[i] ?? ""
}
for (let i = 0; i < 8; i++) {
itemData.system.properties.optionnalconditions[i] = itemData.system.properties.optionnalconditions[i] ?? ""
}
}
} else if (itemData.type === "feature") {
// Set default subtype/category
if (!itemData.system.subtype) {
itemData.system.category = "origin"
}
}
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
this.#dragDrop.forEach((d) => d.bind(this.element))
this._activateTabs()
this._activateListeners()
}
/**
* Activate tab navigation via CSS only (no re-render) to preserve unsaved form state.
* @private
*/
_activateTabs() {
const nav = this.element.querySelector('nav[data-group="primary"]')
if (!nav) return
const section = this.element.querySelector('section.sheet-body')
if (!section) return
const activeTab = this.tabGroups.primary || "description"
// Set initial active state
nav.querySelectorAll('[data-tab]').forEach(link => {
link.classList.toggle('active', link.dataset.tab === activeTab)
})
section.querySelectorAll('[data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
// Tab click — CSS-only switch, no render()
nav.querySelectorAll('[data-tab]').forEach(link => {
link.addEventListener('click', (event) => {
event.preventDefault()
const tab = link.dataset.tab
this.tabGroups.primary = tab
nav.querySelectorAll('[data-tab]').forEach(l => l.classList.toggle('active', l.dataset.tab === tab))
section.querySelectorAll('[data-tab]').forEach(c => c.classList.toggle('active', c.dataset.tab === tab))
})
})
}
/**
* Activate custom listeners
* @private
*/
_activateListeners() {
if (!this.isEditable) return
// Armor quality change handler
const armorQuality = this.element.querySelector('.armorQuality')
if (armorQuality) {
armorQuality.addEventListener('change', (ev) => {
const value = ev.currentTarget.value
const soakFormula = this.element.querySelector('.soakFormula')
if (soakFormula && game.bol.config.soakFormulas[value]) {
soakFormula.value = game.bol.config.soakFormulas[value]
}
})
}
}
// #region Drag-and-Drop Workflow
/**
* Create drag-and-drop workflow handlers for this Application
* @returns {DragDrop[]}
* @private
*/
#createDragDropHandlers() {
return []
}
// #endregion
// #region Actions
/**
* Handle editing the item image
* @param {PointerEvent} event
* @param {HTMLElement} target
* @private
*/
static async #onEditImage(event, target) {
const fp = new FilePicker({
current: this.document.img,
type: "image",
callback: (path) => {
this.document.update({ img: path })
},
})
return fp.browse()
}
/**
* Handle posting the item to chat
* @param {PointerEvent} event
* @param {HTMLElement} target
* @private
*/
static async #onPostItem(event, target) {
const BoLUtility = (await import("../../system/bol-utility.js")).BoLUtility
let chatData = foundry.utils.duplicate(this.document)
if (this.document.actor) {
chatData.actor = { id: this.document.actor.id }
}
BoLUtility.postItem(chatData)
}
// #endregion
}

View File

@@ -0,0 +1,39 @@
import BoLBaseItemSheet from "./base-item-sheet.mjs"
/**
* Item Sheet for "feature" type items (boons, careers, origins, etc.)
* @extends {BoLBaseItemSheet}
*/
export default class BoLFeatureSheet extends BoLBaseItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["bol", "sheet", "item", "item-type-feature"],
}
/** @override */
static PARTS = {
main: {
template: "systems/bol/templates/item/feature-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
// Add feature-specific context
context.isFeature = true
context.isBoon = context.system.subtype === "boon"
context.isFlaw = context.system.subtype === "flaw"
context.isCareer = context.system.subtype === "career"
context.isOrigin = context.system.subtype === "origin"
context.isRace = context.system.subtype === "race"
context.isFightOption = context.system.subtype === "fightoption"
context.isEffect = context.system.subtype === "effect"
context.isHoroscope = context.system.subtype === "horoscope"
context.isXpLog = context.system.subtype === "xplog"
return context
}
}

View File

@@ -0,0 +1,65 @@
import BoLBaseActorSheet from "./base-actor-sheet.mjs"
/**
* Actor Sheet for BoL hordes using AppV2
*/
export default class BoLHordeSheet extends BoLBaseActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes],
actions: {
...super.DEFAULT_OPTIONS.actions,
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/bol/templates/actor/horde-sheet.hbs",
},
}
/** @override */
tabGroups = { primary: "stats" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
context.options = this.options
context.editScore = this.options.editScore
context.description = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.details?.biography || "", { async: true }
)
console.log("HORDE (AppV2)", context)
return context
}
/** @override */
_activateListeners() {
super._activateListeners()
if (!this.isEditable) return
// Item create via header dataset
this.element.querySelectorAll(".item-create").forEach((el) => {
el.addEventListener("click", (ev) => {
ev.preventDefault()
const header = ev.currentTarget
const type = header.dataset.type
const data = foundry.utils.duplicate(header.dataset)
delete data.type
this.actor.createEmbeddedDocuments("Item", [{ name: `New ${type}`, type, data }])
})
})
// Create generic item
this.element.querySelectorAll(".create_item").forEach((el) => {
el.addEventListener("click", () => {
this.actor.createEmbeddedDocuments("Item", [{ name: "Nouvel Equipement", type: "item" }], { renderSheet: true })
})
})
}
}

View File

@@ -0,0 +1,38 @@
import BoLBaseItemSheet from "./base-item-sheet.mjs"
/**
* Item Sheet for "item" type items (equipment, weapons, etc.)
* @extends {BoLBaseItemSheet}
*/
export default class BoLItemSheet extends BoLBaseItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
classes: ["bol", "sheet", "item", "item-type-item"],
}
/** @override */
static PARTS = {
main: {
template: "systems/bol/templates/item/item-sheet.hbs",
},
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
// Add item-specific context
context.isItem = true
context.isEquipment = context.category === "equipment"
context.isWeapon = context.category === "weapon"
context.isProtection = context.category === "protection"
context.isSpell = context.category === "spell"
context.isAlchemy = context.category === "alchemy"
context.isCapacity = context.category === "capacity"
context.isMagical = context.category === "magical"
context.isVehicle = context.category === "vehicle"
return context
}
}

View File

@@ -0,0 +1,70 @@
import BoLBaseActorSheet from "./base-actor-sheet.mjs"
/**
* Actor Sheet for BoL vehicles using AppV2
*/
export default class BoLVehicleSheet extends BoLBaseActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes],
actions: {
...super.DEFAULT_OPTIONS.actions,
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/bol/templates/actor/vehicle-sheet.hbs",
},
}
/** @override */
tabGroups = { primary: "stats" }
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
context.weapons = actor.vehicleWeapons
context.options = this.options
context.editScore = this.options.editScore
context.description = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
actor.system.description || "", { async: true }
)
console.log("VEHICLE (AppV2)", context) // TODO: remove before release
return context
}
/** @override */
_activateListeners() {
super._activateListeners()
if (!this.isEditable) return
// Item create via header dataset
this.element.querySelectorAll(".item-create").forEach((el) => {
el.addEventListener("click", (ev) => {
ev.preventDefault()
const header = ev.currentTarget
const type = header.dataset.type
const data = foundry.utils.duplicate(header.dataset)
delete data.type
this.actor.createEmbeddedDocuments("Item", [{ name: `New ${type}`, type, data }])
})
})
// Create vehicle weapon
this.element.querySelectorAll(".vehicle-weapon-add").forEach((el) => {
el.addEventListener("click", () => {
this.actor.createEmbeddedDocuments("Item", [{
name: game.i18n.localize("BOL.ui.newEquipment"),
type: "item",
system: { category: "vehicleweapon" },
}], { renderSheet: true })
})
})
}
}

View File

@@ -1,11 +1,9 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
// Import Modules // Import Modules
import { BoLActor } from "./actor/actor.js" import { BoLActor } from "./actor/actor.js"
import { BoLActorSheet } from "./actor/actor-sheet.js" // AppV1 actor sheets kept for reference only (AppV2 used via sheets.* below)
import { BoLVehicleSheet } from "./actor/vehicle-sheet.js"
import { BoLHordeSheet } from "./actor/horde-sheet.js"
import { BoLItem } from "./item/item.js" import { BoLItem } from "./item/item.js"
import { BoLItemSheet } from "./item/item-sheet.js" // Note: Old BoLItemSheet (AppV1) is now replaced by AppV2 sheets
import { System, BOL } from "./system/config.js" import { System, BOL } from "./system/config.js"
import { preloadHandlebarsTemplates } from "./system/templates.js" import { preloadHandlebarsTemplates } from "./system/templates.js"
import { registerHandlebarsHelpers } from "./system/helpers.js" import { registerHandlebarsHelpers } from "./system/helpers.js"
@@ -18,6 +16,12 @@ import { BoLHotbar } from "./system/bol-hotbar.js"
import { BoLCommands } from "./system/bol-commands.js" import { BoLCommands } from "./system/bol-commands.js"
import { BoLRoll } from "./controllers/bol-rolls.js" import { BoLRoll } from "./controllers/bol-rolls.js"
// Import DataModels
import * as models from "./models/_module.mjs"
// Import AppV2 Sheets
import * as sheets from "./applications/sheets/_module.mjs"
/* -------------------------------------------- */ /* -------------------------------------------- */
Hooks.once('init', async function () { Hooks.once('init', async function () {
@@ -28,7 +32,9 @@ Hooks.once('init', async function () {
BoLRoll, BoLRoll,
BoLUtility, BoLUtility,
macros: Macros, macros: Macros,
config: BOL config: BOL,
models,
sheets
}; };
// Game socket // Game socket
@@ -47,17 +53,38 @@ Hooks.once('init', async function () {
// Define custom Entity classes // Define custom Entity classes
CONFIG.Actor.documentClass = BoLActor; CONFIG.Actor.documentClass = BoLActor;
CONFIG.Actor.dataModels = {
character: models.BoLCharacter,
encounter: models.BoLEncounter,
horde: models.BoLHorde,
vehicle: models.BoLVehicle
}
CONFIG.Item.documentClass = BoLItem; CONFIG.Item.documentClass = BoLItem;
CONFIG.Item.dataModels = {
item: models.BoLItem,
feature: models.BoLFeature
}
CONFIG.Combat.documentClass = BoLCombatManager; CONFIG.Combat.documentClass = BoLCombatManager;
// Register sheet application classes // Register sheet application classes
Actors.unregisterSheet("core", ActorSheet); foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
Actors.registerSheet("bol", BoLActorSheet, { types: ["character", "encounter"], makeDefault: true }) foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLActorSheet, { types: ["character", "encounter"], makeDefault: true })
Actors.registerSheet("bol", BoLVehicleSheet, { types: ["vehicle"], makeDefault: true }) foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLVehicleSheet, { types: ["vehicle"], makeDefault: true })
Actors.registerSheet("bol", BoLHordeSheet, { types: ["horde"], makeDefault: true }) foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLHordeSheet, { types: ["horde"], makeDefault: true })
Items.unregisterSheet("core", ItemSheet); // Register AppV2 Item Sheets
Items.registerSheet("bol", BoLItemSheet, { makeDefault: true }); foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("bol", sheets.BoLItemSheet, { types: ["item"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("bol", sheets.BoLFeatureSheet, { types: ["feature"], makeDefault: true });
// Debug: Verify AppV2 sheets are loaded
console.log("BoL Item Sheets registered:", {
BoLItemSheet: sheets.BoLItemSheet.name,
BoLFeatureSheet: sheets.BoLFeatureSheet.name,
extendsApplicationV2: sheets.BoLItemSheet.prototype instanceof foundry.applications.api.ApplicationV2
});
// Inot useful stuff // Inot useful stuff
BoLUtility.init() BoLUtility.init()
@@ -83,18 +110,22 @@ Hooks.once('init', async function () {
/* -------------------------------------------- */ /* -------------------------------------------- */
function welcomeMessage() { async function welcomeMessage() {
ChatMessage.create({ const noRulebook = !game.modules.find(m => m.id === "bol-rulebook")
user: game.user.id, const content = await foundry.applications.handlebars.renderTemplate(
whisper: [game.user.id], "systems/bol/templates/chat/chat-welcome.hbs",
content: `<div id="welcome-message-bol"><span class="rdd-roll-part"> { noRulebook }
<strong>` + game.i18n.localize("BOL.chat.welcome1") + `</strong><p>` + )
game.i18n.localize("BOL.chat.welcome2") + "<p>" + ChatMessage.create({ user: game.user.id, whisper: [game.user.id], content })
game.i18n.localize("BOL.chat.welcome3") + "<p>" +
game.i18n.localize("BOL.chat.welcome4") + "</p>" + if (game.user.isGM && game.i18n.lang == 'en' && !game.modules.find(m => m.id == "babele")) {
game.i18n.localize("BOL.chat.welcome5") + "<br>" + ChatMessage.create({
game.i18n.localize("BOL.chat.welcome6") user: game.user.id,
}) whisper: [game.user.id],
content: `<div class="bol-welcome-card"><div class="welcome-body"><p class="welcome-warning">⚠ WARNING ! English language selected, but Babele module is not installed !<br>Please install babele from the module tab in Foundry interface.</p></div></div>`
})
ui.notifications.warn("WARNING ! English language selected, but babele module is not installed !<br>Please install babele from the module tab in Foundry interface.")
}
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -157,5 +188,3 @@ Hooks.once('ready', async function () {
} }
}) })

View File

@@ -5,11 +5,6 @@ const _apt2attr = { init: "mind", melee: "agility", ranged: "agility", def: "vig
/* -------------------------------------------- */ /* -------------------------------------------- */
export class BoLRoll { export class BoLRoll {
/* -------------------------------------------- */
static options() {
return { classes: ["bol", "dialog"], width: 480, height: 'fit-content' };
}
/* -------------------------------------------- */ /* -------------------------------------------- */
static getDefaultAttribute(key) { static getDefaultAttribute(key) {
return _apt2attr[key] return _apt2attr[key]
@@ -19,9 +14,9 @@ export class BoLRoll {
static updateApplicableEffects(rollData) { static updateApplicableEffects(rollData) {
let appEffects = [] let appEffects = []
for (let effect of rollData.bolEffects) { for (let effect of rollData.bolEffects) {
if ( (effect.system.properties.identifier == "always") || if ((effect.system.properties.identifier == "always") ||
(effect.system.properties.identifier.includes(rollData.attribute.key)) || (effect.system.properties.identifier.includes(rollData.attribute.key)) ||
(rollData.aptitude && effect.system.properties.identifier.includes(rollData.aptitude.key)) ){ (rollData.aptitude && effect.system.properties.identifier.includes(rollData.aptitude.key))) {
appEffects.push(effect) appEffects.push(effect)
} }
} }
@@ -76,7 +71,7 @@ export class BoLRoll {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static attributeCheck(actor, key="vigor", event=undefined, combatData=undefined) { static attributeCheck(actor, key = "vigor", event = undefined, combatData = undefined) {
let attribute = eval(`actor.system.attributes.${key}`) let attribute = eval(`actor.system.attributes.${key}`)
@@ -89,7 +84,7 @@ export class BoLRoll {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static aptitudeCheck(actor, key="init", event=undefined, combatData=undefined) { static aptitudeCheck(actor, key = "init", event = undefined, combatData = undefined) {
let aptitude = eval(`actor.system.aptitudes.${key}`) let aptitude = eval(`actor.system.aptitudes.${key}`)
let attrKey = this.getDefaultAttribute(key) let attrKey = this.getDefaultAttribute(key)
@@ -129,7 +124,7 @@ export class BoLRoll {
rangeMsg = "BOL.chat.range6" rangeMsg = "BOL.chat.range6"
} }
ChatMessage.create({ ChatMessage.create({
content: await renderTemplate('systems/bol/templates/chat/chat-info-range.hbs', { content: await foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/chat-info-range.hbs', {
weapon: weapon, weapon: weapon,
attackerName: _token.actor.name, attackerName: _token.actor.name,
defenderName: target.actor.name, defenderName: target.actor.name,
@@ -308,26 +303,31 @@ export class BoLRoll {
// Final number of dices // Final number of dices
this.rollData.nbDice = 2 + Math.abs(this.rollData.bmDice) this.rollData.nbDice = 2 + Math.abs(this.rollData.bmDice)
// Bonus or Malus ? // Bonus or Malus ?
if (this.rollData.bmDice == 0) { const nbDiceEl = document.querySelector('#roll-nbdice')
$('#roll-nbdice').val("2") if (nbDiceEl) {
} else { if (this.rollData.bmDice == 0) {
let letter = (this.rollData.bmDice > 0) ? "B" : "M" nbDiceEl.value = "2"
$('#roll-nbdice').val("2 + " + String(Math.abs(this.rollData.bmDice)) + letter) } else {
let letter = (this.rollData.bmDice > 0) ? "B" : "M"
nbDiceEl.value = "2 + " + String(Math.abs(this.rollData.bmDice)) + letter
}
} }
let rollbase = this.rollData.attrValue + "+" + this.rollData.aptValue let rollbase = this.rollData.attrValue + "+" + this.rollData.aptValue
if (this.rollData.weapon && this.rollData.weapon.system.properties.onlymodifier) { if (this.rollData.weapon && this.rollData.weapon.system.properties.onlymodifier) {
rollbase = "" rollbase = ""
} }
$('#roll-modifier').val(rollbase + "+" + this.rollData.careerBonus + "+" + this.rollData.mod + "+" + const modifierEl = document.querySelector('#roll-modifier')
if (modifierEl) modifierEl.value = rollbase + "+" + this.rollData.careerBonus + "+" + this.rollData.mod + "+" +
this.rollData.modRanged + "+" + this.rollData.weaponModifier + "-" + this.rollData.defence + "-" + this.rollData.modArmorMalus + "-" + this.rollData.modRanged + "+" + this.rollData.weaponModifier + "-" + this.rollData.defence + "-" + this.rollData.modArmorMalus + "-" +
this.rollData.shieldMalus + "+" + this.rollData.attackModifier + "+" + this.rollData.appliedArmorMalus + "+" + effectModifier) this.rollData.shieldMalus + "+" + this.rollData.attackModifier + "+" + this.rollData.appliedArmorMalus + "+" + effectModifier
// Rebuild lits of applicable effects // Rebuild list of applicable effects
let selectEffects = "" let selectEffects = ""
for (let effect of this.rollData.bolApplicableEffects) { for (let effect of this.rollData.bolApplicableEffects) {
selectEffects += `<option value="${effect.id}" selected>${effect.name}</option>` selectEffects += `<option value="${effect.id}" selected>${effect.name}</option>`
} }
$('#applicable-effects').html(selectEffects) const effectsEl = document.querySelector('#applicable-effects')
if (effectsEl) effectsEl.innerHTML = selectEffects
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -360,46 +360,48 @@ export class BoLRoll {
/* -------------------------------------------- */ /* -------------------------------------------- */
static updateArmorMalus(rollData) { static updateArmorMalus(rollData) {
rollData.appliedArmorMalus = 0 rollData.appliedArmorMalus = 0
const agiEl = document.querySelector('#armor-agi-malus')
if (rollData.attribute.key == "agility") { if (rollData.attribute.key == "agility") {
$("#armor-agi-malus").show() if (agiEl) agiEl.style.display = ''
rollData.appliedArmorMalus += rollData.armorAgiMalus rollData.appliedArmorMalus += rollData.armorAgiMalus
} else { } else {
$("#armor-agi-malus").hide() if (agiEl) agiEl.style.display = 'none'
} }
const initEl = document.querySelector('#armor-init-malus')
if (rollData.aptitude && rollData.aptitude.key == "init") { if (rollData.aptitude && rollData.aptitude.key == "init") {
$("#armor-init-malus").show() if (initEl) initEl.style.display = ''
rollData.appliedArmorMalus += rollData.armorInitMalus rollData.appliedArmorMalus += rollData.armorInitMalus
} else { } else {
$("#armor-init-malus").hide() if (initEl) initEl.style.display = 'none'
} }
} }
/* ------------------------------ -------------- */ /* ------------------------------ -------------- */
static updatePPCost(rollData) { static updatePPCost(rollData) {
$('#ppcost').html(rollData.ppCost + " + Armor(" + rollData.ppCostArmor + ")=" + Number(rollData.ppCost + rollData.ppCostArmor)) const el = document.querySelector('#ppcost')
if (el) el.innerHTML = rollData.ppCost + " + Armor(" + rollData.ppCostArmor + ")=" + Number(rollData.ppCost + rollData.ppCostArmor)
} }
/* ------------------------------ -------------- */ /* ------------------------------ -------------- */
static rollDialogListener(html) { static rollDialogListener(html) {
this.updateTotalDice() this.updateTotalDice()
html.find('#optcond').change((event) => { // Dynamic change of PP cost of spell html.querySelector('#optcond')?.addEventListener('change', (event) => {
let pp = BoLUtility.computeSpellCost(this.rollData.spell, event.currentTarget.selectedOptions.length) let pp = BoLUtility.computeSpellCost(this.rollData.spell, event.currentTarget.selectedOptions.length)
this.rollData.ppCost = pp this.rollData.ppCost = pp
this.updatePPCost(this.rollData) this.updatePPCost(this.rollData)
}) })
html.find('#mod').change((event) => { html.querySelector('#mod')?.addEventListener('change', (event) => {
this.rollData.mod = Number(event.currentTarget.value) this.rollData.mod = Number(event.currentTarget.value)
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#modRanged').change((event) => { html.querySelector('#modRanged')?.addEventListener('change', (event) => {
this.rollData.modRanged = Number(event.currentTarget.value) this.rollData.modRanged = Number(event.currentTarget.value)
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#attr').change((event) => { html.querySelector('#attr')?.addEventListener('change', (event) => {
let attrKey = event.currentTarget.value let attrKey = event.currentTarget.value
let actor = BoLUtility.getActorFromRollData(this.rollData) let actor = BoLUtility.getActorFromRollData(this.rollData)
this.rollData.attribute = foundry.utils.duplicate(actor.system.attributes[attrKey]) this.rollData.attribute = foundry.utils.duplicate(actor.system.attributes[attrKey])
@@ -407,7 +409,7 @@ export class BoLRoll {
this.rollData.bolApplicableEffects = this.updateApplicableEffects(this.rollData) this.rollData.bolApplicableEffects = this.updateApplicableEffects(this.rollData)
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#apt').change((event) => { html.querySelector('#apt')?.addEventListener('change', (event) => {
let aptKey = event.currentTarget.value let aptKey = event.currentTarget.value
let actor = BoLUtility.getActorFromRollData(this.rollData) let actor = BoLUtility.getActorFromRollData(this.rollData)
this.rollData.aptitude = foundry.utils.duplicate(actor.system.aptitudes[aptKey]) this.rollData.aptitude = foundry.utils.duplicate(actor.system.aptitudes[aptKey])
@@ -416,65 +418,58 @@ export class BoLRoll {
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#applyShieldMalus').click((event) => { html.querySelector('#applyShieldMalus')?.addEventListener('click', (event) => {
if (event.currentTarget.checked) { this.rollData.shieldMalus = event.currentTarget.checked ? this.rollData.shieldAttackMalus : 0
this.rollData.shieldMalus = this.rollData.shieldAttackMalus
} else {
this.rollData.shieldMalus = 0
}
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#career').change((event) => { html.querySelector('#career')?.addEventListener('change', (event) => {
let careers = $('#career').val() let careers = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
this.rollData.careerBonus = (!careers || careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i))) this.rollData.careerBonus = (!careers || careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i)))
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#boon').change((event) => { html.querySelector('#boon')?.addEventListener('change', (event) => {
let boons = $('#boon').val() let boons = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
this.rollData.nbBoons = (!boons || boons.length == 0) ? 0 : boons.length this.rollData.nbBoons = (!boons || boons.length == 0) ? 0 : boons.length
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#flaw').change((event) => { html.querySelector('#flaw')?.addEventListener('change', (event) => {
let flaws = $('#flaw').val() let flaws = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
this.rollData.nbFlaws = (!flaws || flaws.length == 0) ? 0 : flaws.length this.rollData.nbFlaws = (!flaws || flaws.length == 0) ? 0 : flaws.length
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('.bdice').click((event) => { html.querySelectorAll('.bdice').forEach(el => el.addEventListener('click', (event) => {
this.rollData.mDice = 0 this.rollData.mDice = 0
this.rollData.bDice = Number(event.currentTarget.value) this.rollData.bDice = Number(event.currentTarget.value)
this.updateTotalDice() this.updateTotalDice()
}) }))
html.find('.mdice').click((event) => { html.querySelectorAll('.mdice').forEach(el => el.addEventListener('click', (event) => {
this.rollData.bDice = 0 this.rollData.bDice = 0
this.rollData.mDice = Number(event.currentTarget.value) this.rollData.mDice = Number(event.currentTarget.value)
this.updateTotalDice() this.updateTotalDice()
}) }))
html.find('#horoscope-bonus-applied').change((event) => { html.querySelector('#horoscope-bonus-applied')?.addEventListener('change', (event) => {
this.rollData.selectedHoroscope = [] this.rollData.selectedHoroscope = []
for (let option of event.currentTarget.selectedOptions) { for (let option of event.currentTarget.selectedOptions) {
this.rollData.selectedHoroscope.push(foundry.utils.duplicate(this.rollData.horoscopeBonusList[Number(option.index)])) this.rollData.selectedHoroscope.push(foundry.utils.duplicate(this.rollData.horoscopeBonusList[Number(option.index)]))
} }
let horoscopes = $('#horoscope-bonus-applied').val() let horoscopes = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
this.rollData.horoscopeBonus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length this.rollData.horoscopeBonus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length
this.updateTotalDice() this.updateTotalDice()
}) })
html.querySelector('#horoscope-malus-applied')?.addEventListener('change', (event) => {
html.find('#horoscope-malus-applied').change((event) => {
this.rollData.selectedHoroscope = [] this.rollData.selectedHoroscope = []
for (let option of event.currentTarget.selectedOptions) { for (let option of event.currentTarget.selectedOptions) {
this.rollData.selectedHoroscope.push(foundry.utils.duplicate(this.rollData.horoscopeBonusList[Number(option.index)])) this.rollData.selectedHoroscope.push(foundry.utils.duplicate(this.rollData.horoscopeBonusList[Number(option.index)]))
} }
let horoscopes = $('#horoscope-malus-applied').val() let horoscopes = Array.from(event.currentTarget.selectedOptions).map(o => o.value)
this.rollData.horoscopeMalus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length this.rollData.horoscopeMalus = (!horoscopes || horoscopes.length == 0) ? 0 : horoscopes.length
this.updateTotalDice() this.updateTotalDice()
}) })
html.find('#horoscope-group-applied').change((event) => { html.querySelector('#horoscope-group-applied')?.addEventListener('change', (event) => {
this.rollData.selectedGroupHoroscopeIndex = event.currentTarget.value this.rollData.selectedGroupHoroscopeIndex = event.currentTarget.value
this.updateTotalDice() this.updateTotalDice()
}) })
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -482,10 +477,15 @@ export class BoLRoll {
if (rollData.mode == "weapon") { if (rollData.mode == "weapon") {
rollData.weaponModifier = rollData.weapon.system.properties.attackModifiers ?? 0 rollData.weaponModifier = rollData.weapon.system.properties.attackModifiers ?? 0
rollData.attackBonusDice = rollData.weapon.system.properties.attackBonusDice rollData.attackBonusDice = rollData.weapon.system.properties.attackBonusDice
rollData.attackMalusDice = rollData.weapon.system.properties.attackMalusDice
if (rollData.attackBonusDice) { if (rollData.attackBonusDice) {
rollData.adv = "1B" rollData.adv = "1B"
rollData.bDice = 1 rollData.bDice = 1
} }
if (rollData.attackMalusDice) {
rollData.adv = "1M"
rollData.mDice = 1
}
if (defender) { // If target is selected if (defender) { // If target is selected
rollData.defence = defender.defenseValue rollData.defence = defender.defenseValue
rollData.armorMalus = defender.armorMalusValue rollData.armorMalus = defender.armorMalusValue
@@ -534,6 +534,7 @@ export class BoLRoll {
rollData.id = foundry.utils.randomID(16) rollData.id = foundry.utils.randomID(16)
rollData.weaponModifier = 0 rollData.weaponModifier = 0
rollData.attackBonusDice = false rollData.attackBonusDice = false
rollData.attackMalusDice = false
rollData.armorMalus = 0 rollData.armorMalus = 0
// Specific stuff // Specific stuff
this.preProcessWeapon(rollData, defender) this.preProcessWeapon(rollData, defender)
@@ -546,41 +547,47 @@ export class BoLRoll {
} else { } else {
rollData.shieldMalus = 0 rollData.shieldMalus = 0
} }
// Save // Save & pre-initialize computed fields
this.rollData = rollData this.rollData = rollData
this.updateTotalDice()
console.log("ROLLDATA", rollData) console.log("ROLLDATA", rollData)
// Then display+process the dialog // Then display+process the dialog
const rollOptionContent = await renderTemplate(rollOptionTpl, rollData); const rollOptionContent = await foundry.applications.handlebars.renderTemplate(rollOptionTpl, rollData);
let d = new Dialog({ // Use Hooks to reliably get the rendered HTMLElement (renderDialogV2 receives (app, element, context))
title: rollData.label, Hooks.once('renderDialogV2', (app, element) => {
element.classList.add('bol');
this.rollDialogListener(element);
});
return foundry.applications.api.DialogV2.wait({
window: { title: rollData.label },
content: rollOptionContent, content: rollOptionContent,
rollData: rollData, rejectClose: false,
render: html => this.rollDialogListener(html), buttons: [
buttons: { {
cancel: { type: 'button',
icon: '<i class="fas fa-times"></i>',
label: game.i18n.localize("BOL.ui.cancel"), label: game.i18n.localize("BOL.ui.cancel"),
callback: () => { icon: 'fas fa-times',
} action: 'cancel'
}, },
submit: { {
icon: '<i class="fas fa-check"></i>', type: 'submit',
label: game.i18n.localize("BOL.ui.submit"), label: game.i18n.localize("BOL.ui.submit"),
callback: (html) => { icon: 'fas fa-check',
action: 'submit',
callback: (event, button, dialog) => {
console.log("Submit Roll!!!!"); console.log("Submit Roll!!!!");
if (rollData.mode == 'spell' && rollData.ppCurrent < rollData.ppCost) { // Check PP available if (rollData.mode == 'spell' && rollData.ppCurrent < rollData.ppCost) {
ui.notifications.warn("Pas assez de Points de Pouvoir !") ui.notifications.warn("Pas assez de Points de Pouvoir !")
return return false
} }
rollData.registerInit = (rollData.aptitude && rollData.aptitude.key == 'init') ? $('#register-init').is(":checked") : false; rollData.registerInit = (rollData.aptitude && rollData.aptitude.key == 'init') ?
(dialog.element.querySelector('#register-init')?.checked ?? false) : false;
const isMalus = (rollData.bmDice < 0) const isMalus = (rollData.bmDice < 0)
let rollbase = rollData.attrValue + rollData.aptValue let rollbase = rollData.attrValue + rollData.aptValue
if (rollData.weapon?.system.properties.onlymodifier) { if (rollData.weapon?.system.properties.onlymodifier) rollbase = 0
rollbase = 0
}
let diceData = BoLUtility.getDiceData() let diceData = BoLUtility.getDiceData()
let malusInit = rollData.combatData?.malusInit || 0 let malusInit = rollData.combatData?.malusInit || 0
const modifiers = rollbase + rollData.careerBonus + rollData.mod + rollData.weaponModifier - rollData.defence - rollData.modArmorMalus + rollData.shieldMalus + rollData.attackModifier + rollData.appliedArmorMalus + rollData.effectModifier - malusInit const modifiers = rollbase + rollData.careerBonus + rollData.mod + rollData.weaponModifier - rollData.defence - rollData.modArmorMalus + rollData.shieldMalus + rollData.attackModifier + rollData.appliedArmorMalus + rollData.effectModifier - malusInit
@@ -593,12 +600,8 @@ export class BoLRoll {
r.roll(); r.roll();
} }
} }
}, ]
default: onEnter, }, { classes: ['bol', 'dialog'], width: 480 });
close: () => { }
}, this.options());
return d.render(true);
} }
} }
@@ -634,7 +637,7 @@ export class BoLDefaultRoll {
const diceTotal = activeDice.map(r => r.result).reduce((a, b) => a + b) const diceTotal = activeDice.map(r => r.result).reduce((a, b) => a + b)
this.rollData.roll = r this.rollData.roll = r
this.rollData.isFumble = (diceTotal <= diceData.criticalFailureValue) this.rollData.isFumble = (diceTotal <= diceData.criticalFailureValue)
if ( this.rollData.isFumble ) { if (this.rollData.isFumble) {
this.rollData.isSuccess = false this.rollData.isSuccess = false
this.rollData.isCritical = false this.rollData.isCritical = false
this.rollData.isRealCritical = false this.rollData.isRealCritical = false
@@ -642,7 +645,7 @@ export class BoLDefaultRoll {
this.rollData.isFailure = true this.rollData.isFailure = true
} else { } else {
this.rollData.isCritical = (diceTotal >= diceData.criticalSuccessValue) this.rollData.isCritical = (diceTotal >= diceData.criticalSuccessValue)
if ( this.rollData.isCritical) { if (this.rollData.isCritical) {
this.rollData.isSuccess = true this.rollData.isSuccess = true
} else { } else {
this.rollData.isSuccess = (r.total >= diceData.successValue) this.rollData.isSuccess = (r.total >= diceData.successValue)
@@ -692,18 +695,15 @@ export class BoLDefaultRoll {
/* -------------------------------------------- */ /* -------------------------------------------- */
async sendChatMessage() { async sendChatMessage() {
let actor = BoLUtility.getActorFromRollData(this.rollData) const actor = BoLUtility.getActorFromRollData(this.rollData)
this._buildChatMessage(this.rollData).then(async msgFlavor => { const rollMode = game.settings.get("core", "rollMode")
//console.log("MSG", msgFlavor ) const msgFlavor = await this._buildChatMessage(this.rollData)
let msg = await this.rollData.roll.toMessage({ const msg = await this.rollData.roll.toMessage({
user: game.user.id, flavor: msgFlavor,
rollMode: game.settings.get("core", "rollMode"), speaker: ChatMessage.getSpeaker({ actor: actor }),
flavor: msgFlavor, }, { rollMode })
speaker: ChatMessage.getSpeaker({ actor: actor }), this.rollData.roll = foundry.utils.duplicate(this.rollData.roll)
}) if (msg) await msg.setFlag("world", "bol-roll-data", this.rollData)
this.rollData.roll = foundry.utils.duplicate(this.rollData.roll) // Remove object, keep data (v111 ready)
msg.setFlag("world", "bol-roll-data", this.rollData)
})
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -805,13 +805,13 @@ export class BoLDefaultRoll {
/* -------------------------------------------- */ /* -------------------------------------------- */
_buildDamageChatMessage(rollData) { _buildDamageChatMessage(rollData) {
const rollMessageTpl = 'systems/bol/templates/chat/rolls/damage-roll-card.hbs'; const rollMessageTpl = 'systems/bol/templates/chat/rolls/damage-roll-card.hbs';
return renderTemplate(rollMessageTpl, rollData) return foundry.applications.handlebars.renderTemplate(rollMessageTpl, rollData)
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
_buildChatMessage(rollData) { _buildChatMessage(rollData) {
const rollMessageTpl = 'systems/bol/templates/chat/rolls/default-roll-card.hbs' const rollMessageTpl = 'systems/bol/templates/chat/rolls/default-roll-card.hbs'
return renderTemplate(rollMessageTpl, rollData) return foundry.applications.handlebars.renderTemplate(rollMessageTpl, rollData)
} }
} }

View File

@@ -4,7 +4,7 @@ import { BoLUtility } from "../system/bol-utility.js";
* Extend the basic ItemSheet with some very simple modifications * Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet} * @extends {ItemSheet}
*/ */
export class BoLItemSheet extends ItemSheet { export class BoLItemSheet extends foundry.appv1.sheets.ItemSheet {
/** @override */ /** @override */
static get defaultOptions() { static get defaultOptions() {
@@ -13,6 +13,7 @@ export class BoLItemSheet extends ItemSheet {
template: "systems/bol/templates/item/item-sheet.hbs", template: "systems/bol/templates/item/item-sheet.hbs",
width: 650, width: 650,
height: 780, height: 780,
dragDrop: [{ dragSelector: null, dropSelector: null }],
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }] tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }]
}); });
} }
@@ -27,7 +28,7 @@ export class BoLItemSheet extends ItemSheet {
data.category = itemData.system.category data.category = itemData.system.category
data.isGM = game.user.isGM; data.isGM = game.user.isGM;
data.itemProperties = this.item.itemProperties; data.itemProperties = this.item.itemProperties;
data.description = await TextEditor.enrichHTML(this.object.system.description, { async: true }) data.description = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.description, { async: true })
if (data.document.actor) { if (data.document.actor) {
data.careers = data.document.actor.careers data.careers = data.document.actor.careers
} }
@@ -102,6 +103,7 @@ export class BoLItemSheet extends ItemSheet {
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
// Everything below here is only needed if the sheet is editable // Everything below here is only needed if the sheet is editable
if (!this.options.editable) return; if (!this.options.editable) return;
// Roll handlers, click handlers, etc. would go here. // Roll handlers, click handlers, etc. would go here.

View File

@@ -0,0 +1,119 @@
import { BoLUtility } from "../system/bol-utility.js";
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class BoLItemSheet extends foundry.appv1.sheets.ItemSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["bol", "sheet", "item"],
template: "systems/bol/templates/item/item-sheet.hbs",
width: 650,
height: 780,
dragDrop: [{ dragSelector: null, dropSelector: null }],
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description" }]
});
}
/* -------------------------------------------- */
/** @override */
async getData(options) {
const data = super.getData(options)
let itemData = foundry.utils.duplicate(data.document)
data.config = game.bol.config
data.item = itemData
data.category = itemData.system.category
data.isGM = game.user.isGM;
data.itemProperties = this.item.itemProperties;
data.description = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.description, { async: true })
if (data.document.actor) {
data.careers = data.document.actor.careers
}
// Dynamic default data fix/adapt
if (itemData.type == "item") {
if (!itemData.system.category) {
itemData.system.category = "equipment"
}
if (itemData.system.category == "equipment" && itemData.system.properties.equipable) {
if (!itemData.system.properties.slot) {
itemData.system.properties.slot = "-"
}
}
if (itemData.system.category == 'spell') {
if (!itemData.system.properties.mandatoryconditions) {
itemData.system.properties.mandatoryconditions = []
}
if (!itemData.system.properties.optionnalconditions) {
itemData.system.properties.optionnalconditions = []
}
for (let i = 0; i < 4; i++) {
itemData.system.properties.mandatoryconditions[i] = itemData.system.properties.mandatoryconditions[i] ?? ""
}
for (let i = 0; i < 8; i++) {
itemData.system.properties.optionnalconditions[i] = itemData.system.properties.optionnalconditions[i] ?? ""
}
}
} else {
if (!itemData.system.subtype) {
itemData.system.category = "origin"
}
}
console.log("ITEMDATA", data);
return data;
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
buttons.unshift({
class: "post",
icon: "fas fa-comment",
onclick: ev => this.postItem()
});
return buttons
}
/* -------------------------------------------- */
postItem() {
let chatData = foundry.utils.duplicate(this.item)
if (this.actor) {
chatData.actor = { id: this.actor.id };
}
BoLUtility.postItem(chatData);
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
return position;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Roll handlers, click handlers, etc. would go here.
html.find('.armorQuality').change(ev => {
const li = $(ev.currentTarget);
console.log(game.bol.config.soakFormulas[li.val()]);
$('.soakFormula').val(game.bol.config.soakFormulas[li.val()]);
});
}
}

View File

@@ -0,0 +1,36 @@
/**
* Extend the basic Item with some very simple modifications.
* @extends {Item}
*/
export class BoLItem extends Item {
/**
* Augment the basic Item data model with additional dynamic data.
*/
prepareData() {
super.prepareData()
const actorData = this.actor ? this.actor.system : {}
}
/* -------------------------------------------- */
get properties() {
return this.system.properties
}
/* -------------------------------------------- */
/**
* Get the Array of item properties which are used in the small sidebar of the description tab
* @return {Array}
* @private
*/
get itemProperties() {
const props = [];
if ( this.type === "item" ) {
const entries = Object.entries(this.system.properties)
props.push(...entries.filter(e => e[1] === true).map(e => { return game.bol.config.itemProperties2[e[0]] }))
}
return props.filter(p => !!p)
}
}

112
module/models/README.md Normal file
View File

@@ -0,0 +1,112 @@
# BoL DataModels
Ce dossier contient les DataModels pour le système Barbarians of Lemuria (BoL).
## Structure
### Actors DataModels
- **character.mjs** : Personnages joueurs
- Attributs (Vigor, Agility, Mind, Appeal)
- Aptitudes (Initiative, Mêlée, Distance, Défense)
- Ressources (HP, Hero Points, Faith, Power, Alchemy, Astrology)
- XP et création
- Bougette (argent)
- **encounter.mjs** : PNJ et créatures
- Mêmes attributs que character
- Champs spécifiques : chartype (tough/villain), isundead, size, environment
- **horde.mjs** : Hordes de créatures
- Mêmes attributs de base
- Champs spécifiques : hordesize, hordebasehp, hasdamagerule, damagerule
- **vehicle.mjs** : Véhicules (navires, chars, etc.)
- Attributs véhicules : hull, crew, resources
- Champs spécifiques : vehicletype, row, spur, status
### Items DataModels
- **item.mjs** : Équipements et objets
- Propriétés (weapon, armor, magical, etc.)
- Équipement (quantity, weight, price, worn)
- Category et subtype
- **feature.mjs** : Capacités, traits, sorts
- Rank (niveau/rang)
- Description
- Category et subtype
## Architecture
Tous les DataModels héritent de `foundry.abstract.TypeDataModel` et définissent leur schéma via `defineSchema()`.
Exemple de structure :
```javascript
export default class BoLCharacterDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
return {
// Définition des champs
};
}
static LOCALIZATION_PREFIXES = ["BOL.Character"];
// Méthodes personnalisées (à ajouter)
}
```
## Types de champs utilisés
- `StringField` : Chaînes de caractères
- `NumberField` : Nombres (avec option `integer: true` pour entiers)
- `BooleanField` : Booléens
- `HTMLField` : HTML enrichi (descriptions, biographies)
- `ArrayField` : Tableaux
- `SchemaField` : Objets imbriqués
## Export
Le fichier `_module.mjs` exporte tous les DataModels :
```javascript
export { default as BoLCharacter } from "./character.mjs"
export { default as BoLEncounter } from "./encounter.mjs"
export { default as BoLHorde } from "./horde.mjs"
export { default as BoLVehicle } from "./vehicle.mjs"
export { default as BoLItem } from "./item.mjs"
export { default as BoLFeature } from "./feature.mjs"
```
## Configuration dans bol.js
Les DataModels sont enregistrés dans `CONFIG` :
```javascript
CONFIG.Actor.dataModels = {
character: models.BoLCharacter,
encounter: models.BoLEncounter,
horde: models.BoLHorde,
vehicle: models.BoLVehicle
}
CONFIG.Item.dataModels = {
item: models.BoLItem,
feature: models.BoLFeature
}
```
## Compatibilité
Les DataModels sont compatibles avec le `template.json` existant. La migration est transparente pour les données existantes.
## Prochaines étapes
1. Ajouter `prepareDerivedData()` pour les calculs automatiques
2. Migrer la logique métier depuis actor.js
3. Ajouter des validations personnalisées
4. Documenter avec JSDoc

View File

@@ -0,0 +1,6 @@
export { default as BoLCharacter } from "./character.mjs"
export { default as BoLEncounter } from "./encounter.mjs"
export { default as BoLHorde } from "./horde.mjs"
export { default as BoLVehicle } from "./vehicle.mjs"
export { default as BoLItem } from "./item.mjs"
export { default as BoLFeature } from "./feature.mjs"

192
module/models/character.mjs Normal file
View File

@@ -0,0 +1,192 @@
/**
* Data model for Character actors
*/
export default class BoLCharacterDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
return {
// Details
details: new fields.SchemaField({
biography: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
height: new fields.StringField({ initial: "" }),
age: new fields.StringField({ initial: "" }),
weight: new fields.StringField({ initial: "" }),
hair: new fields.StringField({ initial: "" }),
eyes: new fields.StringField({ initial: "" }),
signs: new fields.StringField({ initial: "" }),
size: new fields.StringField({ initial: "" }),
languages: new fields.ArrayField(new fields.StringField(), { initial: [] }),
xplog: new fields.ArrayField(new fields.ObjectField(), { initial: [] })
}),
// Combat
combat: new fields.SchemaField({
lastinit: new fields.NumberField({ ...requiredInteger, initial: 0 }),
iscritical: new fields.BooleanField({ initial: false }),
isfumble: new fields.BooleanField({ initial: false }),
islegendary: new fields.BooleanField({ initial: false })
}),
// Character type
chartype: new fields.StringField({ initial: "player" }),
villainy: new fields.BooleanField({ initial: false }),
// Bougette
bougette: new fields.SchemaField({
state: new fields.StringField({ initial: "nomoney" }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
// XP
xp: new fields.SchemaField({
key: new fields.StringField({ initial: "xp" }),
label: new fields.StringField({ initial: "BOL.traits.xp" }),
total: new fields.NumberField({ ...requiredInteger, initial: 0 }),
spent: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
// Creation
creation: new fields.SchemaField({
key: new fields.StringField({ initial: "creation" }),
label: new fields.StringField({ initial: "BOL.resources.creation" }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
// Protection
prot: new fields.SchemaField({
key: new fields.StringField({ initial: "prot" }),
label: new fields.StringField({ initial: "BOL.aptitudes.prot" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
// Attributes
attributes: new fields.SchemaField({
vigor: new fields.SchemaField({
key: new fields.StringField({ initial: "vigor" }),
label: new fields.StringField({ initial: "BOL.attributes.vigor" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
agility: new fields.SchemaField({
key: new fields.StringField({ initial: "agility" }),
label: new fields.StringField({ initial: "BOL.attributes.agility" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
mind: new fields.SchemaField({
key: new fields.StringField({ initial: "mind" }),
label: new fields.StringField({ initial: "BOL.attributes.mind" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
appeal: new fields.SchemaField({
key: new fields.StringField({ initial: "appeal" }),
label: new fields.StringField({ initial: "BOL.attributes.appeal" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
}),
// Aptitudes
aptitudes: new fields.SchemaField({
init: new fields.SchemaField({
key: new fields.StringField({ initial: "init" }),
label: new fields.StringField({ initial: "BOL.aptitudes.init" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
melee: new fields.SchemaField({
key: new fields.StringField({ initial: "melee" }),
label: new fields.StringField({ initial: "BOL.aptitudes.melee" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
ranged: new fields.SchemaField({
key: new fields.StringField({ initial: "ranged" }),
label: new fields.StringField({ initial: "BOL.aptitudes.ranged" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
def: new fields.SchemaField({
key: new fields.StringField({ initial: "def" }),
label: new fields.StringField({ initial: "BOL.aptitudes.def" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
}),
// Resources
resources: new fields.SchemaField({
hp: new fields.SchemaField({
key: new fields.StringField({ initial: "hp" }),
label: new fields.StringField({ initial: "BOL.resources.hp" }),
ismain: new fields.BooleanField({ initial: true }),
base: new fields.NumberField({ ...requiredInteger, initial: 1 }),
value: new fields.NumberField({ ...requiredInteger, initial: 1 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1 })
}),
hero: new fields.SchemaField({
key: new fields.StringField({ initial: "hero" }),
label: new fields.StringField({ initial: "BOL.resources.hero" }),
ismain: new fields.BooleanField({ initial: true }),
value: new fields.NumberField({ ...requiredInteger, initial: 5 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
}),
faith: new fields.SchemaField({
key: new fields.StringField({ initial: "faith" }),
label: new fields.StringField({ initial: "BOL.resources.faith" }),
ismain: new fields.BooleanField({ initial: true }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
power: new fields.SchemaField({
key: new fields.StringField({ initial: "power" }),
label: new fields.StringField({ initial: "BOL.resources.power" }),
ismain: new fields.BooleanField({ initial: true }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
alchemypoints: new fields.SchemaField({
key: new fields.StringField({ initial: "alchemypoints" }),
label: new fields.StringField({ initial: "BOL.resources.alchemypoints" }),
ismain: new fields.BooleanField({ initial: false }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
astrologypoints: new fields.SchemaField({
key: new fields.StringField({ initial: "astrologypoints" }),
label: new fields.StringField({ initial: "BOL.resources.astrologypoints" }),
ismain: new fields.BooleanField({ initial: false }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
})
};
}
static LOCALIZATION_PREFIXES = ["BOL.Character"];
}

173
module/models/encounter.mjs Normal file
View File

@@ -0,0 +1,173 @@
/**
* Data model for Encounter actors
*/
export default class BoLEncounterDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
return {
// Details
details: new fields.SchemaField({
biography: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
height: new fields.StringField({ initial: "" }),
age: new fields.StringField({ initial: "" }),
weight: new fields.StringField({ initial: "" }),
hair: new fields.StringField({ initial: "" }),
eyes: new fields.StringField({ initial: "" }),
signs: new fields.StringField({ initial: "" }),
size: new fields.StringField({ initial: "" }),
languages: new fields.ArrayField(new fields.StringField(), { initial: [] }),
xplog: new fields.ArrayField(new fields.ObjectField(), { initial: [] })
}),
// Combat
combat: new fields.SchemaField({
lastinit: new fields.NumberField({ ...requiredInteger, initial: 0 }),
iscritical: new fields.BooleanField({ initial: false }),
isfumble: new fields.BooleanField({ initial: false }),
islegendary: new fields.BooleanField({ initial: false })
}),
// Character type
chartype: new fields.StringField({ initial: "tough" }),
isundead: new fields.BooleanField({ initial: false }),
villainy: new fields.BooleanField({ initial: false }),
size: new fields.StringField({ initial: "" }),
environment: new fields.StringField({ initial: "" }),
// Protection
prot: new fields.SchemaField({
key: new fields.StringField({ initial: "prot" }),
label: new fields.StringField({ initial: "BOL.aptitudes.prot" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
// Attributes
attributes: new fields.SchemaField({
vigor: new fields.SchemaField({
key: new fields.StringField({ initial: "vigor" }),
label: new fields.StringField({ initial: "BOL.attributes.vigor" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
agility: new fields.SchemaField({
key: new fields.StringField({ initial: "agility" }),
label: new fields.StringField({ initial: "BOL.attributes.agility" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
mind: new fields.SchemaField({
key: new fields.StringField({ initial: "mind" }),
label: new fields.StringField({ initial: "BOL.attributes.mind" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
appeal: new fields.SchemaField({
key: new fields.StringField({ initial: "appeal" }),
label: new fields.StringField({ initial: "BOL.attributes.appeal" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
}),
// Aptitudes
aptitudes: new fields.SchemaField({
init: new fields.SchemaField({
key: new fields.StringField({ initial: "init" }),
label: new fields.StringField({ initial: "BOL.aptitudes.init" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
melee: new fields.SchemaField({
key: new fields.StringField({ initial: "melee" }),
label: new fields.StringField({ initial: "BOL.aptitudes.melee" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
ranged: new fields.SchemaField({
key: new fields.StringField({ initial: "ranged" }),
label: new fields.StringField({ initial: "BOL.aptitudes.ranged" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
def: new fields.SchemaField({
key: new fields.StringField({ initial: "def" }),
label: new fields.StringField({ initial: "BOL.aptitudes.def" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
}),
// Resources
resources: new fields.SchemaField({
hp: new fields.SchemaField({
key: new fields.StringField({ initial: "hp" }),
label: new fields.StringField({ initial: "BOL.resources.hp" }),
ismain: new fields.BooleanField({ initial: true }),
base: new fields.NumberField({ ...requiredInteger, initial: 1 }),
value: new fields.NumberField({ ...requiredInteger, initial: 1 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1 })
}),
hero: new fields.SchemaField({
key: new fields.StringField({ initial: "hero" }),
label: new fields.StringField({ initial: "BOL.resources.hero" }),
ismain: new fields.BooleanField({ initial: true }),
value: new fields.NumberField({ ...requiredInteger, initial: 5 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
}),
faith: new fields.SchemaField({
key: new fields.StringField({ initial: "faith" }),
label: new fields.StringField({ initial: "BOL.resources.faith" }),
ismain: new fields.BooleanField({ initial: true }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
power: new fields.SchemaField({
key: new fields.StringField({ initial: "power" }),
label: new fields.StringField({ initial: "BOL.resources.power" }),
ismain: new fields.BooleanField({ initial: true }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
alchemypoints: new fields.SchemaField({
key: new fields.StringField({ initial: "alchemypoints" }),
label: new fields.StringField({ initial: "BOL.resources.alchemypoints" }),
ismain: new fields.BooleanField({ initial: false }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
astrologypoints: new fields.SchemaField({
key: new fields.StringField({ initial: "astrologypoints" }),
label: new fields.StringField({ initial: "BOL.resources.astrologypoints" }),
ismain: new fields.BooleanField({ initial: false }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
})
};
}
static LOCALIZATION_PREFIXES = ["BOL.Encounter"];
}

55
module/models/feature.mjs Normal file
View File

@@ -0,0 +1,55 @@
/**
* Data model for Feature items
*/
export default class BoLFeatureDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
const nullableNumber = { required: false, nullable: true, initial: null };
return {
// Base fields
category: new fields.StringField({ initial: "" }),
subtype: new fields.StringField({ initial: "default" }),
description: new fields.HTMLField({ initial: "" }),
properties: new fields.SchemaField({
// Career
sorcerer: new fields.BooleanField({ initial: false }),
alchemist: new fields.BooleanField({ initial: false }),
priest: new fields.BooleanField({ initial: false }),
astrologer: new fields.BooleanField({ initial: false }),
// Boon
isbonusdice: new fields.BooleanField({ initial: false }),
// Flaw
ismalusdice: new fields.BooleanField({ initial: false }),
// Fight option
fightoptiontype: new fields.StringField({ initial: "" }),
activated: new fields.BooleanField({ initial: false }),
isspecial: new fields.BooleanField({ initial: false }),
// Effect (boleffect)
identifier: new fields.StringField({ initial: "" }),
modifier: new fields.StringField({ initial: "" }),
// Horoscope
horoscopeanswer: new fields.StringField({ initial: "" }),
rank: new fields.NumberField({ ...nullableNumber }),
// XP log
xptype: new fields.StringField({ initial: "" }),
xpdate: new fields.StringField({ initial: "" }),
xpname: new fields.StringField({ initial: "" }),
xpcost: new fields.NumberField({ ...nullableNumber }),
xpvalue: new fields.NumberField({ ...nullableNumber }),
}),
// Feature-specific fields
rank: new fields.NumberField({ ...requiredInteger, initial: 0 })
};
}
static LOCALIZATION_PREFIXES = ["BOL.Feature"];
}

174
module/models/horde.mjs Normal file
View File

@@ -0,0 +1,174 @@
/**
* Data model for Horde actors
*/
export default class BoLHordeDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
return {
// Details
details: new fields.SchemaField({
biography: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
height: new fields.StringField({ initial: "" }),
age: new fields.StringField({ initial: "" }),
weight: new fields.StringField({ initial: "" }),
hair: new fields.StringField({ initial: "" }),
eyes: new fields.StringField({ initial: "" }),
signs: new fields.StringField({ initial: "" }),
size: new fields.StringField({ initial: "" }),
languages: new fields.ArrayField(new fields.StringField(), { initial: [] }),
xplog: new fields.ArrayField(new fields.ObjectField(), { initial: [] })
}),
// Combat
combat: new fields.SchemaField({
lastinit: new fields.NumberField({ ...requiredInteger, initial: 0 }),
iscritical: new fields.BooleanField({ initial: false }),
isfumble: new fields.BooleanField({ initial: false }),
islegendary: new fields.BooleanField({ initial: false })
}),
// Character type
chartype: new fields.StringField({ initial: "horde" }),
villainy: new fields.BooleanField({ initial: false }),
hordesize: new fields.NumberField({ ...requiredInteger, initial: 1 }),
hordebasehp: new fields.NumberField({ ...requiredInteger, initial: 1 }),
hasdamagerule: new fields.BooleanField({ initial: false }),
damagerule: new fields.StringField({ initial: "none" }),
// Protection
prot: new fields.SchemaField({
key: new fields.StringField({ initial: "prot" }),
label: new fields.StringField({ initial: "BOL.aptitudes.prot" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
// Attributes
attributes: new fields.SchemaField({
vigor: new fields.SchemaField({
key: new fields.StringField({ initial: "vigor" }),
label: new fields.StringField({ initial: "BOL.attributes.vigor" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
agility: new fields.SchemaField({
key: new fields.StringField({ initial: "agility" }),
label: new fields.StringField({ initial: "BOL.attributes.agility" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
mind: new fields.SchemaField({
key: new fields.StringField({ initial: "mind" }),
label: new fields.StringField({ initial: "BOL.attributes.mind" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: -1 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
appeal: new fields.SchemaField({
key: new fields.StringField({ initial: "appeal" }),
label: new fields.StringField({ initial: "BOL.attributes.appeal" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
}),
// Aptitudes
aptitudes: new fields.SchemaField({
init: new fields.SchemaField({
key: new fields.StringField({ initial: "init" }),
label: new fields.StringField({ initial: "BOL.aptitudes.init" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
melee: new fields.SchemaField({
key: new fields.StringField({ initial: "melee" }),
label: new fields.StringField({ initial: "BOL.aptitudes.melee" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
ranged: new fields.SchemaField({
key: new fields.StringField({ initial: "ranged" }),
label: new fields.StringField({ initial: "BOL.aptitudes.ranged" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
def: new fields.SchemaField({
key: new fields.StringField({ initial: "def" }),
label: new fields.StringField({ initial: "BOL.aptitudes.def" }),
base: new fields.NumberField({ ...requiredInteger, initial: 0 }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
}),
// Resources
resources: new fields.SchemaField({
hp: new fields.SchemaField({
key: new fields.StringField({ initial: "hp" }),
label: new fields.StringField({ initial: "BOL.resources.hp" }),
ismain: new fields.BooleanField({ initial: true }),
base: new fields.NumberField({ ...requiredInteger, initial: 1 }),
value: new fields.NumberField({ ...requiredInteger, initial: 1 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 1 })
}),
hero: new fields.SchemaField({
key: new fields.StringField({ initial: "hero" }),
label: new fields.StringField({ initial: "BOL.resources.hero" }),
ismain: new fields.BooleanField({ initial: true }),
value: new fields.NumberField({ ...requiredInteger, initial: 5 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
}),
faith: new fields.SchemaField({
key: new fields.StringField({ initial: "faith" }),
label: new fields.StringField({ initial: "BOL.resources.faith" }),
ismain: new fields.BooleanField({ initial: true }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
power: new fields.SchemaField({
key: new fields.StringField({ initial: "power" }),
label: new fields.StringField({ initial: "BOL.resources.power" }),
ismain: new fields.BooleanField({ initial: true }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
alchemypoints: new fields.SchemaField({
key: new fields.StringField({ initial: "alchemypoints" }),
label: new fields.StringField({ initial: "BOL.resources.alchemypoints" }),
ismain: new fields.BooleanField({ initial: false }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
}),
astrologypoints: new fields.SchemaField({
key: new fields.StringField({ initial: "astrologypoints" }),
label: new fields.StringField({ initial: "BOL.resources.astrologypoints" }),
ismain: new fields.BooleanField({ initial: false }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
bonus: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 0 })
})
})
};
}
static LOCALIZATION_PREFIXES = ["BOL.Horde"];
}

113
module/models/item.mjs Normal file
View File

@@ -0,0 +1,113 @@
/**
* Data model for Item items
*/
export default class BoLItemDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
const nullableNumber = { required: false, nullable: true, initial: null };
return {
// Base fields
category: new fields.StringField({ initial: "" }),
subtype: new fields.StringField({ initial: "default" }),
description: new fields.HTMLField({ initial: "" }),
properties: new fields.SchemaField({
// Base flags
ranged: new fields.BooleanField({ initial: false }),
melee: new fields.BooleanField({ initial: false }),
spell: new fields.BooleanField({ initial: false }),
protection: new fields.BooleanField({ initial: false }),
weapon: new fields.BooleanField({ initial: false }),
armor: new fields.BooleanField({ initial: false }),
helm: new fields.BooleanField({ initial: false }),
shield: new fields.BooleanField({ initial: false }),
equipable: new fields.BooleanField({ initial: false }),
consumable: new fields.BooleanField({ initial: false }),
magical: new fields.BooleanField({ initial: false }),
"2H": new fields.BooleanField({ initial: false }),
reloadable: new fields.BooleanField({ initial: false }),
bow: new fields.BooleanField({ initial: false }),
crossbow: new fields.BooleanField({ initial: false }),
throwing: new fields.BooleanField({ initial: false }),
// Equipment
stackable: new fields.BooleanField({ initial: false }),
stacksize: new fields.NumberField({ ...nullableNumber }),
slot: new fields.StringField({ initial: "-" }),
// Weapon flags
natural: new fields.BooleanField({ initial: false }),
concealable: new fields.BooleanField({ initial: false }),
ignoreshield: new fields.BooleanField({ initial: false }),
attackBonusDice: new fields.BooleanField({ initial: false }),
attackMalusDice: new fields.BooleanField({ initial: false }),
onlymodifier: new fields.BooleanField({ initial: false }),
bashing: new fields.BooleanField({ initial: false }),
throwable: new fields.BooleanField({ initial: false }),
damageReroll1: new fields.BooleanField({ initial: false }),
// Weapon stats
attackAttribute: new fields.StringField({ initial: "vigor" }),
attackAptitude: new fields.StringField({ initial: "melee" }),
attackModifiers: new fields.NumberField({ ...nullableNumber }),
weaponSize: new fields.StringField({ initial: "unarmed" }),
damage: new fields.StringField({ initial: "0" }),
damageAttribute: new fields.StringField({ initial: "" }),
damageModifiers: new fields.NumberField({ ...nullableNumber }),
damageMultiplier: new fields.StringField({ initial: "1" }),
range: new fields.NumberField({ ...nullableNumber }),
reload: new fields.NumberField({ ...nullableNumber }),
// Protection
armorQuality: new fields.StringField({ initial: "" }),
soak: new fields.SchemaField({
formula: new fields.StringField({ initial: "" }),
value: new fields.NumberField({ initial: 0, nullable: true }),
modifier: new fields.NumberField({ initial: 0, nullable: true }),
}),
blocking: new fields.SchemaField({
malus: new fields.NumberField({ initial: 0, nullable: true }),
blocking1: new fields.BooleanField({ initial: false }),
blockingAll: new fields.BooleanField({ initial: false }),
}),
modifiers: new fields.SchemaField({
init: new fields.NumberField({ initial: 0, nullable: true }),
agility: new fields.NumberField({ initial: 0, nullable: true }),
powercost: new fields.NumberField({ initial: 0, nullable: true }),
social: new fields.BooleanField({ initial: false }),
}),
// Spell
circle: new fields.NumberField({ initial: 0, nullable: true }),
difficulty: new fields.StringField({ initial: "" }),
ppcost: new fields.NumberField({ initial: 0, nullable: true }),
duration: new fields.StringField({ initial: "" }),
nbmandatoryconditions: new fields.NumberField({ initial: 0, nullable: true }),
mandatoryconditions: new fields.ArrayField(new fields.StringField()),
optionnalconditions: new fields.ArrayField(new fields.StringField()),
// Alchemy
alchemytype: new fields.StringField({ initial: "" }),
pccost: new fields.NumberField({ initial: 0, nullable: true }),
pccurrent: new fields.NumberField({ initial: 0, nullable: true }),
// Vehicle weapon
isfiredamage: new fields.BooleanField({ initial: false }),
ishulldamage: new fields.BooleanField({ initial: false }),
iscrewdamage: new fields.BooleanField({ initial: false }),
isboarding: new fields.BooleanField({ initial: false }),
isspur: new fields.BooleanField({ initial: false }),
isbreakrow: new fields.BooleanField({ initial: false }),
}),
// Equipment fields
quantity: new fields.NumberField({ ...requiredInteger, initial: 1 }),
weight: new fields.NumberField({ initial: 0 }),
price: new fields.NumberField({ initial: 0 }),
worn: new fields.BooleanField({ initial: false })
};
}
static LOCALIZATION_PREFIXES = ["BOL.Item"];
}

55
module/models/vehicle.mjs Normal file
View File

@@ -0,0 +1,55 @@
/**
* Data model for Vehicle actors
*/
export default class BoLVehicleDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
const requiredInteger = { required: true, nullable: false, integer: true };
return {
vehicletype: new fields.StringField({ initial: "boat" }),
attributes: new fields.SchemaField({
hull: new fields.SchemaField({
key: new fields.StringField({ initial: "hull" }),
label: new fields.StringField({ initial: "BOL.attributes.hull" }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
}),
crew: new fields.SchemaField({
key: new fields.StringField({ initial: "crew" }),
label: new fields.StringField({ initial: "BOL.attributes.crew" }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
}),
resources: new fields.SchemaField({
key: new fields.StringField({ initial: "resources" }),
label: new fields.StringField({ initial: "BOL.attributes.resources" }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
})
}),
row: new fields.SchemaField({
key: new fields.StringField({ initial: "row" }),
label: new fields.StringField({ initial: "BOL.attributes.row" }),
value: new fields.NumberField({ ...requiredInteger, initial: 0 }),
min: new fields.NumberField({ ...requiredInteger, initial: 0 }),
max: new fields.NumberField({ ...requiredInteger, initial: 5 })
}),
spur: new fields.SchemaField({
value: new fields.StringField({ initial: "" })
}),
status: new fields.SchemaField({}),
description: new fields.HTMLField({ initial: "" })
};
}
static LOCALIZATION_PREFIXES = ["BOL.Vehicle"];
}

View File

@@ -12,8 +12,8 @@ export class BoLTokenHud {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async removeExtensionHud(app, html, tokenId) { static async removeExtensionHud(app, html, tokenId) {
html.find('.control-icon.bol-roll').remove() $(html).find('.control-icon.bol-roll').remove()
html.find('.control-icon.bol-action').remove() $(html).find('.control-icon.bol-action').remove()
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@@ -25,7 +25,7 @@ export class BoLTokenHud {
const hudData = { actor: actor, actionsList: actor.buildListeActions(), rollsList: actor.buildRollList() } const hudData = { actor: actor, actionsList: actor.buildListeActions(), rollsList: actor.buildRollList() }
const controlIconActions = html.find('.control-icon[data-action=combat]'); const controlIconActions = $(html).find('.control-icon[data-action=combat]');
// initiative // initiative
await BoLTokenHud._configureSubMenu(controlIconActions, 'systems/bol/templates/token/hud-actor-actions.hbs', hudData, await BoLTokenHud._configureSubMenu(controlIconActions, 'systems/bol/templates/token/hud-actor-actions.hbs', hudData,
(event) => { (event) => {
@@ -43,7 +43,7 @@ export class BoLTokenHud {
} }
}) })
const controlIconTarget = html.find('.control-icon[data-action=target]'); const controlIconTarget = $(html).find('.control-icon[data-action=target]');
// att+apt+career // att+apt+career
await BoLTokenHud._configureSubMenu(controlIconTarget, 'systems/bol/templates/token/hud-actor-rolls.hbs', hudData, await BoLTokenHud._configureSubMenu(controlIconTarget, 'systems/bol/templates/token/hud-actor-rolls.hbs', hudData,
(event) => { (event) => {
@@ -59,7 +59,7 @@ export class BoLTokenHud {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async addTokenHudExtensions(app, html, tokenId) { static async addTokenHudExtensions(app, html, tokenId) {
const controlIconCombat = html.find('.control-icon[data-action=combat]') const controlIconCombat = $(html).find('.control-icon[data-action=combat]')
if (controlIconCombat.length > 0) { if (controlIconCombat.length > 0) {
BoLTokenHud.addExtensionHud(app, html, tokenId); BoLTokenHud.addExtensionHud(app, html, tokenId);
} }
@@ -67,7 +67,7 @@ export class BoLTokenHud {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async _configureSubMenu(insertionPoint, template, hudData, onMenuItem) { static async _configureSubMenu(insertionPoint, template, hudData, onMenuItem) {
const hud = $(await renderTemplate(template, hudData)) const hud = $(await foundry.applications.handlebars.renderTemplate(template, hudData))
const list = hud.find('div.bol-hud-list') const list = hud.find('div.bol-hud-list')
BoLTokenHud._toggleHudListActive(hud, list); BoLTokenHud._toggleHudListActive(hud, list);

View File

@@ -331,7 +331,7 @@ export class BoLCalendar extends Application {
calendrierData.jourMoisOptions = RdDCalendrier.buildJoursMois(); calendrierData.jourMoisOptions = RdDCalendrier.buildJoursMois();
calendrierData.heuresOptions = [0, 1]; calendrierData.heuresOptions = [0, 1];
calendrierData.minutesOptions = Array(RDD_MINUTES_PAR_HEURES).fill().map((item, index) => 0 + index); calendrierData.minutesOptions = Array(RDD_MINUTES_PAR_HEURES).fill().map((item, index) => 0 + index);
let html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/calendar-editor-template.html', calendrierData); let html = await foundry.applications.handlebars.renderTemplate('systems/foundryvtt-reve-de-dragon/templates/calendar-editor-template.html', calendrierData);
this.editeur = new RdDCalendrierEditeur(html, this, calendrierData) this.editeur = new RdDCalendrierEditeur(html, this, calendrierData)
} }
this.editeur.updateData(calendrierData); this.editeur.updateData(calendrierData);

View File

@@ -237,6 +237,11 @@ export class BoLUtility {
if (chatData.img.includes("/blank.png")) { if (chatData.img.includes("/blank.png")) {
chatData.img = null; chatData.img = null;
} }
// For old-format weapon items lacking stat fields, apply defaults so the chat card can display them
if (chatData.system?.properties?.weapon && !chatData.system.properties.damage) {
const defaults = game.bol.config.defaultNaturalWeapon?.properties ?? {};
chatData.system.properties = Object.assign(foundry.utils.duplicate(defaults), chatData.system.properties);
}
// JSON object for easy creation // JSON object for easy creation
chatData.jsondata = JSON.stringify( chatData.jsondata = JSON.stringify(
{ {
@@ -244,7 +249,7 @@ export class BoLUtility {
payload: chatData, payload: chatData,
}); });
renderTemplate('systems/bol/templates/item/post-item.hbs', chatData).then(html => { foundry.applications.handlebars.renderTemplate('systems/bol/templates/item/post-item.hbs', chatData).then(html => {
let chatOptions = BoLUtility.chatDataSetup(html); let chatOptions = BoLUtility.chatDataSetup(html);
ChatMessage.create(chatOptions) ChatMessage.create(chatOptions)
}); });
@@ -344,7 +349,7 @@ export class BoLUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async chatMessageHandler(message, html, data) { static async chatMessageHandler(message, html, data) {
const chatCard = html.find('.flavor-text') const chatCard = $(html).find('.flavor-text')
if (chatCard.length > 0) { if (chatCard.length > 0) {
// If the user is the message author or the actor owner, proceed // If the user is the message author or the actor owner, proceed
const actor = game.actors.get(data.message.speaker.actor) const actor = game.actors.get(data.message.speaker.actor)
@@ -381,6 +386,8 @@ export class BoLUtility {
/* -------------------------------------------- */ /* -------------------------------------------- */
static async chatListeners(html) { static async chatListeners(html) {
html = $(html);
// Damage handling // Damage handling
html.on("click", '.chat-damage-apply', event => { html.on("click", '.chat-damage-apply', event => {
let rollData = BoLUtility.getRollDataFromMessage(event) let rollData = BoLUtility.getRollDataFromMessage(event)
@@ -427,7 +434,7 @@ export class BoLUtility {
html.on("click", '.damage-handling', event => { html.on("click", '.damage-handling', event => {
event.preventDefault() event.preventDefault()
let attr = event.currentTarget.attributes['data-attack-id'] let attr = event.currentTarget.attributes['data-attack-id']
if ( !attr) { if (!attr) {
ui.notifications.warn("Impossible de trouver l'attaque correspondante, erreur de suivi de combat.") ui.notifications.warn("Impossible de trouver l'attaque correspondante, erreur de suivi de combat.")
return return
} }
@@ -480,13 +487,17 @@ export class BoLUtility {
if (defenseMode == 'damage-with-armor') { if (defenseMode == 'damage-with-armor') {
let armorFormula = defender.getArmorFormula() let armorFormula = defender.getArmorFormula()
rollData.rollArmor = new Roll(armorFormula) if (armorFormula === "0") {
await rollData.rollArmor.roll() rollData.armorProtect = 0
let msg = await rollData.rollArmor.toMessage({ flavor: "BOL.chat.armorRoll : " + armorFormula }); } else {
if ( game.dice3d) { // wait animation end when DsN is there rollData.rollArmor = new Roll(armorFormula)
await game.dice3d.waitFor3DAnimationByMessageID(msg.id); await rollData.rollArmor.roll()
let msg = await rollData.rollArmor.toMessage({ flavor: game.i18n.localize("BOL.chat.armorRoll") + " : " + armorFormula })
if (game.dice3d && msg) {
await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
}
rollData.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total
} }
rollData.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total
rollData.finalDamage = rollData.damageTotal - rollData.armorProtect rollData.finalDamage = rollData.damageTotal - rollData.armorProtect
rollData.finalDamage = (rollData.finalDamage < 0) ? 0 : rollData.finalDamage rollData.finalDamage = (rollData.finalDamage < 0) ? 0 : rollData.finalDamage
await defender.sufferDamage(rollData.finalDamage) await defender.sufferDamage(rollData.finalDamage)
@@ -498,9 +509,17 @@ export class BoLUtility {
} }
if (defenseMode == 'hero-reduce-damage') { if (defenseMode == 'hero-reduce-damage') {
let armorFormula = defender.getArmorFormula() let armorFormula = defender.getArmorFormula()
rollData.rollArmor = new Roll(armorFormula) if (armorFormula === "0") {
await rollData.rollArmor.roll() rollData.armorProtect = 0
rollData.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total } else {
rollData.rollArmor = new Roll(armorFormula)
await rollData.rollArmor.roll()
let msg = await rollData.rollArmor.toMessage({ flavor: game.i18n.localize("BOL.chat.armorRoll") + " : " + armorFormula })
if (game.dice3d && msg) {
await game.dice3d.waitFor3DAnimationByMessageID(msg.id)
}
rollData.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total
}
rollData.rollHero = new Roll("1d6") rollData.rollHero = new Roll("1d6")
await rollData.rollHero.roll() await rollData.rollHero.roll()
rollData.finalDamage = rollData.damageTotal - rollData.rollHero.total - rollData.armorProtect rollData.finalDamage = rollData.damageTotal - rollData.rollHero.total - rollData.armorProtect
@@ -535,13 +554,13 @@ export class BoLUtility {
ChatMessage.create({ ChatMessage.create({
alias: defender.name, alias: defender.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(defender.name), whisper: BoLUtility.getWhisperRecipientsAndGMs(defender.name),
content: await renderTemplate('systems/bol/templates/chat/rolls/defense-result-card.hbs', damageResults) content: await foundry.applications.handlebars.foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/rolls/defense-result-card.hbs', damageResults)
}) })
console.log("Defender data : ", defenderUser) console.log("Defender data : ", defenderUser)
ChatMessage.create({ ChatMessage.create({
alias: defender.name, alias: defender.name,
whisper: BoLUtility.getOtherWhisperRecipients(defenderUser?.name), whisper: BoLUtility.getOtherWhisperRecipients(defenderUser?.name),
content: await renderTemplate('systems/bol/templates/chat/rolls/defense-summary-card.hbs', damageResults) content: await foundry.applications.handlebars.foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/rolls/defense-summary-card.hbs', damageResults)
}) })
} }
} }
@@ -628,11 +647,11 @@ export class BoLUtility {
let msg = await ChatMessage.create({ let msg = await ChatMessage.create({
alias: defender.name, alias: defender.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(defender.name), whisper: BoLUtility.getWhisperRecipientsAndGMs(defender.name),
content: await renderTemplate('systems/bol/templates/chat/rolls/defense-request-card.hbs', { content: await foundry.applications.handlebars.foundry.applications.handlebars.renderTemplate('systems/bol/templates/chat/rolls/defense-request-card.hbs', {
attackId: rollData.id, attackId: rollData.id,
attacker: rollData.attacker, attacker: rollData.attacker,
defender: defender, defender: defender,
defenderHeroPoints:defender.getHeroPoints(), defenderHeroPoints: defender.getHeroPoints(),
defenderWeapons: defenderWeapons, defenderWeapons: defenderWeapons,
damageTotal: rollData.damageTotal, damageTotal: rollData.damageTotal,
damagesIgnoresArmor: rollData.damagesIgnoresArmor, damagesIgnoresArmor: rollData.damagesIgnoresArmor,

View File

@@ -188,19 +188,25 @@ BOL.rangeModifiers = {
"-8": "BOL.dialog.utmost" "-8": "BOL.dialog.utmost"
} }
BOL.difficultyModifiers = { BOL.difficultyModifiers = [
"4": "BOL.dialog.soeasy", { value: "-12", label: "BOL.dialog.divine" },
"2": "BOL.dialog.veryeasy", { value: "-11", label: "BOL.dialog.mythic11" },
"1": "BOL.dialog.easy", { value: "-10", label: "BOL.dialog.mythic" },
"0": "BOL.dialog.moderate", { value: "-9", label: "BOL.dialog.heroic9" },
"-1": "BOL.dialog.hard", { value: "-8", label: "BOL.dialog.heroic" },
"-2": "BOL.dialog.tough", { value: "-7", label: "BOL.dialog.formidable7" },
"-4": "BOL.dialog.demanding", { value: "-6", label: "BOL.dialog.formidable" },
"-6": "BOL.dialog.formidable", { value: "-5", label: "BOL.dialog.demanding5" },
"-8": "BOL.dialog.heroic", { value: "-4", label: "BOL.dialog.demanding" },
"-10": "BOL.dialog.mythic", { value: "-3", label: "BOL.dialog.tough3" },
"-12": "BOL.dialog.divine" { value: "-2", label: "BOL.dialog.tough" },
} { value: "-1", label: "BOL.dialog.hard" },
{ value: "0", label: "BOL.dialog.moderate" },
{ value: "1", label: "BOL.dialog.easy" },
{ value: "2", label: "BOL.dialog.veryeasy" },
{ value: "3", label: "BOL.dialog.soeasy3" },
{ value: "4", label: "BOL.dialog.soeasy" },
]
BOL.alchemyModifiers = { BOL.alchemyModifiers = {
"2": "BOL.dialog.veryeasy", "2": "BOL.dialog.veryeasy",

View File

@@ -15,7 +15,7 @@ export default function registerHooks() {
})) }))
Hooks.on('renderChatLog', (log, html, data) => BoLUtility.chatListeners(html)) Hooks.on('renderChatLog', (log, html, data) => BoLUtility.chatListeners(html))
Hooks.on('renderChatMessage', (message, html, data) => BoLUtility.chatMessageHandler(message, html, data)) Hooks.on('renderChatMessageHTML', (message, html, data) => BoLUtility.chatMessageHandler(message, html, data))
/** /**
* Create a macro when dropping an entity on the hotbar * Create a macro when dropping an entity on the hotbar
@@ -73,7 +73,7 @@ export default function registerHooks() {
button.addEventListener('click', () => { button.addEventListener('click', () => {
game.bol.charSummary.render(true) game.bol.charSummary.render(true)
}) })
html.find('.header-actions').after(button) $(html).find('.header-actions').after(button)
} }
}) })

View File

@@ -52,6 +52,7 @@ export const preloadHandlebarsTemplates = async function () {
"systems/bol/templates/chat/rolls/alchemy-roll-card.hbs", "systems/bol/templates/chat/rolls/alchemy-roll-card.hbs",
"systems/bol/templates/chat/rolls/selected-horoscope-roll-card.hbs", "systems/bol/templates/chat/rolls/selected-horoscope-roll-card.hbs",
"systems/bol/templates/chat/rolls/horoscope-roll-card.hbs", "systems/bol/templates/chat/rolls/horoscope-roll-card.hbs",
"systems/bol/templates/chat/chat-welcome.hbs",
"systems/bol/templates/dialogs/aptitude-roll-part.hbs", "systems/bol/templates/dialogs/aptitude-roll-part.hbs",
"systems/bol/templates/dialogs/attribute-roll-part.hbs", "systems/bol/templates/dialogs/attribute-roll-part.hbs",
"systems/bol/templates/dialogs/mod-roll-part.hbs", "systems/bol/templates/dialogs/mod-roll-part.hbs",
@@ -62,9 +63,10 @@ export const preloadHandlebarsTemplates = async function () {
"systems/bol/templates/dialogs/flaws-roll-part.hbs", "systems/bol/templates/dialogs/flaws-roll-part.hbs",
"systems/bol/templates/dialogs/total-roll-part.hbs", "systems/bol/templates/dialogs/total-roll-part.hbs",
"systems/bol/templates/dialogs/fightoptions-roll-part.hbs", "systems/bol/templates/dialogs/fightoptions-roll-part.hbs",
"systems/bol/templates/dialogs/horoscope-roll-part.hbs" "systems/bol/templates/dialogs/horoscope-roll-part.hbs",
"systems/bol/templates/apps/character-summary-template.html"
]; ];
// Load the template parts // Load the template parts
return loadTemplates(templatePaths); return foundry.applications.handlebars.loadTemplates(templatePaths);
}; };

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000606 MANIFEST-001076

View File

@@ -1,8 +1,8 @@
2024/11/26-19:35:41.682372 7f20f0ff96c0 Recovering log #604 2026/02/28-22:59:53.852685 7f56e3fff6c0 Recovering log #1074
2024/11/26-19:35:41.693713 7f20f0ff96c0 Delete type=3 #602 2026/02/28-22:59:53.863099 7f56e3fff6c0 Delete type=3 #1072
2024/11/26-19:35:41.693812 7f20f0ff96c0 Delete type=0 #604 2026/02/28-22:59:53.863210 7f56e3fff6c0 Delete type=0 #1074
2024/11/26-20:03:53.562225 7f20ef7ff6c0 Level-0 table #609: started 2026/03/01-01:08:46.366409 7f54e37ef6c0 Level-0 table #1079: started
2024/11/26-20:03:53.562310 7f20ef7ff6c0 Level-0 table #609: 0 bytes OK 2026/03/01-01:08:46.366440 7f54e37ef6c0 Level-0 table #1079: 0 bytes OK
2024/11/26-20:03:53.568954 7f20ef7ff6c0 Delete type=0 #607 2026/03/01-01:08:46.372420 7f54e37ef6c0 Delete type=0 #1077
2024/11/26-20:03:53.576999 7f20ef7ff6c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.391669 7f54e37ef6c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)
2024/11/26-20:03:53.577053 7f20ef7ff6c0 Manual compaction at level-1 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.391720 7f54e37ef6c0 Manual compaction at level-1 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/11/25-23:05:12.650755 7f20f1ffb6c0 Recovering log #600 2026/02/28-17:23:52.883622 7f56f93fe6c0 Recovering log #1070
2024/11/25-23:05:12.661690 7f20f1ffb6c0 Delete type=3 #598 2026/02/28-17:23:52.893895 7f56f93fe6c0 Delete type=3 #1068
2024/11/25-23:05:12.661789 7f20f1ffb6c0 Delete type=0 #600 2026/02/28-17:23:52.893967 7f56f93fe6c0 Delete type=0 #1070
2024/11/25-23:09:33.899483 7f20ef7ff6c0 Level-0 table #605: started 2026/02/28-22:59:43.750682 7f54e37ef6c0 Level-0 table #1075: started
2024/11/25-23:09:33.899549 7f20ef7ff6c0 Level-0 table #605: 0 bytes OK 2026/02/28-22:59:43.750749 7f54e37ef6c0 Level-0 table #1075: 0 bytes OK
2024/11/25-23:09:33.906044 7f20ef7ff6c0 Delete type=0 #603 2026/02/28-22:59:43.758241 7f54e37ef6c0 Delete type=0 #1073
2024/11/25-23:09:33.921223 7f20ef7ff6c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.758440 7f54e37ef6c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)
2024/11/25-23:09:33.921337 7f20ef7ff6c0 Manual compaction at level-1 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.758473 7f54e37ef6c0 Manual compaction at level-1 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
packs/armors/000679.ldb Normal file

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000512 MANIFEST-000981

View File

@@ -1,8 +1,8 @@
2024/11/26-19:35:41.649466 7f20f17fa6c0 Recovering log #510 2026/02/28-22:59:53.826224 7f56f8bfd6c0 Recovering log #979
2024/11/26-19:35:41.660924 7f20f17fa6c0 Delete type=3 #508 2026/02/28-22:59:53.836758 7f56f8bfd6c0 Delete type=3 #977
2024/11/26-19:35:41.661016 7f20f17fa6c0 Delete type=0 #510 2026/02/28-22:59:53.836823 7f56f8bfd6c0 Delete type=0 #979
2024/11/26-20:03:53.569223 7f20ef7ff6c0 Level-0 table #515: started 2026/03/01-01:08:46.372471 7f54e37ef6c0 Level-0 table #984: started
2024/11/26-20:03:53.569292 7f20ef7ff6c0 Level-0 table #515: 0 bytes OK 2026/03/01-01:08:46.372486 7f54e37ef6c0 Level-0 table #984: 0 bytes OK
2024/11/26-20:03:53.576679 7f20ef7ff6c0 Delete type=0 #513 2026/03/01-01:08:46.378495 7f54e37ef6c0 Delete type=0 #982
2024/11/26-20:03:53.577018 7f20ef7ff6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.391687 7f54e37ef6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
2024/11/26-20:03:53.577065 7f20ef7ff6c0 Manual compaction at level-1 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.391731 7f54e37ef6c0 Manual compaction at level-1 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/11/25-23:05:12.616998 7f20f07f86c0 Recovering log #506 2026/02/28-17:23:52.858522 7f56e3fff6c0 Recovering log #975
2024/11/25-23:05:12.628857 7f20f07f86c0 Delete type=3 #504 2026/02/28-17:23:52.869495 7f56e3fff6c0 Delete type=3 #973
2024/11/25-23:05:12.629080 7f20f07f86c0 Delete type=0 #506 2026/02/28-17:23:52.869552 7f56e3fff6c0 Delete type=0 #975
2024/11/25-23:09:33.892926 7f20ef7ff6c0 Level-0 table #511: started 2026/02/28-22:59:43.737596 7f54e37ef6c0 Level-0 table #980: started
2024/11/25-23:09:33.892966 7f20ef7ff6c0 Level-0 table #511: 0 bytes OK 2026/02/28-22:59:43.737635 7f54e37ef6c0 Level-0 table #980: 0 bytes OK
2024/11/25-23:09:33.899242 7f20ef7ff6c0 Delete type=0 #509 2026/02/28-22:59:43.743801 7f54e37ef6c0 Delete type=0 #978
2024/11/25-23:09:33.921183 7f20ef7ff6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.758414 7f54e37ef6c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
2024/11/25-23:09:33.921311 7f20ef7ff6c0 Manual compaction at level-1 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.758457 7f54e37ef6c0 Manual compaction at level-1 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
packs/boons/000843.ldb Normal file

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000605 MANIFEST-001075

View File

@@ -1,8 +1,8 @@
2024/11/26-19:35:41.515241 7f20f17fa6c0 Recovering log #603 2026/02/28-22:59:53.718001 7f56f8bfd6c0 Recovering log #1073
2024/11/26-19:35:41.527032 7f20f17fa6c0 Delete type=3 #601 2026/02/28-22:59:53.729345 7f56f8bfd6c0 Delete type=3 #1071
2024/11/26-19:35:41.527200 7f20f17fa6c0 Delete type=0 #603 2026/02/28-22:59:53.729412 7f56f8bfd6c0 Delete type=0 #1073
2024/11/26-20:03:53.492768 7f20ef7ff6c0 Level-0 table #608: started 2026/03/01-01:08:46.321084 7f54e37ef6c0 Level-0 table #1078: started
2024/11/26-20:03:53.492848 7f20ef7ff6c0 Level-0 table #608: 0 bytes OK 2026/03/01-01:08:46.321111 7f54e37ef6c0 Level-0 table #1078: 0 bytes OK
2024/11/26-20:03:53.499433 7f20ef7ff6c0 Delete type=0 #606 2026/03/01-01:08:46.327221 7f54e37ef6c0 Delete type=0 #1076
2024/11/26-20:03:53.521097 7f20ef7ff6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.339937 7f54e37ef6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
2024/11/26-20:03:53.521187 7f20ef7ff6c0 Manual compaction at level-1 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.339972 7f54e37ef6c0 Manual compaction at level-1 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/11/25-23:05:12.491852 7f20f07f86c0 Recovering log #599 2026/02/28-17:23:52.757933 7f56f9bff6c0 Recovering log #1069
2024/11/25-23:05:12.504348 7f20f07f86c0 Delete type=3 #597 2026/02/28-17:23:52.767692 7f56f9bff6c0 Delete type=3 #1067
2024/11/25-23:05:12.504565 7f20f07f86c0 Delete type=0 #599 2026/02/28-17:23:52.767761 7f56f9bff6c0 Delete type=0 #1069
2024/11/25-23:09:33.836227 7f20ef7ff6c0 Level-0 table #604: started 2026/02/28-22:59:43.697108 7f54e37ef6c0 Level-0 table #1074: started
2024/11/25-23:09:33.836305 7f20ef7ff6c0 Level-0 table #604: 0 bytes OK 2026/02/28-22:59:43.697152 7f54e37ef6c0 Level-0 table #1074: 0 bytes OK
2024/11/25-23:09:33.843100 7f20ef7ff6c0 Delete type=0 #602 2026/02/28-22:59:43.704224 7f54e37ef6c0 Delete type=0 #1072
2024/11/25-23:09:33.864669 7f20ef7ff6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.704461 7f54e37ef6c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
2024/11/25-23:09:33.864738 7f20ef7ff6c0 Manual compaction at level-1 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.704511 7f54e37ef6c0 Manual compaction at level-1 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)

Binary file not shown.

BIN
packs/boons/MANIFEST-001075 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000605 MANIFEST-001074

View File

@@ -1,8 +1,8 @@
2024/11/26-19:35:41.531914 7f20f07f86c0 Recovering log #603 2026/02/28-22:59:53.732583 7f56e3fff6c0 Recovering log #1072
2024/11/26-19:35:41.543882 7f20f07f86c0 Delete type=3 #601 2026/02/28-22:59:53.742972 7f56e3fff6c0 Delete type=3 #1070
2024/11/26-19:35:41.543967 7f20f07f86c0 Delete type=0 #603 2026/02/28-22:59:53.743061 7f56e3fff6c0 Delete type=0 #1072
2024/11/26-20:03:53.499798 7f20ef7ff6c0 Level-0 table #608: started 2026/03/01-01:08:46.313828 7f54e37ef6c0 Level-0 table #1077: started
2024/11/26-20:03:53.499871 7f20ef7ff6c0 Level-0 table #608: 0 bytes OK 2026/03/01-01:08:46.313877 7f54e37ef6c0 Level-0 table #1077: 0 bytes OK
2024/11/26-20:03:53.506272 7f20ef7ff6c0 Delete type=0 #606 2026/03/01-01:08:46.320973 7f54e37ef6c0 Delete type=0 #1075
2024/11/26-20:03:53.521127 7f20ef7ff6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.339925 7f54e37ef6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
2024/11/26-20:03:53.521200 7f20ef7ff6c0 Manual compaction at level-1 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.339953 7f54e37ef6c0 Manual compaction at level-1 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/11/25-23:05:12.508449 7f20f0ff96c0 Recovering log #599 2026/02/28-17:23:52.769847 7f56f93fe6c0 Recovering log #1068
2024/11/25-23:05:12.520500 7f20f0ff96c0 Delete type=3 #597 2026/02/28-17:23:52.780583 7f56f93fe6c0 Delete type=3 #1066
2024/11/25-23:05:12.520595 7f20f0ff96c0 Delete type=0 #599 2026/02/28-17:23:52.780639 7f56f93fe6c0 Delete type=0 #1068
2024/11/25-23:09:33.850634 7f20ef7ff6c0 Level-0 table #604: started 2026/02/28-22:59:43.677524 7f54e37ef6c0 Level-0 table #1073: started
2024/11/25-23:09:33.850696 7f20ef7ff6c0 Level-0 table #604: 0 bytes OK 2026/02/28-22:59:43.677620 7f54e37ef6c0 Level-0 table #1073: 0 bytes OK
2024/11/25-23:09:33.857611 7f20ef7ff6c0 Delete type=0 #602 2026/02/28-22:59:43.684329 7f54e37ef6c0 Delete type=0 #1071
2024/11/25-23:09:33.864711 7f20ef7ff6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.704401 7f54e37ef6c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
2024/11/25-23:09:33.864774 7f20ef7ff6c0 Manual compaction at level-1 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.704474 7f54e37ef6c0 Manual compaction at level-1 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

BIN
packs/careers/000772.ldb Normal file

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000605 MANIFEST-001074

View File

@@ -1,8 +1,8 @@
2024/11/26-19:35:41.563736 7f20f1ffb6c0 Recovering log #603 2026/02/28-22:59:53.759689 7f56f8bfd6c0 Recovering log #1072
2024/11/26-19:35:41.577358 7f20f1ffb6c0 Delete type=3 #601 2026/02/28-22:59:53.770229 7f56f8bfd6c0 Delete type=3 #1070
2024/11/26-19:35:41.577444 7f20f1ffb6c0 Delete type=0 #603 2026/02/28-22:59:53.770305 7f56f8bfd6c0 Delete type=0 #1072
2024/11/26-20:03:53.506677 7f20ef7ff6c0 Level-0 table #608: started 2026/03/01-01:08:46.327327 7f54e37ef6c0 Level-0 table #1077: started
2024/11/26-20:03:53.506777 7f20ef7ff6c0 Level-0 table #608: 0 bytes OK 2026/03/01-01:08:46.327354 7f54e37ef6c0 Level-0 table #1077: 0 bytes OK
2024/11/26-20:03:53.513269 7f20ef7ff6c0 Delete type=0 #606 2026/03/01-01:08:46.333262 7f54e37ef6c0 Delete type=0 #1075
2024/11/26-20:03:53.521153 7f20ef7ff6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.339946 7f54e37ef6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
2024/11/26-20:03:53.521212 7f20ef7ff6c0 Manual compaction at level-1 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.339979 7f54e37ef6c0 Manual compaction at level-1 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/11/25-23:05:12.539671 7f20f17fa6c0 Recovering log #599 2026/02/28-17:23:52.796426 7f56f9bff6c0 Recovering log #1068
2024/11/25-23:05:12.551722 7f20f17fa6c0 Delete type=3 #597 2026/02/28-17:23:52.805920 7f56f9bff6c0 Delete type=3 #1066
2024/11/25-23:05:12.551810 7f20f17fa6c0 Delete type=0 #599 2026/02/28-17:23:52.805973 7f56f9bff6c0 Delete type=0 #1068
2024/11/25-23:09:33.864938 7f20ef7ff6c0 Level-0 table #604: started 2026/02/28-22:59:43.711333 7f54e37ef6c0 Level-0 table #1073: started
2024/11/25-23:09:33.865010 7f20ef7ff6c0 Level-0 table #604: 0 bytes OK 2026/02/28-22:59:43.711370 7f54e37ef6c0 Level-0 table #1073: 0 bytes OK
2024/11/25-23:09:33.871298 7f20ef7ff6c0 Delete type=0 #602 2026/02/28-22:59:43.717336 7f54e37ef6c0 Delete type=0 #1071
2024/11/25-23:09:33.892710 7f20ef7ff6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.731041 7f54e37ef6c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
2024/11/25-23:09:33.892775 7f20ef7ff6c0 Manual compaction at level-1 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.731086 7f54e37ef6c0 Manual compaction at level-1 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000603 MANIFEST-001072

View File

@@ -1,8 +1,8 @@
2024/11/26-19:35:41.803462 7f20f07f86c0 Recovering log #601 2026/02/28-22:59:53.948768 7f56f93fe6c0 Recovering log #1070
2024/11/26-19:35:41.815192 7f20f07f86c0 Delete type=3 #599 2026/02/28-22:59:53.958702 7f56f93fe6c0 Delete type=3 #1068
2024/11/26-19:35:41.815382 7f20f07f86c0 Delete type=0 #601 2026/02/28-22:59:53.958781 7f56f93fe6c0 Delete type=0 #1070
2024/11/26-20:03:53.606302 7f20ef7ff6c0 Level-0 table #606: started 2026/03/01-01:08:46.425514 7f54e37ef6c0 Level-0 table #1075: started
2024/11/26-20:03:53.606351 7f20ef7ff6c0 Level-0 table #606: 0 bytes OK 2026/03/01-01:08:46.425561 7f54e37ef6c0 Level-0 table #1075: 0 bytes OK
2024/11/26-20:03:53.613146 7f20ef7ff6c0 Delete type=0 #604 2026/03/01-01:08:46.431671 7f54e37ef6c0 Delete type=0 #1073
2024/11/26-20:03:53.637304 7f20ef7ff6c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.445290 7f54e37ef6c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)
2024/11/26-20:03:53.637456 7f20ef7ff6c0 Manual compaction at level-1 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.445328 7f54e37ef6c0 Manual compaction at level-1 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/11/25-23:05:12.756820 7f20f0ff96c0 Recovering log #597 2026/02/28-17:23:52.970603 7f56f9bff6c0 Recovering log #1066
2024/11/25-23:05:12.768761 7f20f0ff96c0 Delete type=3 #595 2026/02/28-17:23:52.981835 7f56f9bff6c0 Delete type=3 #1064
2024/11/25-23:05:12.769033 7f20f0ff96c0 Delete type=0 #597 2026/02/28-17:23:52.981888 7f56f9bff6c0 Delete type=0 #1066
2024/11/25-23:09:33.951420 7f20ef7ff6c0 Level-0 table #602: started 2026/02/28-22:59:43.785498 7f54e37ef6c0 Level-0 table #1071: started
2024/11/25-23:09:33.951618 7f20ef7ff6c0 Level-0 table #602: 0 bytes OK 2026/02/28-22:59:43.785523 7f54e37ef6c0 Level-0 table #1071: 0 bytes OK
2024/11/25-23:09:33.959422 7f20ef7ff6c0 Delete type=0 #600 2026/02/28-22:59:43.792317 7f54e37ef6c0 Delete type=0 #1069
2024/11/25-23:09:33.981150 7f20ef7ff6c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.813749 7f54e37ef6c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)
2024/11/25-23:09:33.981237 7f20ef7ff6c0 Manual compaction at level-1 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.813808 7f54e37ef6c0 Manual compaction at level-1 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

BIN
packs/equipment/000773.ldb Normal file

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000606 MANIFEST-001075

View File

@@ -1,8 +1,8 @@
2024/11/26-19:35:41.615509 7f20f0ff96c0 Recovering log #604 2026/02/28-22:59:53.798832 7f56f9bff6c0 Recovering log #1073
2024/11/26-19:35:41.627787 7f20f0ff96c0 Delete type=3 #602 2026/02/28-22:59:53.809449 7f56f9bff6c0 Delete type=3 #1071
2024/11/26-19:35:41.627884 7f20f0ff96c0 Delete type=0 #604 2026/02/28-22:59:53.809533 7f56f9bff6c0 Delete type=0 #1073
2024/11/26-20:03:53.528454 7f20ef7ff6c0 Level-0 table #609: started 2026/03/01-01:08:46.340100 7f54e37ef6c0 Level-0 table #1078: started
2024/11/26-20:03:53.528504 7f20ef7ff6c0 Level-0 table #609: 0 bytes OK 2026/03/01-01:08:46.340139 7f54e37ef6c0 Level-0 table #1078: 0 bytes OK
2024/11/26-20:03:53.534691 7f20ef7ff6c0 Delete type=0 #607 2026/03/01-01:08:46.346846 7f54e37ef6c0 Delete type=0 #1076
2024/11/26-20:03:53.548839 7f20ef7ff6c0 Manual compaction at level-0 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.366221 7f54e37ef6c0 Manual compaction at level-0 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)
2024/11/26-20:03:53.548906 7f20ef7ff6c0 Manual compaction at level-1 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end) 2026/03/01-01:08:46.366278 7f54e37ef6c0 Manual compaction at level-1 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/11/25-23:05:12.586537 7f20f1ffb6c0 Recovering log #600 2026/02/28-17:23:52.834261 7f56f9bff6c0 Recovering log #1069
2024/11/25-23:05:12.598080 7f20f1ffb6c0 Delete type=3 #598 2026/02/28-17:23:52.843912 7f56f9bff6c0 Delete type=3 #1067
2024/11/25-23:05:12.598168 7f20f1ffb6c0 Delete type=0 #600 2026/02/28-17:23:52.843969 7f56f9bff6c0 Delete type=0 #1069
2024/11/25-23:09:33.877968 7f20ef7ff6c0 Level-0 table #605: started 2026/02/28-22:59:43.704672 7f54e37ef6c0 Level-0 table #1074: started
2024/11/25-23:09:33.878010 7f20ef7ff6c0 Level-0 table #605: 0 bytes OK 2026/02/28-22:59:43.704745 7f54e37ef6c0 Level-0 table #1074: 0 bytes OK
2024/11/25-23:09:33.885003 7f20ef7ff6c0 Delete type=0 #603 2026/02/28-22:59:43.711212 7f54e37ef6c0 Delete type=0 #1072
2024/11/25-23:09:33.892747 7f20ef7ff6c0 Manual compaction at level-0 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.731026 7f54e37ef6c0 Manual compaction at level-0 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)
2024/11/25-23:09:33.892800 7f20ef7ff6c0 Manual compaction at level-1 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end) 2026/02/28-22:59:43.731077 7f54e37ef6c0 Manual compaction at level-1 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)

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