6 Commits

Author SHA1 Message Date
ea6f267f8f Migration datamodels !
All checks were successful
Release Creation / build (release) Successful in 55s
2026-01-11 22:45:43 +01:00
cfda525f7c Migration datamodels ! 2026-01-11 22:44:49 +01:00
1afb1d0769 Migration datamodels ! 2026-01-11 22:40:24 +01:00
b282427406 Migration datamodels ! 2026-01-11 22:40:18 +01:00
fc7c51e369 Migration datamodels ! 2026-01-11 22:40:06 +01:00
8d3fdbd009 Fix version 2026-01-10 18:20:10 +01:00
241 changed files with 17140 additions and 2607 deletions

View File

@@ -0,0 +1,63 @@
name: Release Creation
on:
release:
types: [published]
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo "💡 The ${{ gitea.repository }} repository will cloned to the runner."
#- uses: actions/checkout@v3
- uses: RouxAntoine/checkout@v3.5.4
# 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 system.json
- name: Substitute Manifest and Download Links For Versioned Ones
id: sub_manifest_link_version
uses: microsoft/variable-substitution@v1
with:
files: 'system.json'
env:
version: ${{steps.get_version.outputs.version-without-v}}
url: https://www.uberwald.me/gitea/${{gitea.repository}}
manifest: https://www.uberwald.me/gitea/public/fvtt-yggdrasill/releases/download/latest/system.json
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-yggdrasill.zip
# Create a zip file with all files required by the module to add to the release
- run: |
apt update -y
apt install -y zip
- run: zip -r ./fvtt-yggdrasill.zip system.json README.md fonts/ images/ lang/ less/ modules/ packs/ styles/ templates/ template.json
- name: setup go
uses: https://github.com/actions/setup-go@v4
with:
go-version: '>=1.20.1'
- name: Use Go Action
id: use-go-action
uses: https://gitea.com/actions/release-action@main
with:
files: |-
./fvtt-yggdrasill.zip
system.json
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: 'fvtt-yggdrasill'
version: ${{github.event.release.tag_name}}
manifest: 'https://www.uberwald.me/gitea/public/fvtt-yggdrasill/releases/download/latest/system.json'
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{github.event.release.tag_name}}/fvtt-yggdrasill.zip'
compatibility-minimum: '13'
compatibility-verified: '13'

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.history/
node_modules/
packs/_source/

28
LICENSE.md Normal file
View File

@@ -0,0 +1,28 @@
# Licence
## Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)
Ce système Foundry VTT pour Yggdrasill est sous licence Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International.
### Vous êtes autorisé à :
- **Partager** — copier et redistribuer le matériel sous quelque support ou format que ce soit
### Selon les conditions suivantes :
- **Attribution** — Vous devez créditer l'œuvre, fournir un lien vers la licence et indiquer si des modifications ont été effectuées. Vous devez indiquer ces informations par tous les moyens raisonnables, sans toutefois suggérer que l'offrant vous soutient ou soutient la façon dont vous avez utilisé son œuvre.
- **Pas d'Utilisation Commerciale** — Vous n'êtes pas autorisé à faire un usage commercial de cette œuvre, tout ou partie du matériel la composant.
- **Pas de modifications** — Dans le cas où vous effectuez un remix, que vous transformez, ou créez à partir du matériel composant l'œuvre originale, vous n'êtes pas autorisé à distribuer ou mettre à disposition l'œuvre modifiée.
- **Pas de restrictions complémentaires** — Vous n'êtes pas autorisé à appliquer des conditions légales ou des mesures techniques qui restreindraient légalement autrui à utiliser l'œuvre dans les conditions décrites par la licence.
### Texte complet de la licence
Pour consulter une copie complète de cette licence, visitez :
https://creativecommons.org/licenses/by-nc-nd/4.0/legalcode.fr
---
**Note** : Ce système est un projet non officiel et n'est pas affilié à l'éditeur du jeu de rôle Yggdrasill.

35
gulpfile.js Normal file
View File

@@ -0,0 +1,35 @@
const gulp = require('gulp');
const less = require('gulp-less');
const sourcemaps = require('gulp-sourcemaps');
// Paths
const paths = {
styles: {
src: 'less/**/*.less',
dest: 'styles/'
}
};
// Compile LESS to CSS
function styles() {
return gulp.src('less/yggdrasill.less')
.pipe(sourcemaps.init())
.pipe(less())
.pipe(sourcemaps.write('.'))
.pipe(gulp.dest(paths.styles.dest));
}
// Watch files
function watchFiles() {
gulp.watch(paths.styles.src, styles);
}
// Define complex tasks
const build = gulp.series(styles);
const watch = gulp.series(build, watchFiles);
// Export tasks
exports.styles = styles;
exports.build = build;
exports.watch = watch;
exports.default = build;

27
lang/fr.json Normal file
View File

@@ -0,0 +1,27 @@
{
"TYPES": {
"Item": {
"competence": "Compétence",
"don": "Don",
"faiblesse": "Faiblesse",
"blessure": "Blessure",
"maladie": "Maladie",
"poison": "Poison",
"prouesse": "Prouesse",
"sortsejdr": "Sort Sejdr",
"sortgaldr": "Sort Galdr",
"rune": "Rune",
"armecc": "Arme de corps à corps",
"armedist": "Arme de distance",
"armure": "Armure",
"bouclier": "Bouclier",
"equipement": "Équipement",
"monnaie": "Monnaie",
"effetmagique": "Effet magique"
},
"Actor": {
"personnage": "Personnage",
"figurant": "Figurant"
}
}
}

View File

@@ -0,0 +1,534 @@
/* ========================================
CHAT MESSAGE STYLES - Viking Theme
======================================== */
.ygg-chat-card {
font-family: "Vinque", serif;
background: linear-gradient(135deg, rgba(245, 235, 220, 0.95) 0%, rgba(230, 220, 205, 0.95) 100%);
border: 3px solid #4a0404;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), inset 0 0 20px rgba(218, 165, 32, 0.1);
overflow: hidden;
margin: 0.3rem 0;
/* Header Section */
.ygg-chat-header {
background: linear-gradient(135deg, #4a0404 0%, #6b0505 100%);
color: #f5ead3;
padding: 0.5rem 0.75rem;
display: flex;
align-items: center;
gap: 0.5rem;
border-bottom: 2px solid #daa520;
.header-decorative-border {
flex: 1;
height: 2px;
background: linear-gradient(90deg, transparent 0%, #daa520 50%, transparent 100%);
}
.actor-portrait {
width: 40px;
height: 40px;
border-radius: 50%;
border: 2px solid #daa520;
box-shadow: 0 0 8px rgba(218, 165, 32, 0.5);
object-fit: cover;
}
.header-content {
flex-shrink: 0;
text-align: center;
}
.actor-name {
margin: 0;
font-family: "CaslonAntique", serif;
font-size: 1.1rem;
font-weight: bold;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
color: #f5ead3;
}
.roll-type {
font-size: 0.75rem;
font-weight: normal;
opacity: 0.9;
margin-top: -0.1rem;
i {
margin-right: 0.2rem;
}
}
}
/* Roll Description */
.ygg-roll-description {
padding: 0.5rem 0.75rem;
background: rgba(255, 255, 255, 0.4);
border-bottom: 1px solid rgba(74, 4, 4, 0.2);
font-size: 0.9rem;
strong {
color: #4a0404;
font-size: 1rem;
}
.dice-formula,
.skill-level,
.carac-detail,
.skill-detail {
color: #555;
font-size: 0.8rem;
}
}
/* Dice Section */
.ygg-dice-section {
padding: 0.5rem 0.75rem;
background: rgba(255, 250, 240, 0.6);
border-bottom: 1px solid rgba(74, 4, 4, 0.2);
.dice-results,
.furor-results {
margin-bottom: 0.4rem;
&:last-child {
margin-bottom: 0;
}
label {
display: block;
font-weight: bold;
color: #4a0404;
margin-bottom: 0.3rem;
font-size: 0.85rem;
i {
margin-right: 0.25rem;
}
}
}
.dice-list {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
}
.die-result {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 32px;
height: 32px;
padding: 0 0.4rem;
background: linear-gradient(135deg, #fff 0%, #f5f5f5 100%);
border: 2px solid #8b4513;
border-radius: 5px;
font-weight: bold;
font-size: 1rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), inset 0 1px 2px rgba(255, 255, 255, 0.5);
&.high {
background: linear-gradient(135deg, #90ee90 0%, #7ad87a 100%);
border-color: #228b22;
color: #004d00;
}
&.low {
background: linear-gradient(135deg, #ffcccb 0%, #ffb3b3 100%);
border-color: #8b0000;
color: #4d0000;
}
&.furor {
background: linear-gradient(135deg, #ff6b35 0%, #ff4500 100%);
border-color: #8b0000;
color: #fff;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
}
}
/* Calculation Breakdown */
.ygg-calculation {
padding: 0.5rem 0.75rem;
background: rgba(255, 255, 255, 0.3);
border-bottom: 1px solid rgba(74, 4, 4, 0.2);
.calc-row {
display: flex;
justify-content: space-between;
padding: 0.2rem 0;
border-bottom: 1px dashed rgba(74, 4, 4, 0.15);
font-size: 0.85rem;
&:last-child {
border-bottom: none;
}
&.furor-row .calc-label i {
color: #ff4500;
}
}
.calc-label {
font-weight: 600;
color: #4a0404;
}
.calc-value {
font-weight: bold;
font-family: "MedievalSharp", serif;
&.negative {
color: #8b0000;
}
&.positive {
color: #228b22;
}
}
}
/* Final Result */
.ygg-result {
padding: 0.75rem;
background: linear-gradient(135deg, rgba(240, 230, 210, 0.8) 0%, rgba(230, 220, 200, 0.8) 100%);
border-top: 3px double #4a0404;
&.critical-success {
background: linear-gradient(135deg, rgba(144, 238, 144, 0.3) 0%, rgba(122, 216, 122, 0.3) 100%);
border-top-color: #228b22;
}
&.critical-failure {
background: linear-gradient(135deg, rgba(255, 99, 71, 0.3) 0%, rgba(220, 20, 60, 0.3) 100%);
border-top-color: #8b0000;
}
&.success {
background: linear-gradient(135deg, rgba(173, 216, 230, 0.2) 0%, rgba(135, 206, 235, 0.2) 100%);
}
.result-total {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.4rem;
padding-bottom: 0.4rem;
border-bottom: 2px solid rgba(74, 4, 4, 0.3);
.result-label {
font-size: 1.1rem;
font-weight: bold;
color: #4a0404;
font-family: "CaslonAntique", serif;
}
.result-value {
font-size: 1.7rem;
font-weight: bold;
font-family: "MedievalSharp", serif;
color: #4a0404;
text-shadow: 2px 2px 4px rgba(218, 165, 32, 0.3);
}
}
.result-vs {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
font-size: 0.85rem;
.vs-label {
color: #666;
}
.vs-value {
font-weight: bold;
color: #4a0404;
}
}
.result-status {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.5);
border-radius: 5px;
border: 2px solid rgba(74, 4, 4, 0.2);
.status-icon {
width: 28px;
height: 28px;
flex-shrink: 0;
font-size: 24px;
}
.status-text {
font-size: 1rem;
font-weight: bold;
font-family: "CaslonAntique", serif;
&.critical {
color: #228b22;
text-shadow: 0 0 8px rgba(34, 139, 34, 0.5);
}
&.success {
color: #4682b4;
}
&.failure {
color: #8b0000;
}
}
}
}
/* Damage Section */
.ygg-damage {
padding: 0.5rem 0.75rem;
background: linear-gradient(135deg, rgba(139, 0, 0, 0.1) 0%, rgba(178, 34, 34, 0.1) 100%);
border-top: 2px solid #8b0000;
.damage-header {
display: flex;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.4rem;
i {
color: #8b0000;
font-size: 1.1rem;
}
.damage-label {
font-weight: bold;
font-size: 0.9rem;
color: #4a0404;
}
}
.damage-value {
font-size: 1.5rem;
font-weight: bold;
color: #8b0000;
font-family: "MedievalSharp", serif;
text-shadow: 2px 2px 4px rgba(139, 0, 0, 0.2);
margin-bottom: 0.2rem;
}
.damage-detail {
font-size: 0.75rem;
color: #666;
font-style: italic;
}
.damage-note {
margin-top: 0.4rem;
padding: 0.3rem;
background: rgba(255, 255, 255, 0.6);
border-left: 3px solid #daa520;
font-size: 0.8rem;
color: #4a0404;
}
}
/* Weapon Details */
.ygg-weapon-details {
padding: 0.5rem 0.75rem;
background: linear-gradient(135deg, rgba(139, 69, 19, 0.08) 0%, rgba(160, 82, 45, 0.08) 100%);
border-top: 2px solid #8b4513;
.weapon-header {
display: flex;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.5rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid rgba(139, 69, 19, 0.3);
&.collapsible {
cursor: pointer;
user-select: none;
transition: background 0.2s ease;
margin: -0.5rem -0.75rem 0;
padding: 0.5rem 0.75rem 0.4rem;
border-bottom: none;
&:hover {
background: rgba(139, 69, 19, 0.1);
}
.toggle-icon {
margin-left: auto;
transition: transform 0.3s ease;
font-size: 0.8rem;
}
&.expanded .toggle-icon {
transform: rotate(180deg);
}
}
i {
color: #8b4513;
font-size: 1.1rem;
}
span {
font-weight: bold;
font-size: 0.9rem;
color: #4a0404;
font-family: "CaslonAntique", serif;
}
}
.weapon-content {
max-height: 500px;
overflow: hidden;
transition: max-height 0.3s ease, opacity 0.3s ease;
opacity: 1;
&.collapsed {
max-height: 0;
opacity: 0;
}
}
.weapon-properties {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.3rem 0.5rem;
}
.weapon-property {
display: flex;
gap: 0.4rem;
font-size: 0.85rem;
.property-label {
font-weight: 600;
color: #4a0404;
white-space: nowrap;
}
.property-value {
color: #555;
}
}
.weapon-description {
margin-top: 0.5rem;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.6);
border-radius: 5px;
border-left: 3px solid #8b4513;
font-size: 0.85rem;
line-height: 1.4;
color: #333;
}
}
/* Magic Details */
.ygg-magic-details {
padding: 0.5rem 0.75rem;
background: linear-gradient(135deg, rgba(138, 43, 226, 0.08) 0%, rgba(148, 0, 211, 0.08) 100%);
border-top: 2px solid #8a2be2;
.magic-header {
display: flex;
align-items: center;
gap: 0.4rem;
margin-bottom: 0.5rem;
padding-bottom: 0.4rem;
border-bottom: 1px solid rgba(138, 43, 226, 0.3);
&.collapsible {
cursor: pointer;
user-select: none;
transition: background 0.2s ease;
margin: -0.5rem -0.75rem 0;
padding: 0.5rem 0.75rem 0.4rem;
border-bottom: none;
&:hover {
background: rgba(138, 43, 226, 0.1);
}
.toggle-icon {
margin-left: auto;
transition: transform 0.3s ease;
font-size: 0.8rem;
}
&.expanded .toggle-icon {
transform: rotate(180deg);
}
}
i {
color: #8a2be2;
font-size: 1.1rem;
}
span {
font-weight: bold;
font-size: 0.9rem;
color: #4a0404;
font-family: "CaslonAntique", serif;
}
}
.magic-content {
max-height: 500px;
overflow: hidden;
transition: max-height 0.3s ease, opacity 0.3s ease;
opacity: 1;
&.collapsed {
max-height: 0;
opacity: 0;
}
}
.magic-property {
display: flex;
gap: 0.4rem;
margin-bottom: 0.3rem;
font-size: 0.85rem;
.property-label {
font-weight: 600;
color: #4a0404;
min-width: 110px;
}
.property-value {
color: #555;
}
}
.rune-details {
background: rgba(255, 255, 255, 0.4);
padding: 0.4rem;
border-radius: 4px;
margin-top: 0.4rem;
}
.magic-description {
margin-top: 0.5rem;
padding: 0.5rem;
background: rgba(255, 255, 255, 0.6);
border-radius: 5px;
border-left: 3px solid #8a2be2;
font-size: 0.85rem;
line-height: 1.4;
color: #333;
}
}
}

View File

@@ -0,0 +1,2 @@
/* Chat Message Styles - Viking Theme */
@import url("yggdrasill-chat-viking.less");

2684
less/yggdrasill-main.less Normal file

File diff suppressed because it is too large Load Diff

4
less/yggdrasill.less Normal file
View File

@@ -0,0 +1,4 @@
// Main LESS file for Yggdrasill system
// Importing all component styles
@import "yggdrasill-main";

View File

@@ -0,0 +1,27 @@
/**
* Index des applications AppV2 pour Yggdrasill
* Ce fichier centralise tous les exports des applications
*/
// Applications de feuilles d'acteurs
export { default as YggdrasillPersonnageSheet } from './yggdrasill-personnage-sheet.mjs';
export { default as YggdrasillFigurantSheet } from './yggdrasill-figurant-sheet.mjs';
// Applications de feuilles d'items
export { default as YggdrasillCompetenceSheet } from './yggdrasill-competence-sheet.mjs';
export { default as YggdrasillDonSheet } from './yggdrasill-don-sheet.mjs';
export { default as YggdrasillFaiblesseSheet } from './yggdrasill-faiblesse-sheet.mjs';
export { default as YggdrasillBlessureSheet } from './yggdrasill-blessure-sheet.mjs';
export { default as YggdrasillMaladieSheet } from './yggdrasill-maladie-sheet.mjs';
export { default as YggdrasillPoisonSheet } from './yggdrasill-poison-sheet.mjs';
export { default as YggdrasillProuesseSheet } from './yggdrasill-prouesse-sheet.mjs';
export { default as YggdrasillSortsejdrSheet } from './yggdrasill-sortsejdr-sheet.mjs';
export { default as YggdrasillSortgaldrSheet } from './yggdrasill-sortgaldr-sheet.mjs';
export { default as YggdrasillRuneSheet } from './yggdrasill-rune-sheet.mjs';
export { default as YggdrasillArmeccSheet } from './yggdrasill-armecc-sheet.mjs';
export { default as YggdrasillArmedistSheet } from './yggdrasill-armedist-sheet.mjs';
export { default as YggdrasillArmureSheet } from './yggdrasill-armure-sheet.mjs';
export { default as YggdrasillBouclierSheet } from './yggdrasill-bouclier-sheet.mjs';
export { default as YggdrasillEquipementSheet } from './yggdrasill-equipement-sheet.mjs';
export { default as YggdrasillMonnaieSheet } from './yggdrasill-monnaie-sheet.mjs';
export { default as YggdrasillEffetmagiqueSheet } from './yggdrasill-effetmagique-sheet.mjs';

View File

@@ -0,0 +1,449 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
import { YggdrasillUtility } from "../../yggdrasill-utility.js"
export default class YggdrasillActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
/**
* Different sheet modes.
* @enum {number}
*/
static SHEET_MODES = { EDIT: 0, PLAY: 1 }
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
this._sheetMode = this.constructor.SHEET_MODES.PLAY
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-yggdrasill", "sheet", "actor"],
position: {
width: 750,
height: 720,
},
form: {
submitOnChange: true,
closeOnSubmit: false,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: 'nav[data-group="primary"]',
contentSelector: "section.sheet-body",
initial: "principal",
},
],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
actions: {
editImage: YggdrasillActorSheet.#onEditImage,
toggleSheet: YggdrasillActorSheet.#onToggleSheet,
editItem: YggdrasillActorSheet.#onEditItem,
deleteItem: YggdrasillActorSheet.#onDeleteItem,
createItem: YggdrasillActorSheet.#onCreateItem,
equipItem: YggdrasillActorSheet.#onEquipItem,
rollCarac: YggdrasillActorSheet.#onRollCarac,
rollCompetence: YggdrasillActorSheet.#onRollCompetence,
rollArme: YggdrasillActorSheet.#onRollArme,
rollSort: YggdrasillActorSheet.#onRollSort,
rollProuesse: YggdrasillActorSheet.#onRollProuesse,
rollDamage: YggdrasillActorSheet.#onRollDamage,
lockUnlock: YggdrasillActorSheet.#onLockUnlock,
incrementPV: YggdrasillActorSheet.#onIncrementPV,
decrementPV: YggdrasillActorSheet.#onDecrementPV,
updateCompetence: YggdrasillActorSheet.#onUpdateCompetence,
},
}
/**
* Is the sheet currently in 'Play' mode?
* @type {boolean}
*/
get isPlayMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
return this._sheetMode === this.constructor.SHEET_MODES.PLAY
}
/**
* Is the sheet currently in 'Edit' mode?
* @type {boolean}
*/
get isEditMode() {
if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY
return this._sheetMode === this.constructor.SHEET_MODES.EDIT
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = {
primary: "principal",
}
/** @override */
async _prepareContext() {
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
actor: this.document,
system: this.document.system,
source: this.document.toObject(),
isEditMode: this.isEditMode,
isPlayMode: this.isPlayMode,
isEditable: this.isEditable,
isGM: game.user.isGM,
config: game.system.yggdrasill.config,
editScore: this.isEditMode,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
// Activate tab navigation manually
const nav = this.element.querySelector('nav.tabs[data-group], nav.sheet-tabs[data-group]')
if (nav) {
const group = nav.dataset.group
// Activate the current tab
const activeTab = this.tabGroups[group] || "principal"
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()
})
})
// Show/hide tab content
this.element.querySelectorAll('[data-group="' + group + '"][data-tab]').forEach(content => {
content.classList.toggle('active', content.dataset.tab === activeTab)
})
}
// Add change listener for competence niveau selects
this.element.querySelectorAll('select.competence-niveau').forEach(select => {
select.addEventListener('change', async (event) => {
const itemId = event.target.dataset.itemId
const item = this.document.items.get(itemId)
if (item) {
const newNiveau = parseInt(event.target.value)
await item.update({ "system.niveau": newNiveau })
}
})
})
}
/**
* Creates drag-and-drop handlers for this application
* @returns {DragDrop[]} An array of DragDrop handlers
* @private
*/
#createDragDropHandlers() {
return []
}
/**
* Handle changing a Document's image
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onEditImage(event, target) {
const attr = target.dataset.edit
const current = foundry.utils.getProperty(this.document, attr)
const fp = new FilePicker({
current,
type: "image",
callback: (path) => {
this.document.update({ [attr]: path })
},
})
return fp.browse()
}
/**
* Toggle sheet mode between Edit and Play
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onToggleSheet(event, target) {
this._sheetMode = this.isEditMode
? this.constructor.SHEET_MODES.PLAY
: this.constructor.SHEET_MODES.EDIT
this.render()
}
/**
* Handle item editing
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onEditItem(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const item = this.document.items.get(itemId)
if (item) item.sheet.render(true)
}
/**
* Handle item deletion
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onDeleteItem(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const item = this.document.items.get(itemId)
if (item) {
await item.delete()
}
}
/**
* Handle item creation
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onCreateItem(event, target) {
const itemType = target.dataset.itemType
const itemData = {
name: `Nouveau ${itemType}`,
type: itemType,
}
await this.document.createEmbeddedDocuments("Item", [itemData])
}
/**
* Handle item equip toggle
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onEquipItem(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const item = this.document.items.get(itemId)
if (item && item.system.equipe !== undefined) {
await item.update({ "system.equipe": !item.system.equipe })
}
}
/**
* Handle characteristic roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollCarac(event, target) {
const caracCateg = target.dataset.caracCateg
const caracKey = target.dataset.caracKey
this.document.rollCarac(caracCateg, caracKey)
}
/**
* Handle competence roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollCompetence(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
this.document.rollCompetence(itemId)
}
/**
* Handle weapon roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollArme(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
this.document.rollArme(itemId)
}
/**
* Handle lock/unlock toggle
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onLockUnlock(event, target) {
this._sheetMode = this.isEditMode
? this.constructor.SHEET_MODES.PLAY
: this.constructor.SHEET_MODES.EDIT
this.render()
}
/**
* Handle incrementing PV
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onIncrementPV(event, target) {
const currentPV = this.document.system.caracsecondaire.pv.value || 0
const maxPV = this.document.system.caracsecondaire.pv.max || 0
const newPV = Math.min(currentPV + 1, maxPV)
await this.document.update({ "system.caracsecondaire.pv.value": newPV })
}
/**
* Handle decrementing PV
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onDecrementPV(event, target) {
const currentPV = this.document.system.caracsecondaire.pv.value || 0
const newPV = Math.max(currentPV - 1, 0)
await this.document.update({ "system.caracsecondaire.pv.value": newPV })
}
/**
* Handle competence niveau update
* @this {YggdrasillActorSheet}
* @param {Event} event - The triggering event
* @param {HTMLElement} target - The select element
*/
static async #onUpdateCompetence(event, target) {
const itemId = target.dataset.itemId
const item = this.document.items.get(itemId)
if (!item) return
const newNiveau = parseInt(target.value)
await item.update({ "system.niveau": newNiveau })
}
/**
* Handle sort roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollSort(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const sortType = target.dataset.sortType || "sejdr"
this.document.rollSort(itemId, sortType)
}
/**
* Handle prouesse roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollProuesse(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
this.document.rollProuesse(itemId)
}
/**
* Handle damage roll
* @this {YggdrasillActorSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static #onRollDamage(event, target) {
const itemId = target.closest("[data-item-id]").dataset.itemId
const weapon = this.document.items.get(itemId)
if (weapon) {
this.document.rollDamage(weapon, 'damage')
}
}
/**
* Handle beginning of a drag operation
* @param {DragEvent} event - The originating drag event
* @protected
*/
_onDragStart(event) {
const li = event.currentTarget
const itemId = li.dataset.itemId
const item = this.document.items.get(itemId)
if (!item) return
const dragData = item.toDragData()
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Handle a drop event
* @param {DragEvent} event - The originating drop event
* @protected
*/
async _onDrop(event) {
const data = TextEditor.getDragEventData(event)
const actor = this.document
// Handle different data types
switch (data.type) {
case "Item":
return this._onDropItem(event, data)
case "ActiveEffect":
return this._onDropActiveEffect(event, data)
}
}
/**
* Handle dropping an Item on the sheet
* @param {DragEvent} event - The originating drop event
* @param {object} data - The dropped data
* @protected
*/
async _onDropItem(event, data) {
if (!this.isEditable) return false
const item = await Item.implementation.fromDropData(data)
const itemData = item.toObject()
// Handle item from same actor
if (this.document.uuid === item.parent?.uuid) return this._onSortItem(event, itemData)
// Create the item
return this._onDropItemCreate(itemData)
}
/**
* Handle creating an owned item from drop data
* @param {object} itemData - The item data to create
* @protected
*/
async _onDropItemCreate(itemData) {
itemData = itemData instanceof Array ? itemData : [itemData]
return this.document.createEmbeddedDocuments("Item", itemData)
}
/**
* Handle sorting items
* @param {DragEvent} event - The originating drop event
* @param {object} itemData - The item data being sorted
* @protected
*/
_onSortItem(event, itemData) {
// Implement sorting logic if needed
return Promise.resolve()
}
/**
* Handle dropping an ActiveEffect on the sheet
* @param {DragEvent} event - The originating drop event
* @param {object} data - The dropped data
* @protected
*/
async _onDropActiveEffect(event, data) {
const effect = await ActiveEffect.implementation.fromDropData(data)
if (!this.isEditable || !effect) return false
if (this.document.uuid === effect.parent?.uuid) return false
return ActiveEffect.create(effect.toObject(), { parent: this.document })
}
}

View File

@@ -0,0 +1,178 @@
const { HandlebarsApplicationMixin } = foundry.applications.api
export default class YggdrasillItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options)
this.#dragDrop = this.#createDragDropHandlers()
}
#dragDrop
/** @override */
static DEFAULT_OPTIONS = {
classes: ["fvtt-yggdrasill", "item"],
position: {
width: 620,
height: 600,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
tabs: [
{
navSelector: 'nav[data-group="primary"]',
contentSelector: "section.sheet-body",
initial: "description",
},
],
dragDrop: [{ dragSelector: "[data-drag]", dropSelector: null }],
actions: {
editImage: YggdrasillItemSheet.#onEditImage,
postItem: YggdrasillItemSheet.#onPostItem,
},
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" }
/** @override */
async _prepareContext() {
// Import config
const YGGDRASILL_CONFIG = game.system.yggdrasill?.config || game.system.config || {};
// Create options for niveau 0-5
const optionsNiveaux4 = {};
for (let i = 0; i <= 5; i++) {
optionsNiveaux4[`${i}`] = `${i}`;
}
// Create options for base (0-20)
const optionsBase = {};
for (let i = 0; i <= 20; i++) {
optionsBase[`${i}`] = `${i}`;
}
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
data: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description || "", { async: true }),
enrichedEffet: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.effet || "", { async: true }),
isEditMode: true,
isEditable: this.isEditable,
editable: this.isEditable,
isGM: game.user.isGM,
config: YGGDRASILL_CONFIG,
optionsBase: optionsBase,
optionsNiveaux4: optionsNiveaux4,
}
return context
}
/** @override */
_onRender(context, options) {
super._onRender(context, options)
// Activate tab navigation manually
const nav = this.element.querySelector('nav.tabs[data-group]')
if (nav) {
const group = nav.dataset.group
// Activate the current tab
const activeTab = this.tabGroups[group] || "description"
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()
})
})
// Show/hide tab content
const body = this.element.querySelector('section.sheet-body')
if (body) {
body.querySelectorAll('[data-tab]').forEach(content => {
const tab = content.dataset.tab
content.style.display = tab === activeTab ? 'block' : 'none'
})
}
}
}
/**
* Creates drag-and-drop handlers for this application
* @returns {DragDrop[]} An array of DragDrop handlers
* @private
*/
#createDragDropHandlers() {
return []
}
/**
* Handle changing a Document's image
* @this {YggdrasillItemSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onEditImage(event, target) {
const attr = target.dataset.edit || "img"
const current = foundry.utils.getProperty(this.document, attr)
const fp = new FilePicker({
current,
type: "image",
callback: (path) => {
this.document.update({ [attr]: path })
},
})
return fp.browse()
}
/**
* Handle posting item to chat
* @this {YggdrasillItemSheet}
* @param {PointerEvent} event - The triggering event
* @param {HTMLElement} target - The button element
*/
static async #onPostItem(event, target) {
const item = this.document
const chatData = {
user: game.user.id,
speaker: ChatMessage.getSpeaker(),
content: await foundry.applications.handlebars.renderTemplate("systems/fvtt-yggdrasill/templates/chat-item-card.hbs", {
item: item,
system: item.system,
description: await TextEditor.enrichHTML(item.system.description || "", { async: true })
})
}
ChatMessage.create(chatData)
}
/**
* Handle beginning of a drag operation
* @param {DragEvent} event - The originating drag event
* @protected
*/
_onDragStart(event) {
const dragData = this.document.toDragData()
event.dataTransfer.setData("text/plain", JSON.stringify(dragData))
}
/**
* Handle a drop event
* @param {DragEvent} event - The originating drop event
* @protected
*/
async _onDrop(event) {
// Items generally don't handle drops, but method exists for extensibility
return false
}
}

View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Liste des types d'items restants à créer
items=(
"poison:Poison"
"prouesse:Prouesse"
"sortsejdr:Sortsejdr"
"sortgaldr:Sortgaldr"
"rune:Rune"
"armecc:Armecc"
"armedist:Armedist"
"armure:Armure"
"bouclier:Bouclier"
"equipement:Equipement"
"monnaie:Monnaie"
"effetmagique:Effetmagique"
)
for item in "${items[@]}"; do
type="${item%%:*}"
class="${item##*:}"
cat > "yggdrasill-${type}-sheet.mjs" << EOF
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class Yggdrasill${class}Sheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "${type}"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-${type}-sheet.hbs",
},
}
}
EOF
done
echo "Created all item sheets"
ls -1 yggdrasill-*-sheet.mjs | wc -l

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillArmeccSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "armecc"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-armecc-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillArmedistSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "armedist"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-armedist-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillArmureSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "armure"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-armure-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillBlessureSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "blessure"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-blessure-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillBouclierSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "bouclier"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-bouclier-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,21 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillCompetenceSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "competence"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-competence-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "details",
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillDonSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "don"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-don-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillEffetmagiqueSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "effetmagique"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-effetmagique-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillEquipementSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "equipement"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-equipement-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillFaiblesseSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "faiblesse"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-faiblesse-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,40 @@
import YggdrasillActorSheet from "./base-actor-sheet.mjs"
export default class YggdrasillFigurantSheet extends YggdrasillActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "figurant"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "Feuille de Figurant",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-yggdrasill/templates/actor-figurant-sheet.hbs",
},
}
/** @override */
tabGroups = {
primary: "principal",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// System fields for formInput helpers
context.systemFields = actor.system.schema.fields
// Enrich HTML fields
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.description || "", { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.notes || "", { async: true })
return context
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillMaladieSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "maladie"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-maladie-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillMonnaieSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "monnaie"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-monnaie-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,99 @@
import YggdrasillActorSheet from "./base-actor-sheet.mjs"
export default class YggdrasillPersonnageSheet extends YggdrasillActorSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "personnage"],
window: {
...super.DEFAULT_OPTIONS.window,
title: "Feuille de Personnage",
},
}
/** @override */
static PARTS = {
sheet: {
template: "systems/fvtt-yggdrasill/templates/actor-personnage-sheet-new.hbs",
},
}
/** @override */
tabGroups = {
primary: "principal",
}
/** @override */
async _prepareContext() {
const context = await super._prepareContext()
const actor = this.document
// Prepare items by type
context.competencesGenerales = actor.items.filter(i => i.type === 'competence' && i.system.categorie === 'generale')
context.competencesMartiales = actor.items.filter(i => i.type === 'competence' && i.system.categorie === 'martiale')
context.competencesMagiques = actor.items.filter(i => i.type === 'competence' && i.system.categorie === 'magique')
context.dons = actor.items.filter(i => i.type === 'don')
context.faiblesses = actor.items.filter(i => i.type === 'faiblesse')
context.blessures = actor.items.filter(i => i.type === 'blessure')
context.maladies = actor.items.filter(i => i.type === 'maladie')
context.poisons = actor.items.filter(i => i.type === 'poison')
context.prouesses = actor.items.filter(i => i.type === 'prouesse')
context.sortsSejdr = actor.items.filter(i => i.type === 'sortsejdr')
context.sortsGaldr = actor.items.filter(i => i.type === 'sortgaldr')
context.runes = actor.items.filter(i => i.type === 'rune')
context.armesCC = actor.items.filter(i => i.type === 'armecc')
context.armesDist = actor.items.filter(i => i.type === 'armedist')
context.armures = actor.items.filter(i => i.type === 'armure')
context.boucliers = actor.items.filter(i => i.type === 'bouclier')
context.equipements = actor.items.filter(i => i.type === 'equipement')
context.monnaies = actor.items.filter(i => i.type === 'monnaie')
context.effetsMagiques = actor.items.filter(i => i.type === 'effetmagique')
// Prepare equipped items
context.armesEquipees = actor.items.filter(i => (i.type === 'armecc' || i.type === 'armedist') && i.system.equipe)
context.armureEquipee = actor.items.find(i => i.type === 'armure' && i.system.equipe)
context.bouclierEquipe = actor.items.find(i => i.type === 'bouclier' && i.system.equipe)
// Calculate total protection from equipped armors
context.protectionTotal = context.armures
.filter(a => a.system.equipe)
.reduce((sum, a) => sum + (parseInt(a.system.protection) || 0), 0)
// Calculate shield defense bonus from equipped shield
context.dpBouclier = context.boucliers
.filter(b => b.system.equipe)
.reduce((sum, b) => sum + (parseInt(b.system.defensebonus) || 0), 0)
// Calculate total encumbrance from equipements
context.encTotal = context.equipements
.reduce((sum, e) => sum + ((parseInt(e.system.quantite) || 0) * (parseInt(e.system.enc) || 0)), 0)
// Options for selects
context.optionsCarac = {}
for (let i = 0; i <= 10; i++) {
context.optionsCarac[i] = i.toString()
}
context.optionsBase = {}
for (let i = 0; i <= 5; i++) {
context.optionsBase[i] = i.toString()
}
// Options for bonus/malus (-10 to +10)
context.optionsDMDP = []
for (let i = -10; i <= 10; i++) {
context.optionsDMDP.push({ value: i, text: i >= 0 ? `+${i}` : i.toString() })
}
// System fields for formInput helpers
context.systemFields = actor.system.schema.fields
// Enrich HTML fields
context.enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.description || "", { async: true })
context.enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.notes || "", { async: true })
context.enrichedGMNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.gmnotes || "", { async: true })
context.enrichedTirageRunes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(actor.system.biodata?.tiragerunes || "", { async: true })
return context
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillPoisonSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "poison"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-poison-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillProuesseSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "prouesse"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-prouesse-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillRuneSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "rune"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-rune-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillSortgaldrSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "sortgaldr"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-sortgaldr-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,16 @@
import YggdrasillItemSheet from "./base-item-sheet.mjs"
export default class YggdrasillSortsejdrSheet extends YggdrasillItemSheet {
/** @override */
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
classes: [...super.DEFAULT_OPTIONS.classes, "sortsejdr"],
}
/** @override */
static PARTS = {
main: {
template: "systems/fvtt-yggdrasill/templates/item-sortsejdr-sheet.hbs",
},
}
}

View File

@@ -0,0 +1,334 @@
import { YggdrasillUtility } from "../yggdrasill-utility.js"
const dureeGaldrSD = { "1d5a": 3, "1d10t": 6, "1d10m": 9, "1d10h": 12, "1d5j": 15}
const ciblesGaldrSD = { "1": 3, "2_4": 6, "5_9": 9, "10_49": 12, "50plus": 15}
const zonesciblesGaldrSD = { "INS10cm3": 3, "INS50cm3": 6, "INS1m3": 9, "INS5m3": 12, "INS10m3": 15}
/**
* Dialogue de jet de dé pour Yggdrasill - Version DialogV2
*/
export class YggdrasillRollDialog {
/**
* Create and display the roll dialog
* @param {YggdrasillActor} actor - The actor making the roll
* @param {Object} rollData - Data for the roll
* @returns {Promise}
*/
static async create(actor, rollData) {
// Initialiser attackData pour les armes si nécessaire
if ((rollData.mode === "armecc" || rollData.mode === "armetir" || rollData.mode === "armedist") && rollData.attackDef) {
// Les données sont déjà calculées dans attackDef par getAttaqueData/getTirData
// On les copie simplement dans attackData pour le template
rollData.attackData = { ...rollData.attackDef }
}
// Calculer srTotal pour les sorts
if (rollData.mode === "galdr") {
this._updateGaldrSD(rollData)
rollData.srTotal = 14 + (rollData.sdGaldr || 0)
} else if (rollData.mode === "sejdr") {
rollData.srTotal = 14 + (rollData.bonusdefense || 0)
} else if (rollData.mode === "rune") {
this._updateRuneData(rollData, actor)
}
// Préparer le contexte pour le template
const context = {
...rollData,
img: actor.img,
name: actor.name,
config: game.system.yggdrasill.config,
}
// Titre selon le mode
let title = "Test"
if (rollData.mode === "competence") title = "Test de Compétence"
else if (rollData.mode === "carac") title = "Test de Caractéristique"
else if (rollData.mode === "attribut") title = "Test d'Attribut"
else if (rollData.mode === "armecc") title = "Attaque au Corps à Corps"
else if (rollData.mode === "armetir" || rollData.mode === "armedist") title = "Attaque à Distance"
else if (rollData.mode === "sejdr") title = "Sort Sejdr"
else if (rollData.mode === "galdr") title = "Sort Galdr"
else if (rollData.mode === "rune") title = "Rune"
// Rendre le template en HTML
const content = await foundry.applications.handlebars.renderTemplate(
"systems/fvtt-yggdrasill/templates/roll-dialog-generic-new.hbs",
context
)
// Utiliser DialogV2.wait avec le HTML rendu
return foundry.applications.api.DialogV2.wait({
window: {
title: title,
icon: "fa-solid fa-dice-d20"
},
classes: ["yggdrasill-roll-dialog"],
position: { width: 600 },
modal: false,
content,
buttons: [
{
action: "roll",
label: "Lancer le Test",
icon: "fa-solid fa-dice",
default: true,
callback: (event, button, dialog) => {
this._updateRollDataFromForm(rollData, button.form.elements, actor)
this._performRoll(rollData)
}
},
],
actions: {},
render: (event, dialog) => {
// Pour Galdr: recalculer srTotal quand durée, cibles ou zone changent
if (rollData.mode === "galdr") {
$("#dureeGaldr, #nbCibles, #zoneGaldr").on("change", () => {
rollData.dureeGaldr = $("#dureeGaldr").val()
rollData.nbCibles = $("#nbCibles").val()
rollData.zoneGaldr = $("#zoneGaldr").val()
this._updateGaldrSD(rollData)
rollData.srTotal = 14 + (rollData.sdGaldr || 0)
$("#srTotal").text(rollData.srTotal)
})
}
// Pour Sejdr: recalculer srTotal quand DM change
if (rollData.mode === "sejdr") {
$("#bonusdefense").on("change", () => {
rollData.bonusdefense = parseInt($("#bonusdefense").val()) || 0
rollData.srTotal = 14 + rollData.bonusdefense
$("#srTotal").text(rollData.srTotal)
})
}
// Pour Rune: recalculer srTotal quand support ou puissance changent
if (rollData.mode === "rune") {
$("#supportRune, #puissanceRune").on("change", () => {
rollData.supportRune = $("#supportRune").val()
rollData.puissanceRune = parseInt($("#puissanceRune").val()) || 1
this._updateRuneData(rollData, actor)
$("#runeDuree").text(rollData.runeDuree)
$("#runeDureeVie").text(rollData.runeDureeVie)
$("#srTotal").text(rollData.srTotal)
})
}
},
rejectClose: false,
})
}
/**
* Perform the roll based on mode
* @param {Object} rollData - The roll data
* @private
*/
static _performRoll(rollData) {
if (rollData.mode === "attribut") {
YggdrasillUtility.rollAttribute(rollData)
} else {
YggdrasillUtility.rollYggdrasill(rollData)
}
}
/**
* Mettre à jour rollData avec les valeurs du formulaire
* @param {Object} rollData - L'objet rollData à mettre à jour
* @param {HTMLFormControlsCollection} formElements - Les éléments du formulaire
* @param {YggdrasillActor} actor - L'acteur pour récupérer les données
* @private
*/
static _updateRollDataFromForm(rollData, formElements, actor) {
// Caractéristique (pour compétences)
if (formElements.caracName) {
const caracKey = formElements.caracName.value
rollData.caracName = caracKey
rollData.selectedCarac = this._findCaracByKey(actor, caracKey)
}
// Type d'attaque (pour armes)
if (formElements.typeAttack) {
rollData.attackDef = rollData.attackDef || {}
rollData.attackDef.typeAttack = formElements.typeAttack.value
this._updateAttackData(rollData, actor)
}
// Bonus/Malus
if (formElements.bonusMalus) {
rollData.bonusMalus = parseInt(formElements.bonusMalus.value) || 0
}
// Furor
if (formElements.furorUsage) {
rollData.furorUsage = parseInt(formElements.furorUsage.value) || 0
}
// Seuil de Réussite
if (formElements.sr) {
rollData.sr = parseInt(formElements.sr.value) || 9
}
// Galdr options
if (formElements.dureeGaldr) {
rollData.dureeGaldr = formElements.dureeGaldr.value
}
if (formElements.nbCibles) {
rollData.nbCibles = formElements.nbCibles.value
}
if (formElements.zoneGaldr) {
rollData.zoneGaldr = formElements.zoneGaldr.value
}
if (rollData.mode === "galdr") {
this._updateGaldrSD(rollData)
rollData.srTotal = 14 + (rollData.sdGaldr || 0)
}
// Sejdr options
if (formElements.bonusdefense) {
rollData.bonusdefense = parseInt(formElements.bonusdefense.value) || 0
}
if (rollData.mode === "sejdr") {
rollData.srTotal = 14 + (rollData.bonusdefense || 0)
}
// Rune options
if (formElements.runeDuree) {
rollData.runeDuree = formElements.runeDuree.value
}
}
/**
* Find characteristic by key in actor
* @param {YggdrasillActor} actor
* @param {String} caracKey
* @returns {Object}
* @private
*/
static _findCaracByKey(actor, caracKey) {
for (let [categKey, categ] of Object.entries(actor.system.carac)) {
for (let [key, carac] of Object.entries(categ.carac)) {
if (key === caracKey) {
return { ...carac, categName: categKey, caracName: key }
}
}
}
return null
}
/**
* Update attack data based on attack type
* @param {Object} rollData
* @param {YggdrasillActor} actor
* @private
*/
static _updateAttackData(rollData, actor) {
const config = game.system.yggdrasill.config
const attackType = rollData.attackDef.typeAttack
const attackMode = config.attackMode?.[attackType]
if (attackMode) {
rollData.attackData = rollData.attackData || {}
rollData.attackData.categName = attackMode.categName
rollData.attackData.caracName = attackMode.caracName
rollData.attackData.malus = this._computeValue(attackMode.malus, actor)
rollData.attackData.bonusdegats = this._computeValue(attackMode.bonusdegats, actor)
rollData.attackData.protection = this._computeValue(attackMode.protection, actor)
rollData.attackData.label = attackMode.label
rollData.attackData.description = attackMode.description
}
}
/**
* Compute value from string formula like "puissance;3"
* @param {String|Number} value
* @param {YggdrasillActor} actor
* @returns {Number}
* @private
*/
static _computeValue(value, actor) {
if (typeof value === 'number') return value
if (!value || value === "0") return 0
const parts = String(value).split(';')
if (parts.length === 2) {
const caracName = parts[0]
const multiplier = parseInt(parts[1]) || 1
const caracValue = this._getCaracValue(actor, caracName)
return caracValue * multiplier
}
return 0
}
/**
* Get characteristic value by name
* @param {YggdrasillActor} actor
* @param {String} caracName
* @returns {Number}
* @private
*/
static _getCaracValue(actor, caracName) {
for (let categ of Object.values(actor.system.carac)) {
if (categ.carac && categ.carac[caracName]) {
return categ.carac[caracName].value || 0
}
}
return 0
}
/**
* Update rune data based on support and power
* @param {Object} rollData
* @param {YggdrasillActor} actor
* @private
*/
static _updateRuneData(rollData, actor) {
let support = 0
rollData.dureeRune = 6 - (rollData.agiliteCarac?.value || 0)
if (rollData.supportRune === "peau") {
support = 3
rollData.echelleDuree = "Actions"
rollData.echelleDureeVie = "Heures"
} else if (rollData.supportRune === "tissu") {
support = 6
rollData.echelleDuree = "Tours"
rollData.echelleDureeVie = "Jours"
} else if (rollData.supportRune === "cuir") {
support = 9
rollData.echelleDuree = "Minutes"
rollData.echelleDureeVie = "Semaines"
} else if (rollData.supportRune === "bois") {
support = 12
rollData.echelleDuree = "Heures"
rollData.echelleDureeVie = "Mois"
} else if (rollData.supportRune === "pierremetal") {
support = 15
rollData.echelleDuree = "Jours"
rollData.echelleDureeVie = "Années"
}
rollData.runeDuree = `${rollData.dureeRune} ${rollData.echelleDuree}`
rollData.runeDureeVie = `${rollData.competence?.system.niveau || 1} ${rollData.echelleDureeVie}`
rollData.srTotal = (rollData.puissanceRune || 1) + (Number(rollData.sort?.system.niveau || 0) * 3) + support
}
/**
* Update Galdr SD based on duration and targets
* @param {Object} rollData
* @private
*/
static _updateGaldrSD(rollData) {
let sdDuree = Number(dureeGaldrSD[rollData.dureeGaldr]) || 0
let sdVar = 0
if (rollData.sort?.system.voie === "illusion") {
sdVar = Number(zonesciblesGaldrSD[rollData.zoneGaldr]) || 0
} else {
sdVar = Number(ciblesGaldrSD[rollData.nbCibles]) || 0
}
rollData.sdGaldr = sdDuree + sdVar
}
}

17
modules/models/armecc.mjs Normal file
View File

@@ -0,0 +1,17 @@
/**
* Data model pour les armes de corps à corps
*/
export default class ArmeccDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
degat: new fields.NumberField({ initial: 0, integer: true }),
solidite: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,18 @@
/**
* Data model pour les armes à distance
*/
export default class ArmedistDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
degat: new fields.NumberField({ initial: 0, integer: true }),
solidite: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
portee: new fields.StringField({ initial: "" }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

16
modules/models/armure.mjs Normal file
View File

@@ -0,0 +1,16 @@
/**
* Data model pour les armures
*/
export default class ArmureDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
protection: new fields.StringField({ initial: "" }),
enc: new fields.NumberField({ initial: 0, integer: true }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les blessures
*/
export default class BlessureDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,18 @@
/**
* Data model pour les boucliers
*/
export default class BouclierDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
equipe: new fields.BooleanField({ initial: false }),
defensebonus: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
enccomb: new fields.NumberField({ initial: 0, integer: true }),
solidite: new fields.NumberField({ initial: 0, integer: true }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,16 @@
/**
* Data model pour les compétences
*/
export default class CompetenceDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
isspecialisation: new fields.BooleanField({ initial: false }),
categorie: new fields.StringField({ initial: "" }),
specialisation: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
niveauunrequis: new fields.BooleanField({ initial: false })
};
}
}

11
modules/models/don.mjs Normal file
View File

@@ -0,0 +1,11 @@
/**
* Data model pour les dons
*/
export default class DonDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,12 @@
/**
* Data model pour les effets magiques
*/
export default class EffetmagiqueDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
origine: new fields.StringField({ initial: "" }),
effet: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,15 @@
/**
* Data model pour l'équipement
*/
export default class EquipementDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
equipe: new fields.BooleanField({ initial: false }),
enc: new fields.NumberField({ initial: 0, integer: true }),
valeur: new fields.NumberField({ initial: 0, integer: true }),
quantite: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,11 @@
/**
* Data model pour les faiblesses
*/
export default class FaiblesseDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,87 @@
/**
* Data model pour les figurants
*/
export default class FigurantDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
biodata: new fields.SchemaField({
age: new fields.NumberField({ initial: 0, integer: true }),
taille: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" }),
poids: new fields.NumberField({ initial: 0, integer: true }),
notes: new fields.HTMLField({ initial: "" })
}),
attributs: new fields.SchemaField({
conflit: new fields.SchemaField({
values: new fields.SchemaField({
offensif: new fields.SchemaField({
label: new fields.StringField({ initial: "Offensif" }),
value: new fields.NumberField({ initial: 0, integer: true }),
degats: new fields.BooleanField({ initial: true })
}),
defensif: new fields.SchemaField({
label: new fields.StringField({ initial: "Défensif" }),
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: false }),
label: new fields.StringField({ initial: "Conflit" })
}),
relationnel: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: true }),
label: new fields.StringField({ initial: "Relationnel" })
}),
physique: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: true }),
label: new fields.StringField({ initial: "Physique" })
}),
mental: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: true }),
label: new fields.StringField({ initial: "Mental" })
}),
mystique: new fields.SchemaField({
values: new fields.SchemaField({
actif: new fields.SchemaField({
label: new fields.StringField({ initial: "Actif" }),
value: new fields.NumberField({ initial: 0, integer: true })
}),
passif: new fields.SchemaField({
label: new fields.StringField({ initial: "Passif" }),
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: false }),
label: new fields.StringField({ initial: "Mystique" })
}),
vitalite: new fields.SchemaField({
values: new fields.SchemaField({
defaut: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
})
}),
rollable: new fields.BooleanField({ initial: false }),
label: new fields.StringField({ initial: "Vitalité" })
})
}),
etat: new fields.SchemaField({
etat: new fields.StringField({ initial: "fringant" })
})
};
}
}

27
modules/models/index.mjs Normal file
View File

@@ -0,0 +1,27 @@
/**
* Index des DataModels pour Yggdrasill
* Ce fichier centralise tous les exports des modèles de données
*/
// Modèles d'acteurs
export { default as PersonnageDataModel } from './personnage.mjs';
export { default as FigurantDataModel } from './figurant.mjs';
// Modèles d'items
export { default as CompetenceDataModel } from './competence.mjs';
export { default as DonDataModel } from './don.mjs';
export { default as FaiblesseDataModel } from './faiblesse.mjs';
export { default as BlessureDataModel } from './blessure.mjs';
export { default as MaladieDataModel } from './maladie.mjs';
export { default as PoisonDataModel } from './poison.mjs';
export { default as ProuesseDataModel } from './prouesse.mjs';
export { default as SortsejdrDataModel } from './sortsejdr.mjs';
export { default as SortgaldrDataModel } from './sortgaldr.mjs';
export { default as RuneDataModel } from './rune.mjs';
export { default as ArmeccDataModel } from './armecc.mjs';
export { default as ArmedistDataModel } from './armedist.mjs';
export { default as ArmureDataModel } from './armure.mjs';
export { default as BouclierDataModel } from './bouclier.mjs';
export { default as EquipementDataModel } from './equipement.mjs';
export { default as MonnaieDataModel } from './monnaie.mjs';
export { default as EffetmagiqueDataModel } from './effetmagique.mjs';

View File

@@ -0,0 +1,13 @@
/**
* Data model pour les maladies
*/
export default class MaladieDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
periode: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,13 @@
/**
* Data model pour la monnaie
*/
export default class MonnaieDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
quantite: new fields.NumberField({ initial: 0, integer: true }),
enc: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,158 @@
/**
* Data model pour les personnages
*/
export default class PersonnageDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
biodata: new fields.SchemaField({
nom: new fields.StringField({ initial: "" }),
archetype: new fields.StringField({ initial: "" }),
profession: new fields.StringField({ initial: "" }),
royaume: new fields.StringField({ initial: "" }),
age: new fields.NumberField({ initial: 0, integer: true }),
taille: new fields.NumberField({ initial: 0, integer: true }),
poids: new fields.NumberField({ initial: 0, integer: true }),
don: new fields.StringField({ initial: "" }),
faiblesse: new fields.StringField({ initial: "" }),
pointlegende: new fields.NumberField({ initial: 0, integer: true }),
renomee: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" }),
notes: new fields.HTMLField({ initial: "" }),
tiragerunes: new fields.StringField({ initial: "" }),
gmnotes: new fields.HTMLField({ initial: "" })
}),
carac: new fields.SchemaField({
corps: new fields.SchemaField({
label: new fields.StringField({ initial: "Corps" }),
carac: new fields.SchemaField({
puissance: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Puissance" }),
categorie: new fields.StringField({ initial: "corps" }),
abbrev: new fields.StringField({ initial: "pui" })
}),
vigueur: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Vigueur" }),
categorie: new fields.StringField({ initial: "corps" }),
abbrev: new fields.StringField({ initial: "vig" })
}),
agilite: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Agilité" }),
categorie: new fields.StringField({ initial: "corps" }),
abbrev: new fields.StringField({ initial: "agi" })
})
})
}),
esprit: new fields.SchemaField({
label: new fields.StringField({ initial: "Esprit" }),
carac: new fields.SchemaField({
intellect: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Intellect" }),
categorie: new fields.StringField({ initial: "esprit" }),
abbrev: new fields.StringField({ initial: "int" })
}),
perception: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Perception" }),
categorie: new fields.StringField({ initial: "esprit" }),
abbrev: new fields.StringField({ initial: "per" })
}),
tenacite: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Tenacité" }),
categorie: new fields.StringField({ initial: "esprit" }),
abbrev: new fields.StringField({ initial: "ten" })
})
})
}),
ame: new fields.SchemaField({
label: new fields.StringField({ initial: "Ame" }),
carac: new fields.SchemaField({
charisme: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Charisme" }),
categorie: new fields.StringField({ initial: "ame" }),
abbrev: new fields.StringField({ initial: "cha" })
}),
communication: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Communication" }),
categorie: new fields.StringField({ initial: "ame" }),
abbrev: new fields.StringField({ initial: "com" })
}),
instinct: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Instinct" }),
categorie: new fields.StringField({ initial: "ame" }),
abbrev: new fields.StringField({ initial: "ins" })
})
})
})
}),
furor: new fields.SchemaField({
value: new fields.NumberField({ initial: 1, integer: true }),
max: new fields.NumberField({ initial: 1, integer: true }),
label: new fields.StringField({ initial: "Furor" })
}),
xp: new fields.SchemaField({
total: new fields.NumberField({ initial: 1, integer: true }),
current: new fields.NumberField({ initial: 1, integer: true }),
label: new fields.StringField({ initial: "XP" })
}),
renommee: new fields.SchemaField({
value: new fields.NumberField({ initial: 1, integer: true }),
label: new fields.StringField({ initial: "Renommée" })
}),
status: new fields.SchemaField({
epuise: new fields.BooleanField({ initial: false }),
blesse: new fields.BooleanField({ initial: false }),
meurtri: new fields.BooleanField({ initial: false })
}),
caracsecondaire: new fields.SchemaField({
reaction: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Réaction" }),
abbrev: new fields.StringField({ initial: "rea" })
}),
defensephy: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
bonusmalus: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Défense Physique" }),
abbrev: new fields.StringField({ initial: "dp" })
}),
defensemen: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
bonusmalus: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Défense Mentale" }),
abbrev: new fields.StringField({ initial: "dm" })
}),
deplacement: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Déplacement" }),
abbrev: new fields.StringField({ initial: "dep" })
}),
capaenc: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Capacité d'Encombrement" }),
abbrev: new fields.StringField({ initial: "cpe" })
}),
pv: new fields.SchemaField({
value: new fields.NumberField({ initial: 10, integer: true }),
max: new fields.NumberField({ initial: 10, integer: true }),
min: new fields.NumberField({ initial: 0, integer: true }),
label: new fields.StringField({ initial: "Points de Vie" }),
abbrev: new fields.StringField({ initial: "pv" })
})
})
};
}
}

13
modules/models/poison.mjs Normal file
View File

@@ -0,0 +1,13 @@
/**
* Data model pour les poisons
*/
export default class PoisonDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
type: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,16 @@
/**
* Data model pour les prouesses
*/
export default class ProuesseDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
categorie: new fields.StringField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
armes: new fields.StringField({ initial: "" }),
prerequis: new fields.StringField({ initial: "" }),
modificateur: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

12
modules/models/rune.mjs Normal file
View File

@@ -0,0 +1,12 @@
/**
* Data model pour les runes
*/
export default class RuneDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
niveau: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,13 @@
/**
* Data model pour les sorts Galdr
*/
export default class SortgaldrDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
voie: new fields.StringField({ initial: "" }),
sd: new fields.NumberField({ initial: 0, integer: true }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -0,0 +1,17 @@
/**
* Data model pour les sorts Sejdr
*/
export default class SortsejdrDataModel extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
forme: new fields.StringField({ initial: "" }),
preparation: new fields.StringField({ initial: "" }),
niveau: new fields.NumberField({ initial: 0, integer: true }),
malus: new fields.NumberField({ initial: 0, integer: true }),
duree: new fields.StringField({ initial: "" }),
zone: new fields.StringField({ initial: "" }),
description: new fields.HTMLField({ initial: "" })
};
}
}

View File

@@ -13,7 +13,7 @@ export class YggdrasillActorSheet extends foundry.appv1.sheets.ActorSheet {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["yggdrasill", "sheet", "actor"],
template: "systems/fvtt-yggdrasill/templates/actor-sheet.html",
template: "systems/fvtt-yggdrasill/templates/actor-personnage-sheet.hbs",
width: 680,
height: 740,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],

View File

@@ -1,6 +1,6 @@
/* -------------------------------------------- */
import { YggdrasillUtility } from "./yggdrasill-utility.js";
import { YggdrasillRoll } from "./yggdrasill-roll-dialog.js";
import { YggdrasillRollDialog } from "./applications/yggdrasill-roll-dialog.mjs";
/* -------------------------------------------- */
const statusEffects = [
@@ -441,45 +441,37 @@ export class YggdrasillActor extends Actor {
let sumame = baseame.charisme.value + baseame.communication.value + baseame.instinct.value
let newPV = (sumcorps*3) + (sumesprit *2) + sumame;
if ( newPV != this.system.caracsecondaire.pv.max) {
this.system.caracsecondaire.pv.max = newPV;
this.update( { 'system.caracsecondaire.pv.max': newPV });
}
this.system.caracsecondaire.reaction.value = baseesprit.intellect.value + baseesprit.perception.value + baseame.instinct.value;
let newReac = baseesprit.intellect.value + baseesprit.perception.value + baseame.instinct.value;
if ( newReac != this.system.caracsecondaire.reaction.max) {
this.system.caracsecondaire.reaction.max = newReac
this.update( { 'system.caracsecondaire.reaction.max': newReac });
}
this.system.caracsecondaire.defensephy.value = basecorps.agilite.value + basecorps.vigueur.value + baseame.instinct.value;
let newDef = basecorps.agilite.value + basecorps.vigueur.value + baseame.instinct.value;
if ( newDef != this.system.caracsecondaire.defensephy.max) {
this.system.caracsecondaire.defensephy.max = newDef
this.update( { 'system.caracsecondaire.defensephy.max': newDef });
// Initialize bonusmalus if not set
if (this.system.caracsecondaire.defensephy.bonusmalus === undefined) {
this.system.caracsecondaire.defensephy.bonusmalus = 0
}
this.system.caracsecondaire.defensephy.total = newDef + this.system.caracsecondaire.defensephy.bonusmalus
this.system.caracsecondaire.defensemen.value = baseesprit.tenacite.value + baseame.instinct.value + baseesprit.intellect.value;
newDef = baseesprit.tenacite.value + baseame.instinct.value + baseesprit.intellect.value;
if ( newDef != this.system.caracsecondaire.defensemen.max) {
this.system.caracsecondaire.defensemen.max = newDef
this.update( { 'system.caracsecondaire.defensemen.max': newDef });
// Initialize bonusmalus if not set
if (this.system.caracsecondaire.defensemen.bonusmalus === undefined) {
this.system.caracsecondaire.defensemen.bonusmalus = 0
}
this.system.caracsecondaire.defensemen.total = newDef + this.system.caracsecondaire.defensemen.bonusmalus
this.system.caracsecondaire.deplacement.value = basecorps.agilite.value + basecorps.vigueur.value;
let depl = basecorps.agilite.value + basecorps.vigueur.value;
if ( depl != this.system.caracsecondaire.deplacement.max) {
this.system.caracsecondaire.deplacement.max = depl
this.update( { 'system.caracsecondaire.deplacement.max': depl });
}
this.system.caracsecondaire.capaenc.value = (basecorps.puissance.value * 2) + basecorps.vigueur.value;
let enc = (basecorps.puissance.value * 2) + basecorps.vigueur.value;
if ( enc != this.system.caracsecondaire.capaenc.max ) {
this.system.caracsecondaire.capaenc.max = enc
this.update( { 'system.caracsecondaire.capaenc.max': enc });
}
//console.log("CARAC SEC", this.system.caracsecondaire)
}
@@ -531,9 +523,7 @@ export class YggdrasillActor extends Actor {
isBlesse: this.system.etat.etat == "blesse",
}
this.addDefaultRoll(rollData)
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Attribut non trouvée");
}
@@ -541,11 +531,11 @@ export class YggdrasillActor extends Actor {
/* -------------------------------------------- */
addDefaultRoll(rollData) {
rollData.optionsBonusMalus= YggdrasillUtility.createOptions(-15, 15)
rollData.bonusMalus= "0"
rollData.bonusMalus= 0
rollData.optionsFuror= YggdrasillUtility.createOptions(0, this.getCurrentFuror())
rollData.furorUsage= "0"
rollData.furorUsage= 0
rollData.optionsBD= YggdrasillUtility.createOptions(0, +15)
rollData.sr= "0"
rollData.sr= 0
rollData.puissanceRune = 1
rollData.optionsPuissanceRune= YggdrasillUtility.createOptions(1, +15)
rollData.supportRune= "peau"
@@ -571,9 +561,7 @@ export class YggdrasillActor extends Actor {
}
this.addDefaultRoll(rollData)
console.log("CARAC : ", rollData, this.system.carac);
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Caractéristique non trouvée");
}
@@ -598,9 +586,7 @@ export class YggdrasillActor extends Actor {
isMeurtri: this.isMeurtri(),
}
this.addDefaultRoll(rollData)
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Compétence non trouvée");
}
@@ -691,9 +677,7 @@ export class YggdrasillActor extends Actor {
}
this.addDefaultRoll(rollData)
rollData.sr = 14
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Sortilège ou Compétence non trouvée !", sort, compName);
}
@@ -734,9 +718,7 @@ export class YggdrasillActor extends Actor {
}
this.addDefaultRoll(rollData)
rollData.sr = 14
let rollDialog = await YggdrasillRoll.create( this, rollData);
console.log(rollDialog);
rollDialog.render( true );
await YggdrasillRollDialog.create( this, rollData);
} else {
ui.notifications.warn("Arme ou Compétence Martiale non trouvée !", arme, compName);
}

View File

@@ -55,16 +55,16 @@ export const YGGDRASILL_CONFIG = {
{ key:"visee", label:"Attaque Visée" }
],
optionsSR : [
{key: "0", label: "Aucun"},
{key: "5", label: "Très Simple (5)"},
{key: "7", label: "Simple (7)"},
{key: "10", label: "Aisé (10)"},
{key: "14", label: "Moyen (14)"},
{key: "19", label: "Difficile (19)"},
{key: "25", label: "Très Difficile (25)"},
{key: "32", label: "Exceptionnel (32)"},
{key: "40", label: "Légendaire (40)"},
{key: "49", label: "Divin (49)"}
{key: 0, label: "Aucun"},
{key: 5, label: "Très Simple (5)"},
{key: 7, label: "Simple (7)"},
{key: 10, label: "Aisé (10)"},
{key: 14, label: "Moyen (14)"},
{key: 19, label: "Difficile (19)"},
{key: 25, label: "Très Difficile (25)"},
{key: 32, label: "Exceptionnel (32)"},
{key: 40, label: "Légendaire (40)"},
{key: 49, label: "Divin (49)"}
],
optionsCarac: [

View File

@@ -1,103 +1,115 @@
import { YggdrasillUtility } from "./yggdrasill-utility.js";
import { YGGDRASILL_CONFIG } from "./yggdrasill-config.js";
const { HandlebarsApplicationMixin } = foundry.applications.api;
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
* @extends {ItemSheetV2}
*/
export class YggdrasillItemSheet extends foundry.appv1.sheets.ItemSheet {
export class YggdrasillItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
constructor(options = {}) {
super(options);
this.#dragDrop = this.#createDragDropHandlers();
}
#dragDrop;
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-yggdrasill", "sheet", "item"],
template: "systems/fvtt-yggdrasill/templates/item-sheet.html",
static DEFAULT_OPTIONS = {
classes: ["fvtt-yggdrasill", "item"],
position: {
width: 550,
height: 550
//tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}]
});
}
height: 550,
},
form: {
submitOnChange: true,
},
window: {
resizable: true,
},
actions: {
editImage: YggdrasillItemSheet.#onEditImage,
},
};
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
// Add "Post to chat" button
// We previously restricted this to GM and editable items only. If you ever find this comment because it broke something: eh, sorry!
buttons.unshift(
{
class: "post",
icon: "fas fa-comment",
onclick: ev => {}
})
return buttons
}
/**
* Tab groups state
* @type {object}
*/
tabGroups = { primary: "description" };
/* -------------------------------------------- */
/** @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;
static PARTS = {
sheet: {
template: "systems/fvtt-yggdrasill/templates/item-{type}-sheet.hbs"
}
};
/** @override */
async _prepareContext() {
// Ensure config is always available with fallback to direct import
const config = game.system?.config || game.system?.yggdrasill?.config || YGGDRASILL_CONFIG || {};
// Create options for niveau 0-5
const optionsNiveaux4 = {};
for (let i = 0; i <= 5; i++) {
optionsNiveaux4[`${i}`] = `${i}`;
}
/* -------------------------------------------- */
async getData() {
const objectData = foundry.utils.duplicate(this.object);
const optionsBase = YggdrasillUtility.createDirectOptionList(0, 20) || {};
let formData = {
title: this.title,
id: objectData.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
const context = {
fields: this.document.schema.fields,
systemFields: this.document.system.schema.fields,
item: this.document,
system: this.document.system,
data: this.document.system,
source: this.document.toObject(),
enrichedDescription: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description || "", { async: true }),
enrichedEffet: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.effet || "", { async: true }),
isEditMode: true,
isEditable: this.isEditable,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
data: foundry.utils.deepClone(this.object.system),
optionsBase: YggdrasillUtility.createDirectOptionList(0, 20),
optionsNiveaux4: Array.fromRange(5, 1),
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.description, {async: true}),
limited: this.object.limited,
options: this.options,
owner: this.document.isOwner,
isGM: game.user.isGM,
config: game.system.config
config: config,
optionsBase: optionsBase,
optionsNiveaux4: optionsNiveaux4,
};
return context;
}
return formData;
return context;
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
_onRender(context, options) {
super._onRender(context, options);
this.#dragDrop.forEach((d) => d.bind(this.element));
}
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// #region Drag-and-Drop Workflow
/**
* Create drag-and-drop workflow handlers for this Application
*/
#createDragDropHandlers() {
return [];
}
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item");
const item = this.object.options.actor.items.get(li.data("item-id"));
item.sheet.render(true);
// #region Actions
/**
* Handle editing the item image
* @param {Event} event - The triggering event
*/
static async #onEditImage(event) {
event.preventDefault();
const filePicker = new FilePicker({
type: "image",
current: this.document.img,
callback: (path) => {
this.document.update({ img: path });
},
});
// Update Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.object.options.actor.deleteEmbeddedDocuments( "Item", [li.data("item-id") ] ).then( this.render(true));
});
}
/* -------------------------------------------- */
get template()
{
let type = this.item.type;
return `systems/fvtt-yggdrasill/templates/item-${type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
return this.object.update(formData);
filePicker.browse();
}
}

View File

@@ -9,14 +9,17 @@
/* -------------------------------------------- */
// Import Modules
import { YggdrasillActor } from "./yggdrasill-actor.js";
import { YggdrasillItemSheet } from "./yggdrasill-item-sheet.js";
import { YggdrasillActorSheet } from "./yggdrasill-actor-sheet.js";
import { YggdrasillFigurantSheet } from "./yggdrasill-figurant-sheet.js";
import { YggdrasillUtility } from "./yggdrasill-utility.js";
import { YggdrasillCombat } from "./yggdrasill-combat.js";
import { YGGDRASILL_CONFIG } from "./yggdrasill-config.js";
import { ClassCounter} from "https://www.uberwald.me/fvtt_appcount/count-class-ready.js"
// Import DataModels
import * as models from "./models/index.mjs";
// Import AppV2 Sheets
import * as sheets from "./applications/sheets/_module.mjs";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
@@ -46,30 +49,82 @@ Hooks.once("init", async function () {
/* -------------------------------------------- */
// Define custom Entity classes
CONFIG.Actor.documentClass = YggdrasillActor;
CONFIG.Actor.dataModels = {
personnage: models.PersonnageDataModel,
figurant: models.FigurantDataModel
};
CONFIG.Combat.documentClass = YggdrasillCombat;
CONFIG.Item.dataModels = {
competence: models.CompetenceDataModel,
don: models.DonDataModel,
faiblesse: models.FaiblesseDataModel,
blessure: models.BlessureDataModel,
maladie: models.MaladieDataModel,
poison: models.PoisonDataModel,
prouesse: models.ProuesseDataModel,
sortsejdr: models.SortsejdrDataModel,
sortgaldr: models.SortgaldrDataModel,
rune: models.RuneDataModel,
armecc: models.ArmeccDataModel,
armedist: models.ArmedistDataModel,
armure: models.ArmureDataModel,
bouclier: models.BouclierDataModel,
equipement: models.EquipementDataModel,
monnaie: models.MonnaieDataModel,
effetmagique: models.EffetmagiqueDataModel
};
CONFIG.Yggdrasill = {
}
game.system.yggdrasill = {
config: YGGDRASILL_CONFIG,
models,
sheets
}
/* -------------------------------------------- */
// Register sheet application classes
// Register AppV2 Actor Sheets
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("fvtt-yggdrasill", YggdrasillActorSheet, { types: ["personnage"], makeDefault: true });
foundry.documents.collections.Actors.registerSheet("fvtt-yggdrasill", YggdrasillFigurantSheet, { types: ["figurant"], makeDefault: false });
foundry.documents.collections.Actors.registerSheet("fvtt-yggdrasill", sheets.YggdrasillPersonnageSheet, { types: ["personnage"], makeDefault: true });
foundry.documents.collections.Actors.registerSheet("fvtt-yggdrasill", sheets.YggdrasillFigurantSheet, { types: ["figurant"], makeDefault: true });
// Register AppV2 Item Sheets
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", YggdrasillItemSheet, { makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillCompetenceSheet, { types: ["competence"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillDonSheet, { types: ["don"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillFaiblesseSheet, { types: ["faiblesse"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillBlessureSheet, { types: ["blessure"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillMaladieSheet, { types: ["maladie"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillPoisonSheet, { types: ["poison"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillProuesseSheet, { types: ["prouesse"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillSortsejdrSheet, { types: ["sortsejdr"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillSortgaldrSheet, { types: ["sortgaldr"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillRuneSheet, { types: ["rune"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillArmeccSheet, { types: ["armecc"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillArmedistSheet, { types: ["armedist"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillArmureSheet, { types: ["armure"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillBouclierSheet, { types: ["bouclier"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillEquipementSheet, { types: ["equipement"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillMonnaieSheet, { types: ["monnaie"], makeDefault: true });
foundry.documents.collections.Items.registerSheet("fvtt-yggdrasill", sheets.YggdrasillEffetmagiqueSheet, { types: ["effetmagique"], makeDefault: true });
});
/* -------------------------------------------- */
function welcomeMessage() {
async function welcomeMessage() {
const templateData = {};
const html = await foundry.applications.handlebars.renderTemplate("systems/fvtt-yggdrasill/templates/chat-welcome-message.hbs", templateData);
ChatMessage.create({
user: game.user.id,
whisper: [game.user.id],
content: `<div id="welcome-message-yggdrasill"><span class="rdd-roll-part"><strong>Bienvenue à Yggdrasill !</strong>
<p>Yggdrasill est un Jeu de Rôle publié par 7ième Cercle : https://www.7emecercle.com/7C_site/jeux-de-roles/yggdrasill/</p>
<br>Yggdrasill est une propriété de 7ième Cercle.
</div>
` });
content: html
});
}
/* -------------------------------------------- */
@@ -101,3 +156,28 @@ Hooks.on("chatMessage", (html, content, msg) => {
}
return true;
});
/* -------------------------------------------- */
/* Chat Message Rendering */
/* -------------------------------------------- */
Hooks.on("renderChatMessageHTML", (message, html, data) => {
// Handle collapsible magic details
html.querySelector('.magic-header.collapsible')?.addEventListener('click', function() {
const header = this;
const content = header.parentElement.querySelector('.magic-content');
// Toggle collapsed state
content?.classList.toggle('collapsed');
header.classList.toggle('expanded');
});
// Handle collapsible weapon details
html.querySelector('.weapon-header.collapsible')?.addEventListener('click', function() {
const header = this;
const content = header.parentElement.querySelector('.weapon-content');
// Toggle collapsed state
content?.classList.toggle('collapsed');
header.classList.toggle('expanded');
});
});

View File

@@ -1,212 +0,0 @@
import { YggdrasillUtility } from "./yggdrasill-utility.js";
const dureeGaldrSD = { "1d5a": 3, "1d10t": 6, "1d10m": 9, "1d10h": 12, "1d5j": 15};
const ciblesGaldrSD = { "1": 3, "2_4": 6, "5_9": 9, "10_49": 12, "50plus": 15};
const zonesciblesGaldrSD = { "INS10cm3": 3, "INS50cm3": 6, "INS1m3": 9, "INS5m3": 12, "INS10m3": 15};
export class YggdrasillRoll extends Dialog {
/* -------------------------------------------- */
static async create(actor, rollData ) {
let html
let h = 440;
if ( rollData.mode == "competence") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-competence.html', rollData);
h = 340;
} else if (rollData.mode == "carac") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-carac.html', rollData);
h = 320;
} else if (rollData.mode == "attribut") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-attribut.html', rollData);
h = 320;
} else if (rollData.mode == "armecc") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-armecc.html', rollData);
} else if (rollData.mode == "sejdr") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-sejdr.html', rollData);
} else if (rollData.mode == "rune") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-rune.html', rollData);
} else if (rollData.mode == "galdr") {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-galdr.html', rollData);
} else {
html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-yggdrasill/templates/roll-dialog-armetir.html', rollData);
}
let options = { classes: ["yggdrasilldialog"], width: 600, height: h, 'z-index': 99999 };
return new YggdrasillRoll(actor, rollData, html, options );
}
/* -------------------------------------------- */
constructor(actor, rollData, html, options, close = undefined) {
let conf = {
title: (rollData.mode == "competence") ? "Compétence" : "Caractéristique",
content: html,
buttons: {
roll: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer le Test",
callback: () => { this.roll() }
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler",
callback: () => { this.close() }
} },
default: "Roll",
close: close
}
super(conf, options);
this.actor = actor;
this.rollData = rollData;
}
/* -------------------------------------------- */
roll () {
if ( this.rollData.mode == "attribut") {
YggdrasillUtility.rollAttribute(this.rollData)
} else {
YggdrasillUtility.rollYggdrasill( this.rollData )
}
}
/* -------------------------------------------- */
updateGaldrSR( ) {
let sdDuree = Number(dureeGaldrSD[this.rollData.dureeGaldr]);
let sdVar = 0;
if ( this.rollData.sort.system.voie == "illusion") {
sdVar = Number(zonesciblesGaldrSD[this.rollData.zoneGaldr]);
} else {
sdVar = Number(ciblesGaldrSD[this.rollData.nbCibles]);
}
let SR = Number(this.rollData.sort.system.sd) + sdDuree + sdVar;
$("#srTotal").text(SR);
this.rollData.sr = SR;
}
/* -------------------------------------------- */
updateRuneSR() {
let support = 0;
this.rollData.dureeRune = 6 - this.rollData.agiliteCarac.value;
if ( this.rollData.supportRune == "peau") {
support = 3;
this.rollData.echelleDuree = "Actions";
this.rollData.echelleDureeVie = "Heures"
}
if ( this.rollData.supportRune == "tissu") {
support = 6;
this.rollData.echelleDuree = "Tours";
this.rollData.echelleDureeVie = "Jours"
}
if ( this.rollData.supportRune == "cuir") {
support = 9;
this.rollData.echelleDuree = "Minutes";
this.rollData.echelleDureeVie = "Semaines"
}
if ( this.rollData.supportRune == "bois") {
support = 12;
this.rollData.echelleDuree = "Heures";
this.rollData.echelleDureeVie = "Mois"
}
if ( this.rollData.supportRune == "pierremetal") {
support = 15;
this.rollData.echelleDuree = "Jours";
this.rollData.echelleDureeVie = "Années"
}
let SR = this.rollData.puissanceRune + (Number(this.rollData.sort.system.niveau)*3) + support;
$("#srTotal").text(SR);
$("#runeDuree").text( this.rollData.dureeRune + " " + this.rollData.echelleDuree);
$("#runeDureeVie").text( this.rollData.competence.system.niveau + " " + this.rollData.echelleDureeVie);
this.rollData.sr = SR;
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
let dialog = this;
function onLoad() {
if (dialog.rollData.mode == "competence") {
let carac = dialog.actor.getCarac( "Puissance" );
dialog.rollData.selectedCarac = carac;
} else if (dialog.rollData.mode == "armecc" || dialog.rollData.mode == "armedist" ) {
$("#caracName").text(dialog.rollData.selectedCarac.label);
$("#attackDescr").text(dialog.rollData.attackDef.description);
} else if ( dialog.rollData.mode == "sejdr" || dialog.rollData.mode == "rune" || dialog.rollData.mode == "galdr" ) {
$("#caracName").text(dialog.rollData.selectedCarac.label);
}
if (dialog.rollData.mode == "rune" ) {
dialog.updateRuneSR();
}
if (dialog.rollData.mode == "galdr" ) {
dialog.updateGaldrSR();
}
if (dialog.rollData.mode == "attribut") {
$("#attrValue").text("2d10+"+dialog.rollData.subAttr.value);
} else {
$("#caracValue").text(dialog.rollData.selectedCarac.value+"d10");
}
}
$(function () { onLoad(); });
html.find('#caracName').change((event) => {
let caracKey = event.currentTarget.value;
let carac = this.actor.getCarac( caracKey );
this.rollData.selectedCarac = carac;
$("#caracValue").text(carac.value+"d10");
});
html.find('#typeAttack').change((event) => {
let attackType = event.currentTarget.value;
let attackDef
if ( this.rollData.mode == 'armecc')
attackDef = this.actor.getAttaqueData( attackType);
else
attackDef = this.actor.getTirData( attackType);
this.rollData.attackDef = attackDef;
this.rollData.selectedCarac = attackDef.carac;
$("#caracValue").text(attackDef.carac.value+"d10");
$("#caracName").text(attackDef.carac.label);
$("#attackDescr").text(attackDef.description);
$("#malus").text(attackDef.malus);
});
html.find('#supportRune').change((event) => {
this.rollData.supportRune = event.currentTarget.value;
this.updateRuneSR();
});
html.find('#puissanceRune').change((event) => {
this.rollData.puissanceRune = Number(event.currentTarget.value);
this.updateRuneSR();
});
html.find('#dureeGaldr').change((event) => {
this.rollData.dureeGaldr = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#nbCibles').change((event) => {
this.rollData.nbCibles = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#zoneGaldr').change((event) => {
this.rollData.zoneGaldr = event.currentTarget.value;
this.updateGaldrSR();
});
html.find('#bonusMalus').change((event) => {
this.rollData.bonusMalus = Number(event.currentTarget.value);
});
html.find('#furorUsage').change((event) => {
this.rollData.furorUsage = Number(event.currentTarget.value);
});
html.find('#sr').change((event) => {
this.rollData.sr = Number(event.currentTarget.value);
});
html.find('#bonusdefense').change((event) => {
this.rollData.bonusdefense = Number(event.currentTarget.value);
});
}
}

View File

@@ -11,7 +11,7 @@ export class YggdrasillUtility {
static async preloadHandlebarsTemplates() {
const templatePaths = [
'systems/fvtt-yggdrasill/templates/actor-sheet.html',
'systems/fvtt-yggdrasill/templates/actor-personnage-sheet.hbs',
'systems/fvtt-yggdrasill/templates/editor-notes-gm.html'
]
return foundry.applications.handlebars.loadTemplates(templatePaths);
@@ -156,7 +156,7 @@ export class YggdrasillUtility {
console.log("ROLLLL ATTR!!!!", rollData);
this.createChatWithRollMode( rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result.html`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result-new.hbs`, rollData)
});
}
@@ -218,9 +218,14 @@ export class YggdrasillUtility {
rollData.niveauCompetence = niveauCompetence
// Compute total SR
rollData.srFinal = rollData.sr;
// Pour les sorts, utiliser srTotal si calculé, sinon sr + bonusdefense
if (rollData.srTotal !== undefined) {
rollData.srFinal = rollData.srTotal
} else {
rollData.srFinal = rollData.sr
if (rollData.bonusdefense) {
rollData.srFinal += rollData.bonusdefense;
rollData.srFinal += rollData.bonusdefense
}
}
if ( rollData.srFinal > 0 ) {
@@ -272,7 +277,7 @@ export class YggdrasillUtility {
console.log("ROLLLL!!!!", rollData);
this.createChatWithRollMode( rollData.alias, {
content: await renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result.html`, rollData)
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-yggdrasill/templates/chat-generic-result-new.hbs`, rollData)
});
}

4971
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

16
package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "fvtt-yggdrasill",
"version": "12.0.0",
"description": "Yggdrasill RPG for FoundryVTT - French",
"scripts": {
"build": "gulp build",
"watch": "gulp watch"
},
"author": "Uberwald/LeRatierBretonnien",
"license": "SEE LICENSE IN LICENCE.txt",
"devDependencies": {
"gulp": "^4.0.2",
"gulp-less": "^5.0.0",
"gulp-sourcemaps": "^3.0.0"
}
}

View File

@@ -1 +1 @@
MANIFEST-000036
MANIFEST-000053

View File

@@ -1,15 +1,8 @@
2025/05/01-23:39:32.648964 7fe00affd6c0 Recovering log #34
2025/05/01-23:39:32.659213 7fe00affd6c0 Delete type=0 #34
2025/05/01-23:39:32.659296 7fe00affd6c0 Delete type=3 #32
2025/05/02-00:00:01.649791 7fe0093ff6c0 Level-0 table #39: started
2025/05/02-00:00:01.653017 7fe0093ff6c0 Level-0 table #39: 2327 bytes OK
2025/05/02-00:00:01.659302 7fe0093ff6c0 Delete type=0 #37
2025/05/02-00:00:01.659500 7fe0093ff6c0 Manual compaction at level-0 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)
2025/05/02-00:00:01.690344 7fe0093ff6c0 Manual compaction at level-1 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at '!items!x3aMYeKa73GxJ1Ge' @ 45 : 1
2025/05/02-00:00:01.690353 7fe0093ff6c0 Compacting 1@1 + 1@2 files
2025/05/02-00:00:01.693550 7fe0093ff6c0 Generated table #40@1: 15 keys, 2327 bytes
2025/05/02-00:00:01.693570 7fe0093ff6c0 Compacted 1@1 + 1@2 files => 2327 bytes
2025/05/02-00:00:01.700976 7fe0093ff6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
2025/05/02-00:00:01.701159 7fe0093ff6c0 Delete type=2 #5
2025/05/02-00:00:01.701309 7fe0093ff6c0 Delete type=2 #39
2025/05/02-00:00:01.701436 7fe0093ff6c0 Manual compaction at level-1 from '!items!x3aMYeKa73GxJ1Ge' @ 45 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)
2026/01/11-22:42:43.362261 7fd478fff6c0 Recovering log #51
2026/01/11-22:42:43.390767 7fd478fff6c0 Delete type=0 #51
2026/01/11-22:42:43.398607 7fd478fff6c0 Delete type=3 #49
2026/01/11-22:44:34.484930 7fd4627fc6c0 Level-0 table #56: started
2026/01/11-22:44:34.484958 7fd4627fc6c0 Level-0 table #56: 0 bytes OK
2026/01/11-22:44:34.491990 7fd4627fc6c0 Delete type=0 #54
2026/01/11-22:44:34.498603 7fd4627fc6c0 Manual compaction at level-0 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)
2026/01/11-22:44:34.498641 7fd4627fc6c0 Manual compaction at level-1 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/05/02-09:20:30.852464 7f844c2006c0 Recovering log #30
2024/05/02-09:20:30.862615 7f844c2006c0 Delete type=3 #28
2024/05/02-09:20:30.862709 7f844c2006c0 Delete type=0 #30
2024/05/02-09:24:00.910769 7f844ae006c0 Level-0 table #35: started
2024/05/02-09:24:00.910814 7f844ae006c0 Level-0 table #35: 0 bytes OK
2024/05/02-09:24:00.917379 7f844ae006c0 Delete type=0 #33
2024/05/02-09:24:00.931103 7f844ae006c0 Manual compaction at level-0 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)
2024/05/02-09:24:00.931173 7f844ae006c0 Manual compaction at level-1 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)
2026/01/11-22:38:18.324856 7fd4637fe6c0 Recovering log #47
2026/01/11-22:38:18.334440 7fd4637fe6c0 Delete type=3 #45
2026/01/11-22:38:18.334494 7fd4637fe6c0 Delete type=0 #47
2026/01/11-22:39:21.508921 7fd4627fc6c0 Level-0 table #52: started
2026/01/11-22:39:21.508963 7fd4627fc6c0 Level-0 table #52: 0 bytes OK
2026/01/11-22:39:21.514976 7fd4627fc6c0 Delete type=0 #50
2026/01/11-22:39:21.515163 7fd4627fc6c0 Manual compaction at level-0 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)
2026/01/11-22:39:21.515223 7fd4627fc6c0 Manual compaction at level-1 from '!items!3VBHojfjdD504ibv' @ 72057594037927935 : 1 .. '!items!x3aMYeKa73GxJ1Ge' @ 0 : 0; will stop at (end)

Binary file not shown.

BIN
packs/armes/MANIFEST-000053 Normal file

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000036
MANIFEST-000053

View File

@@ -1,15 +1,8 @@
2025/05/01-23:39:32.662878 7fe00a7fc6c0 Recovering log #34
2025/05/01-23:39:32.674030 7fe00a7fc6c0 Delete type=0 #34
2025/05/01-23:39:32.674181 7fe00a7fc6c0 Delete type=3 #32
2025/05/02-00:00:01.701554 7fe0093ff6c0 Level-0 table #39: started
2025/05/02-00:00:01.704718 7fe0093ff6c0 Level-0 table #39: 3197 bytes OK
2025/05/02-00:00:01.710800 7fe0093ff6c0 Delete type=0 #37
2025/05/02-00:00:01.741412 7fe0093ff6c0 Manual compaction at level-0 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)
2025/05/02-00:00:01.741481 7fe0093ff6c0 Manual compaction at level-1 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at '!items!qUXBrstsh5Oo8FEx' @ 24 : 1
2025/05/02-00:00:01.741491 7fe0093ff6c0 Compacting 1@1 + 1@2 files
2025/05/02-00:00:01.744814 7fe0093ff6c0 Generated table #40@1: 8 keys, 3197 bytes
2025/05/02-00:00:01.744839 7fe0093ff6c0 Compacted 1@1 + 1@2 files => 3197 bytes
2025/05/02-00:00:01.751538 7fe0093ff6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
2025/05/02-00:00:01.751680 7fe0093ff6c0 Delete type=2 #5
2025/05/02-00:00:01.751873 7fe0093ff6c0 Delete type=2 #39
2025/05/02-00:00:01.782461 7fe0093ff6c0 Manual compaction at level-1 from '!items!qUXBrstsh5Oo8FEx' @ 24 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)
2026/01/11-22:42:43.415431 7fd4637fe6c0 Recovering log #51
2026/01/11-22:42:43.448752 7fd4637fe6c0 Delete type=0 #51
2026/01/11-22:42:43.448972 7fd4637fe6c0 Delete type=3 #49
2026/01/11-22:44:34.511453 7fd4627fc6c0 Level-0 table #56: started
2026/01/11-22:44:34.511476 7fd4627fc6c0 Level-0 table #56: 0 bytes OK
2026/01/11-22:44:34.517400 7fd4627fc6c0 Delete type=0 #54
2026/01/11-22:44:34.523930 7fd4627fc6c0 Manual compaction at level-0 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)
2026/01/11-22:44:34.523965 7fd4627fc6c0 Manual compaction at level-1 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/05/02-09:20:30.867038 7f844cc006c0 Recovering log #30
2024/05/02-09:20:30.878552 7f844cc006c0 Delete type=3 #28
2024/05/02-09:20:30.878665 7f844cc006c0 Delete type=0 #30
2024/05/02-09:24:00.917585 7f844ae006c0 Level-0 table #35: started
2024/05/02-09:24:00.917644 7f844ae006c0 Level-0 table #35: 0 bytes OK
2024/05/02-09:24:00.924086 7f844ae006c0 Delete type=0 #33
2024/05/02-09:24:00.931117 7f844ae006c0 Manual compaction at level-0 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)
2024/05/02-09:24:00.931187 7f844ae006c0 Manual compaction at level-1 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)
2026/01/11-22:38:18.336786 7fd462ffd6c0 Recovering log #47
2026/01/11-22:38:18.346917 7fd462ffd6c0 Delete type=3 #45
2026/01/11-22:38:18.347006 7fd462ffd6c0 Delete type=0 #47
2026/01/11-22:39:21.528136 7fd4627fc6c0 Level-0 table #52: started
2026/01/11-22:39:21.528192 7fd4627fc6c0 Level-0 table #52: 0 bytes OK
2026/01/11-22:39:21.534692 7fd4627fc6c0 Delete type=0 #50
2026/01/11-22:39:21.541091 7fd4627fc6c0 Manual compaction at level-0 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)
2026/01/11-22:39:21.541131 7fd4627fc6c0 Manual compaction at level-1 from '!items!4YGPuZ813BWQaNbq' @ 72057594037927935 : 1 .. '!items!qUXBrstsh5Oo8FEx' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000036
MANIFEST-000053

View File

@@ -1,15 +1,8 @@
2025/05/01-23:39:32.635609 7fe00b7fe6c0 Recovering log #34
2025/05/01-23:39:32.645474 7fe00b7fe6c0 Delete type=0 #34
2025/05/01-23:39:32.645571 7fe00b7fe6c0 Delete type=3 #32
2025/05/02-00:00:01.629647 7fe0093ff6c0 Level-0 table #39: started
2025/05/02-00:00:01.633062 7fe0093ff6c0 Level-0 table #39: 4373 bytes OK
2025/05/02-00:00:01.639223 7fe0093ff6c0 Delete type=0 #37
2025/05/02-00:00:01.659477 7fe0093ff6c0 Manual compaction at level-0 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)
2025/05/02-00:00:01.670214 7fe0093ff6c0 Manual compaction at level-1 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at '!items!swTZ43FJRWkqjR75' @ 45 : 1
2025/05/02-00:00:01.670224 7fe0093ff6c0 Compacting 1@1 + 1@2 files
2025/05/02-00:00:01.673584 7fe0093ff6c0 Generated table #40@1: 15 keys, 4373 bytes
2025/05/02-00:00:01.673615 7fe0093ff6c0 Compacted 1@1 + 1@2 files => 4373 bytes
2025/05/02-00:00:01.679975 7fe0093ff6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
2025/05/02-00:00:01.680089 7fe0093ff6c0 Delete type=2 #5
2025/05/02-00:00:01.680247 7fe0093ff6c0 Delete type=2 #39
2025/05/02-00:00:01.701412 7fe0093ff6c0 Manual compaction at level-1 from '!items!swTZ43FJRWkqjR75' @ 45 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)
2026/01/11-22:42:43.343711 7fd478fff6c0 Recovering log #51
2026/01/11-22:42:43.360165 7fd478fff6c0 Delete type=0 #51
2026/01/11-22:42:43.360231 7fd478fff6c0 Delete type=3 #49
2026/01/11-22:44:34.492077 7fd4627fc6c0 Level-0 table #56: started
2026/01/11-22:44:34.492100 7fd4627fc6c0 Level-0 table #56: 0 bytes OK
2026/01/11-22:44:34.498484 7fd4627fc6c0 Delete type=0 #54
2026/01/11-22:44:34.498613 7fd4627fc6c0 Manual compaction at level-0 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)
2026/01/11-22:44:34.498635 7fd4627fc6c0 Manual compaction at level-1 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/05/02-09:20:30.837163 7f844cc006c0 Recovering log #30
2024/05/02-09:20:30.848664 7f844cc006c0 Delete type=3 #28
2024/05/02-09:20:30.848767 7f844cc006c0 Delete type=0 #30
2024/05/02-09:24:00.903725 7f844ae006c0 Level-0 table #35: started
2024/05/02-09:24:00.903795 7f844ae006c0 Level-0 table #35: 0 bytes OK
2024/05/02-09:24:00.910581 7f844ae006c0 Delete type=0 #33
2024/05/02-09:24:00.931083 7f844ae006c0 Manual compaction at level-0 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)
2024/05/02-09:24:00.931143 7f844ae006c0 Manual compaction at level-1 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)
2026/01/11-22:38:18.312395 7fd463fff6c0 Recovering log #47
2026/01/11-22:38:18.322989 7fd463fff6c0 Delete type=3 #45
2026/01/11-22:38:18.323061 7fd463fff6c0 Delete type=0 #47
2026/01/11-22:39:21.501777 7fd4627fc6c0 Level-0 table #52: started
2026/01/11-22:39:21.501807 7fd4627fc6c0 Level-0 table #52: 0 bytes OK
2026/01/11-22:39:21.508771 7fd4627fc6c0 Delete type=0 #50
2026/01/11-22:39:21.515149 7fd4627fc6c0 Manual compaction at level-0 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)
2026/01/11-22:39:21.515211 7fd4627fc6c0 Manual compaction at level-1 from '!items!4rM9IvDuijsjbAhI' @ 72057594037927935 : 1 .. '!items!swTZ43FJRWkqjR75' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000036
MANIFEST-000053

View File

@@ -1,15 +1,8 @@
2025/05/01-23:39:32.556908 7fe009ffb6c0 Recovering log #34
2025/05/01-23:39:32.567507 7fe009ffb6c0 Delete type=0 #34
2025/05/01-23:39:32.567628 7fe009ffb6c0 Delete type=3 #32
2025/05/02-00:00:01.567300 7fe0093ff6c0 Level-0 table #39: started
2025/05/02-00:00:01.570622 7fe0093ff6c0 Level-0 table #39: 18501 bytes OK
2025/05/02-00:00:01.576851 7fe0093ff6c0 Delete type=0 #37
2025/05/02-00:00:01.577006 7fe0093ff6c0 Manual compaction at level-0 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)
2025/05/02-00:00:01.609221 7fe0093ff6c0 Manual compaction at level-1 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at '!items!ylqZMDyXujUlSorr' @ 90 : 1
2025/05/02-00:00:01.609228 7fe0093ff6c0 Compacting 1@1 + 1@2 files
2025/05/02-00:00:01.612484 7fe0093ff6c0 Generated table #40@1: 30 keys, 18501 bytes
2025/05/02-00:00:01.612511 7fe0093ff6c0 Compacted 1@1 + 1@2 files => 18501 bytes
2025/05/02-00:00:01.618471 7fe0093ff6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
2025/05/02-00:00:01.618573 7fe0093ff6c0 Delete type=2 #5
2025/05/02-00:00:01.618669 7fe0093ff6c0 Delete type=2 #39
2025/05/02-00:00:01.618752 7fe0093ff6c0 Manual compaction at level-1 from '!items!ylqZMDyXujUlSorr' @ 90 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)
2026/01/11-22:42:43.240973 7fd463fff6c0 Recovering log #51
2026/01/11-22:42:43.255834 7fd463fff6c0 Delete type=0 #51
2026/01/11-22:42:43.255892 7fd463fff6c0 Delete type=3 #49
2026/01/11-22:44:34.459907 7fd4627fc6c0 Level-0 table #56: started
2026/01/11-22:44:34.459930 7fd4627fc6c0 Level-0 table #56: 0 bytes OK
2026/01/11-22:44:34.466013 7fd4627fc6c0 Delete type=0 #54
2026/01/11-22:44:34.472607 7fd4627fc6c0 Manual compaction at level-0 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)
2026/01/11-22:44:34.472642 7fd4627fc6c0 Manual compaction at level-1 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/05/02-09:20:30.743214 7f844cc006c0 Recovering log #30
2024/05/02-09:20:30.753988 7f844cc006c0 Delete type=3 #28
2024/05/02-09:20:30.754165 7f844cc006c0 Delete type=0 #30
2024/05/02-09:24:00.851545 7f844ae006c0 Level-0 table #35: started
2024/05/02-09:24:00.851582 7f844ae006c0 Level-0 table #35: 0 bytes OK
2024/05/02-09:24:00.858223 7f844ae006c0 Delete type=0 #33
2024/05/02-09:24:00.864778 7f844ae006c0 Manual compaction at level-0 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)
2024/05/02-09:24:00.874940 7f844ae006c0 Manual compaction at level-1 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)
2026/01/11-22:38:18.238976 7fd4637fe6c0 Recovering log #47
2026/01/11-22:38:18.249505 7fd4637fe6c0 Delete type=3 #45
2026/01/11-22:38:18.249563 7fd4637fe6c0 Delete type=0 #47
2026/01/11-22:39:21.489412 7fd4627fc6c0 Level-0 table #52: started
2026/01/11-22:39:21.489449 7fd4627fc6c0 Level-0 table #52: 0 bytes OK
2026/01/11-22:39:21.495431 7fd4627fc6c0 Delete type=0 #50
2026/01/11-22:39:21.515115 7fd4627fc6c0 Manual compaction at level-0 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)
2026/01/11-22:39:21.515177 7fd4627fc6c0 Manual compaction at level-1 from '!items!26CQEICWZs8Gw4Xi' @ 72057594037927935 : 1 .. '!items!ylqZMDyXujUlSorr' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000036
MANIFEST-000053

View File

@@ -1,15 +1,8 @@
2025/05/01-23:39:32.583707 7fe00b7fe6c0 Recovering log #34
2025/05/01-23:39:32.593612 7fe00b7fe6c0 Delete type=0 #34
2025/05/01-23:39:32.593661 7fe00b7fe6c0 Delete type=3 #32
2025/05/02-00:00:01.557564 7fe0093ff6c0 Level-0 table #39: started
2025/05/02-00:00:01.560815 7fe0093ff6c0 Level-0 table #39: 1604 bytes OK
2025/05/02-00:00:01.567160 7fe0093ff6c0 Delete type=0 #37
2025/05/02-00:00:01.576997 7fe0093ff6c0 Manual compaction at level-0 from '!items!RAhkadJOfEnVBLpy' @ 72057594037927935 : 1 .. '!items!mIBiTKqfqiiepeyz' @ 0 : 0; will stop at (end)
2025/05/02-00:00:01.598577 7fe0093ff6c0 Manual compaction at level-1 from '!items!RAhkadJOfEnVBLpy' @ 72057594037927935 : 1 .. '!items!mIBiTKqfqiiepeyz' @ 0 : 0; will stop at '!items!mIBiTKqfqiiepeyz' @ 9 : 1
2025/05/02-00:00:01.598591 7fe0093ff6c0 Compacting 1@1 + 1@2 files
2025/05/02-00:00:01.601754 7fe0093ff6c0 Generated table #40@1: 3 keys, 1604 bytes
2025/05/02-00:00:01.601786 7fe0093ff6c0 Compacted 1@1 + 1@2 files => 1604 bytes
2025/05/02-00:00:01.608938 7fe0093ff6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
2025/05/02-00:00:01.609061 7fe0093ff6c0 Delete type=2 #5
2025/05/02-00:00:01.609167 7fe0093ff6c0 Delete type=2 #39
2025/05/02-00:00:01.618745 7fe0093ff6c0 Manual compaction at level-1 from '!items!mIBiTKqfqiiepeyz' @ 9 : 1 .. '!items!mIBiTKqfqiiepeyz' @ 0 : 0; will stop at (end)
2026/01/11-22:42:43.275614 7fd478fff6c0 Recovering log #51
2026/01/11-22:42:43.291244 7fd478fff6c0 Delete type=0 #51
2026/01/11-22:42:43.291308 7fd478fff6c0 Delete type=3 #49
2026/01/11-22:44:34.447688 7fd4627fc6c0 Level-0 table #56: started
2026/01/11-22:44:34.447757 7fd4627fc6c0 Level-0 table #56: 0 bytes OK
2026/01/11-22:44:34.453835 7fd4627fc6c0 Delete type=0 #54
2026/01/11-22:44:34.472578 7fd4627fc6c0 Manual compaction at level-0 from '!items!RAhkadJOfEnVBLpy' @ 72057594037927935 : 1 .. '!items!mIBiTKqfqiiepeyz' @ 0 : 0; will stop at (end)
2026/01/11-22:44:34.472629 7fd4627fc6c0 Manual compaction at level-1 from '!items!RAhkadJOfEnVBLpy' @ 72057594037927935 : 1 .. '!items!mIBiTKqfqiiepeyz' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/05/02-09:20:30.774893 7f844cc006c0 Recovering log #30
2024/05/02-09:20:30.785238 7f844cc006c0 Delete type=3 #28
2024/05/02-09:20:30.785346 7f844cc006c0 Delete type=0 #30
2024/05/02-09:24:00.874955 7f844ae006c0 Level-0 table #35: started
2024/05/02-09:24:00.875023 7f844ae006c0 Level-0 table #35: 0 bytes OK
2024/05/02-09:24:00.882281 7f844ae006c0 Delete type=0 #33
2024/05/02-09:24:00.896915 7f844ae006c0 Manual compaction at level-0 from '!items!RAhkadJOfEnVBLpy' @ 72057594037927935 : 1 .. '!items!mIBiTKqfqiiepeyz' @ 0 : 0; will stop at (end)
2024/05/02-09:24:00.903558 7f844ae006c0 Manual compaction at level-1 from '!items!RAhkadJOfEnVBLpy' @ 72057594037927935 : 1 .. '!items!mIBiTKqfqiiepeyz' @ 0 : 0; will stop at (end)
2026/01/11-22:38:18.263695 7fd462ffd6c0 Recovering log #47
2026/01/11-22:38:18.273321 7fd462ffd6c0 Delete type=3 #45
2026/01/11-22:38:18.273380 7fd462ffd6c0 Delete type=0 #47
2026/01/11-22:39:21.475468 7fd4627fc6c0 Level-0 table #52: started
2026/01/11-22:39:21.475500 7fd4627fc6c0 Level-0 table #52: 0 bytes OK
2026/01/11-22:39:21.482505 7fd4627fc6c0 Delete type=0 #50
2026/01/11-22:39:21.489246 7fd4627fc6c0 Manual compaction at level-0 from '!items!RAhkadJOfEnVBLpy' @ 72057594037927935 : 1 .. '!items!mIBiTKqfqiiepeyz' @ 0 : 0; will stop at (end)
2026/01/11-22:39:21.489296 7fd4627fc6c0 Manual compaction at level-1 from '!items!RAhkadJOfEnVBLpy' @ 72057594037927935 : 1 .. '!items!mIBiTKqfqiiepeyz' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000036
MANIFEST-000053

View File

@@ -1,15 +1,8 @@
2025/05/01-23:39:32.570549 7fe00a7fc6c0 Recovering log #34
2025/05/01-23:39:32.580330 7fe00a7fc6c0 Delete type=0 #34
2025/05/01-23:39:32.580396 7fe00a7fc6c0 Delete type=3 #32
2025/05/02-00:00:01.547194 7fe0093ff6c0 Level-0 table #39: started
2025/05/02-00:00:01.550461 7fe0093ff6c0 Level-0 table #39: 3895 bytes OK
2025/05/02-00:00:01.557446 7fe0093ff6c0 Delete type=0 #37
2025/05/02-00:00:01.576988 7fe0093ff6c0 Manual compaction at level-0 from '!items!6IqVsJsYPrbxUJZX' @ 72057594037927935 : 1 .. '!items!mJSWXawEGARKlNja' @ 0 : 0; will stop at (end)
2025/05/02-00:00:01.577021 7fe0093ff6c0 Manual compaction at level-1 from '!items!6IqVsJsYPrbxUJZX' @ 72057594037927935 : 1 .. '!items!mJSWXawEGARKlNja' @ 0 : 0; will stop at '!items!mJSWXawEGARKlNja' @ 24 : 1
2025/05/02-00:00:01.577027 7fe0093ff6c0 Compacting 1@1 + 1@2 files
2025/05/02-00:00:01.580395 7fe0093ff6c0 Generated table #40@1: 8 keys, 3895 bytes
2025/05/02-00:00:01.580431 7fe0093ff6c0 Compacted 1@1 + 1@2 files => 3895 bytes
2025/05/02-00:00:01.586914 7fe0093ff6c0 compacted to: files[ 0 0 1 0 0 0 0 ]
2025/05/02-00:00:01.587137 7fe0093ff6c0 Delete type=2 #5
2025/05/02-00:00:01.587348 7fe0093ff6c0 Delete type=2 #39
2025/05/02-00:00:01.618727 7fe0093ff6c0 Manual compaction at level-1 from '!items!mJSWXawEGARKlNja' @ 24 : 1 .. '!items!mJSWXawEGARKlNja' @ 0 : 0; will stop at (end)
2026/01/11-22:42:43.257983 7fd4637fe6c0 Recovering log #51
2026/01/11-22:42:43.273297 7fd4637fe6c0 Delete type=0 #51
2026/01/11-22:42:43.273355 7fd4637fe6c0 Delete type=3 #49
2026/01/11-22:44:34.466106 7fd4627fc6c0 Level-0 table #56: started
2026/01/11-22:44:34.466129 7fd4627fc6c0 Level-0 table #56: 0 bytes OK
2026/01/11-22:44:34.472447 7fd4627fc6c0 Delete type=0 #54
2026/01/11-22:44:34.472618 7fd4627fc6c0 Manual compaction at level-0 from '!items!6IqVsJsYPrbxUJZX' @ 72057594037927935 : 1 .. '!items!mJSWXawEGARKlNja' @ 0 : 0; will stop at (end)
2026/01/11-22:44:34.472649 7fd4627fc6c0 Manual compaction at level-1 from '!items!6IqVsJsYPrbxUJZX' @ 72057594037927935 : 1 .. '!items!mJSWXawEGARKlNja' @ 0 : 0; will stop at (end)

View File

@@ -1,8 +1,8 @@
2024/05/02-09:20:30.758816 7f844c2006c0 Recovering log #30
2024/05/02-09:20:30.770889 7f844c2006c0 Delete type=3 #28
2024/05/02-09:20:30.770994 7f844c2006c0 Delete type=0 #30
2024/05/02-09:24:00.858366 7f844ae006c0 Level-0 table #35: started
2024/05/02-09:24:00.858395 7f844ae006c0 Level-0 table #35: 0 bytes OK
2024/05/02-09:24:00.864599 7f844ae006c0 Delete type=0 #33
2024/05/02-09:24:00.864804 7f844ae006c0 Manual compaction at level-0 from '!items!6IqVsJsYPrbxUJZX' @ 72057594037927935 : 1 .. '!items!mJSWXawEGARKlNja' @ 0 : 0; will stop at (end)
2024/05/02-09:24:00.874924 7f844ae006c0 Manual compaction at level-1 from '!items!6IqVsJsYPrbxUJZX' @ 72057594037927935 : 1 .. '!items!mJSWXawEGARKlNja' @ 0 : 0; will stop at (end)
2026/01/11-22:38:18.251489 7fd478fff6c0 Recovering log #47
2026/01/11-22:38:18.261390 7fd478fff6c0 Delete type=3 #45
2026/01/11-22:38:18.261445 7fd478fff6c0 Delete type=0 #47
2026/01/11-22:39:21.463016 7fd4627fc6c0 Level-0 table #52: started
2026/01/11-22:39:21.463081 7fd4627fc6c0 Level-0 table #52: 0 bytes OK
2026/01/11-22:39:21.469148 7fd4627fc6c0 Delete type=0 #50
2026/01/11-22:39:21.489211 7fd4627fc6c0 Manual compaction at level-0 from '!items!6IqVsJsYPrbxUJZX' @ 72057594037927935 : 1 .. '!items!mJSWXawEGARKlNja' @ 0 : 0; will stop at (end)
2026/01/11-22:39:21.489274 7fd4627fc6c0 Manual compaction at level-1 from '!items!6IqVsJsYPrbxUJZX' @ 72057594037927935 : 1 .. '!items!mJSWXawEGARKlNja' @ 0 : 0; will stop at (end)

Binary file not shown.

View File

@@ -1 +1 @@
MANIFEST-000036
MANIFEST-000053

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