Skip to content

Commit

Permalink
Merge pull request #142 from brantai/container-items
Browse files Browse the repository at this point in the history
Container items
  • Loading branch information
ClipplerBlood committed Nov 3, 2023
2 parents 0c43932 + f13e542 commit 839f4f8
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@
"DL.ItemShowInfo": "Item Info",
"DL.ItemRollText": "Item:",
"DL.ItemMaxUsesReached": "Maximum uses of the item reached",
"DL.ContentsTitle": "Contents",
"DL.WealthTitle": "Wealth",
"DL.WealthLifestyle": "Lifestyle: ",
"DL.WealthCoinsGC": "GC: ",
Expand Down
12 changes: 10 additions & 2 deletions src/module/item/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ export class DemonlordItem extends Item {
_onUpdate(changed, options, userId) {
super._onUpdate(changed, options, userId)
// Search for open path/ancestry/role sheets and re-render them. This allows the nested objects to fetch new values
if (!['path', 'ancestry', 'creaturerole'].includes(this.type)) {
if (!['path', 'ancestry', 'creaturerole', 'item'].includes(this.type)) {
// eslint-disable-next-line no-prototype-builtins
let openSheets = Object.entries(ui.windows).map(i => i[1]).filter(i => Item.prototype.isPrototypeOf(i.object))
openSheets = openSheets.filter(s => ['path', 'ancestry', 'creaturerole'].includes(s.object.type))
openSheets = openSheets.filter(s => ['path', 'ancestry', 'creaturerole', 'item'].includes(s.object.type))
openSheets.forEach(s => s.render())
}
}
Expand Down Expand Up @@ -72,4 +72,12 @@ export class DemonlordItem extends Item {
hasHealing() {
return this.system.healing?.healing ?? false
}

sameItem(item) {
const sources = [this.uuid, this._id]
if (this.flags?.core?.sourceId != undefined) sources.push(this.flags.core.sourceId)
const itemSources = [item.uuid, item._id]
if (item.flags?.core?.sourceId != undefined) itemSources.push(item.flags.core.sourceId)
return (sources.some(r=> itemSources.includes(r)))
}
}
6 changes: 6 additions & 0 deletions src/module/item/nested-objects.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,14 @@ export async function getNestedItemData(nestedData) {

// Remember user selection & enrich description
itemData.selected = nestedData.selected
itemData.system.enrichedDescription = await TextEditor.enrichHTML(itemData?.system?.description, {
aync: true})

itemData.system.enrichedDescriptionUnrolled = await enrichHTMLUnrolled(itemData?.system?.description)

// Keep the quantity previously stored, if any
itemData.system.quantity = nestedData?.system?.quantity ?? itemData.system.quantity

// Return only the data
// Warning: here the implicit assertion is that entity is an Item and not an Actor or something else
return itemData
Expand Down
180 changes: 177 additions & 3 deletions src/module/item/sheets/base-item-sheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import 'tippy.js/animations/shift-away.css';
import {initDlEditor} from "../../utils/editor";
import {DemonlordItem} from "../item";
import {enrichHTMLUnrolled, i18n} from "../../utils/utils";
import {
getNestedItemData,
getNestedDocument
} from '../nested-objects';

export default class DLBaseItemSheet extends ItemSheet {
/** @override */
Expand Down Expand Up @@ -75,6 +79,10 @@ export default class DLBaseItemSheet extends ItemSheet {

this.sectionStates = this.sectionStates || new Map()

if (data?.system?.contents != undefined && data.system.contents.length > 0) {
data.system.contents = await Promise.all(data.system.contents.map(getNestedItemData))
}

return data
}

Expand Down Expand Up @@ -231,20 +239,57 @@ export default class DLBaseItemSheet extends ItemSheet {
} else collapsableTitles.removeClass('active')
})

// Contents Quantity Button Info
html.find('.dlToggleInfoBtn').click(ev => {
const root = $(ev.currentTarget).closest('[data-item-id]')
const elem = $(ev.currentTarget)
const selector = '.fa-chevron-down, .fa-chevron-up'
const chevron = elem.is(selector) ? elem : elem.find(selector);
const elements = $(root).find('.dlInfo')
elements.each((_, el) => {
if (el.style.display === 'none') {
$(el).slideDown(100)
chevron?.removeClass('fa-chevron-up')
chevron?.addClass('fa-chevron-down')
} else {
$(el).slideUp(100)
chevron?.removeClass('fa-chevron-down')
chevron?.addClass('fa-chevron-up')
}
})
})

// Add drag events.
html
.find('.drop-area, .dl-drop-zone, .dl-drop-zone *')
.on('dragover', await this._onDragOver.bind(this))
.on('dragleave', await this._onDragLeave.bind(this))
.on('drop', await this._onDrop.bind(this))

// Create nested items by dropping onto item
this.form.ondrop = ev => this._onDropItem(ev);

// Custom editor
initDlEditor(html, this)

// Nested item create, edit
html.find('.create-nested-item').click(async (ev) => await this._onNestedItemCreate(ev))
html.find('.edit-nested-item').click(async (ev) => await this._onNestedItemEdit(ev))


// Contents item quantity and delete functions
html.on('mousedown', '.item-uses', async ev => await this._onUpdateContentsItemQuantity(ev))
html.find('.item-delete').click(async ev => await this._onContentsItemDelete(ev))

if (this.object.parent?.isOwner) {
const dragHandler = async ev => await this._onDrag(ev)
html.find('.dl-nested-item').each((i, li) => {
li.setAttribute('draggable', true)
li.addEventListener('dragstart', dragHandler, false)
li.addEventListener('dragend', dragHandler, false)
})
}

}

/* -------------------------------------------- */
Expand Down Expand Up @@ -282,6 +327,21 @@ export default class DLBaseItemSheet extends ItemSheet {

/* -------------------------------------------- */

async _onDrag(ev){
const itemIndex = $(ev.currentTarget).closest('[data-item-index]').data('itemIndex')
const data = await this.getData({})
if (ev.type == 'dragend') {
if (data.system.contents[itemIndex].system.quantity <= 1) {
await this.deleteContentsItem(itemIndex)
} else {
await this.decreaseContentsItemQuantity(itemIndex)
}
} else if (ev.type == 'dragstart') {
const dragData = { type: 'Item', 'uuid': data.system.contents[itemIndex].uuid }
ev.dataTransfer.setData("text/plain", JSON.stringify(dragData));
}
}

_onDragOver(ev) {
$(ev.originalEvent.target).addClass('drop-hover')
}
Expand All @@ -294,6 +354,52 @@ export default class DLBaseItemSheet extends ItemSheet {
$(ev.originalEvent.target).removeClass('drop-hover')
}

async _onDropItem(ev) {
if (this.item.system?.contents != undefined){
try {
const itemData = JSON.parse(ev.dataTransfer.getData('text/plain'))
if (itemData.type === 'Item') {
let actor
const item = await fromUuid(itemData.uuid)
if (!item || !['ammo', 'armor', 'item', 'weapon'].includes(item.type)) return
const itemUpdate = {'_id': item._id}
if (itemData.uuid.startsWith('Actor.')) {
actor = item.parent
/*Since item contents aren't actually embedded we don't want to end up with a reference
to a non-existant item. If our item exists outside of the actor-embedded version, use that as our source.
Otherwise, create it as a world-level item and update the original's core.sourceId flag accordingly.*/
if (item.flags?.core?.sourceId != undefined) {
game.items.getName(item.name) ? itemData.uuid = game.items.getName(item.name).uuid : itemData.uuid = item.flags.core.sourceId
} else {
const newItem = await this.createNestedItem(duplicate(item), `${actor.name}'s Items`)
itemUpdate['flags.core.sourceId'] = newItem.uuid;
itemData.uuid = newItem.uuid
}
}
if (itemUpdate?.flags?.core?.sourceId == undefined) itemUpdate['flags.core.sourceId'] = itemData.uuid

//If the item we're adding is the same as the container, bail now
if (this.item.sameItem(item)) {
ui.notifications.warn("Can't put an item inside itself!")
return
}

//If the item was in an Actor's inventory, update the quantity there
if (actor != undefined) {
if (item.system.quantity > 0) {
itemUpdate['system.quantity'] = item.system.quantity-1;
await actor.updateEmbeddedDocuments('Item', [itemUpdate])
}
}

await this.addContentsItem(itemData)
}
} catch (e) {
console.warn(e)
}
}
}

async _onNestedItemCreate(ev) {
const type = $(ev.currentTarget).closest('[data-type]').data('type')

Expand All @@ -305,19 +411,87 @@ export default class DLBaseItemSheet extends ItemSheet {
folder = await Folder.create({name:folderName, type: DemonlordItem.documentName})
}

const item = await DemonlordItem.create({
const item = {
name: `New ${type.capitalize()}`,
type: type,
folder: folder.id,
data: {},
})
}

await this.createNestedItem(item, folderName)
item.sheet.render(true)
this.render()
return item
}

// eslint-disable-next-line no-unused-vars
_onNestedItemEdit(ev) {
async _onNestedItemEdit(ev) {
const data = await this.getData({})
if (!data.item.system.contents) {
return
}
const itemId = $(ev.currentTarget).closest('[data-item-id]').data('itemId')
const nestedData = data.system.contents.find(i => i._id === itemId)
await getNestedDocument(nestedData).then(d => {
if (d.sheet) d.sheet.render(true)
else ui.notifications.warn('The item is not present in the game and cannot be edited.')
})
}

async _onUpdateContentsItemQuantity(ev) {
const itemIndex = $(ev.currentTarget).closest('[data-item-index]').data('itemIndex')

if (ev.button == 0) {
await this.increaseContentsItemQuantity(itemIndex)
} else if (ev.button == 2) {
await this.decreaseContentsItemQuantity(itemIndex)
}
}

async _onContentsItemDelete(ev) {
const itemIndex = $(ev.currentTarget).closest('[data-item-index]').data('itemIndex')
await this.deleteContentsItem(itemIndex)

}

async createNestedItem(itemData, folderName) {
let folder = game.folders.find(f => f.name === folderName)
if (!folder) {
folder = await Folder.create({name:folderName, type: DemonlordItem.documentName})
}
if (itemData?._id != undefined) delete itemData._id;
itemData.folder = folder._id

return await DemonlordItem.create(itemData)
}

async addContentsItem(data) {
const item = await getNestedItemData(data)
const containerData = duplicate(this.item)
containerData.system.contents.push(item)
await this.item.update(containerData, {diff: false}).then(_ => this.render)
}

async increaseContentsItemQuantity(itemIndex) {
const itemData = duplicate(this.item)
itemData.system.contents[itemIndex].system.quantity++
await this.item.update(itemData, {diff: false}).then(_ => this.render)
}

async decreaseContentsItemQuantity(itemIndex) {
const itemData = duplicate(this.item)
if (itemData.system.contents[itemIndex].system.quantity > 0) {
itemData.system.contents[itemIndex].system.quantity--
await this.item.update(itemData, {diff: false}).then(_ => this.render)
} else {
return
}
}

async deleteContentsItem(itemIndex) {
const itemData = duplicate(this.item)

itemData.system.contents.splice(itemIndex, 1)
await this.item.update(itemData)
}
}
1 change: 1 addition & 0 deletions src/module/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const preloadHandlebarsTemplates = async function () {

// Item Sheet Partials
'systems/demonlord/templates/item/partial/item-activation.hbs',
'systems/demonlord/templates/item/partial/item-contents.hbs',
'systems/demonlord/templates/item/partial/item-description.hbs',
'systems/demonlord/templates/item/partial/item-effects.hbs',
'systems/demonlord/templates/item/partial/item-sheet-header.hbs',
Expand Down
20 changes: 20 additions & 0 deletions src/styles/newrules/item-sheet.scss
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,26 @@
font-size: $f-size-big-slight;
}

.dl-item-section-contents {
& > div > b {
display: inline-block;
font-family: $font-primary;
font-weight: bolder;
margin-bottom: 4px;
}

& > .dl-item-row-header {
display: grid;
grid-template-columns: repeat(12, 1fr);
grid-gap: 16px;
}

margin: 4px 0;
padding: 0 16px;
font-family: $font-primary;
font-size: $f-size-big-slight;
}

a.create-nested-item {
margin-right: 7px;
}
Expand Down
5 changes: 4 additions & 1 deletion src/template.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,10 +238,13 @@
"max": 0
}
}
},
"container": {
"contents": []
}
},
"item": {
"templates": ["base", "action", "enchantment"],
"templates": ["base", "action", "enchantment", "container"],
"quantity": 1,
"availability": "",
"value": "",
Expand Down
2 changes: 2 additions & 0 deletions src/templates/item/item-item-sheet.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
<hr>
{{/if}}

{{#if system.contents}}{{>"systems/demonlord/templates/item/partial/item-contents.hbs"}}{{/if}}

<div class="dl-item-section-section margin">
<div>
<b>{{localize "DL.Availability"}}</b>
Expand Down
44 changes: 44 additions & 0 deletions src/templates/item/partial/item-contents.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!--div class="tab" data-group="primary" data-tab="contents"-->
<div class="dl-item-section-contents">
<div><b>{{localize "DL.ContentsTitle"}}</b></div>
<div class="dl-text-header-upper dl-underline-red dl-item-row-header">
<div class="col-7">{{localize "DL.TabsItems"}}</div>
<div class="col-2">{{localize "DL.ItemAmount"}}</div>
<div class="col-2">{{localize "DL.ItemValue"}}</div>
</div>
{{#each item.system.contents as |content index|}}
<div class="dl-item-row dl-nested-item dropitem" data-item-index="{{index}}" data-item-id="{{content._id}}">
<div class="row">
<div class="col-7">
<img class='dl-clickable-nored edit-nested-item' src="{{content.img}}" title="{{content.name}}" width="32"
height="32"/>
<label class="dl-clickable-nored item-roll">{{content.name}}</label>
<div class="dl-clickable dlToggleInfoBtn">
<i class="fas fa-angle-right"></i> {{localize "DL.ItemShowInfo"}}
</div>
</div>
<div class="col-2 item-uses">
{{content.system.quantity}} <i class="fas fa-arrows-alt-v"></i>
</div>
<div class="col-2">
{{defaultValue system.value ""}}
</div>
<div class="col-1 dl-clickable" style="padding-top: 3px">
<a class="dl-clickable item-delete" title="{{localize 'DL.ItemDeleteItem'}}"
style="margin-left: 2px; margin-top: 3px;">
<i class="fas fa-times"></i>
</a>
</div>
</div>
{{#if content.system.description}}
<div class="row dlInfo" style="display: none; text-align: left">
<div class="col-12" style="padding: 2px 4px 4px;">
{{{content.system.enrichedDescription}}}
</div>
</div>
{{/if}}
</div>
{{/each}}
</div>
<hr>
<!--/div-->

0 comments on commit 839f4f8

Please sign in to comment.