mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 11:41:08 +01:00
parent
cf571aa2a5
commit
bcedc74bf3
16 changed files with 57 additions and 156 deletions
|
|
@ -2,7 +2,6 @@ export { default as BeastformDialog } from './beastformDialog.mjs';
|
||||||
export { default as d20RollDialog } from './d20RollDialog.mjs';
|
export { default as d20RollDialog } from './d20RollDialog.mjs';
|
||||||
export { default as DamageDialog } from './damageDialog.mjs';
|
export { default as DamageDialog } from './damageDialog.mjs';
|
||||||
export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
|
export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
|
||||||
export { default as DamageSelectionDialog } from './damageSelectionDialog.mjs';
|
|
||||||
export { default as DeathMove } from './deathMove.mjs';
|
export { default as DeathMove } from './deathMove.mjs';
|
||||||
export { default as Downtime } from './downtime.mjs';
|
export { default as Downtime } from './downtime.mjs';
|
||||||
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,128 +0,0 @@
|
||||||
// TO DELETE ?
|
|
||||||
|
|
||||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
|
||||||
|
|
||||||
export default class DamageSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
|
||||||
constructor(rollString, bonusDamage, resolve, hope = 0) {
|
|
||||||
super({});
|
|
||||||
|
|
||||||
this.data = {
|
|
||||||
rollString,
|
|
||||||
bonusDamage: bonusDamage.reduce((acc, x) => {
|
|
||||||
if (x.appliesOn === CONFIG.DH.EFFECTS.applyLocations.damageRoll.id) {
|
|
||||||
acc.push({
|
|
||||||
...x,
|
|
||||||
hopeUses: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, []),
|
|
||||||
hope
|
|
||||||
};
|
|
||||||
this.resolve = resolve;
|
|
||||||
}
|
|
||||||
|
|
||||||
static DEFAULT_OPTIONS = {
|
|
||||||
tag: 'form',
|
|
||||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
|
|
||||||
position: {
|
|
||||||
width: 400,
|
|
||||||
height: 'auto'
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
decreaseHopeUse: this.decreaseHopeUse,
|
|
||||||
increaseHopeUse: this.increaseHopeUse,
|
|
||||||
rollDamage: this.rollDamage
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
handler: this.updateSelection,
|
|
||||||
submitOnChange: true,
|
|
||||||
closeOnSubmit: false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @override */
|
|
||||||
static PARTS = {
|
|
||||||
damageSelection: {
|
|
||||||
id: 'damageSelection',
|
|
||||||
template: 'systems/daggerheart/templates/dialogs/dice-roll/damageSelection.hbs'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
|
||||||
|
|
||||||
/** @inheritDoc */
|
|
||||||
get title() {
|
|
||||||
return `Damage Options`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
|
||||||
return {
|
|
||||||
rollString: this.getRollString(),
|
|
||||||
bonusDamage: this.data.bonusDamage,
|
|
||||||
hope: this.data.hope + 1,
|
|
||||||
hopeUsed: this.getHopeUsed()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static updateSelection(event, _, formData) {
|
|
||||||
const { bonusDamage, ...rest } = foundry.utils.expandObject(formData.object);
|
|
||||||
|
|
||||||
for (var index in bonusDamage) {
|
|
||||||
this.data.bonusDamage[index].initiallySelected = bonusDamage[index].initiallySelected;
|
|
||||||
if (bonusDamage[index].hopeUses) {
|
|
||||||
const value = Number.parseInt(bonusDamage[index].hopeUses);
|
|
||||||
if (!Number.isNaN(value)) this.data.bonusDamage[index].hopeUses = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.data = foundry.utils.mergeObject(this.data, rest);
|
|
||||||
this.render(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
getRollString() {
|
|
||||||
return this.data.rollString.concat(
|
|
||||||
this.data.bonusDamage.reduce((acc, x) => {
|
|
||||||
if (x.initiallySelected) {
|
|
||||||
const nr = 1 + x.hopeUses;
|
|
||||||
const baseDamage = x.value;
|
|
||||||
return acc.concat(` + ${nr}${baseDamage}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, '')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getHopeUsed() {
|
|
||||||
return this.data.bonusDamage.reduce((acc, x) => acc + x.hopeUses, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static decreaseHopeUse(_, button) {
|
|
||||||
const index = Number.parseInt(button.dataset.index);
|
|
||||||
if (this.data.bonusDamage[index].hopeUses - 1 >= 0) {
|
|
||||||
this.data.bonusDamage[index].hopeUses -= 1;
|
|
||||||
this.render(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static increaseHopeUse(_, button) {
|
|
||||||
const index = Number.parseInt(button.dataset.index);
|
|
||||||
if (this.data.bonusDamage[index].hopeUses <= this.data.hope + 1) {
|
|
||||||
this.data.bonusDamage[index].hopeUses += 1;
|
|
||||||
this.render(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static rollDamage(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
this.resolve({
|
|
||||||
rollString: this.getRollString(),
|
|
||||||
bonusDamage: this.data.bonusDamage,
|
|
||||||
hopeUsed: this.getHopeUsed()
|
|
||||||
});
|
|
||||||
this.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,7 +7,11 @@ export default class DhpEnvironment extends DHBaseActorSheet {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['environment'],
|
classes: ['environment'],
|
||||||
position: {
|
position: {
|
||||||
width: 500
|
width: 500,
|
||||||
|
height: 725
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
resizable: true
|
||||||
},
|
},
|
||||||
actions: {},
|
actions: {},
|
||||||
dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]
|
dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]
|
||||||
|
|
|
||||||
|
|
@ -323,6 +323,20 @@ export default function DHApplicationMixin(Base) {
|
||||||
];
|
];
|
||||||
|
|
||||||
if (usable)
|
if (usable)
|
||||||
|
options.unshift({
|
||||||
|
name: 'DAGGERHEART.GENERAL.damage',
|
||||||
|
icon: 'fa-solid fa-explosion',
|
||||||
|
condition: target => {
|
||||||
|
const doc = getDocFromElementSync(target);
|
||||||
|
return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length;
|
||||||
|
},
|
||||||
|
callback: async (target, event) => {
|
||||||
|
const doc = await getDocFromElement(target),
|
||||||
|
action = doc?.system?.attack ?? doc;
|
||||||
|
return action && action.use(event, { byPassRoll: true })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
options.unshift({
|
options.unshift({
|
||||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||||
icon: 'fa-solid fa-burst',
|
icon: 'fa-solid fa-burst',
|
||||||
|
|
@ -334,7 +348,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (toChat)
|
if (toChat)
|
||||||
options.unshift({
|
options.push({
|
||||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||||
icon: 'fa-solid fa-message',
|
icon: 'fa-solid fa-message',
|
||||||
callback: async target => (await getDocFromElement(target)).toChat(this.document.id)
|
callback: async target => (await getDocFromElement(target)).toChat(this.document.id)
|
||||||
|
|
|
||||||
|
|
@ -194,8 +194,11 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
const item = await foundry.utils.fromUuid(message.system.origin);
|
const item = await foundry.utils.fromUuid(message.system.origin);
|
||||||
const action = item.system.actions.get(event.currentTarget.id);
|
const action = item.system.attack?.id === event.currentTarget.id ? item.system.attack : item.system.actions.get(event.currentTarget.id);
|
||||||
await item.use(action);
|
if(event.currentTarget.dataset.directDamage)
|
||||||
|
action.use(event, { byPassRoll: true })
|
||||||
|
else
|
||||||
|
action.use(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async actionUseButton(event, message) {
|
async actionUseButton(event, message) {
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async use(event, ...args) {
|
async use(event, options) {
|
||||||
const result = await super.use(event, args);
|
const result = await super.use(event, options);
|
||||||
|
|
||||||
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
|
||||||
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id);
|
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id);
|
||||||
|
|
|
||||||
|
|
@ -111,12 +111,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
return actorData;
|
return actorData;
|
||||||
}
|
}
|
||||||
|
|
||||||
async use(event, ...args) {
|
async use(event, options = {}) {
|
||||||
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||||
|
|
||||||
if (this.chatDisplay) await this.toChat();
|
if (this.chatDisplay) await this.toChat();
|
||||||
|
|
||||||
let config = this.prepareConfig(event);
|
let { byPassRoll } = options,
|
||||||
|
config = this.prepareConfig(event, byPassRoll);
|
||||||
for (let i = 0; i < this.constructor.extraSchemas.length; i++) {
|
for (let i = 0; i < this.constructor.extraSchemas.length; i++) {
|
||||||
let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]);
|
let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]);
|
||||||
if (clsField?.prepareConfig) {
|
if (clsField?.prepareConfig) {
|
||||||
|
|
@ -133,14 +134,14 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.hasRoll) {
|
if (config.hasRoll) {
|
||||||
const rollConfig = this.prepareRoll(config);
|
const rollConfig = this.prepareRoll(config);
|
||||||
config.roll = rollConfig;
|
config.roll = rollConfig;
|
||||||
config = await this.actor.diceRoll(config);
|
config = await this.actor.diceRoll(config);
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.doFollowUp()) {
|
if (this.doFollowUp(config)) {
|
||||||
if (this.rollDamage && this.damage.parts.length) await this.rollDamage(event, config);
|
if (this.rollDamage && this.damage.parts.length) await this.rollDamage(event, config);
|
||||||
else if (this.trigger) await this.trigger(event, config);
|
else if (this.trigger) await this.trigger(event, config);
|
||||||
else if (this.hasSave || this.hasEffect) {
|
else if (this.hasSave || this.hasEffect) {
|
||||||
|
|
@ -160,7 +161,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
}
|
}
|
||||||
|
|
||||||
/* */
|
/* */
|
||||||
prepareConfig(event) {
|
prepareConfig(event, byPass = false) {
|
||||||
|
const hasRoll = this.getUseHasRoll(byPass);
|
||||||
return {
|
return {
|
||||||
event,
|
event,
|
||||||
title: `${this.item.name}: ${this.name}`,
|
title: `${this.item.name}: ${this.name}`,
|
||||||
|
|
@ -170,10 +172,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
actor: this.actor.uuid
|
actor: this.actor.uuid
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
configure: this.hasRoll
|
configure: hasRoll
|
||||||
},
|
},
|
||||||
type: this.type,
|
type: this.type,
|
||||||
hasRoll: this.hasRoll,
|
hasRoll: hasRoll,
|
||||||
hasDamage: this.damage?.parts?.length && this.type !== 'healing',
|
hasDamage: this.damage?.parts?.length && this.type !== 'healing',
|
||||||
hasHealing: this.damage?.parts?.length && this.type === 'healing',
|
hasHealing: this.damage?.parts?.length && this.type === 'healing',
|
||||||
hasEffect: !!this.effects?.length,
|
hasEffect: !!this.effects?.length,
|
||||||
|
|
@ -182,12 +184,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
selectedRollMode: game.settings.get('core', 'rollMode'),
|
selectedRollMode: game.settings.get('core', 'rollMode'),
|
||||||
isFastForward: event.shiftKey,
|
isFastForward: event.shiftKey,
|
||||||
data: this.getRollData(),
|
data: this.getRollData(),
|
||||||
evaluate: this.hasRoll
|
evaluate: hasRoll
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
requireConfigurationDialog(config) {
|
requireConfigurationDialog(config) {
|
||||||
return !config.event.shiftKey && !this.hasRoll && (config.costs?.length || config.uses);
|
return !config.event.shiftKey && !config.hasRoll && (config.costs?.length || config.uses);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareRoll() {
|
prepareRoll() {
|
||||||
|
|
@ -205,7 +207,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
}
|
}
|
||||||
|
|
||||||
doFollowUp(config) {
|
doFollowUp(config) {
|
||||||
return !this.hasRoll;
|
return !config.hasRoll;
|
||||||
}
|
}
|
||||||
|
|
||||||
async consume(config, successCost = false) {
|
async consume(config, successCost = false) {
|
||||||
|
|
@ -257,6 +259,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
/* */
|
/* */
|
||||||
|
|
||||||
/* ROLL */
|
/* ROLL */
|
||||||
|
getUseHasRoll(byPass = false) {
|
||||||
|
return this.hasRoll && !byPass;
|
||||||
|
}
|
||||||
|
|
||||||
get hasRoll() {
|
get hasRoll() {
|
||||||
return !!this.roll?.type;
|
return !!this.roll?.type;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import DHBaseAction from './baseAction.mjs';
|
||||||
export default class DhBeastformAction extends DHBaseAction {
|
export default class DhBeastformAction extends DHBaseAction {
|
||||||
static extraSchemas = [...super.extraSchemas, 'beastform'];
|
static extraSchemas = [...super.extraSchemas, 'beastform'];
|
||||||
|
|
||||||
async use(event, ...args) {
|
async use(event, options) {
|
||||||
const beastformConfig = this.prepareBeastformConfig();
|
const beastformConfig = this.prepareBeastformConfig();
|
||||||
|
|
||||||
const abort = await this.handleActiveTransformations();
|
const abort = await this.handleActiveTransformations();
|
||||||
|
|
@ -20,7 +20,7 @@ export default class DhBeastformAction extends DHBaseAction {
|
||||||
const { selected, evolved, hybrid } = await BeastformDialog.configure(beastformConfig, this.item);
|
const { selected, evolved, hybrid } = await BeastformDialog.configure(beastformConfig, this.item);
|
||||||
if (!selected) return;
|
if (!selected) return;
|
||||||
|
|
||||||
const result = await super.use(event, args);
|
const result = await super.use(event, options);
|
||||||
if (!result) return;
|
if (!result) return;
|
||||||
|
|
||||||
await this.transform(selected, evolved, hybrid);
|
await this.transform(selected, evolved, hybrid);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export default class DHDamageAction extends DHBaseAction {
|
||||||
|
|
||||||
getFormulaValue(part, data) {
|
getFormulaValue(part, data) {
|
||||||
let formulaValue = part.value;
|
let formulaValue = part.value;
|
||||||
if (this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
|
if (data.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
|
||||||
|
|
||||||
const isAdversary = this.actor.type === 'adversary';
|
const isAdversary = this.actor.type === 'adversary';
|
||||||
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
|
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ export default class DHMacroAction extends DHBaseAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
async trigger(event, ...args) {
|
async trigger(event, ...args) {
|
||||||
// const config = await super.use(event, args);
|
|
||||||
// if (['error', 'warning'].includes(config.type)) return;
|
|
||||||
const fixUUID = !this.documentUUID.includes('Macro.') ? `Macro.${this.documentUUID}` : this.documentUUID,
|
const fixUUID = !this.documentUUID.includes('Macro.') ? `Macro.${this.documentUUID}` : this.documentUUID,
|
||||||
macro = await fromUuid(fixUUID);
|
macro = await fromUuid(fixUUID);
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ export default class DHSummonAction extends DHBaseAction {
|
||||||
|
|
||||||
async trigger(event, ...args) {
|
async trigger(event, ...args) {
|
||||||
if (!this.canSummon || !canvas.scene) return;
|
if (!this.canSummon || !canvas.scene) return;
|
||||||
// const config = await super.use(event, args);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get canSummon() {
|
get canSummon() {
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
if(this.targetSelection === true) {
|
if(this.targetSelection === true) {
|
||||||
this.targetShort = this.targets.reduce((a,c) => {
|
this.targetShort = this.targets.reduce((a,c) => {
|
||||||
if(c.hit) a.hit += 1;
|
if(c.hit) a.hit += 1;
|
||||||
else c.miss += 1;
|
else a.miss += 1;
|
||||||
return a;
|
return a;
|
||||||
}, {hit: 0, miss: 0})
|
}, {hit: 0, miss: 0})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ export default class D20Roll extends DHRoll {
|
||||||
config.targetSelection = true;
|
config.targetSelection = true;
|
||||||
config.targets.forEach(target => {
|
config.targets.forEach(target => {
|
||||||
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
|
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
|
||||||
target.hit = this.isCritical || roll.total >= difficulty;
|
target.hit = roll.isCritical || roll.total >= difficulty;
|
||||||
});
|
});
|
||||||
data.success = config.targets.some(target => target.hit)
|
data.success = config.targets.some(target => target.hit)
|
||||||
} else if (config.roll.difficulty) {
|
} else if (config.roll.difficulty) {
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
img: this.img,
|
img: this.img,
|
||||||
tags: this._getTags()
|
tags: this._getTags()
|
||||||
},
|
},
|
||||||
actions: item.system.actions,
|
actions: item.system.actionsList,
|
||||||
description: this.system.description
|
description: this.system.description
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
.feature-tab {
|
.feature-tab {
|
||||||
border: none;
|
border: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: light-dark(@dark-blue, @golden);
|
color: light-dark(@dark-blue, @golden);
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,12 @@
|
||||||
<button class="ability-use-button" id="{{action.id}}">
|
<button class="ability-use-button" id="{{action.id}}">
|
||||||
{{action.name}}
|
{{action.name}}
|
||||||
</button>
|
</button>
|
||||||
{{#if action.cost.value}}<div class="ability-card-action-cost">{{action.cost.value}} {{action.cost.type}}</div>{{/if}}
|
{{#if (eq action.systemPath "attack")}}
|
||||||
|
<button class="ability-use-button" id="{{action.id}}" data-direct-damage="true">
|
||||||
|
{{localize "DAGGERHEART.GENERAL.damage"}}
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
{{!-- {{#if action.cost.value}}<div class="ability-card-action-cost">{{action.cost.value}} {{action.cost.type}}</div>{{/if}} --}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue