Signes draconiques aléatoires

This commit is contained in:
Vincent Vandemeulebrouck 2021-05-11 21:21:33 +02:00
parent b80af454a2
commit d6325581f7
15 changed files with 204 additions and 145 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
.idea .idea
todo.txt todo.txt
todo.md todo.md
/.vscode

BIN
icons/heures/de-heures.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -2,59 +2,44 @@ import { ChatUtility } from "./chat-utility.js";
import { HtmlUtility } from "./html-utility.js"; import { HtmlUtility } from "./html-utility.js";
import { RdDItemSigneDraconique } from "./item-signedraconique.js"; import { RdDItemSigneDraconique } from "./item-signedraconique.js";
import { Misc } from "./misc.js"; import { Misc } from "./misc.js";
import { RdDRollTables } from "./rdd-rolltables.js";
import { TMRType, TMRUtility } from "./tmr-utility.js"; import { TMRType, TMRUtility } from "./tmr-utility.js";
export class DialogCreateSigneDraconiqueForActors extends Dialog { export class DialogCreateSigneDraconique extends Dialog {
static async createSigneForActors() { static async createSigneForActors() {
const signe = await RdDItemSigneDraconique.randomSigneDraconique();
let dialogData = { let dialogData = {
signe: { signe: signe,
name: 'Un signe draconique', tmrs: TMRUtility.listSelectedTMR(signe.data.typesTMR ?? []),
type: "signedraconique", actors: game.actors.filter(actor => actor.isHautRevant()).map(actor => {
img: 'systems/foundryvtt-reve-de-dragon/icons/tmr/gift.webp', let actorData = duplicate(Misc.data(actor));
data: { actorData.selected = actor.hasPlayerOwner;
typesTMR: DialogCreateSigneDraconiqueForActors.selectRandomTmrs(), return actorData;
ephemere: true, })
duree: "1 round",
difficulte: -5,
valeur: { norm: 10, sign: 10, part: 15 },
}
},
actors: game.actors.filter(actor => actor.isHautRevant()).map(it => duplicate(Misc.data(it)))
}; };
dialogData.tmrs = TMRUtility.listSelectedTMR(dialogData.signe.data.typesTMR ?? []);
dialogData.actors.forEach(it => it.selected = false);
const html = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/dialog-create-signedraconique-actors.html", dialogData); const html = await renderTemplate("systems/foundryvtt-reve-de-dragon/templates/dialog-create-signedraconique.html", dialogData);
new DialogCreateSigneDraconiqueForActors(dialogData, html) new DialogCreateSigneDraconique(dialogData, html)
.render(true); .render(true);
}
static selectRandomTmrs() {
let tmrs = Object.values(TMRType).map(value => Misc.upperFirst(value.name));
const nbTmr = tmrs.length;
let remove = Math.floor(Math.random() * (nbTmr - 1));
for (let i = nbTmr; i > remove; i--) {
tmrs.splice(Math.floor(Math.random() * i), 1);
}
return tmrs;
} }
constructor(dialogData, html, callback) { constructor(dialogData, html, callback) {
let options = { classes: ["DialogCreateSigneDraconiqueActorsActors"], width: 500, height: 650, 'z-index': 99999 }; let options = { classes: ["DialogCreateSigneDraconiqueActorsActors"], width: 500, height: 650, 'z-index': 99999 };
let conf = { let conf = {
title: "Créer un signe pour les personnages", title: "Créer un signe",
content: html, content: html,
default: "Créer le signe", default: "Ajouter aux haut-rêvants",
buttons: { "Créer le signe": { label: "Créer le signe", callback: it => { this._onCreerSigne(); } } } buttons: {
"Ajouter aux haut-rêvants": { label: "Ajouter aux haut-rêvants", callback: it => { this._onCreerSigneActeurs(); } }
}
}; };
super(conf, options); super(conf, options);
this.dialogData = dialogData; this.dialogData = dialogData;
} }
async _onCreerSigne() { async _onCreerSigneActeurs() {
this.validerSigne();
this.dialogData.actors.filter(it => it.selected).map(it => game.actors.get(it._id)) this.dialogData.actors.filter(it => it.selected).map(it => game.actors.get(it._id))
.forEach(actor => this._createSigneForActor(actor, this.dialogData.signe)); .forEach(actor => this._createSigneForActor(actor, this.dialogData.signe));
} }
@ -70,30 +55,47 @@ export class DialogCreateSigneDraconiqueForActors extends Dialog {
}); });
} }
validerSigne() {
this.dialogData.signe.name = $("[name='signe.name']").val();
this.dialogData.signe.data.valeur.norm = $("[name='signe.data.valeur.norm']").val();
this.dialogData.signe.data.valeur.sign = $("[name='signe.data.valeur.sign']").val();
this.dialogData.signe.data.valeur.part = $("[name='signe.data.valeur.part']").val();
this.dialogData.signe.data.difficulte = $("[name='signe.data.difficulte']").val();
this.dialogData.signe.data.ephemere = $("[name='signe.data.ephemere']").prop("checked");
this.dialogData.signe.data.duree = $("[name='signe.data.duree']").val();
this.dialogData.signe.data.typesTMR = $(".select-tmr").val();
}
/* -------------------------------------------- */ /* -------------------------------------------- */
activateListeners(html) { activateListeners(html) {
super.activateListeners(html); super.activateListeners(html);
this.setEphemere(this.dialogData.signe.data.ephemere); this.setEphemere(this.dialogData.signe.data.ephemere);
html.find(".signe-name").change((event) => this.dialogData.signe.name = event.currentTarget.value); html.find(".signe-aleatoire").click(event => this.setSigneAleatoire());
html.find(".signe-data-ephemere").change((event) => this.setEphemere(event.currentTarget.checked)); html.find("[name='signe.data.ephemere']").change((event) => this.setEphemere(event.currentTarget.checked));
html.find(".select-tmr").change((event) => this.onSelectTmr(event));
html.find(".select-actor").change((event) => this.onSelectActor(event)); html.find(".select-actor").change((event) => this.onSelectActor(event));
html.find(".valeur-xp-sort").change((event) => this.onValeurXpSort(event)); html.find(".signe-xp-sort").change((event) => this.onValeurXpSort(event));
} }
async setEphemere(ephemere){ async setSigneAleatoire() {
const newSigne = await RdDItemSigneDraconique.randomSigneDraconique();
$("[name='signe.name']").val(newSigne.name);
$("[name='signe.data.valeur.norm']").val(newSigne.data.valeur.norm);
$("[name='signe.data.valeur.sign']").val(newSigne.data.valeur.sign);
$("[name='signe.data.valeur.part']").val(newSigne.data.valeur.part);
$("[name='signe.data.difficulte']").val(newSigne.data.difficulte);
$("[name='signe.data.duree']").val(newSigne.data.duree);
$("[name='signe.data.ephemere']").prop("checked", newSigne.data.ephemere);
$(".select-tmr").val(newSigne.data.typesTMR);
this.setEphemere(newSigne.data.ephemere);
}
async setEphemere(ephemere) {
this.dialogData.signe.data.ephemere = ephemere; this.dialogData.signe.data.ephemere = ephemere;
HtmlUtility._showControlWhen($(".signe-data-duree"), ephemere); HtmlUtility._showControlWhen($(".signe-data-duree"), ephemere);
} }
async onSelectTmr(event) {
event.preventDefault();
this.dialogData.signe.data.typesTMR = $(".select-tmr").val();
}
async onSelectActor(event) { async onSelectActor(event) {
event.preventDefault(); event.preventDefault();
let selectActor = $(".select-actor");
const options = event.currentTarget.options; const options = event.currentTarget.options;
for (var i = 0; i < options.length; i++) { // looping over the options for (var i = 0; i < options.length; i++) { // looping over the options
const actorId = options[i].attributes["data-actor-id"].value; const actorId = options[i].attributes["data-actor-id"].value;

View File

@ -60,8 +60,23 @@ export class RdDSigneDraconiqueItemSheet extends ItemSheet {
if (!this.options.editable) return; if (!this.options.editable) return;
html.find(".signe-aleatoire").click(event => this.setSigneAleatoire());
html.find(".select-tmr").change((event) => this.onSelectTmr(event)); html.find(".select-tmr").change((event) => this.onSelectTmr(event));
html.find(".valeur-xp-sort").change((event) => this.onValeurXpSort(event.currentTarget.attributes['data-typereussite']?.value, Number(event.currentTarget.value))); html.find(".signe-xp-sort").change((event) => this.onValeurXpSort(event.currentTarget.attributes['data-typereussite']?.value, Number(event.currentTarget.value)));
}
async setSigneAleatoire() {
const newSigne = await RdDItemSigneDraconique.randomSigneDraconique();
// $("[name='signe.name']").val(newSigne.name);
// $("[name='signe.data.valeur.norm']").val(newSigne.data.valeur.norm);
// $("[name='signe.data.valeur.sign']").val(newSigne.data.valeur.sign);
// $("[name='signe.data.valeur.part']").val(newSigne.data.valeur.part);
// $("[name='signe.data.difficulte']").val(newSigne.data.difficulte);
// $("[name='signe.data.duree']").val(newSigne.data.duree);
// $("[name='signe.data.ephemere']").prop("checked", newSigne.data.ephemere);
// $(".select-tmr").val(newSigne.data.typesTMR);
// this.setEphemere(newSigne.data.ephemere);
this.object.update(newSigne);
} }
async onSelectTmr(event) { async onSelectTmr(event) {

View File

@ -1,5 +1,15 @@
import { Misc } from "./misc.js"; import { Misc } from "./misc.js";
import { RdDRollTables } from "./rdd-rolltables.js";
import { TMRType } from "./tmr-utility.js";
const tableSignesIndicatifs = [
{ rarete: "Très facile", difficulte: 0, xp: 6, nbCases: 14 },
{ rarete: "Facile", difficulte: -2, xp: 10, nbCases: 10 },
{ rarete: "Moyen", difficulte: -3, xp: 15, nbCases: 7 },
{ rarete: "Difficile", difficulte: -5, xp: 20, nbCases: 4 },
{ rarete: "Ardu", difficulte: -8, xp: 30, nbCases: 1 }
]
export class RdDItemSigneDraconique { export class RdDItemSigneDraconique {
static prepareSigneDraconiqueMeditation(meditation, rolled) { static prepareSigneDraconiqueMeditation(meditation, rolled) {
@ -57,4 +67,33 @@ export class RdDItemSigneDraconique {
} }
} }
static async randomSigneDraconique() {
let modele = await Misc.rollOneOf(tableSignesIndicatifs);
return {
name: await RdDItemSigneDraconique.randomSigneDescription(),
type: "signedraconique",
img: 'systems/foundryvtt-reve-de-dragon/icons/tmr/signe_draconique.webp',
data: {
typesTMR: await RdDItemSigneDraconique.randomTmrs(modele.nbCases),
ephemere: true,
duree: "1 round",
difficulte: modele.difficulte,
valeur: { norm: modele.xp, sign: modele.xp, part: Math.floor(modele.xp * 1.5) },
}
};
}
static async randomTmrs(nbTmr = undefined) {
let tmrs = Object.values(TMRType).map(value => Misc.upperFirst(value.name));
let keep = nbTmr ?? (await new Roll("1d" + TMRType.length).evaluate().total + 1);
for (let i = tmrs.length; i > keep; i--) {
tmrs.splice(await new Roll("1d" + i).evaluate().total, 1);
}
return tmrs;
}
static async randomSigneDescription() {
return await RdDRollTables.drawTextFromRollTable("Signes draconiques", false);
}
} }

View File

@ -1,6 +1,6 @@
/* -------------------------------------------- */ /* -------------------------------------------- */
import { DialogCreateSigneDraconiqueForActors } from "./dialog-create-signedraconique-actors.js"; import { DialogCreateSigneDraconique } from "./dialog-create-signedraconique.js";
import { RdDItemCompetence } from "./item-competence.js"; import { RdDItemCompetence } from "./item-competence.js";
import { Misc } from "./misc.js"; import { Misc } from "./misc.js";
import { RdDCarac } from "./rdd-carac.js"; import { RdDCarac } from "./rdd-carac.js";
@ -325,7 +325,7 @@ export class RdDCommands {
} }
async creerSignesDraconiques() { async creerSignesDraconiques() {
DialogCreateSigneDraconiqueForActors.createSigneForActors(); DialogCreateSigneDraconique.createSigneForActors();
return true; return true;
} }

View File

@ -8,31 +8,20 @@ export class RdDRollTables {
const table = await pack.getDocument(entry._id); const table = await pack.getDocument(entry._id);
const draw = await table.draw({ displayChat: toChat, rollMode: "gmroll"}); const draw = await table.draw({ displayChat: toChat, rollMode: "gmroll"});
console.log("RdDRollTables", tableName, toChat, ":", draw); console.log("RdDRollTables", tableName, toChat, ":", draw);
return draw; return draw.results.length > 0 ? draw.results[0] : undefined;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static async drawItemFromRollTable(tableName, toChat) { static async drawItemFromRollTable(tableName, toChat = false) {
const draw = await RdDRollTables.genericGetTableResult(tableName, toChat); const drawResult = await RdDRollTables.genericGetTableResult(tableName, toChat);
const drawnItemRef = draw.results.length > 0 ? draw.results[0] : undefined; const pack = game.packs.get(drawResult.data.collection);
if (drawnItemRef.data.collection) { return await pack.getDocument(drawResult.data.resultId);
console.log(drawnItemRef);
const pack = game.packs.get(drawnItemRef.data.collection);
return await pack.getDocument(drawnItemRef.data.resultId);
}
ui.notifications.warn("le tirage ne correspond pas à une entrée d'un Compendium")
return drawnItemRef.text;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
static async drawTextFromRollTable(tableName, toChat) { static async drawTextFromRollTable(tableName, toChat) {
const draw = await RdDRollTables.genericGetTableResult(tableName, toChat); const drawResult = await RdDRollTables.genericGetTableResult(tableName, toChat);
const drawnItemRef = draw.results.length > 0 ? draw.results[0] : undefined; return drawResult.data.text;
if (drawnItemRef.collection) {
ui.notifications.warn("le tirage correspond à une entrée d'un Compendium, on attendait un texte")
return await pack.getDocument(drawnItemRef.resultId);
}
return drawnItemRef.text;
} }
/* -------------------------------------------- */ /* -------------------------------------------- */

View File

@ -149,6 +149,8 @@ export class RdDTMRDialog extends Dialog {
} }
_updateDemiReve() { _updateDemiReve() {
this.notifierResonanceSigneDraconique(this._getActorCoord());
if (!this.cacheTMR) { if (!this.cacheTMR) {
this._setTokenPosition(this.demiReve); this._setTokenPosition(this.demiReve);
} }
@ -237,14 +239,14 @@ export class RdDTMRDialog extends Dialog {
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
updateValuesDisplay() { async updateValuesDisplay() {
Array.from(document.getElementsByClassName("lire-signe-draconique")) const coord = this._getActorCoord();
.forEach(it => HtmlUtility._showControlWhen(it, this.actor.isResonanceSigneDraconique(this._getActorCoord()))); const actorData = Misc.data(this.actor);
HtmlUtility._showControlWhen($(".lire-signe-draconique"), this.actor.isResonanceSigneDraconique(coord));
let ptsreve = document.getElementById("tmr-pointsreve-value"); let ptsreve = document.getElementById("tmr-pointsreve-value");
const actorData = Misc.data(this.actor);
ptsreve.innerHTML = actorData.data.reve.reve.value; ptsreve.innerHTML = actorData.data.reve.reve.value;
const coord = this._getActorCoord();
let tmrpos = document.getElementById("tmr-pos"); let tmrpos = document.getElementById("tmr-pos");
if (this.cacheTMR) { if (this.cacheTMR) {
@ -906,6 +908,7 @@ export class RdDTMRDialog extends Dialog {
Si la case est le demi-rêve, ne pas lancer de sort. Si la case est le demi-rêve, ne pas lancer de sort.
Si un lancement de sort est en cours, trouver un moyen de réafficher cette fenêtre si on essaie de lancer un sort (ou bloquer le lancer de sort) Si un lancement de sort est en cours, trouver un moyen de réafficher cette fenêtre si on essaie de lancer un sort (ou bloquer le lancer de sort)
*/ */
this.notifierResonanceSigneDraconique(targetCoord);
await this.actor.rollUnSort(targetCoord); await this.actor.rollUnSort(targetCoord);
this.nettoyerRencontre(); this.nettoyerRencontre();
} }
@ -952,6 +955,15 @@ export class RdDTMRDialog extends Dialog {
} }
} }
async notifierResonanceSigneDraconique(coord) {
if (this.actor.isResonanceSigneDraconique(coord)) {
ChatMessage.create({
whisper: ChatUtility.getWhisperRecipientsAndGMs(game.user.name),
content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-signe-draconique-resonance.html`, { alias: this.actor.name, typeTMR: TMRUtility.getTMRType(coord) })
});
}
}
/* -------------------------------------------- */ /* -------------------------------------------- */
async postRencontre(tmr) { async postRencontre(tmr) {
if (!(this.viewOnly || this.currentRencontre)) { if (!(this.viewOnly || this.currentRencontre)) {

View File

@ -338,7 +338,7 @@ export class TMRUtility {
static getTMRType(coord) { static getTMRType(coord) {
const tmr = TMRMapping[coord]; const tmr = TMRMapping[coord];
return Misc.upperFirst(tmr.type); return Misc.upperFirst(TMRType[tmr.type].name);
} }
static getTMRDescr(coord) { static getTMRDescr(coord) {

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,4 @@
<h4><img class="chat-icon" src="systems/foundryvtt-reve-de-dragon/icons/tmr/signe_draconique.webp" alt="Signe draconique" />
{{alias}} peut lire un signe draconique
</h4>
<p>Vous venez de trouver une terre de résonance en {{typeTMR}}, vous pouvez le lire un signe draconique.</p>

View File

@ -1,57 +0,0 @@
<form class="skill-roll-dialog">
<div>
<h4>Un signe draconique éphémère se manifeste:
<br><input class="signe-name" type="text" name="signe.name" value="{{signe.name}}" data-dtype="String" />
</h4>
</div>
<div class="form-group">
<label for="actors">Personnages concernés</label>
<select class="select-actor attribute-value" name="actors" id="actors" size="7" multiple>
{{#each actors as |actor key|}}
<option value="{{actor.name}}" data-actor-id="{{actor._id}}" {{#if actor.selected}}selected{{/if}}>
<img class="chat-icon" src="{{actor.img}}" title="{{actor.name}}" alt="{{actor.name}}" />
{{actor.name}}
</option>
{{/each}}
</select>
</div>
<div class="form-group">
<label for="signe.data.difficulte">Difficulte</label>
<input class="attribute-value" type="number" name="signe.data.difficulte" value="{{signe.data.difficulte}}" data-dtype="Number" />
</div>
<div class="form-group">
<label for="signe.data.valeur.norm">Expérience en sorts</label>
<div class="flexrow">
<input class="valeur-xp-sort" type="number" name="signe.data.valeur.norm" data-typereussite="norm"
value="{{signe.data.valeur.norm}}" min="1" max="100" data-dtype="Number" />
<span>Sign.</span>
<input class="valeur-xp-sort" type="number" name="signe.data.valeur.sign" data-typereussite="sign"
value="{{signe.data.valeur.sign}}" min="1" max="100" data-dtype="Number" />
<span>Part.</span>
<input class="valeur-xp-sort" type="number" name="signe.data.valeur.part" data-typereussite="part"
value="{{signe.data.valeur.part}}" min="1" max="100" data-dtype="Number" />
</div>
</div>
<div class="form-group">
<span>
<label for="signe.data.ephemere">Ephémère</label>
<input class="attribute-value signe-data-ephemere" type="checkbox" name="signe.data.ephemere" {{#if signe.data.ephemere}}checked{{/if}} />
</span>
<span>
<input class="signe-data-duree attribute-value" type="text" name="signe.data.duree" value="{{signe.data.duree}}" data-dtype="String" />
</span>
</div>
<div class="form-group">
<label for="tmrs">Terres médianes</label>
<select class="select-tmr attribute-value" name="tmrs" id="tmrs" size="{{tmrs.length}}" multiple>
{{#each tmrs as |tmr key|}}
<option value="{{tmr.name}}" {{#if tmr.selected}}selected{{/if}}>{{tmr.name}}</option>
{{/each}}
</select>
</div>
</form>

View File

@ -0,0 +1,52 @@
<form class="skill-roll-dialog">
<div>
<h4>Paramétrer le signe draconique
<span class="chat-card-button-area">
<a class="signe-aleatoire chat-card-button">Signe aléatoire</a>
</span>
<br><input type="text" name="signe.name" value="{{signe.name}}" data-dtype="String" />
</h4>
</div>
<div class="form-group">
<label for="signe.data.difficulte">Difficulte</label>
<input type="number" name="signe.data.difficulte" value="{{signe.data.difficulte}}" data-dtype="Number" />
</div>
<div class="form-group">
<label for="signe.data.valeur.norm">Expérience en sorts</label>
<div class="flexrow">
<input class="signe-xp-sort" type="number" name="signe.data.valeur.norm" data-typereussite="norm"
value="{{signe.data.valeur.norm}}" min="1" max="100" data-dtype="Number" />
<span>Sign.</span>
<input class="signe-xp-sort" type="number" name="signe.data.valeur.sign" data-typereussite="sign"
value="{{signe.data.valeur.sign}}" min="1" max="100" data-dtype="Number" />
<span>Part.</span>
<input class="signe-xp-sort" type="number" name="signe.data.valeur.part" data-typereussite="part"
value="{{signe.data.valeur.part}}" min="1" max="100" data-dtype="Number" />
</div>
</div>
<div class="form-group flexrow">
<label for="signe.data.ephemere">Ephémère</label>
<input class="flex-shrink" type="checkbox" name="signe.data.ephemere" {{#if signe.data.ephemere}}checked{{/if}} />
<span>
<input type="text" name="signe.data.duree" value="{{signe.data.duree}}" data-dtype="String" />
</span>
</div>
<div class="form-group">
<label for="tmrs">Terres médianes</label>
<select class="select-tmr" name="tmrs" id="tmrs" size="{{tmrs.length}}" multiple>
{{#each tmrs as |tmr key|}}
<option value="{{tmr.name}}" {{#if tmr.selected}}selected{{/if}}>{{tmr.name}}</option>
{{/each}}
</select>
</div>
<div class="form-group">
<label for="actors">Haut-rêvants concernés</label>
<select class="select-actor" id="actors" size="7" multiple>
{{#each actors as |actor key|}}
<option value="{{actor.name}}" data-actor-id="{{actor._id}}" {{#if actor.selected}}selected{{/if}}>{{actor.name}}</option>
{{/each}}
</select>
</div>
</form>

View File

@ -3,9 +3,10 @@
<img class="profile-img" src="{{img}}" data-edit="img" title="{{name}}" /> <img class="profile-img" src="{{img}}" data-edit="img" title="{{name}}" />
<div class="header-fields"> <div class="header-fields">
<h1 class="charname"><input name="name" type="text" value="{{name}}" placeholder="Name" /></h1> <h1 class="charname"><input name="name" type="text" value="{{name}}" placeholder="Name" /></h1>
<a class="signe-aleatoire chat-card-button">Signe aléatoire</a>
</div> </div>
</header> </header>
{{!-- Sheet Body --}} {{!-- Sheet Body --}}
<section class="sheet-body"> <section class="sheet-body">
<div class="form-group"> <div class="form-group">
@ -16,13 +17,13 @@
<div class="form-group"> <div class="form-group">
<label for="data.valeur.norm">Expérience en sorts</label> <label for="data.valeur.norm">Expérience en sorts</label>
<div class="flexrow"> <div class="flexrow">
<input class="valeur-xp-sort" type="number" name="data.valeur.norm" data-typereussite="norm" <input class="signe-xp-sort" type="number" name="data.valeur.norm" data-typereussite="norm"
value="{{data.valeur.norm}}" min="1" max="100" data-dtype="Number" /> value="{{data.valeur.norm}}" min="1" max="100" data-dtype="Number" />
<span>Sign.</span> <span>Sign.</span>
<input class="valeur-xp-sort" type="number" name="data.valeur.sign" data-typereussite="sign" <input class="signe-xp-sort" type="number" name="data.valeur.sign" data-typereussite="sign"
value="{{data.valeur.sign}}" min="1" max="100" data-dtype="Number" /> value="{{data.valeur.sign}}" min="1" max="100" data-dtype="Number" />
<span>Part.</span> <span>Part.</span>
<input class="valeur-xp-sort" type="number" name="data.valeur.part" data-typereussite="part" <input class="signe-xp-sort" type="number" name="data.valeur.part" data-typereussite="part"
value="{{data.valeur.part}}" min="1" max="100" data-dtype="Number" /> value="{{data.valeur.part}}" min="1" max="100" data-dtype="Number" />
</div> </div>
</div> </div>