Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@
"DL.AvailabilityR": "Rare",
"DL.AvailabilityU": "Uncommon",
"DL.BackgroundTitle": "Background",
"DL.BecomeFrightenedForRound" : "Your become frightened {round} round.",
"DL.BecomeFrightenedForRounds" : "Your become frightened {round} rounds.",
"DL.BecomeStunned" : "Your become stunned for {round} round.",
"DL.ButtonEdit": "Edit",
"DL.ButtonView": "View",
"DL.ChallengeRequestRollText": "Request Challenge Roll",
Expand Down Expand Up @@ -383,6 +386,9 @@
"DL.FeatureDelete": "Delete feature",
"DL.FeatureEdit": "Edit feature",
"DL.FeaturesTitle": "Features",
"DL.FourOrMoreCreatures": "Four or more creatures at once",
"DL.FourOrMoreCreaturesInSight": "Sees four or more creatures at once",
"DL.GainedInsanity" : "You gain {insanity} Insanity.",
"DL.GMTools": "GM Tools",
"DL.GMnote": "GM Notes",
"DL.GMnoteEdit": "Edit GM Note",
Expand Down Expand Up @@ -426,6 +432,7 @@
"DL.LanguagesTitle": "Languages",
"DL.LanguagesWrite": "Write",
"DL.LanguagesWriteShort": "W",
"DL.LookOutCreatures": "Look out creatures!",
"DL.MacroApplyAfflicationTitle": "Apply Afflictions",
"DL.MacroCancel": "Cancel",
"DL.MacroChallengeChoose": "Choose Attribute:",
Expand Down
8 changes: 4 additions & 4 deletions src/module/active-effects/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,17 @@ export function prepareActiveEffectCategories(effects, showCreateButtons = false
if (e.duration.turns > 0) {
const rr = calcEffectRemainingRounds(e, game.combat.round)
const rt = calcEffectRemainingTurn(e, game.combat.turn)
const sr = `${rr} ${Math.abs(rr) > 1 ? i18n("COMBAT.Rounds") : i18n("COMBAT.Round")}`
const st = `${rt} ${Math.abs(rt) > 1 ? i18n("COMBAT.Turns") : i18n("COMBAT.Turn")}`
const sr = Math.abs(rr) > 1 ? `${rr} ${i18n("COMBAT.DURATION.ROUNDS.many")}` : `${rr} ${i18n("COMBAT.DURATION.ROUNDS.one")}`
const st = Math.abs(rt) > 1 ? `${rt} ${i18n("COMBAT.DURATION.TURNS.many")}` : `${rt} ${i18n("COMBAT.DURATION.TURNS.one")}`
e.dlRemaining = sr + ' ' + st
}
else {
const r = calcEffectRemainingRounds(e, game.combat.round)
e.dlRemaining = `${r} ${Math.abs(r) > 1 ? i18n("COMBAT.Rounds") : i18n("COMBAT.Round")}`
e.dlRemaining = Math.abs(r) > 1 ? `${r} ${i18n("COMBAT.DURATION.ROUNDS.many")}` : `${r} ${i18n("COMBAT.DURATION.ROUNDS.one")}`
}
} else {
const r = calcEffectRemainingSeconds(e, game.time.worldTime)
e.dlRemaining = `${r} ${i18n("TIME.Seconds")}`
e.dlRemaining = Math.abs(r) > 1 ? `${r} ${i18n("TIME.Second.other")}` : `${r} ${i18n("TIME.Second.one")}`
}
} else {
e.dlRemaining = e.duration.label
Expand Down
135 changes: 134 additions & 1 deletion src/module/actor/actor.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {DLActiveEffects} from '../active-effects/item-effects'
import {DLAfflictions} from '../active-effects/afflictions'
import {capitalize, plusify} from '../utils/utils'
import launchRollDialog from '../dialog/roll-dialog'
import launchFrightenedDialog from '../dialog/frightened-dialog'
import {
postAttackToChat,
postAttributeToChat,
Expand All @@ -14,7 +15,8 @@ import {
postSpellToChat,
postTalentToChat,
postFortuneToChat,
postRestToChat
postRestToChat,
postPlainTextToChat
} from '../chat/roll-messages'
import {handleCreateAncestry, handleCreatePath, handleCreateRole, handleCreateRelic } from '../item/nested-objects'
import {TokenManager} from '../pixi/token-manager'
Expand Down Expand Up @@ -951,6 +953,137 @@ export class DemonlordActor extends Actor {

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

async goingMad() {
const compendia = await game.packs.get('sdlc-1000.tables')
if (compendia) {
const compendiumRollTables = await compendia.getDocuments()
const tableMadness = compendiumRollTables.find(i => i.name === 'Madness')
if (tableMadness) await tableMadness.draw()
}
}

async stunnedChallengeRoll() {
let actor = this
if (!actor.isImmuneToAffliction('stunned')) {
const attribute = actor.getAttribute('will')
let roll = await actor.rollAttributeChallenge(attribute, 0, 0)
const targetNumber = game.settings.get('demonlord', 'optionalRuleDieRollsMode') === 'b' ? 11 : 10
if (roll._total < targetNumber) {
const stunnedEffect = CONFIG.statusEffects.find(e => e.id === 'stunned')
stunnedEffect['statuses'] = stunnedEffect.id
stunnedEffect.duration.rounds = 1
await ActiveEffect.create(stunnedEffect, {
parent: actor,
})
}
let durationRoll = new Roll(`1d1`)
await durationRoll.evaluate()
postPlainTextToChat(actor, 'stunned',durationRoll)
} else ui.notifications.warn(game.i18n.localize('DL.DialogWarningActorImmune'))
}

async setInsanityValue(roll) {
let actor = this
let increment = roll._total
let newValue =
actor.system.characteristics.insanity.value + increment < actor.system.characteristics.insanity.max
? actor.system.characteristics.insanity.value + increment
: actor.system.characteristics.insanity.max
const isFrightened = actor.effects.find(e => e.statuses?.has('frightened')) === undefined ? false : true
const isStunned = actor.effects.find(e => e.statuses?.has('stunned')) === undefined ? false : true
await actor.update({ 'system.characteristics.insanity.value': newValue })
if (!isFrightened) {
const frightenedEffect = CONFIG.statusEffects.find(e => e.id === 'frightened')
frightenedEffect['statuses'] = frightenedEffect.id
frightenedEffect.duration.rounds = newValue

if (!actor.isImmuneToAffliction('frightened')) {
await ActiveEffect.create(frightenedEffect, {
parent: actor,
})
} else ui.notifications.warn(game.i18n.localize('DL.DialogWarningActorImmune'))
} else {
const frightenedEffect = actor.effects.find(e => e.statuses?.has('frightened'))
await frightenedEffect.update({ 'duration.rounds': newValue })
if (!isStunned) await this.stunnedChallengeRoll()
}
}

async rollFrightened() {
let actor = this
const isStunned = actor.effects.find(e => e.statuses?.has('stunned')) === undefined ? false : true
launchFrightenedDialog(async (dHtml, creatureTrait) => {
let fourOrMore = dHtml.currentTarget.querySelector("input[id='fourOrMore']").checked ? -1 : 0
if (fourOrMore) {
let fourAndMoreEffect = new ActiveEffect({
name: game.i18n.localize('DL.FourOrMoreCreaturesInSight'),
icon: 'icons/svg/terror.svg',
changes: [{ key: 'system.bonuses.challenge.boons.will', value: -1, mode: CONST.ACTIVE_EFFECT_MODES.ADD }],
flags: { demonlord: { specialDuration: 'NextChallengeRoll' } },
})

await ActiveEffect.create(fourAndMoreEffect, {
parent: actor,
})
}

if (!isStunned) {
const attribute = actor.getAttribute('will')
let roll = await actor.rollAttributeChallenge(attribute, 0, 0)
const targetNumber = game.settings.get('demonlord', 'optionalRuleDieRollsMode') === 'b' ? 11 : 10
if (roll._total < targetNumber) await actor.rollDurationAndSanity(creatureTrait)
}
// Stunned -> autofail
else await actor.rollDurationAndSanity(creatureTrait)
})
}

async rollDurationAndSanity(creatureTrait) {
const actor = this
const isHorrifying = creatureTrait === 'h' || creatureTrait === 'fh' ? true : false
const isFrightening = creatureTrait === 'f' ? true : false
const isFrightened = actor.effects.find(e => e.statuses?.has('frightened')) === undefined ? false : true

if (isFrightening) {
if (!isFrightened) {
if (!actor.isImmuneToAffliction('frightened')) {
let durationRoll = actor.system.characteristics.insanity.value
? new Roll(`1d3+${actor.system.characteristics.insanity.value}`)
: new Roll(`1d3`)
await durationRoll.evaluate()
const frightenedEffect = CONFIG.statusEffects.find(e => e.id === 'frightened')
frightenedEffect['statuses'] = frightenedEffect.id
frightenedEffect.duration.rounds = durationRoll.total
await ActiveEffect.create(frightenedEffect, {
parent: actor,
})
postPlainTextToChat(actor,'frightenedForRounds', durationRoll)
} else ui.notifications.warn(game.i18n.localize('DL.DialogWarningActorImmune'))
} else
{
let durationRoll = new Roll(`1d1`)
await durationRoll.evaluate()
this.setInsanityValue(durationRoll)
postPlainTextToChat(actor,'frightenedForRounds', durationRoll)
}
}

if (isHorrifying) {
if (!isFrightened) {
let durationRoll = new Roll(`1d3`)
await durationRoll.evaluate()
this.setInsanityValue(durationRoll)
} else {
let insanityIncreaseRoll = new Roll('1d3')
await insanityIncreaseRoll.evaluate()
postPlainTextToChat(actor, 'gainedInsanity', insanityIncreaseRoll)
this.setInsanityValue(insanityIncreaseRoll)
}
}

if (actor.system.characteristics.insanity.value === actor.system.characteristics.insanity.max) await this.goingMad()
}

async createItemCreate(event) {
event.preventDefault()

Expand Down
5 changes: 5 additions & 0 deletions src/module/actor/sheets/character-sheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export default class DLCharacterSheet extends DLBaseActorSheet {
},
actions: {
rollCorruption: this.onRollCorruption,
rollFrightened: this.onrollFrightened,
editStatBar: this.onEditStatBar,
editLanguages: this.onEditLanguages,
//editReligion: this.onEditReligion // Unused?
Expand Down Expand Up @@ -215,6 +216,10 @@ export default class DLCharacterSheet extends DLBaseActorSheet {
return await this.actor.rollCorruption()
}

static async onrollFrightened() {
return await this.actor.rollFrightened()
}

/** Edit HealthBar, Insanity and Corruption */
static async onEditStatBar() {
const actor = this.actor
Expand Down
49 changes: 39 additions & 10 deletions src/module/chat/roll-messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ export function postAttackToChat(attacker, defender, item, attackRoll, attackAtt

const attackAttributeImmune = attacker?.getAttribute(attackAttribute)?.immune
const defenseAttributeImmune = defender?.getAttribute(defenseAttribute)?.immune
const voidRoll = attackAttributeImmune || defenseAttributeImmune

const targetNumber =
defenseAttribute === 'defense'
? defender?.system.characteristics.defense
: defender?.getAttribute(defenseAttribute)?.value || ''

const voidRoll = attackAttributeImmune || defenseAttributeImmune || !targetNumber
const plus20 = attackRoll?.total >= 20 && (targetNumber ? attackRoll?.total > targetNumber + (game.settings.get('demonlord', 'optionalRuleExceedsByFive') ? 5 : 4) : true)
const didHit = voidRoll ? false : attackRoll?.total >= targetNumber

Expand All @@ -66,7 +66,7 @@ export function postAttackToChat(attacker, defender, item, attackRoll, attackAtt
diceTotal = '?'
// resultText = ''
}
const resultBoxClass = voidRoll ? 'FAILURE' : (resultText === '' ? '' : didHit ? 'SUCCESS' : 'FAILURE')
const resultBoxClass = voidRoll ? '' : (resultText === '' ? '' : didHit ? 'SUCCESS' : 'FAILURE')
const defenseShow = game.settings.get('demonlord', 'attackShowDefense')
const againstNumber = ((defender?.type === 'character' && defender?.isPC) || defenseShow) && targetNumber ? targetNumber : '?'

Expand Down Expand Up @@ -165,7 +165,7 @@ export function postAttributeToChat(actor, attribute, challengeRoll, inputBoons,
const templateData = {
actor: actor,
tokenId: actor.token ? actor.token.uuid : null,
item: {name: attribute?.toUpperCase()},
item: {name: game.i18n.localize(CONFIG.DL.attributes[attribute]?.toUpperCase())},
diceData: formatDice(challengeRoll),
data: {},
}
Expand Down Expand Up @@ -216,7 +216,6 @@ export function postTalentToChat(actor, talent, attackRoll, target, inputBoons,

const attackAttributeImmune = actor?.getAttribute(attackAttribute)?.immune
const defenseAttributeImmune = target?.getAttribute(defenseAttribute)?.immune
const voidRoll = attackAttributeImmune || defenseAttributeImmune

let usesText = ''
if (parseInt(talentData?.uses?.value) >= 0 && parseInt(talentData?.uses?.max) > 0) {
Expand All @@ -226,6 +225,7 @@ export function postTalentToChat(actor, talent, attackRoll, target, inputBoons,
}

const targetNumber = talentData?.action?.attack ? actor.getTargetNumber(talent) : ''
const voidRoll = attackAttributeImmune || defenseAttributeImmune || !targetNumber
const plus20 = attackRoll?.total >= 20 && (targetNumber ? attackRoll?.total > targetNumber + (game.settings.get('demonlord', 'optionalRuleExceedsByFive') ? 5 : 4) : true)

let resultText =
Expand All @@ -241,7 +241,7 @@ export function postTalentToChat(actor, talent, attackRoll, target, inputBoons,
diceTotal = '?'
// resultText = ''
}
const resultBoxClass = voidRoll ? 'FAILURE' : (resultText === '' ? '' : attackRoll?.total >= +targetNumber ? 'SUCCESS' : 'FAILURE')
const resultBoxClass = voidRoll ? '' : (resultText === '' ? '' : attackRoll?.total >= +targetNumber ? 'SUCCESS' : 'FAILURE')
const defenseShow = game.settings.get('demonlord', 'attackShowDefense')
const againstNumber = ((target?.actor?.type === 'character' && target?.actor?.isPC) || defenseShow) && targetNumber ? targetNumber : '?'

Expand Down Expand Up @@ -338,14 +338,14 @@ export async function postSpellToChat(actor, spell, attackRoll, target, inputBoo

const attackAttributeImmune = actor?.getAttribute(attackAttribute)?.immune
const defenseAttributeImmune = target?.getAttribute(defenseAttribute)?.immune
const voidRoll = attackAttributeImmune || defenseAttributeImmune

let uses = parseInt(spellData?.castings?.value)
let usesMax = parseInt(spellData?.castings?.max)
let usesText = ''
if (uses >= 0 && usesMax > 0) usesText = game.i18n.localize('DL.SpellCastingsUses') + ': ' + uses + ' / ' + usesMax

const targetNumber = actor.getTargetNumber(spell)
const voidRoll = attackAttributeImmune || defenseAttributeImmune || !targetNumber
const plus20 = attackRoll?.total >= 20 && (targetNumber ? attackRoll?.total > targetNumber + (game.settings.get('demonlord', 'optionalRuleExceedsByFive') ? 5 : 4) : true)

let resultText =
Expand All @@ -360,7 +360,7 @@ export async function postSpellToChat(actor, spell, attackRoll, target, inputBoo
diceTotal = '?'
// resultText = ''
}
const resultBoxClass = voidRoll ? 'FAILURE' : (resultText === '' ? '' : attackRoll?.total >= +targetNumber ? 'SUCCESS' : 'FAILURE')
const resultBoxClass = voidRoll ? '' : (resultText === '' ? '' : attackRoll?.total >= +targetNumber ? 'SUCCESS' : 'FAILURE')
const defenseShow = game.settings.get('demonlord', 'attackShowDefense')
const againstNumber = ((target?.actor?.type === 'character' && target?.actor?.isPC) || defenseShow) && targetNumber ? targetNumber : '?'

Expand Down Expand Up @@ -460,6 +460,37 @@ export async function postSpellToChat(actor, spell, attackRoll, target, inputBoo

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

export function postPlainTextToChat(actor, text, roll) {
const templateData = {
actor: actor,
data: {},
}

const rollMode = game.settings.get('core', 'rollMode')
const chatData = getChatBaseData(actor, rollMode)
const data = templateData.data
data['actorInfo'] = buildActorInfo(actor)
data['text'] = text
switch (text) {
case 'frightenedForRounds':
data['text'] = Math.abs(roll._total === 1) ? game.i18n.format('DL.BecomeFrightenedForRound', {round: roll._total}) :
game.i18n.format('DL.BecomeFrightenedForRounds', {round: roll._total})
break
case 'stunned':
data['text'] = game.i18n.format('DL.BecomeStunned', {round: roll._total})
break
case 'gainedInsanity':
data['text'] = game.i18n.format('DL.GainedInsanity', {insanity: roll._total})
break
}
if (roll) chatData.rolls = [roll]
const template = 'systems/demonlord/templates/chat/text.hbs'
foundry.applications.handlebars.renderTemplate(template, templateData).then(content => {
chatData.content = content
ChatMessage.create(chatData)
})
}

export async function postCorruptionToChat(actor, corruptionRoll) {
const templateData = {
actor: actor,
Expand Down Expand Up @@ -490,10 +521,8 @@ export async function postCorruptionToChat(actor, corruptionRoll) {
chatData.content = await foundry.applications.handlebars.renderTemplate(template, templateData)
chatData.sound = CONFIG.sounds.dice
await ChatMessage.create(chatData)

// Get mark of darkess if roll < corruption value
if (corruptionRoll.total < actor.system.characteristics.corruption.value) {
const compendiumRollTables = await game.packs.get('demonlord.sotdl roll tabels').getDocuments()
const compendiumRollTables = await game.packs.get('demonlord.sotdl-roll-tables').getDocuments()
const tableMarkOfDarkess = compendiumRollTables.find(i => i.name === 'Mark of Darkness')
const result = await tableMarkOfDarkess.draw()
let resultText = result.results[0].text
Expand Down
40 changes: 40 additions & 0 deletions src/module/dialog/frightened-dialog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const { DialogV2 } = foundry.applications.api

export default function launchFrightenedDialog(callback) {
const d = new DialogV2({
window: {
title: game.i18n.localize('DL.LookOutCreatures'),
},
options : { closeOnSubmit: true},
position: {
width: 500,
},
content: `
<div class="frighteneddialog">
<div class="inputContainer">
<input id='fourOrMore' type='checkbox' data-dtype='Boolean'/>
<label for='fourOrMore'><b>${game.i18n.localize('DL.FourOrMoreCreatures')}</b></label>
</div>
</div>
`,
buttons: [
{
action: 'f',
label: game.i18n.localize('DL.CreatureFrightening'),
callback: html => callback(html, 'f'),
},
{
action: 'h',
label: game.i18n.localize('DL.CreatureHorrifying'),
callback: html => callback(html, 'h'),
},
{
action: 'fh',
label: `${game.i18n.localize('DL.CreatureFrightening')}/${game.i18n.localize('DL.CreatureHorrifying')}`,
callback: html => callback(html, 'fh'),
},
],
close: () => {},
})
d.render(true)
}
Loading