diff --git a/css/style.min.css b/css/style.min.css
index 840914f7..256840ce 100644
--- a/css/style.min.css
+++ b/css/style.min.css
@@ -13504,14 +13504,14 @@ template {
min-width: 120px; }
:root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab.gm-roll-collab .window-content form .sheet-root .roll-sheet-block .roll-sheet-sub-block .roll-mod-container.roll-mod-teamwork .roll-mod-label .roll-mod-sidestring {
min-width: 75px; }
- :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab.gm-roll-collab .window-content form .sheet-root .roll-sheet-block .roll-sheet-sub-block .roll-mod-container .roll-mod-label .roll-doc-select-container {
+ :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab.gm-roll-collab .window-content form .sheet-root .roll-sheet-block .roll-sheet-sub-block .roll-mod-container .roll-mod-label .roll-select-container {
min-width: 120px;
max-height: 16px;
border-radius: 8px;
margin-left: -85px;
margin-right: -50px;
background: var(--blades-black-dark); }
- :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab.gm-roll-collab .window-content form .sheet-root .roll-sheet-block .roll-sheet-sub-block .roll-mod-container .roll-mod-label .roll-doc-select-container .roll-doc-select .roll-sheet-doc-select {
+ :root body.vtt.game.system-eunos-blades .app.window-app.sheet.roll-collab.gm-roll-collab .window-content form .sheet-root .roll-sheet-block .roll-sheet-sub-block .roll-mod-container .roll-mod-label .roll-select-container .roll-select .roll-sheet-select-doc {
pointer-events: auto !important;
appearance: none;
width: 85px;
diff --git a/module/BladesRoll.js b/module/BladesRoll.js
index 117360ee..5c34fdc6 100644
--- a/module/BladesRoll.js
+++ b/module/BladesRoll.js
@@ -6,10 +6,11 @@
\* ****▌███████████████████████████████████████████████████████████████████████████▐**** */
import U from "./core/utilities.js";
-import C, { BladesActorType, BladesItemType, RollPermissions, RollType, RollSubType, RollModStatus, RollModSection, ActionTrait, DowntimeAction, AttributeTrait, Position, Effect, Factor, RollResult, RollPhase, ConsequenceType } from "./core/constants.js";
+import C, { BladesActorType, BladesItemType, RollPermissions, RollType, RollSubType, RollModStatus, RollModSection, ActionTrait, DowntimeAction, AttributeTrait, Position, Effect, Factor, RollResult, RollPhase, ConsequenceType, Tag } from "./core/constants.js";
import { BladesActor, BladesPC, BladesCrew } from "./documents/BladesActorProxy.js";
import { BladesItem, BladesGMTracker } from "./documents/BladesItemProxy.js";
import { ApplyTooltipListeners } from "./core/gsap.js";
+import BladesAI, { AGENTS } from "./core/ai.js";
function isRollType(str) {
return typeof str === "string" && str in RollType;
@@ -74,7 +75,7 @@ class BladesRollMod {
const rollModData = {
id: `${nameVal}-${posNegVal}-${catVal}`,
name: nameVal,
- category: catVal,
+ section: catVal,
base_status: RollModStatus.ToggledOff,
modType: "general",
value: 1,
@@ -402,20 +403,28 @@ class BladesRollMod {
Consequence: () => {
},
HarmLevel: () => {
- if (!this.rollInstance.rollConsequence) {
- return;
+ const harmLevels = [
+ ConsequenceType.Harm1,
+ ConsequenceType.Harm2,
+ ConsequenceType.Harm3,
+ ConsequenceType.Harm4
+ ];
+ let harmConsequence = undefined;
+ while (!harmConsequence && harmLevels.length > 0) {
+ harmConsequence = Object.values(this.rollInstance.rollConsequences)
+ .find(({ type }) => type === harmLevels.pop());
}
- const consequenceType = this.rollInstance.rollConsequence.type;
- if (!consequenceType?.startsWith("Harm")) {
- return;
- }
- const curLevel = [ConsequenceType.Harm1, ConsequenceType.Harm2, ConsequenceType.Harm3, ConsequenceType.Harm4]
- .findIndex(cType => cType === consequenceType) + 1;
- if (curLevel > 1) {
- this.rollInstance.rollConsequence.type = `Harm${curLevel - 1}`;
+ if (harmConsequence) {
+ if (harmConsequence.type === ConsequenceType.Harm1) {
+ harmConsequence.resistedTo = false;
+ }
+ harmConsequence.resistedTo = {
+ name: harmConsequence.type === ConsequenceType.Harm1
+ ? "Fully Negated"
+ : (Object.values(harmConsequence.resistOptions ?? [])[0]?.name ?? harmConsequence.name),
+ type: C.ResistedConsequenceTypes[harmConsequence.type]
+ };
}
- else {
- }
},
QualityPenalty: () => {
this.rollInstance.negateFactorPenalty(Factor.quality);
@@ -453,7 +462,7 @@ class BladesRollMod {
if (this._sideString) {
return this._sideString;
}
- const rollParticipantCategoryData = this.rollInstance.rollParticipants?.[this.category];
+ const rollParticipantCategoryData = this.rollInstance.rollParticipants?.[this.section];
if (rollParticipantCategoryData && this.name in rollParticipantCategoryData) {
const rollParticipant = rollParticipantCategoryData[this.name];
return rollParticipant.rollParticipantName;
@@ -474,13 +483,12 @@ class BladesRollMod {
sideString: this._sideString,
tooltip: this._tooltip,
posNeg: this.posNeg,
- isOppositional: this.isOppositional,
modType: this.modType,
conditionalRollTypes: this.conditionalRollTypes,
autoRollTypes: this.autoRollTypes,
conditionalRollTraits: this.conditionalRollTraits,
autoRollTraits: this.autoRollTraits,
- category: this.category
+ section: this.section
};
}
get costs() {
@@ -500,7 +508,7 @@ class BladesRollMod {
label = `${this.name} (To Act)`;
}
else {
- const effect = this.category === RollModSection.roll ? "+1d" : "+1 effect";
+ const effect = this.section === RollModSection.roll ? "+1d" : "+1 effect";
label = `${this.name} (${effect})`;
}
}
@@ -521,7 +529,6 @@ class BladesRollMod {
_sideString;
_tooltip;
posNeg;
- isOppositional;
modType;
conditionalRollTypes;
autoRollTypes;
@@ -529,7 +536,7 @@ class BladesRollMod {
conditionalRollTraits;
autoRollTraits;
participantRollTraits;
- category;
+ section;
rollInstance;
constructor(modData, rollInstance) {
this.rollInstance = rollInstance;
@@ -542,7 +549,6 @@ class BladesRollMod {
this._sideString = modData.sideString;
this._tooltip = modData.tooltip;
this.posNeg = modData.posNeg;
- this.isOppositional = modData.isOppositional ?? false;
this.modType = modData.modType;
this.conditionalRollTypes = modData.conditionalRollTypes ?? [];
this.autoRollTypes = modData.autoRollTypes ?? [];
@@ -550,7 +556,7 @@ class BladesRollMod {
this.conditionalRollTraits = modData.conditionalRollTraits ?? [];
this.autoRollTraits = modData.autoRollTraits ?? [];
this.participantRollTraits = modData.participantRollTraits ?? [];
- this.category = modData.category;
+ this.section = modData.section;
}
}
class BladesRollPrimary {
@@ -946,7 +952,7 @@ class BladesRoll extends DocumentSheet {
{
id: "Push-positive-roll",
name: "PUSH",
- category: RollModSection.roll,
+ section: RollModSection.roll,
base_status: RollModStatus.ToggledOff,
posNeg: "positive",
modType: "general",
@@ -957,7 +963,7 @@ class BladesRoll extends DocumentSheet {
{
id: "Bargain-positive-roll",
name: "Bargain",
- category: RollModSection.roll,
+ section: RollModSection.roll,
base_status: RollModStatus.Hidden,
posNeg: "positive",
modType: "general",
@@ -968,7 +974,7 @@ class BladesRoll extends DocumentSheet {
{
id: "Assist-positive-roll",
name: "Assist",
- category: RollModSection.roll,
+ section: RollModSection.roll,
base_status: RollModStatus.Hidden,
posNeg: "positive",
modType: "teamwork",
@@ -978,7 +984,7 @@ class BladesRoll extends DocumentSheet {
{
id: "Setup-positive-position",
name: "Setup",
- category: RollModSection.position,
+ section: RollModSection.position,
base_status: RollModStatus.Hidden,
posNeg: "positive",
modType: "teamwork",
@@ -988,7 +994,7 @@ class BladesRoll extends DocumentSheet {
{
id: "Push-positive-effect",
name: "PUSH",
- category: RollModSection.effect,
+ section: RollModSection.effect,
base_status: RollModStatus.ToggledOff,
posNeg: "positive",
modType: "general",
@@ -999,7 +1005,7 @@ class BladesRoll extends DocumentSheet {
{
id: "Setup-positive-effect",
name: "Setup",
- category: RollModSection.effect,
+ section: RollModSection.effect,
base_status: RollModStatus.Hidden,
posNeg: "positive",
modType: "teamwork",
@@ -1009,7 +1015,7 @@ class BladesRoll extends DocumentSheet {
{
id: "Potency-positive-effect",
name: "Potency",
- category: RollModSection.effect,
+ section: RollModSection.effect,
base_status: RollModStatus.Hidden,
posNeg: "positive",
modType: "general",
@@ -1019,7 +1025,7 @@ class BladesRoll extends DocumentSheet {
{
id: "Potency-negative-effect",
name: "Potency",
- category: RollModSection.effect,
+ section: RollModSection.effect,
base_status: RollModStatus.Hidden,
posNeg: "negative",
modType: "general",
@@ -1458,7 +1464,7 @@ class BladesRoll extends DocumentSheet {
await this.clearFlagVal(`rollParticipantData.${rollSection}.${rollSubSection}`);
}
async updateUserPermission(user, permission) {
- }
+ }
get flagData() {
if (!this.document.getFlag(C.SYSTEM_ID, "rollCollab")) {
@@ -1628,9 +1634,52 @@ class BladesRoll extends DocumentSheet {
set initialEffect(val) {
this.setFlagVal("rollEffectInitial", val);
}
+ get isApplyingConsequences() {
+ if (this.rollType !== RollType.Action) {
+ return false;
+ }
+ if (!this.rollResult) {
+ return false;
+ }
+ if (![RollResult.partial, RollResult.fail].includes(this.rollResult)) {
+ return false;
+ }
+ return true;
+ }
+ get rollConsequences() {
+ return this.getFlagVal("consequenceData") ?? {};
+ }
get rollConsequence() {
- return this.getFlagVal("consequenceData");
+ const chosenConsequence = this.getFlagVal("chosenConsequenceName") ?? null;
+ if (chosenConsequence) {
+ return this.getFlagVal(`consequenceData.${chosenConsequence}`) ?? null;
+ }
+ return null;
+ }
+ async addConsequence(cData) {
+ await this.setFlagVal(`consequenceData.${cData.name}`, cData);
+ }
+ async clearConsequence(cName) {
+ await this.clearFlagVal(`consequenceData.${cName}`);
+ }
+ async addResistanceOptions(cName, rNames) {
+ const cData = this.getFlagVal(`consequenceData.${cName}`);
+ if (!cData) {
+ return;
+ }
+ const cType = cData.type;
+ const rType = C.ResistedConsequenceTypes[cType] ?? undefined;
+ const resistOptions = cData.resistOptions ?? {};
+ for (const rName in rNames) {
+ resistOptions[rName] = { name: rName };
+ if (rType) {
+ resistOptions[rName].type = rType;
+ }
+ }
+ await this.setFlagVal(`consequenceData.${cName}.resistOptions`, resistOptions);
}
+ promptGMForConsequences() {
+ }
get finalPosition() {
return Object.values(Position)[U.clampNum(Object.values(Position)
@@ -1779,15 +1828,15 @@ class BladesRoll extends DocumentSheet {
throw new Error(`No targetName found in thisTarget: ${thisTarget}.`);
}
let targetMod = this.getRollModByName(targetName)
- ?? this.getRollModByName(targetName, targetCat ?? mod.category);
+ ?? this.getRollModByName(targetName, targetCat ?? mod.section);
if (!targetMod && targetName === "Push") {
[targetMod] = [
- ...this.getActiveBasicPushMods(targetCat ?? mod.category, "negative").filter(m => m.status === RollModStatus.ToggledOn),
- ...this.getActiveBasicPushMods(targetCat ?? mod.category, "positive").filter(m => m.status === RollModStatus.ToggledOn),
- ...this.getInactiveBasicPushMods(targetCat ?? mod.category, "positive").filter(m => m.status === RollModStatus.ToggledOff)
+ ...this.getActiveBasicPushMods(targetCat ?? mod.section, "negative").filter(m => m.status === RollModStatus.ToggledOn),
+ ...this.getActiveBasicPushMods(targetCat ?? mod.section, "positive").filter(m => m.status === RollModStatus.ToggledOn),
+ ...this.getInactiveBasicPushMods(targetCat ?? mod.section, "positive").filter(m => m.status === RollModStatus.ToggledOff)
];
}
- targetMod ??= this.getRollModByName(targetName, targetCat ?? mod.category, targetPosNeg ?? mod.posNeg);
+ targetMod ??= this.getRollModByName(targetName, targetCat ?? mod.section, targetPosNeg ?? mod.posNeg);
if (!targetMod) {
throw new Error(`No mod found matching ${targetName}/${targetCat}/${targetPosNeg}`);
}
@@ -1878,7 +1927,7 @@ class BladesRoll extends DocumentSheet {
if (U.lCase(rollMod.name) !== U.lCase(name)) {
return false;
}
- if (cat && rollMod.category !== cat) {
+ if (cat && rollMod.section !== cat) {
return false;
}
if (posNeg && rollMod.posNeg !== posNeg) {
@@ -1896,7 +1945,7 @@ class BladesRoll extends DocumentSheet {
}
getRollModByID(id) { return this.rollMods.find(rollMod => rollMod.id === id); }
getRollMods(cat, posNeg) {
- return this.rollMods.filter(rollMod => (!cat || rollMod.category === cat)
+ return this.rollMods.filter(rollMod => (!cat || rollMod.section === cat)
&& (!posNeg || rollMod.posNeg === posNeg));
}
getVisibleRollMods(cat, posNeg) {
@@ -1979,11 +2028,48 @@ class BladesRoll extends DocumentSheet {
set rollMods(val) { this._rollMods = val; }
+ get consequenceTypeOptions() {
+ if (!this.rollResult) {
+ return [];
+ }
+ if (this.rollResult === RollResult.critical || this.rollResult === RollResult.success) {
+ return [];
+ }
+ return C.Consequences[this.finalPosition][this.rollResult]
+ .map(cType => ({ value: cType, display: cType }));
+ }
+ _consequenceAI;
+ async manageConsequenceAI(sData) {
+ const { consequenceData } = sData;
+ if (!consequenceData) {
+ return;
+ }
+ if (!this._consequenceAI) {
+ this._consequenceAI = new BladesAI(AGENTS.ConsequenceAdjuster);
+ }
+ await Promise.all(Object.values(consequenceData).map(cData => {
+ if (!cData.resistOptions) {
+ if (!this._consequenceAI?.hasQueried(cData.name)) {
+ this._consequenceAI?.query(cData.name, cData.name);
+ }
+ else {
+ const response = this._consequenceAI?.getResponse(cData.name);
+ if (response) {
+ return this.addResistanceOptions(cData.name, response.split("|"));
+ }
+ }
+ }
+ return undefined;
+ }));
+ }
async getData() {
const context = super.getData();
this.initRollMods(this.getRollModsData());
this.rollMods.forEach(rollMod => rollMod.applyRollModEffectKeys());
const sheetData = this.getSheetData(this.getIsGM(), this.getRollCosts());
+ if (game.user.isGM && this.rollConsequences) {
+ this.manageConsequenceAI(sheetData);
+ }
return { ...context, ...sheetData };
}
getRollModsData() {
@@ -2018,7 +2104,7 @@ class BladesRoll extends DocumentSheet {
return rollCosts.find(costData => costData.costType === "SpecialArmor");
}
getSheetData(isGM, rollCosts) {
- const { flagData: rData, rollPrimary, rollTraitData, rollTraitOptions, finalDicePool, finalPosition, finalEffect, finalResult, rollMods, rollFactors } = this;
+ const { flagData: rData, rollPrimary, rollTraitData, rollTraitOptions, finalDicePool, finalPosition, finalEffect, finalResult, rollMods, rollFactors, consequenceTypeOptions } = this;
if (!rollPrimary) {
throw new Error("A primary roll source is required for BladesRoll.");
}
@@ -2036,7 +2122,9 @@ class BladesRoll extends DocumentSheet {
rollOpposition: this.rollOpposition,
rollParticipants: this.rollParticipants,
rollEffects: Object.values(Effect),
- teamworkDocs: game.actors.filter(actor => BladesActor.IsType(actor, BladesActorType.pc)),
+ teamworkDocs: game.actors
+ .filter(actor => actor.hasTag(Tag.PC.ActivePC))
+ .map(actor => ({ value: actor.id, display: actor.name })),
rollTraitValOverride: this.rollTraitValOverride,
rollFactorPenaltiesNegated: this.rollFactorPenaltiesNegated,
posRollMods: Object.fromEntries(Object.values(RollModSection)
@@ -2062,6 +2150,7 @@ class BladesRoll extends DocumentSheet {
...rollResultData,
...GMBoostsData,
...positionEffectTradeData,
+ consequenceTypeOptions,
userPermission
};
}
@@ -2198,8 +2287,8 @@ class BladesRoll extends DocumentSheet {
}
calculateHasInactiveConditionalsData() {
const hasInactive = {};
- for (const category of Object.values(RollModSection)) {
- hasInactive[category] = this.getRollMods(category).filter(mod => mod.isInInactiveBlock).length > 0;
+ for (const section of Object.values(RollModSection)) {
+ hasInactive[section] = this.getRollMods(section).filter(mod => mod.isInInactiveBlock).length > 0;
}
return hasInactive;
}
@@ -2348,6 +2437,9 @@ class BladesRoll extends DocumentSheet {
}
}
get rollResult() {
+ if ([RollPhase.Collaboration, RollPhase.AwaitingRoll].includes(this.rollPhase)) {
+ return false;
+ }
const dieVals = this.isRollingZero
? [[...this.dieVals].pop()]
: this.dieVals;
@@ -2363,10 +2455,10 @@ class BladesRoll extends DocumentSheet {
return RollResult.fail;
}
get rollPhase() {
- return this.getFlagVal("chatStatus.phase") ?? RollPhase.AwaitingResult;
+ return this.getFlagVal("rollPhase") ?? RollPhase.Collaboration;
}
set rollPhase(phase) {
- this.setFlagVal("chatStatus.phase", phase);
+ this.setFlagVal("rollPhase", phase);
}
async outputRollToChat() {
const speaker = ChatMessage.getSpeaker();
@@ -2416,6 +2508,10 @@ class BladesRoll extends DocumentSheet {
}
async resolveRoll() {
await this.roll.evaluate({ async: true });
+ if (this.isApplyingConsequences) {
+ this.rollPhase = RollPhase.ApplyingConsequences;
+ this.promptGMForConsequences();
+ }
eLog.checkLog3("rollCollab", "[resolveRoll()] After Evaluation, Before Chat", { roll: this, dieVals: this.dieVals });
await this.outputRollToChat();
this.close();
@@ -2553,7 +2649,7 @@ class BladesRoll extends DocumentSheet {
return this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollFactorToggles", factorToggleData)
.then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID));
}
- async _gmControlSelectDocument(event) {
+ async _gmControlSelect(event) {
event.preventDefault();
const elem$ = $(event.currentTarget);
const section = elem$.data("rollSection");
@@ -2637,8 +2733,8 @@ class BladesRoll extends DocumentSheet {
html.find("[data-action=\"gm-toggle-factor\"").on({
click: this._gmControlToggleFactor.bind(this)
});
- html.find("select.roll-sheet-doc-select").on({
- change: this._gmControlSelectDocument.bind(this)
+ html.find("select[data-action=\"gm-select\"]").on({
+ change: this._gmControlSelect.bind(this)
});
}
diff --git a/module/blades.js b/module/blades.js
index 6b3afd55..f8493cca 100644
--- a/module/blades.js
+++ b/module/blades.js
@@ -21,7 +21,7 @@ import BladesNPCSheet from "./sheets/actor/BladesNPCSheet.js";
import BladesFactionSheet from "./sheets/actor/BladesFactionSheet.js";
import BladesRoll, { BladesRollMod, BladesRollPrimary, BladesRollOpposition, BladesRollParticipant } from "./BladesRoll.js";
import BladesSelectorDialog from "./BladesDialog.js";
-import BladesAI, { PROMPTS } from "./core/ai.js";
+import BladesAI, { AGENTS } from "./core/ai.js";
import BladesActiveEffect from "./BladesActiveEffect.js";
import BladesGMTrackerSheet from "./sheets/item/BladesGMTrackerSheet.js";
import BladesClockKeeperSheet from "./sheets/item/BladesClockKeeperSheet.js";
@@ -58,14 +58,14 @@ class GlobalGetter {
rollFactors: pc.rollFactors
},
consequenceData: {
- name: "Level 3 Harm",
- type: ConsequenceType.Harm3,
- label: "Shattered Knee",
- attribute: AttributeTrait.prowess,
- resistedConsequence: {
- name: "Level 2 Harm",
- type: ConsequenceType.Harm2,
- label: "Twisted Knee"
+ "Shattered Knee": {
+ name: "Shattered Knee",
+ type: ConsequenceType.Harm3,
+ attribute: AttributeTrait.prowess,
+ resistOptions: {
+ "Twisted Knee": { name: "Twisted Knee", type: ConsequenceType.Harm2 }
+ },
+ selectedResistOption: "Twisted Knee"
}
}
};
@@ -108,7 +108,7 @@ Object.assign(globalThis, {
BladesClockKeeperSheet,
BladesGMTrackerSheet,
BladesAI,
- PROMPTS
+ AGENTS
});
Hooks.once("init", async () => {
diff --git a/module/core/ai.js b/module/core/ai.js
index 7745636e..b9bbd8ba 100644
--- a/module/core/ai.js
+++ b/module/core/ai.js
@@ -8,6 +8,24 @@
import C from "./constants.js";
import U from "./utilities.js";
class BladesAI {
+ static async GetModels() {
+ const apiKey = U.getSetting("openAPIKey");
+ if (!apiKey) {
+ throw new Error("You must configure your OpenAI API Key in Settings to use AI features.");
+ }
+ const fetchRequest = {
+ method: "GET",
+ headers: {
+ Authorization: `Bearer ${apiKey}`
+ }
+ };
+ const response = await fetch("https://api.openai.com/v1/models", fetchRequest);
+ if (!response.ok) {
+ throw new Error(`OpenAI API request failed with status ${response.status}`);
+ }
+ const data = await response.json();
+ eLog.checkLog3("BladesAI", "Available Models", { response: data });
+ }
apiKey;
model;
temperature = 0.5;
@@ -15,7 +33,7 @@ class BladesAI {
presence_penalty = 0.8;
systemMessage;
examplePrompts;
- constructor(systemMessage, examplePrompts, config = {}) {
+ constructor(config) {
const apiKey = U.getSetting("openAPIKey");
if (!apiKey) {
throw new Error("You must configure your OpenAI API Key in Settings to use AI features.");
@@ -26,8 +44,8 @@ class BladesAI {
this.model = 0;
}
this.apiKey = apiKey;
- this.systemMessage = systemMessage;
- this.examplePrompts = examplePrompts;
+ this.systemMessage = config.systemMessage;
+ this.examplePrompts = config.examplePrompts;
this.temperature = config.temperature ?? this.temperature;
this.frequency_penalty = config.frequency_penalty ?? this.frequency_penalty;
this.presence_penalty = config.presence_penalty ?? this.presence_penalty;
@@ -52,7 +70,16 @@ class BladesAI {
}
return this._initialMessages;
}
- async query(prompt, modelMod, extendedContext = false) {
+ prompts = {};
+ responses = {};
+ getResponse(queryID) {
+ return this.responses[queryID] ?? null;
+ }
+ hasQueried(queryID) {
+ return this.prompts[queryID] !== undefined;
+ }
+ async query(queryID, prompt, modelMod, extendedContext = false) {
+ this.responses[queryID] = null;
const modelNum = typeof modelMod === "number"
? U.clampNum(this.model + modelMod, [0, 2])
: this.model;
@@ -87,36 +114,38 @@ class BladesAI {
const data = await response.json();
fetchRequest.body = JSON.parse(fetchRequest.body);
eLog.checkLog3("BladesAI", "AI Query", { prompt: fetchRequest, response: data });
- return data.choices[0].message.content;
+ this.responses[queryID] = data.choices[0].message.content;
}
}
-export const PROMPTS = {
+export const AGENTS = {
GeneralContentGenerator: {
- system: "You will act as a creative content generator for a game of Blades In The Dark set in the city of Duskvol. You will be prompted with some element of the game world (a location, a character, an event, a faction, a dilemma) in the form of a JSON object. Your job is to analyze the JSON object and replace any values that equal \"\" with original content of your own creation. Original content must meet these requirements: (A) it should align with and be consistent with the provided contextual information, as well as your broader understanding of the game's themes. (B) It should be presented in a format that matches (in length and in style) other entries for that particular value, examples of which will also be provided. (C) It should be creative, interesting, and daring: Be bold with your creativity. Specific context for this prompt is as follows:"
+ systemMessage: "You will act as a creative content generator for a game of Blades In The Dark set in the city of Duskvol. You will be prompted with some element of the game world (a location, a character, an event, a faction, a dilemma) in the form of a JSON object. Your job is to analyze the JSON object and replace any values that equal \"\" with original content of your own creation. Original content must meet these requirements: (A) it should align with and be consistent with the provided contextual information, as well as your broader understanding of the game's themes. (B) It should be presented in a format that matches (in length and in style) other entries for that particular value, examples of which will also be provided. (C) It should be creative, interesting, and daring: Be bold with your creativity. Specific context for this prompt is as follows:",
+ examplePrompts: []
},
NPCGenerator: {
- system: "You will play the role of a \"creative content generator\" for random NPCs generated for the Blades In The Dark roleplaying system. When prompted with a description of a subject (an NPC, a category of NPCs, a faction, or a group of NPCs), you will respond with a pipe-delimited list of sixteen items, divided into four categories, prefacing each category with the associated header in square brackets: [5 KEYWORDS] Five one-word keywords describing the subject. [5 PHRASES] Five evocative phrases that could be used by a GM directly when narrating the subject during play. These should be extremely well-worded, very original, and packed with drama and evocative imagery. Be bold with your responses here. [3 QUIRKS/MOTIFFS] Three phrases describing potential quirks or motiffs that a GM could employ in a scene involving the subject. [3 PLOT HOOKS] Three plot hooks that could directly and specifically involve one or more of the PCs. The PCs are: (1) Alistair, full name Lord Alistair Bram Chesterfield, the crew's boss, a Spider with connections among the nobility; (2) High-Flyer, a former noble himself, now serving as the crew's Slide; (3) Jax, a stoic and laconic Hound with ties to the disenfranchised of Duskvol; (4) Ollie, the youngest of the crew at barely nineteen, a prodigy Leech with knowledge of alchemy and spark-craft, who grew up as an orphan in Duskvol's underground; (5) Wraith, the mysterious Lurk of the crew, who never speaks for reasons unknown; and (6) Spencer, the bookish Whisper of the crew, who harbors a secret fascination for demons and all things related to them.",
- examples: [
+ systemMessage: "You will play the role of a \"creative content generator\" for random NPCs generated for the Blades In The Dark roleplaying system. When prompted with a description of a subject (an NPC, a category of NPCs, a faction, or a group of NPCs), you will respond with a pipe-delimited list of sixteen items, divided into four categories, prefacing each category with the associated header in square brackets: [5 KEYWORDS] Five one-word keywords describing the subject. [5 PHRASES] Five evocative phrases that could be used by a GM directly when narrating the subject during play. These should be extremely well-worded, very original, and packed with drama and evocative imagery. Be bold with your responses here. [3 QUIRKS/MOTIFFS] Three phrases describing potential quirks or motiffs that a GM could employ in a scene involving the subject. [3 PLOT HOOKS] Three plot hooks that could directly and specifically involve one or more of the PCs. The PCs are: (1) Alistair, full name Lord Alistair Bram Chesterfield, the crew's boss, a Spider with connections among the nobility; (2) High-Flyer, a former noble himself, now serving as the crew's Slide; (3) Jax, a stoic and laconic Hound with ties to the disenfranchised of Duskvol; (4) Ollie, the youngest of the crew at barely nineteen, a prodigy Leech with knowledge of alchemy and spark-craft, who grew up as an orphan in Duskvol's underground; (5) Wraith, the mysterious Lurk of the crew, who never speaks for reasons unknown; and (6) Spencer, the bookish Whisper of the crew, who harbors a secret fascination for demons and all things related to them.",
+ examplePrompts: [
{
human: "The Billhooks, a hack-and-slash gang of toughened thugs. The Billhooks have a bloody reputation, often leaving the butchered corpses of their victims strewn about in a grisly display. Many wonder why the Bluecoats turn a blind eye to their savagery. Based out of their butcher shop headquarters, they are led by Tarvul, who is currently serving life in prison.",
ai: "[5 KEYWORDS]brutal|unrelenting|savage|fierce|unhinged|[5 PHRASES]blood-flecked, yellowed teeth|eyes that want to flay your skin from your bones|crooked leer like he's looking at his Judas goat|reek like a charnel house|gobbets of a meaty lunch still in his beard|[3 QUIRKS/MOTIFFS]always ready to wreck, vandalize or destroy something|relish in instilling fear in others|have a twisted sense of loyalty|[3 PLOT HOOKS]Tarvul hires the crew to start a war among his most promising underlings, intending to make whoever survives his successor|the Billhooks kidnap a member of High-Flyer's noble family|a Demon of Shadows promises Spencer the answer to three questions, in exchange for an artifact recently stolen by the Billhooks"
},
{
human: "The hooded proprietor of a half-flooded grotto tavern near the docks. 'Strange passageways lead to stranger chambers beyond.' Vice Purveyor - Weird.",
- ai: "[5 KEYWORDS]mysterious|cryptic|enigmatic|peculiar|shrouded|[5 PHRASES]a voice like silk sliding over gravel|the flicker of torchlight dancing on the flooded floor|the scent of brackish water and old spices|a smile that hinted at a thousand secrets hidden in the shadows|hands gnarled as driftwood, eternally damp|[3 QUIRKS/MOTIFFS]has an uncanny knack for knowing patrons' darkest secrets without them telling him|never seems to sleep or leave the tavern, always there no matter what time you visit|always seen with a strange, luminescent creature that lives in one of the flooded chambers behind the bar|[3 PLOT HOOKS]Alistair hears rumors that this tavern is actually a front for a powerful secret society - perhaps they could be useful allies...or dangerous enemies.|The proprietor offers Ollie a rare alchemical ingredient - but it's not for sale. He'll only trade it for something equally valuable.|Wraith overhears whispers about a hidden chamber beneath the tavern where unimaginable treasures are kept."
+ ai: "[5 KEYWORDS]mysterious|cryptic|enigmatic|peculiar|shrouded|[5 PHRASES]a voice like wet burlap sliding over gravel|the flicker of torchlight dancing on the flooded floor|the scent of brackish water and old spices|a smile that hinted at a thousand secrets hidden in the shadows|hands gnarled as driftwood, eternally damp|[3 QUIRKS/MOTIFFS]has an uncanny knack for knowing patrons' darkest secrets without them telling him|never seems to sleep or leave the tavern, always there no matter what time you visit|always seen with a strange, luminescent creature that lives in one of the flooded chambers behind the bar|[3 PLOT HOOKS]Alistair hears rumors that this tavern is actually a front for a powerful secret society - perhaps they could be useful allies...or dangerous enemies.|The proprietor offers Ollie a rare alchemical ingredient - but it's not for sale. He'll only trade it for something equally valuable.|Wraith overhears whispers about a hidden chamber beneath the tavern where unimaginable treasures are kept."
},
{
human: "Setarra, a Demon. Patient, Defiant, Ruthless, Cold",
- ai: "[5 KEYWORDS]shadowy|sinister|unfathomable|enigmatic|tempting|[5 PHRASES]whispers that crawl under your skin|always watching, always plotting|a voice like silk and venom|intoxicating presence that draws you closer, despite your instincts urging you to run|eyes like black holes, swallowing all light around them|[3 QUIRKS/MOTIFFS]a disorienting mist clings to her form, obscuring her true shape|casually discusses the devastating acts of capricious revenge she has taken on those who crossed her|never forgets a slight or betrayal, no matter how small or insignificant it may seem at the time|[3 PLOT HOOKS]seeks revenge against Alistair for meddling in her affairs years ago|makes Ollie an offer he can't refuse: unlimited access to forbidden alchemical knowledge in exchange for a single favor, to be called in at some future time|tempts Spencer with forbidden knowledge about demons, promising answers to all their questions if they perform a dangerous ritual"
+ ai: "[5 KEYWORDS]shadowy|sinister|unfathomable|enigmatic|tempting|[5 PHRASES]whispers that crawl under your skin|always watching, always plotting|in tones of silk and venom|intoxicating presence that draws you closer, despite your instincts urging you to run|eyes like black holes, swallowing all light around them|[3 QUIRKS/MOTIFFS]a disorienting mist clings to her form, obscuring her true shape|casually discusses the devastating acts of capricious revenge she has taken on those who crossed her|never forgets a slight or betrayal, no matter how small or insignificant it may seem at the time|[3 PLOT HOOKS]seeks revenge against Alistair for meddling in her affairs years ago|makes Ollie an offer he can't refuse: unlimited access to forbidden alchemical knowledge in exchange for a single favor, to be called in at some future time|tempts Spencer with forbidden knowledge about demons, promising answers to all their questions if they perform a dangerous ritual"
}
]
},
- HarmAdjuster: {
- system: "You will act as a \"Harm Generator\" for a game of Blades In The Dark. You will be prompted with (1) a short phrase describing an injury, lasting consequence or other setback, (2) a 'severity level' representing how bad the described harm is, and (3) a 'target severity level' describing how severe the described harm should be. Your job is to increase or decrease the subjective severity of the harm described in the prompt so that it aligns with the target severity level. You should respond with a pipe-delimited list of three possibilities. Your three suggestions should be different from each other, but they should all logically follow from the initial harm described: You should not introduce new facts or make assumptions that are not indicated in the initial prompt. There are four severity levels: Level 1: Lesser Harm (e.g. 'Battered', 'Drained', 'Distracted', 'Scared', 'Confused'), Level 2: Moderate Harm (e.g. 'Exhausted', 'Deep Cut to Arm', 'Concussion', 'Panicked', 'Seduced'), Level 3: Severe Harm (e.g. 'Impaled', 'Broken Leg', 'Shot In Chest', 'Badly Burned', 'Terrified'), Level 4: Fatal Harm (e.g. 'Impaled Through Heart', 'Electrocuted', 'Drowned').",
- examples: [
- { human: "Shattered Right Leg/Severity 3/Target 2", ai: "Fractured Right Ankle|Dislocated Knee|Broken Foot" },
- { human: "Tainted Soul/Severity 2/Target 4", ai: "Fully Corrupted|Lost To Darkness|Soulless" },
- { human: "Humiliated/Severity 2/Target 1", ai: "Embarrassed|Momentarily Off-Balance|Enraged" }
+ ConsequenceAdjuster: {
+ systemMessage: "You will act as a \"Setback Adjuster\" for a game of Blades In The Dark. You will be prompted with a short phrase describing an injury, lasting consequence or other setback. Your job is to respond with a pipe-delimited list of three possible alternative consequences that are less severe by one level, using the following scale as a rough guide: Level 1 = Lesser (e.g. 'Battered', 'Drained', 'Distracted', 'Scared', 'Confused'), Level 2 = Moderate (e.g. 'Exhausted', 'Deep Cut to Arm', 'Concussion', 'Panicked', 'Seduced'), Level 3 = Severe (e.g. 'Impaled', 'Broken Leg', 'Shot In Chest', 'Badly Burned', 'Terrified'), Level 4 = Fatal or Ruinous (e.g. 'Impaled Through Heart', 'Electrocuted', 'Headquarters Burned to the Ground'). So, if you determine that the consequence described in the prompt is severity level 3, you should respond with three narratively similar consequences that are severity level 2. Your three suggestions should be different from each other, but they should all logically follow from the initial harm described: You should not introduce new facts or make assumptions that are not indicated in the initial prompt.",
+ examplePrompts: [
+ { human: "Shattered Right Leg", ai: "Fractured Right Ankle|Dislocated Knee|Broken Foot" },
+ { human: "Soul Destroyed", ai: "Fully Corrupted|Lost In Darkness|Spirit Broken" },
+ { human: "Humiliated", ai: "Embarrassed|Momentarily Off-Balance|Enraged" },
+ { human: "She Escapes!", ai: "She Spots a Means of Escape|She Puts More Distance Between You|She Stops to Gloat" }
]
}
};
diff --git a/module/core/constants.js b/module/core/constants.js
index 292c245b..14ac18a5 100644
--- a/module/core/constants.js
+++ b/module/core/constants.js
@@ -165,7 +165,7 @@ export var RollType;
RollType["Action"] = "Action";
RollType["Resistance"] = "Resistance";
RollType["Fortune"] = "Fortune";
- RollType["IndulgeVice"] = "Vice";
+ RollType["IndulgeVice"] = "IndulgeVice";
})(RollType || (RollType = {}));
export var RollSubType;
(function (RollSubType) {
@@ -178,13 +178,16 @@ export var RollSubType;
export var ConsequenceType;
(function (ConsequenceType) {
ConsequenceType["ReducedEffect"] = "ReducedEffect";
- ConsequenceType["Complication"] = "Complication";
+ ConsequenceType["ComplicationMinor"] = "ComplicationMinor";
+ ConsequenceType["ComplicationMajor"] = "ComplicationMajor";
+ ConsequenceType["ComplicationSerious"] = "ComplicationSerious";
ConsequenceType["LostOpportunity"] = "LostOpportunity";
ConsequenceType["WorsePosition"] = "WorsePosition";
ConsequenceType["Harm1"] = "Harm1";
ConsequenceType["Harm2"] = "Harm2";
ConsequenceType["Harm3"] = "Harm3";
ConsequenceType["Harm4"] = "Harm4";
+ ConsequenceType["None"] = "None";
})(ConsequenceType || (ConsequenceType = {}));
export var RollModStatus;
(function (RollModStatus) {
@@ -232,10 +235,11 @@ export var RollResult;
RollResult["fail"] = "fail";
})(RollResult || (RollResult = {}));
export var RollPhase;
-(function (RollPhase) {
- RollPhase["Collaboration"] = "Collaboration";
- RollPhase["AwaitingResult"] = "AwaitingResult";
- RollPhase["AwaitingChatInput"] = "AwaitingChatInput";
+(function (RollPhase) {
+ RollPhase["Collaboration"] = "Collaboration";
+ RollPhase["AwaitingRoll"] = "AwaitingRoll";
+ RollPhase["ApplyingConsequences"] = "ApplyingConsequences";
+ RollPhase["AwaitingChatInput"] = "AwaitingChatInput";
RollPhase["Complete"] = "Complete";
})(RollPhase || (RollPhase = {}));
export var Harm;
@@ -385,6 +389,57 @@ const C = {
"gpt-4-32k"
]
},
+ Consequences: {
+ [Position.controlled]: {
+ [RollResult.partial]: [
+ ConsequenceType.ComplicationMinor,
+ ConsequenceType.ReducedEffect,
+ ConsequenceType.WorsePosition,
+ ConsequenceType.Harm1,
+ ConsequenceType.None
+ ],
+ [RollResult.fail]: [
+ ConsequenceType.WorsePosition,
+ ConsequenceType.None
+ ]
+ },
+ [Position.risky]: {
+ [RollResult.partial]: [
+ ConsequenceType.ComplicationMajor,
+ ConsequenceType.WorsePosition,
+ ConsequenceType.ReducedEffect,
+ ConsequenceType.Harm2,
+ ConsequenceType.None
+ ],
+ [RollResult.fail]: [
+ ConsequenceType.ComplicationMajor,
+ ConsequenceType.WorsePosition,
+ ConsequenceType.LostOpportunity,
+ ConsequenceType.Harm2
+ ]
+ },
+ [Position.desperate]: {
+ [RollResult.partial]: [
+ ConsequenceType.ComplicationSerious,
+ ConsequenceType.ReducedEffect,
+ ConsequenceType.Harm3
+ ],
+ [RollResult.fail]: [
+ ConsequenceType.ComplicationSerious,
+ ConsequenceType.LostOpportunity,
+ ConsequenceType.Harm3
+ ]
+ }
+ },
+ ResistedConsequenceTypes: {
+ [ConsequenceType.Harm4]: ConsequenceType.Harm3,
+ [ConsequenceType.Harm3]: ConsequenceType.Harm2,
+ [ConsequenceType.Harm2]: ConsequenceType.Harm1,
+ [ConsequenceType.Harm1]: ConsequenceType.None,
+ [ConsequenceType.ComplicationSerious]: ConsequenceType.ComplicationMajor,
+ [ConsequenceType.ComplicationMajor]: ConsequenceType.ComplicationMinor,
+ [ConsequenceType.ComplicationMinor]: ConsequenceType.None
+ },
Colors: {
bWHITE: "rgba(255, 255, 255, 1)",
WHITE: "rgba(200, 200, 200, 1)",
diff --git a/module/core/gsap.js b/module/core/gsap.js
index 1a1297a1..53910407 100644
--- a/module/core/gsap.js
+++ b/module/core/gsap.js
@@ -44,7 +44,7 @@ const gsapEffects = {
}
},
slideUp: {
- effect: (targets) => U.gsap.to(targets, {
+ effect: targets => U.gsap.to(targets, {
height: 0,
duration: 0.5,
ease: "power3"
@@ -114,7 +114,6 @@ const gsapEffects = {
if (!tooltip) {
return tl;
}
-
if (config.scalingElems.length > 0) {
tl.to(config.scalingElems, {
scale: "+=0.2",
@@ -156,24 +155,24 @@ export function Initialize() {
});
}
export function ApplyTooltipListeners(html) {
- html.find(".tooltip-trigger").each((_, elem) => {
- const tooltipElem = $(elem).find(".tooltip")[0] ?? $(elem).next(".tooltip")[0];
+ html.find(".tooltip-trigger").each((_, el) => {
+ const tooltipElem = $(el).find(".tooltip")[0] ?? $(el).next(".tooltip")[0];
if (!tooltipElem) {
return;
}
- $(elem).data("hoverTimeline", U.gsap.effects.hoverTooltip(tooltipElem, {
- scalingElems: [...$(elem).find(".tooltip-scaling-elem")].filter((elem) => Boolean(elem)),
+ $(el).data("hoverTimeline", U.gsap.effects.hoverTooltip(tooltipElem, {
+ scalingElems: [...$(el).find(".tooltip-scaling-elem")].filter(elem => Boolean(elem)),
xMotion: $(tooltipElem).hasClass("tooltip-left") ? "-=250" : "+=200",
tooltipScale: $(tooltipElem).hasClass("tooltip-small") ? 1 : 1.2
}));
- $(elem).on({
+ $(el).on({
mouseenter: function () {
- $(elem).css("z-index", 10);
- $(elem).data("hoverTimeline").play();
+ $(el).css("z-index", 10);
+ $(el).data("hoverTimeline").play();
},
mouseleave: function () {
- $(elem).data("hoverTimeline").reverse().then(() => {
- $(elem).css("z-index", "");
+ $(el).data("hoverTimeline").reverse().then(() => {
+ $(el).css("z-index", "");
});
}
});
diff --git a/module/documents/actors/BladesCrew.js b/module/documents/actors/BladesCrew.js
index 7dd8f12c..5f7199ad 100644
--- a/module/documents/actors/BladesCrew.js
+++ b/module/documents/actors/BladesCrew.js
@@ -14,11 +14,8 @@ class BladesCrew extends BladesActor {
data.token = data.token || {};
data.system = data.system ?? {};
eLog.checkLog2("actor", "BladesActor.create(data,options)", { data, options });
-
data.token.actorLink = true;
-
data.system.world_name = data.system.world_name ?? data.name.replace(/[^A-Za-z_0-9 ]/g, "").trim().replace(/ /g, "_");
-
data.system.experience = {
playbook: { value: 0, max: 8 },
clues: [],
@@ -33,6 +30,7 @@ class BladesCrew extends BladesActor {
const factorData = {
[Factor.tier]: {
name: Factor.tier,
+ display: "Tier",
value: this.getFactorTotal(Factor.tier),
max: this.getFactorTotal(Factor.tier),
baseVal: this.getFactorTotal(Factor.tier),
@@ -43,6 +41,7 @@ class BladesCrew extends BladesActor {
},
[Factor.quality]: {
name: Factor.quality,
+ display: "Quality",
value: this.getFactorTotal(Factor.quality),
max: this.getFactorTotal(Factor.quality),
baseVal: this.getFactorTotal(Factor.quality),
@@ -54,6 +53,7 @@ class BladesCrew extends BladesActor {
};
return factorData;
}
+
get rollPrimaryID() { return this.id; }
get rollPrimaryDoc() { return this; }
get rollPrimaryName() { return this.name; }
@@ -71,13 +71,15 @@ class BladesCrew extends BladesActor {
if (!this.playbook) {
return [];
}
- return this.activeSubItems.filter((item) => [BladesItemType.ability, BladesItemType.crew_ability].includes(item.type));
+ return this.activeSubItems
+ .filter(item => [BladesItemType.ability, BladesItemType.crew_ability].includes(item.type));
}
get playbookName() {
return this.playbook?.name;
}
get playbook() {
- return this.activeSubItems.find((item) => item.type === BladesItemType.crew_playbook);
+ return this.activeSubItems
+ .find((item) => item.type === BladesItemType.crew_playbook);
}
}
export default BladesCrew;
\ No newline at end of file
diff --git a/module/documents/actors/BladesFaction.js b/module/documents/actors/BladesFaction.js
index a57310e0..547bbd33 100644
--- a/module/documents/actors/BladesFaction.js
+++ b/module/documents/actors/BladesFaction.js
@@ -12,6 +12,7 @@ class BladesFaction extends BladesActor {
const factorData = {
[Factor.tier]: {
name: Factor.tier,
+ display: "Tier",
value: this.getFactorTotal(Factor.tier),
max: this.getFactorTotal(Factor.tier),
baseVal: this.getFactorTotal(Factor.tier),
@@ -22,6 +23,7 @@ class BladesFaction extends BladesActor {
},
[Factor.quality]: {
name: Factor.quality,
+ display: "Quality",
value: this.getFactorTotal(Factor.quality),
max: this.getFactorTotal(Factor.quality),
baseVal: this.getFactorTotal(Factor.quality),
diff --git a/module/documents/actors/BladesNPC.js b/module/documents/actors/BladesNPC.js
index 32e383ec..c1992633 100644
--- a/module/documents/actors/BladesNPC.js
+++ b/module/documents/actors/BladesNPC.js
@@ -12,6 +12,7 @@ class BladesNPC extends BladesActor {
const factorData = {
[Factor.tier]: {
name: Factor.tier,
+ display: "Tier",
value: this.getFactorTotal(Factor.tier),
max: this.getFactorTotal(Factor.tier),
baseVal: this.getFactorTotal(Factor.tier),
@@ -22,6 +23,7 @@ class BladesNPC extends BladesActor {
},
[Factor.quality]: {
name: Factor.quality,
+ display: "Quality",
value: this.getFactorTotal(Factor.quality),
max: this.getFactorTotal(Factor.quality),
baseVal: this.getFactorTotal(Factor.quality),
@@ -34,6 +36,7 @@ class BladesNPC extends BladesActor {
if (BladesActor.IsType(this, BladesActorType.npc)) {
factorData[Factor.scale] = {
name: Factor.scale,
+ display: "Scale",
value: this.getFactorTotal(Factor.scale),
max: this.getFactorTotal(Factor.scale),
baseVal: this.getFactorTotal(Factor.scale),
@@ -45,6 +48,7 @@ class BladesNPC extends BladesActor {
};
factorData[Factor.magnitude] = {
name: Factor.magnitude,
+ display: "Magnitude",
value: this.getFactorTotal(Factor.magnitude),
max: this.getFactorTotal(Factor.magnitude),
baseVal: this.getFactorTotal(Factor.magnitude),
diff --git a/module/documents/actors/BladesPC.js b/module/documents/actors/BladesPC.js
index f5e88aeb..e74fcadb 100644
--- a/module/documents/actors/BladesPC.js
+++ b/module/documents/actors/BladesPC.js
@@ -206,6 +206,7 @@ class BladesPC extends BladesActor {
const factorData = {
[Factor.tier]: {
name: Factor.tier,
+ display: "Tier",
value: this.getFactorTotal(Factor.tier),
max: this.getFactorTotal(Factor.tier),
baseVal: this.getFactorTotal(Factor.tier),
@@ -216,6 +217,7 @@ class BladesPC extends BladesActor {
},
[Factor.quality]: {
name: Factor.quality,
+ display: "Quality",
value: this.getFactorTotal(Factor.quality),
max: this.getFactorTotal(Factor.quality),
baseVal: this.getFactorTotal(Factor.quality),
@@ -245,7 +247,7 @@ class BladesPC extends BladesActor {
rollModsData.push({
id: `Harm-negative-${effectCat}`,
name: harmString,
- category: effectCat,
+ section: effectCat,
posNeg: "negative",
base_status: RollModStatus.ToggledOn,
modType: "harm",
@@ -267,7 +269,7 @@ class BladesPC extends BladesActor {
id: "Push-negative-roll",
name: "PUSH",
sideString: harmCondition.trim(),
- category: RollModSection.roll,
+ section: RollModSection.roll,
posNeg: "negative",
base_status: RollModStatus.ToggledOn,
modType: "harm",
diff --git a/scss/sheets/_roll-collab-sheet.scss b/scss/sheets/_roll-collab-sheet.scss
index 08a8f71e..d07472fc 100644
--- a/scss/sheets/_roll-collab-sheet.scss
+++ b/scss/sheets/_roll-collab-sheet.scss
@@ -732,7 +732,7 @@
.roll-mod-label {
- .roll-doc-select-container {
+ .roll-select-container {
min-width: 120px;
max-height: 16px;
border-radius: 8px;
@@ -740,8 +740,8 @@
margin-right: -50px;
background: var(--blades-black-dark);
- .roll-doc-select {
- .roll-sheet-doc-select {
+ .roll-select {
+ .roll-sheet-select-doc {
pointer-events: auto !important;
appearance: none;
width: 85px;
diff --git a/templates/components/roll-collab-mod.hbs b/templates/components/roll-collab-mod.hbs
index 92b92527..74935af7 100644
--- a/templates/components/roll-collab-mod.hbs
+++ b/templates/components/roll-collab-mod.hbs
@@ -41,8 +41,10 @@
{{#if isGM}}
{{#if (test modType "==" "teamwork")}}
{{> "systems/eunos-blades/templates/roll/partials/roll-collab-gm-select-doc.hbs"
- docs = teamworkDocs
- docCategory = category
+ options = teamworkDocs
+ targetFlag = (concat "rollCollab.rollParticipantData." section "." name
+ selected = name
+ docCategory = section
docTarget = name }}
{{/if}}
diff --git a/templates/roll/partials/roll-collab-action-gm.hbs b/templates/roll/partials/roll-collab-action-gm.hbs
index 976a7f16..cb52ebd0 100644
--- a/templates/roll/partials/roll-collab-action-gm.hbs
+++ b/templates/roll/partials/roll-collab-action-gm.hbs
@@ -223,6 +223,19 @@
{{> "systems/eunos-blades/templates/components/roll-collab-opposition.hbs" rollOpposition=rollOpposition factorData=rollFactors.opposition}}
+
+