Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/assets/ui/consumables/incantation.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@
"DL.ConsumableType": "Consumable Type",
"DL.ConsumableTypeD": "Drink",
"DL.ConsumableTypeF": "Food",
"DL.ConsumableTypeI": "Incantation",
"DL.ConsumableTypeP": "Potion",
"DL.ConsumableTypeT": "Trinket",
"DL.ConsumableTypeV": "Poison",
Expand Down
22 changes: 16 additions & 6 deletions src/module/actor/actor.js
Original file line number Diff line number Diff line change
Expand Up @@ -864,14 +864,24 @@ export class DemonlordActor extends Actor {
await item.update({'system.quantity': --item.system.quantity}, {parent: this})
}

if (item.system?.action?.attack) {
launchRollDialog(game.i18n.localize('DL.ItemVSRoll') + game.i18n.localize(item.name), async (event, html) =>
await this.useItem(item, html.form.elements.boonsbanes.value, html.form.elements.modifier.value),
)
// Incantations
if (item.system.consumabletype === 'I' && item.system.contents.length === 1) {
item.system.contents[0].name = `${game.i18n.localize('DL.ConsumableTypeI')}: ${item.system.contents[0].name}`
let spell = new Item(item.system.contents[0])
let tempSpell = await this.createEmbeddedDocuments('Item', [spell])
// Before deleting the spell we store the uuid of the spell in the chat card for later use
const updateData = {[`flags.${game.system.id}.incantationSpellUuid`]: item.system.contents[0]._stats.compendiumSource};
await tempSpell[0].update(updateData);
await this.rollSpell(tempSpell[0].id)
await this.deleteEmbeddedDocuments('Item', [tempSpell[0].id])
} else {
await this.useItem(item, 0, 0)
if (item.system?.action?.attack) {
launchRollDialog(game.i18n.localize('DL.ItemVSRoll') + game.i18n.localize(item.name), async (event, html) =>
await this.useItem(item, html.form.elements.boonsbanes.value, html.form.elements.modifier.value), )
} else {
await this.useItem(item, 0, 0)
}
}

if (deleteItem) await item.delete()

}
Expand Down
78 changes: 76 additions & 2 deletions src/module/actor/sheets/base-actor-sheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -714,16 +714,18 @@ export default class DLBaseActorSheet extends HandlebarsApplicationMixin(ActorSh
/** @override */
async _onDropItem(ev, _item) {
try {
const item = _item
const incantation = this.tabGroups.primary === 'inventory' && _item.type === 'spell' ? true : false
const item = incantation ? await this.createIncantation(_item) : _item

// TODO: If item (by ID) exists in this object, ignore
if (this.actor.items.has(_item.id)) return

const isAllowed = await this.checkDroppedItem(_item)
if (isAllowed) {
const data = foundry.utils.duplicate(item);
this.actor.createEmbeddedDocuments('Item', [data])
let dropItem = await this.actor.createEmbeddedDocuments('Item', [data])
await this.postDropItemCreate(data)
if (incantation) await this.embedSpell(dropItem[0], foundry.utils.duplicate(_item))
} else {
console.warn('Wrong item type dragged', this.document, item)
}
Expand All @@ -739,4 +741,76 @@ export default class DLBaseActorSheet extends HandlebarsApplicationMixin(ActorSh
async postDropItemCreate(_itemData) {
return true
}

async embedSpell(incantation, spell) {
const containerData = foundry.utils.duplicate(incantation)
containerData.system.contents.push(spell)
await incantation.update(containerData, {diff: false})
}

async createIncantation(_itemData) {
let availability
let value
switch (_itemData.system.rank) {
case 0:
availability = 'U'
value = '1 ss'
break
case 1:
availability = 'U'
value = '5 ss'
break
case 2:
availability = 'R'
value = '1 gc'
break
case 3:
availability = 'R'
value = '5 gc'
break
case 4:
availability = 'E'
value = '10 gc'
break
case 5:
availability = 'E'
value = '50 gc'
break
case 6:
availability = 'E'
value = '100 gc'
break
case 7:
availability = 'E'
value = '250 gc'
break
case 8:
availability = 'E'
value = '500 gc'
break
case 9:
availability = 'E'
value = '1000 gc'
break
case 10:
availability = 'E'
value = '5000 gc'
break
}

let incantation = new Item({
name: `${game.i18n.localize('DL.ConsumableTypeI')}: ${_itemData.name}`,
type: 'item',
system: {
consumabletype: 'I',
description : _itemData.system.description,
enrichedDescription : _itemData.system.enrichedDescription,
enrichedDescriptionUnrolled : _itemData.system.enrichedDescriptionUnrolled,
availability : availability,
value : value,
},
img: _itemData.img,
})
return incantation
}
}
8 changes: 8 additions & 0 deletions src/module/chat/roll-messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,12 @@ export async function postSpellToChat(actor, spell, attackRoll, target, inputBoo
data['damageType'] = spellData.action?.damagetype
data['damageTypes'] = spellData.action?.damagetypes
data['damageExtra20PlusFormula'] = spellData.action?.plus20damage + extraDamage20Plus
// Incantation (effects) - We replace the uuid of the Scene.Actor.SpellItem.effect with the uuid of the SpellItem.effect as we already deleted the spell from the actor.
if (spell.getFlag('demonlord','incantationSpellUuid')) {
[...spell.effects].forEach((effect) => {
effect.incantationspelluuid = `${spell.getFlag('demonlord','incantationSpellUuid')}.ActiveEffect.${effect._id}`
})
}
data['itemEffects'] = spell.effects
}
data['description'] = spellData.description
Expand Down Expand Up @@ -435,6 +441,8 @@ export async function postSpellToChat(actor, spell, attackRoll, target, inputBoo
data['ifBlindedRoll'] = rollMode === 'blindroll'
data['hasAreaTarget'] = spellData?.activatedEffect?.target?.type in CONFIG.DL.actionAreaShape
data['actorInfo'] = buildActorInfo(actor)
// Incantation (measured templates) - We replace the uuid of the Scene.Actor.SpellItem with the uuid of the SpellItem as we already deleted the spell from the actor.
data['incantationspelluuid'] = spell.getFlag('demonlord','incantationSpellUuid')

const chatData = getChatBaseData(actor, rollMode)
if (attackRoll) {
Expand Down
6 changes: 3 additions & 3 deletions src/module/combat/combat.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class DLCombat extends Combat {
await this.rollInitiativeStandard(ids, options)
return this
case "i":
await this.rollInitiativeInduvidual(ids, options)
await this.rollInitiativeIndividual(ids, options)
return this
case "h":
await this.rollInitiativeGroup(ids, options)
Expand All @@ -30,8 +30,8 @@ export class DLCombat extends Combat {
}

// eslint-disable-next-line no-unused-vars
async rollInitiativeInduvidual(ids, {formula = null, updateTurn = true, messageOptions = {}} = {}) {
console.log("Calling rollInitiativeInduvidual with", ids, formula, updateTurn, messageOptions)
async rollInitiativeIndividual(ids, {formula = null, updateTurn = true, messageOptions = {}} = {}) {
console.log("Calling rollInitiativeIndividual with", ids, formula, updateTurn, messageOptions)
// Structure input data
ids = typeof ids === 'string' ? [ids] : ids
const combatantUpdates = []
Expand Down
5 changes: 3 additions & 2 deletions src/module/compendium-browser/compendium-browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,9 +348,10 @@ export default class DLCompendiumBrowser extends HandlebarsApplicationMixin(Appl
}

consumableTypeOptions = {
'None': game.i18n.localize('DL.ConsumableNone'),
'': game.i18n.localize('DL.ConsumableNone'),
'D': game.i18n.localize('DL.ConsumableTypeD'),
'F': game.i18n.localize('DL.ConsumableTypeF'),
'I': game.i18n.localize('DL.ConsumableTypeI'),
'P': game.i18n.localize('DL.ConsumableTypeP'),
'T': game.i18n.localize('DL.ConsumableTypeT'),
'V': game.i18n.localize('DL.ConsumableTypeV'),
Expand Down Expand Up @@ -751,7 +752,7 @@ export default class DLCompendiumBrowser extends HandlebarsApplicationMixin(Appl
case 'item':
results = results.filter(e => {
if (filters?.item?.availability && e.system.availability !== filters.item.availability) return false
if (filters?.item?.consumableType && e.system.consumableType !== filters.item.consumableType) return false
if (e.system.consumabletype !== filters.item.consumableType) return false

return true
})
Expand Down
15 changes: 14 additions & 1 deletion src/module/demonlord.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {registerHandlebarsHelpers} from "./utils/handlebars-helpers"
import {_onUpdateWorldTime, DLCombat} from "./combat/combat" // optional for styling
import { activateSocketListener } from "./utils/socket.js"
import DLCompendiumBrowser from './compendium-browser/compendium-browser.js'
import TokenRulerDemonLord from "./utils/token-ruler.js"

const { Actors, Items } = foundry.documents.collections //eslint-disable-line no-shadow
const { ActorSheet, ItemSheet } = foundry.appv1.sheets //eslint-disable-line no-shadow
Expand Down Expand Up @@ -158,6 +159,18 @@ Hooks.once('init', async function () {
Babele.get().setSystemTranslationsDir('packs/translations')
}
activateSocketListener()
// Token Ruler
if (game.settings.get('demonlord', 'integrateTokenRuler')) {
delete CONFIG.Token.movement.actions.blink
delete CONFIG.Token.movement.actions.jump
delete CONFIG.Token.movement.actions.burrow
delete CONFIG.Token.movement.actions.climb.getCostFunction
delete CONFIG.Token.movement.actions.crawl.getCostFunction
delete CONFIG.Token.movement.actions.fly.getCostFunction
delete CONFIG.Token.movement.actions.swim.getCostFunction
CONFIG.Token.movement.actions.fly.canSelect = token => token?.actor?.system.canFly
CONFIG.Token.rulerClass = TokenRulerDemonLord
}
})

Hooks.once('ready', async function () {
Expand Down Expand Up @@ -613,6 +626,6 @@ Hooks.on('DL.ApplyHealing', data => {

Hooks.on('DL.Action', async () => {
if (!game.settings.get('demonlord', 'templateAutoRemove')) return
const actionTemplates = canvas.scene.templates.filter(a => a.flags.actionTemplate).map(a => a.id)
const actionTemplates = canvas.scene.templates.filter(a => a.flags.demonlord.actionTemplate).map(a => a.id)
if (actionTemplates.length > 0) await canvas.scene.deleteEmbeddedDocuments('MeasuredTemplate', actionTemplates)
})
5 changes: 3 additions & 2 deletions src/module/item/sheets/base-item-sheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -1064,7 +1064,8 @@ export default class DLBaseItemSheet extends HandlebarsApplicationMixin(ItemShee
// Filter drops depending on the item type
switch (this.document.type) {
case 'item':
acceptedItemTypes = ['ammo', 'armor', 'item', 'weapon']
if (this.document.system.consumabletype === '') acceptedItemTypes = ['ammo', 'armor', 'item', 'weapon']
else if (this.document.system.consumabletype === 'I') acceptedItemTypes = ['spell']
break
case 'relic':
acceptedItemTypes = ['talent']
Expand All @@ -1074,7 +1075,7 @@ export default class DLBaseItemSheet extends HandlebarsApplicationMixin(ItemShee
break
}

if (!item && !acceptedItemTypes.includes(item.type)) return
if (!item || !acceptedItemTypes.includes(item.type)) return

const itemUpdate = {'_id': item._id}
if (itemData.uuid.startsWith('Actor.')) {
Expand Down
3 changes: 2 additions & 1 deletion src/module/pixi/action-template.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ export class ActionTemplate extends foundry.canvas.placeables.MeasuredTemplate {
canvas.app.view.oncontextmenu = null
canvas.app.view.onwheel = null
initialLayer.activate()
this.actorSheet.maximize()
// Incantations: Measured template does not have parent actor.
if (this.actorSheet) this.actorSheet.maximize()
}

// Confirm the workflow (left-click)
Expand Down
15 changes: 3 additions & 12 deletions src/module/utils/handlebars-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ export function registerHandlebarsHelpers() {
Handlebars.registerHelper('dlConsumableDropdown', (groupName, checkedKey) => _buildConsumableDropdownItem(groupName, checkedKey))
Handlebars.registerHelper('dlAmmoDropdown', (groupName, checkedKey, weapon) => _buildAmmoDropdownItem(groupName, checkedKey, weapon))
Handlebars.registerHelper('dlCheckItemOnActor', (data) => _CheckItemOnActor(data))
Handlebars.registerHelper('dlCheckCharacteristicsIsNull', (actorData) => _CheckCharacteristicsIsNull(actorData))
Handlebars.registerHelper('dlIsNestedItem', (item) => _IsNestedItem(item))
Handlebars.registerHelper('dlGetNestedItemSource', (item) => _GetNestedItemSource(item))

Expand Down Expand Up @@ -91,7 +90,7 @@ function _getAttributes(groupName) {
} else if (groupName === 'system.requirement.attribute') {
attributes = ['', 'strength', 'agility', 'intellect', 'will', 'perception']
} else if (groupName === 'system.consumabletype') {
attributes = ['', 'D', 'F', 'P', 'V', 'T']
attributes = ['', 'D', 'F', 'I', 'P', 'V', 'T']
} else if (groupName === 'level.attributeSelect') {
attributes = ['', 'choosetwo', 'choosethree', 'fixed', 'twosets']
}
Expand Down Expand Up @@ -148,14 +147,6 @@ function _GetNestedItemSource(item) {

}

function _CheckCharacteristicsIsNull(actorData) {
if (actorData === null) {
return true
} else {
return false
}
}

function _CheckItemOnActor(data) {
if (data.indexOf('Actor.') === -1) {
return false
Expand Down Expand Up @@ -470,7 +461,7 @@ function _buildAvailabilityDropdownItem(groupName, checkedKey) {


function _buildConsumableDropdownItem(groupName, checkedKey) {
const attributes = ['', 'D', 'F', 'P', 'V', 'T']
const attributes = ['', 'D', 'F', 'I', 'P', 'V', 'T']
for (let attribute of attributes) {
if (checkedKey != attribute) continue
const label = !attribute ? i18n('DL.ConsumableNone') : i18n(`DL.ConsumableType${attribute}`)
Expand Down Expand Up @@ -655,7 +646,7 @@ function _buildDropdownWithValue(groupName, checkedKey, valueName, valueKey) {
}

function _buildConsumableDropdown(groupName, checkedKey) {
const attributes = ['', 'D', 'F', 'P', 'V', 'T']
const attributes = ['', 'D', 'F', 'I', 'P', 'V', 'T']
for (let attribute of attributes) {
if (checkedKey != attribute) continue
const label = !attribute ? i18n('DL.ConsumableNone') : i18n(`DL.ConsumableType${attribute}`)
Expand Down
26 changes: 11 additions & 15 deletions src/module/utils/token-ruler.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

export default class TokenRulerDemonLord extends foundry.canvas.placeables.tokens.TokenRuler {
static STYLES = {
move: { color: 0x5bcc28 },
run: { color: 0xffcc00 },
exceed: { color: 0xb22222 },
move: { color: 0x33bc4e, alpha: 0.5},
run: { color: 0xf1d836, alpha: 0.5 },
exceed: { color: 0xe72124, alpha: 0.5 },
}

/** @override */
Expand All @@ -20,7 +20,7 @@ export default class TokenRulerDemonLord extends foundry.canvas.placeables.token

const cost = waypoint.measurement.cost

if (cost === 0) return style
if (cost === 0) return this.constructor.STYLES.move
if (maxMovement < cost) return this.constructor.STYLES.exceed

// 2-step gradient
Expand All @@ -33,18 +33,14 @@ export default class TokenRulerDemonLord extends foundry.canvas.placeables.token
/** @override */
_getSegmentStyle(waypoint) {
const scale = canvas.dimensions.uiScale
if (canvas.scene.grid.type != 0) {
return { width: 0 }
} else {
const movement = this.token.actor.system.ranges[waypoint.action] || 0
const maxMovement = typeof movement === 'object' ? movement[1] : movement
const cost = waypoint.measurement.cost
const movement = this.token.actor.system.ranges[waypoint.action] || 0
const maxMovement = typeof movement === 'object' ? movement[1] : movement
const cost = waypoint.measurement.cost

let color = this.constructor.STYLES.move.color
if (movement[0] < cost && cost <= maxMovement) color = this.constructor.STYLES.run.color
else if (maxMovement < cost) color = this.constructor.STYLES.exceed.color
return { width: 4 * scale, color: color, alpha: 0.5 }
}
let color = this.constructor.STYLES.move.color
if (movement[0] < cost && cost <= maxMovement) color = this.constructor.STYLES.run.color
else if (maxMovement < cost) color = this.constructor.STYLES.exceed.color
return { width: 4 * scale, color: color}
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/styles/v2/_sheets.scss
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,10 @@ span.vertical-rule {
@include demonlord-icon('../assets/ui/consumables/food.svg');
}

.icon-consumable-I {
@include demonlord-icon('../assets/ui/consumables/incantation.svg');
}

.icon-consumable-P {
@include demonlord-icon('../assets/ui/consumables/potion.svg');
}
Expand Down
Loading