Add spells and alchemy

This commit is contained in:
2022-01-23 09:25:09 +01:00
parent 5eb059a2fa
commit aeb7739879
28 changed files with 696 additions and 71 deletions

View File

@ -38,6 +38,11 @@ export class BoLActorSheet extends ActorSheet {
// Equip/Unequip item
html.find('.item-equip').click(this._onToggleEquip.bind(this));
html.find(".inc-dec-btns-alchemy").click((ev) => {
const li = $(ev.currentTarget).parents(".item");
this.actor.spendAlchemyPoint( li.data("itemId"), 1)
})
// Incr./Decr. career ranks
html.find(".inc-dec-btns").click((ev) => {
const li = $(ev.currentTarget).parents(".item");
@ -65,10 +70,6 @@ export class BoLActorSheet extends ActorSheet {
item.update(update);
}
}
// const input = html.find("#" + type);
// let value = parseInt(input.val(), 10) || 0;
// value += operator === "plus" ? 1 : -1;
// input.val(value > 0 ? value : 0);
});
@ -110,26 +111,36 @@ export class BoLActorSheet extends ActorSheet {
const data = super.getData(options);
const actorData = duplicate(data.data);
let formData = duplicate(data)
formData.config = game.bol.config;
formData.data = actorData.data;
formData.details = this.actor.details;
formData.attributes = this.actor.attributes;
formData.aptitudes = this.actor.aptitudes;
formData.resources = this.actor.getResourcesFromType();
formData.equipment = this.actor.equipment;
formData.weapons = this.actor.weapons;
formData.protections = this.actor.protections;
formData.containers = this.actor.containers;
formData.treasure = this.actor.treasure;
formData.config = game.bol.config
formData.data = actorData.data
formData.details = this.actor.details
formData.attributes = this.actor.attributes
formData.aptitudes = this.actor.aptitudes
formData.resources = this.actor.getResourcesFromType()
formData.equipment = this.actor.equipment
formData.weapons = this.actor.weapons
formData.protections = this.actor.protections
formData.spells = this.actor.spells
formData.alchemy = this.actor.alchemy
formData.containers = this.actor.containers
formData.treasure = this.actor.treasure
formData.treasure = this.actor.treasure
formData.treasure = this.actor.alchemyrecipe
formData.vehicles = this.actor.vehicles;
formData.ammos = this.actor.ammos;
formData.misc = this.actor.misc;
formData.combat = this.actor.buildCombat();
formData.features = this.actor.buildFeatures();
formData.isGM = game.user.isGM;
formData.options= this.options,
formData.owner= this.document.isOwner,
formData.editScore= this.options.editScore,
formData.features = this.actor.buildFeatures()
formData.isGM = game.user.isGM
formData.options= this.options
formData.owner= this.document.isOwner
formData.editScore= this.options.editScore
formData.isSorcerer = this.actor.isSorcerer()
formData.isAlchemist = this.actor.isAlchemist()
formData.isPriest = this.actor.isPriest()
formData.isGM= game.user.isGM
console.log("ACTORDATA", formData);
@ -193,6 +204,12 @@ export class BoLActorSheet extends ActorSheet {
case "weapon":
BoLRoll.weaponCheck(this.actor, actorData, dataset, event);
break;
case "spell":
BoLRoll.spellCheck(this.actor, actorData, dataset, event);
break;
case "alchemy":
BoLRoll.alchemyCheck(this.actor, actorData, dataset, event);
break;
case "protection":
this.actor.rollProtection(li.data("item-id"))
break;

View File

@ -109,6 +109,12 @@ export class BoLActor extends Actor {
get protections() {
return this.armors.concat(this.helms).concat(this.shields)
}
get spells() {
return this.itemData.filter(i => i.type === "item" && i.data.category === "spell");
}
get alchemy() {
return this.itemData.filter(i => i.type === "item" && i.data.category === "alchemy");
}
get melee() {
return this.weapons.filter(i => i.data.properties.melee === true);
}
@ -136,6 +142,66 @@ export class BoLActor extends Actor {
return this.itemData.filter(i => i.type === "item" && i.data.category === "equipment" && (i.data.subtype === "other" ||i.data.subtype === "container" ||i.data.subtype === "scroll" || i.data.subtype === "jewel"));
}
isSorcerer( ) {
if ( this.careers.find( item => item.data.properties.sorcerer == true) )
return true
return false
}
isAlchemist( ) {
if ( this.careers.find( item => item.data.properties.alchemist == true) )
return true
return false
}
isPriest( ) {
if ( this.careers.find( item => item.data.properties.priest == true) )
return true
return false
}
spendPowerPoint( ppCost ) {
let newPP = this.data.data.resources.power.value - ppCost
newPP = (newPP<0) ? 0 : newPP
this.update( {'data.resources.power.value': newPP})
}
resetAlchemyStatus( alchemyId ) {
let alchemy = this.data.items.get( alchemyId)
if (alchemy) {
this.updateEmbeddedDocuments('Item', [{_id: alchemy.id, 'data.properties.pccurrent': 0}] )
}
}
async spendAlchemyPoint( alchemyId, pcCost) {
let alchemy = this.data.items.get( alchemyId)
if (alchemy) {
pcCost = Number(pcCost)?? 0
if ( this.data.data.resources.alchemypoints.value >= pcCost) {
let newPC = this.data.data.resources.alchemypoints.value - pcCost
newPC = (newPC<0) ? 0 : newPC
this.update( {'data.resources.alchemypoints.value': newPC} )
newPC = alchemy.data.data.properties.pccurrent + pcCost
await this.updateEmbeddedDocuments('Item', [{_id: alchemy.id, 'data.properties.pccurrent': newPC}] )
} else {
ui.notifications.warn("Plus assez de Points de Création !")
}
}
}
getAlchemistBonus() {
let sorcerer = this.careers.find( item => item.data.properties.alchemist == true)
if (sorcerer) {
return sorcerer.data.rank
}
return 0;
}
getSorcererBonus() {
let sorcerer = this.careers.find( item => item.data.properties.sorcerer == true)
if (sorcerer) {
return sorcerer.data.rank
}
return 0;
}
heroReroll( ) {
if (this.type == 'character') {
return this.data.data.resources.hero.value > 0;

View File

@ -63,8 +63,6 @@ export class BoLRoll {
}
static weaponCheck(actor, actorData, dataset, event) {
// const elt = $(event.currentTarget)[0];
// let key = elt.attributes["data-rolling"].value;
let target = BoLUtility.getTarget()
const li = $(event.currentTarget).parents(".item");
const weapon = actor.items.get(li.data("item-id"));
@ -85,13 +83,83 @@ export class BoLRoll {
label: (weapon.name) ? weapon.name : game.i18n.localize('BOL.ui.noWeaponName'),
description: actor.name + " - " + game.i18n.localize('BOL.ui.weaponAttack'),
adv: "2",
}
console.debug("WEAPON!", attackDef, weaponData);
return this.displayRollDialog(attackDef);
}
static alchemyCheck( actor, actorData, dataset, event) {
const li = $(event.currentTarget).parents(".item");
const alchemy = actor.items.get(li.data("item-id"));
if (!alchemy) {
ui.notifications.warn("Unable to find Alchemy !");
return;
}
let alchemyData = alchemy.data.data
if (alchemyData.properties.pccurrent < alchemyData.properties.pccost) {
ui.notifications.warn("Pas assez de Points de Cration investis dans la Préparation !")
return
}
let alchemyDef = {
mode: "alchemy",
actor: actor,
actorData: actorData,
alchemy: alchemy,
attribute: actor.data.data.attributes.mind,
careerBonus: actor.getAlchemistBonus(),
pcCost: alchemyData.properties.pccost,
pcCostCurrent: alchemyData.properties.pccurrent,
mod: alchemyData.properties.difficulty,
label: alchemy.name,
adv: "2",
description: actor.name + " - " + game.i18n.localize('BOL.ui.makeAlchemy'),
}
console.log("ALCHEMY!", alchemyDef);
return this.displayRollDialog(alchemyDef);
}
static spellCheck( actor, actorData, dataset, event) {
if (actor.data.data.resources.power.value <= 0) {
ui.notifications.warn("Plus assez de points de Pouvoir !")
return
}
const li = $(event.currentTarget).parents(".item");
const spell = actor.items.get(li.data("item-id"));
if (!spell) {
ui.notifications.warn("Unable to find spell !");
return;
}
let spellData = spell.data.data;
let spellDef = {
mode: "spell",
actor: actor,
actorData: actorData,
spell: spell,
attribute: actor.data.data.attributes.mind,
ppCurrent: actor.data.data.resources.power.value,
careerBonus: actor.getSorcererBonus(),
ppCost: spell.data.data.properties.ppcost,
mod: spellData.properties.difficulty,
label: spell.name,
adv: "2",
description: actor.name + " - " + game.i18n.localize('BOL.ui.focusSpell'),
}
console.log("SPELL!", spellDef);
return this.displayRollDialog(spellDef);
}
/* -------------------------------------------- */
static rollDialogListener(html) {
html.find('#optcond').change((event) => { // Dynamic change of PP cost of spell
let pp = BoLUtility.computeSpellCost(this.rollData.spell, event.currentTarget.selectedOptions.length)
$('#ppcost').html(pp)
this.rollData.ppCost = pp
});
}
/* ROLL DIALOGS */
/* -------------------------------------------- */
static async displayRollDialog(rollData, onEnter = "submit") {
@ -101,8 +169,10 @@ export class BoLRoll {
rollData.boons = rollData.actorData.features.boons
rollData.flaws = rollData.actorData.features.flaws
rollData.defence = 0
rollData.mod = 0
rollData.careerBonus = rollData.careerBonus?? 0
rollData.mod = rollData.mod?? 0
rollData.id = randomID(16)
this.rollData = rollData
// Weapon mode specific management
rollData.weaponModifier = 0
@ -128,6 +198,7 @@ export class BoLRoll {
title: rollData.label,
content: rollOptionContent,
rollData: rollData,
render: html => this.rollDialogListener(html),
buttons: {
cancel: {
icon: '<i class="fas fa-times"></i>',
@ -139,6 +210,11 @@ export class BoLRoll {
icon: '<i class="fas fa-check"></i>',
label: game.i18n.localize("BOL.ui.submit"),
callback: (html) => {
if (rollData.mode == 'spell' && rollData.ppCurrent < rollData.ppCost) { // Check PP available
ui.notifications.warn("Pas assez de Points de Pouvoir !")
return
}
rollData.attrKey = html.find('#attr').val();
rollData.aptKey = html.find('#apt').val();
rollData.adv = $("input[name='adv']:checked").val() || "2";
@ -156,14 +232,14 @@ export class BoLRoll {
}
}
const isMalus = rollData.adv.includes('M');
const isMalus = rollData.adv.includes('M')
let dicePool = __adv2dice[rollData.adv]
dicePool += (rollData.attackBonusDice) ? 1 : 0
//// const dicePool = (isMalus) ? 2 - parseInt(rollData.adv) : 2 + parseInt(rollData.adv);
const attrValue = (rollData.attrKey) && eval(`rollData.actor.data.data.attributes.${rollData.attrKey}.value`) || 0;
const aptValue = (rollData.aptKey) && eval(`rollData.actor.data.data.aptitudes.${rollData.aptKey}.value`) || 0
const modifiers = rollData.weaponModifier + parseInt(attrValue) + parseInt(aptValue) + parseInt(rollData.mod) + parseInt(rollData.career) - rollData.defence - shieldMalus;
const modifiers = rollData.careerBonus + rollData.weaponModifier + parseInt(attrValue) + parseInt(aptValue) + parseInt(rollData.mod) + parseInt(rollData.career) - rollData.defence - shieldMalus;
const formula = (isMalus) ? dicePool + "d6kl2 + " + modifiers : dicePool + "d6kh2 + " + modifiers;
rollData.formula = formula;
rollData.modifiers = modifiers
@ -176,6 +252,7 @@ export class BoLRoll {
default: onEnter,
close: () => { }
}, this.options());
return d.render(true);
}
}
@ -219,10 +296,14 @@ export class BoLDefaultRoll {
if (this.rollData.registerInit) {
this.rollData.actor.registerInit(r.total, this.rollData.isCritical);
}
if (this.rollData.isSuccess && this.rollData.mode == "spell") { // PP cost management
this.rollData.actor.spendPowerPoint(this.rollData.ppCost)
}
if (this.rollData.mode == "alchemy") { // PP cost management
this.rollData.actor.resetAlchemyStatus(this.rollData.alchemy.id)
}
console.log("ROLL", this.rollData)
await this.sendChatMessage()
}
async sendChatMessage() {

View File

@ -17,18 +17,7 @@ export class BoLItemSheet extends ItemSheet {
});
}
// /** @override */
// get template() {
// const path = "systems/bol/templates/item";
// // Return a single sheet for all item types.
// //return `${path}/item-sheet.hbs`;
// // Alternatively, you could use the following return statement to do a
// // unique item sheet by type, like `weapon-sheet.html`.
// return `${path}/item-${this.item.data.type}-sheet.hbs`;
// }
/* -------------------------------------------- */
/** @override */
getData(options) {
const data = super.getData(options);
@ -39,7 +28,18 @@ export class BoLItemSheet extends ItemSheet {
data.category = itemData.category;
data.itemProperties = this.item.itemProperties;
data.isGM = game.user.isGM;
console.debug("ITEMDATA", data);
// Dynamic spell fix
if (itemData.type == "item" && itemData.data.category == 'spell') {
for (let i=0; i<4; i++) {
itemData.data.properties.mandatoryconditions[i] = itemData.data.properties.mandatoryconditions[i]?? ""
}
for (let i=0; i<8; i++) {
itemData.data.properties.optionnalconditions[i] = itemData.data.properties.optionnalconditions[i]?? ""
}
}
console.log("ITEMDATA", data);
return data;
}

View File

@ -1,5 +1,8 @@
import { BoLDefaultRoll } from "../controllers/bol-rolls.js";
// Spell circle to min PP cost
const __circle2minpp = { 0: 0, 1: 2,2: 6, 3: 11}
export class BoLUtility {
@ -375,6 +378,14 @@ export class BoLUtility {
if (sockmsg.name == "msg_damage_handling") {
BoLUtility.processDamageHandling(sockmsg.data.event, sockmsg.data.attackId, sockmsg.data.defenseMode)
}
}
/* -------------------------------------------- */
static computeSpellCost( spell, nbOptCond= 0) {
let pp = spell.data.data.properties.ppcost
let minpp = __circle2minpp[spell.data.data.properties.circle]
pp = (pp-nbOptCond<minpp) ? minpp : pp-nbOptCond
return pp
}
/* -------------------------------------------- */

View File

@ -27,6 +27,20 @@ BOL.damageMultiplier = {
"4": "x4",
}
BOL.spellType = {
"0": "BOL.spellItem.charm",
"1": "BOL.spellItem.circle1",
"2": "BOL.spellItem.circle2",
"3": "BOL.spellItem.circle3"
}
BOL.alchemyType = {
"common": "BOL.alchemyItem.common",
"scarce": "BOL.alchemyItem.scarce",
"legend": "BOL.alchemyItem.legend",
"mythic": "BOL.alchemyItem.mythic",
}
BOL.equipmentSlots = {
"none" : "BOL.equipmentSlots.none",
"head" : "BOL.equipmentSlots.head",
@ -115,6 +129,7 @@ BOL.itemCategories = {
"equipment" : "BOL.itemCategory.equipment",
"capacity" : "BOL.itemCategory.capacity",
"spell" : "BOL.itemCategory.spell",
"alchemy" : "BOL.itemCategory.alchemy",
"vehicle" : "BOL.itemCategory.vehicle",
"other" : "BOL.itemCategory.other"
}

View File

@ -63,7 +63,14 @@ export const registerHandlebarsHelpers = function () {
Handlebars.registerHelper('or3', function (val1, val2, val3) {
return val1 || val2 || val3;
});
Handlebars.registerHelper('for', function(from, to, incr, block) {
var accum = '';
for(var i = from; i < to; i += incr)
accum += block.fn(i);
return accum;
});
Handlebars.registerHelper('not', function (cond) {
return !cond;
});
@ -94,6 +101,10 @@ export const registerHandlebarsHelpers = function () {
Handlebars.registerHelper('add', function (a, b) {
return parseInt(a) + parseInt(b);
});
Handlebars.registerHelper('sub', function (a, b) {
return parseInt(a) - parseInt(b);
});
Handlebars.registerHelper('valueAtIndex', function (arr, idx) {
return arr[idx];

View File

@ -14,6 +14,7 @@ export const preloadHandlebarsTemplates = async function () {
"systems/bol/templates/actor/parts/tabs/actor-actions.hbs",
"systems/bol/templates/actor/parts/tabs/actor-features.hbs",
"systems/bol/templates/actor/parts/tabs/actor-equipment.hbs",
"systems/bol/templates/actor/parts/tabs/actor-spellalchemy.hbs",
// ITEMS
"systems/bol/templates/item/parts/item-header.hbs",
"systems/bol/templates/item/parts/properties/feature-properties.hbs",
@ -24,6 +25,7 @@ export const preloadHandlebarsTemplates = async function () {
"systems/bol/templates/item/parts/properties/item/protection-properties.hbs",
"systems/bol/templates/item/parts/properties/item/weapon-properties.hbs",
"systems/bol/templates/item/parts/properties/item/spell-properties.hbs",
"systems/bol/templates/item/parts/properties/item/alchemy-properties.hbs",
"systems/bol/templates/item/parts/properties/item/magical-properties.hbs",
"systems/bol/templates/item/parts/properties/feature/career-properties.hbs",
"systems/bol/templates/item/parts/properties/feature/boon-properties.hbs",
@ -33,6 +35,8 @@ export const preloadHandlebarsTemplates = async function () {
// DIALOGS
"systems/bol/templates/chat/rolls/attack-damage-card.hbs",
"systems/bol/templates/chat/rolls/spell-roll-card.hbs",
"systems/bol/templates/chat/rolls/alchemy-roll-card.hbs",
"systems/bol/templates/roll/parts/roll-dialog-modifiers.hbs",
"systems/bol/templates/roll/parts/roll-dialog-attribute.hbs",
"systems/bol/templates/dialogs/aptitude-roll-part.hbs",