Compare commits

..

16 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
204 changed files with 9135 additions and 2232 deletions

View File

@@ -12,15 +12,13 @@ jobs:
#- uses: actions/checkout@v3
- uses: RouxAntoine/checkout@v3.5.4
with:
ref: "v13"
# get part of the tag after the `v`
- name: Extract tag version number
id: get_version
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
id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1
@@ -28,9 +26,9 @@ jobs:
files: "system.json"
env:
version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/public/bol
manifest: https://www.uberwald.me/gitea/public/bol/releases/latest/system.json
download: https://www.uberwald.me/gitea/public/bol/releases/download/${{github.event.release.tag_name}}/bol.zip
url: https://www.uberwald.me/gitea/${{gitea.repository}}
manifest: https://www.uberwald.me/gitea/public/bol/releases/download/latest/system.json
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
- run: |
@@ -51,4 +49,15 @@ jobs:
files: |-
./bol.zip
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

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

@@ -111,7 +111,7 @@
"BOL.ui.defender": "Verteidiger",
"BOL.ui.difficulty": "Schwierigkeit",
"BOL.ui.spellProperties": "Zaubereigenschaften",
"BOL.ui.duration": "Dauer",
"BOL.ui.duration": "Dauer",
"BOL.ui.spellkeep": "Aufrechterhalten",
"BOL.ui.concentrate": "Konzentrieren",
"BOL.ui.registerInit": "Register Init.",
@@ -134,7 +134,7 @@
"BOL.ui.makeAlchemy": "Alchemika herstellen",
"BOL.ui.alchemyCostTotal": "Alchemiepunkte ingesamt",
"BOL.ui.alchemyInvest": "Alchemiepunkte investieren",
"BOL.ui.alchemyCurrent": "Aktuelle Alchemiepunkte in Objekten",
"BOL.ui.alchemyCurrent": "Aktuelle Alchemiepunkte in Objekten",
"BOL.ui.advance": "Status",
"BOL.ui.isadvantage": "Gibt zusätzlichen Würfel?",
"BOL.ui.isbonusdice": "Gibt zusätzlichen Würfel?",
@@ -153,8 +153,8 @@
"BOL.ui.status": "Status",
"BOL.ui.toactivated": "Aktiv (>Deaktivieren)",
"BOL.ui.todeactivated": "Inaktiv (>Aktivieren)",
"BOL.ui.armorAgiMalus": "Rüschtung+Schild-Malus (Geschick)",
"BOL.ui.armorInitMalus": "Rüstungsmalus (Init)",
"BOL.ui.armorAgiMalus": "Rüschtung+Schild-Malus (Geschick)",
"BOL.ui.armorInitMalus": "Rüstungsmalus (Init)",
"BOL.ui.attackValue": "Angriffswert",
"BOL.ui.initMalus": "Init malus",
"BOL.ui.createEquipment": "Create Equipment",
@@ -177,7 +177,7 @@
"BOL.featureSubtypes.fightOption": "Kampfoption",
"BOL.bougette.nomoney": "Mittellos",
"BOL.bougette.tolive": "Zum Überleben",
"BOL.bougette.tolive": "Zum Überleben",
"BOL.bougette.easylife": "Einfaches Leben",
"BOL.bougette.luxury" : "Luxuriöses Leben",
"BOL.bougette.rich": "Reich!",
@@ -224,14 +224,14 @@
"BOL.protectionCategory.other": "Verschiedenes",
"BOL.spellItem.charm": "Zauber",
"BOL.spellItem.circle1": "Erster Kreis",
"BOL.spellItem.circle2": "Zweiter Kreis",
"BOL.spellItem.circle3": "Dritter Kreis",
"BOL.spellItem.circle1": "Erster Kreis",
"BOL.spellItem.circle2": "Zweiter Kreis",
"BOL.spellItem.circle3": "Dritter Kreis",
"BOL.alchemyItem.common": "Häufig",
"BOL.alchemyItem.scarce": "Selten",
"BOL.alchemyItem.legend": "Legendär",
"BOL.alchemyItem.mythic": "Mystisch",
"BOL.alchemyItem.common": "Häufig",
"BOL.alchemyItem.scarce": "Selten",
"BOL.alchemyItem.legend": "Legendär",
"BOL.alchemyItem.mythic": "Mystisch",
"BOL.weaponCategory.melee": "Nahkampf",
"BOL.weaponCategory.ranged": "Fernkampf",
@@ -290,6 +290,7 @@
"BOL.itemProperty.difficulty": "Schwierigkeit",
"BOL.itemProperty.natural": "Natürliche Waffe",
"BOL.itemProperty.onlymodifier": "Nur Modifikator (d.h. Angriffe von Kreaturen)",
"BOL.itemProperty.attackMalusDice": "Angriffsmalus (Würfel)",
"BOL.itemStat.quantity": "Anzahl",
"BOL.itemStat.weight": "Gewicht",
@@ -359,7 +360,7 @@
"BOL.ui.astrologerPoints": "Points d'Astrologie",
"BOL.ui.astrologerPointsLabel": "Points d'Astrologie actuels",
"BOL.ui.ishoroscopemajor": "Horoscope Majeur (ie de groupe) ?",
"BOL.ui.answer": "Réponse",
"BOL.ui.answer": "Réponse",
"BOL.ui.horoscopefavorable": "Favorable (1dB)",
"BOL.ui.horoscopeunfavorable": "Défavorable (1dM)",
"BOL.ui.horoscopes": "Horoscopes",
@@ -468,7 +469,7 @@
"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.welcome6": "",
"BOL.chat.bolRulebookMessage": "Don't miss the full Rulebook module (including Sagas) available at : https://www.ludospherik-editions.com/en_gb/ !",
"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.rollArmorTooltip": "Roll for armor value, fixed value if unchecked",

View File

@@ -47,7 +47,7 @@
"BOL.ui.astrologerPoints": "Points d'Astrologie",
"BOL.ui.astrologerPointsLabel": "Points d'Astrologie actuels",
"BOL.ui.ishoroscopemajor": "Horoscope Majeur (ie de groupe) ?",
"BOL.ui.answer": "Réponse",
"BOL.ui.answer": "Réponse",
"BOL.ui.horoscopefavorable": "Favorable (1dB)",
"BOL.ui.horoscopeunfavorable": "Défavorable (1dM)",
"BOL.ui.horoscopes": "Horoscopes",
@@ -132,7 +132,7 @@
"BOL.ui.defender": "Defender",
"BOL.ui.difficulty": "Difficulty",
"BOL.ui.spellProperties": "Spell Properties",
"BOL.ui.duration": "Duration",
"BOL.ui.duration": "Duration",
"BOL.ui.spellkeep": "Maintain",
"BOL.ui.concentrate": "Concentrate",
"BOL.ui.registerInit": "Register Init.",
@@ -156,7 +156,7 @@
"BOL.ui.makeAlchemy": "Make Alchemy",
"BOL.ui.alchemyCostTotal": "Alchemy Points Total Cost",
"BOL.ui.alchemyInvest": "Invest Alchemy Points",
"BOL.ui.alchemyCurrent": "Current Alchemy Points in Object",
"BOL.ui.alchemyCurrent": "Current Alchemy Points in Object",
"BOL.ui.advance": "Status",
"BOL.ui.isadvantage": "Provides a bonus dice?",
"BOL.ui.bonusmalus": "Additional bonus/penalty",
@@ -173,8 +173,8 @@
"BOL.ui.status": "Status",
"BOL.ui.toactivated": "Active (>Deactivated)",
"BOL.ui.todeactivated": "Inactive (>Active)",
"BOL.ui.armorAgiMalus": "Armor+Shield Modifier (Agi)",
"BOL.ui.armorInitMalus": "Armor Modifier (Init)",
"BOL.ui.armorAgiMalus": "Armor+Shield Modifier (Agi)",
"BOL.ui.armorInitMalus": "Armor Modifier (Init)",
"BOL.ui.attackValue": "Attack Value",
"BOL.ui.attackModifier": "Attack Modifier",
"BOL.ui.weaponbonus": "Cette arme bénéficie déja d'un Dé de Bonus (Arme Favorite prise en compte, par exemple)",
@@ -251,7 +251,7 @@
"BOL.featureSubtypes.xplog": "XP Journal",
"BOL.bougette.nomoney": "Nothing",
"BOL.bougette.tolive": "To live",
"BOL.bougette.tolive": "To live",
"BOL.bougette.easylife": "Easy Life",
"BOL.bougette.luxury" : "Luxury life",
"BOL.bougette.rich": "Rich!",
@@ -299,15 +299,15 @@
"BOL.protectionCategory.helm": "Helm",
"BOL.protectionCategory.other": "Other",
"BOL.spellItem.charm": "Charm",
"BOL.spellItem.circle1": "First Circle",
"BOL.spellItem.circle2": "Second Circle",
"BOL.spellItem.circle3": "Third Circle",
"BOL.spellItem.charm": "Charm",
"BOL.spellItem.circle1": "First Circle",
"BOL.spellItem.circle2": "Second Circle",
"BOL.spellItem.circle3": "Third Circle",
"BOL.alchemyItem.common": "Common",
"BOL.alchemyItem.scarce": "Scarce",
"BOL.alchemyItem.legend": "Legendary",
"BOL.alchemyItem.mythic": "Mythic",
"BOL.alchemyItem.common": "Common",
"BOL.alchemyItem.scarce": "Scarce",
"BOL.alchemyItem.legend": "Legendary",
"BOL.alchemyItem.mythic": "Mythic",
"BOL.weaponCategory.melee": "Melee",
"BOL.weaponCategory.ranged": "Ranged",
@@ -365,6 +365,7 @@
"BOL.itemProperty.difficulty": "Difficulty",
"BOL.itemProperty.natural": "Natural weapon",
"BOL.itemProperty.onlymodifier": "Modifier only (ie creatures attacks)",
"BOL.itemProperty.attackMalusDice": "Attack Malus (Dice)",
"BOL.itemStat.quantity": "Quantity",
"BOL.itemStat.weight": "Weight",
@@ -458,7 +459,7 @@
"BOL.size.colossal": "Colossal",
"BOL.chat.fightactive": "{name} activates the fight option {foName} for this round !",
"BOL.chat.fightunactive": "{name} deactivates the fight option {foName} for this round !",
"BOL.chat.fightunactive": "{name} deactivates the fight option {foName} for this round !",
"BOL.chat.isdead": "{name} is dead !",
"BOL.chat.epitaph": "Keep his name and memory in honor !",
"BOL.chat.vitalityzero": "Lifeblood of {name} is now {hp} : he is going to fall unconscious !",
@@ -543,18 +544,24 @@
"BOL.chat.criticalbuttonjournal": "Legendary/Heroic Success",
"BOL.chat.nodamage": "Do not apply damages",
"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.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.soeasy3": "So easy (+3)",
"BOL.dialog.veryeasy": "Very easy (+2)",
"BOL.dialog.easy": "Easy (+1)",
"BOL.dialog.moderate": "Moderate (0)",
"BOL.dialog.hard": "Hard (-1)",
"BOL.dialog.tough": "Tough (-2)",
"BOL.dialog.tough3": "Tough (-3)",
"BOL.dialog.demanding": "Demanding (-4)",
"BOL.dialog.demanding5": "Demanding (-5)",
"BOL.dialog.formidable": "Formidable (-6)",
"BOL.dialog.formidable7": "Formidable (-7)",
"BOL.dialog.heroic": "Heroic (-8)",
"BOL.dialog.heroic9": "Heroic (-9)",
"BOL.dialog.mythic": "Mythic (-10)",
"BOL.dialog.mythic11": "Mythic (-11)",
"BOL.dialog.divine": "Divine (-12)",
"BOL.dialog.pointblank": "Point blank (+1)",
@@ -563,7 +570,7 @@
"BOL.dialog.long": "Long (-2)",
"BOL.dialog.distant": "Distant (-4)",
"BOL.dialog.extreme": "Extreme (-6)",
"BOL.dialog.utmost": "Utmost (-8)",
"BOL.dialog.utmost": "Utmost (-8)",
"BOL.ui.name": "Name",
"BOL.ui.xp": "Experience",
@@ -602,5 +609,10 @@
"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.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

@@ -132,7 +132,7 @@
"BOL.ui.defender": "Defensor",
"BOL.ui.difficulty": "Dificultad",
"BOL.ui.spellProperties": "Propiedades Conjuro",
"BOL.ui.duration": "Duración",
"BOL.ui.duration": "Duración",
"BOL.ui.spellkeep": "Mantenimiento",
"BOL.ui.concentrate": "Concentración",
"BOL.ui.registerInit": "Registrar Inic.",
@@ -156,7 +156,7 @@
"BOL.ui.makeAlchemy": "Realizar Alquimia",
"BOL.ui.alchemyCostTotal": "Coste Total Puntos Alquimia",
"BOL.ui.alchemyInvest": "Invertir Puntos Alquimia",
"BOL.ui.alchemyCurrent": "Puntos Alquimia actuales en Objeto",
"BOL.ui.alchemyCurrent": "Puntos Alquimia actuales en Objeto",
"BOL.ui.advance": "Estado",
"BOL.ui.isadvantage": "¿Da un dado ventaja?",
"BOL.ui.bonusmalus": "Ventaja/desventaja adicional",
@@ -245,7 +245,7 @@
"BOL.featureSubtypes.xplog": "Diario PX",
"BOL.bougette.nomoney": "Nada",
"BOL.bougette.tolive": "Vivir justo",
"BOL.bougette.tolive": "Vivir justo",
"BOL.bougette.easylife": "Vida simple",
"BOL.bougette.luxury" : "Vida lujosa",
"BOL.bougette.rich": "¡Rico!",
@@ -293,15 +293,15 @@
"BOL.protectionCategory.helm": "Casco",
"BOL.protectionCategory.other": "Otro",
"BOL.spellItem.charm": "Truco",
"BOL.spellItem.circle1": "Primer Círculo",
"BOL.spellItem.circle2": "Segundo Círculo",
"BOL.spellItem.circle3": "Tercer Círculo",
"BOL.spellItem.charm": "Truco",
"BOL.spellItem.circle1": "Primer Círculo",
"BOL.spellItem.circle2": "Segundo Círculo",
"BOL.spellItem.circle3": "Tercer Círculo",
"BOL.alchemyItem.common": "Común",
"BOL.alchemyItem.scarce": "Escaso",
"BOL.alchemyItem.legend": "Legendario",
"BOL.alchemyItem.mythic": "Mítico",
"BOL.alchemyItem.common": "Común",
"BOL.alchemyItem.scarce": "Escaso",
"BOL.alchemyItem.legend": "Legendario",
"BOL.alchemyItem.mythic": "Mítico",
"BOL.weaponCategory.melee": "Melé",
"BOL.weaponCategory.ranged": "Distancia",
@@ -359,6 +359,7 @@
"BOL.itemProperty.difficulty": "Dificultad",
"BOL.itemProperty.natural": "Arma natural",
"BOL.itemProperty.onlymodifier": "Sólo modificador (ej criatura)",
"BOL.itemProperty.attackMalusDice": "Dado Desventaja Ataque",
"BOL.itemStat.quantity": "Cantidad",
"BOL.itemStat.weight": "Peso",
@@ -452,7 +453,7 @@
"BOL.size.colossal": "Colosal",
"BOL.chat.fightactive": "¡Activa la opción de combate {foName} este asalto!",
"BOL.chat.fightunactive": "¡Desactiva la opción de combate {foName} este asalto!",
"BOL.chat.fightunactive": "¡Desactiva la opción de combate {foName} este asalto!",
"BOL.chat.isdead": "¡{name} esta muerto!",
"BOL.chat.epitaph": "¡Guardar el honor de su nombre y su memoria!",
"BOL.chat.vitalityzero": "Vitalidad de {name} es {hp}: ¡va a caer inconsciente!",
@@ -534,7 +535,7 @@
"BOL.chat.criticalinfo": "¡Esto es un éxito Asombroso o Legendario! Escoge tus opciones y efectos",
"BOL.chat.criticalbuttonjournal": "Éxito Asombroso/Legendario",
"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.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.veryeasy": "Muy fácil (+2)",
@@ -554,7 +555,7 @@
"BOL.dialog.long": "Larga (-2)",
"BOL.dialog.distant": "Distante (-4)",
"BOL.dialog.extreme": "Extrema (-6)",
"BOL.dialog.utmost": "Límite (-8)",
"BOL.dialog.utmost": "Límite (-8)",
"BOL.ui.name": "Nombre",
"BOL.ui.xp": "Experiencia",

View File

@@ -50,7 +50,7 @@
"BOL.ui.astrologerPoints": "Points d'Astrologie",
"BOL.ui.astrologerPointsLabel": "Points d'Astrologie actuels",
"BOL.ui.ishoroscopemajor": "Horoscope Majeur (ie de groupe) ?",
"BOL.ui.answer": "Réponse",
"BOL.ui.answer": "Réponse",
"BOL.ui.horoscopefavorable": "Favorable (1dB)",
"BOL.ui.horoscopeunfavorable": "Défavorable (1dM)",
"BOL.ui.horoscopes": "Horoscopes",
@@ -137,7 +137,7 @@
"BOL.ui.duration": "Durée",
"BOL.ui.spellkeep": "Prolongation possible ?",
"BOL.ui.concentrate": "Concentration à maintenir ?",
"BOL.ui.aggressive": "Sort aggressif ?",
"BOL.ui.aggressive": "Sort aggressif ?",
"BOL.ui.registerInit": "Enregistrer comme Init. de combat",
"BOL.ui.initMalus": "Malus d'initiative",
"BOL.ui.magicnewrules": "Règles supplémentaires (cf. supplément fan-made Sorcellerie!)",
@@ -147,7 +147,7 @@
"BOL.ui.flaw":"Désanvatage",
"BOL.ui.cost":"Cout XP",
"BOL.ui.date":"Date",
"BOL.ui.isSorcerer": "Carrière de Sorcier ?",
"BOL.ui.isAlchemist": "Carrière d'Alchimiste ?",
"BOL.ui.isPriest": "Carrière de Prêtre/Druide ?",
@@ -259,7 +259,7 @@
"BOL.bougette.easylife": "A l'aise",
"BOL.bougette.luxury": "Luxe&Volupté",
"BOL.bougette.rich": "Richissime",
"BOL.featureSubtypes.origin": "Origine",
"BOL.featureSubtypes.race": "Race",
"BOL.featureSubtypes.career": "Carrière",
@@ -393,8 +393,9 @@
"BOL.itemProperty.crewDamageMultiplier": "Multiplicateur",
"BOL.itemProperty.isboarding": "Abordage",
"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.weight": "Poids",
"BOL.itemStat.price": "Prix",
@@ -486,7 +487,7 @@
"BOL.size.colossal": "Monstrueuse",
"BOL.chat.fightactive": "{name} active son option de combat {foName} pour ce round !",
"BOL.chat.fightunactive": "{name} désactive son option de combat {foName} pour ce round !",
"BOL.chat.fightunactive": "{name} désactive son option de combat {foName} pour ce round !",
"BOL.chat.isdead": "{name} est mort !",
"BOL.chat.epitaph": "Que son nom soit honoré sur les champs de batailles de Lémurie !",
"BOL.chat.vitalityzero": "La Vitalité de {name} est {hp} : il va s'écrouler au sol et sombrer dans l'inconscience !",
@@ -569,21 +570,29 @@
"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.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.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.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.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.soeasy3": "Inmanquable (+3)",
"BOL.dialog.veryeasy": "Trés Facile (+2)",
"BOL.dialog.easy": "Facile (+1)",
"BOL.dialog.moderate": "Moyenne (0)",
"BOL.dialog.hard": "Ardue (-1)",
"BOL.dialog.tough": "Difficile (-2)",
"BOL.dialog.tough3": "Difficile (-3)",
"BOL.dialog.demanding": "Très Difficile (-4)",
"BOL.dialog.demanding5": "Très Difficile (-5)",
"BOL.dialog.formidable": "Impossible (-6)",
"BOL.dialog.formidable7": "Impossible (-7)",
"BOL.dialog.heroic": "Héroïque (-8)",
"BOL.dialog.heroic9": "Héroïque (-9)",
"BOL.dialog.mythic": "Mythique (-10)",
"BOL.dialog.mythic11": "Mythique (-11)",
"BOL.dialog.divine": "Divine (-12)",
"BOL.dialog.pointblank": "Bout portant (+1)",
@@ -620,7 +629,6 @@
"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.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.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)",
"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

@@ -7,6 +7,8 @@ import { BoLUtility } from "../system/bol-utility.js";
*/
export class BoLActor extends Actor {
static _healthLock = new Set()
static async create(data, options) {
// Case of compendium global import
@@ -357,7 +359,7 @@ export class BoLActor extends Actor {
ChatMessage.create({
alias: 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 })
})
}
@@ -852,36 +854,42 @@ export class BoLActor extends Actor {
/*-------------------------------------------- */
async manageHealthState() {
let hpID = "lastHP" + this.id
let lastHP = await this.getFlag("world", hpID)
if (lastHP != this.system.resources.hp.value && game.user.isGM) { // Only GM sends this
await this.setFlag("world", hpID, this.system.resources.hp.value)
let prone = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusProne"))
let dead = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusDead"))
if (this.system.resources.hp.value <= 0) {
if (!prone) {
await this.createEmbeddedDocuments("ActiveEffect", [
{ name: game.i18n.localize('EFFECT.StatusProne'), icon: 'icons/svg/falling.svg', statuses: 'prone' }
])
}
if (this.system.resources.hp.value < -5 && !dead) {
await this.createEmbeddedDocuments("ActiveEffect", [
{ name: game.i18n.localize('EFFECT.StatusDead'), icon: 'icons/svg/skull.svg', statuses: 'dead' }
])
}
ChatMessage.create({
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() })
})
} else {
if (prone) {
await this.deleteEmbeddedDocuments("ActiveEffect", [prone.id])
}
if (dead) {
await this.deleteEmbeddedDocuments("ActiveEffect", [dead.id])
if (BoLActor._healthLock.has(this.id)) return
BoLActor._healthLock.add(this.id)
try {
let hpID = "lastHP" + this.id
let lastHP = await this.getFlag("world", hpID)
if (lastHP != this.system.resources.hp.value && game.user.isGM) { // Only GM sends this
await this.setFlag("world", hpID, this.system.resources.hp.value)
let prone = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusProne"))
let dead = this.effects.find(ef => ef.name == game.i18n.localize("EFFECT.StatusDead"))
if (this.system.resources.hp.value <= 0) {
if (!prone) {
await this.createEmbeddedDocuments("ActiveEffect", [
{ name: game.i18n.localize('EFFECT.StatusProne'), icon: 'icons/svg/falling.svg', statuses: 'prone' }
])
}
if (this.system.resources.hp.value < -5 && !dead) {
await this.createEmbeddedDocuments("ActiveEffect", [
{ name: game.i18n.localize('EFFECT.StatusDead'), icon: 'icons/svg/skull.svg', statuses: 'dead' }
])
}
ChatMessage.create({
alias: this.name,
whisper: BoLUtility.getWhisperRecipientsAndGMs(this.name),
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() })
})
} else {
if (prone) {
await this.deleteEmbeddedDocuments("ActiveEffect", [prone.id])
}
if (dead) {
await this.deleteEmbeddedDocuments("ActiveEffect", [dead.id])
}
}
}
} finally {
BoLActor._healthLock.delete(this.id)
}
}
@@ -904,7 +912,7 @@ export class BoLActor extends Actor {
let msg = await ChatMessage.create({
alias: 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,
img: this.img,
actorId: this.id,

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 { BoLActor } from "./actor/actor.js"
import { BoLActorSheet } from "./actor/actor-sheet.js"
import { BoLVehicleSheet } from "./actor/vehicle-sheet.js"
import { BoLHordeSheet } from "./actor/horde-sheet.js"
// AppV1 actor sheets kept for reference only (AppV2 used via sheets.* below)
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 { preloadHandlebarsTemplates } from "./system/templates.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 { 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 () {
@@ -28,7 +32,9 @@ Hooks.once('init', async function () {
BoLRoll,
BoLUtility,
macros: Macros,
config: BOL
config: BOL,
models,
sheets
};
// Game socket
@@ -47,17 +53,38 @@ Hooks.once('init', async function () {
// Define custom Entity classes
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.dataModels = {
item: models.BoLItem,
feature: models.BoLFeature
}
CONFIG.Combat.documentClass = BoLCombatManager;
// Register sheet application classes
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("bol", BoLActorSheet, { types: ["character", "encounter"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("bol", BoLVehicleSheet, { types: ["vehicle"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("bol", BoLHordeSheet, { types: ["horde"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLActorSheet, { types: ["character", "encounter"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLVehicleSheet, { types: ["vehicle"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("bol", sheets.BoLHordeSheet, { types: ["horde"], makeDefault: true })
// Register AppV2 Item Sheets
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("bol", BoLItemSheet, { makeDefault: true });
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
BoLUtility.init()
@@ -83,35 +110,22 @@ Hooks.once('init', async function () {
/* -------------------------------------------- */
function welcomeMessage() {
let content = `<div id="welcome-message-bol"><span class="rdd-roll-part">
<strong>` + game.i18n.localize("BOL.chat.welcome1") + `</strong><p>` +
game.i18n.localize("BOL.chat.welcome2") + "</p><p>" +
game.i18n.localize("BOL.chat.welcome3") + "</p><p>" +
game.i18n.localize("BOL.chat.welcome4") + "</p><p>" +
game.i18n.localize("BOL.chat.welcome5") + "</p>" +
game.i18n.localize("BOL.chat.welcome6")
async function welcomeMessage() {
const noRulebook = !game.modules.find(m => m.id === "bol-rulebook")
const content = await foundry.applications.handlebars.renderTemplate(
"systems/bol/templates/chat/chat-welcome.hbs",
{ noRulebook }
)
ChatMessage.create({ user: game.user.id, whisper: [game.user.id], content })
let rulebook = game.modules.find( m => m.id === "bol-rulebook")
if ( !rulebook ) {
content += "<p>" + game.i18n.localize("BOL.chat.bolRulebookMessage") + "</p>"
}
ChatMessage.create({
user: game.user.id,
whisper: [game.user.id],
content: content
})
if (game.user.isGM && game.i18n.lang == 'en' && !game.modules.find(m => m.id == "babele") ){
if (game.user.isGM && game.i18n.lang == 'en' && !game.modules.find(m => m.id == "babele")) {
ChatMessage.create({
user: game.user.id,
whisper: [game.user.id],
content: `<div id="welcome-message-bol"><span class="rdd-roll-part">
<strong>WARNING ! English language selected, but Babele module is not installed !<br>Please install babele from the module tab in Foundry interface.`
} )
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.")
}
}
/* -------------------------------------------- */

View File

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

View File

@@ -13,6 +13,7 @@ export class BoLItemSheet extends foundry.appv1.sheets.ItemSheet {
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" }]
});
}
@@ -102,6 +103,7 @@ export class BoLItemSheet extends foundry.appv1.sheets.ItemSheet {
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.

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

@@ -67,7 +67,7 @@ export class BoLTokenHud {
/* -------------------------------------------- */
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')
BoLTokenHud._toggleHudListActive(hud, list);

View File

@@ -237,6 +237,11 @@ export class BoLUtility {
if (chatData.img.includes("/blank.png")) {
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
chatData.jsondata = JSON.stringify(
{
@@ -244,7 +249,7 @@ export class BoLUtility {
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);
ChatMessage.create(chatOptions)
});
@@ -429,7 +434,7 @@ export class BoLUtility {
html.on("click", '.damage-handling', event => {
event.preventDefault()
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.")
return
}
@@ -482,13 +487,17 @@ export class BoLUtility {
if (defenseMode == 'damage-with-armor') {
let armorFormula = defender.getArmorFormula()
rollData.rollArmor = new Roll(armorFormula)
await rollData.rollArmor.roll()
let msg = await rollData.rollArmor.toMessage({ flavor: "BOL.chat.armorRoll : " + armorFormula });
if ( game.dice3d) { // wait animation end when DsN is there
await game.dice3d.waitFor3DAnimationByMessageID(msg.id);
if (armorFormula === "0") {
rollData.armorProtect = 0
} 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.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total
rollData.finalDamage = rollData.damageTotal - rollData.armorProtect
rollData.finalDamage = (rollData.finalDamage < 0) ? 0 : rollData.finalDamage
await defender.sufferDamage(rollData.finalDamage)
@@ -500,9 +509,17 @@ export class BoLUtility {
}
if (defenseMode == 'hero-reduce-damage') {
let armorFormula = defender.getArmorFormula()
rollData.rollArmor = new Roll(armorFormula)
await rollData.rollArmor.roll()
rollData.armorProtect = (rollData.rollArmor.total < 0) ? 0 : rollData.rollArmor.total
if (armorFormula === "0") {
rollData.armorProtect = 0
} 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")
await rollData.rollHero.roll()
rollData.finalDamage = rollData.damageTotal - rollData.rollHero.total - rollData.armorProtect
@@ -537,13 +554,13 @@ export class BoLUtility {
ChatMessage.create({
alias: 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)
ChatMessage.create({
alias: defender.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)
})
}
}
@@ -630,11 +647,11 @@ export class BoLUtility {
let msg = await ChatMessage.create({
alias: 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,
attacker: rollData.attacker,
defender: defender,
defenderHeroPoints:defender.getHeroPoints(),
defenderHeroPoints: defender.getHeroPoints(),
defenderWeapons: defenderWeapons,
damageTotal: rollData.damageTotal,
damagesIgnoresArmor: rollData.damagesIgnoresArmor,

View File

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

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/selected-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/attribute-roll-part.hbs",
"systems/bol/templates/dialogs/mod-roll-part.hbs",
@@ -62,7 +63,8 @@ export const preloadHandlebarsTemplates = async function () {
"systems/bol/templates/dialogs/flaws-roll-part.hbs",
"systems/bol/templates/dialogs/total-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

View File

@@ -1 +1 @@
MANIFEST-000787
MANIFEST-001076

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:43.111563 7f181c7f86c0 Recovering log #785
2025/05/15-14:13:43.123121 7f181c7f86c0 Delete type=3 #783
2025/05/15-14:13:43.123237 7f181c7f86c0 Delete type=0 #785
2025/05/15-14:14:15.449405 7f181bff76c0 Level-0 table #790: started
2025/05/15-14:14:15.449426 7f181bff76c0 Level-0 table #790: 0 bytes OK
2025/05/15-14:14:15.458980 7f181bff76c0 Delete type=0 #788
2025/05/15-14:14:15.459158 7f181bff76c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.459194 7f181bff76c0 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:53.852685 7f56e3fff6c0 Recovering log #1074
2026/02/28-22:59:53.863099 7f56e3fff6c0 Delete type=3 #1072
2026/02/28-22:59:53.863210 7f56e3fff6c0 Delete type=0 #1074
2026/03/01-01:08:46.366409 7f54e37ef6c0 Level-0 table #1079: started
2026/03/01-01:08:46.366440 7f54e37ef6c0 Level-0 table #1079: 0 bytes OK
2026/03/01-01:08:46.372420 7f54e37ef6c0 Delete type=0 #1077
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)
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 @@
2025/05/15-14:12:22.257458 7f181d7fa6c0 Recovering log #781
2025/05/15-14:12:22.290228 7f181d7fa6c0 Delete type=3 #779
2025/05/15-14:12:22.290288 7f181d7fa6c0 Delete type=0 #781
2025/05/15-14:13:28.749490 7f181bff76c0 Level-0 table #786: started
2025/05/15-14:13:28.749512 7f181bff76c0 Level-0 table #786: 0 bytes OK
2025/05/15-14:13:28.755584 7f181bff76c0 Delete type=0 #784
2025/05/15-14:13:28.776349 7f181bff76c0 Manual compaction at level-0 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.776403 7f181bff76c0 Manual compaction at level-1 from '!journal!3xJg1rCxnWvEmoxS' @ 72057594037927935 : 1 .. '!journal.pages!veAAxCtCKcFIsnln.0kUgZspxXO7VS8bd' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.883622 7f56f93fe6c0 Recovering log #1070
2026/02/28-17:23:52.893895 7f56f93fe6c0 Delete type=3 #1068
2026/02/28-17:23:52.893967 7f56f93fe6c0 Delete type=0 #1070
2026/02/28-22:59:43.750682 7f54e37ef6c0 Level-0 table #1075: started
2026/02/28-22:59:43.750749 7f54e37ef6c0 Level-0 table #1075: 0 bytes OK
2026/02/28-22:59:43.758241 7f54e37ef6c0 Delete type=0 #1073
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)
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)

View File

@@ -1 +1 @@
MANIFEST-000692
MANIFEST-000981

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:43.084195 7f181d7fa6c0 Recovering log #690
2025/05/15-14:13:43.093610 7f181d7fa6c0 Delete type=3 #688
2025/05/15-14:13:43.093672 7f181d7fa6c0 Delete type=0 #690
2025/05/15-14:14:15.420732 7f181bff76c0 Level-0 table #695: started
2025/05/15-14:14:15.420773 7f181bff76c0 Level-0 table #695: 0 bytes OK
2025/05/15-14:14:15.426739 7f181bff76c0 Delete type=0 #693
2025/05/15-14:14:15.459093 7f181bff76c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.459118 7f181bff76c0 Manual compaction at level-1 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
2026/02/28-22:59:53.826224 7f56f8bfd6c0 Recovering log #979
2026/02/28-22:59:53.836758 7f56f8bfd6c0 Delete type=3 #977
2026/02/28-22:59:53.836823 7f56f8bfd6c0 Delete type=0 #979
2026/03/01-01:08:46.372471 7f54e37ef6c0 Level-0 table #984: started
2026/03/01-01:08:46.372486 7f54e37ef6c0 Level-0 table #984: 0 bytes OK
2026/03/01-01:08:46.378495 7f54e37ef6c0 Delete type=0 #982
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)
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 @@
2025/05/15-14:12:22.231002 7f181cff96c0 Recovering log #686
2025/05/15-14:12:22.241565 7f181cff96c0 Delete type=3 #684
2025/05/15-14:12:22.241622 7f181cff96c0 Delete type=0 #686
2025/05/15-14:13:28.762827 7f181bff76c0 Level-0 table #691: started
2025/05/15-14:13:28.762900 7f181bff76c0 Level-0 table #691: 0 bytes OK
2025/05/15-14:13:28.769448 7f181bff76c0 Delete type=0 #689
2025/05/15-14:13:28.776374 7f181bff76c0 Manual compaction at level-0 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.776417 7f181bff76c0 Manual compaction at level-1 from '!items!G3dZTHIabA3LA1hY' @ 72057594037927935 : 1 .. '!items!xhEcsi3WHjbt2ro9' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.858522 7f56e3fff6c0 Recovering log #975
2026/02/28-17:23:52.869495 7f56e3fff6c0 Delete type=3 #973
2026/02/28-17:23:52.869552 7f56e3fff6c0 Delete type=0 #975
2026/02/28-22:59:43.737596 7f54e37ef6c0 Level-0 table #980: started
2026/02/28-22:59:43.737635 7f54e37ef6c0 Level-0 table #980: 0 bytes OK
2026/02/28-22:59:43.743801 7f54e37ef6c0 Delete type=0 #978
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)
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.

BIN
packs/boons/000843.ldb Normal file

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000785
MANIFEST-001075

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:42.973680 7f181cff96c0 Recovering log #783
2025/05/15-14:13:42.986320 7f181cff96c0 Delete type=3 #781
2025/05/15-14:13:42.986380 7f181cff96c0 Delete type=0 #783
2025/05/15-14:14:15.364727 7f181bff76c0 Level-0 table #788: started
2025/05/15-14:14:15.364774 7f181bff76c0 Level-0 table #788: 0 bytes OK
2025/05/15-14:14:15.371304 7f181bff76c0 Delete type=0 #786
2025/05/15-14:14:15.391269 7f181bff76c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.391342 7f181bff76c0 Manual compaction at level-1 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
2026/02/28-22:59:53.718001 7f56f8bfd6c0 Recovering log #1073
2026/02/28-22:59:53.729345 7f56f8bfd6c0 Delete type=3 #1071
2026/02/28-22:59:53.729412 7f56f8bfd6c0 Delete type=0 #1073
2026/03/01-01:08:46.321084 7f54e37ef6c0 Level-0 table #1078: started
2026/03/01-01:08:46.321111 7f54e37ef6c0 Level-0 table #1078: 0 bytes OK
2026/03/01-01:08:46.327221 7f54e37ef6c0 Delete type=0 #1076
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)
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 @@
2025/05/15-14:12:22.120550 7f181c7f86c0 Recovering log #779
2025/05/15-14:12:22.131209 7f181c7f86c0 Delete type=3 #777
2025/05/15-14:12:22.131268 7f181c7f86c0 Delete type=0 #779
2025/05/15-14:13:28.697236 7f181bff76c0 Level-0 table #784: started
2025/05/15-14:13:28.697270 7f181bff76c0 Level-0 table #784: 0 bytes OK
2025/05/15-14:13:28.704397 7f181bff76c0 Delete type=0 #782
2025/05/15-14:13:28.723085 7f181bff76c0 Manual compaction at level-0 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.723129 7f181bff76c0 Manual compaction at level-1 from '!items!039ZF3E3MtAGwbiX' @ 72057594037927935 : 1 .. '!items!zgspy1QKaxdEetEw' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.757933 7f56f9bff6c0 Recovering log #1069
2026/02/28-17:23:52.767692 7f56f9bff6c0 Delete type=3 #1067
2026/02/28-17:23:52.767761 7f56f9bff6c0 Delete type=0 #1069
2026/02/28-22:59:43.697108 7f54e37ef6c0 Level-0 table #1074: started
2026/02/28-22:59:43.697152 7f54e37ef6c0 Level-0 table #1074: 0 bytes OK
2026/02/28-22:59:43.704224 7f54e37ef6c0 Delete type=0 #1072
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)
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.

View File

@@ -1 +1 @@
MANIFEST-000785
MANIFEST-001074

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:42.988678 7f181dffb6c0 Recovering log #783
2025/05/15-14:13:42.999520 7f181dffb6c0 Delete type=3 #781
2025/05/15-14:13:42.999571 7f181dffb6c0 Delete type=0 #783
2025/05/15-14:14:15.384672 7f181bff76c0 Level-0 table #788: started
2025/05/15-14:14:15.384703 7f181bff76c0 Level-0 table #788: 0 bytes OK
2025/05/15-14:14:15.391032 7f181bff76c0 Delete type=0 #786
2025/05/15-14:14:15.391330 7f181bff76c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.391354 7f181bff76c0 Manual compaction at level-1 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
2026/02/28-22:59:53.732583 7f56e3fff6c0 Recovering log #1072
2026/02/28-22:59:53.742972 7f56e3fff6c0 Delete type=3 #1070
2026/02/28-22:59:53.743061 7f56e3fff6c0 Delete type=0 #1072
2026/03/01-01:08:46.313828 7f54e37ef6c0 Level-0 table #1077: started
2026/03/01-01:08:46.313877 7f54e37ef6c0 Level-0 table #1077: 0 bytes OK
2026/03/01-01:08:46.320973 7f54e37ef6c0 Delete type=0 #1075
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)
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 @@
2025/05/15-14:12:22.135197 7f181dffb6c0 Recovering log #779
2025/05/15-14:12:22.145294 7f181dffb6c0 Delete type=3 #777
2025/05/15-14:12:22.145384 7f181dffb6c0 Delete type=0 #779
2025/05/15-14:13:28.704497 7f181bff76c0 Level-0 table #784: started
2025/05/15-14:13:28.704518 7f181bff76c0 Level-0 table #784: 0 bytes OK
2025/05/15-14:13:28.710561 7f181bff76c0 Delete type=0 #782
2025/05/15-14:13:28.723101 7f181bff76c0 Manual compaction at level-0 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.723135 7f181bff76c0 Manual compaction at level-1 from '!items!CoqlfsDV1gL5swbK' @ 72057594037927935 : 1 .. '!items!yofwG0YrsL902G77' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.769847 7f56f93fe6c0 Recovering log #1068
2026/02/28-17:23:52.780583 7f56f93fe6c0 Delete type=3 #1066
2026/02/28-17:23:52.780639 7f56f93fe6c0 Delete type=0 #1068
2026/02/28-22:59:43.677524 7f54e37ef6c0 Level-0 table #1073: started
2026/02/28-22:59:43.677620 7f54e37ef6c0 Level-0 table #1073: 0 bytes OK
2026/02/28-22:59:43.684329 7f54e37ef6c0 Delete type=0 #1071
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)
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)

View File

@@ -1 +1 @@
MANIFEST-000785
MANIFEST-001074

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:43.014338 7f181cff96c0 Recovering log #783
2025/05/15-14:13:43.024408 7f181cff96c0 Delete type=3 #781
2025/05/15-14:13:43.024523 7f181cff96c0 Delete type=0 #783
2025/05/15-14:14:15.371465 7f181bff76c0 Level-0 table #788: started
2025/05/15-14:14:15.371502 7f181bff76c0 Level-0 table #788: 0 bytes OK
2025/05/15-14:14:15.378367 7f181bff76c0 Delete type=0 #786
2025/05/15-14:14:15.391295 7f181bff76c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.391382 7f181bff76c0 Manual compaction at level-1 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
2026/02/28-22:59:53.759689 7f56f8bfd6c0 Recovering log #1072
2026/02/28-22:59:53.770229 7f56f8bfd6c0 Delete type=3 #1070
2026/02/28-22:59:53.770305 7f56f8bfd6c0 Delete type=0 #1072
2026/03/01-01:08:46.327327 7f54e37ef6c0 Level-0 table #1077: started
2026/03/01-01:08:46.327354 7f54e37ef6c0 Level-0 table #1077: 0 bytes OK
2026/03/01-01:08:46.333262 7f54e37ef6c0 Delete type=0 #1075
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)
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 @@
2025/05/15-14:12:22.162465 7f181c7f86c0 Recovering log #779
2025/05/15-14:12:22.173563 7f181c7f86c0 Delete type=3 #777
2025/05/15-14:12:22.173678 7f181c7f86c0 Delete type=0 #779
2025/05/15-14:13:28.723253 7f181bff76c0 Level-0 table #784: started
2025/05/15-14:13:28.723292 7f181bff76c0 Level-0 table #784: 0 bytes OK
2025/05/15-14:13:28.729706 7f181bff76c0 Delete type=0 #782
2025/05/15-14:13:28.749292 7f181bff76c0 Manual compaction at level-0 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.749330 7f181bff76c0 Manual compaction at level-1 from '!items!4S4xAfMXGnuU0O1a' @ 72057594037927935 : 1 .. '!items!zxY3sW0iCJBvwjOS' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.796426 7f56f9bff6c0 Recovering log #1068
2026/02/28-17:23:52.805920 7f56f9bff6c0 Delete type=3 #1066
2026/02/28-17:23:52.805973 7f56f9bff6c0 Delete type=0 #1068
2026/02/28-22:59:43.711333 7f54e37ef6c0 Level-0 table #1073: started
2026/02/28-22:59:43.711370 7f54e37ef6c0 Level-0 table #1073: 0 bytes OK
2026/02/28-22:59:43.717336 7f54e37ef6c0 Delete type=0 #1071
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)
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)

View File

@@ -1 +1 @@
MANIFEST-000783
MANIFEST-001072

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:43.208069 7f181cff96c0 Recovering log #781
2025/05/15-14:13:43.217852 7f181cff96c0 Delete type=3 #779
2025/05/15-14:13:43.217905 7f181cff96c0 Delete type=0 #781
2025/05/15-14:14:15.500215 7f181bff76c0 Level-0 table #786: started
2025/05/15-14:14:15.500243 7f181bff76c0 Level-0 table #786: 0 bytes OK
2025/05/15-14:14:15.507301 7f181bff76c0 Delete type=0 #784
2025/05/15-14:14:15.513900 7f181bff76c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.513920 7f181bff76c0 Manual compaction at level-1 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)
2026/02/28-22:59:53.948768 7f56f93fe6c0 Recovering log #1070
2026/02/28-22:59:53.958702 7f56f93fe6c0 Delete type=3 #1068
2026/02/28-22:59:53.958781 7f56f93fe6c0 Delete type=0 #1070
2026/03/01-01:08:46.425514 7f54e37ef6c0 Level-0 table #1075: started
2026/03/01-01:08:46.425561 7f54e37ef6c0 Level-0 table #1075: 0 bytes OK
2026/03/01-01:08:46.431671 7f54e37ef6c0 Delete type=0 #1073
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)
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 @@
2025/05/15-14:12:22.372916 7f181dffb6c0 Recovering log #777
2025/05/15-14:12:22.383625 7f181dffb6c0 Delete type=3 #775
2025/05/15-14:12:22.383705 7f181dffb6c0 Delete type=0 #777
2025/05/15-14:13:28.804910 7f181bff76c0 Level-0 table #782: started
2025/05/15-14:13:28.804998 7f181bff76c0 Level-0 table #782: 0 bytes OK
2025/05/15-14:13:28.811621 7f181bff76c0 Delete type=0 #780
2025/05/15-14:13:28.834451 7f181bff76c0 Manual compaction at level-0 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.834477 7f181bff76c0 Manual compaction at level-1 from '!items!6fTZ6hOKR4pWbWOe' @ 72057594037927935 : 1 .. '!items!zwSNMO9HpiqUCMt8' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.970603 7f56f9bff6c0 Recovering log #1066
2026/02/28-17:23:52.981835 7f56f9bff6c0 Delete type=3 #1064
2026/02/28-17:23:52.981888 7f56f9bff6c0 Delete type=0 #1066
2026/02/28-22:59:43.785498 7f54e37ef6c0 Level-0 table #1071: started
2026/02/28-22:59:43.785523 7f54e37ef6c0 Level-0 table #1071: 0 bytes OK
2026/02/28-22:59:43.792317 7f54e37ef6c0 Delete type=0 #1069
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)
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)

View File

@@ -1 +1 @@
MANIFEST-000786
MANIFEST-001075

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:43.055306 7f181cff96c0 Recovering log #784
2025/05/15-14:13:43.065319 7f181cff96c0 Delete type=3 #782
2025/05/15-14:13:43.065453 7f181cff96c0 Delete type=0 #784
2025/05/15-14:14:15.398707 7f181bff76c0 Level-0 table #789: started
2025/05/15-14:14:15.398743 7f181bff76c0 Level-0 table #789: 0 bytes OK
2025/05/15-14:14:15.405948 7f181bff76c0 Delete type=0 #787
2025/05/15-14:14:15.420498 7f181bff76c0 Manual compaction at level-0 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.420551 7f181bff76c0 Manual compaction at level-1 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)
2026/02/28-22:59:53.798832 7f56f9bff6c0 Recovering log #1073
2026/02/28-22:59:53.809449 7f56f9bff6c0 Delete type=3 #1071
2026/02/28-22:59:53.809533 7f56f9bff6c0 Delete type=0 #1073
2026/03/01-01:08:46.340100 7f54e37ef6c0 Level-0 table #1078: started
2026/03/01-01:08:46.340139 7f54e37ef6c0 Level-0 table #1078: 0 bytes OK
2026/03/01-01:08:46.346846 7f54e37ef6c0 Delete type=0 #1076
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)
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 @@
2025/05/15-14:12:22.203559 7f181c7f86c0 Recovering log #780
2025/05/15-14:12:22.214330 7f181c7f86c0 Delete type=3 #778
2025/05/15-14:12:22.214427 7f181c7f86c0 Delete type=0 #780
2025/05/15-14:13:28.742785 7f181bff76c0 Level-0 table #785: started
2025/05/15-14:13:28.742826 7f181bff76c0 Level-0 table #785: 0 bytes OK
2025/05/15-14:13:28.749157 7f181bff76c0 Delete type=0 #783
2025/05/15-14:13:28.749322 7f181bff76c0 Manual compaction at level-0 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.749355 7f181bff76c0 Manual compaction at level-1 from '!items!0ErhyqifZLDCmMfT' @ 72057594037927935 : 1 .. '!items!yE8UH6YAgNGjKDEu' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.834261 7f56f9bff6c0 Recovering log #1069
2026/02/28-17:23:52.843912 7f56f9bff6c0 Delete type=3 #1067
2026/02/28-17:23:52.843969 7f56f9bff6c0 Delete type=0 #1069
2026/02/28-22:59:43.704672 7f54e37ef6c0 Level-0 table #1074: started
2026/02/28-22:59:43.704745 7f54e37ef6c0 Level-0 table #1074: 0 bytes OK
2026/02/28-22:59:43.711212 7f54e37ef6c0 Delete type=0 #1072
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)
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)

View File

@@ -1 +1 @@
MANIFEST-000785
MANIFEST-001074

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:43.151877 7f181dffb6c0 Recovering log #783
2025/05/15-14:13:43.162938 7f181dffb6c0 Delete type=3 #781
2025/05/15-14:13:43.162985 7f181dffb6c0 Delete type=0 #783
2025/05/15-14:14:15.459203 7f181bff76c0 Level-0 table #788: started
2025/05/15-14:14:15.459226 7f181bff76c0 Level-0 table #788: 0 bytes OK
2025/05/15-14:14:15.465739 7f181bff76c0 Delete type=0 #786
2025/05/15-14:14:15.487054 7f181bff76c0 Manual compaction at level-0 from '!items!AoT2c0af4lY6aBsx' @ 72057594037927935 : 1 .. '!items!vGydqADwTsHZ9B3j' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.487094 7f181bff76c0 Manual compaction at level-1 from '!items!AoT2c0af4lY6aBsx' @ 72057594037927935 : 1 .. '!items!vGydqADwTsHZ9B3j' @ 0 : 0; will stop at (end)
2026/02/28-22:59:53.895577 7f56f8bfd6c0 Recovering log #1072
2026/02/28-22:59:53.906749 7f56f8bfd6c0 Delete type=3 #1070
2026/02/28-22:59:53.906807 7f56f8bfd6c0 Delete type=0 #1072
2026/03/01-01:08:46.391890 7f54e37ef6c0 Level-0 table #1077: started
2026/03/01-01:08:46.391936 7f54e37ef6c0 Level-0 table #1077: 0 bytes OK
2026/03/01-01:08:46.398187 7f54e37ef6c0 Delete type=0 #1075
2026/03/01-01:08:46.417834 7f54e37ef6c0 Manual compaction at level-0 from '!items!AoT2c0af4lY6aBsx' @ 72057594037927935 : 1 .. '!items!vGydqADwTsHZ9B3j' @ 0 : 0; will stop at (end)
2026/03/01-01:08:46.417875 7f54e37ef6c0 Manual compaction at level-1 from '!items!AoT2c0af4lY6aBsx' @ 72057594037927935 : 1 .. '!items!vGydqADwTsHZ9B3j' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2025/05/15-14:12:22.320169 7f181cff96c0 Recovering log #779
2025/05/15-14:12:22.330454 7f181cff96c0 Delete type=3 #777
2025/05/15-14:12:22.330580 7f181cff96c0 Delete type=0 #779
2025/05/15-14:13:28.784053 7f181bff76c0 Level-0 table #784: started
2025/05/15-14:13:28.784099 7f181bff76c0 Level-0 table #784: 0 bytes OK
2025/05/15-14:13:28.790437 7f181bff76c0 Delete type=0 #782
2025/05/15-14:13:28.804568 7f181bff76c0 Manual compaction at level-0 from '!items!AoT2c0af4lY6aBsx' @ 72057594037927935 : 1 .. '!items!vGydqADwTsHZ9B3j' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.804648 7f181bff76c0 Manual compaction at level-1 from '!items!AoT2c0af4lY6aBsx' @ 72057594037927935 : 1 .. '!items!vGydqADwTsHZ9B3j' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.920377 7f56e3fff6c0 Recovering log #1068
2026/02/28-17:23:52.930570 7f56e3fff6c0 Delete type=3 #1066
2026/02/28-17:23:52.930642 7f56e3fff6c0 Delete type=0 #1068
2026/02/28-22:59:43.758590 7f54e37ef6c0 Level-0 table #1073: started
2026/02/28-22:59:43.758628 7f54e37ef6c0 Level-0 table #1073: 0 bytes OK
2026/02/28-22:59:43.764731 7f54e37ef6c0 Delete type=0 #1071
2026/02/28-22:59:43.785330 7f54e37ef6c0 Manual compaction at level-0 from '!items!AoT2c0af4lY6aBsx' @ 72057594037927935 : 1 .. '!items!vGydqADwTsHZ9B3j' @ 0 : 0; will stop at (end)
2026/02/28-22:59:43.785372 7f54e37ef6c0 Manual compaction at level-1 from '!items!AoT2c0af4lY6aBsx' @ 72057594037927935 : 1 .. '!items!vGydqADwTsHZ9B3j' @ 0 : 0; will stop at (end)

View File

@@ -1 +1 @@
MANIFEST-000268
MANIFEST-000557

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:43.166580 7f181cff96c0 Recovering log #266
2025/05/15-14:13:43.176277 7f181cff96c0 Delete type=3 #264
2025/05/15-14:13:43.176334 7f181cff96c0 Delete type=0 #266
2025/05/15-14:14:15.472711 7f181bff76c0 Level-0 table #271: started
2025/05/15-14:14:15.472751 7f181bff76c0 Level-0 table #271: 0 bytes OK
2025/05/15-14:14:15.479090 7f181bff76c0 Delete type=0 #269
2025/05/15-14:14:15.487070 7f181bff76c0 Manual compaction at level-0 from '!items!CLRp0k5qV8mD03pW' @ 72057594037927935 : 1 .. '!items!wYEpnhbyYaMkaNdL' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.487124 7f181bff76c0 Manual compaction at level-1 from '!items!CLRp0k5qV8mD03pW' @ 72057594037927935 : 1 .. '!items!wYEpnhbyYaMkaNdL' @ 0 : 0; will stop at (end)
2026/02/28-22:59:53.909216 7f56f93fe6c0 Recovering log #555
2026/02/28-22:59:53.919079 7f56f93fe6c0 Delete type=3 #553
2026/02/28-22:59:53.919136 7f56f93fe6c0 Delete type=0 #555
2026/03/01-01:08:46.411300 7f54e37ef6c0 Level-0 table #560: started
2026/03/01-01:08:46.411339 7f54e37ef6c0 Level-0 table #560: 0 bytes OK
2026/03/01-01:08:46.417727 7f54e37ef6c0 Delete type=0 #558
2026/03/01-01:08:46.417880 7f54e37ef6c0 Manual compaction at level-0 from '!items!CLRp0k5qV8mD03pW' @ 72057594037927935 : 1 .. '!items!wYEpnhbyYaMkaNdL' @ 0 : 0; will stop at (end)
2026/03/01-01:08:46.417911 7f54e37ef6c0 Manual compaction at level-1 from '!items!CLRp0k5qV8mD03pW' @ 72057594037927935 : 1 .. '!items!wYEpnhbyYaMkaNdL' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2025/05/15-14:12:22.333642 7f181dffb6c0 Recovering log #262
2025/05/15-14:12:22.344069 7f181dffb6c0 Delete type=3 #260
2025/05/15-14:12:22.344121 7f181dffb6c0 Delete type=0 #262
2025/05/15-14:13:28.776612 7f181bff76c0 Level-0 table #267: started
2025/05/15-14:13:28.776657 7f181bff76c0 Level-0 table #267: 0 bytes OK
2025/05/15-14:13:28.783851 7f181bff76c0 Delete type=0 #265
2025/05/15-14:13:28.804558 7f181bff76c0 Manual compaction at level-0 from '!items!CLRp0k5qV8mD03pW' @ 72057594037927935 : 1 .. '!items!wYEpnhbyYaMkaNdL' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.804656 7f181bff76c0 Manual compaction at level-1 from '!items!CLRp0k5qV8mD03pW' @ 72057594037927935 : 1 .. '!items!wYEpnhbyYaMkaNdL' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.933111 7f56f9bff6c0 Recovering log #551
2026/02/28-17:23:52.944230 7f56f9bff6c0 Delete type=3 #549
2026/02/28-17:23:52.944299 7f56f9bff6c0 Delete type=0 #551
2026/02/28-22:59:43.771257 7f54e37ef6c0 Level-0 table #556: started
2026/02/28-22:59:43.771301 7f54e37ef6c0 Level-0 table #556: 0 bytes OK
2026/02/28-22:59:43.778711 7f54e37ef6c0 Delete type=0 #554
2026/02/28-22:59:43.785356 7f54e37ef6c0 Manual compaction at level-0 from '!items!CLRp0k5qV8mD03pW' @ 72057594037927935 : 1 .. '!items!wYEpnhbyYaMkaNdL' @ 0 : 0; will stop at (end)
2026/02/28-22:59:43.785385 7f54e37ef6c0 Manual compaction at level-1 from '!items!CLRp0k5qV8mD03pW' @ 72057594037927935 : 1 .. '!items!wYEpnhbyYaMkaNdL' @ 0 : 0; will stop at (end)

Binary file not shown.

BIN
packs/flaws/000843.ldb Normal file

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000785
MANIFEST-001075

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:43.001422 7f181d7fa6c0 Recovering log #783
2025/05/15-14:13:43.011666 7f181d7fa6c0 Delete type=3 #781
2025/05/15-14:13:43.011715 7f181d7fa6c0 Delete type=0 #783
2025/05/15-14:14:15.378503 7f181bff76c0 Level-0 table #788: started
2025/05/15-14:14:15.378528 7f181bff76c0 Level-0 table #788: 0 bytes OK
2025/05/15-14:14:15.384525 7f181bff76c0 Delete type=0 #786
2025/05/15-14:14:15.391315 7f181bff76c0 Manual compaction at level-0 from '!items!0wCqg1UpGd50uJrS' @ 72057594037927935 : 1 .. '!items!znd0K3b7HzYpdehs' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.391369 7f181bff76c0 Manual compaction at level-1 from '!items!0wCqg1UpGd50uJrS' @ 72057594037927935 : 1 .. '!items!znd0K3b7HzYpdehs' @ 0 : 0; will stop at (end)
2026/02/28-22:59:53.745821 7f56f9bff6c0 Recovering log #1073
2026/02/28-22:59:53.756326 7f56f9bff6c0 Delete type=3 #1071
2026/02/28-22:59:53.756400 7f56f9bff6c0 Delete type=0 #1073
2026/03/01-01:08:46.333368 7f54e37ef6c0 Level-0 table #1078: started
2026/03/01-01:08:46.333402 7f54e37ef6c0 Level-0 table #1078: 0 bytes OK
2026/03/01-01:08:46.339817 7f54e37ef6c0 Delete type=0 #1076
2026/03/01-01:08:46.339962 7f54e37ef6c0 Manual compaction at level-0 from '!items!0wCqg1UpGd50uJrS' @ 72057594037927935 : 1 .. '!items!znd0K3b7HzYpdehs' @ 0 : 0; will stop at (end)
2026/03/01-01:08:46.339986 7f54e37ef6c0 Manual compaction at level-1 from '!items!0wCqg1UpGd50uJrS' @ 72057594037927935 : 1 .. '!items!znd0K3b7HzYpdehs' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2025/05/15-14:12:22.148548 7f181d7fa6c0 Recovering log #779
2025/05/15-14:12:22.158871 7f181d7fa6c0 Delete type=3 #777
2025/05/15-14:12:22.158926 7f181d7fa6c0 Delete type=0 #779
2025/05/15-14:13:28.710679 7f181bff76c0 Level-0 table #784: started
2025/05/15-14:13:28.710701 7f181bff76c0 Level-0 table #784: 0 bytes OK
2025/05/15-14:13:28.716552 7f181bff76c0 Delete type=0 #782
2025/05/15-14:13:28.723116 7f181bff76c0 Manual compaction at level-0 from '!items!0wCqg1UpGd50uJrS' @ 72057594037927935 : 1 .. '!items!znd0K3b7HzYpdehs' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.723142 7f181bff76c0 Manual compaction at level-1 from '!items!0wCqg1UpGd50uJrS' @ 72057594037927935 : 1 .. '!items!znd0K3b7HzYpdehs' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.782908 7f56e3fff6c0 Recovering log #1069
2026/02/28-17:23:52.794098 7f56e3fff6c0 Delete type=3 #1067
2026/02/28-17:23:52.794177 7f56e3fff6c0 Delete type=0 #1069
2026/02/28-22:59:43.690888 7f54e37ef6c0 Level-0 table #1074: started
2026/02/28-22:59:43.690928 7f54e37ef6c0 Level-0 table #1074: 0 bytes OK
2026/02/28-22:59:43.696960 7f54e37ef6c0 Delete type=0 #1072
2026/02/28-22:59:43.704444 7f54e37ef6c0 Manual compaction at level-0 from '!items!0wCqg1UpGd50uJrS' @ 72057594037927935 : 1 .. '!items!znd0K3b7HzYpdehs' @ 0 : 0; will stop at (end)
2026/02/28-22:59:43.704500 7f54e37ef6c0 Manual compaction at level-1 from '!items!0wCqg1UpGd50uJrS' @ 72057594037927935 : 1 .. '!items!znd0K3b7HzYpdehs' @ 0 : 0; will stop at (end)

Binary file not shown.

BIN
packs/flaws/MANIFEST-001075 Normal file

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000785
MANIFEST-001074

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:43.125974 7f181cff96c0 Recovering log #783
2025/05/15-14:13:43.135422 7f181cff96c0 Delete type=3 #781
2025/05/15-14:13:43.135478 7f181cff96c0 Delete type=0 #783
2025/05/15-14:14:15.437499 7f181bff76c0 Level-0 table #788: started
2025/05/15-14:14:15.437537 7f181bff76c0 Level-0 table #788: 0 bytes OK
2025/05/15-14:14:15.449295 7f181bff76c0 Delete type=0 #786
2025/05/15-14:14:15.459111 7f181bff76c0 Manual compaction at level-0 from '!items!46qF6OBN5gf0dqWc' @ 72057594037927935 : 1 .. '!items!vaAQiuAHdyQrQiUX' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.459185 7f181bff76c0 Manual compaction at level-1 from '!items!46qF6OBN5gf0dqWc' @ 72057594037927935 : 1 .. '!items!vaAQiuAHdyQrQiUX' @ 0 : 0; will stop at (end)
2026/02/28-22:59:53.868071 7f56f9bff6c0 Recovering log #1072
2026/02/28-22:59:53.878397 7f56f9bff6c0 Delete type=3 #1070
2026/02/28-22:59:53.878486 7f56f9bff6c0 Delete type=0 #1072
2026/03/01-01:08:46.384669 7f54e37ef6c0 Level-0 table #1077: started
2026/03/01-01:08:46.384685 7f54e37ef6c0 Level-0 table #1077: 0 bytes OK
2026/03/01-01:08:46.391370 7f54e37ef6c0 Delete type=0 #1075
2026/03/01-01:08:46.391711 7f54e37ef6c0 Manual compaction at level-0 from '!items!46qF6OBN5gf0dqWc' @ 72057594037927935 : 1 .. '!items!vaAQiuAHdyQrQiUX' @ 0 : 0; will stop at (end)
2026/03/01-01:08:46.391749 7f54e37ef6c0 Manual compaction at level-1 from '!items!46qF6OBN5gf0dqWc' @ 72057594037927935 : 1 .. '!items!vaAQiuAHdyQrQiUX' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2025/05/15-14:12:22.294680 7f181dffb6c0 Recovering log #779
2025/05/15-14:12:22.304847 7f181dffb6c0 Delete type=3 #777
2025/05/15-14:12:22.304902 7f181dffb6c0 Delete type=0 #779
2025/05/15-14:13:28.755727 7f181bff76c0 Level-0 table #784: started
2025/05/15-14:13:28.755750 7f181bff76c0 Level-0 table #784: 0 bytes OK
2025/05/15-14:13:28.762532 7f181bff76c0 Delete type=0 #782
2025/05/15-14:13:28.776363 7f181bff76c0 Manual compaction at level-0 from '!items!46qF6OBN5gf0dqWc' @ 72057594037927935 : 1 .. '!items!vaAQiuAHdyQrQiUX' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.776394 7f181bff76c0 Manual compaction at level-1 from '!items!46qF6OBN5gf0dqWc' @ 72057594037927935 : 1 .. '!items!vaAQiuAHdyQrQiUX' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.896387 7f56f9bff6c0 Recovering log #1068
2026/02/28-17:23:52.906464 7f56f9bff6c0 Delete type=3 #1066
2026/02/28-17:23:52.906539 7f56f9bff6c0 Delete type=0 #1068
2026/02/28-22:59:43.731206 7f54e37ef6c0 Level-0 table #1073: started
2026/02/28-22:59:43.731246 7f54e37ef6c0 Level-0 table #1073: 0 bytes OK
2026/02/28-22:59:43.737434 7f54e37ef6c0 Delete type=0 #1071
2026/02/28-22:59:43.758397 7f54e37ef6c0 Manual compaction at level-0 from '!items!46qF6OBN5gf0dqWc' @ 72057594037927935 : 1 .. '!items!vaAQiuAHdyQrQiUX' @ 0 : 0; will stop at (end)
2026/02/28-22:59:43.758449 7f54e37ef6c0 Manual compaction at level-1 from '!items!46qF6OBN5gf0dqWc' @ 72057594037927935 : 1 .. '!items!vaAQiuAHdyQrQiUX' @ 0 : 0; will stop at (end)

View File

@@ -1 +1 @@
MANIFEST-000785
MANIFEST-001074

View File

@@ -1,8 +1,8 @@
2025/05/15-14:13:43.193735 7f181c7f86c0 Recovering log #783
2025/05/15-14:13:43.204647 7f181c7f86c0 Delete type=3 #781
2025/05/15-14:13:43.204736 7f181c7f86c0 Delete type=0 #783
2025/05/15-14:14:15.487240 7f181bff76c0 Level-0 table #788: started
2025/05/15-14:14:15.487259 7f181bff76c0 Level-0 table #788: 0 bytes OK
2025/05/15-14:14:15.493276 7f181bff76c0 Delete type=0 #786
2025/05/15-14:14:15.513883 7f181bff76c0 Manual compaction at level-0 from '!items!0Yhn3r8AFsKXEKeS' @ 72057594037927935 : 1 .. '!items!xVWrSPiX0Nwccsn6' @ 0 : 0; will stop at (end)
2025/05/15-14:14:15.513908 7f181bff76c0 Manual compaction at level-1 from '!items!0Yhn3r8AFsKXEKeS' @ 72057594037927935 : 1 .. '!items!xVWrSPiX0Nwccsn6' @ 0 : 0; will stop at (end)
2026/02/28-22:59:53.934035 7f56f8bfd6c0 Recovering log #1072
2026/02/28-22:59:53.944554 7f56f8bfd6c0 Delete type=3 #1070
2026/02/28-22:59:53.944697 7f56f8bfd6c0 Delete type=0 #1072
2026/03/01-01:08:46.417957 7f54e37ef6c0 Level-0 table #1077: started
2026/03/01-01:08:46.417992 7f54e37ef6c0 Level-0 table #1077: 0 bytes OK
2026/03/01-01:08:46.425325 7f54e37ef6c0 Delete type=0 #1075
2026/03/01-01:08:46.445276 7f54e37ef6c0 Manual compaction at level-0 from '!items!0Yhn3r8AFsKXEKeS' @ 72057594037927935 : 1 .. '!items!xVWrSPiX0Nwccsn6' @ 0 : 0; will stop at (end)
2026/03/01-01:08:46.445310 7f54e37ef6c0 Manual compaction at level-1 from '!items!0Yhn3r8AFsKXEKeS' @ 72057594037927935 : 1 .. '!items!xVWrSPiX0Nwccsn6' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2025/05/15-14:12:22.359455 7f181cff96c0 Recovering log #779
2025/05/15-14:12:22.369403 7f181cff96c0 Delete type=3 #777
2025/05/15-14:12:22.369515 7f181cff96c0 Delete type=0 #779
2025/05/15-14:13:28.819137 7f181bff76c0 Level-0 table #784: started
2025/05/15-14:13:28.819218 7f181bff76c0 Level-0 table #784: 0 bytes OK
2025/05/15-14:13:28.825832 7f181bff76c0 Delete type=0 #782
2025/05/15-14:13:28.834468 7f181bff76c0 Manual compaction at level-0 from '!items!0Yhn3r8AFsKXEKeS' @ 72057594037927935 : 1 .. '!items!xVWrSPiX0Nwccsn6' @ 0 : 0; will stop at (end)
2025/05/15-14:13:28.834504 7f181bff76c0 Manual compaction at level-1 from '!items!0Yhn3r8AFsKXEKeS' @ 72057594037927935 : 1 .. '!items!xVWrSPiX0Nwccsn6' @ 0 : 0; will stop at (end)
2026/02/28-17:23:52.958813 7f56e3fff6c0 Recovering log #1068
2026/02/28-17:23:52.968345 7f56e3fff6c0 Delete type=3 #1066
2026/02/28-17:23:52.968393 7f56e3fff6c0 Delete type=0 #1068
2026/02/28-22:59:43.764864 7f54e37ef6c0 Level-0 table #1073: started
2026/02/28-22:59:43.764900 7f54e37ef6c0 Level-0 table #1073: 0 bytes OK
2026/02/28-22:59:43.771060 7f54e37ef6c0 Delete type=0 #1071
2026/02/28-22:59:43.785345 7f54e37ef6c0 Manual compaction at level-0 from '!items!0Yhn3r8AFsKXEKeS' @ 72057594037927935 : 1 .. '!items!xVWrSPiX0Nwccsn6' @ 0 : 0; will stop at (end)
2026/02/28-22:59:43.785379 7f54e37ef6c0 Manual compaction at level-1 from '!items!0Yhn3r8AFsKXEKeS' @ 72057594037927935 : 1 .. '!items!xVWrSPiX0Nwccsn6' @ 0 : 0; will stop at (end)

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