fix+feat: button overrun, natural weapon rolls, Wraith Teeth & Claw (v1.6.1)
CSS: morph-shift-btn now flex: 0 0 auto; max-width: 110px
specialty-name gets flex: 1; min-width: 0 so it takes remaining space
flex-wrap on specialty-name-row prevents overflow
Natural weapons:
- template.json: isNaturalWeapon/naturalWeaponDamage/AP/ROF fields on specialty
- Morphic Strike: ROF 2, isNaturalWeapon: true
- Wraith Teeth & Claw added to wraithraider racial abilities (DMG 2, AP 1, ROF 2)
- _ensureRacialAbilities patches existing items with missing natural weapon fields
- _onNaturalWeaponRoll: STR + Melee roll through full weapon chat card pipeline
- STRIKE button appears on any specialty with isNaturalWeapon: true
- All condition/drug/armor/racial mods applied to natural weapon rolls
This commit is contained in:
+15
-1
@@ -971,6 +971,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zero-engine .specialty-name {
|
.zero-engine .specialty-name {
|
||||||
@@ -978,6 +979,8 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.zero-engine .specialty-name:hover {
|
.zero-engine .specialty-name:hover {
|
||||||
@@ -4278,7 +4281,7 @@ body > .ze-pause-root .ze-sat-inner {
|
|||||||
padding: 2px 10px; font-size: 10px; font-weight: bold; text-transform: uppercase;
|
padding: 2px 10px; font-size: 10px; font-weight: bold; text-transform: uppercase;
|
||||||
letter-spacing: 0.5px; border-radius: 3px; cursor: pointer; transition: all 0.2s;
|
letter-spacing: 0.5px; border-radius: 3px; cursor: pointer; transition: all 0.2s;
|
||||||
background: rgba(0,180,255,0.1); border: 1px solid #0090cc; color: #44bbff;
|
background: rgba(0,180,255,0.1); border: 1px solid #0090cc; color: #44bbff;
|
||||||
white-space: nowrap; flex-shrink: 0;
|
white-space: nowrap; flex: 0 0 auto; max-width: 110px;
|
||||||
}
|
}
|
||||||
.zero-engine .morph-shift-btn:hover { background: rgba(0,180,255,0.25); }
|
.zero-engine .morph-shift-btn:hover { background: rgba(0,180,255,0.25); }
|
||||||
.zero-engine .morph-shift-btn.morph-shift-active {
|
.zero-engine .morph-shift-btn.morph-shift-active {
|
||||||
@@ -4286,6 +4289,17 @@ body > .ze-pause-root .ze-sat-inner {
|
|||||||
box-shadow: 0 0 8px rgba(0,200,255,0.3);
|
box-shadow: 0 0 8px rgba(0,200,255,0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Natural weapon STRIKE button */
|
||||||
|
.zero-engine .natural-weapon-roll-btn {
|
||||||
|
padding: 2px 10px; font-size: 10px; font-weight: bold; text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px; border-radius: 3px; cursor: pointer; transition: all 0.2s;
|
||||||
|
background: rgba(255,80,0,0.15); border: 1px solid #cc4400; color: #ff7733;
|
||||||
|
white-space: nowrap; flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.zero-engine .natural-weapon-roll-btn:hover {
|
||||||
|
background: rgba(255,80,0,0.3); border-color: #ff6600; color: #ffaa55;
|
||||||
|
}
|
||||||
|
|
||||||
/* ══════════════════════════════════════════════════════════════════════════════
|
/* ══════════════════════════════════════════════════════════════════════════════
|
||||||
PLAYER CHARACTER FLAG — Bio Tab
|
PLAYER CHARACTER FLAG — Bio Tab
|
||||||
══════════════════════════════════════════════════════════════════════════════ */
|
══════════════════════════════════════════════════════════════════════════════ */
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"id": "zero-engine",
|
"id": "zero-engine",
|
||||||
"title": "Zero Engine (SLA Industries)",
|
"title": "Zero Engine (SLA Industries)",
|
||||||
"description": "A basic game system for SLA Industries using the Zero Engine rules.",
|
"description": "A basic game system for SLA Industries using the Zero Engine rules.",
|
||||||
"version": "1.6.0",
|
"version": "1.6.1",
|
||||||
"compatibility": {
|
"compatibility": {
|
||||||
"minimum": 13,
|
"minimum": 13,
|
||||||
"verified": "14.359"
|
"verified": "14.359"
|
||||||
|
|||||||
+5
-1
@@ -310,7 +310,11 @@
|
|||||||
"package": "",
|
"package": "",
|
||||||
"healthMod": 0,
|
"healthMod": 0,
|
||||||
"resolveMod": 0,
|
"resolveMod": 0,
|
||||||
"isActive": true
|
"isActive": true,
|
||||||
|
"isNaturalWeapon": false,
|
||||||
|
"naturalWeaponDamage": 0,
|
||||||
|
"naturalWeaponAP": 0,
|
||||||
|
"naturalWeaponROF": 0
|
||||||
},
|
},
|
||||||
"ebb": {
|
"ebb": {
|
||||||
"description": "",
|
"description": "",
|
||||||
|
|||||||
@@ -477,6 +477,13 @@
|
|||||||
<div class="specialty-name-row">
|
<div class="specialty-name-row">
|
||||||
<span class="specialty-name">{{this.name}}</span>
|
<span class="specialty-name">{{this.name}}</span>
|
||||||
<span class="specialty-package">{{this.system.package}}</span>
|
<span class="specialty-package">{{this.system.package}}</span>
|
||||||
|
{{!-- Natural weapon STRIKE button --}}
|
||||||
|
{{#if this.system.isNaturalWeapon}}
|
||||||
|
<button type="button" class="natural-weapon-roll-btn" data-item-id="{{this._id}}"
|
||||||
|
title="Strike: STR + Melee | DMG {{this.system.naturalWeaponDamage}}, AP {{this.system.naturalWeaponAP}}, ROF {{this.system.naturalWeaponROF}}">
|
||||||
|
<i class="fas fa-fist-raised"></i> STRIKE
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
{{!-- Morph form SHIFT button for Vevaphon forms --}}
|
{{!-- Morph form SHIFT button for Vevaphon forms --}}
|
||||||
{{#if (eq this.system.prerequisites "Vevaphon")}}
|
{{#if (eq this.system.prerequisites "Vevaphon")}}
|
||||||
{{#unless (eq this.name "Morphic Strike")}}
|
{{#unless (eq this.name "Morphic Strike")}}
|
||||||
|
|||||||
+82
-9
@@ -460,6 +460,9 @@ class ZeroEngineActorSheet extends foundry.appv1.sheets.ActorSheet {
|
|||||||
// Health / Resolve save rolls — click the vital label name
|
// Health / Resolve save rolls — click the vital label name
|
||||||
html.find('.vital-save-label').click(this._onVitalSave.bind(this));
|
html.find('.vital-save-label').click(this._onVitalSave.bind(this));
|
||||||
|
|
||||||
|
// Natural weapon attack roll (Morphic Strike, Wraith Teeth & Claw, etc.)
|
||||||
|
html.find('.natural-weapon-roll-btn').click(this._onNaturalWeaponRoll.bind(this));
|
||||||
|
|
||||||
// GM PC condition status report
|
// GM PC condition status report
|
||||||
html.find('.pc-status-btn').click(ev => { ev.preventDefault(); _broadcastPCConditions(); });
|
html.find('.pc-status-btn').click(ev => { ev.preventDefault(); _broadcastPCConditions(); });
|
||||||
|
|
||||||
@@ -1230,6 +1233,53 @@ class ZeroEngineActorSheet extends foundry.appv1.sheets.ActorSheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a natural weapon attack roll from a specialty (Morphic Strike, Wraith Teeth & Claw, etc.)
|
||||||
|
* Uses STR + Melee, same pipeline as a melee weapon attack, with the weapon's own DMG/AP/ROF.
|
||||||
|
* @param {Event} event
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
async _onNaturalWeaponRoll(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const itemId = event.currentTarget.dataset.itemId;
|
||||||
|
const item = this.actor.items.get(itemId);
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
const s = item.system;
|
||||||
|
const damage = Number(s.naturalWeaponDamage) || 2;
|
||||||
|
const ap = Number(s.naturalWeaponAP) || 1;
|
||||||
|
const rof = Number(s.naturalWeaponROF) || 2;
|
||||||
|
|
||||||
|
const attrValue = parseInt(this.actor.system.attributes?.strength?.value) || 2;
|
||||||
|
const skillValue = parseInt(this.actor.system.skills?.melee?.value) || 0;
|
||||||
|
const armorMods = this._getArmorRollModifiers(this.actor, { attribute: 'strength', skill: 'melee' });
|
||||||
|
const drugMods = this._getDrugRollModifiers(this.actor, { attribute: 'strength', skill: 'melee' });
|
||||||
|
const racialMods = this._getRacialDiceBonus(this.actor, { skill: 'melee', attribute: 'strength' });
|
||||||
|
const condMods = _getConditionModifiers(this.actor, { attribute: 'strength', skill: 'melee' });
|
||||||
|
const baseDice = attrValue + skillValue + armorMods.total + drugMods.total + racialMods.bonus + condMods.bonus;
|
||||||
|
|
||||||
|
const formulaStr = `STR ${attrValue} + Melee ${skillValue}` +
|
||||||
|
`${armorMods.total !== 0 ? ` + Armor ${armorMods.total}` : ''}` +
|
||||||
|
`${drugMods.total !== 0 ? ` + Drug ${drugMods.total}` : ''}` +
|
||||||
|
`${racialMods.bonus !== 0 ? ` + Racial ${racialMods.bonus}` : ''}` +
|
||||||
|
`${condMods.bonus !== 0 ? ` + Conditions ${condMods.bonus}` : ''}` +
|
||||||
|
` = ${baseDice}d6 | DMG ${damage}, AP ${ap}, ROF ${rof}`;
|
||||||
|
|
||||||
|
const weaponInfo = {
|
||||||
|
name: item.name,
|
||||||
|
damage,
|
||||||
|
ap,
|
||||||
|
range: 'Engaged',
|
||||||
|
rof,
|
||||||
|
category: 'melee',
|
||||||
|
weaponType: 'Natural'
|
||||||
|
};
|
||||||
|
|
||||||
|
return this._rollYZEDice(baseDice, item.name, formulaStr, 'strength', weaponInfo);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle Health or Resolve save roll — clicking the vital label name.
|
* Handle Health or Resolve save roll — clicking the vital label name.
|
||||||
* Rolls current value in plain d6 (no stress dice, no dialog).
|
* Rolls current value in plain d6 (no stress dice, no dialog).
|
||||||
@@ -6858,6 +6908,20 @@ const SLA_RACIAL_ABILITY_DATA = {
|
|||||||
prerequisites: "Wraith Raider",
|
prerequisites: "Wraith Raider",
|
||||||
isActive: true, healthMod: 0, resolveMod: 0
|
isActive: true, healthMod: 0, resolveMod: 0
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Wraith Teeth & Claw",
|
||||||
|
type: "specialty",
|
||||||
|
img: "systems/zero-engine/assets/racial/natural-weapons.svg",
|
||||||
|
system: {
|
||||||
|
category: "racial",
|
||||||
|
description: "Bone-hard teeth and razor claws grown through decades of cold-world adaptation. Natural melee weapons — Damage 2, AP 1, ROF 2. Always counts as armed.",
|
||||||
|
effects: "Natural weapon: DMG 2, AP 1, ROF 2. Always armed.",
|
||||||
|
racialBonuses: [],
|
||||||
|
prerequisites: "Wraith Raider",
|
||||||
|
isActive: true, healthMod: 0, resolveMod: 0,
|
||||||
|
isNaturalWeapon: true, naturalWeaponDamage: 2, naturalWeaponAP: 1, naturalWeaponROF: 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -6946,10 +7010,11 @@ const SLA_RACIAL_ABILITY_DATA = {
|
|||||||
name: "Morphic Strike",
|
name: "Morphic Strike",
|
||||||
type: "specialty", img: "systems/zero-engine/assets/racial/natural-weapons.svg",
|
type: "specialty", img: "systems/zero-engine/assets/racial/natural-weapons.svg",
|
||||||
system: {
|
system: {
|
||||||
category: "racial", description: "Natural biogenetic weapon formed from the Vevaphon's own mass. Damage 2, AP 1. Always available regardless of morph form. Counts as melee weapon for skill rolls.",
|
category: "racial", description: "Natural biogenetic weapon formed from the Vevaphon's own mass. Damage 2, AP 1, ROF 2. Always available regardless of morph form. Counts as melee weapon for skill rolls.",
|
||||||
effects: "Natural weapon: DMG 2, AP 1. Always available.",
|
effects: "Natural weapon: DMG 2, AP 1, ROF 2. Always available.",
|
||||||
racialBonuses: [],
|
racialBonuses: [],
|
||||||
prerequisites: "Vevaphon", isActive: true, healthMod: 0, resolveMod: 0
|
prerequisites: "Vevaphon", isActive: true, healthMod: 0, resolveMod: 0,
|
||||||
|
isNaturalWeapon: true, naturalWeaponDamage: 2, naturalWeaponAP: 1, naturalWeaponROF: 2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -6970,20 +7035,28 @@ async function _ensureRacialAbilities(actor) {
|
|||||||
if (!abilities || !abilities.length) return;
|
if (!abilities || !abilities.length) return;
|
||||||
|
|
||||||
const toCreate = [];
|
const toCreate = [];
|
||||||
const toUpdateImg = [];
|
const toUpdate = [];
|
||||||
for (const ability of abilities) {
|
for (const ability of abilities) {
|
||||||
const existing = actor.items.find(
|
const existing = actor.items.find(
|
||||||
i => i.type === "specialty" && i.name === ability.name
|
i => i.type === "specialty" && i.name === ability.name
|
||||||
);
|
);
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
toCreate.push(ability);
|
toCreate.push(ability);
|
||||||
} else if (ability.img && existing.img !== ability.img) {
|
} else {
|
||||||
// Update stale icon on existing item
|
const patch = {};
|
||||||
toUpdateImg.push({ _id: existing.id, img: ability.img });
|
if (ability.img && existing.img !== ability.img) patch.img = ability.img;
|
||||||
|
// Patch natural weapon fields if missing or stale
|
||||||
|
if (ability.system?.isNaturalWeapon && !existing.system?.isNaturalWeapon) {
|
||||||
|
patch["system.isNaturalWeapon"] = true;
|
||||||
|
patch["system.naturalWeaponDamage"] = ability.system.naturalWeaponDamage ?? 2;
|
||||||
|
patch["system.naturalWeaponAP"] = ability.system.naturalWeaponAP ?? 1;
|
||||||
|
patch["system.naturalWeaponROF"] = ability.system.naturalWeaponROF ?? 2;
|
||||||
|
}
|
||||||
|
if (Object.keys(patch).length) toUpdate.push({ _id: existing.id, ...patch });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (toUpdateImg.length) {
|
if (toUpdate.length) {
|
||||||
try { await actor.updateEmbeddedDocuments("Item", toUpdateImg); } catch(_) {}
|
try { await actor.updateEmbeddedDocuments("Item", toUpdate); } catch(_) {}
|
||||||
}
|
}
|
||||||
if (!toCreate.length) return;
|
if (!toCreate.length) return;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user