Skip to content
Open
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
8 changes: 8 additions & 0 deletions src/lang/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ GRIMWILD:
ignored: ignored from harm
vex: Vex
dropped: Dropped
setup: Setup
potent: Potent
Traits:
brave: Brave
caring: Caring
Expand Down Expand Up @@ -83,7 +85,13 @@ GRIMWILD:
disaster: Disaster
thorns: Thorns
difficulty: Difficulty
ignoredByPotency: ignored by potency
total: Total
configure: Configure roll
isRolling: '{name} is rolling {pool}'
isAssisting: '{assisting} is assisting {rolling}'
assistedBy: Assisted by
waitingForAssists: Waiting for assists...
Resources:
pools: Pools
points: Points
Expand Down
110 changes: 53 additions & 57 deletions src/module/data/actor-character.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import GrimwildActorBase from "./base-actor.mjs";
import { DicePoolField } from "../helpers/schema.mjs";
import { GrimwildRollDialog } from "../apps/roll-dialog.mjs";

export default class GrimwildCharacter extends GrimwildActorBase {
static LOCALIZATION_PREFIXES = [
Expand Down Expand Up @@ -270,71 +269,68 @@ export default class GrimwildCharacter extends GrimwildActorBase {
return data;
}

async roll(options) {
async roll(options, event) {
const rollData = this.getRollData();

if (options?.stat && rollData?.stats?.[options.stat]) {
const rollDialog = await GrimwildRollDialog.open({
rollData: {
name: this?.name ?? this?.parent?.name,
spark: rollData?.spark,
stat: options.stat,
diceDefault: rollData?.stats?.[options.stat].value,
isBloodied: rollData?.isBloodied,
isRattled: rollData?.isRattled,
isMarked: rollData?.stats?.[options.stat].marked
}
});
// bail out if they closed the dialog
if (rollDialog === null) {
return;
}
rollData.thorns = rollDialog.thorns;
rollData.statDice = rollDialog.dice;
options.assists = rollDialog.assisters;
const formula = "{(@statDice)d6kh, (@thorns)d8}";
const roll = new grimwild.roll(formula, rollData, options);

const updates = {};

// Remove used spark.
if (rollDialog.sparkUsed > 0) {
let sparkUsed = rollDialog.sparkUsed;
const newSpark = {
steps: this.spark.steps
};
// All of your spark is used.
if (sparkUsed > 1 || this.spark.value === 1) {
newSpark.steps[0] = false;
newSpark.steps[1] = false;
}
// If half of your spark is used.
else if (sparkUsed === 1 && this.spark.value > 1) {
newSpark.steps[0] = true;
newSpark.steps[1] = false;
}
updates["system.spark"] = newSpark;
}
const chatRollData = {
name: this?.name ?? this?.parent?.name,
actorId: this?.parent?.id,
spark: rollData?.spark,
stat: options.stat,
diceDefault: rollData?.stats?.[options.stat].value,
isBloodied: rollData?.isBloodied,
isRattled: rollData?.isRattled,
isMarked: rollData?.stats?.[options.stat].marked,
isVex: false,
difficulty: 0,
conditions: rollData?.conditions
};

// Remove marks.
if (rollData?.stats?.[options.stat].marked) {
updates[`system.stats.${options.stat}.marked`] = false;
}
const roll = new grimwild.rolls.configure(undefined, chatRollData, { ...options, ...chatRollData });
roll._evaluated = true;

const message = await CONFIG.ChatMessage.documentClass.create(
{
user: game.user.id,
actor: this,
speaker: ChatMessage.getSpeaker({ actor: this.parent }),
content: "",
rolls: [roll],
sound: CONFIG.sounds.notification
},
{
rollMode: game.settings.get("core", "rollMode")
}
);
message.sheet?.render(true);
}
}

// Handle the updates.
const actor = game.actors.get(this.parent.id);
await actor.update(updates);
actor.sheet.render(true);
async assist(chatMessage, event) {
const chatRollData = {
name: this?.name ?? this?.parent?.name,
rollMessageId: chatMessage.id,
actorId: this.parent.id
};
const roll = new grimwild.rolls.assist(undefined, chatRollData, chatRollData);
roll._evaluated = true;

await roll.toMessage({
await CONFIG.ChatMessage.documentClass.create(
{
user: game.user.id,
actor: this,
speaker: ChatMessage.getSpeaker({ actor: this }),
speaker: ChatMessage.getSpeaker({ actor: this.parent }),
content: "",
rolls: [roll],
sound: CONFIG.sounds.notification
},
{
rollMode: game.settings.get("core", "rollMode")
});

await this.updateCombatActionCount();
}
);

}
await this.updateCombatActionCount();
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/module/dice/_module.mjs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as GrimwildRoll } from "./rolls.mjs";
export { GrimwildConfigurationRoll, GrimwildAssistRoll } from "./chat-rolls.mjs";
export { default as GrimwildDiePoolRoll } from "./die-pools.mjs";
92 changes: 92 additions & 0 deletions src/module/dice/chat-rolls.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { isMentalStat, isPhysicalStat } from "../helpers/config.mjs";

const { renderTemplate } = foundry.applications.handlebars;

export class GrimwildConfigurationRoll extends Roll {
static CHAT_TEMPLATE = "systems/grimwild/templates/chat/roll-action-config.hbs";

get dice() {
const fromDefault = this.options.diceDefault;
const fromSpark = this.options.sparkUsed ?? 0;
const fromSetup = this.options.isSetup ?? 0;

return fromDefault + fromSpark + fromSetup;
}

get isMarkIgnored() {
return this.options.isMarked
&& ((this.options.isBloodied && isPhysicalStat(this.options.stat))
|| (this.options.isRattled && isMentalStat(this.options.stat)));
}

get thorns() {
const fromDifficulty = this.options.isPotent ? 0 : this.options.difficulty;
const fromMarked = this.options.isMarked && !this.isMarkIgnored ? 1 : 0;
const fromBloodied = this.options.isBloodied ? 1 : 0;
const fromRattled = this.options.isRattled ? 1 : 0;
const fromVex = this.options.isVex ? 1 : 0;
const fromConditions = this.options.conditionsApplied?.length ?? 0

return fromDifficulty + fromMarked + fromBloodied + fromRattled + fromVex + fromConditions;
}

get assistMessages() {
return game.messages.filter(message => message.rolls[0]?.options.rollMessageId == this.options.chatMessageId);
}

get assistants() {
return this.assistMessages.map(message => game.actors.get(message.rolls[0]?.options.actorId));
}

get assistingActor() {
const owner = game.actors.get(this.options.actorId);
const actor = canvas.tokens?.controlled.length == 1 && canvas.tokens?.controlled[0]?.actor.type === "character"
? canvas.tokens?.controlled[0].actor
: !game.user.isGM ? game.user.character : undefined;

return owner !== actor && !this.assistants.includes(actor) ? actor : undefined;
}

async render({ flavor, template=this.constructor.CHAT_TEMPLATE, isPrivate=false }={}) {
const isOwner = game.actors.get(this.options.actorId)?.isOwner;
const assistingActor = this.assistingActor;

const chatData = {
...this.options,
rolled: false,
isPrivate,
isGM: game.user.isGM,
isOwner,
assistingActor,
hasAssistingActor: !!assistingActor
};
return renderTemplate(template, chatData);
}
}

export class GrimwildAssistRoll extends Roll {
static CHAT_TEMPLATE = "systems/grimwild/templates/chat/roll-action-assist.hbs";

async render({ flavor, template=this.constructor.CHAT_TEMPLATE, isPrivate=false }={}) {
const isOwner = game.actors.get(this.options.actorId)?.isOwner;
const targetMessageId = this.options.rollMessageId;
const assistingActorId = this.options.actorId;

const targetMessage = game.messages.get(targetMessageId);
const targetRollOptions = targetMessage?.rolls[0]?.options;
const rollingActorId = targetRollOptions?.actorId;

const assistingActor = game.actors.get(assistingActorId);
const rollingActor = game.actors.get(rollingActorId);

const chatData = {
isPrivate,
dice: this.dice[0]?.results,
assistingActor,
rollingActor,
targetRollOptions,
isOwner
};
return renderTemplate(template, chatData);
}
}
28 changes: 17 additions & 11 deletions src/module/dice/rolls.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ export default class GrimwildRoll extends Roll {
async render({ flavor, template=this.constructor.CHAT_TEMPLATE, isPrivate=false }={}) {
if (!this._evaluated) await this.evaluate();

const assistMessages = this.options.assistMessageIds?.map(id => game.messages.get(id)).filter(m => m) ?? [];
const assists = assistMessages.reduce((cur, message) => {
const value = message?.rolls[0]?.dice[0]?.results;
const name = game.actors.get(message?.rolls[0]?.options.actorId)?.name;

if (name && value) {
cur[name] = value;
}
return cur;
}, {});

const chatData = {
formula: isPrivate ? "???" : this._formula,
flavor: isPrivate ? null : flavor ?? this.options.flavor,
Expand All @@ -21,7 +32,8 @@ export default class GrimwildRoll extends Roll {
total: isPrivate ? "?" : this.total,
dice: this.dice[0].results,
thorns: this.dice[1].results,
assists: {},
assists,
isWaitingForAssists: Object.values(assists).length !== assistMessages.length,
crit: false,
success: 0,
rawSuccess: 0,
Expand All @@ -31,10 +43,12 @@ export default class GrimwildRoll extends Roll {
hasActions: false
};

const sixes = chatData.dice.filter((die) => die.result === 6);
const dice = [...chatData.dice, ...Object.values(assists).flat()];

const sixes = dice.filter((die) => die.result === 6);
const cuts = chatData.thorns.filter((die) => die.result >= 7);

const diceTotal = this.dice[0].total;
const diceTotal = Math.max(this.dice[0].total, ...Object.values(assists).flat().flatMap(result => result.result));

// Handle initial results.
if (diceTotal === 6) {
Expand Down Expand Up @@ -68,14 +82,6 @@ export default class GrimwildRoll extends Roll {
chatData.rawResult = successToResult(chatData.rawSuccess);
chatData.isCut = chatData.success !== chatData.rawSuccess;

// Separate assist dice from other dice
if (this.options?.assists) {
for (const [name, diceNum] of Object.entries(this.options.assists)) {
const assistResults = chatData.dice.splice(diceNum * -1);
chatData.assists[name] = assistResults;
}
}

// Handle actions.
if (chatData.result === "disaster") {
chatData.hasActions = true;
Expand Down
64 changes: 63 additions & 1 deletion src/module/documents/chat-message.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { GrimwildRollSheet } from "../sheets/roll-sheet.mjs";

export class GrimwildChatMessage extends ChatMessage {
/** @inheritDoc */
async renderHTML(...args) {
Expand Down Expand Up @@ -95,7 +97,11 @@ export class GrimwildChatMessage extends ChatMessage {
*/
get actions() {
return {
updateSpark: this._updateSpark
updateSpark: this._updateSpark,
updateDifficulty: this._updateDifficulty,
configureRoll: this._configureRoll,
assistRoll: this._assistRoll,
rollAssist: this._rollAssist
};
}

Expand Down Expand Up @@ -167,4 +173,60 @@ export class GrimwildChatMessage extends ChatMessage {
ui.notifications.warn(`${actor.name} already has maximum spark. Use it more often!`);
}
}

_configureRoll(event, target) {
this.sheet.render(true);
}

async _assistRoll(event, target) {
const actorId = target.dataset.actorId;
const assistingActor = game.actors.get(actorId);
await assistingActor?.system.assist(this, event);
ui.chat.updateMessage(this);
}

async _rollAssist(event, target) {
const newRoll = new grimwild.rolls.assist("1d6", {}, this.rolls[0]?.options);
await newRoll.evaluate();
await game.dice3d?.showForRoll(newRoll);
await this.update({ rolls: [newRoll] });
}

_updateDifficulty(event, target) {
this.rolls[0].options.difficulty = parseInt(target.dataset.difficulty);
this.update({ rolls: this.rolls });
}

async performRoll() {
const roll = this.rolls.length == 1 && this.rolls[0] instanceof grimwild.rolls.configure ? this.rolls[0] : null;
if (roll) {
const rollData = {};
const options = {};
rollData.thorns = roll.thorns;
rollData.statDice = roll.dice;
options.assistMessageIds = roll.assistMessages.map(message => message.id);
options.actorId = roll.options.actorId;
const formula = "{(@statDice)d6kh, (@thorns)d8}";
const newRoll = new grimwild.rolls.dice(formula, rollData, options);
await newRoll.evaluate();
await game.dice3d?.showForRoll(newRoll);
await this.update({ rolls: [newRoll] });

const actor = game.actors.get(roll.options.actorId);
const sparkUsed = roll.options.sparkUsed ?? 0;
if (actor && sparkUsed > 0) {
const value = Math.max(actor.system.spark.value - sparkUsed, 0);
const steps = [...Array(2).keys()].map(n => n < value);

await actor.update({ "system.spark": { steps, value } });
}
}
}

_getSheetClass() {
if (this.rolls.length == 1 && this.rolls[0] instanceof grimwild.rolls.configure) {
return GrimwildRollSheet;
}
return super._getSheetClass();
}
}
Loading
Loading