import KHDiceRoller from "../helpers/dice-helper.js" import ActorHelpers from "../helpers/actor-helper.js"; /** * Extend the basic ActorSheet with some very simple modifications * @extends {ActorSheet} */ export class ActorSheetKH extends ActorSheet { khRoller = new KHDiceRoller(); /** @override */ static get defaultOptions() { if(game.settings.get("kopparhavet", "gameSystem") === "hjaltarnas-tid") { return mergeObject(super.defaultOptions, { classes: ["kopparhavet", "sheet", "actor"], template: "systems/kopparhavet/templates/actors/ht-character-sheet.html", width: 710, height: 650, tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "main" }], scrollY: [".skills-tab .skills", ".talent-tab .items"], }); } return mergeObject(super.defaultOptions, { classes: ["kopparhavet", "sheet", "actor"], template: "systems/kopparhavet/templates/actors/character-sheet.html", width: 710, height: 650, tabs: [{ navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "main" }], scrollY: [".skills-tab .skills", ".talent-tab .items"], }); } /** @override */ get template() { const path = "systems/kopparhavet/templates/actors"; let prefix = ""; if(game.settings.get("kopparhavet", "gameSystem") === "hjaltarnas-tid") { prefix = "ht-"; } return `${path}/${prefix}${this.actor.data.type}-sheet.html`; } /* -------------------------------------------- */ /** @override */ getData() { const data = super.getData(); data.dtypes = ["String", "Number", "Boolean"]; // Prepare items. this._prepareCharacterItems(data); return data; } /** * Organize and classify Items for Character sheets. * @param {Object} actorData The actor to prepare. * @return {undefined} */ _prepareCharacterItems(sheetData) { const actorData = sheetData.actor; // Initialize containers. const skills = []; const talents = []; const weapons = []; const armor = []; const gear = []; const spells = []; const attacks = []; const shipweapons = []; const shiptalents = []; // Iterate through items, allocating to containers for (let i of sheetData.items) { i.img = i.img || DEFAULT_TOKEN; // Append to gear. if (i.type === "skill") { i.hasBase = i.data.type.value === "base"; i.hasAdventure = i.data.type.value === "adventure"; i.hasCombat = i.data.type.value === "combat"; skills.push(i); } else if (i.type === "talent") { talents.push(i); } else if (i.type === "weapon") { weapons.push(i); } else if (i.type === "armor") { armor.push(i); } else if (i.type === "gear") { gear.push(i); } else if (i.type === "spell") { spells.push(i); } else if (i.type === "adversaryAttack") { attacks.push(i); } else if (i.type === "shiptalent") { shiptalents.push(i); } else if (i.type === "shipweapon") { shipweapons.push(i); } } skills.sort((a,b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); // Assign and return actorData.skills = skills; actorData.talents = talents; actorData.weapons = weapons; actorData.armor = armor; actorData.gear = gear; actorData.spells = spells; actorData.attacks = attacks; actorData.shiptalents = shiptalents; actorData.shipweapons = shipweapons; } /* -------------------------------------------- */ /** @override */ activateListeners(html) { super.activateListeners(html); /* Send item Details to chat */ new ContextMenu(html, "li.item", [ { name: game.i18n.localize("MENU.SENTTOCHAT"), icon: '', callback: (li) => { let itemId = li.data("itemId"); this._itemDetailsToChat(itemId); }, }, ]); new ContextMenu(html, "div.item", [ { name: game.i18n.localize("MENU.SENTTOCHAT"), icon: '', callback: (li) => { let itemId = li.data("itemId"); this._itemDetailsToChat(itemId); }, }, ]); new ContextMenu(html, "li.item-weapon", [ { name: game.i18n.localize("MENU.SHOWROLLDIALOG"), icon: '', callback: (li) => { let itemId = li.data("itemId"); let _item = this.actor.items.find((element) => element._id == itemId); // Retrieve skill based on name let skill = this.actor.items.find((element) => element.name === _item.data.data.skill.value); let skillName = skill.name let skillValue = skill.data.data.value let showValue = false if(this.actor.data.type === "character") { showValue = true } this.khRoller.rollSkillDialogInChat(skillName, skillValue, showValue, this.actor) }, }, { name: game.i18n.localize("MENU.SENTTOCHAT"), icon: '', callback: (li) => { let itemId = li.data("itemId"); this._itemDetailsToChat(itemId); }, }, ]); new ContextMenu(html, "li.item-skill", [ { name: game.i18n.localize("MENU.SHOWROLLDIALOG"), icon: '', callback: (li) => { let itemId = li.data("itemId"); let _item = this.actor.items.find((element) => element._id == itemId); let skillName = _item.name let skillValue = _item.data.data.value let showValue = false if(this.actor.data.type === "character") { showValue = true } this.khRoller.rollSkillDialogInChat(skillName, skillValue, showValue, this.actor) }, }, { name: game.i18n.localize("MENU.SENTTOCHAT"), icon: '', callback: (li) => { let itemId = li.data("itemId"); this._itemDetailsToChat(itemId); }, }, ]); new ContextMenu(html, "li.item-spell", [ { name: game.i18n.localize("MENU.SHOWROLLDIALOG"), icon: '', callback: (li) => { let itemId = li.data("itemId"); let _item = this.actor.items.find((element) => element._id == itemId); if (!_item) { _item = game.items.get(itemId); if (!_item) { console.log("IMPORT ERROR") return } } let showValue = false let difficulty = 0 if(this.actor.data.type === "character") { showValue = true } switch (_item.data.data.difficulty.value) { case "simple": difficulty = 5 break; case "easy": difficulty = 2 break; case "hard": difficulty = -2 break; case "daunting": difficulty = -5 break; } if(_item.data.data.roll.value === "roll" || _item.data.data.roll.value === "attackroll") { // Retrieve skill based on name let skill = this.actor.items.find((element) => element.name === _item.data.data.roll.skill); let skillName = _item.name let skillValue = skill.data.data.value if(this.actor.data.type === "character") { skillName = _item.name + " (" + skill.name + ")" } this.khRoller.rollSkillDialogInChat(skillName, skillValue, showValue, this.actor, difficulty) } else if(_item.data.data.roll.value === "opposite") { // Retrieve skill based on name let skill = this.actor.items.find((element) => element.name === _item.data.data.roll.skill); let skillName = _item.name let skillValue = skill.data.data.value if(this.actor.data.type === "character") { skillName = _item.name + " (" + skill.name + ")" } this.khRoller.rollSkillDialogInChat(skillName, skillValue, showValue, this.actor, difficulty) } else if(_item.data.data.roll.value === "ritual") { console.log("Not supported yet") this.khRoller.rollSkillDialogInChat("Ritual", -1, showValue, this.actor, difficulty) } }, }, { name: game.i18n.localize("MENU.SENTTOCHAT"), icon: '', callback: (li) => { let itemId = li.data("itemId"); this._itemDetailsToChat(itemId); }, }, ]); new ContextMenu(html, "li.item-attack", [ { name: game.i18n.localize("MENU.SHOWROLLDIALOG"), icon: '', callback: (li) => { let skillValue = li.data("ability"); let skillName = "ITEM.ATTACK"; let showValue = false if(this.actor.data.type === "character") { showValue = true } this.khRoller.rollSkillDialogInChat(skillName, skillValue, showValue, this.actor) }, }, { name: game.i18n.localize("MENU.SENTTOCHAT"), icon: '', callback: (li) => { let itemId = li.data("itemId"); this._itemDetailsToChat(itemId); }, }, ]); new ContextMenu(html, "li.item-defence", [ { name: game.i18n.localize("MENU.SHOWROLLDIALOG"), icon: '', callback: (li) => { const skillValue = li.data("defence"); let skillName = "ADVERSARY.DEFENCE"; this.khRoller.rollSkillDialogInChat(skillName, skillValue, false, this.actor) }, } ]); html.find(".feature").click(async (ev) => { const featureName = $(ev.currentTarget).data("feature"); const featureValue = this.actor.data.data.feature[featureName].value; if (featureName === "one") { this.actor.update({ "data.feature.one.value": !featureValue }); } else if (featureName === "two") { this.actor.update({ "data.feature.two.value": !featureValue }); } else if (featureName === "three") { this.actor.update({ "data.feature.three.value": !featureValue }); } else if (featureName === "four") { this.actor.update({ "data.feature.four.value": !featureValue }); } this._render(); }); // Delete Inventory Item html.find(".item-delete").click((ev) => { let parent = $(ev.currentTarget).data("parent") const li = $(ev.currentTarget).parents(parent); const item = this.actor.getOwnedItem(li.data("itemId")); if(item) { if (item.type === "armor") { let initValue = -1 if (item.data.data.equipable.equipped) { initValue = 4 this.actor.items.map((i) => { if (i.type === "armor") { if (i._id !== item._id && i.data.data.equipable.equipped && i.data?.data?.modifications) { for (let k of Object.keys(i.data.data.modifications)) { if (i.data.data.modifications[k].modtype === "init") { initValue = i.data.data.modifications[k].value; } } } } }); } if (initValue > 0) { this.actor.update({["data.combat.init"]: initValue}); } } } this.actor.deleteOwnedItem(li.data("itemId")); li.slideUp(200, () => this.render(false)); }); // Edit Inventory Item html.find(".item-edit").click(async (ev) => { let parent = $(ev.currentTarget).data("parent") let li = $(ev.currentTarget).parents(parent); let itemId = li.data("itemId"); let item = this.actor.getOwnedItem(itemId); if (!item) { item = game.items.get(itemId); if (!item) { console.log("IMPORT ERROR") } } if (item?.sheet) { item.sheet.render(true); } }); /* Roll spell cost */ html.find(".roll-spell-cost").click((ev) => { const li = $(ev.currentTarget).parents(".item-spell"); let itemId = li.data("itemId"); let spell = this.actor.getOwnedItem(itemId); if (!spell) { spell = game.items.get(itemId); if (!spell) { console.log("IMPORT ERROR") return } } let cost = spell.data.data.cost.value; let regex = /([0-9]*)t([0-9]*)/g; let regexMatch; while (regexMatch = regex.exec(cost.toLowerCase())) { this.khRoller.rollSpellCostInChat(regexMatch[1], this.actor) } }); html.find(".roll-spell-skill").click((ev) => { const li = $(ev.currentTarget).parents(".item-spell"); let itemId = li.data("itemId"); let spell = this.actor.getOwnedItem(itemId); if (!spell) { spell = game.items.get(itemId); if (!spell) { console.log("IMPORT ERROR") return } } let showValue = false let difficulty = 0 if(this.actor.data.type === "character") { showValue = true } switch (spell.data.data.difficulty.value) { case "simple": difficulty = 5 break; case "easy": difficulty = 2 break; case "hard": difficulty = -2 break; case "daunting": difficulty = -5 break; } if(spell.data.data.roll.value === "roll" || spell.data.data.roll.value === "attackroll") { // Retrieve skill based on name let skill = this.actor.items.find((element) => element.name === spell.data.data.roll.skill); let skillName = spell.name let skillValue = skill.data.data.value if(this.actor.data.type === "character") { skillName = spell.name + " (" + skill.name + ")" } this.khRoller.rollSkillInChat(skillName, skillValue, showValue, this.actor, difficulty) } else if(spell.data.data.roll.value === "opposite") { // Retrieve skill based on name let skill = this.actor.items.find((element) => element.name === spell.data.data.roll.skill); let skillName = spell.name let skillValue = skill.data.data.value if(this.actor.data.type === "character") { skillName = spell.name + " (" + skill.name + ")" } this.khRoller.rollSkillInChat(skillName, skillValue, showValue, this.actor, difficulty) } else if(spell.data.data.roll.value === "ritual") { console.log("Not supported yet") } }); /* Roll skill */ html.find(".roll-skill").click((ev) => { const li = $(ev.currentTarget).parents(".item-skill"); let itemId = li.data("itemId"); let _item = this.actor.items.find((element) => element._id == itemId); let skillName = _item.name let skillValue = _item.data.data.value let showValue = false if(this.actor.data.type === "character") { showValue = true } this.khRoller.rollSkillInChat(skillName, skillValue, showValue, this.actor) }); /* Roll weapon skill */ html.find(".roll-weapon-skill").click((ev) => { const li = $(ev.currentTarget).parents(".item-weapon"); let itemId = li.data("itemId"); let weapon = this.actor.getOwnedItem(itemId); if (!weapon) { weapon = game.items.get(itemId); if (!weapon) { console.log("IMPORT ERROR") return } } // Retrieve skill based on name let skill = this.actor.items.find((element) => element.name === weapon.data.data.skill.value); let skillName = skill.name let skillValue = skill.data.data.value let showValue = false if(this.actor.data.type === "character") { showValue = true } this.khRoller.rollSkillInChat(skillName, skillValue, showValue, this.actor) }); /* Roll weapon damage */ html.find(".roll-damage").click((ev) => { const li = $(ev.currentTarget).parents(".item-weapon"); let itemId = li.data("itemId"); let weapon = this.actor.getOwnedItem(itemId); if (!weapon) { weapon = game.items.get(itemId); if (!weapon) { console.log("IMPORT ERROR") return } } let damage = weapon.data.data.damage.value; let regex = /([0-9]*)t([0-9]*)/g; let regexMatch; while (regexMatch = regex.exec(damage.toLowerCase())) { this.khRoller.rollDamageInChat(regexMatch[1], this.actor) } }); /* Roll armor */ html.find(".roll-armor").click((ev) => { const li = $(ev.currentTarget).parents(".item"); let itemId = li.data("itemId"); let armor = this.actor.getOwnedItem(itemId); if (!armor) { armor = game.items.get(itemId); if (!armor) { console.log("IMPORT ERROR") return } } let defence = armor.data.data.defence.value; let hasHelmet = false; if(game.settings.get("kopparhavet", "gameSystem") === "kopparhavet") { this.actor.items.map((i) => { if (i.type === "armor") { if (i.data.data.equipable.equipped && i.data.data.helmet.value) { if (CONFIG.KH.armor_types[i.data.data.type.value]?.ac >= CONFIG.KH.armor_types[armor.data.data.type.value]?.ac) { hasHelmet = true } } } }); } let regex = /([0-9]*)t([0-9]*)/g; let regexMatch; while (regexMatch = regex.exec(defence.toLowerCase())) { this.khRoller.rollArmorInChat(regexMatch[1], hasHelmet, this.actor) } }); /* Toggle item equipped */ html.find(".items .item a.toggle-equipped").click(this._toggleEquippedItem.bind(this)); html.find(".items .item-weapon a.toggle-equipped").click(this._toggleEquippedItem.bind(this)); /* Handle increase of items in inventory */ html.find(".item-quantity .quantity.increase").click(this._increaseQuantity.bind((this))); /* Handle decrease of items in inventory */ html.find(".item-quantity .quantity.decrease").click(this._decreaseQuantity.bind(this)); /* CHANGE SKILL VALUE */ html.find(".skill-value").change(this._onChangeSkillValue.bind(this)); html.find(".click-skill").click(this._onClickSkill.bind(this)); /* Adversary specific */ html.find(".roll-adversary-attack").click((ev) => { const li = $(ev.currentTarget).parents(".item-attack"); let skillValue = li.data("ability"); let skillName = "ITEM.ATTACK"; this.khRoller.rollSkillInChat(skillName, skillValue, false, this.actor) }); html.find(".roll-attack-damage").click((ev) => { const li = $(ev.currentTarget).parents(".item-attack"); let itemId = li.data("itemId"); let weapon = this.actor.getOwnedItem(itemId); if (!weapon) { weapon = game.items.get(itemId); if (!weapon) { console.log("IMPORT ERROR") return } } let damage = weapon.data.data.damage.value; let regex = /([0-9]*)t([0-9]*)/g; let regexMatch; while (regexMatch = regex.exec(damage.toLowerCase())) { this.khRoller.rollDamageInChat(regexMatch[1], this.actor) } }); html.find(".roll-defence").click((ev) => { const skillValue = $(ev.currentTarget).data("defence"); let skillName = "ADVERSARY.DEFENCE"; this.khRoller.rollSkillInChat(skillName, skillValue, false, this.actor) }); html.find(".adversary-helmet-click").click((ev) => { ev.preventDefault(); let helmetValue = this.actor.data.data.combat.helmet; if(helmetValue === undefined || helmetValue === null) { helmetValue = false } this.actor.update({ "data.combat.helmet": !helmetValue }); }); html.find(".roll-adversary-armor").click((ev) => { const armor = $(ev.currentTarget).data("armor"); let hasHelmet = this.actor.data.data.combat.helmet; if(hasHelmet === undefined || hasHelmet === null) { hasHelmet = false; } let regex = /([0-9]*)t([0-9]*)/g; let regexMatch; while (regexMatch = regex.exec(armor.toLowerCase())) { this.khRoller.rollArmorInChat(regexMatch[1], hasHelmet, this.actor) } }); // Add or Remove relationship html.find(".learning-control").click(this._onClickLearningControl.bind(this)); html.find(".learning-click").click(this._onClickLearingLearned.bind(this)); /* HjÀltarnas Tid specefic */ if(game.settings.get("kopparhavet", "gameSystem") === "hjaltarnas-tid") { // Add or Remove relationship html.find(".relation-control").click(this._onClickRelationshipControl.bind(this)); html.find(".relation-click").click(this._onClickRelationshipUsed.bind(this)); } } async _onClickLearingLearned(event) { event.stopPropagation(); const li1 = $(event.currentTarget); const li2 = $(event.currentTarget).parents(".learning"); let box = li1.data("num") let learingKey = li2.data("attribute"); const clickedValue = (this.actor.data.data.learning[learingKey][box] == undefined ? false : this.actor.data.data.learning[learingKey][box]); let dataName = "data.learning." + learingKey + "." + box let tempData = {} tempData[dataName] = !clickedValue this.actor.update(tempData); this._render(); } async _onClickLearningControl(event) { event.preventDefault(); const a = event.currentTarget; const action = a.dataset.action; const attrs = this.object.data.data.learning; const form = this.form; // Add new modification if (action === "create") { const nk = new Date().getTime(); let newKey = document.createElement("div"); newKey.innerHTML = ``; form.appendChild(newKey); await this._onSubmit(event); } // Remove existing modification else if (action === "delete") { const li = a.closest(".learning"); li.parentElement.removeChild(li); await this._onSubmit(event); } } async _onClickRelationshipControl(event) { event.preventDefault(); const a = event.currentTarget; const action = a.dataset.action; const attrs = this.object.data.data.relationships; const form = this.form; // Add new modification if (action === "create") { const nk = new Date().getTime(); let newKey = document.createElement("div"); newKey.innerHTML = ``; form.appendChild(newKey); await this._onSubmit(event); } // Remove existing modification else if (action === "delete") { const li = a.closest(".relation"); li.parentElement.removeChild(li); await this._onSubmit(event); } } async _onClickRelationshipUsed(event) { event.stopPropagation(); const li = $(event.currentTarget).parents(".relation"); let relationshipKey = li.data("attribute"); const clickedValue = (this.actor.data.data.relationships[relationshipKey].check == undefined ? false : this.actor.data.data.relationships[relationshipKey].check); let dataName = "data.relationships." + relationshipKey + ".check" let tempData = {} tempData[dataName] = !clickedValue this.actor.update(tempData); this._render(); } async _toggleEquippedItem(event) { const li = $(event.currentTarget); const item = this.actor.getOwnedItem(li.data("itemId")); const actor = this.actor; if(item) { if(item.type === "armor") { let initValue = -1 if(item.data.data.equipable.equipped) { initValue = 4 actor.items.map((i) => { if(i.type === "armor") { if(i._id !== item._id && i.data.data.equipable.equipped && i.data?.data?.modifications) { for(let k of Object.keys(i.data.data.modifications)) { if(i.data.data.modifications[k].modtype === "init") { initValue = i.data.data.modifications[k].value; } } } } }); } else { if (item.data?.data?.modifications) { for(let k of Object.keys(item.data.data.modifications)) { if(item.data.data.modifications[k].modtype === "init") { initValue = item.data.data.modifications[k].value; } } } } if(initValue > 0) { actor.update({ ["data.combat.init"]: initValue }); } } item.update({ ["data.equipable.equipped"]: !item.data.data.equipable.equipped }); } else { console.log("Could not find item") } } async _increaseQuantity(event) { event.stopPropagation(); const li = $(event.currentTarget).parents(".item"); let itemId = li.data("itemId"); let item = this.actor.getOwnedItem(itemId); if (!item) { console.log("IMPORT ERROR") } item.update({ ["data.quantity.value"]: item.data.data.quantity.value + 1 }); } async _decreaseQuantity(event) { event.stopPropagation(); const li = $(event.currentTarget).parents(".item"); let itemId = li.data("itemId"); let item = this.actor.getOwnedItem(itemId); if (!item) { console.log("IMPORT ERROR") } let count = item.data.data.quantity.value - 1 > 0 ? item.data.data.quantity.value - 1 : 0; item.update({ ["data.quantity.value"]: count }); } async _onChangeSkillValue(event) { event.preventDefault(); const itemId = $(event.currentTarget).data("item-id"); let _item = this.actor.items.find((element) => element._id == itemId); if (_item) { let update = { _id: _item._id, data: { value: $(event.currentTarget).val() }, }; await this.actor.updateEmbeddedEntity("OwnedItem", update); } } async _onClickSkill(event) { event.preventDefault(); const itemId = $(event.currentTarget).data("item-id"); let _item = this.actor.items.find((element) => element._id == itemId); if (_item) { let newVal = true; if(_item.data.data.used !== undefined) { newVal = !_item.data.data.used; } let update = { _id: _item._id, data: { used: newVal }, }; await this.actor.updateEmbeddedEntity("OwnedItem", update); } } /** @override */ _updateObject(event, formData) { const actorUpdate = ActorHelpers.actorUpdate.bind(this); actorUpdate(event, formData); } /** * Send details of an item to chat. * @private */ async _itemDetailsToChat(itemId) { let item = this.actor.getOwnedItem(itemId); if (!item) { item = game.items.get(itemId); } if (!item) { console.log("IMPORT ERROR") return } const itemDetails = item?.getItemDetails(); const html = await renderTemplate("systems/kopparhavet/templates/chat/item-card.html", itemDetails); const messageData = { user: game.user._id, type: CONST.CHAT_MESSAGE_TYPES.OTHER, content: html, speaker: { actor: this.actor._id, token: this.actor.token, alias: this.actor.name, }, }; ChatMessage.create(messageData); } }