clean
parent
d832e9ab22
commit
ad5f2ccd85
Binary file not shown.
Binary file not shown.
|
@ -1,5 +1,4 @@
|
|||
import { OseActor } from "./entity.js";
|
||||
import { ActorTraitSelector } from "../apps/trait-selector.js";
|
||||
|
||||
/**
|
||||
* Extend the basic ActorSheet with some very simple modifications
|
||||
|
@ -40,7 +39,7 @@ export class OseActorSheetMonster extends ActorSheet {
|
|||
*/
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
data.config = CONFIG.MAJI;
|
||||
data.config = CONFIG.OSE;
|
||||
|
||||
// Prepare owned items
|
||||
this._prepareItems(data);
|
||||
|
@ -52,18 +51,6 @@ export class OseActorSheetMonster extends ActorSheet {
|
|||
* @private
|
||||
*/
|
||||
_prepareItems(data) {
|
||||
let [traits, techniques] = data.items.reduce(
|
||||
(arr, item) => {
|
||||
// Classify items into types
|
||||
if (item.type === "feature") arr[0].push(item);
|
||||
else if (item.type === "technique") arr[1].push(item);
|
||||
return arr;
|
||||
},
|
||||
[[], [], [], []]
|
||||
);
|
||||
// Assign and return
|
||||
data.traits = traits;
|
||||
data.techniques = techniques;
|
||||
}
|
||||
|
||||
_onItemSummary(event) {
|
||||
|
@ -91,70 +78,6 @@ export class OseActorSheetMonster extends ActorSheet {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle spawning the ActorTraitSelector application which allows a checkbox of multiple trait options
|
||||
* @param {Event} event The click event which originated the selection
|
||||
* @private
|
||||
*/
|
||||
_onTraitSelector(event) {
|
||||
event.preventDefault();
|
||||
const a = event.currentTarget;
|
||||
const options = {
|
||||
name: `data.${a.dataset.target}`,
|
||||
title: a.innerText,
|
||||
choices: CONFIG.MAJI[a.dataset.options],
|
||||
};
|
||||
new ActorTraitSelector(this.actor, options).render(true);
|
||||
}
|
||||
|
||||
_keyUpHandler(event) {
|
||||
if (event.keyCode == 17) {
|
||||
let icons = document.querySelectorAll(".monster .roll-empowerable");
|
||||
for (let i = 0; i < icons.length; i++) {
|
||||
let icon = icons[i].getElementsByTagName("img")[0];
|
||||
if (icon.getAttribute("src") == "/systems/majimonsters/assets/icons/dice/d6s.png") {
|
||||
icon.setAttribute("src", "/systems/majimonsters/assets/icons/dice/d8s.png");
|
||||
} else if (icon.getAttribute("src") == "/systems/majimonsters/assets/icons/dice/d.png"){
|
||||
icon.setAttribute("src", "/systems/majimonsters/assets/icons/dice/dp.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_keyDownHandler(event) {
|
||||
if (event.keyCode == 17) {
|
||||
let icons = document.querySelectorAll(".monster .roll-empowerable");
|
||||
for (let i = 0; i < icons.length; i++) {
|
||||
let icon = icons[i].getElementsByTagName("img")[0];
|
||||
if (icon.getAttribute("src") == "/systems/majimonsters/assets/icons/dice/d8s.png") {
|
||||
icon.setAttribute("src", "/systems/majimonsters/assets/icons/dice/d6s.png");
|
||||
} else if (icon.getAttribute("src") == "/systems/majimonsters/assets/icons/dice/dp.png"){
|
||||
icon.setAttribute("src", "/systems/majimonsters/assets/icons/dice/d.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onRollTechnique(event) {
|
||||
event.preventDefault();
|
||||
let itemId = event.currentTarget.parentElement.previousElementSibling.dataset.itemId;
|
||||
const technique = this.actor.getOwnedItem(itemId);
|
||||
return technique.roll({empowered: event.ctrlKey, type: event.currentTarget.dataset.rollType});
|
||||
}
|
||||
|
||||
_onRollStat(event) {
|
||||
event.preventDefault();
|
||||
let stat = event.currentTarget.parentElement.dataset.attribute;
|
||||
this.actor.rollStat(stat, { event: event, empowered: event.ctrlKey });
|
||||
}
|
||||
|
||||
_onShowCard(event) {
|
||||
event.preventDefault();
|
||||
let itemId = event.currentTarget.closest(".item").dataset.itemId;
|
||||
const technique = this.actor.getOwnedItem(itemId);
|
||||
return technique.roll({empowered: event.ctrlKey, type: event.currentTarget.dataset.rollType});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
@ -196,60 +119,7 @@ export class OseActorSheetMonster extends ActorSheet {
|
|||
this._onItemSummary(event);
|
||||
});
|
||||
|
||||
// Switch Gender
|
||||
html.find(".mm_gender").click((event) => {
|
||||
event.preventDefault();
|
||||
if (this.actor.data["data"].details.gender === "female") {
|
||||
this.actor.data["data"].details.gender = "male";
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
this.actor.data["data"].details.gender = "female";
|
||||
this.render();
|
||||
});
|
||||
|
||||
// Config modifier
|
||||
html.find(".mm_bullet").click((ev) => {
|
||||
event.preventDefault();
|
||||
let attribute = $(ev.currentTarget).siblings(".modifier");
|
||||
if (attribute.hasClass("hidden")) {
|
||||
attribute.removeClass("hidden");
|
||||
return;
|
||||
}
|
||||
attribute.addClass("hidden");
|
||||
});
|
||||
|
||||
// trait Selector
|
||||
html.find(".trait-selector").click(this._onTraitSelector.bind(this));
|
||||
|
||||
// Listen to event preventing duplicate bindings
|
||||
if (document.getElementsByClassName("monster").length == 1) {
|
||||
document.addEventListener("keydown", this._keyUpHandler);
|
||||
document.addEventListener("keyup", this._keyDownHandler);
|
||||
}
|
||||
|
||||
html.find(".roll-icon").click((event) => {
|
||||
if (event.currentTarget.classList.contains("roll-technique")) {
|
||||
this._onRollTechnique(event);
|
||||
return;
|
||||
}
|
||||
this._onRollStat(event);
|
||||
});
|
||||
|
||||
html.find(".item-show").click((event) => {
|
||||
this._onShowCard(event);
|
||||
});
|
||||
|
||||
// Handle default listeners last so system listeners are triggered first
|
||||
super.activateListeners(html);
|
||||
}
|
||||
|
||||
/** @override */
|
||||
async close(options) {
|
||||
if (document.getElementsByClassName("monster").length == 1) {
|
||||
document.removeEventListener("keydown", this._keyUpHandler);
|
||||
document.removeEventListener("keyup", this._keyDownHandler);
|
||||
}
|
||||
return super.close(options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
/**
|
||||
* A specialized form used to select damage or condition types which apply to an Actor
|
||||
* @type {FormApplication}
|
||||
*/
|
||||
export class ActorTraitSelector extends FormApplication {
|
||||
/** @override */
|
||||
static get defaultOptions() {
|
||||
return mergeObject(super.defaultOptions, {
|
||||
id: "trait-selector",
|
||||
classes: ["maji"],
|
||||
title: "Actor Trait Selection",
|
||||
template: "systems/ose/templates/apps/trait-selector.html",
|
||||
width: 320,
|
||||
height: "auto",
|
||||
choices: {},
|
||||
});
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Return a reference to the target attribute
|
||||
* @type {String}
|
||||
*/
|
||||
get attribute() {
|
||||
return this.options.name;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Provide data to the HTML template for rendering
|
||||
* @type {Object}
|
||||
*/
|
||||
getData() {
|
||||
// Get current values
|
||||
let attr = getProperty(this.object.data, this.attribute);
|
||||
// Populate choices
|
||||
const choices = duplicate(this.options.choices);
|
||||
for (let [k, v] of Object.entries(choices)) {
|
||||
choices[k] = {
|
||||
label: v,
|
||||
chosen: attr.includes(k),
|
||||
};
|
||||
}
|
||||
|
||||
// Return data
|
||||
return { choices: choices };
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Update the Actor object with new trait data processed from the form
|
||||
* @private
|
||||
*/
|
||||
_updateObject(event, formData) {
|
||||
const choices = [];
|
||||
for (let [k, v] of Object.entries(formData)) {
|
||||
if (v) {
|
||||
choices.push(k);
|
||||
}
|
||||
}
|
||||
this.object.update({[`${this.attribute}`]: choices});
|
||||
}
|
||||
}
|
|
@ -12,245 +12,4 @@ export class OseItem extends Item {
|
|||
prepareData() {
|
||||
super.prepareData();
|
||||
}
|
||||
|
||||
static async create(data, options = {}) {
|
||||
return super.create(data, options);
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
/** @override */
|
||||
async update(data, options = {}) {
|
||||
return super.update(data, options);
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
static chatListeners(html) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll the item to Chat
|
||||
* @return {Promise}
|
||||
*/
|
||||
async roll(options = {}) {
|
||||
if (options.type == "chat") {
|
||||
this.rollCard(options);
|
||||
return;
|
||||
}
|
||||
if (options.type == "attack") {
|
||||
this.rollAttack(options);
|
||||
return;
|
||||
}
|
||||
if (options.type == "damage") {
|
||||
this.rollDamage(options);
|
||||
return;
|
||||
}
|
||||
if (options.type == "trigger") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* Item Rolls - Attack, Damage, Saves, Checks */
|
||||
/* -------------------------------------------- */
|
||||
async rollCard(options = {}) {
|
||||
// Basic template rendering data
|
||||
const token = this.actor.token;
|
||||
const templateData = {
|
||||
actor: this.actor,
|
||||
tokenId: token ? `${token.scene._id}.${token.id}` : null,
|
||||
item: this.data,
|
||||
hasAttack: this.data.data.details.attack != "",
|
||||
isHealing: this.data.data.tags.descriptor == "healing",
|
||||
hasDamage: this.data.data.damage.die != 0,
|
||||
hasTrigger: this.data.data.trigger.threshold != 0,
|
||||
config: CONFIG.MAJI,
|
||||
};
|
||||
// Render the chat card
|
||||
const template = "systems/ose/templates/chat/technique-card.html";
|
||||
const html = await renderTemplate(template, templateData);
|
||||
|
||||
// Basic chat message data
|
||||
const chatData = {
|
||||
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,
|
||||
},
|
||||
};
|
||||
// Toggle default roll mode
|
||||
let rollMode = game.settings.get("core", "rollMode");
|
||||
if (["gmroll", "blindroll"].includes(rollMode))
|
||||
chatData["whisper"] = ChatMessage.getWhisperIDs("GM");
|
||||
if (rollMode === "blindroll") chatData["blind"] = true;
|
||||
|
||||
// Create the chat message
|
||||
return ChatMessage.create(chatData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Place an attack roll using an item (weapon, feat, spell, or equipment)
|
||||
* Rely upon the Dice5e.d20Roll logic for the core implementation
|
||||
*
|
||||
* @return {Promise.<Roll>} A Promise which resolves to the created Roll instance
|
||||
*/
|
||||
rollAttack(options = {}) {
|
||||
const itemData = this.data.data;
|
||||
const actorData = this.actor.data.data;
|
||||
if (this.type != "technique") return;
|
||||
const label = this.name;
|
||||
let parts = [];
|
||||
if (options.empowered) {
|
||||
parts.push("2d8");
|
||||
} else {
|
||||
parts.push("2d6");
|
||||
}
|
||||
parts.push(
|
||||
actorData.attributes[itemData.details.attack].value +
|
||||
actorData.attributes[itemData.details.attack].mod
|
||||
);
|
||||
|
||||
let rollMode = game.settings.get("core", "rollMode");
|
||||
let roll = new Roll(parts.join(" + "), {}).roll();
|
||||
roll.toMessage(
|
||||
{
|
||||
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
|
||||
flavor: `${label} ${game.i18n.localize("MAJI.technique.attack")}`,
|
||||
},
|
||||
{ rollMode }
|
||||
);
|
||||
return roll;
|
||||
}
|
||||
|
||||
rollDamage(options = {}) {
|
||||
const itemData = this.data.data;
|
||||
const actorData = this.actor.data.data;
|
||||
if (this.type != "technique") return;
|
||||
const label = this.name;
|
||||
let parts = [];
|
||||
if (itemData.damage.num && itemData.damage.die) {
|
||||
parts.push(`${itemData.damage.num}d${itemData.damage.die}`);
|
||||
}
|
||||
if (itemData.damage.bonus) {
|
||||
parts.push(
|
||||
actorData.attributes[itemData.damage.bonus].value +
|
||||
actorData.attributes[itemData.damage.bonus].mod
|
||||
);
|
||||
}
|
||||
if (options.empowered && itemData.damage.die) {
|
||||
parts.push(`1d${itemData.damage.die}`);
|
||||
}
|
||||
if (actorData.affinities.includes(itemData.element)) {
|
||||
parts.push(actorData.affinity.value + actorData.affinity.mod);
|
||||
}
|
||||
let rollMode = game.settings.get("core", "rollMode");
|
||||
let roll = new Roll(parts.join(" + "), {}).roll();
|
||||
roll.toMessage(
|
||||
{
|
||||
speaker: ChatMessage.getSpeaker({ actor: this.actor }),
|
||||
flavor: `${label} ${game.i18n.localize("MAJI.technique.damage")}`,
|
||||
},
|
||||
{ rollMode }
|
||||
);
|
||||
return roll;
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Handle toggling the visibility of chat card content when the name is clicked
|
||||
* @param {Event} event The originating click event
|
||||
* @private
|
||||
*/
|
||||
static _onChatCardToggleContent(event) {
|
||||
event.preventDefault();
|
||||
const header = event.currentTarget;
|
||||
const card = header.closest(".chat-card");
|
||||
const content = card.querySelector(".card-content");
|
||||
content.style.display = content.style.display === "none" ? "block" : "none";
|
||||
}
|
||||
|
||||
static async _onChatCardAction(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Extract card data
|
||||
const button = event.currentTarget;
|
||||
button.disabled = true;
|
||||
const card = button.closest(".chat-card");
|
||||
const messageId = card.closest(".message").dataset.messageId;
|
||||
const message = game.messages.get(messageId);
|
||||
const action = button.dataset.action;
|
||||
|
||||
// Validate permission to proceed with the roll
|
||||
const isTargetted = action === "trigger";
|
||||
if (!(isTargetted || game.user.isGM || message.isAuthor)) return;
|
||||
|
||||
// Get the Actor from a synthetic Token
|
||||
const actor = this._getChatCardActor(card);
|
||||
if (!actor) return;
|
||||
|
||||
// Get the Item
|
||||
const item = actor.getOwnedItem(card.dataset.itemId);
|
||||
|
||||
// Get card targets
|
||||
let targets = [];
|
||||
if (isTargetted) {
|
||||
targets = this._getChatCardTargets(card);
|
||||
if (!targets.length) {
|
||||
ui.notifications.warn(
|
||||
`You must have one or more controlled Tokens in order to use this option.`
|
||||
);
|
||||
return (button.disabled = false);
|
||||
}
|
||||
}
|
||||
|
||||
// Attack and Damage Rolls
|
||||
if (action === "attack") await item.rollAttack({ event });
|
||||
// Saving Throws for card targets
|
||||
else if (action === "trigger") {
|
||||
for (let t of targets) {
|
||||
await t.rollTrigger(
|
||||
{
|
||||
threshold: button.dataset.threshold,
|
||||
condition: button.dataset.condition,
|
||||
},
|
||||
{ event }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-enable the button
|
||||
button.disabled = false;
|
||||
}
|
||||
|
||||
static _getChatCardActor(card) {
|
||||
// Case 1 - a synthetic actor from a Token
|
||||
const tokenKey = card.dataset.tokenId;
|
||||
if (tokenKey) {
|
||||
const [sceneId, tokenId] = tokenKey.split(".");
|
||||
const scene = game.scenes.get(sceneId);
|
||||
if (!scene) return null;
|
||||
const tokenData = scene.getEmbeddedEntity("Token", tokenId);
|
||||
if (!tokenData) return null;
|
||||
const token = new Token(tokenData);
|
||||
return token.actor;
|
||||
}
|
||||
|
||||
// Case 2 - use Actor ID directory
|
||||
const actorId = card.dataset.actorId;
|
||||
return game.actors.get(actorId) || null;
|
||||
}
|
||||
|
||||
static _getChatCardTargets(card) {
|
||||
const character = game.user.character;
|
||||
const controlled = canvas.tokens.controlled;
|
||||
const targets = controlled.reduce(
|
||||
(arr, t) => (t.actor ? arr.concat([t.actor]) : arr),
|
||||
[]
|
||||
);
|
||||
if (character && controlled.length === 0) targets.push(character);
|
||||
return targets;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,23 +45,10 @@ export class OseItemSheet extends ItemSheet {
|
|||
*/
|
||||
getData() {
|
||||
const data = super.getData();
|
||||
data.config = CONFIG.MAJI;
|
||||
data.config = CONFIG.OSE;
|
||||
return data;
|
||||
}
|
||||
|
||||
_onDrop(event) {
|
||||
event.preventDefault();
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(event.dataTransfer.getData("text/plain"));
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
// Only handle Actor drops
|
||||
if (data.type !== "Actor") return;
|
||||
this.entity.update({ data: { monster: { id: data.id } } });
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
|
@ -69,30 +56,6 @@ export class OseItemSheet extends ItemSheet {
|
|||
* @param html {HTML} The prepared HTML object ready to be rendered into the DOM
|
||||
*/
|
||||
activateListeners(html) {
|
||||
if (this.item.type == "drajule") {
|
||||
const bar = html.find(".monster-drop");
|
||||
bar[0].ondrop = this._onDrop.bind(this);
|
||||
}
|
||||
|
||||
html.find(".entity").click(event => {
|
||||
event.preventDefault();
|
||||
const element = event.currentTarget;
|
||||
const entityId = element.dataset.entityId;
|
||||
const entity = game.actors.entities.find(f => f.id === entityId);
|
||||
const sheet = entity.sheet;
|
||||
if (sheet._minimized) return sheet.maximize();
|
||||
else return sheet.render(true);
|
||||
});
|
||||
super.activateListeners(html);
|
||||
}
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Implement the _updateObject method as required by the parent class spec
|
||||
* This defines how to update the subject of the form when the form is submitted
|
||||
* @private
|
||||
*/
|
||||
_updateObject(event, formData) {
|
||||
return this.object.update(formData);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
<form class="{{cssClass}}" autocomplete="off">
|
||||
<div class="flexrow mm_part1">
|
||||
{{> "systems/ose/templates/actors/partials/monster-header.html"}}
|
||||
</div>
|
||||
{{! Navigation }}
|
||||
<div class="flex flexrow">
|
||||
{{! Sheet Tab Navigation }}
|
||||
<nav class="sheet-tabs tabs" data-group="primary">
|
||||
<a class="item" data-tab="notes">
|
||||
<span>{{localize "MAJI.category.notes"}}</span>
|
||||
</a>
|
||||
<a class="item" data-tab="stats">
|
||||
<span>{{localize "MAJI.category.stats"}}</span>
|
||||
</a>
|
||||
<a class="item" data-tab="traits">
|
||||
<span>{{localize "MAJI.category.traits"}}</span>
|
||||
</a>
|
||||
<a class="item" data-tab="techniques">
|
||||
<span>{{localize "MAJI.category.techniques"}}</span>
|
||||
</a>
|
||||
</nav>
|
||||
<section class="sheet-body">
|
||||
<div class="mm_mmtabs tab" data-group="primary" data-tab="notes">
|
||||
{{editor content=data.details.biography target="data.details.biography"
|
||||
button=true owner=owner editable=editable}}
|
||||
</div>
|
||||
{{! Attributes }}
|
||||
<div class="mm_mmtabs tab" data-group="primary" data-tab="stats">
|
||||
{{> "systems/ose/templates/actors/partials/monster-attributes-tab.html"}}
|
||||
</div>
|
||||
<div class="mm_mmtabs tab" data-group="primary" data-tab="traits">
|
||||
{{> "systems/ose/templates/actors/partials/monster-traits-tab.html"}}
|
||||
</div>
|
||||
<div class="mm_mmtabs tab" data-group="primary" data-tab="techniques">
|
||||
{{> "systems/ose/templates/actors/partials/monster-techniques-tab.html"}}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</form>
|
Loading…
Reference in New Issue