Compare commits
	
		
			1 Commits
		
	
	
		
			13.0.14
			...
			5d8da860c4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 5d8da860c4 | 
| @@ -12,8 +12,8 @@ Pseudo : LeRatierBretonnien | |||||||
|  |  | ||||||
| Mainteneur/Développeur : LeRatierBretonnien | Mainteneur/Développeur : LeRatierBretonnien | ||||||
| Développeur : VincentVk | Développeur : VincentVk | ||||||
| Tests, Compendiums, Données: Fred, Fab, Grendel | Tests, Compendiums, Données: Fred, Fab, Grendel, LeRatierBretonnien, VincentVk | ||||||
| Styles/CSS : Mandar | Styles/CSS : Mandar, VincentVk | ||||||
|  |  | ||||||
| # Mentions Légales | # Mentions Légales | ||||||
|  |  | ||||||
| @@ -23,6 +23,6 @@ La carte des Terres Médianes du Rêve est une illustration de **Jidus**, utilis | |||||||
| Les silhouettes des créatures, humanoïdes et entités sont des illustrations de **Roland Barthélémy**, et sont utilisés dans le cadre de ce projet avec son aimable autorisation. | Les silhouettes des créatures, humanoïdes et entités sont des illustrations de **Roland Barthélémy**, et sont utilisés dans le cadre de ce projet avec son aimable autorisation. | ||||||
| Merci à eux !! | Merci à eux !! | ||||||
|  |  | ||||||
| Toute la propriété intellectuelle leur appartient, ce système est une adpatation destinée à fonctionner sous FoundryVTT. | Toute la propriété intellectuelle leur appartient, ce système est une adaptation destinée à fonctionner sous FoundryVTT. | ||||||
|  |  | ||||||
| L'ensemble du code est sous licence Creative Commons. | L'ensemble du code est sous licence Creative Commons. | ||||||
| @@ -3,6 +3,11 @@ | |||||||
| ## 13.0.9 - Le combat d'Illysis | ## 13.0.9 - Le combat d'Illysis | ||||||
| - Fix | - Fix | ||||||
|   - La montée en TMR fonctionne |   - La montée en TMR fonctionne | ||||||
|  | - Nouvelle fenêtre de jets de dés | ||||||
|  |   - avancement du mode attaque | ||||||
|  |     - choix de tactique | ||||||
|  |     - choix des dommages, affichage | ||||||
|  |     - affichage de la surprise du défenseur | ||||||
|  |  | ||||||
| ## 13.0.8 - Le renouveau d'Illysis | ## 13.0.8 - Le renouveau d'Illysis | ||||||
|  |  | ||||||
|   | |||||||
| @@ -526,6 +526,7 @@ select, | |||||||
|   grid-area: selection; |   grid-area: selection; | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: row; |   flex-direction: row; | ||||||
|  |   margin: 0.1rem 0; | ||||||
| } | } | ||||||
| .system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-img { | .system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-img { | ||||||
|   display: flex; |   display: flex; | ||||||
| @@ -549,6 +550,7 @@ select, | |||||||
| .system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-detail subline { | .system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-detail subline { | ||||||
|   display: flex; |   display: flex; | ||||||
|   flex-direction: row; |   flex-direction: row; | ||||||
|  |   margin: 0.1rem 0; | ||||||
| } | } | ||||||
| .system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-detail subline div.poesie-extrait { | .system-foundryvtt-reve-de-dragon .roll-dialog roll-choix roll-section roll-part-detail subline div.poesie-extrait { | ||||||
|   display: flex; |   display: flex; | ||||||
| @@ -608,6 +610,11 @@ select, | |||||||
|   margin: 0 0.2rem; |   margin: 0 0.2rem; | ||||||
|   padding: 0; |   padding: 0; | ||||||
| } | } | ||||||
|  | .system-foundryvtt-reve-de-dragon .roll-dialog :is(roll-choix, roll-conditions, roll-carac, roll-comp) img.button-effect-img { | ||||||
|  |   max-width: 1rem; | ||||||
|  |   max-height: 1rem; | ||||||
|  |   margin: 0 0.1rem; | ||||||
|  | } | ||||||
| .system-foundryvtt-reve-de-dragon .roll-dialog roll-carac select[name="select-carac"] { | .system-foundryvtt-reve-de-dragon .roll-dialog roll-carac select[name="select-carac"] { | ||||||
|   max-width: 6rem; |   max-width: 6rem; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -83,11 +83,14 @@ | |||||||
|     gap: 0.2rem; |     gap: 0.2rem; | ||||||
|     align-items: start; |     align-items: start; | ||||||
|  |  | ||||||
|  |      | ||||||
|     subline { |     subline { | ||||||
|       grid-area: selection; |       grid-area: selection; | ||||||
|       display: flex; |       display: flex; | ||||||
|       flex-direction: row; |       flex-direction: row; | ||||||
|  |       margin: 0.1rem 0; | ||||||
|     } |     } | ||||||
|  |    | ||||||
|     roll-part-img { |     roll-part-img { | ||||||
|       display: flex; |       display: flex; | ||||||
|       flex-direction: column; |       flex-direction: column; | ||||||
| @@ -110,6 +113,7 @@ | |||||||
|       subline { |       subline { | ||||||
|         display: flex; |         display: flex; | ||||||
|         flex-direction: row; |         flex-direction: row; | ||||||
|  |         margin: 0.1rem 0; | ||||||
|         div.poesie-extrait{ |         div.poesie-extrait{ | ||||||
|           display: flex; |           display: flex; | ||||||
|           flex-direction: column; |           flex-direction: column; | ||||||
| @@ -178,6 +182,11 @@ | |||||||
|           margin: 0 0.2rem; |           margin: 0 0.2rem; | ||||||
|           padding: 0; |           padding: 0; | ||||||
|     } |     } | ||||||
|  |     img.button-effect-img { | ||||||
|  |           max-width: 1rem; | ||||||
|  |           max-height: 1rem; | ||||||
|  |           margin: 0 0.1rem; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   roll-carac select[name="select-carac"] { |   roll-carac select[name="select-carac"] { | ||||||
|   | |||||||
| @@ -49,7 +49,7 @@ export class RdDActorSheet extends RdDBaseActorSangSheet { | |||||||
|     }); |     }); | ||||||
|     foundry.utils.mergeObject(formData.calc, { |     foundry.utils.mergeObject(formData.calc, { | ||||||
|       surenc: this.actor.computeMalusSurEncombrement(), |       surenc: this.actor.computeMalusSurEncombrement(), | ||||||
|       surprise: RdDBonus.find(this.actor.getSurprise(false)).descr, |       surprise: RdDBonus.find(this.actor.getSurprise(false)).label, | ||||||
|       resumeBlessures: this.actor.computeResumeBlessure(this.actor.system.blessures), |       resumeBlessures: this.actor.computeResumeBlessure(this.actor.system.blessures), | ||||||
|       caracTotal: RdDCarac.computeTotal(this.actor.system.carac, this.actor.system.beaute), |       caracTotal: RdDCarac.computeTotal(this.actor.system.carac, this.actor.system.beaute), | ||||||
|       surEncombrementMessage: this.actor.isSurenc() ? "Sur-Encombrement!" : "", |       surEncombrementMessage: this.actor.isSurenc() ? "Sur-Encombrement!" : "", | ||||||
|   | |||||||
| @@ -208,7 +208,7 @@ export class RdDActor extends RdDBaseActorSang { | |||||||
|       ui.notifications.info(`Les dommages de l'arme à 1/2 mains ${arme.name} ne sont pas corrects (ie sous la forme X/Y)`) |       ui.notifications.info(`Les dommages de l'arme à 1/2 mains ${arme.name} ne sont pas corrects (ie sous la forme X/Y)`) | ||||||
|     } |     } | ||||||
|     const tableauDommages = dommages.includes("/") ? dommages.split("/") : [dommages, dommages] |     const tableauDommages = dommages.includes("/") ? dommages.split("/") : [dommages, dommages] | ||||||
|     const dmg = main == '(2 mains)' ? tableauDommages[1] : tableauDommages[0] |     const dommagesArme = parseInt(main == '(2 mains)' ? tableauDommages[1] : tableauDommages[0]) | ||||||
|     const niveau = comp?.system.niveau ?? (['(lancer)', '(tir)'].includes(main) ? -8 : -6) |     const niveau = comp?.system.niveau ?? (['(lancer)', '(tir)'].includes(main) ? -8 : -6) | ||||||
|     const ajustement = (arme.parent?.getEtatGeneral() ?? 0) + (arme.system.magique) ? arme.system.ecaille_efficacite : 0 |     const ajustement = (arme.parent?.getEtatGeneral() ?? 0) + (arme.system.magique) ? arme.system.ecaille_efficacite : 0 | ||||||
|  |  | ||||||
| @@ -221,7 +221,7 @@ export class RdDActor extends RdDBaseActorSang { | |||||||
|       main: main, |       main: main, | ||||||
|       carac: { key: caracCode, value: caracValue }, |       carac: { key: caracCode, value: caracValue }, | ||||||
|       equipe: arme.system.equipe, |       equipe: arme.system.equipe, | ||||||
|       dmg: dmg, |       dommagesArme: dommagesArme, | ||||||
|       initiative: RdDInitiative.calculInitiative(niveau, caracValue, ajustement) |       initiative: RdDInitiative.calculInitiative(niveau, caracValue, ajustement) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -54,34 +54,37 @@ export class RdDItemCompetenceCreature extends Item { | |||||||
|     const categorieAttaque = RdDItemCompetenceCreature.getCategorieAttaque(comp) |     const categorieAttaque = RdDItemCompetenceCreature.getCategorieAttaque(comp) | ||||||
|     if (categorieAttaque != undefined) { |     if (categorieAttaque != undefined) { | ||||||
|       const initative = RdDInitiative.calculInitiative(comp.system.niveau, comp.system.carac_value); |       const initative = RdDInitiative.calculInitiative(comp.system.niveau, comp.system.carac_value); | ||||||
|       return { |       const armeComp = new RdDItem({ | ||||||
|  |         name: comp.name, | ||||||
|  |         type: ITEM_TYPES.arme, | ||||||
|  |         img: comp.img, | ||||||
|  |         system: { | ||||||
|  |           competence: comp.name, | ||||||
|  |           cac: categorieAttaque == "naturelle" ? "naturelle" : "", | ||||||
|  |           niveau: comp.system.niveau, | ||||||
|  |           initiative: initative, | ||||||
|  |           mortalite: comp.system.mortalite, | ||||||
|  |           dommages: comp.system.dommages, | ||||||
|  |           equipe: true, | ||||||
|  |           resistance: 100, | ||||||
|  |           penetration: 0, | ||||||
|  |           force: 0, | ||||||
|  |           rapide: true, | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       const attaque = { | ||||||
|         name: comp.name, |         name: comp.name, | ||||||
|         action: comp.isCompetencePossession() ? 'possession' : 'attaque', |         action: comp.isCompetencePossession() ? 'possession' : 'attaque', | ||||||
|         initOnly: false, |         initOnly: false, | ||||||
|         arme: new RdDItem({ |         arme: armeComp, | ||||||
|           name: comp.name, |  | ||||||
|           type: ITEM_TYPES.arme, |  | ||||||
|           img: comp.img, |  | ||||||
|           system: { |  | ||||||
|             competence: comp.name, |  | ||||||
|             cac: categorieAttaque == "naturelle" ? "naturelle" : "", |  | ||||||
|             niveau: comp.system.niveau, |  | ||||||
|             initiative: initative, |  | ||||||
|             equipe: true, |  | ||||||
|             resistance: 100, |  | ||||||
|             dommagesReels: comp.system.dommages, |  | ||||||
|             penetration: 0, |  | ||||||
|             force: 0, |  | ||||||
|             rapide: true, |  | ||||||
|           } |  | ||||||
|         }), |  | ||||||
|         comp: comp, |         comp: comp, | ||||||
|         // main: '', |  | ||||||
|         carac: { key: comp.name, value: comp.system.carac_value }, |         carac: { key: comp.name, value: comp.system.carac_value }, | ||||||
|         equipe: true, |         equipe: true, | ||||||
|  |         mortalite: comp.system.mortalite, | ||||||
|         dmg: comp.system.dommages, |         dmg: comp.system.dommages, | ||||||
|         initiative: initative |         initiative: initative | ||||||
|       } |       }; | ||||||
|  |       return attaque | ||||||
|     } |     } | ||||||
|     return undefined; |     return undefined; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -30,6 +30,10 @@ export class RdDItemArme extends RdDItem { | |||||||
|     //return "systems/foundryvtt-reve-de-dragon/icons/armes_armure/epee_sord.webp"; |     //return "systems/foundryvtt-reve-de-dragon/icons/armes_armure/epee_sord.webp"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   penetration() { | ||||||
|  |     return parseInt(this.system.penetration ?? 0); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   /* -------------------------------------------- */ |   /* -------------------------------------------- */ | ||||||
|  |  | ||||||
|   static getArme(arme) { |   static getArme(arme) { | ||||||
|   | |||||||
| @@ -1,21 +1,23 @@ | |||||||
| import { RdDCarac } from "./rdd-carac.js"; |  | ||||||
| import { RdDPossession } from "./rdd-possession.js"; | import { RdDPossession } from "./rdd-possession.js"; | ||||||
|  |  | ||||||
| const conditionsTactiques = [ | const conditionsTactiques = [ | ||||||
|   { type: '', descr: '', dmg: 0, attaque: 0, parade: 0, esquive: true }, |   { key: '', label: '', dmg: 0, attaque: 0, parade: 0, esquive: true, isTactique: false }, | ||||||
|   { type: 'charge', descr: 'Charge', dmg: 2, attaque: 4, parade: -4, esquive: false }, |   { key: 'normale', label: 'Attaque normale', dmg: 0, attaque: 0, parade: 0, esquive: true, isTactique: true }, | ||||||
|   { type: 'feinte', descr: 'Feinte', dmg: 1, attaque: 1, parade: 0, esquive: true }, |   { key: 'charge', label: 'Charge', dmg: 2, attaque: 4, parade: -4, esquive: false, isTactique: true }, | ||||||
|   { type: 'pret', descr: 'prêt', dmg: 0, attaque: 0, parade: 0, esquive: true }, |   { key: 'feinte', label: 'Feinte', dmg: 1, attaque: 1, parade: 0, esquive: true, isTactique: true }, | ||||||
|   { type: 'demi', descr: 'Demi-surprise', dmg: 1, attaque: 0, parade: 0, esquive: true }, |   { key: 'pret', label: 'prêt', dmg: 0, attaque: 0, parade: 0, esquive: true, isTactique: false }, | ||||||
|   { type: 'totale', descr: 'Surprise totale', dmg: 10, attaque: 6, parade: 0, esquive: true }, |   { key: 'demi', label: 'Demi-surprise', dmg: 1, attaque: 0, parade: 0, esquive: true, isTactique: false }, | ||||||
|  |   { key: 'totale', label: 'Surprise totale', dmg: 10, attaque: 6, parade: 0, esquive: true, isTactique: false }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| /* -------------------------------------------- */ | /* -------------------------------------------- */ | ||||||
| export class RdDBonus { | export class RdDBonus { | ||||||
|  |   static get tactiques(){ | ||||||
|  |     return conditionsTactiques.filter(it => it.isTactique) | ||||||
|  |   } | ||||||
|   /* -------------------------------------------- */ |   /* -------------------------------------------- */ | ||||||
|   static find(condition) { |   static find(condition) { | ||||||
|     return conditionsTactiques.find(e => e.type == condition) || conditionsTactiques.find(e => e.type == 'pret'); |     return conditionsTactiques.find(e => e.key == condition) || conditionsTactiques[0]; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -32,7 +34,7 @@ export class RdDBonus { | |||||||
|  |  | ||||||
|   /* -------------------------------------------- */ |   /* -------------------------------------------- */ | ||||||
|   static dmg(rollData, actor, isEntiteIncarnee = false) { |   static dmg(rollData, actor, isEntiteIncarnee = false) { | ||||||
|     const dmgArme = RdDBonus.dmgArme(rollData.arme) |     const dmgArme = RdDBonus.dmgArme(rollData.arme, rollData.arme?.system.dommagesReels) | ||||||
|     let dmg = { |     let dmg = { | ||||||
|       total: 0, |       total: 0, | ||||||
|       dmgArme: dmgArme, |       dmgArme: dmgArme, | ||||||
| @@ -49,31 +51,35 @@ export class RdDBonus { | |||||||
|  |  | ||||||
|   /* -------------------------------------------- */ |   /* -------------------------------------------- */ | ||||||
|   static description(condition) { |   static description(condition) { | ||||||
|     return RdDBonus.find(condition).descr; |     return RdDBonus.find(condition).label | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /* -------------------------------------------- */ |   /* -------------------------------------------- */ | ||||||
|   static dmgBonus(condition) { |   static dmgBonus(condition) { | ||||||
|     return RdDBonus.find(condition).dmg; |     return RdDBonus.find(condition).dmg | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /* -------------------------------------------- */ |   /* -------------------------------------------- */ | ||||||
|   static bonusAttaque(condition) { |   static bonusAttaque(condition) { | ||||||
|     return RdDBonus.find(condition).attaque; |     return RdDBonus.find(condition).attaque | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /* -------------------------------------------- */ |   /* -------------------------------------------- */ | ||||||
|   static _calculMortalite(rollData, isEntiteIncarnee) { |   static _calculMortalite(rollData, isEntiteIncarnee) { | ||||||
|  |     return RdDBonus.mortalite(rollData.dmg?.mortalite, rollData.arme?.system.mortalite, isEntiteIncarnee) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static mortalite(mortaliteSelect, mortaliteArme, isEntiteIncarnee) { | ||||||
|     return isEntiteIncarnee ? "entiteincarnee" |     return isEntiteIncarnee ? "entiteincarnee" | ||||||
|       : rollData.dmg?.mortalite |       : mortaliteSelect | ||||||
|       ?? rollData.arme?.system.mortalite |       ?? mortaliteArme | ||||||
|       ?? "mortel"; |       ?? "mortel"; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /* -------------------------------------------- */ |   /* -------------------------------------------- */ | ||||||
|   static dmgArme(arme) { |   static dmgArme(arme, dommagesMain) { | ||||||
|     if (arme) { |     if (arme) { | ||||||
|       let dmgBase = arme.system.dommagesReels ?? Number(arme.system.dommages ?? 0); |       let dmgBase = dommagesMain ?? Number(arme.system.dommages ?? 0); | ||||||
|       //Le bonus dégats magiques ne peut pas faire dépasser le bonus de l'arme (cf p.278) |       //Le bonus dégats magiques ne peut pas faire dépasser le bonus de l'arme (cf p.278) | ||||||
|       return dmgBase + Math.min(dmgBase, arme.system.magique ? arme.system.ecaille_efficacite : 0); |       return dmgBase + Math.min(dmgBase, arme.system.magique ? arme.system.ecaille_efficacite : 0); | ||||||
|     } |     } | ||||||
| @@ -92,8 +98,8 @@ export class RdDBonus { | |||||||
|       return 0 |       return 0 | ||||||
|     } |     } | ||||||
|     switch (categorie) { |     switch (categorie) { | ||||||
|       case "tir": return 0; |       case "(tir)": case "tir": return 0 | ||||||
|       case "lancer": return Math.max(0, Math.min(dmgArme, dmgActor)); |       case "(lancer)": case "lancer": return Math.max(0, Math.min(dmgArme, dmgActor)); | ||||||
|     } |     } | ||||||
|     return dmgActor; |     return dmgActor; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -187,7 +187,12 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2 | |||||||
|     foundry.applications.handlebars.loadTemplates(ROLL_PARTS.map(p => p.template)) |     foundry.applications.handlebars.loadTemplates(ROLL_PARTS.map(p => p.template)) | ||||||
|     ROLL_PARTS.forEach(p => p.onReady()) |     ROLL_PARTS.forEach(p => p.onReady()) | ||||||
|  |  | ||||||
|     Handlebars.registerHelper('roll-centered-array', (base, show) => RollDialog.centeredArray(base, show)) |     Handlebars.registerHelper('roll-centered-array', (base, show) => { | ||||||
|  |       show = Math.abs(show) | ||||||
|  |       const start = base - show | ||||||
|  |       return [...Array(2 * show + 1).keys()].map(it => start + it) | ||||||
|  |     }) | ||||||
|  |  | ||||||
|     Handlebars.registerHelper('roll-list-item-value', (list, key, path = undefined) => { |     Handlebars.registerHelper('roll-list-item-value', (list, key, path = undefined) => { | ||||||
|       const selected = list.find(p => p.key == key) |       const selected = list.find(p => p.key == key) | ||||||
|       if (selected && path && path != '') { |       if (selected && path && path != '') { | ||||||
| @@ -195,6 +200,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2 | |||||||
|       } |       } | ||||||
|       return selected |       return selected | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     Handlebars.registerHelper('roll-part-context', (rollData, code) => { |     Handlebars.registerHelper('roll-part-context', (rollData, code) => { | ||||||
|       const rollPart = ROLL_PARTS.find(it => it.code == code) |       const rollPart = ROLL_PARTS.find(it => it.code == code) | ||||||
|       if (rollPart == undefined) { |       if (rollPart == undefined) { | ||||||
| @@ -211,12 +217,7 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2 | |||||||
|     }) |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   static centeredArray(base, show) { |    | ||||||
|     show = Math.abs(show) |  | ||||||
|     const start = base - show |  | ||||||
|     return [...Array(2 * show + 1).keys()].map(it => start + it) |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static async create(rollData, rollOptions = {}) { |   static async create(rollData, rollOptions = {}) { | ||||||
|     const rollDialog = new RollDialog(rollData, rollOptions) |     const rollDialog = new RollDialog(rollData, rollOptions) | ||||||
|     rollDialog.render(true) |     rollDialog.render(true) | ||||||
| @@ -267,30 +268,33 @@ export default class RollDialog extends HandlebarsApplicationMixin(ApplicationV2 | |||||||
|   /** pre-configure les paramètres des différentes parties de la fenêtre (par exemple, prépare les listes de caractéristiques/compétences */ |   /** pre-configure les paramètres des différentes parties de la fenêtre (par exemple, prépare les listes de caractéristiques/compétences */ | ||||||
|   $loadParts() { |   $loadParts() { | ||||||
|     const rollData = this.rollData; |     const rollData = this.rollData; | ||||||
|     const loadedMode = rollData.mode?.current |  | ||||||
|     rollData.current = rollData.current ?? {} |     rollData.current = rollData.current ?? {} | ||||||
|     rollData.selected = rollData.selected ?? {} |     rollData.selected = rollData.selected ?? {} | ||||||
|     rollData.mode = rollData.mode ?? {} |     rollData.mode = rollData.mode ?? {} | ||||||
|     rollData.mode.retry = rollData.mode.retry ?? false |     rollData.mode.retry = rollData.mode.retry ?? false | ||||||
|     BASIC_PARTS.restore(rollData) |     BASIC_PARTS.restore(rollData) | ||||||
|  |  | ||||||
|  |     const loadedMode = ROLL_MODE_TABS.find(m => m.code == rollData.mode?.current)?.code | ||||||
|  |     const allowedModes = ROLL_MODE_TABS.filter(m => m.isAllowed(rollData) && m.visible(rollData)).map(m => m.code) | ||||||
|  |  | ||||||
|     rollData.mode.allowed = rollData.mode.retry ? [loadedMode] : rollData.mode.allowed ?? ROLL_MODE_TABS.map(m => m.code) |     rollData.mode.allowed = rollData.mode.retry ? [loadedMode] : rollData.mode.allowed ?? ROLL_MODE_TABS.map(m => m.code) | ||||||
|     rollData.mode.current = loadedMode ?? ROLL_MODE_TABS.find(m => m.isAllowed(rollData) && m.visible(rollData))?.code ?? ROLL_MODE_COMP |     rollData.mode.current = allowedModes.find(m => m == rollData.mode?.current) ?? (allowedModes.length > 0 ? allowedModes[0] : ROLL_MODE_COMP) | ||||||
|  |  | ||||||
|     this.getSelectedMode().setRollDataMode(rollData) |     this.getSelectedMode().setRollDataMode(rollData) | ||||||
|      |  | ||||||
|     rollData.refs = this.$prepareRefs(rollData) |     rollData.refs = this.$prepareRefs(rollData) | ||||||
|     rollData.options = rollData.options ?? { showDice: true, rollMode: game.settings.get("core", "rollMode") } |     rollData.options = rollData.options ?? { showDice: true, rollMode: game.settings.get("core", "rollMode") } | ||||||
|      |  | ||||||
|     ROLL_PARTS.forEach(p => p.initialize(rollData)) |     ROLL_PARTS.forEach(p => p.initialize(rollData)) | ||||||
|     ROLL_PARTS.forEach(p => p.restore(rollData)) |     ROLL_PARTS.forEach(p => p.restore(rollData)) | ||||||
|     ROLL_PARTS.filter(p => p.isValid(rollData)) |     ROLL_PARTS.filter(p => p.isValid(rollData)) | ||||||
|     .forEach(p => { |       .forEach(p => { | ||||||
|       p.loadRefs(rollData) |         p.loadRefs(rollData) | ||||||
|       p.prepareContext(rollData) |         p.prepareContext(rollData) | ||||||
|     }) |       }) | ||||||
|     this.selectMode(); |     this.selectMode(); | ||||||
|   } |   } | ||||||
|    |  | ||||||
|   selectMode() { |   selectMode() { | ||||||
|     this.rollData.mode.label = this.getSelectedMode().title(this.rollData) |     this.rollData.mode.label = this.getSelectedMode().title(this.rollData) | ||||||
|     this.getSelectedMode().setRollDataMode(this.rollData) |     this.getSelectedMode().setRollDataMode(this.rollData) | ||||||
|   | |||||||
| @@ -37,6 +37,7 @@ export class RollPartAppelMoral extends RollPartCheckbox { | |||||||
|     } |     } | ||||||
|     return '<i class="fa-regular fa-face-meh"></i>' |     return '<i class="fa-regular fa-face-meh"></i>' | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getCheckboxLabel(rollData) { return "Appel au moral" } |   getCheckboxLabel(rollData) { return "Appel au moral" } | ||||||
|   getCheckboxValue(rollData) { return 1 } |   getCheckboxValue(rollData) { return 1 } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,12 +1,16 @@ | |||||||
| import { Grammar } from "../grammar.js" | import { Grammar } from "../grammar.js" | ||||||
|  | import { RdDBonus } from "../rdd-bonus.js" | ||||||
|  | import { StatusEffects } from "../settings/status-effects.js" | ||||||
| import { ROLL_MODE_ATTAQUE } from "./roll-constants.mjs" | import { ROLL_MODE_ATTAQUE } from "./roll-constants.mjs" | ||||||
| import { PART_CARAC } from "./roll-part-carac.mjs" | import { PART_CARAC } from "./roll-part-carac.mjs" | ||||||
| import { PART_COMP } from "./roll-part-comp.mjs" | import { PART_COMP } from "./roll-part-comp.mjs" | ||||||
| import { RollPartSelect } from "./roll-part-select.mjs" | import { RollPartSelect } from "./roll-part-select.mjs" | ||||||
| import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" | import { ROLLDIALOG_SECTION } from "./roll-part.mjs" | ||||||
|  |  | ||||||
| export const PART_ATTAQUE = 'attaque' | export const PART_ATTAQUE = 'attaque' | ||||||
|  |  | ||||||
|  | const TACTIQUES = RdDBonus.tactiques.filter(it => it.isTactique) | ||||||
|  |  | ||||||
| export class RollPartAttaque extends RollPartSelect { | export class RollPartAttaque extends RollPartSelect { | ||||||
|  |  | ||||||
|   get code() { return PART_ATTAQUE } |   get code() { return PART_ATTAQUE } | ||||||
| @@ -18,7 +22,8 @@ export class RollPartAttaque extends RollPartSelect { | |||||||
|     const refs = this.getRefs(rollData) |     const refs = this.getRefs(rollData) | ||||||
|     const attaques = rollData.active.actor.listAttaques() |     const attaques = rollData.active.actor.listAttaques() | ||||||
|     refs.attaques = attaques.map(it => RollPartAttaque.$extractAttaque(it, rollData.active.actor)) |     refs.attaques = attaques.map(it => RollPartAttaque.$extractAttaque(it, rollData.active.actor)) | ||||||
|     if (refs.attaques.length>0){ |     refs.tactiques = TACTIQUES | ||||||
|  |     if (refs.attaques.length > 0) { | ||||||
|       this.$selectAttaque(rollData) |       this.$selectAttaque(rollData) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -27,20 +32,69 @@ export class RollPartAttaque extends RollPartSelect { | |||||||
|  |  | ||||||
|   static $extractAttaque(action, actor) { |   static $extractAttaque(action, actor) { | ||||||
|     return { |     return { | ||||||
|       key: `${action.action}::${action.arme.id}::${action.comp.id}`, |       key: `${action.action}::${action.name}`, | ||||||
|       label: action.name, |       label: action.name, | ||||||
|       action: action, |       action: action, | ||||||
|  |       tactique: TACTIQUES[0], | ||||||
|       arme: action.arme, |       arme: action.arme, | ||||||
|       comp: action.comp, |       comp: action.comp, | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   prepareContext(rollData) { | ||||||
|  |     const current = this.getCurrent(rollData) | ||||||
|  |     const effetsSurprise = rollData.opponent?.actor?.getEffects(it => StatusEffects.niveauSurprise(it, true) > 0) ?? [] | ||||||
|  |     current.defenseur = effetsSurprise | ||||||
|  |       ? { | ||||||
|  |         surprise: RdDBonus.find(rollData.opponent?.actor?.getSurprise(true)), | ||||||
|  |         effects: effetsSurprise | ||||||
|  |       } | ||||||
|  |       : undefined | ||||||
|  |     current.dmg = this.dmgRollV2(rollData, current) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   getAjustements(rollData) { | ||||||
|  |     const current = this.getCurrent(rollData) | ||||||
|  |     const tactique = current.tactique | ||||||
|  |     const surprise = current.defenseur?.surprise | ||||||
|  |     const ajustements = [] | ||||||
|  |     if (tactique) { | ||||||
|  |       ajustements.push({ label: tactique.label, diff: tactique.attaque }) | ||||||
|  |     } | ||||||
|  |     if (surprise) { | ||||||
|  |       ajustements.push({ label: surprise.label, diff: surprise.attaque }) | ||||||
|  |     } | ||||||
|  |     return ajustements | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   dmgRollV2(rollData, current) { | ||||||
|  |     const actor = rollData.active.actor | ||||||
|  |     const defender = rollData.opponent.actor | ||||||
|  |     const dmgArme = RdDBonus.dmgArme(current.arme, current.action.dommagesArme) | ||||||
|  |     const dmg = { | ||||||
|  |       total: 0, | ||||||
|  |       dmgArme: dmgArme, | ||||||
|  |       penetration: current.arme.penetration(), | ||||||
|  |       dmgTactique: current.tactique?.dmg ?? 0, | ||||||
|  |       dmgParticuliere: 0, // TODO RdDBonus._dmgParticuliere(rollData), | ||||||
|  |       dmgSurprise: current.defenseur?.surprise.dmg, | ||||||
|  |       mortalite: RdDBonus.mortalite(current.dmg?.mortalite, current.arme.system.mortalite, defender?.isEntite()), | ||||||
|  |       dmgActor: RdDBonus.bonusDmg(actor, current.action.carac.key, dmgArme) | ||||||
|  |     } | ||||||
|  |     dmg.total = dmg.dmgSurprise + dmg.dmgTactique + dmg.dmgArme + dmg.dmgActor + dmg.dmgParticuliere | ||||||
|  |     return dmg; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   $selectAttaque(rollData, key) { |   $selectAttaque(rollData, key) { | ||||||
|     this.selectByKey(rollData, key, 0) |     this.selectByKey(rollData, key, 0) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   async _onRender(rollDialog, context, options) { |   async _onRender(rollDialog, context, options) { | ||||||
|     const selectAttaque = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-attaque"]`) |     const selectAttaque = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-attaque"]`) | ||||||
|  |     const selectTactique = rollDialog.element.querySelector(`roll-section[name="${this.code}"] select[name="select-tactique"]`) | ||||||
|  |     const checkMortalite = rollDialog.element.querySelector(`roll-section[name="${this.code}"] input[name="check-mortalite"]`) | ||||||
|  |     const current = this.getCurrent(rollDialog.rollData) | ||||||
|  |  | ||||||
|     selectAttaque.addEventListener("change", e => { |     selectAttaque.addEventListener("change", e => { | ||||||
|       const selectOptions = e.currentTarget.options |       const selectOptions = e.currentTarget.options | ||||||
| @@ -49,6 +103,18 @@ export class RollPartAttaque extends RollPartSelect { | |||||||
|       rollDialog.setModeTitle() |       rollDialog.setModeTitle() | ||||||
|       rollDialog.render() |       rollDialog.render() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|  |     selectTactique.addEventListener("change", e => { | ||||||
|  |       const selectOptions = e.currentTarget.options | ||||||
|  |       const index = selectOptions.selectedIndex | ||||||
|  |       current.tactique = RdDBonus.find(selectOptions[index]?.value) | ||||||
|  |       rollDialog.render() | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     checkMortalite?.addEventListener("change", e => { | ||||||
|  |       current.dmg.mortalite = (e.currentTarget.checked ? 'mortel' : 'non-mortel') | ||||||
|  |       rollDialog.render() | ||||||
|  |     }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   getExternalPartsFilter(partCode, rollData) { |   getExternalPartsFilter(partCode, rollData) { | ||||||
|   | |||||||
| @@ -18,8 +18,8 @@ export class RollPartCheckbox extends RollPart { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   loadRefs(rollData) { |   loadRefs(rollData) { | ||||||
|     const current = this.getCurrent(rollData) |     const refs = this.getRefs(rollData) | ||||||
|     current.label = this.getCheckboxLabel(rollData) |     refs.label = this.getCheckboxLabel(rollData) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   prepareContext(rollData) { |   prepareContext(rollData) { | ||||||
| @@ -43,7 +43,7 @@ export class RollPartCheckbox extends RollPart { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   getCheckboxLabelAjustement(rollData) { |   getCheckboxLabelAjustement(rollData) { | ||||||
|     return `${this.getCheckboxIcon(rollData)} ${this.getCurrent(rollData).label}` |     return `${this.getCheckboxIcon(rollData)} ${this.getRefs(rollData).label}` | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   async _onRender(rollDialog, context, options) { |   async _onRender(rollDialog, context, options) { | ||||||
|   | |||||||
| @@ -14,4 +14,8 @@ export class RollPartDefense extends RollPart { | |||||||
|     refs.defenses =[] |     refs.defenses =[] | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   isForceInsuffisante(rollData) { | ||||||
|  |     return true | ||||||
|  |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -43,15 +43,15 @@ export class RollPartOeuvre extends RollPartSelect { | |||||||
|   loadRefs(rollData) { |   loadRefs(rollData) { | ||||||
|     const refs = this.getRefs(rollData) |     const refs = this.getRefs(rollData) | ||||||
|     refs.oeuvres = rollData.active.actor.items |     refs.oeuvres = rollData.active.actor.items | ||||||
|     .filter(it => it.isOeuvre() && RollPartOeuvre.getArt(it)) |       .filter(it => it.isOeuvre() && RollPartOeuvre.getArt(it)) | ||||||
|     .map(it => RollPartOeuvre.$extractOeuvre(it, rollData.active.actor)) |       .map(it => RollPartOeuvre.$extractOeuvre(it, rollData.active.actor)) | ||||||
|     if (refs.oeuvres.length > 0) { |     if (refs.oeuvres.length > 0) { | ||||||
|       this.$selectOeuvre(rollData) |       this.$selectOeuvre(rollData) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|    |  | ||||||
|   choices(refs) { return refs.oeuvres } |   choices(refs) { return refs.oeuvres } | ||||||
|    |  | ||||||
|   static $extractOeuvre(oeuvre, actor) { |   static $extractOeuvre(oeuvre, actor) { | ||||||
|     const art = RollPartOeuvre.getArt(oeuvre) |     const art = RollPartOeuvre.getArt(oeuvre) | ||||||
|     return { |     return { | ||||||
| @@ -64,7 +64,7 @@ export class RollPartOeuvre extends RollPartSelect { | |||||||
|       comp: actor.getCompetence(art.competence(oeuvre)) |       comp: actor.getCompetence(art.competence(oeuvre)) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|    |  | ||||||
|   static getArt(oeuvre) { |   static getArt(oeuvre) { | ||||||
|     return ARTS.find(it => it.type == oeuvre.type) |     return ARTS.find(it => it.type == oeuvre.type) | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ export class RollPartSelect extends RollPart { | |||||||
|     const current = this.getCurrent(rollData) |     const current = this.getCurrent(rollData) | ||||||
|     const newChoice = (choices.length == 0) |     const newChoice = (choices.length == 0) | ||||||
|       ? { key: '', value: defValue } |       ? { key: '', value: defValue } | ||||||
|       : this.$getSelectedChoice(choices, key ?? current?.key ?? refs.key ?? '') |       : this.$getSelectedChoice(choices, key ?? current?.key ?? refs?.key ?? '') | ||||||
|     this.setCurrent(rollData, newChoice) |     this.setCurrent(rollData, newChoice) | ||||||
|     return newChoice |     return newChoice | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| import { Misc } from "../misc.js" | import { Misc } from "../misc.js" | ||||||
| import { StatusEffects } from "../settings/status-effects.js" | import { StatusEffects } from "../settings/status-effects.js" | ||||||
| import { ROLL_MODE_ATTAQUE, ROLL_MODE_DEFENSE } from "./roll-constants.mjs" | import { ROLL_MODE_ATTAQUE, ROLL_MODE_DEFENSE } from "./roll-constants.mjs" | ||||||
|  | import { PART_ATTAQUE, RollPartAttaque } from "./roll-part-attaque.mjs" | ||||||
| import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" | import { ROLLDIALOG_SECTION, RollPart } from "./roll-part.mjs" | ||||||
|  |  | ||||||
| export const PART_SIGN = "sign" | export const PART_SIGN = "sign" | ||||||
| @@ -64,8 +65,17 @@ export class RollPartSign extends RollPart { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   isForceInsuffisante(rollData) { |   isForceInsuffisante(rollData) { | ||||||
|     //this.isCombat(rollData) && ... arme avec force min |     if ([ROLL_MODE_ATTAQUE, ROLL_MODE_DEFENSE].includes(rollData.mode.current)) { | ||||||
|     return this.isCombat(rollData) && true |       const arme = rollData.current[rollData.mode.current]?.arme | ||||||
|  |       const actor = rollData.active.actor | ||||||
|  |  | ||||||
|  |       if (actor?.isPersonnage() && arme) { | ||||||
|  |         const requise = parseInt(arme?.system.force ?? 0) | ||||||
|  |         const force = parseInt(actor.system.carac.force.value) | ||||||
|  |         return requise > force | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return false | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   isAttaqueFinesse(rollData) { |   isAttaqueFinesse(rollData) { | ||||||
|   | |||||||
| @@ -121,7 +121,7 @@ export const referenceAjustements = { | |||||||
|   }, |   }, | ||||||
|   tactique: { |   tactique: { | ||||||
|     isUsed: (rollData, actor) => rollData.tactique, |     isUsed: (rollData, actor) => rollData.tactique, | ||||||
|     getLabel: (rollData, actor) => RdDBonus.find(rollData.tactique).descr, |     getLabel: (rollData, actor) => RdDBonus.find(rollData.tactique).label, | ||||||
|     getValue: (rollData, actor) => RdDBonus.find(rollData.tactique).attaque, |     getValue: (rollData, actor) => RdDBonus.find(rollData.tactique).attaque, | ||||||
|   }, |   }, | ||||||
|   finesse: { |   finesse: { | ||||||
| @@ -130,11 +130,11 @@ export const referenceAjustements = { | |||||||
|   }, |   }, | ||||||
|   surprise: { |   surprise: { | ||||||
|     isUsed: (rollData, actor) => actor.getSurprise(rollData.passeArme), |     isUsed: (rollData, actor) => actor.getSurprise(rollData.passeArme), | ||||||
|     getDescr: (rollData, actor) => RdDBonus.find(actor.getSurprise()).descr |     getDescr: (rollData, actor) => RdDBonus.find(actor.getSurprise()).label | ||||||
|   }, |   }, | ||||||
|   attaqueDefenseurSurpris: { |   attaqueDefenseurSurpris: { | ||||||
|     isUsed: (rollData, actor) => rollData.surpriseDefenseur, |     isUsed: (rollData, actor) => rollData.surpriseDefenseur, | ||||||
|     getLabel: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).descr + (rollData.attackerRoll ? '' : ' défenseur'), |     getLabel: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).label + (rollData.attackerRoll ? '' : ' défenseur'), | ||||||
|     getValue: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).attaque, |     getValue: (rollData, actor) => RdDBonus.find(rollData.surpriseDefenseur).attaque, | ||||||
|   }, |   }, | ||||||
|   armeParade: { |   armeParade: { | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| <div> | <div> | ||||||
|   {{#if effects}} |   {{#if effects}} | ||||||
|     {{#each effects as |effect key|}} |     {{#each effects as |effect|}} | ||||||
|     <span class="active-effect" data-effect="{{effect.id}}"> |     <span class="active-effect" data-effect="{{effect.id}}"> | ||||||
|       <img class="button-effect-img {{#if @root.options.isGM}}delete-active-effect{{/if}}" src="{{effect.img}}" data-tooltip="{{localize effect.name}}" width="24" height="24" /> |       <img class="button-effect-img {{#if @root.options.isGM}}delete-active-effect{{/if}}" src="{{effect.img}}" data-tooltip="{{localize effect.name}}" width="24" height="24" /> | ||||||
|     </span> |     </span> | ||||||
|   | |||||||
| @@ -1,14 +1,45 @@ | |||||||
| {{log 'roll-part-attaque.current' current}} | {{log 'roll-part-attaque.current' current}} | ||||||
| {{log 'roll-part-attaque.refs' refs}} | {{log 'roll-part-attaque.refs' refs}} | ||||||
|  | <subline> | ||||||
|  |   <select name="select-attaque" {{#if rollData.mode.retry}}disabled{{/if}}> | ||||||
|  |     {{selectOptions refs.attaques selected=current.key valueAttr="key" labelAttr="label"}} | ||||||
|  |   </select> | ||||||
|  | </subline> | ||||||
| <roll-part-img> | <roll-part-img> | ||||||
|   {{#if current.attaque}} |   {{#if current.arme}} | ||||||
|   <img src="{{current.attaque.arme.img}}" data-tooltip="{{current.attaque.arme.name}}" /> |   <img src="{{current.arme.img}}" data-tooltip="{{current.arme.name}}" /> | ||||||
|   {{/if}} |   {{/if}} | ||||||
| </roll-part-img> | </roll-part-img> | ||||||
| <roll-part-detail> | <roll-part-detail> | ||||||
|   <subline> |   <subline> | ||||||
|     <select name="select-attaque" {{#if rollData.mode.retry}}disabled{{/if}}> |     <label for="select-tactique">Tactique:</label> | ||||||
|       {{selectOptions refs.attaques selected=current.key valueAttr="key" labelAttr="label"}} |     <select name="select-tactique" {{#if rollData.mode.retry}}disabled{{/if}}> | ||||||
|  |       {{selectOptions refs.tactiques selected=current.tactique.key valueAttr="key" labelAttr="label"}} | ||||||
|     </select> |     </select> | ||||||
|   </subline> |   </subline> | ||||||
|  |   <subline> | ||||||
|  |     {{#if (eq current.arme.system.mortalite 'empoignade')}} | ||||||
|  |       Empoignade, pas de dommages directs | ||||||
|  |     {{else}} | ||||||
|  |       {{#if (and (ne current.arme.system.mortalite 'non-mortel') (eq current.dmg.penetration 0))}} | ||||||
|  |         <input name="check-mortalite" type="checkbox" {{#if (eq current.dmg.mortalite 'mortel')}}checked{{/if}} /> | ||||||
|  |       {{/if}} | ||||||
|  |       <label for="check-mortalite"> | ||||||
|  |         Dommages: {{plusMoins current.dmg.total}} ({{current.dmg.mortalite}}) | ||||||
|  |         {{~#if current.dmg.penetration}}, pénétration {{current.dmg.penetration}}{{/if}} | ||||||
|  |       </label> | ||||||
|  |     {{/if}} | ||||||
|  |   </subline> | ||||||
|  |   {{#if current.defenseur.surprise}} | ||||||
|  |   <subline> | ||||||
|  |       Defenseur en {{lowerFirst current.defenseur.surprise.label}} | ||||||
|  |       {{#each current.defenseur.effects as  |effect|}} | ||||||
|  |         <img class="button-effect-img" src="{{effect.img}}" data-tooltip="{{localize effect.name}}" data-effect="{{effect.id}}"/> | ||||||
|  |         {{localize effect.name}} | ||||||
|  |       {{/each}} | ||||||
|  |   </subline> | ||||||
|  |   {{/if}} | ||||||
|  |   <subline> | ||||||
|  |  | ||||||
|  |   </subline> | ||||||
| </roll-part-detail> | </roll-part-detail> | ||||||
		Reference in New Issue
	
	Block a user