Initial import

This commit is contained in:
2025-10-26 00:31:32 +02:00
commit 7107516a28
199 changed files with 16870 additions and 0 deletions

25
LICENCE.txt Normal file
View File

@@ -0,0 +1,25 @@
MIT License
Copyright (c) 2022 Uberwald/LeRatierBretonnien
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
This license does not apply to the compendium content listed in this software's
"packs" directory. See the README for licensing information for the compendium
packs.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

29
README.md Normal file
View File

@@ -0,0 +1,29 @@
# Système Foundry pour Mournblade avec le règles CYD 2.0 (French RPG, Titam France/Sombres Projets)
## EN
Unofficial system for Mournblade CYD 2.0 (French version from Titam France).
This system has been approved by Département des Sombres Projets ( http://www.titam-france.fr/ ), thanks !
Books are mandatory to play and are available at : http://www.titam-france.fr
## FR
Système non-officiel pour le JDR Mournblade (Titam France/Sombres Projets).
Ce système a été autorisé par le Département des Sombres Projets ( http://www.titam-france.fr/ ), merci à eux !
Les livres du jeu sont nécessaires pour jouer, et sont disponibles ici : http://www.titam-france.fr
# Credits
Mournblade, le jeu de rôle du Troisième Millénaire, is a property of Titam France/Sombres Projets.
# Developpement
LeRatierBretonnien
# Tests, icones et saisie des données
Prêtre, Blondin, Zechrub/Chris, Kyllian, Lightbringer

BIN
assets/fonts/CentaurMT.otf Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
assets/icons/adresse.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
assets/icons/arme.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
assets/icons/artefact.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
assets/icons/contacts.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
assets/icons/distance.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
assets/icons/melee.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
assets/icons/monnaie.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/icons/mutation.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
assets/icons/notoriete.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
assets/icons/presence.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
assets/icons/profil.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
assets/icons/puissance.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
assets/icons/reseau.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

BIN
assets/icons/talent.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

BIN
assets/icons/tir.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
assets/icons/trempe.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
assets/icons/vitesse.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 KiB

BIN
assets/ui/pc_sheet_bg.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

38
lang/fr.json Normal file
View File

@@ -0,0 +1,38 @@
{
"TYPES": {
"Actor": {
"personnage": "Personnage",
"cellule": "Cellule",
"creature": "Créature"
},
"Item": {
"artefact": "Artefact",
"arme": "Arme",
"talent": "Talent",
"historique": "Historique",
"profil": "Profil",
"competence": "Compétence",
"protection": "Protection",
"monnaie": "Monnaie",
"equipement": "Equipement",
"ressource": "Ressource",
"contact": "Contact",
"mutation": "Mutation",
"don": "Don",
"pacte": "Pacte",
"rune": "Rune",
"runeeffect": "Effet de Rune",
"tendance": "Tendance",
"traitchaotique": "Trait Chaotique",
"traitespece": "Trait d'Espèce"
}
},
"HAWKMOON": {
"ui": {
"editContact": "Modifier le contact",
"deleteContact": "Supprimer le contact",
"editTrait": "Modifier le trait",
"deleteTrait": "Supprimer le trait"
}
}
}

View File

@@ -0,0 +1,224 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
/* -------------------------------------------- */
export class MournbladeCYD2ActorSheet extends foundry.appv1.sheets.ActorSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-mournblade-cyd2", "sheet", "actor"],
template: "systems/fvtt-mournblade-cyd2/templates/actor-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
})
}
/* -------------------------------------------- */
async getData() {
const objectData = foundry.utils.duplicate(this.object)
let formData = {
title: this.title,
id: objectData.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
system: objectData.system,
effects: this.object.effects.map(e => foundry.utils.deepClone(e.data)),
limited: this.object.limited,
skills: this.actor.getSkills(),
armes: foundry.utils.duplicate(this.actor.getWeapons()),
monnaies: foundry.utils.duplicate(this.actor.getMonnaies()),
protections: foundry.utils.duplicate(this.actor.getArmors()),
historiques: foundry.utils.duplicate(this.actor.getHistoriques() || []),
talents: foundry.utils.duplicate(this.actor.getTalents() || []),
mutations: foundry.utils.duplicate(this.actor.getMutations() || []),
dons: foundry.utils.duplicate(this.actor.getDons() || []),
pactes: foundry.utils.duplicate(this.actor.getPactes() || []),
tendances: foundry.utils.duplicate(this.actor.getTendances() || []),
runes: foundry.utils.duplicate(this.actor.getRunes() || []),
traitsChaotiques: foundry.utils.duplicate(this.actor.getTraitsChaotiques() || []),
traitsEspeces: foundry.utils.duplicate(this.actor.getTraitsEspeces() || []),
aspect: this.actor.getAspect(),
marge: this.actor.getMarge(),
talentsCell: this.getCelluleTalents(),
profils: foundry.utils.duplicate(this.actor.getProfils() || []),
combat: this.actor.getCombatValues(),
equipements: foundry.utils.duplicate(this.actor.getEquipments()),
artefacts: foundry.utils.duplicate(this.actor.getArtefacts()),
richesse: this.actor.computeRichesse(),
coupDevastateur: this.actor.items.find(it => it.type == "talent" && it.name.toLowerCase() == "coup devastateur" && !it.system.used),
valeurEquipement: this.actor.computeValeurEquipement(),
nbCombativite: this.actor.system.sante.nbcombativite,
combativiteList: MournbladeCYD2Utility.getCombativiteList(this.actor.system.sante.nbcombativite),
initiative: this.actor.getFlag("world", "last-initiative") || -1,
description: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.biodata.description, { async: true }),
habitat: await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.object.system.biodata.habitat, { async: true }),
options: this.options,
owner: this.document.isOwner,
editScore: this.options.editScore,
isGM: game.user.isGM,
config: game.system.mournbladecyd2.config
}
this.formData = formData;
console.log("PC : ", formData, this.object);
return formData;
}
/* -------------------------------------------- */
getCelluleTalents() {
let talents = []
for (let cellule of game.actors) {
if (cellule.type == "cellule") {
let found = cellule.system.members.find(it => it.id == this.actor.id)
if (found) {
talents = talents.concat(cellule.getTalents())
}
}
}
return talents
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id")
const item = this.actor.items.get(itemId)
item.sheet.render(true)
})
// Delete Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
MournbladeCYD2Utility.confirmDelete(this, li);
})
html.find('.edit-item-data').change(ev => {
const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id")
let itemType = li.data("item-type")
let itemField = $(ev.currentTarget).data("item-field")
let dataType = $(ev.currentTarget).data("dtype")
let value = ev.currentTarget.value
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
})
html.find('.adversite-modify').click(event => {
const li = $(event.currentTarget).parents(".item")
let adv = li.data("adversite")
let value = Number($(event.currentTarget).data("adversite-value"))
this.actor.incDecAdversite(adv, value)
})
html.find('.quantity-modify').click(event => {
const li = $(event.currentTarget).parents(".item")
const value = Number($(event.currentTarget).data("quantite-value"))
this.actor.incDecQuantity(li.data("item-id"), value);
})
html.find('.roll-initiative').click((event) => {
this.actor.rollAttribut("adr", true)
})
html.find('.roll-attribut').click((event) => {
const li = $(event.currentTarget).parents(".item")
let attrKey = li.data("attr-key")
this.actor.rollAttribut(attrKey, false)
})
html.find('.roll-competence').click((event) => {
const li = $(event.currentTarget).parents(".item")
let attrKey = $(event.currentTarget).data("attr-key")
let compId = li.data("item-id")
this.actor.rollCompetence(attrKey, compId)
})
html.find('.roll-arme-offensif').click((event) => {
const li = $(event.currentTarget).parents(".item")
let armeId = li.data("item-id")
this.actor.rollArmeOffensif(armeId)
})
html.find('.roll-assommer').click((event) => {
this.actor.rollAssommer()
})
html.find('.roll-coup-bas').click((event) => {
this.actor.rollCoupBas()
})
html.find('.roll-immobiliser').click((event) => {
this.actor.rollImmobiliser()
})
html.find('.roll-repousser').click((event) => {
this.actor.rollRepousser()
})
html.find('.roll-desengager').click((event) => {
this.actor.rollDesengager()
})
html.find('.roll-arme-degats').click((event) => {
const li = $(event.currentTarget).parents(".item")
let armeId = li.data("item-id")
this.actor.rollArmeDegats(armeId)
})
html.find('.item-add').click((event) => {
const itemType = $(event.currentTarget).data("type")
this.actor.createEmbeddedDocuments('Item', [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
})
html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore;
this.render(true);
});
html.find('.item-equip').click(ev => {
const li = $(ev.currentTarget).parents(".item");
this.actor.equipItem(li.data("item-id"));
this.render(true);
});
}
/* -------------------------------------------- */
/** @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;
}
/* -------------------------------------------- */
async _onDropItem(event, dragData) {
let data = event.dataTransfer.getData('text/plain')
let dataItem = JSON.parse(data)
let item = fromUuidSync(dataItem.uuid)
if (item.pack) {
item = await MournbladeCYD2Utility.searchItem(item)
}
let autoresult = MournbladeCYD2Automation.processAutomations("on-drop", item, this.actor)
if (autoresult.isValid) {
super._onDropItem(event, dragData)
} else {
ui.notifications.warn(autoresult.warningMessage)
}
}
}

View File

@@ -0,0 +1,822 @@
/* -------------------------------------------- */
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import { MournbladeCYD2RollDialog } from "./mournblade-cyd2-roll-dialog.js";
/* -------------------------------------------- */
const __degatsBonus = [-2, -2, -1, -1, 0, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 8, 8, 9, 9, 10, 10]
const __vitesseBonus = [-2, -2, -1, -1, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8]
/* -------------------------------------------- */
/**
* Extend the base Actor entity by defining a custom roll data structure which is ideal for the Simple system.
* @extends {Actor}
*/
export class MournbladeCYD2Actor extends Actor {
/* -------------------------------------------- */
/**
* Override the create() function to provide additional SoS functionality.
*
* This overrided create() function adds initial items
* Namely: Basic skills, money,
*
* @param {Object} data Barebones actor data which this function adds onto.
* @param {Object} options (Unused) Additional options which customize the creation workflow.
*
*/
static async create(data, options) {
// Case of compendium global import
if (data instanceof Array) {
return super.create(data, options);
}
// If the created actor has items (only applicable to duplicated actors) bypass the new actor creation logic
if (data.items) {
let actor = super.create(data, options);
return actor;
}
if (data.type == 'personnage') {
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.skills")
data.items = skills.map(i => i.toObject())
}
if (data.type == 'creature') {
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.skills-creatures")
data.items = skills.map(i => i.toObject())
data.items.push({ name: "Arme naturelle 1", type: 'arme', img: "systems/fvtt-mournblade-cyd2/assets/icons/melee.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } })
data.items.push({ name: "Arme naturelle 2", type: 'arme', img: "systems/fvtt-mournblade-cyd2/assets/icons/melee.webp", system: { typearme: "contact", bonusmaniementoff: 0, seuildefense: 0, degats: "0" } })
}
return super.create(data, options);
}
/* -------------------------------------------- */
getBonusDefenseFromTalents() {
let talents = this.items.filter(item => item.type == "talent" && item.system.isautomated)
let bonus = 0
for (let talent of talents) {
for (let auto of talent.system.automations) {
if (auto.eventtype == "bonus-permanent" && auto.bonusname == "bonus-defensif") {
bonus += Number(auto.bonus || 0)
}
}
}
return bonus
}
/* -------------------------------------------- */
prepareArme(arme) {
if (this.type == "cellule") {
return arme
}
arme = foundry.utils.duplicate(arme)
let combat = this.getCombatValues()
if (arme.system.typearme == "contact" || arme.system.typearme == "contactjet") {
let bonusDefense = this.getBonusDefenseFromTalents()
arme.system.competence = foundry.utils.duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "mêlée"))
arme.system.attrKey = "pui"
arme.system.totalDegats = arme.system.degats + "+" + combat.bonusDegatsTotal
arme.system.totalOffensif = this.system.attributs.pui.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff + (this.system.combat.monte ? 3 : 0)
arme.system.totalDefensif = combat.defenseTotal + arme.system.competence.system.niveau + arme.system.seuildefense + bonusDefense + (this.system.combat.monte ? 3 : 0)
console.log("Arme", arme.system.totalDefensif, combat, arme.system.competence.system.niveau, arme.system.seuildefense, bonusDefense)
arme.system.isdefense = true
arme.system.isMelee = true
arme.system.isDistance = false
}
if (arme.system.typearme == "jet" || arme.system.typearme == "tir") {
arme.system.competence = foundry.utils.duplicate(this.items.find(item => item.type == "competence" && item.name.toLowerCase() == "armes à distance"))
arme.system.attrKey = "adr"
arme.system.totalOffensif = this.system.attributs.adr.value + arme.system.competence.system.niveau + arme.system.bonusmaniementoff
arme.system.totalDegats = arme.system.degats
arme.system.isMelee = false
arme.system.isDistance = true
if (arme.system.isdefense) {
arme.system.totalDefensif = combat.defenseTotal + arme.system.competence.system.niveau + arme.system.seuildefense
}
}
return arme
}
/* -------------------------------------------- */
getItemSorted(types) {
let items = this.items.filter(item => types.includes(item.type)) || []
MournbladeCYD2Utility.sortArrayObjectsByName(items)
return items
}
getWeapons() {
let armes = []
for (let arme of this.items) {
if (arme.type == "arme") {
armes.push(this.prepareArme(arme))
}
}
MournbladeCYD2Utility.sortArrayObjectsByName(armes)
return armes
}
getMonnaies() {
return this.getItemSorted(["monnaie"])
}
getEquipments() {
return this.getItemSorted(["equipement"])
}
getArtefacts() {
return this.getItemSorted(["artefact"])
}
getArmors() {
return this.getItemSorted(["protection"])
}
getHistoriques() {
return this.getItemSorted(["historique"])
}
getProfils() {
return this.getItemSorted(["profil"])
}
getTalents() {
return this.getItemSorted(["talent"])
}
getRessources() {
return this.getItemSorted(["ressource"])
}
getContacts() {
return this.getItemSorted(["contact"])
}
getMutations() {
return this.getItemSorted(["mutation"])
}
getDons() {
return this.getItemSorted(["don"])
}
getPactes() {
return this.getItemSorted(["pacte"])
}
getTendances() {
return this.getItemSorted(["tendance"])
}
getRunes() {
return this.getItemSorted(["rune"])
}
getTraitsChaotiques() {
return this.getItemSorted(["traitchaotique"])
}
getTraitsEspeces() {
return this.getItemSorted(["traitespece"])
}
/* -------------------------------------------- */
getAspect() {
return (this.system.balance.loi > this.system.balance.chaos) ? this.system.balance.loi : this.system.balance.chaos
}
getMarge() {
return Math.abs(this.system.balance.loi - this.system.balance.chaos)
}
getAlignement() {
return (this.system.balance.loi > this.system.balance.chaos) ? "loyal" : "chaotique"
}
/* -------------------------------------------- */
getSkills() {
let comp = []
for (let item of this.items) {
item = foundry.utils.duplicate(item)
if (item.type == "competence") {
item.system.attribut1total = item.system.niveau + (this.system.attributs[item.system.attribut1]?.value || 0)
item.system.attribut2total = item.system.niveau + (this.system.attributs[item.system.attribut2]?.value || 0)
item.system.attribut3total = item.system.niveau + (this.system.attributs[item.system.attribut3]?.value || 0)
if (item.system.niveau == 0) {
item.system.attribut1total -= 3
item.system.attribut2total -= 3
item.system.attribut3total -= 3
}
item.system.attribut1label = this.system.attributs[item.system.attribut1]?.label || ""
item.system.attribut2label = this.system.attributs[item.system.attribut2]?.label || ""
item.system.attribut3label = this.system.attributs[item.system.attribut3]?.label || ""
comp.push(item)
}
}
MournbladeCYD2Utility.sortArrayObjectsByName(comp)
return comp
}
/* ----------------------- --------------------- */
addMember(actorId) {
let members = foundry.utils.duplicate(this.system.members)
members.push({ id: actorId })
this.update({ 'system.members': members })
}
async removeMember(actorId) {
let members = this.system.members.filter(it => it.id != actorId)
this.update({ 'system.members': members })
}
/* -------------------------------------------- */
getDefenseBase() {
return Math.max(this.system.attributs.tre.value, this.system.attributs.adr.value)
}
/* -------------------------------------------- */
getVitesseBase() {
return 5 + __vitesseBonus[this.system.attributs.adr.value]
}
/* -------------------------------------------- */
getProtection() {
let equipProtection = 0
for (let armor of this.items) {
if (armor.type == "protection" && armor.system.equipped) {
equipProtection += Number(armor.system.protection)
}
}
if (equipProtection < 4) {
return 4 + equipProtection // Cas des boucliers + sans armure
}
return equipProtection // Uniquement la protection des armures + boucliers
}
/* -------------------------------------------- */
getCombatValues() {
if (this.type == "cellule") {
return {
initBase: 0,
initTotal: 0,
bonusDegats: 0,
bonusDegatsTotal: 0,
vitesseBase: 0,
vitesseTotal: 0,
defenseBase: 0,
protection: 0,
defenseTotal: 0
}
}
let combat = {
initBase: this.system.attributs.adr.value,
initTotal: this.system.attributs.adr.value + this.system.combat.initbonus,
bonusDegats: this.getBonusDegats(),
bonusDegatsTotal: this.getBonusDegats() + this.system.combat.bonusdegats,
vitesseBase: this.getVitesseBase(),
vitesseTotal: this.getVitesseBase() + this.system.combat.vitessebonus,
defenseBase: this.getDefenseBase(),
protection: this.getProtection(),
defenseTotal: this.getDefenseBase() + this.system.combat.defensebonus + this.getProtection() - this.getTotalAdversite() + (this.system.combat.defensetotale ? 3 : 0)
}
return combat
}
/* -------------------------------------------- */
prepareBaseData() {
}
/* -------------------------------------------- */
async prepareData() {
super.prepareData();
}
/* -------------------------------------------- */
prepareDerivedData() {
if (this.type == 'personnage') {
let talentBonus = this.getVigueurBonus()
let vigueur = Math.floor((this.system.attributs.pui.value + this.system.attributs.tre.value) / 2) + talentBonus + this.system.sante.vigueurmodifier
if (vigueur != this.system.sante.vigueur) {
this.update({ 'system.sante.vigueur': vigueur })
}
}
super.prepareDerivedData()
}
/* -------------------------------------------- */
_preUpdate(changed, options, user) {
if (changed?.system?.sante?.etat && changed?.system?.sante?.etat != this.system.sante.etat) {
setTimeout(() => {
this.processCombativite(changed.system.sante)
}, 800)
}
super._preUpdate(changed, options, user);
}
/* -------------------------------------------- */
_onUpdate(data, options, user) {
super._onUpdate(data, options, user);
}
/* -------------------------------------------- */
getItemById(id) {
let item = this.items.find(item => item.id == id);
if (item) {
item = foundry.utils.duplicate(item)
}
return item;
}
/* -------------------------------------------- */
async equipItem(itemId) {
let item = this.items.find(item => item.id == itemId)
if (item && item.system) {
let update = { _id: item.id, "system.equipped": !item.system.equipped }
await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
editItemField(itemId, itemType, itemField, dataType, value) {
let item = this.items.find(item => item.id == itemId)
if (item) {
console.log("Item ", item, itemField, dataType, value)
if (dataType.toLowerCase() == "number") {
value = Number(value)
} else {
value = String(value)
}
let update = { _id: item.id, [`system.${itemField}`]: value };
this.updateEmbeddedDocuments("Item", [update])
}
}
/* -------------------------------------------- */
checkAttribut(attribut, minLevel) {
let attr = this.system.attributs.find(at => at.labelnorm == attribut.toLowerCase())
if (attr && attr.value >= minLevel) {
return { isValid: true, attr: foundry.utils.duplicate(attr) }
}
return { isValid: false }
}
/* -------------------------------------------- */
checkAttributOrCompetenceLevel(compName, minLevel) {
let comp = this.items.find(i => i.type == "competence" && i.name.toLowerCase() == compName.toLowerCase() && i.system.niveau >= minLevel)
if (comp) {
return { isValid: true, item: foundry.utils.duplicate(comp) }
} else {
for (let attrKey in this.system.attributs) {
if (this.system.attributs[attrKey].label.toLowerCase() == compName.toLowerCase() && this.system.attributs[attrKey].value >= minLevel) {
return { isValid: true, item: foundry.utils.duplicate(this.system.attributs[attrKey]) }
}
}
}
return { isValid: false, warningMessage: `Prérequis insuffisant : la compétence/attribut ${compName} doit être de niveau ${minLevel} au minimum` }
}
/* -------------------------------------------- */
addCompetenceBonus(compName, bonus, baCost) {
let comp = this.items.find(i => i.type == "competence" && i.name.toLowerCase() == compName.toLowerCase())
if (comp) {
comp = foundry.utils.duplicate(comp)
comp.system.bonus = bonus
comp.system.baCost = baCost
return { isValid: true, item: comp }
}
return { isValid: false, warningMessage: `Compétence ${compName} non trouvée` }
}
/* -------------------------------------------- */
checkIfCompetence(compName) {
let comp = this.items.find(i => i.type == "competence" && i.name.toLowerCase() == compName.toLowerCase())
if (comp) {
return { isValid: true, item: comp }
}
return { isValid: false }
}
/* -------------------------------------------- */
getVigueur() {
return this.system.sante.vigueur
}
/* -------------------------------------------- */
getVigueurBonus() {
let talents = this.items.filter(item => item.type == "talent" && item.system.isautomated)
let bonus = 0
for (let talent of talents) {
for (let auto of talent.system.automations) {
if (auto.eventtype == "bonus-permanent" && auto.bonusname == "vigueur") {
bonus += Number(auto.bonus || 0)
}
}
}
return bonus
}
/* -------------------------------------------- */
getBonneAventure() {
return this.system.bonneaventure.actuelle
}
/* -------------------------------------------- */
checkBonneAventure(cost) {
return (this.system.bonneaventure.actuelle >= cost)
}
/* -------------------------------------------- */
changeBonneAventure(value) {
let newBA = this.system.bonneaventure.actuelle
newBA += value
this.update({ 'system.bonneaventure.actuelle': newBA })
}
/* -------------------------------------------- */
getEclat() {
return this.system.eclat.value
}
/* -------------------------------------------- */
changeEclat(value) {
let newE = this.system.eclat.value
newE += value
this.update({ 'system.eclat.value': newE })
}
/* -------------------------------------------- */
compareName(a, b) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
}
/* -------------------------------------------- */
getAttribute(attrKey) {
return this.system.attributes[attrKey]
}
/* -------------------------------------------- */
getBonusDegats() {
return 0;
}
/* -------------------------------------------- */
changeEtatCombativite(value) {
if (value === "vaincu") {
value = 200
}
let sante = foundry.utils.duplicate(this.system.sante)
sante.etat += Number(value)
sante.etat = Math.max(sante.etat, 0)
sante.etat = Math.min(sante.etat, this.system.sante.nbcombativite)
this.update({ 'system.sante': sante })
if (sante.etat == this.system.sante.nbcombativite) {
ChatMessage.create({ content: `<strong>${this.name} est vaincu !</strong>` })
}
// Duplicated ! this.processCombativite(sante)
}
/* -------------------------------------------- */
processCombativite(sante) {
sante = sante || foundry.utils.duplicate(this.system.sante)
// Gestion des états affaibli et très affaibli
if (sante.etat == this.system.sante.nbcombativite - 2 || sante.etat == this.system.sante.nbcombativite - 1) {
if (sante.etat == this.system.sante.nbcombativite - 2 && this.items.find(item => item.type == "talent" && item.name.toLowerCase() == "encaissement")) {
ChatMessage.create({ content: `<strong>${this.name} ne subit pas les 2 adversités rouge grâce à Encaissement. Pensez à les ajouter à la fin de la scène !</strong>` })
} else if (sante.etat == this.system.sante.nbcombativite - 1 && this.items.find(item => item.type == "talent" && item.name.toLowerCase().includes("vaillant"))) {
ChatMessage.create({ content: `<strong>${this.name} ne subit pas les 2 adversités rouge grâce à Vaillant. Pensez à les ajouter à la fin de la scène !</strong>` })
} else {
ChatMessage.create({ content: `<strong>${this.name} subit 2 adversités rouge !</strong>` })
this.incDecAdversite("rouge", 2)
}
}
}
/* -------------------------------------------- */
async equipGear(equipmentId) {
let item = this.items.find(item => item.id == equipmentId);
if (item?.system?.data) {
let update = { _id: item.id, "system.equipped": !item.system.equipped };
await this.updateEmbeddedDocuments('Item', [update]); // Updates one EmbeddedEntity
}
}
/* -------------------------------------------- */
getSubActors() {
let subActors = [];
for (let id of this.system.subactors) {
subActors.push(foundry.utils.duplicate(game.actors.get(id)));
}
return subActors;
}
/* -------------------------------------------- */
async addSubActor(subActorId) {
let subActors = foundry.utils.duplicate(this.system.subactors);
subActors.push(subActorId);
await this.update({ 'system.subactors': subActors });
}
/* -------------------------------------------- */
async delSubActor(subActorId) {
let newArray = [];
for (let id of this.system.subactors) {
if (id != subActorId) {
newArray.push(id);
}
}
await this.update({ 'system.subactors': newArray });
}
/* -------------------------------------------- */
getTotalAdversite() {
return this.system.adversite.bleue + this.system.adversite.rouge + this.system.adversite.noire
}
/* -------------------------------------------- */
async incDecAdversite(adv, incDec = 0) {
let adversite = foundry.utils.duplicate(this.system.adversite)
adversite[adv] += Number(incDec)
adversite[adv] = Math.max(adversite[adv], 0)
adversite[adv] = Math.min(adversite[adv], 20)
this.update({ 'system.adversite': adversite })
}
/* -------------------------------------------- */
async incDecQuantity(objetId, incDec = 0) {
let objetQ = this.items.get(objetId)
if (objetQ) {
let newQ = objetQ.system.quantite + incDec
newQ = Math.max(newQ, 0)
await this.updateEmbeddedDocuments('Item', [{ _id: objetQ.id, 'system.quantite': newQ }]); // pdates one EmbeddedEntity
}
}
/* -------------------------------------------- */
computeRichesse() {
let valueSC = 0
for (let monnaie of this.items) {
if (monnaie.type == "monnaie") {
valueSC += Number(monnaie.system.prixsc) * Number(monnaie.system.quantite)
}
}
return MournbladeCYD2Utility.computeMonnaieDetails(valueSC)
}
/* -------------------------------------------- */
computeValeurEquipement() {
let valueSC = 0
for (let equip of this.items) {
if (equip.type == "equipement" || equip.type == "arme" || equip.type == "protection") {
valueSC += Number(equip.system.prixsc) * Number(equip.system.quantite ?? 1)
valueSC += (Number(equip.system.prixca) * Number(equip.system.quantite ?? 1)) * 20
valueSC += (Number(equip.system.prixpo) * Number(equip.system.quantite ?? 1)) * 400
}
}
return MournbladeCYD2Utility.computeMonnaieDetails(valueSC)
}
/* -------------------------------------------- */
getCompetence(compId) {
return this.items.get(compId)
}
/* -------------------------------------------- */
async setPredilectionUsed(compId, predIdx) {
let comp = this.items.get(compId)
let pred = foundry.utils.duplicate(comp.system.predilections)
pred[predIdx].used = true
await this.updateEmbeddedDocuments('Item', [{ _id: compId, 'system.predilections': pred }])
}
/* -------------------------------------------- */
getInitiativeScore() {
let init = this.getFlag("world", "last-initiative")
return init || -1
}
/* -------------------------------------------- */
getBestAttackValue() {
let attackList = this.items.filter(item => (item.type == "arme" || item.type == "talent") && item.system.equipped)
let maxOff = 0
let bestArme
for (let arme of attackList) {
if (arme.type == "arme") {
arme = this.prepareArme(arme)
}
if (arme.system.totalOffensif > maxOff) {
maxOff = arme.system.totalOffensif
bestArme = foundry.utils.duplicate(arme)
}
}
return bestArme
}
/* -------------------------------------------- */
getBestDefenseValue() {
let defenseList = this.items.filter(item => (item.type == "arme") && item.system.equipped)
let maxDef = 0
let bestArme
for (let arme of defenseList) {
if (arme.type == "arme") {
arme = this.prepareArme(arme)
}
if (arme.system.totalDefensif > maxDef) {
maxDef = arme.system.totalDefensif
bestArme = foundry.utils.duplicate(arme)
}
}
return bestArme
}
/* -------------------------------------------- */
searchRelevantTalents(competence) {
let talents = []
for (let talent of this.items) {
if (talent.type == "talent" && talent.system.isautomated && talent.system.automations.length > 0) {
for (let auto of talent.system.automations) {
if (auto.eventtype === "prepare-roll") {
if (auto.competence.toLowerCase() == competence.name.toLowerCase()) {
talent = foundry.utils.duplicate(talent)
talent.system.bonus = auto.bonus
talent.system.baCost = auto.baCost
talents.push(talent)
}
}
}
}
}
return talents
}
/* -------------------------------------------- */
buildListeAdversites() {
return []
}
/* -------------------------------------------- */
getCommonRollData(attrKey = undefined, compId = undefined, compName = undefined) {
let rollData = MournbladeCYD2Utility.getBasicRollData()
rollData.alias = this.name
rollData.actorImg = this.img
rollData.actorId = this.id
rollData.tokenId = this.token?.id
rollData.img = this.img
rollData.attributs = MournbladeCYD2Utility.getAttributs()
rollData.maitriseId = "none"
rollData.nbEclat = this.system.eclat.value
rollData.nbBA = this.system.bonneaventure.actuelle
rollData.nbAdversites = this.getTotalAdversite()
rollData.talents = []
rollData.attrKey2 = "none"
rollData.coupDevastateur = this.items.find(it => it.type == "talent" && it.name.toLowerCase() == "coup dévastateur" && !it.system.used)
rollData.hasAmbidextre = this.items.find(it => it.type == "talent" && it.name.toLowerCase() == "ambidextre")
rollData.hasFeinte = this.system.bonneaventure.actuelle > 0 && this.items.find(it => it.type == "talent" && it.name.toLowerCase() == "feinte")
rollData.isMonte = this.system.combat.monte
rollData.config = game.system.mournbladecyd2.config
if (attrKey) {
rollData.attrKey = attrKey
if (attrKey != "tochoose") {
rollData.actionImg = "systems/fvtt-mournblade-cyd2/assets/icons/" + this.system.attributs[attrKey].labelnorm + ".webp"
rollData.attr = foundry.utils.duplicate(this.system.attributs[attrKey])
}
}
if (compId) {
rollData.competence = foundry.utils.duplicate(this.items.get(compId) || {})
let maitrises = [{ key: "none", label: "Aucune" }]
rollData.competence.system.predilections.forEach(function (item) {
if (item.maitrise) {
maitrises.push({ key: item.id, label: item.name });
}
})
rollData.maitrises = maitrises // rollData.competence.system.predilections.filter(p => p.maitrise)
rollData.actionImg = rollData.competence?.img
rollData.talents = this.searchRelevantTalents(rollData.competence)
}
if (compName) {
rollData.competence = foundry.utils.duplicate(this.items.find(item => item.name.toLowerCase() == compName.toLowerCase()) || {})
rollData.actionImg = rollData.competence?.img
}
return rollData
}
/* -------------------------------------------- */
async rollAttribut(attrKey, isInit = false) {
let rollData = this.getCommonRollData(attrKey)
rollData.multiplier = (isInit) ? 1 : 2
rollData.isInit = isInit
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
}
/* -------------------------------------------- */
async rollCompetence(attrKey, compId) {
let rollData = this.getCommonRollData(attrKey, compId)
rollData.multiplier = 1 // Attr multiplier, always 1 in competence mode
console.log("RollDatra", rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
}
/* -------------------------------------------- */
async rollArmeOffensif(armeId) {
let arme = this.items.get(armeId)
if (arme.type == "arme") {
arme = this.prepareArme(arme)
}
let rollData = this.getCommonRollData(arme.system.attrKey, arme.system.competence._id)
rollData.arme = arme
MournbladeCYD2Utility.updateWithTarget(rollData)
console.log("ARME!", rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
}
/* -------------------------------------------- */
async rollAssommer() {
let rollData = this.getCommonRollData("pui", undefined, "Filouterie")
rollData.assomer = true
rollData.conditionsCommunes = true
MournbladeCYD2Utility.updateWithTarget(rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
}
/* -------------------------------------------- */
async rollCoupBas() {
let rollData = this.getCommonRollData("pui", undefined, "Mêlée")
rollData.coupBas = true
rollData.conditionsCommunes = true
MournbladeCYD2Utility.updateWithTarget(rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
}
/* -------------------------------------------- */
async rollImmobiliser() {
let rollData = this.getCommonRollData("pui", undefined, "Mêlée")
rollData.immobiliser = true
rollData.conditionsCommunes = true
rollData.cibleconsciente = true
MournbladeCYD2Utility.updateWithTarget(rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
}
/* -------------------------------------------- */
async rollRepousser() {
let rollData = this.getCommonRollData("pui", undefined, "Mêlée")
rollData.repousser = true
rollData.conditionsCommunes = true
rollData.cibleconsciente = true
MournbladeCYD2Utility.updateWithTarget(rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
}
/* -------------------------------------------- */
async rollDesengager() {
let rollData = this.getCommonRollData("adr", undefined, "Mouvements")
rollData.desengager = true
rollData.conditionsCommunes = true
MournbladeCYD2Utility.updateWithTarget(rollData)
let rollDialog = await MournbladeCYD2RollDialog.create(this, rollData)
rollDialog.render(true)
}
/* -------------------------------------------- */
async rollArmeDegats(armeId, targetVigueur = undefined, rollDataInput = undefined) {
let arme = this.items.get(armeId)
if (arme.type == "arme") {
arme = this.prepareArme(arme)
}
console.log("DEGATS", arme, targetVigueur, rollDataInput)
let roll
let bonus = 0
let bonus2 = 0
if (rollDataInput?.applyCoupDevastateur) {
bonus2 = Math.floor(this.system.attributs.pui.value / 2)
let talent = this.items.find(item => item.type == "talent" && item.name.toLowerCase() == "coup dévastateur")
this.updateEmbeddedDocuments('Item', [{ _id: talent.id, 'system.used': true }])
}
if (rollDataInput?.isHeroique) {
if (rollDataInput?.attaqueCharge) {
bonus = 5
}
if (rollDataInput?.chargeCavalerie) {
bonus = 6
}
roll = await new Roll("2d10rr10+" + arme.system.totalDegats + "+" + bonus + "+" + bonus2).roll()
} else {
if (rollDataInput?.attaqueCharge) {
bonus = 3
}
if (rollDataInput?.chargeCavalerie) {
bonus = 4
}
roll = await new Roll("1d10+" + arme.system.totalDegats + "+" + bonus + "+" + bonus2).roll()
}
await MournbladeCYD2Utility.showDiceSoNice(roll, game.settings.get("core", "rollMode"));
let nbEtatPerdus = 0
if (targetVigueur) {
nbEtatPerdus = Math.floor(roll.total / targetVigueur)
}
//console.log(roll)
let rollData = {
arme: arme,
finalResult: roll.total,
formula: roll.formula,
alias: this.name,
actorImg: this.img,
actorId: this.id,
defenderTokenId: rollDataInput?.defenderTokenId,
actionImg: arme.img,
targetVigueur: targetVigueur,
nbEtatPerdus: nbEtatPerdus
}
MournbladeCYD2Utility.createChatWithRollMode(rollData.alias, {
content: await renderTemplate(`systems/fvtt-mournblade-cyd2/templates/chat-degats-result.html`, rollData)
})
if (rollDataInput?.defenderTokenId && nbEtatPerdus) {
MournbladeCYD2Utility.applyCombativite(rollDataInput, nbEtatPerdus)
}
}
}

View File

@@ -0,0 +1,52 @@
/* -------------------------------------------- */
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import "./xregexp-all.js";
const __level1Expr = '(?<effectType>[a-z]+):(?<objectType>[a-zA-Z]+)\\((?<objectName>[a-zA-Z0-9\\.]+)\\)\\s+(?<modifierValue>[\\d]+)\\s*{*(?<modifierLabel>[a-zA-Zàéè\\s]*)}*'
const __effectTypes = {modifier: 1, cost: 1, provide: 1}
/* -------------------------------------------- */
export class MournbladeCYD2Automation {
/* -------------------------------------------- */
static init() {
this.__objectTypes = { }
Object.entries(game.data.model.Actor).forEach(kv => {
this.__objectTypes[kv[0]] = foundry.utils.duplicate(kv[1])
})
Object.entries(game.data.model.Item).forEach(kv => {
this.__objectTypes[kv[0]] = foundry.utils.duplicate(kv[1])
})
}
/* -------------------------------------------- */
static processAutomations(event, item, actor) {
//console.log("We have", event, item, actor)
if ( !item.system.isautomated || item.system.automations.length == 0 ) {
return {isValid: true}
}
let relevantAutomations = item.system.automations.filter( auto => auto.eventtype == event)
if ( !relevantAutomations || relevantAutomations.length == 0) {
return {isValid: true}
}
let validTab = []
for(let auto of relevantAutomations) {
if ( event == "on-drop") {
validTab.push( actor.checkAttributOrCompetenceLevel( auto.competence, auto.minLevel) )
}
}
// Post process validation array
for (let ret of validTab) {
if ( !ret.isValid) {
return ret
}
}
return {isValid: true}
}
}

View File

@@ -0,0 +1,173 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
/* -------------------------------------------- */
const __ALLOWED_ITEM_CELLULE = { "talent": 1, "ressource": 1, "contact": 1, "equipement": 1, "protection": 1, "artefact": 1, "arme": 1, "monnaie": 1 }
/* -------------------------------------------- */
export class MournbladeCYD2CelluleSheet extends foundry.appv1.sheets.ActorSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-mournblade-cyd2", "sheet", "actor"],
template: "systems/fvtt-mournblade-cyd2/templates/cellule-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "talents" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
});
}
/* -------------------------------------------- */
async getData() {
const objectData = foundry.utils.duplicate(this.object)
let formData = {
title: this.title,
id: objectData.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
system: objectData.system,
effects: this.object.effects.map(e => foundry.utils.deepClone(e.data)),
limited: this.object.limited,
talents: foundry.utils.duplicate(this.actor.getTalents() || {}),
ressources: foundry.utils.duplicate(this.actor.getRessources()),
contacts: foundry.utils.duplicate(this.actor.getContacts()),
members: this.getMembers(),
equipements: foundry.utils.duplicate(this.actor.getEquipments()),
artefacts: foundry.utils.duplicate(this.actor.getArtefacts()),
armes: foundry.utils.duplicate(this.actor.getWeapons()),
monnaies: foundry.utils.duplicate(this.actor.getMonnaies()),
protections: foundry.utils.duplicate(this.actor.getArmors()),
richesse: this.actor.computeRichesse(),
valeurEquipement: this.actor.computeValeurEquipement(),
description: await TextEditor.enrichHTML(this.object.system.description, { async: true }),
options: this.options,
owner: this.document.isOwner,
editScore: this.options.editScore,
isGM: game.user.isGM,
config: game.system.mournbladecyd2.config
}
this.formData = formData;
console.log("CELLULE : ", formData, this.object);
return formData;
}
/* -------------------------------------------- */
getMembers( ) {
let membersFull = []
for(let def of this.actor.system.members) {
let actor = game.actors.get(def.id)
membersFull.push( { name: actor.name, id: actor.id, img: actor.img } )
}
return membersFull
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.actor-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item")
let actorId = li.data("actor-id")
const actor = game.actors.get(actorId)
actor.sheet.render(true)
})
html.find('.actor-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item")
let actorId = li.data("actor-id")
this.actor.removeMember(actorId)
})
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id")
const item = this.actor.items.get(itemId)
item.sheet.render(true)
})
// Delete Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
MournbladeCYD2Utility.confirmDelete(this, li);
})
html.find('.edit-item-data').change(ev => {
const li = $(ev.currentTarget).parents(".item")
let itemId = li.data("item-id")
let itemType = li.data("item-type")
let itemField = $(ev.currentTarget).data("item-field")
let dataType = $(ev.currentTarget).data("dtype")
let value = ev.currentTarget.value
this.actor.editItemField(itemId, itemType, itemField, dataType, value)
})
html.find('.quantity-modify').click(event => {
const li = $(event.currentTarget).parents(".item")
const value = Number($(event.currentTarget).data("quantite-value"))
this.actor.incDecQuantity( li.data("item-id"), value );
})
html.find('.item-add').click((event) => {
const itemType = $(event.currentTarget).data("type")
this.actor.createEmbeddedDocuments('Item', [{ name: `Nouveau ${itemType}`, type: itemType }], { renderSheet: true })
})
html.find('.lock-unlock-sheet').click((event) => {
this.options.editScore = !this.options.editScore;
this.render(true);
});
}
/* -------------------------------------------- */
async _onDropActor(event, dragData) {
const actor = fromUuidSync(dragData.uuid)
if (actor) {
this.actor.addMember(actor.id)
} else {
ui.notifications.warn("Cet acteur n'a pas été trouvé.")
}
super._onDropActor(event)
}
/* -------------------------------------------- */
async _onDropItem(event, dragData) {
let data = event.dataTransfer.getData('text/plain')
let dataItem = JSON.parse(data)
let item = fromUuidSync(dataItem.uuid)
if (item.pack) {
item = await MournbladeCYD2Utility.searchItem(item)
}
if ( __ALLOWED_ITEM_CELLULE[item.type]) {
super._onDropItem(event, dragData)
return
}
ui.notification.info("Ce type d'item n'est pas autorisé sur une Cellule.")
}
/* -------------------------------------------- */
/** @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;
}
}

View File

@@ -0,0 +1,24 @@
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
/* -------------------------------------------- */
export class MournbladeCYD2Combat extends Combat {
/* -------------------------------------------- */
async rollInitiative(ids, formula = undefined, messageOptions = {} ) {
ids = typeof ids === "string" ? [ids] : ids;
for (let cId of ids) {
const c = this.combatants.get(cId);
console.log("Init for combattant", c, ids)
let id = c._id || c.id
let initValue = c.actor ? c.actor.getInitiativeScore() : 0
await this.updateEmbeddedDocuments("Combatant", [ { _id: id, initiative: initValue } ]);
}
return this;
}
/* -------------------------------------------- */
_onUpdate(changed, options, userId) {
}
}

View File

@@ -0,0 +1,123 @@
/* -------------------------------------------- */
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import { MournbladeCYD2RollDialog } from "./mournblade-cyd2-roll-dialog.js";
/* -------------------------------------------- */
export class MournbladeCYD2Commands {
static init() {
if (!game.system.mournbladecyd2.commands) {
//const MournbladeCYD2Commands = new MournbladeCYD2Commands()
//MournbladeCYD2Commands.registerCommand({ path: ["/char"], func: (content, msg, params) => MournbladeCYD2Commands.createChar(msg), descr: "Create a new character" });
//game.system.mournblade.commands = MournbladeCYD2Commands
}
}
constructor() {
this.commandsTable = {}
}
/* -------------------------------------------- */
registerCommand(command) {
this._addCommand(this.commandsTable, command.path, '', command);
}
/* -------------------------------------------- */
_addCommand(targetTable, path, fullPath, command) {
if (!this._validateCommand(targetTable, path, command)) {
return;
}
const term = path[0];
fullPath = fullPath + term + ' '
if (path.length == 1) {
command.descr = `<strong>${fullPath}</strong>: ${command.descr}`;
targetTable[term] = command;
}
else {
if (!targetTable[term]) {
targetTable[term] = { subTable: {} };
}
this._addCommand(targetTable[term].subTable, path.slice(1), fullPath, command)
}
}
/* -------------------------------------------- */
_validateCommand(targetTable, path, command) {
if (path.length > 0 && path[0] && command.descr && (path.length != 1 || targetTable[path[0]] == undefined)) {
return true;
}
console.warn("MournbladeCYD2Commands._validateCommand failed ", targetTable, path, command);
return false;
}
/* -------------------------------------------- */
/* Manage chat commands */
processChatCommand(commandLine, content = '', msg = {}) {
// Setup new message's visibility
let rollMode = game.settings.get("core", "rollMode");
if (["gmroll", "blindroll"].includes(rollMode)) msg["whisper"] = ChatMessage.getWhisperRecipients("GM");
if (rollMode === "blindroll") msg["blind"] = true;
msg["type"] = 0;
let command = commandLine[0].toLowerCase();
let params = commandLine.slice(1);
return this.process(command, params, content, msg);
}
/* -------------------------------------------- */
process(command, params, content, msg) {
return this._processCommand(this.commandsTable, command, params, content, msg);
}
/* -------------------------------------------- */
_processCommand(commandsTable, name, params, content = '', msg = {}, path = "") {
console.log("===> Processing command")
let command = commandsTable[name];
path = path + name + " ";
if (command && command.subTable) {
if (params[0]) {
return this._processCommand(command.subTable, params[0], params.slice(1), content, msg, path)
}
else {
this.help(msg, command.subTable);
return true;
}
}
if (command && command.func) {
const result = command.func(content, msg, params);
if (result == false) {
RdDCommands._chatAnswer(msg, command.descr);
}
return true;
}
return false;
}
/* -------------------------------------------- */
async createChar(msg) {
game.system.MournbladeCYD2.creator = new MournbladeCYD2ActorCreate();
game.system.MournbladeCYD2.creator.start();
}
/* -------------------------------------------- */
static _chatAnswer(msg, content) {
msg.whisper = [game.user.id];
msg.content = content;
ChatMessage.create(msg);
}
/* -------------------------------------------- */
async poolRoll( msg) {
let rollData = MournbladeCYD2Utility.getBasicRollData()
rollData.alias = "Dice Pool Roll",
rollData.mode = "generic"
rollData.title = `Dice Pool Roll`;
let rollDialog = await MournbladeCYD2RollDialog.create( this, rollData);
rollDialog.render( true );
}
}

View File

@@ -0,0 +1,143 @@
export const MOURNBLADECYD2_CONFIG = {
optionsDifficulte: [
{ key: "0", label: "Aucune/Inconnue" },
{ key: "5", label: "Facile (5)" },
{ key: "6", label: "(6)" },
{ key: "7", label: "(7)" },
{ key: "8", label: "(8)" },
{ key: "9", label: "(9)" },
{ key: "10", label: "Moyenne (10)" },
{ key: "11", label: "(11)" },
{ key: "12", label: "(12)" },
{ key: "13", label: "(13)" },
{ key: "14", label: "(14)" },
{ key: "15", label: "Ardue (15)" },
{ key: "16", label: "(16)" },
{ key: "17", label: "(17)" },
{ key: "18", label: "(18)" },
{ key: "19", label: "(19)" },
{ key: "20", label: "Hasardeuse (20)" },
{ key: "21", label: "(21)" },
{ key: "22", label: "(22)" },
{ key: "23", label: "(23)" },
{ key: "24", label: "(24)" },
{ key: "25", label: "Insensée (25)" },
{ key: "26", label: "(26)" },
{ key: "27", label: "(27)" },
{ key: "28", label: "(28)" },
{ key: "29", label: "(29)" },
{ key: "30", label: "Pure Folie (30)" }
],
optionsDistanceTir: [
{ key: "porteecourte", label: "Courte ({protectionDefenseur}+5)" },
{ key: "porteemoyenne", label: "Moyenne ({protectionDefenseur}+9)" },
{ key: "porteelongue", label: "Longue ({protectionDefenseur}+14)" }
],
optionsBonusMalus: [
{ key: "-10", label: "-10" },
{ key: "-9", label: "-9" },
{ key: "-8", label: "-8" },
{ key: "-7", label: "-7" },
{ key: "-6", label: "-6" },
{ key: "-5", label: "-5" },
{ key: "-4", label: "-4" },
{ key: "-3", label: "-3" },
{ key: "-2", label: "-2" },
{ key: "-1", label: "-1" },
{ key: "0", label: "0" },
{ key: "1", label: "+1" },
{ key: "2", label: "+2" },
{ key: "3", label: "+3" },
{ key: "4", label: "+4" },
{ key: "5", label: "+5" },
{ key: "6", label: "+6" },
{ key: "7", label: "+7" },
{ key: "8", label: "+8" },
{ key: "9", label: "+9" },
{ key: "10", label: "+10" }
],
optionsTailleCible: [
{ key: "normal", label: "Normal (SD+0)" },
{ key: "main", label: "Main (SD+10)" },
{ key: "enfant", label: "Enfant (SD+3)" },
{ key: "maison", label: "Maison (SD-10)" }
],
optionsCouvert: [
{ key: "aucun", label: "Aucun" },
{ key: "leger", label: "Léger (SD+5)" },
{ key: "complet", label: "Quasi complet (SD+10)" }
],
optionsTireurDeplacement: [
{ key: "immobile", label: "Immobile (SD+0)" },
{ key: "lent", label: "Lent (SD+3)" },
{ key: "rapide", label: "Rapide (SD+5)" }
],
optionsSoutiens: [
{ key: "0", label: "Aucun" },
{ key: "1", label: "Un soutien (+3)" },
{ key: "2", label: "Deux soutiens (+4)" },
{ key: "3", label: "Trois soutiens (+5)" }
],
optionsStatutResistant: [
{ key: "commun", label: "Commun" },
{ key: "clandestin", label: "Clandestin" },
{ key: "insurge", label: "Insurgé" }
],
optionsTypeArme: [
{key: "contact", label: "Arme de contact"},
{key: "contactjet", label: "Arme de contact et de Jet"},
{key: "jet", label: "Arme de Jet"},
{key: "tir", label: "Arme de Tir"}
],
optionsArtefactBranche: [
{key: "alchimie", label: "Alchimie"},
{key: "biologie", label: "Biologie"},
{key: "electricite", label: "Electricité"},
{key: "mecanique", label: "Mécanique"},
{key: "scienceesprit", label: "Science de l'Esprit"}
],
optionsArtefactBranche2: [
{key: "none", label: "Aucune"},
{key: "alchimie", label: "Alchimie"},
{key: "biologie", label: "Biologie"},
{key: "electricite", label: "Electricité"},
{key: "mecanique", label: "Mécanique"},
{key: "scienceesprit", label: "Science de l'Esprit"}
],
optionsTypeContact: [
{key: "contact", label: "Contact"},
{key: "allie", label: "Allié"}
],
optionsTypeMutation: [
{key: "tares_communes", label: "Tares et Malformations communes"},
{key: "evolutions_communes", label: "Evolutions communes"},
{key: "tares_rares", label: "Tares et Malformations rares"},
{key: "evolutions_rares", label: "Evolutions rares"},
{key: "tares_majeures", label: "Tares et Maformations majeures"},
{key: "evolutions_majeures", label: "Evolutions majeures"},
{key: "tares_except", label: "Tares et Malformations exceptionnelles"},
{key: "evolutions_except", label: "Evolutions exceptionnelles"}
],
optionsTypeTalent: [
{key: "personnage", label: "Personnage"},
{key: "cellule", label: "Cellule"},
{key: "traitespece", label: "Trait d'espèce"}
],
optionsUseTalent: [
{key: "permanent", label: "Permanent"},
{key: "sceance", label: "Une fois par scéance"},
{key: "scenario", label: "Une fois par scénario"},
{key: "jour", label: "Une fois par jour"},
{key: "unique", label: "Unique"}
],
optionsAutomationEvent: [
{key: "on-drop", label: "Drop sur l'acteur"},
{key: "prepare-roll", label: "Préparation d'un jet"},
{key: "bonus-permanent", label: "Bonus permanent"}
],
optionsBonusPermanent: [
{key: "vigueur", label: "Vigueur"},
{key: "bonus-defensif", label: "Bonus au Seuil de Défense"}
]
}

View File

@@ -0,0 +1,26 @@
/**
* Extend the basic ActorSheet with some very simple modifications
* @extends {ActorSheet}
*/
import { MournbladeCYD2ActorSheet } from "./mournblade-cyd2-actor-sheet.js";
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
/* -------------------------------------------- */
export class MournbladeCYD2CreatureSheet extends MournbladeCYD2ActorSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-mournblade-cyd2", "sheet", "actor"],
template: "systems/fvtt-mournblade-cyd2/templates/creature-sheet.html",
width: 640,
height: 720,
tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "stats" }],
dragDrop: [{ dragSelector: ".item-list .item", dropSelector: null }],
editScore: false
})
}
}

View File

@@ -0,0 +1,71 @@
/* -------------------------------------------- */
/* -------------------------------------------- */
export class MournbladeCYD2TokenHud {
static init() {
// Integration du TokenHUD
Hooks.on('renderTokenHUD', (app, html, data) => { MournbladeCYD2TokenHud.addTokenHudExtensions(app, html, data._id) });
}
/* -------------------------------------------- */
static async removeExtensionHud(app, html, tokenId) {
html.find('.control-icon.mournblade-cyd2-adversite').remove()
}
/* -------------------------------------------- */
static async addExtensionHud(app, html, tokenId) {
let token = canvas.tokens.get(tokenId)
let actor = token.actor
app.hasExtension = true
const hudData = { actor: actor }
const controlIconActions = $(html).find('.control-icon[data-action=combat]');
// initiative
await MournbladeCYD2TokenHud._configureSubMenu(controlIconActions, 'systems/fvtt-mournblade-cyd2/templates/hud-adversites.html', hudData,
(event) => {
let adversite = event.currentTarget.attributes['data-action-index'].value
let value = Number(event.currentTarget.attributes['data-action-value'].value)
hudData.actor.incDecAdversite( adversite, value)
} )
}
/* -------------------------------------------- */
static async addTokenHudExtensions(app, html, tokenId) {
const controlIconCombat = $(html).find('.control-icon[data-action=combat]')
if (controlIconCombat.length>0 ) {
MournbladeCYD2TokenHud.addExtensionHud(app, html, tokenId);
}
}
/* -------------------------------------------- */
static async _configureSubMenu(insertionPoint, template, hudData, onMenuItem) {
const hud = $(await foundry.applications.handlebars.renderTemplate(template, hudData))
const list = hud.find('div.mournblade-cyd2-hud-list')
MournbladeCYD2TokenHud._toggleHudListActive(hud, list);
hud.find('img.mournblade-cyd2-hud-togglebutton').click(event => MournbladeCYD2TokenHud._toggleHudListActive(hud, list));
list.find('.mournblade-cyd2-hud-adversite').click(onMenuItem);
insertionPoint.after(hud);
}
/* -------------------------------------------- */
static _showControlWhen(control, condition) {
if (condition) {
control.show()
}
else {
control.hide()
}
}
/* -------------------------------------------- */
static _toggleHudListActive(hud, list) {
hud.toggleClass('active')
MournbladeCYD2TokenHud._showControlWhen(list, hud.hasClass('active'))
}
}

View File

@@ -0,0 +1,228 @@
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class MournbladeCYD2ItemSheet extends foundry.appv1.sheets.ItemSheet {
/** @override */
static get defaultOptions() {
return foundry.utils.mergeObject(super.defaultOptions, {
classes: ["fvtt-mournblade-cyd2", "sheet", "item"],
template: "systems/fvtt-mournblade-cyd2/templates/item-sheet.html",
dragDrop: [{ dragSelector: null, dropSelector: null }],
width: 620,
height: 550,
tabs: [{navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "description"}]
});
}
/* -------------------------------------------- */
_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
}
/* -------------------------------------------- */
/** @override */
setPosition(options = {}) {
const position = super.setPosition(options);
const sheetBody = this.element.find(".sheet-body");
const bodyHeight = position.height - 192;
sheetBody.css("height", bodyHeight);
if (this.item.type.includes('weapon')) {
position.width = 640;
}
return position;
}
/* -------------------------------------------- */
async getData() {
const objectData = foundry.utils.duplicate(this.object)
let formData = {
title: this.title,
id: this.id,
type: objectData.type,
img: objectData.img,
name: objectData.name,
editable: this.isEditable,
cssClass: this.isEditable ? "editable" : "locked",
attributs: MournbladeCYD2Utility.getAttributs(),
system: objectData.system,
limited: this.object.limited,
options: this.options,
owner: this.document.isOwner,
description: await TextEditor.enrichHTML(this.object.system.description, {async: true}),
mr: (this.object.type == 'specialisation'),
isGM: game.user.isGM,
config: game.system.mournbladecyd2.config
}
if ( objectData.type == "don") {
formData.sacrifice = await TextEditor.enrichHTML(this.object.system.sacrifice, {async: true})
}
//this.options.editable = !(this.object.origin == "embeddedItem");
console.log("ITEM DATA", formData, this);
return formData;
}
/* -------------------------------------------- */
_getHeaderButtons() {
let buttons = super._getHeaderButtons();
buttons.unshift({
class: "post",
icon: "fas fa-comment",
onclick: ev => this.postItem()
});
return buttons
}
/* -------------------------------------------- */
postItem() {
let chatData = foundry.utils.duplicate(MournbladeCYD2Utility.data(this.item));
if (this.actor) {
chatData.actor = { id: this.actor.id };
}
// Don't post any image for the item (which would leave a large gap) if the default image is used
if (chatData.img.includes("/blank.png")) {
chatData.img = null;
}
// JSON object for easy creation
chatData.jsondata = JSON.stringify(
{
compendium: "postedItem",
payload: chatData,
});
renderTemplate('systems/fvtt-mournblade-cyd2/templates/post-item.html', chatData).then(html => {
let chatOptions = MournbladeCYD2Utility.chatDataSetup(html);
ChatMessage.create(chatOptions)
});
}
/* -------------------------------------------- */
/** @override */
activateListeners(html) {
super.activateListeners(html);
// Everything below here is only needed if the sheet is editable
if (!this.options.editable) return;
// Update Inventory Item
html.find('.item-edit').click(ev => {
const li = $(ev.currentTarget).parents(".item")
const item = this.object.options.actor.getOwnedItem(li.data("item-id"))
item.sheet.render(true);
});
html.find('.delete-subitem').click(ev => {
this.deleteSubitem(ev);
})
html.find('.edit-predilection').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].name = ev.currentTarget.value
pred[index].id = pred[index].id || randomID(16)
this.object.update( { 'system.predilections': pred })
})
html.find('.edit-predilection-description').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].description = ev.currentTarget.value
pred[index].id = pred[index].id || randomID(16)
this.object.update( { 'system.predilections': pred })
})
html.find('.predilection-acquise').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].acquise = ev.currentTarget.checked
pred[index].id = pred[index].id || randomID(16)
this.object.update( { 'system.predilections': pred })
})
html.find('.predilection-maitrise').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].maitrise = ev.currentTarget.checked
pred[index].id = pred[index].id || randomID(16)
this.object.update( { 'system.predilections': pred })
})
html.find('.predilection-used').change(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred[index].used = ev.currentTarget.checked
pred[index].id = pred[index].id || randomID(16)
this.object.update( { 'system.predilections': pred })
})
html.find('#add-predilection').click(ev => {
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred.push( { name: "Nouvelle prédilection", id: randomID(16), used: false })
this.object.update( { 'system.predilections': pred })
})
html.find('.delete-prediction').click(ev => {
const li = $(ev.currentTarget).parents(".prediction-item")
let index = li.data("prediction-index")
let pred = foundry.utils.duplicate(this.object.system.predilections)
pred.splice(index,1)
this.object.update( { 'system.predilections': pred })
})
html.find('#add-automation').click(ev => {
let autom = foundry.utils.duplicate(this.object.system.automations)
autom.push( { eventtype: "on-drop", name: "Automatisation 1", bonusname: "vigueur", bonus: 0, competence: "", minLevel: 0, baCost: 0, id: randomID(16) })
this.object.update( { 'system.automations': autom })
})
html.find('.delete-automation').click(ev => {
const li = $(ev.currentTarget).parents(".automation-item")
let index = li.data("automation-index")
let autom = foundry.utils.duplicate(this.object.system.automations)
autom.splice(index,1)
this.object.update( { 'system.automations': autom })
})
html.find('.automation-edit-field').change(ev => {
let index = $(ev.currentTarget).data("automation-index")
let field = $(ev.currentTarget).data("automation-field")
let auto = foundry.utils.duplicate(this.object.system.automations)
auto[index][field] = ev.currentTarget.value
auto[index].id = auto[index].id || randomID(16)
this.object.update( { 'system.automations': auto })
})
// Update Inventory Item
html.find('.item-delete').click(ev => {
const li = $(ev.currentTarget).parents(".item");
let itemId = li.data("item-id");
let itemType = li.data("item-type");
});
}
/* -------------------------------------------- */
get template() {
let type = this.item.type;
return `systems/fvtt-mournblade-cyd2/templates/item-${type}-sheet.html`;
}
/* -------------------------------------------- */
/** @override */
_updateObject(event, formData) {
return this.object.update(formData);
}
}

View File

@@ -0,0 +1,32 @@
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
export const defaultItemImg = {
competence: "systems/fvtt-mournblade-cyd2/assets/icons/competence.webp",
arme: "systems/fvtt-mournblade-cyd2/assets/icons/melee.webp",
equipement: "systems/fvtt-mournblade-cyd2/assets/icons/equipement.webp",
monnaie: "systems/fvtt-mournblade-cyd2/assets/icons/monnaie.webp",
predilection: "systems/fvtt-mournblade-cyd2/assets/icons/predilection.webp",
protection: "systems/fvtt-mournblade-cyd2/assets/icons/protection.webp",
talent: "systems/fvtt-mournblade-cyd2/assets/icons/talent.webp",
historique: "systems/fvtt-mournblade-cyd2/assets/icons/historique.webp",
profil: "systems/fvtt-mournblade-cyd2/assets/icons/profil.webp",
artefact: "systems/fvtt-mournblade-cyd2/assets/icons/artefact.webp",
contact: "systems/fvtt-mournblade-cyd2/assets/icons/contacts.webp",
ressource: "systems/fvtt-mournblade-cyd2/assets/icons/ressources.webp",
mutation: "systems/fvtt-mournblade-cyd2/assets/icons/mutation.webp",
}
/**
* Extend the basic ItemSheet with some very simple modifications
* @extends {ItemSheet}
*/
export class MournbladeCYD2Item extends Item {
constructor(data, context) {
if (!data.img) {
data.img = defaultItemImg[data.type];
}
super(data, context);
}
}

View File

@@ -0,0 +1,140 @@
/**
* MournbladeCYD2 system
* Author: Uberwald
* Software License: Prop
*/
/* -------------------------------------------- */
/* -------------------------------------------- */
// Import Modules
import { MournbladeCYD2Actor } from "./mournblade-cyd2-actor.js";
import { MournbladeCYD2ItemSheet } from "./mournblade-cyd2-item-sheet.js";
import { MournbladeCYD2ActorSheet } from "./mournblade-cyd2-actor-sheet.js";
import { MournbladeCYD2CreatureSheet } from "./mournblade-cyd2-creature-sheet.js";
import { MournbladeCYD2CelluleSheet } from "./mournblade-cyd2-cellule-sheet.js";
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
import { MournbladeCYD2Combat } from "./mournblade-cyd2-combat.js";
import { MournbladeCYD2Item } from "./mournblade-cyd2-item.js";
import { MournbladeCYD2Automation } from "./mournblade-cyd2-automation.js";
import { MournbladeCYD2TokenHud } from "./mournblade-cyd2-hud.js";
import { MOURNBLADECYD2_CONFIG } from "./mournblade-cyd2-config.js";
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
/************************************************************************************/
Hooks.once("init", async function () {
console.log(`Initializing MournbladeCYD2 RPG`);
/* -------------------------------------------- */
// preload handlebars templates
MournbladeCYD2Utility.preloadHandlebarsTemplates()
/* -------------------------------------------- */
// Set an initiative formula for the system
CONFIG.Combat.initiative = {
formula: "1d10",
decimals: 1
};
/* -------------------------------------------- */
game.socket.on("system.fvtt-mournblade-cyd2", data => {
MournbladeCYD2Utility.onSocketMesssage(data)
});
/* -------------------------------------------- */
// Define custom Entity classes
CONFIG.Combat.documentClass = MournbladeCYD2Combat
CONFIG.Actor.documentClass = MournbladeCYD2Actor
CONFIG.Item.documentClass = MournbladeCYD2Item
game.system.mournbladecyd2 = {
MournbladeCYD2Utility,
MournbladeCYD2Automation,
config: MOURNBLADECYD2_CONFIG
}
/* -------------------------------------------- */
// Regster sheet application classes
foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
foundry.documents.collections.Actors.registerSheet("fvtt-mournblade-cyd2", MournbladeCYD2ActorSheet, { types: ["personnage"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("fvtt-mournblade-cyd2", MournbladeCYD2CreatureSheet, { types: ["creature"], makeDefault: true })
foundry.documents.collections.Actors.registerSheet("fvtt-mournblade-cyd2", MournbladeCYD2CelluleSheet, { types: ["cellule"], makeDefault: true });
foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
foundry.documents.collections.Items.registerSheet("fvtt-mournblade-cyd2", MournbladeCYD2ItemSheet, { makeDefault: true })
MournbladeCYD2Utility.init()
MournbladeCYD2Automation.init()
MournbladeCYD2TokenHud.init()
});
/* -------------------------------------------- */
function welcomeMessage() {
ChatMessage.create({
user: game.user.id,
whisper: [game.user.id],
content: `<div id="welcome-message-MournbladeCYD2"><span class="rdd-roll-part">
<strong>Bienvenue dans MournbladeCYD2 et le troisième Millénaire !</strong>
<p>Les livres de Mournblade sont nécessaires pour jouer : https://www.titam-france.fr</p>
<p>Mournblade est un jeu de rôle publié par Titam France/Sombres projets, tous les droits leur appartiennent.</p>
<p>Système développé par LeRatierBretonnien, support sur le <a href="https://discord.gg/pPSDNJk">Discord FR de Foundry</a>.</p>
` });
}
/* -------------------------------------------- */
async function importDefaultScene() {
let exists = game.scenes.find(j => j.name == "Accueil");
if (!exists) {
const scenes = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.scenes")
let newDocuments = scenes.filter(i => i.name == "Accueil");
if (newDocuments) {
await game.scenes.documentClass.create(newDocuments);
game.scenes.find(i => i.name == "Accueil").activate();
}
}
}
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
Hooks.once("ready", function () {
MournbladeCYD2Utility.ready()
// User warning
if (!game.user.isGM && game.user.character == undefined) {
ui.notifications.info("Attention ! Aucun personnage n'est relié au joueur !");
ChatMessage.create({
content: "<b>ATTENTION</b> Le joueur " + game.user.name + " n'est relié à aucun personnage !",
user: game.user._id
});
}
import("https://www.uberwald.me/fvtt_appcount/count-class-ready.js").then(moduleCounter => {
console.log("ClassCounter loaded", moduleCounter)
moduleCounter.ClassCounter.registerUsageCount()
}).catch(err =>
console.log("No stats available, giving up.")
)
importDefaultScene()
welcomeMessage()
});
/* -------------------------------------------- */
/* Foundry VTT Initialization */
/* -------------------------------------------- */
Hooks.on("chatMessage", (html, content, msg) => {
if (content[0] == '/') {
let regExp = /(\S+)/g;
let commands = content.match(regExp);
if (game.system.mournblade.commands.processChatCommand(commands, content, msg)) {
return false;
}
}
return true;
});

View File

@@ -0,0 +1,142 @@
import { MournbladeCYD2Utility } from "./mournblade-cyd2-utility.js";
export class MournbladeCYD2RollDialog extends Dialog {
/* -------------------------------------------- */
static async create(actor, rollData ) {
let options = { classes: ["MournbladeCYD2Dialog"], width: 320, height: 'fit-content', 'z-index': 99999 };
let html = await foundry.applications.handlebars.renderTemplate('systems/fvtt-mournblade-cyd2/templates/roll-dialog-generic.html', rollData);
return new MournbladeCYD2RollDialog(actor, rollData, html, options );
}
/* -------------------------------------------- */
constructor(actor, rollData, html, options, close = undefined) {
let conf = {
title: "Test de Capacité",
content: html,
buttons: {
rolld10: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer 1d10",
callback: () => { this.roll("d10") }
},
rolld20: {
icon: '<i class="fas fa-check"></i>',
label: "Lancer 1d20",
callback: () => { this.roll("d20") }
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Annuler",
callback: () => { this.close() }
} },
close: close
}
super(conf, options);
this.actor = actor
this.rollData = rollData
}
/* -------------------------------------------- */
roll ( dice) {
this.rollData.mainDice = dice
MournbladeCYD2Utility.rollMournbladeCYD2( this.rollData )
}
/* -------------------------------------------- */
activateListeners(html) {
super.activateListeners(html);
var dialog = this;
function onLoad() {
}
$(function () { onLoad(); });
html.find('#modificateur').change(async (event) => {
this.rollData.modificateur = Number(event.currentTarget.value)
})
html.find('#difficulte').change( (event) => {
console.log("Difficulte: " + event.currentTarget.value)
this.rollData.difficulte = Number(event.currentTarget.value)
})
html.find('#attrKey').change(async (event) => {
this.rollData.attrKey = String(event.currentTarget.value)
})
html.find('#attrKey2').change(async (event) => {
this.rollData.attrKey2 = String(event.currentTarget.value)
})
html.find('#select-maitrise').change(async (event) => {
this.rollData.maitriseId = String(event.currentTarget.value)
})
html.find('#competence-talents').change((event) => {
this.rollData.selectedTalents = $('#competence-talents').val()
})
html.find('#taille-cible').change((event) => {
this.rollData.tailleCible = String(event.currentTarget.value)
})
html.find('#tireur-deplacement').change((event) => {
this.rollData.tireurDeplacement = String(event.currentTarget.value)
})
html.find('#cible-couvert').change((event) => {
this.rollData.cibleCouvert = String(event.currentTarget.value)
})
html.find('#distance-tir').change((event) => {
this.rollData.distanceTir = String(event.currentTarget.value)
})
html.find('#bonus-malus-context').change((event) => {
this.rollData.bonusMalusContext = Number(event.currentTarget.value)
})
html.find('#defenseur-au-sol').change((event) => {
this.rollData.defenseurAuSol = event.currentTarget.checked
})
html.find('#ambidextre-1').change((event) => {
this.rollData.ambidextre1 = event.currentTarget.checked
})
html.find('#ambidextre-2').change((event) => {
this.rollData.ambidextre2 = event.currentTarget.checked
})
html.find('#attaque-monte').change((event) => {
this.rollData.attqueMonte = event.currentTarget.checked
})
html.find('#defenseur-aveugle').change((event) => {
this.rollData.defenseurAveugle = event.currentTarget.checked
})
html.find('#defenseur-de-dos').change((event) => {
this.rollData.defenseurDeDos = event.currentTarget.checked
})
html.find('#defenseur-restreint').change((event) => {
this.rollData.defenseurRestreint = event.currentTarget.checked
})
html.find('#defenseur-immobilise').change((event) => {
this.rollData.defenseurImmobilise = event.currentTarget.checked
})
html.find('#attaque-charge').change((event) => {
this.rollData.attaqueCharge = event.currentTarget.checked
})
html.find('#charge-cavalerie').change((event) => {
this.rollData.chargeCavalerie = event.currentTarget.checked
})
html.find('#attaquants-multiple').change((event) => {
this.rollData.attaquantsMultiples = event.currentTarget.checked
})
html.find('#soutiens').change((event) => {
this.rollData.soutiens = Number(event.currentTarget.value)
})
html.find('#feinte').change((event) => {
this.rollData.feinte = event.currentTarget.checked
})
html.find('#contenir').change((event) => {
this.rollData.contenir = event.currentTarget.checked
})
html.find('#attaque-desarme').change((event) => {
this.rollData.attaqueDesarme = event.currentTarget.checked
})
}
}

View File

@@ -0,0 +1,834 @@
/* -------------------------------------------- */
import { MournbladeCYD2Combat } from "./mournblade-cyd2-combat.js";
import { MournbladeCYD2Commands } from "./mournblade-cyd2-commands.js";
/* -------------------------------------------- */
const __distanceDifficulte = { "porteecourte": 5, "porteemoyenne": 9, "porteelongue": 14 }
const __tireurDeplacement = { immobile: 0, lent: 3, rapide: 5 }
const __cibleCouvert = { aucun: 0, leger: 5, complet: 10 }
const __tailleCible = { normal: 0, main: 10, enfant: 3, maison: -10 }
/* -------------------------------------------- */
export class MournbladeCYD2Utility {
/* -------------------------------------------- */
static async init() {
Hooks.on('renderChatLog', (log, html, data) => MournbladeCYD2Utility.chatListeners(html))
Hooks.on("getChatMessageContextOptions", (html, options) => MournbladeCYD2Utility.chatRollMenu(html, options))
Hooks.on("getCombatTrackerEntryContext", (html, options) => {
MournbladeCYD2Utility.pushInitiativeOptions(html, options);
})
this.rollDataStore = {}
this.defenderStore = {}
MournbladeCYD2Commands.init()
Handlebars.registerHelper('count', function (list) {
return list.length;
})
Handlebars.registerHelper('includes', function (array, val) {
return array.includes(val);
})
Handlebars.registerHelper('upper', function (text) {
return text.toUpperCase();
})
Handlebars.registerHelper('lower', function (text) {
return text.toLowerCase()
})
Handlebars.registerHelper('upperFirst', function (text) {
if (typeof text !== 'string') return text
return text.charAt(0).toUpperCase() + text.slice(1)
})
Handlebars.registerHelper('notEmpty', function (list) {
return list.length > 0;
})
Handlebars.registerHelper('mul', function (a, b) {
return parseInt(a) * parseInt(b);
})
game.settings.register("fvtt-mournblade-cyd2", "mournblade-cyd2-pause-logo", {
name: "Logo de pause",
scope: "world",
config: true,
requiresReload: true,
default: "logo_pause_resistance",
type: String,
choices: { // If choices are defined, the resulting setting will be a select menu
"mournblade-cyd2_logo": "MournbladeCYD2 (Texte)",
"logo_pause_resistance": "Résistance",
"logo_pause_mournblade-cyd2_stone": "MournbladeCYD2 (Pierre)",
"logo_pause_mournblade-cyd2_violet": "MournbladeCYD2 (Violet)",
"logo_pause_mournblade-cyd2_beige": "MournbladeCYD2 (Beige)",
"logo_pause_mournblade-cyd2_rouge": "MournbladeCYD2 (Rouge)"
},
})
}
/* -------------------------------------------- */
static sortArrayObjectsByName(myArray) {
myArray.sort((a, b) => {
return a.name.localeCompare(b.name);
})
}
/* -------------------------------------------- */
static getModificateurOptions() {
let opt = []
for (let i = -15; i <= 15; i++) {
opt.push(`<option value="${i}">${i}</option>`)
}
return opt.concat("\n")
}
/* -------------------------------------------- */
static getPointAmeOptions() {
let opt = []
for (let i = 1; i <= 20; i++) {
opt.push(`<option value="${i}">${i}</option>`)
}
return opt.concat("\n")
}
/* -------------------------------------------- */
static getAttributs() {
return { adr: "Adresse", pui: "Puissance", cla: "Clairvoyance", pre: "Présence", tre: "Trempe" }
}
/* -------------------------------------------- */
static pushInitiativeOptions(html, options) {
}
/* -------------------------------------------- */
static getSkills() {
return this.skills
}
/* -------------------------------------------- */
static async ready() {
const skills = await MournbladeCYD2Utility.loadCompendium("fvtt-mournblade-cyd2.skills")
this.skills = skills.map(i => i.toObject())
// Setup pause logo
let logoPause = "systems/fvtt-mournblade-cyd2/assets/logos/" + game.settings.get("fvtt-mournblade-cyd2", "mournblade-cyd2-pause-logo") + ".webp"
let logoImg = document.querySelector('#pause').children[0]
logoImg.setAttribute('style', `content: url(${logoPause})`)
game.system.mournbladecyd2.config.listeNiveauSkill = this.createDirectOptionList(0, 10)
game.system.mournbladecyd2.config.listeNiveauCreature = this.createDirectOptionList(0, 35)
game.system.mournbladecyd2.config.listeNiveauContact = this.createDirectOptionList(1, 3)
}
/* -------------------------------------------- */
static createDirectOptionList(min, max) {
let options = {};
for (let i = min; i <= max; i++) {
options[`${i}`] = `${i}`;
}
return options;
}
static createArrayOptionList(min, max) {
let options = [];
for (let i = min; i <= max; i++) {
options.push({ key: `${i}`, label: `${i}` });
}
return options;
}
/* -------------------------------------------- */
static async loadCompendiumData(compendium) {
const pack = game.packs.get(compendium);
return await pack?.getDocuments() ?? [];
}
/* -------------------------------------------- */
static async loadCompendium(compendium, filter = item => true) {
let compendiumData = await MournbladeCYD2Utility.loadCompendiumData(compendium);
return compendiumData.filter(filter);
}
/* -------------------------------------------- */
static getOptionsStatusList() {
return this.optionsStatusList;
}
/* -------------------------------------------- */
static async chatListeners(html) {
$(html).on("click", '.predilection-reroll', async event => {
let predIdx = $(event.currentTarget).data("predilection-index")
let messageId = MournbladeCYD2Utility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
await actor.setPredilectionUsed(rollData.competence._id, predIdx)
rollData.competence = foundry.utils.duplicate(actor.getCompetence(rollData.competence._id))
MournbladeCYD2Utility.rollMournbladeCYD2(rollData)
})
$(html).on("click", '.roll-chat-degat', async event => {
let messageId = MournbladeCYD2Utility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
actor.rollArmeDegats(rollData.arme._id, rollData.targetVigueur, rollData)
})
$(html).on("click", '.roll-chat-degat-devastateur', async event => {
let messageId = MournbladeCYD2Utility.findChatMessageId(event.currentTarget)
let message = game.messages.get(messageId)
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
rollData.applyCoupDevastateur = true
actor.rollArmeDegats(rollData.arme._id, rollData.targetVigueur, rollData)
})
}
/* -------------------------------------------- */
static async preloadHandlebarsTemplates() {
const templatePaths = [
'systems/fvtt-mournblade-cyd2/templates/editor-notes-gm.html',
'systems/fvtt-mournblade-cyd2/templates/partial-item-header.html',
'systems/fvtt-mournblade-cyd2/templates/partial-item-description.html',
'systems/fvtt-mournblade-cyd2/templates/partial-item-nav.html',
'systems/fvtt-mournblade-cyd2/templates/partial-item-prix.html',
'systems/fvtt-mournblade-cyd2/templates/partial-automation.html',
'systems/fvtt-mournblade-cyd2/templates/hud-adversites.html',
]
return foundry.applications.handlebars.loadTemplates(templatePaths);
}
/* -------------------------------------------- */
static removeChatMessageId(messageId) {
if (messageId) {
game.messages.get(messageId)?.delete();
}
}
static findChatMessageId(current) {
return MournbladeCYD2Utility.getChatMessageId(MournbladeCYD2Utility.findChatMessage(current));
}
static getChatMessageId(node) {
return node?.attributes.getNamedItem('data-message-id')?.value;
}
static findChatMessage(current) {
return MournbladeCYD2Utility.findNodeMatching(current, it => it.classList.contains('chat-message') && it.attributes.getNamedItem('data-message-id'))
}
static findNodeMatching(current, predicate) {
if (current) {
if (predicate(current)) {
return current;
}
return MournbladeCYD2Utility.findNodeMatching(current.parentElement, predicate);
}
return undefined;
}
/* -------------------------------------------- */
static createDirectOptionList(min, max) {
let options = {};
for (let i = min; i <= max; i++) {
options[`${i}`] = `${i}`;
}
return options;
}
/* -------------------------------------------- */
static buildListOptions(min, max) {
let options = ""
for (let i = min; i <= max; i++) {
options += `<option value="${i}">${i}</option>`
}
return options;
}
/* -------------------------------------------- */
static getTarget() {
if (game.user.targets && game.user.targets.size == 1) {
for (let target of game.user.targets) {
return target;
}
}
return undefined;
}
/* -------------------------------------------- */
static getActorFromRollData(rollData) {
let actor = game.actors.get(rollData.actorId)
if (rollData.tokenId) {
let token = canvas.tokens.placeables.find(t => t.id == rollData.tokenId)
if (token) {
actor = token.actor
}
}
return actor
}
/* -------------------------------------------- */
static updateRollData(rollData) {
let id = rollData.rollId;
let oldRollData = this.rollDataStore[id] || {};
let newRollData = foundry.utils.mergeObject(oldRollData, rollData);
this.rollDataStore[id] = newRollData;
}
/* -------------------------------------------- */
static onSocketMesssage(msg) {
if (msg.msg == "msg_apply_combativite") {
let defender = game.canvas.tokens.get(msg.data.defenderTokenId)?.actor
if (defender) {
defender.changeEtatCombativite(msg.data.value)
} else {
console.warn("MournbladeCYD2Utility.onSocketMesssage : Impossible de trouver le token pour appliquer la combativité", msg.defenderTokenId)
}
}
}
/* -------------------------------------------- */
static chatDataSetup(content, modeOverride, isRoll = false, forceWhisper) {
let chatData = {
user: game.user.id,
rollMode: modeOverride || game.settings.get("core", "rollMode"),
content: content
};
if (["gmroll", "blindroll"].includes(chatData.rollMode)) chatData["whisper"] = ChatMessage.getWhisperRecipients("GM").map(u => u.id);
if (chatData.rollMode === "blindroll") chatData["blind"] = true;
else if (chatData.rollMode === "selfroll") chatData["whisper"] = [game.user];
if (forceWhisper) { // Final force !
chatData["speaker"] = ChatMessage.getSpeaker();
chatData["whisper"] = ChatMessage.getWhisperRecipients(forceWhisper);
}
return chatData;
}
/* -------------------------------------------- */
static async showDiceSoNice(roll, rollMode) {
if (game.modules.get("dice-so-nice")?.active) {
if (game.dice3d) {
let whisper = null;
let blind = false;
rollMode = rollMode ?? game.settings.get("core", "rollMode");
switch (rollMode) {
case "blindroll": //GM only
blind = true;
case "gmroll": // GM + rolling player
whisper = this.getUsers(user => user.isGM);
break;
case "roll": // everybody
whisper = this.getUsers(user => user.active);
break;
case "selfroll":
whisper = [game.user.id];
break;
}
await game.dice3d.showForRoll(roll, game.user, true, whisper, blind);
}
}
}
/* -------------------------------------------- */
static computeMonnaieDetails(valueSC) {
let po = Math.floor(valueSC / 400)
let pa = Math.floor((valueSC - (po * 400)) / 20)
let sc = valueSC - (po * 400) - (pa * 20)
return {
po, pa, sc, valueSC
}
}
/* -------------------------------------------- */
static computeResult(rollData) {
rollData.diceResult = rollData.roll.terms[0].results[0].result
if (rollData.mainDice.includes("d20")) {
let diceValue = rollData.roll.terms[0].results[0].result
if (diceValue % 2 == 1) {
//console.log("PAIR/IMP2", diceValue)
rollData.finalResult -= rollData.roll.terms[0].results[0].result // Substract value
if (diceValue == 1 || diceValue == 11) {
rollData.isDramatique = true
rollData.isSuccess = false
}
}
}
//console.log("Result : ", rollData
this.computeResultQuality(rollData)
}
/* -------------------------------------------- */
static computeResultQuality(rollData) {
if (rollData.difficulte > 0 && !rollData.isDramatique) {
rollData.isSuccess = (rollData.finalResult >= rollData.difficulte)
rollData.isHeroique = ((rollData.finalResult - rollData.difficulte) >= 10)
rollData.isDramatique = ((rollData.finalResult - rollData.difficulte) <= -10)
}
}
/* -------------------------------------------- */
static applyCombativite(rollData, value) {
if (game.user.isGM) {
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor
defender.changeEtatCombativite(value)
} else {
game.socket.emit("system.fvtt-mournblade-cyd2", { msg: "msg_apply_combativite", data: { defenderTokenId: rollData.defenderTokenId, value } });
}
}
/* -------------------------------------------- */
static async rollMournbladeCYD2(rollData) {
let actor = this.getActorFromRollData(rollData)
if (rollData.attrKey == "tochoose") { // No attr selected, force address
rollData.attrKey = "adr"
}
if (!rollData.attr) {
rollData.actionImg = "systems/fvtt-mournblade-cyd2/assets/icons/" + actor.system.attributs[rollData.attrKey].labelnorm + ".webp"
rollData.attr = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey])
}
if (rollData.attrKey2 != "none") {
rollData.attr2 = foundry.utils.duplicate(actor.system.attributs[rollData.attrKey2])
}
if (rollData.maitriseId != "none") {
rollData.selectedMaitrise = rollData.maitrises.find(p => p.id == rollData.maitriseId)
rollData.diceFormula = "2" + rollData.mainDice + "kh"
} else {
rollData.diceFormula = "1" + rollData.mainDice
}
//console.log("BEFORE COMP", rollData)
if (rollData.competence) {
rollData.predilections = foundry.utils.duplicate(rollData.competence.system.predilections || [])
let compmod = (rollData.competence.system.niveau == 0) ? -3 : 0
rollData.diceFormula += `+${rollData.attr.value}+${rollData.competence.system.niveau}+${rollData.modificateur}+${compmod}`
if (rollData.selectedTalents && rollData.selectedTalents.length > 0) {
for (let id of rollData.selectedTalents) {
let talent = rollData.talents.find(t => t._id == id)
let bonusOK = true
if (talent.system.baCost) {
bonusOK = actor.checkBonneAventure(talent.system.baCost)
if (bonusOK) {
actor.changeBonneAventure(-talent.system.baCost)
} else {
ui.notifications.warn("Vous n'avez pas assez de points de Bonne Aventure !")
}
}
if (bonusOK) {
rollData.diceFormula += `+${talent.system.bonus}`
}
}
}
rollData.diceFormula += `+${rollData.bonusMalusContext}`
} else if (rollData.attr2) {
rollData.diceFormula += `+${rollData.attr.value}+${rollData.attr2.value}+${rollData.modificateur}+${rollData.bonusMalusContext}`
} else {
rollData.diceFormula += `+${rollData.attr.value}*${rollData.multiplier}+${rollData.modificateur}+${rollData.bonusMalusContext}`
}
// Bonus arme naturelle en défense
if (rollData.bonusArmeNaturelle) {
rollData.diceFormula += `+${rollData.bonusArmeNaturelle}`
}
if (rollData.attaquantsMultiples) {
rollData.diceFormula += `+3`
}
if (rollData.hasAmbidextre) {
if (rollData.ambidextre1) {
rollData.diceFormula += `-3`
} else if (rollData.ambidextre2) {
rollData.diceFormula += `-6`
}
}
if (rollData.defenseurAuSol) {
rollData.diceFormula += `+3`
}
if (rollData.defenseurAveugle) {
rollData.diceFormula += `+10`
}
if (rollData.defenseurDeDos) {
rollData.diceFormula += `+5`
}
if (rollData.defenseurRestreint) {
rollData.diceFormula += `+3`
}
if (rollData.defenseurImmobilise) {
rollData.diceFormula += `+5`
}
if (rollData.soutiens > 0) { // 1 soutien = +3, 2 soutiens = +4, 3 soutiens = +5
rollData.diceFormula += `+${rollData.soutiens + 2}`
}
if (rollData.arme?.system.isDistance) {
rollData.difficulte = __distanceDifficulte[rollData.distanceTir]
rollData.difficulte += __tireurDeplacement[rollData.tireurDeplacement]
rollData.difficulte += __cibleCouvert[rollData.cibleCouvert]
rollData.difficulte += __tailleCible[rollData.tailleCible]
rollData.difficulte += rollData.cibleDeplace ? 3 : 0
rollData.difficulte += rollData.cibleCaC ? 3 : 0
rollData.difficulte += rollData.protectionDefenseur
}
if (rollData.attaqueDesarme) {
rollData.difficulte += 10
}
// Ajout adversités
rollData.diceFormula += `-${rollData.nbAdversites}`
if (rollData.arme && rollData.arme.type == "arme") {
rollData.diceFormula += `+${rollData.arme.system.bonusmaniementoff}`
}
// Gestion de la feinte éventuelle
rollData.nbCombativitePerdu = 1
let myRoll = await new Roll(rollData.diceFormula).roll()
await this.showDiceSoNice(myRoll, game.settings.get("core", "rollMode"))
rollData.roll = foundry.utils.duplicate(myRoll)
console.log(">>>> ", myRoll)
rollData.finalResult = myRoll.total
this.computeResult(rollData)
if (rollData.isInit) {
actor.setFlag("world", "last-initiative", rollData.finalResult)
}
if (rollData.feinte) {
actor.changeBonneAventure(-1)
if (rollData.isHeroique) {
rollData.nbCombativitePerdu = "vaincu"
} else if (rollData.isSuccess) {
rollData.nbCombativitePerdu = 2
}
}
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd2/templates/chat-generic-result.html`, rollData)
}, rollData)
if ((rollData.coupBas || rollData.arme) && rollData.isSuccess && rollData.defenderTokenId) {
this.applyCombativite(rollData, rollData.nbCombativitePerdu)
}
if (rollData.coupBas && rollData.isSuccess && rollData.defenderTokenId) {
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor
defender.incDecAdversite("bleue", -2)
}
}
/* -------------------------------------------- */
static getCombativiteList(nbActivite) {
let list = [{ value: String(0), label: "Combatif" }]
for (let i = 1; i < nbActivite - 2; i++) {
list.push({ value: String(i), label: "Eprouvé " + i })
}
list[nbActivite - 2] = { value: String(nbActivite - 2), label: "Affaibli" }
list[nbActivite - 1] = { value: String(nbActivite - 1), label: "Très Affaibli" }
list[nbActivite] = { value: String(nbActivite), label: "Vaincu" }
return list
}
/* -------------------------------------------- */
static async bonusRollMournbladeCYD2(rollData) {
rollData.bonusFormula = rollData.addedBonus
console.log("Bonus Roll MournbladeCYD2", rollData.bonusFormula)
if (!Number(rollData.bonusFormula)) {
let bonusRoll = await new Roll(rollData.bonusFormula).roll()
await this.showDiceSoNice(bonusRoll, game.settings.get("core", "rollMode"));
rollData.bonusRoll = foundry.utils.duplicate(bonusRoll)
rollData.finalResult += Number(rollData.bonusRoll.total)
} else {
rollData.finalResult += Number(rollData.bonusFormula)
console.log("Bonus Roll MournbladeCYD2 2", rollData.finalResult)
}
this.computeResultQuality(rollData)
this.createChatWithRollMode(rollData.alias, {
content: await foundry.applications.handlebars.renderTemplate(`systems/fvtt-mournblade-cyd2/templates/chat-generic-result.html`, rollData)
}, rollData)
}
/* -------------------------------------------- */
static getUsers(filter) {
return game.users.filter(filter).map(user => user._id);
}
/* -------------------------------------------- */
static getWhisperRecipients(rollMode, name) {
switch (rollMode) {
case "blindroll": return this.getUsers(user => user.isGM);
case "gmroll": return this.getWhisperRecipientsAndGMs(name);
case "selfroll": return [game.user.id];
}
return undefined;
}
/* -------------------------------------------- */
static getWhisperRecipientsAndGMs(name) {
let recep1 = ChatMessage.getWhisperRecipients(name) || [];
return recep1.concat(ChatMessage.getWhisperRecipients('GM'));
}
/* -------------------------------------------- */
static blindMessageToGM(chatOptions) {
let chatGM = foundry.utils.duplicate(chatOptions);
chatGM.whisper = this.getUsers(user => user.isGM);
chatGM.content = "Blinde message of " + game.user.name + "<br>" + chatOptions.content;
console.log("blindMessageToGM", chatGM);
game.socket.emit("system.fvtt-mournblade-cyd2", { msg: "msg_gm_chat_message", data: chatGM });
}
/* -------------------------------------------- */
static async searchItem(dataItem) {
let item
if (dataItem.pack) {
let id = dataItem.id || dataItem._id
let items = await this.loadCompendium(dataItem.pack, item => item.id == id)
item = items[0] || undefined
} else {
item = game.items.get(dataItem.id)
}
return item
}
/* -------------------------------------------- */
static split3Columns(data) {
let array = [[], [], []];
if (data == undefined) return array;
let col = 0;
for (let key in data) {
let keyword = data[key];
keyword.key = key; // Self-reference
array[col].push(keyword);
col++;
if (col == 3) col = 0;
}
return array;
}
/* -------------------------------------------- */
static async createChatMessage(name, rollMode, chatOptions, rollData = undefined) {
switch (rollMode) {
case "blindroll": // GM only
if (!game.user.isGM) {
this.blindMessageToGM(chatOptions);
chatOptions.whisper = [game.user.id];
chatOptions.content = "Message only to the GM";
}
else {
chatOptions.whisper = this.getUsers(user => user.isGM);
}
break;
default:
chatOptions.whisper = this.getWhisperRecipients(rollMode, name);
break;
}
chatOptions.alias = chatOptions.alias || name
let msg = await ChatMessage.create(chatOptions)
console.log("=======>", rollData)
msg.setFlag("world", "mournblade-cyd2-roll", rollData)
}
/* -------------------------------------------- */
static getBasicRollData() {
let rollData = {
rollId: foundry.utils.randomID(16),
rollMode: game.settings.get("core", "rollMode"),
modificateursOptions: this.getModificateurOptions(),
pointAmeOptions: this.getPointAmeOptions(),
difficulte: 0,
modificateur: 0,
bonusMalusContext: 0,
bonusArmeNaturelle: 0,
defenseurAveugle: false,
defenseurDeDos: false,
defenseurAuSol: false,
defenseurRestreint: false,
defenseurImmobilise: false,
tailleCible: "normal",
tireurDeplacement: "immobile",
cibleCouvert: "aucun",
distanceTir: "porteemoyenne",
attaqueCharge: false,
attaqueDesarme: false,
attaqueAmbidextre1: false,
attaqueAmbidextre2: false,
chargeCavalerie: false,
contenir: false,
soutiens: 0
}
return rollData
}
/* -------------------------------------------- */
static updateWithTarget(rollData) {
let target = MournbladeCYD2Utility.getTarget()
if (target) {
rollData.defenderTokenId = target.id
let defender = game.canvas.tokens.get(rollData.defenderTokenId).actor
rollData.armeDefense = defender.getBestDefenseValue()
rollData.armeAttaqueDefenseur = defender.getBestAttackValue()
rollData.targetVigueur = defender.getVigueur()
rollData.protectionDefenseur = defender.getProtection()
if (rollData.immobiliser || rollData.repousser) {
let combatValues = defender.getCombatValues()
rollData.difficulte = combatValues.defenseTotal + (rollData.armeDefense && rollData.cibleconsciente ? 5 : 0)
} else if (rollData.coupBas) {
let combatValues = defender.getCombatValues()
rollData.difficulte = combatValues.defenseTotal
} else if (rollData.assomer) {
rollData.difficulte = 3 + (defender.system.attributs.tre.value * 2)
} else if (rollData.desengager) {
rollData.difficulte = rollData.armeAttaqueDefenseur?.system?.totalOffensif || 0;
} else if (rollData.armeDefense) {
rollData.difficulte = rollData.armeDefense.system.totalDefensif
if (!rollData.desengager && !rollData.arme.system.armenaturelle && !rollData.arme.system.armefortune) {
if (rollData.armeDefense.system.armenaturelle || rollData.armeDefense.system.armefortune) {
rollData.bonusArmeNaturelle = 3
}
}
} else {
ui.notifications.warn("Aucune arme de défense équipée, difficulté manuelle à positionner.")
}
}
}
/* -------------------------------------------- */
static createChatWithRollMode(name, chatOptions, rollData = undefined) {
this.createChatMessage(name, game.settings.get("core", "rollMode"), chatOptions, rollData)
}
/* -------------------------------------------- */
static applyBonneAventureRoll(li, changed, addedBonus) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
actor.changeBonneAventure(changed)
rollData.isReroll = true
rollData.textBonus = "Bonus de Points d'Aventure"
if (addedBonus == "reroll") {
MournbladeCYD2Utility.rollMournbladeCYD2(rollData)
} else {
rollData.addedBonus = addedBonus
MournbladeCYD2Utility.bonusRollMournbladeCYD2(rollData)
}
}
}
/* -------------------------------------------- */
static applyEclatRoll(li, changed, addedBonus) {
let msgId = $(li).data("message-id")
let msg = game.messages.get(msgId)
if (msg) {
let rollData = msg.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
actor.changeEclat(changed)
rollData.isReroll = true
rollData.textBonus = "Bonus d'Eclat"
if (addedBonus == "reroll") {
MournbladeCYD2Utility.rollMournbladeCYD2(rollData)
} else {
rollData.addedBonus = addedBonus
MournbladeCYD2Utility.bonusRollMournbladeCYD2(rollData)
}
}
}
/* -------------------------------------------- */
static chatRollMenu(html, options) {
let canApply = li => canvas.tokens.controlled.length && li.find(".mournblade-cyd2-roll").length
let canApplyBA = function (li) {
let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
return (!rollData.isReroll && actor.getBonneAventure() > 0)
}
let canApplyPE = function (li) {
let message = game.messages.get($(li).attr("data-message-id"))
let rollData = message.getFlag("world", "mournblade-cyd2-roll")
let actor = MournbladeCYD2Utility.getActorFromRollData(rollData)
return (!rollData.isReroll && actor.getEclat() > 0)
}
options.push(
{
name: "Ajouer +3 (1 point de Bonne Aventure)",
icon: "<i class='fas fa-user-plus'></i>",
condition: canApply && canApplyBA,
callback: li => MournbladeCYD2Utility.applyBonneAventureRoll(li, -1, "+3")
}
)
options.push(
{
name: "Ajouter +10 (1 Point d'Eclat)",
icon: "<i class='fas fa-user-plus'></i>",
condition: canApply && canApplyPE,
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "+10")
}
)
options.push(
{
name: "Relancer le dé (1 point d'Eclat)",
icon: "<i class='fas fa-user-plus'></i>",
condition: canApply && canApplyPE,
callback: li => MournbladeCYD2Utility.applyEclatRoll(li, -1, "reroll")
}
)
return options
}
/* -------------------------------------------- */
static async confirmDelete(actorSheet, li) {
let itemId = li.data("item-id");
let msgTxt = "<p>Etes vous certain de vouloir supprimer cet item ?";
let buttons = {
delete: {
icon: '<i class="fas fa-check"></i>',
label: "Oui !",
callback: () => {
actorSheet.actor.deleteEmbeddedDocuments("Item", [itemId]);
li.slideUp(200, () => actorSheet.render(false));
}
},
cancel: {
icon: '<i class="fas fa-times"></i>',
label: "Non"
}
}
msgTxt += "</p>";
let d = new Dialog({
title: "Confirmer la suppression",
content: msgTxt,
buttons: buttons,
default: "cancel"
});
d.render(true);
}
/************************************************************************************/
static async __create_talents_table() {
let compName = "fvtt-mournblade-cyd2.talents-cellule"
const compData = await MournbladeCYD2Utility.loadCompendium(compName)
let talents = compData.map(i => i.toObject())
let htmlTab = "<table border='1'><tbody>";
for (let entryData of talents) {
console.log(entryData)
htmlTab += `<tr><td>@UUID[Compendium.${compName}.${entryData._id}]{${entryData.name}}</td>`
htmlTab += `<td>${entryData.system.description}</td>`;
htmlTab += "</tr>\n";
}
htmlTab += "</table>";
await JournalEntry.create({ name: 'Liste des Talents de Cellule', content: htmlTab });
}
}

8225
modules/xregexp-all.js Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

View File

@@ -0,0 +1 @@
MANIFEST-000321

0
packs/aides-de-jeu/LOCK Normal file
View File

8
packs/aides-de-jeu/LOG Normal file
View File

@@ -0,0 +1,8 @@
2025/10/25-23:36:03.272311 7ff6e2ffd6c0 Recovering log #319
2025/10/25-23:36:03.325204 7ff6e2ffd6c0 Delete type=3 #317
2025/10/25-23:36:03.325254 7ff6e2ffd6c0 Delete type=0 #319
2025/10/25-23:37:08.116379 7ff6e23ff6c0 Level-0 table #324: started
2025/10/25-23:37:08.116400 7ff6e23ff6c0 Level-0 table #324: 0 bytes OK
2025/10/25-23:37:08.122639 7ff6e23ff6c0 Delete type=0 #322
2025/10/25-23:37:08.148804 7ff6e23ff6c0 Manual compaction at level-0 from '!journal!MUbViCE2PkVxlzqe' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
2025/10/25-23:37:08.148837 7ff6e23ff6c0 Manual compaction at level-1 from '!journal!MUbViCE2PkVxlzqe' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,8 @@
2025/10/17-20:49:09.898677 7f7b753f96c0 Recovering log #315
2025/10/17-20:49:09.909170 7f7b753f96c0 Delete type=3 #313
2025/10/17-20:49:09.909239 7f7b753f96c0 Delete type=0 #315
2025/10/17-22:35:10.581106 7f7b6f3ff6c0 Level-0 table #320: started
2025/10/17-22:35:10.581135 7f7b6f3ff6c0 Level-0 table #320: 0 bytes OK
2025/10/17-22:35:10.587115 7f7b6f3ff6c0 Delete type=0 #318
2025/10/17-22:35:10.603825 7f7b6f3ff6c0 Manual compaction at level-0 from '!journal!MUbViCE2PkVxlzqe' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)
2025/10/17-22:35:10.603883 7f7b6f3ff6c0 Manual compaction at level-1 from '!journal!MUbViCE2PkVxlzqe' @ 72057594037927935 : 1 .. '!journal.pages!gVybbv17TFY8o3Y4.fQidyqfF1TbsZKHM' @ 0 : 0; will stop at (end)

Binary file not shown.

BIN
packs/armes/000275.ldb Normal file

Binary file not shown.

0
packs/armes/000322.log Normal file
View File

1
packs/armes/CURRENT Normal file
View File

@@ -0,0 +1 @@
MANIFEST-000320

0
packs/armes/LOCK Normal file
View File

8
packs/armes/LOG Normal file
View File

@@ -0,0 +1,8 @@
2025/10/25-23:36:02.733608 7ff6e2ffd6c0 Recovering log #318
2025/10/25-23:36:02.794587 7ff6e2ffd6c0 Delete type=3 #316
2025/10/25-23:36:02.794691 7ff6e2ffd6c0 Delete type=0 #318
2025/10/25-23:37:08.072504 7ff6e23ff6c0 Level-0 table #323: started
2025/10/25-23:37:08.072529 7ff6e23ff6c0 Level-0 table #323: 0 bytes OK
2025/10/25-23:37:08.078944 7ff6e23ff6c0 Delete type=0 #321
2025/10/25-23:37:08.090856 7ff6e23ff6c0 Manual compaction at level-0 from '!items!0fit7HelSjaFtXcW' @ 72057594037927935 : 1 .. '!items!wxrzP3NyiHiYnAMJ' @ 0 : 0; will stop at (end)
2025/10/25-23:37:08.090880 7ff6e23ff6c0 Manual compaction at level-1 from '!items!0fit7HelSjaFtXcW' @ 72057594037927935 : 1 .. '!items!wxrzP3NyiHiYnAMJ' @ 0 : 0; will stop at (end)

8
packs/armes/LOG.old Normal file
View File

@@ -0,0 +1,8 @@
2025/10/17-20:49:09.793672 7f7b753f96c0 Recovering log #314
2025/10/17-20:49:09.803546 7f7b753f96c0 Delete type=3 #312
2025/10/17-20:49:09.803615 7f7b753f96c0 Delete type=0 #314
2025/10/17-22:35:10.536969 7f7b6f3ff6c0 Level-0 table #319: started
2025/10/17-22:35:10.537001 7f7b6f3ff6c0 Level-0 table #319: 0 bytes OK
2025/10/17-22:35:10.542920 7f7b6f3ff6c0 Delete type=0 #317
2025/10/17-22:35:10.548820 7f7b6f3ff6c0 Manual compaction at level-0 from '!items!0fit7HelSjaFtXcW' @ 72057594037927935 : 1 .. '!items!wxrzP3NyiHiYnAMJ' @ 0 : 0; will stop at (end)
2025/10/17-22:35:10.548874 7f7b6f3ff6c0 Manual compaction at level-1 from '!items!0fit7HelSjaFtXcW' @ 72057594037927935 : 1 .. '!items!wxrzP3NyiHiYnAMJ' @ 0 : 0; will stop at (end)

BIN
packs/armes/MANIFEST-000320 Normal file

Binary file not shown.

Binary file not shown.

View File

View File

@@ -0,0 +1 @@
MANIFEST-000320

View File

View File

@@ -0,0 +1,8 @@
2025/10/25-23:36:02.598513 7ff6e8bfa6c0 Recovering log #318
2025/10/25-23:36:02.657064 7ff6e8bfa6c0 Delete type=3 #316
2025/10/25-23:36:02.657139 7ff6e8bfa6c0 Delete type=0 #318
2025/10/25-23:37:08.060276 7ff6e23ff6c0 Level-0 table #323: started
2025/10/25-23:37:08.060301 7ff6e23ff6c0 Level-0 table #323: 0 bytes OK
2025/10/25-23:37:08.066211 7ff6e23ff6c0 Delete type=0 #321
2025/10/25-23:37:08.066345 7ff6e23ff6c0 Manual compaction at level-0 from '!items!0nhTxujlIUB63Aqt' @ 72057594037927935 : 1 .. '!items!tFU5yISK6spdNWco' @ 0 : 0; will stop at (end)
2025/10/25-23:37:08.066390 7ff6e23ff6c0 Manual compaction at level-1 from '!items!0nhTxujlIUB63Aqt' @ 72057594037927935 : 1 .. '!items!tFU5yISK6spdNWco' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,8 @@
2025/10/17-20:49:09.768629 7f7b6ffff6c0 Recovering log #314
2025/10/17-20:49:09.779130 7f7b6ffff6c0 Delete type=3 #312
2025/10/17-20:49:09.779215 7f7b6ffff6c0 Delete type=0 #314
2025/10/17-22:35:10.498148 7f7b6f3ff6c0 Level-0 table #319: started
2025/10/17-22:35:10.498192 7f7b6f3ff6c0 Level-0 table #319: 0 bytes OK
2025/10/17-22:35:10.504185 7f7b6f3ff6c0 Delete type=0 #317
2025/10/17-22:35:10.523654 7f7b6f3ff6c0 Manual compaction at level-0 from '!items!0nhTxujlIUB63Aqt' @ 72057594037927935 : 1 .. '!items!tFU5yISK6spdNWco' @ 0 : 0; will stop at (end)
2025/10/17-22:35:10.523691 7f7b6f3ff6c0 Manual compaction at level-1 from '!items!0nhTxujlIUB63Aqt' @ 72057594037927935 : 1 .. '!items!tFU5yISK6spdNWco' @ 0 : 0; will stop at (end)

Binary file not shown.

Binary file not shown.

View File

View File

@@ -0,0 +1 @@
MANIFEST-000320

0
packs/competences/LOCK Normal file
View File

8
packs/competences/LOG Normal file
View File

@@ -0,0 +1,8 @@
2025/10/25-23:36:02.442750 7ff6e37fe6c0 Recovering log #318
2025/10/25-23:36:02.510317 7ff6e37fe6c0 Delete type=3 #316
2025/10/25-23:36:02.515064 7ff6e37fe6c0 Delete type=0 #318
2025/10/25-23:37:08.053820 7ff6e23ff6c0 Level-0 table #323: started
2025/10/25-23:37:08.053846 7ff6e23ff6c0 Level-0 table #323: 0 bytes OK
2025/10/25-23:37:08.060187 7ff6e23ff6c0 Delete type=0 #321
2025/10/25-23:37:08.066336 7ff6e23ff6c0 Manual compaction at level-0 from '!items!15IDGG6JoZnRCQtY' @ 72057594037927935 : 1 .. '!items!yI1zY5k8mAdx9wHK' @ 0 : 0; will stop at (end)
2025/10/25-23:37:08.066381 7ff6e23ff6c0 Manual compaction at level-1 from '!items!15IDGG6JoZnRCQtY' @ 72057594037927935 : 1 .. '!items!yI1zY5k8mAdx9wHK' @ 0 : 0; will stop at (end)

View File

@@ -0,0 +1,8 @@
2025/10/17-20:49:09.744321 7f7b75bfa6c0 Recovering log #314
2025/10/17-20:49:09.753860 7f7b75bfa6c0 Delete type=3 #312
2025/10/17-20:49:09.753934 7f7b75bfa6c0 Delete type=0 #314
2025/10/17-22:35:10.511424 7f7b6f3ff6c0 Level-0 table #319: started
2025/10/17-22:35:10.511449 7f7b6f3ff6c0 Level-0 table #319: 0 bytes OK
2025/10/17-22:35:10.517304 7f7b6f3ff6c0 Delete type=0 #317
2025/10/17-22:35:10.523675 7f7b6f3ff6c0 Manual compaction at level-0 from '!items!15IDGG6JoZnRCQtY' @ 72057594037927935 : 1 .. '!items!yI1zY5k8mAdx9wHK' @ 0 : 0; will stop at (end)
2025/10/17-22:35:10.523698 7f7b6f3ff6c0 Manual compaction at level-1 from '!items!15IDGG6JoZnRCQtY' @ 72057594037927935 : 1 .. '!items!yI1zY5k8mAdx9wHK' @ 0 : 0; will stop at (end)

Binary file not shown.

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