Beastheart Companion Update

- Added Melee kit boxes to monster companion panel
- Updated Damage calculator to take companion kit damage into account
- Fixed a bug with standard actions having the wrong type
- Monster imports now set the stamina_max attribute
- Started work on Beastheart Companion import functionality.
This commit is contained in:
Zen-Raven
2026-04-21 10:33:02 -07:00
parent b017ae08d7
commit d4aa36c33d
3 changed files with 113 additions and 11 deletions
+1 -1
View File
@@ -608,7 +608,7 @@ label[for*='minimize']::before {
display: grid;
grid-template-columns: 80px 1fr;
gap: 4px;
align-items: center;
align-items: baseline;
}
.grid-header {
+110 -9
View File
@@ -109,7 +109,7 @@
<textarea name="attr_forgesteel_json" style="width: 100%;"></textarea>
<button type="action" name="act_import" data-i18n="import-player">Import Player</button>
<button type="action" name="act_importmonster" data-i18n="import-monster">Import Monster</button>
<button type="action" name="act_importmonster" data-i18n="import-companion">Import Companion</button>
<!--<button type="action" name="act_importcompanion" data-i18n="import-companion">Import Companion</button>-->
</div>
</div>
</div>
@@ -3060,13 +3060,30 @@
</div>
</div>
</div>
<input type="hidden" class="npc-companion-toggle" name="attr_options_is_companion" />
<input type="hidden" class="npc-companion-toggle" name="attr_options_is_companion" value="0" />
<div class="npc-companion-panel npc-panel">
<div class="resource-panel">
<div class="dashboard-stat-group resource-input-group">
<label data-i18n="beastheart-rampage" data-i18n-title="rampage-tooltip">Rampage</label>
<input type="number" name="attr_resource" title="Macro: @{NAME|resource}" />
</div>
</div>
<div class="kit-melee-damage-group">
<label class="kit-damage-label" data-i18n="melee-weapon-damage"></label>
<div class="kit-melee-damage">
<div class="kit-melee-damage-1-input-group">
<input type="number" class="kit-melee-damage-1" name="attr_kit_melee_damage_1" value="0" />
<label class="sublabel">≤11</label>
</div>
<div class="kit-melee-damage-2-input-group">
<input type="number" class="kit-melee-damage-2" name="attr_kit_melee_damage_2" value="0" />
<label class="sublabel">12-16</label>
</div>
<div class="kit-melee-damage-3-input-group">
<input type="number" class="kit-melee-damage-3" name="attr_kit_melee_damage_3" value="0" />
<label class="sublabel">17+</label>
</div>
</div>
</div>
</div>
<div class="rampage-table minimizable">
<div class="minimize-control">
@@ -3712,7 +3729,7 @@
<div class="ability-card card-edit">
<textarea class="ability-effect grid-span-full" data-i18n-placeholder="charge" name="attr_charge_name" data-i18n="charge" spellcheck="false"></textarea>
<input type="hidden" class="ability-keywords" data-i18n-placeholder="charge-keywords" name="attr_charge_keywords" value="" />
<input type="hidden" name="attr_charge_type" value="Main Action" />
<input type="hidden" name="attr_charge_type" value="main-action" />
<textarea class="ability-effect grid-span-full" data-i18n="charge-effect" name="attr_charge_effect" spellcheck="false"></textarea>
</div>
<div class="ability-card card-display">
@@ -3737,7 +3754,7 @@
<div class="ability-card card-edit">
<textarea class="ability-name grid-span-full" data-i18n-placeholder="defend" name="attr_defend_name" data-i18n="defend" spellcheck="false"></textarea>
<input type="hidden" class="ability-keywords" data-i18n-placeholder="defend-keywords" name="attr_defend_keywords" value="" />
<input type="hidden" name="attr_defend_type" value="Main Action" />
<input type="hidden" name="attr_defend_type" value="main-action" />
<textarea class="ability-effect grid-span-full" data-i18n="defend-effect" name="attr_defend_effect" spellcheck="false"></textarea>
</div>
<div class="ability-card card-display">
@@ -3762,7 +3779,7 @@
<div class="ability-card card-edit">
<textarea class="ability-effect grid-span-full" data-i18n-placeholder="convert" name="attr_convert_name" data-i18n="convert" spellcheck="false"></textarea>
<input type="hidden" class="ability-keywords" data-i18n-placeholder="convert-keywords" name="attr_convert_keywords" value="" />
<input type="hidden" name="attr_convert_type" value="Main Action" />
<input type="hidden" name="attr_convert_type" value="main-action" />
<textarea class="ability-effect grid-span-full" data-i18n="convert-effect" name="attr_convert_effect" spellcheck="false"></textarea>
</div>
<div class="ability-card card-display">
@@ -5182,7 +5199,7 @@
// 2. Define the character-level attributes needed for both.
const globalAttrsToGet = prefix === 'repeating_npcabilities'
? [] // Monster abilities need nothing
? ['kit_melee_damage_1','kit_melee_damage_2','kit_melee_damage_3'] // Companions need melee kit values
: ['resource_name','kit_melee_damage_1','kit_melee_damage_2','kit_melee_damage_3','kit_ranged_damage_1','kit_ranged_damage_2','kit_ranged_damage_3', 'mag_psi_dam', 'sanct_weap' ]; // Player abilities need resource names and kit damage info
const characteristics = isPowerRoll ? ['might', 'agility', 'reason', 'intuition', 'presence'] : [];
@@ -5237,7 +5254,7 @@
parseInt(attrs[`kit_${type}_damage_2`]) || 0,
parseInt(attrs[`kit_${type}_damage_3`]) || 0
];
if (attrs['sanct_weap']) {
otherDamage += `+${attrs['sanct_weap']}[${getTranslationByKey('sanct-weap')}]`;
}
@@ -6004,6 +6021,7 @@
CHARACTERISTIC_BONUS: 'Characteristic Bonus',
MALICE_ABILITY: 'Malice Ability',
CONDITION_IMMUNITY: 'Condition Immunity',
SUMMON_CHOICE: 'Summon Choice',
};
/**
@@ -6372,7 +6390,87 @@
}
});
// --- Import Companion Event Listener ---
on('clicked:importcompanion', async () => {
try {
// Get the raw JSON string from the character sheet attributes.
const data = await loadForgeSteelData();
if (!data) { return };
// Check if the data is for a single monster or a monster group
if (data.hasOwnProperty('class')) {
const allFeatures = getFeatures(data);
//Get relevant player chaaracter information
const level = data.class.level;
const baseStats = data.class.characteristics.reduce((acc, i) => ({ ...acc, [i.characteristic]:i.value }), {});
const characteristics = ['Might', 'Agility', 'Reason', 'Intuition', 'Presence'].reduce((acc, s) => {
acc[s.toLowerCase()] = calculateCharacteristic(allFeatures, s, baseStats[s]);
return acc;
}, {});
const stamina_max = calculateModifiedStat('Stamina', 0, level, characteristics ,allFeatures);
const classAbilities = allFeatures
.filter(f => f.type === FEATURE_TYPES.CLASS_ABILITY)
.flatMap(f => f.data.selectedIDs)
.reduce((acc, id) => {
const abilityData = extractClassAbility(data, id);
console.log(abilityData.keywords);
if (abilityData.keywords.includes('Beastheart')) {
return { ...acc };
}
return { ...acc, ...processMonsterAbility(abilityData) };
}, {});
const abilityFeatures = allFeatures
.filter(f => f.type === FEATURE_TYPES.ABILITY)
.reduce((acc, f) => {
if (f.data.ability.keywords.includes('Beastheart')) {
return { ...acc };
}
return { ...acc, ...processMonsterAbility(f.data.ability) };
}, {});
//Get the base companion
const companionFeature = allFeatures.filter(f => f.type === FEATURE_TYPES.SUMMON_CHOICE);
if (!companionFeature) throw new Error("No features found in JSON.");
// Extract the companion data
const companionData = companionFeature[0].data?.selected[0];
if (!companionData) {
throw new Error("Companion Data not found");
return;
}
console.log(companionData);
console.log(companionData.monster.freeStrikeDamage, characteristics['might']);
// Do the bulk of the import with the monster importer
await importMonster(companionData.monster, []);
const newAttrs = {
level,
stamina_max,
stamina: stamina_max,
npc_freestrike: parseInt(companionData.monster.freeStrikeDamage) + characteristics['might'],
...abilityFeatures,
...classAbilities,
};
// Set the altered attributes on the character sheet.
await setAttrsAsync(newAttrs);
//Last bit of character sheet setup
calculateWinded();
console.log('Companion import successful!', newAttrs);
} else {
console.error('No companion information.');
return;
}
} catch (error) {
console.error("Failed to import monster data.", error);
}
});
/**
* Imports player data from a loaded JSON file
* @param {object} data the loaded and validated JSON file
@@ -6642,6 +6740,7 @@
size: sizeString,
speed: dataSpeed.value ?? 5,
stamina,
stamina_max: stamina,
stability,
npc_freestrike: freeStrikeDamage,
immunity: getDamageModifiers(data.features, 1, 'Immunity'),
@@ -7040,6 +7139,8 @@
convertRepeatingKits();
// Set new Damage Calculation option (v1.3)
// Set new Sticky Charactersitic Panel option (v1.4)
// Set new Monster Malice Tab option (v1.5)
setNewOptions();
//Sets the sheet's new version
+2 -1
View File
@@ -585,5 +585,6 @@
"beastheart-rampage-table-20": "20 (7th level)",
"beastheart-rampage-table-20-text": "As a free maneuver, your companion can increase their size up to size 2, or increase their size by 1 if their original size is already 2 or larger. This size increase lasts until your companions rampage ends or they use a free maneuver to end it. While your companions size is increased, they gain a +2 bonus to speed and stability, the potencies of their abilities increase by 1, and the size of their Feral Strike abilitys burst increases by 1.",
"beastheart-rampage-table-24": "24 (10th level)",
"beastheart-rampage-table-24-text": "When your companion increases their size, they can increase it up to size 3, or increase their size by 1 if their original size is already 3 or larger. Whenever they make a power roll while their size is increased this way, they can roll 3d10 and discard the lowest roll."
"beastheart-rampage-table-24-text": "When your companion increases their size, they can increase it up to size 3, or increase their size by 1 if their original size is already 3 or larger. Whenever they make a power roll while their size is increased this way, they can roll 3d10 and discard the lowest roll.",
"import-companion": "Import Companion (Beastheart)"
}