Gestion attaque d'arme, step1 #2
@ -92,6 +92,8 @@
|
|||||||
"BOL.ui.treasure" : "Trésor",
|
"BOL.ui.treasure" : "Trésor",
|
||||||
"BOL.ui.vehicles" : "Véhicules/Montures",
|
"BOL.ui.vehicles" : "Véhicules/Montures",
|
||||||
"BOL.ui.misc" : "Divers",
|
"BOL.ui.misc" : "Divers",
|
||||||
|
"BOL.ui.noWeaponName" : "Unknown Weapon",
|
||||||
|
"BOL.ui.targetDefence": "Defence",
|
||||||
|
|
||||||
"BOL.featureCategory.origins": "Origines",
|
"BOL.featureCategory.origins": "Origines",
|
||||||
"BOL.featureCategory.races": "Races",
|
"BOL.featureCategory.races": "Races",
|
||||||
|
@ -94,6 +94,8 @@
|
|||||||
"BOL.ui.misc" : "Divers",
|
"BOL.ui.misc" : "Divers",
|
||||||
"BOL.ui.vehicleProperties" : " Propriétés de véhicule",
|
"BOL.ui.vehicleProperties" : " Propriétés de véhicule",
|
||||||
"BOL.ui.speed" : "Vitesse",
|
"BOL.ui.speed" : "Vitesse",
|
||||||
|
"BOL.ui.noWeaponName" : "Arme Inconnue",
|
||||||
|
"BOL.ui.targetDefence": "Défense",
|
||||||
|
|
||||||
"BOL.featureCategory.origins": "Origines",
|
"BOL.featureCategory.origins": "Origines",
|
||||||
"BOL.featureCategory.races": "Races",
|
"BOL.featureCategory.races": "Races",
|
||||||
|
@ -186,7 +186,7 @@ export class BoLActorSheet extends ActorSheet {
|
|||||||
BoLRoll.aptitudeCheck(this.actor, actorData, dataset, event);
|
BoLRoll.aptitudeCheck(this.actor, actorData, dataset, event);
|
||||||
break;
|
break;
|
||||||
case "weapon":
|
case "weapon":
|
||||||
console.log("ROLL WEAPON !!!"); // TODO
|
BoLRoll.weaponCheck(this.actor, actorData, dataset, event);
|
||||||
break;
|
break;
|
||||||
default : break;
|
default : break;
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,9 @@ export class BoLActor extends Actor {
|
|||||||
get aptitudes() {
|
get aptitudes() {
|
||||||
return Object.values(this.data.data.aptitudes);
|
return Object.values(this.data.data.aptitudes);
|
||||||
}
|
}
|
||||||
|
get defenseValue() {
|
||||||
|
return this.data.data.aptitudes.def.value;
|
||||||
|
}
|
||||||
get resources() {
|
get resources() {
|
||||||
return Object.values(this.data.data.resources);
|
return Object.values(this.data.data.resources);
|
||||||
}
|
}
|
||||||
@ -84,7 +87,6 @@ export class BoLActor extends Actor {
|
|||||||
get protections() {
|
get protections() {
|
||||||
return this.armors.concat(this.helms).concat(this.shields)
|
return this.armors.concat(this.helms).concat(this.shields)
|
||||||
}
|
}
|
||||||
|
|
||||||
get melee() {
|
get melee() {
|
||||||
return this.weapons.filter(i => i.data.properties.melee === true);
|
return this.weapons.filter(i => i.data.properties.melee === true);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { BoLUtility } from "../system/bol-utility.js";
|
||||||
|
|
||||||
export class BoLRoll {
|
export class BoLRoll {
|
||||||
static options() {
|
static options() {
|
||||||
return { classes: ["bol", "dialog"] };
|
return { classes: ["bol", "dialog"] };
|
||||||
@ -25,6 +27,40 @@ export class BoLRoll {
|
|||||||
return this.aptitudeRollDialog(actor, actorData, aptitude, label, description, adv, 0);
|
return this.aptitudeRollDialog(actor, actorData, aptitude, label, description, adv, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static weaponCheck(actor, actorData, dataset, event) {
|
||||||
|
// const elt = $(event.currentTarget)[0];
|
||||||
|
// let key = elt.attributes["data-rolling"].value;
|
||||||
|
let target = BoLUtility.getTarget()
|
||||||
|
if ( !target) {
|
||||||
|
ui.notifications.warn("No target selected for attack !");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const li = $(event.currentTarget).parents(".item");
|
||||||
|
console.log("ITEM", target);
|
||||||
|
const weapon = actor.items.get(li.data("item-id"));
|
||||||
|
if (!weapon) {
|
||||||
|
ui.notifications.warn("Unable to find weapon !");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let weaponData = weapon.data.data;
|
||||||
|
let attackDef= {
|
||||||
|
id:randomID(16),
|
||||||
|
attacker: actor,
|
||||||
|
attackerData: actorData,
|
||||||
|
weapon :weapon,
|
||||||
|
mod: 0,
|
||||||
|
target : target,
|
||||||
|
defender: game.actors.get(target.data.actorId),
|
||||||
|
adv :dataset.adv || 0,
|
||||||
|
attribute : eval(`actor.data.data.attributes.${weaponData.properties.attackAttribute}`),
|
||||||
|
aptitude : eval(`actor.data.data.aptitudes.${weaponData.properties.attackAptitude}`),
|
||||||
|
label : (weapon.name) ? weapon.name : game.i18n.localize('BOL.ui.noWeaponName'),
|
||||||
|
description : actor.name + " - " + game.i18n.localize('BOL.ui.weaponAttack'),
|
||||||
|
}
|
||||||
|
console.log("WEAPON!", attackDef, weaponData);
|
||||||
|
return this.weaponRollDialog(attackDef);
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
/* ROLL DIALOGS */
|
/* ROLL DIALOGS */
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
@ -58,7 +94,7 @@ export class BoLRoll {
|
|||||||
const adv = html.find('#adv').val();
|
const adv = html.find('#adv').val();
|
||||||
const mod = html.find('#mod').val();
|
const mod = html.find('#mod').val();
|
||||||
let careers = html.find('#career').val();
|
let careers = html.find('#career').val();
|
||||||
const career = (!careers) ? 0 : Math.max(...careers.map(i => parseInt(i)));
|
const career = (careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i)));
|
||||||
const isMalus = adv < 0;
|
const isMalus = adv < 0;
|
||||||
const dicePool = (isMalus) ? 2 - parseInt(adv) : 2 + parseInt(adv);
|
const dicePool = (isMalus) ? 2 - parseInt(adv) : 2 + parseInt(adv);
|
||||||
const attrValue = eval(`actor.data.data.attributes.${attr}.value`);
|
const attrValue = eval(`actor.data.data.attributes.${attr}.value`);
|
||||||
@ -75,6 +111,59 @@ export class BoLRoll {
|
|||||||
return d.render(true);
|
return d.render(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async weaponRollDialog( attackDef) {
|
||||||
|
const rollOptionTpl = 'systems/bol/templates/dialogs/weapon-roll-dialog.hbs';
|
||||||
|
const dialogData = {
|
||||||
|
attr:attackDef.attribute,
|
||||||
|
adv:attackDef.adv,
|
||||||
|
mod: attackDef.mod,
|
||||||
|
apt:attackDef.aptitude,
|
||||||
|
weapon: attackDef.weapon,
|
||||||
|
attackId: attackDef.id,
|
||||||
|
careers: attackDef.attackerData.features.careers,
|
||||||
|
boons: attackDef.attackerData.features.boons,
|
||||||
|
flaws: attackDef.attackerData.features.flaws,
|
||||||
|
defence: attackDef.defender.defenseValue,
|
||||||
|
};
|
||||||
|
const rollOptionContent = await renderTemplate(rollOptionTpl, dialogData);
|
||||||
|
let d = new Dialog({
|
||||||
|
title: attackDef.label,
|
||||||
|
content: rollOptionContent,
|
||||||
|
buttons: {
|
||||||
|
cancel: {
|
||||||
|
icon: '<i class="fas fa-times"></i>',
|
||||||
|
label: game.i18n.localize("BOL.ui.cancel"),
|
||||||
|
callback: () => {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submit: {
|
||||||
|
icon: '<i class="fas fa-check"></i>',
|
||||||
|
label: game.i18n.localize("BOL.ui.submit"),
|
||||||
|
callback: (html) => {
|
||||||
|
const attr = html.find('#attr').val();
|
||||||
|
const apt = html.find('#apt').val();
|
||||||
|
const adv = html.find('#adv').val();
|
||||||
|
const mod = html.find('#mod').val() || 0;
|
||||||
|
let careers = html.find('#career').val();
|
||||||
|
const career = (careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i)));
|
||||||
|
const isMalus = adv < 0;
|
||||||
|
const dicePool = (isMalus) ? 2 - parseInt(adv) : 2 + parseInt(adv);
|
||||||
|
const attrValue = eval(`attackDef.attacker.data.data.attributes.${attr}.value`);
|
||||||
|
const aptValue = eval(`attackDef.attacker.data.data.aptitudes.${apt}.value`);
|
||||||
|
const modifiers = parseInt(attrValue) + parseInt(aptValue) + parseInt(mod) + parseInt(career) - dialogData.defence;
|
||||||
|
const formula = (isMalus) ? dicePool + "d6kl2 + " + modifiers : dicePool + "d6kh2 + " + modifiers;
|
||||||
|
attackDef.formula = formula;
|
||||||
|
let r = new BoLAttackRoll(attackDef);
|
||||||
|
r.roll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
default: 'submit',
|
||||||
|
close: () => {}
|
||||||
|
}, this.options());
|
||||||
|
return d.render(true);
|
||||||
|
}
|
||||||
|
|
||||||
static async aptitudeRollDialog(actor, actorData, aptitude, label, description, adv, mod, onEnter = "submit") {
|
static async aptitudeRollDialog(actor, actorData, aptitude, label, description, adv, mod, onEnter = "submit") {
|
||||||
const rollOptionTpl = 'systems/bol/templates/dialogs/aptitude-roll-dialog.hbs';
|
const rollOptionTpl = 'systems/bol/templates/dialogs/aptitude-roll-dialog.hbs';
|
||||||
const dialogData = {
|
const dialogData = {
|
||||||
@ -104,7 +193,7 @@ export class BoLRoll {
|
|||||||
const adv = html.find('#adv').val();
|
const adv = html.find('#adv').val();
|
||||||
const mod = html.find('#mod').val();
|
const mod = html.find('#mod').val();
|
||||||
let careers = html.find('#career').val();
|
let careers = html.find('#career').val();
|
||||||
const career = (!careers) ? 0 : Math.max(...careers.map(i => parseInt(i)));
|
const career = (careers.length == 0) ? 0 : Math.max(...careers.map(i => parseInt(i)));
|
||||||
const isMalus = adv < 0;
|
const isMalus = adv < 0;
|
||||||
const dicePool = (isMalus) ? 2 - parseInt(adv) : 2 + parseInt(adv);
|
const dicePool = (isMalus) ? 2 - parseInt(adv) : 2 + parseInt(adv);
|
||||||
const aptValue = eval(`actor.data.data.aptitudes.${apt}.value`);
|
const aptValue = eval(`actor.data.data.aptitudes.${apt}.value`);
|
||||||
@ -123,12 +212,13 @@ export class BoLRoll {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class BoLDefaultRoll {
|
export class BoLDefaultRoll {
|
||||||
constructor(label, formula, description){
|
constructor(label, formula, description, isWeapon=false){
|
||||||
this._label = label;
|
this._label = label;
|
||||||
this._formula = formula;
|
this._formula = formula;
|
||||||
this._isSuccess = false;
|
this._isSuccess = false;
|
||||||
this._isCritical = false;
|
this._isCritical = false;
|
||||||
this._isFumble = false;
|
this._isFumble = false;
|
||||||
|
this._isWeapon = isWeapon;
|
||||||
this._description = description;
|
this._description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,6 +238,9 @@ export class BoLDefaultRoll {
|
|||||||
flags : {msgType : "default"}
|
flags : {msgType : "default"}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
if (this._isSuccess && this._isWeapon) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildChatMessage(actor) {
|
_buildChatMessage(actor) {
|
||||||
@ -167,6 +260,75 @@ export class BoLDefaultRoll {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class BoLAttackRoll {
|
||||||
|
constructor(attackDef){
|
||||||
|
this.attackDef = attackDef;
|
||||||
|
this._isSuccess = false;
|
||||||
|
this._isCritical = false;
|
||||||
|
this._isFumble = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async roll(){
|
||||||
|
const r = new Roll(this.attackDef.formula);
|
||||||
|
await r.roll({"async": true});
|
||||||
|
const activeDice = r.terms[0].results.filter(r => r.active);
|
||||||
|
const diceTotal = activeDice.map(r => r.result).reduce((a, b) => a + b);
|
||||||
|
this._isSuccess = (r.total >= 9);
|
||||||
|
this._isCritical = (diceTotal === 12);
|
||||||
|
this._isFumble = (diceTotal === 2);
|
||||||
|
this._buildChatMessage(this.attackDef.attacker).then(msgFlavor => {
|
||||||
|
r.toMessage({
|
||||||
|
user: game.user.id,
|
||||||
|
flavor: msgFlavor,
|
||||||
|
speaker: ChatMessage.getSpeaker({actor: this.attackDef.attacker}),
|
||||||
|
flags : {msgType : "default"}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this._isSuccess ) {
|
||||||
|
let attrDamage = this.attackDef.weapon.data.data.properties.damageAttribute;
|
||||||
|
let weaponFormula = BoLUtility.getDamageFormula(this.attackDef.weapon.data.data.properties.damage)
|
||||||
|
let damageFormula = weaponFormula + "+" + this.attackDef.attacker.data.data.attributes[attrDamage].value;
|
||||||
|
this.damageRoll = new Roll(damageFormula);
|
||||||
|
await this.damageRoll.roll({"async": true});
|
||||||
|
this._buildDamageChatMessage(this.attackDef.attacker, this.attackDef.weapon, this.damageRoll.total).then(msgFlavor => {
|
||||||
|
this.damageRoll.toMessage({
|
||||||
|
user: game.user.id,
|
||||||
|
flavor: msgFlavor,
|
||||||
|
speaker: ChatMessage.getSpeaker({actor: this.attackDef.attacker}),
|
||||||
|
flags : {msgType : "default"}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildDamageChatMessage(actor, weapon, total) {
|
||||||
|
const rollMessageTpl = 'systems/bol/templates/chat/rolls/damage-roll-card.hbs';
|
||||||
|
const tplData = {
|
||||||
|
actor : actor,
|
||||||
|
label : this._label,
|
||||||
|
weapon: weapon,
|
||||||
|
damage: total,
|
||||||
|
};
|
||||||
|
return renderTemplate(rollMessageTpl, tplData);
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildChatMessage(actor) {
|
||||||
|
const rollMessageTpl = 'systems/bol/templates/chat/rolls/default-roll-card.hbs';
|
||||||
|
const tplData = {
|
||||||
|
actor : actor,
|
||||||
|
label : this._label,
|
||||||
|
isSuccess : this._isSuccess,
|
||||||
|
isFailure : !this._isSuccess,
|
||||||
|
isCritical : this._isCritical,
|
||||||
|
isFumble : this._isFumble,
|
||||||
|
hasDescription : this._description && this._description.length > 0,
|
||||||
|
description : this._description
|
||||||
|
};
|
||||||
|
return renderTemplate(rollMessageTpl, tplData);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
// export class BoLWeaponRoll {
|
// export class BoLWeaponRoll {
|
||||||
// constructor(actor, label, formula, isCritical, description){
|
// constructor(actor, label, formula, isCritical, description){
|
||||||
// this._label = label;
|
// this._label = label;
|
||||||
|
@ -179,6 +179,30 @@ export class BoLUtility {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
static getDamageFormula( damageString) {
|
||||||
|
if (damageString[0] == 'd') {damageString = "1" + damageString} // Help parsing
|
||||||
|
var myReg = new RegExp('(\\d+)[dD]([\\d]+)([MB]*)?([\\+\\d]*)?', 'g');
|
||||||
|
let res = myReg.exec(damageString);
|
||||||
|
let nbDice = parseInt(res[1]);
|
||||||
|
let postForm = 'kh'+nbDice;
|
||||||
|
let modIndex = 3;
|
||||||
|
if ( res[3]) {
|
||||||
|
if ( res[3] == 'M') {
|
||||||
|
postForm = 'kl'+nbDice;
|
||||||
|
nbDice++;
|
||||||
|
modIndex = 4;
|
||||||
|
}
|
||||||
|
if ( res[3] == 'B') {
|
||||||
|
postForm = 'kh'+nbDice;
|
||||||
|
nbDice++;
|
||||||
|
modIndex = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let formula = nbDice+"d"+res[2] + postForm + ((res[modIndex]) ? res[modIndex] : "");
|
||||||
|
return formula;
|
||||||
|
}
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/* -------------------------------------------- */
|
||||||
static async confirmDelete(actorSheet, li) {
|
static async confirmDelete(actorSheet, li) {
|
||||||
let itemId = li.data("item-id");
|
let itemId = li.data("item-id");
|
||||||
|
7
templates/chat/rolls/damage-roll-card.hbs
Normal file
7
templates/chat/rolls/damage-roll-card.hbs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<img class="chat-icon" src="{{weapon.img}}" alt="{{weapon.name}}"/>
|
||||||
|
<h3><strong>Dommages de l'arme : {{damage}}</strong>
|
||||||
|
{{!#if hasDescription}}
|
||||||
|
<!--<h4>-->
|
||||||
|
<!-- <pre class="rollDescr-line">{{!description}}</pre>-->
|
||||||
|
<!--</h4>-->
|
||||||
|
{{!/if}}
|
72
templates/dialogs/weapon-roll-dialog.hbs
Normal file
72
templates/dialogs/weapon-roll-dialog.hbs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<form class="{{cssClass}}" autocomplete="off">
|
||||||
|
{{!-- Sheet Header --}}
|
||||||
|
<header class="sheet-header">
|
||||||
|
<div class="row flexrow table-header">
|
||||||
|
<div class="flex1 center">
|
||||||
|
<h3>{{localize 'BOL.ui.weaponCheck'}}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="flexrow" style="margin-bottom: 1px;">
|
||||||
|
<div class="flex1 center bg-darkred">
|
||||||
|
<label for="attr">{{localize 'BOL.ui.attribute'}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex1 center cell">
|
||||||
|
<select class="flex1" name="attr" id="attr" data-type="String">
|
||||||
|
<option value="vigor" {{#if (equals attr.key "vigor")}}selected{{/if}}>{{localize 'BOL.attributes.vigor'}}</option>
|
||||||
|
<option value="agility" {{#if (equals attr.key "agility")}}selected{{/if}}>{{localize 'BOL.attributes.agility'}}</option>
|
||||||
|
<option value="mind" {{#if (equals attr.key "mind")}}selected{{/if}}>{{localize 'BOL.attributes.mind'}}</option>
|
||||||
|
<option value="appeal" {{#if (equals attr.key "appeal")}}selected{{/if}}>{{localize 'BOL.attributes.appeal'}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flexrow" style="margin-bottom: 1px;">
|
||||||
|
<div class="flex1 center bg-darkred">
|
||||||
|
<label for="apt">{{localize 'BOL.ui.aptitude'}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex1 center cell">
|
||||||
|
<select class="flex1" name="apt" id="apt" data-type="String">
|
||||||
|
<option value="init" {{#if (equals apt.key "init")}}selected{{/if}}>{{localize 'BOL.aptitudes.init'}}</option>
|
||||||
|
<option value="melee" {{#if (equals apt.key "melee")}}selected{{/if}}>{{localize 'BOL.aptitudes.melee'}}</option>
|
||||||
|
<option value="ranged" {{#if (equals apt.key "ranged")}}selected{{/if}}>{{localize 'BOL.aptitudes.ranged'}}</option>
|
||||||
|
<option value="def" {{#if (equals apt.key "def")}}selected{{/if}}>{{localize 'BOL.aptitudes.def'}}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flexrow" style="margin-bottom: 1px;">
|
||||||
|
<div class="flex1 center bg-darkred">
|
||||||
|
<label for="adv">{{localize 'BOL.ui.advantages'}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex1 center cell">
|
||||||
|
<input type="text" class="field-value" name="adv" id="adv" value="{{adv}}" data-type="Number">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flexrow" style="margin-bottom: 1px;">
|
||||||
|
<div class="flex1 center bg-darkred">
|
||||||
|
<label for="mod">{{localize 'BOL.ui.modifiers'}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex1 center cell">
|
||||||
|
<input type="text" class="field-value" name="mod" id="mod" value="{{mod}}" data-type="Number">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flexrow" style="margin-bottom: 1px;">
|
||||||
|
<div class="flex1 center bg-darkred">
|
||||||
|
<label for="mod">{{localize 'BOL.ui.targetDefence'}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex1 center cell">{{defence}}</div>
|
||||||
|
</div>
|
||||||
|
{{#if careers.items}}
|
||||||
|
<div class="flexrow" style="margin-bottom: 1px;">
|
||||||
|
<div class="flex1 center bg-darkred">
|
||||||
|
<label for="mod">{{localize 'BOL.ui.careers'}}</label>
|
||||||
|
</div>
|
||||||
|
<div class="flex1 center cell">
|
||||||
|
<select class="flex1" name="career" id="career" data-type="String" multiple>
|
||||||
|
{{#each careers.items as | career id|}}
|
||||||
|
<option value="{{career.data.rank}}">{{career.name}} ({{numberFormat career.data.rank decimals=0 sign=true}})</option>
|
||||||
|
{{/each}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</form>
|
Loading…
Reference in New Issue
Block a user