diff --git a/styles/zero-engine.css b/styles/zero-engine.css
index d521e04..ed15c6e 100644
--- a/styles/zero-engine.css
+++ b/styles/zero-engine.css
@@ -971,6 +971,7 @@
align-items: center;
gap: 8px;
margin-bottom: 2px;
+ flex-wrap: wrap;
}
.zero-engine .specialty-name {
@@ -978,6 +979,8 @@
font-size: 13px;
color: #fff;
cursor: pointer;
+ flex: 1;
+ min-width: 0;
}
.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;
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;
- 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.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);
}
+/* 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
══════════════════════════════════════════════════════════════════════════════ */
diff --git a/system.json b/system.json
index fc309b9..74014ab 100644
--- a/system.json
+++ b/system.json
@@ -2,7 +2,7 @@
"id": "zero-engine",
"title": "Zero Engine (SLA Industries)",
"description": "A basic game system for SLA Industries using the Zero Engine rules.",
- "version": "1.6.0",
+ "version": "1.6.1",
"compatibility": {
"minimum": 13,
"verified": "14.359"
diff --git a/template.json b/template.json
index 2e0506c..79c34f9 100644
--- a/template.json
+++ b/template.json
@@ -310,7 +310,11 @@
"package": "",
"healthMod": 0,
"resolveMod": 0,
- "isActive": true
+ "isActive": true,
+ "isNaturalWeapon": false,
+ "naturalWeaponDamage": 0,
+ "naturalWeaponAP": 0,
+ "naturalWeaponROF": 0
},
"ebb": {
"description": "",
diff --git a/templates/actor/character-sheet.hbs b/templates/actor/character-sheet.hbs
index 13159f1..957f2b1 100644
--- a/templates/actor/character-sheet.hbs
+++ b/templates/actor/character-sheet.hbs
@@ -477,6 +477,13 @@
{{this.name}}
{{this.system.package}}
+ {{!-- Natural weapon STRIKE button --}}
+ {{#if this.system.isNaturalWeapon}}
+
+ {{/if}}
{{!-- Morph form SHIFT button for Vevaphon forms --}}
{{#if (eq this.system.prerequisites "Vevaphon")}}
{{#unless (eq this.name "Morphic Strike")}}
diff --git a/zero-engine.mjs b/zero-engine.mjs
index 67c89b8..6d2de1e 100644
--- a/zero-engine.mjs
+++ b/zero-engine.mjs
@@ -460,6 +460,9 @@ class ZeroEngineActorSheet extends foundry.appv1.sheets.ActorSheet {
// Health / Resolve save rolls — click the vital label name
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
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.
* Rolls current value in plain d6 (no stress dice, no dialog).
@@ -6858,6 +6908,20 @@ const SLA_RACIAL_ABILITY_DATA = {
prerequisites: "Wraith Raider",
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",
type: "specialty", img: "systems/zero-engine/assets/racial/natural-weapons.svg",
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.",
- effects: "Natural weapon: DMG 2, AP 1. Always available.",
+ 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, ROF 2. Always available.",
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;
const toCreate = [];
- const toUpdateImg = [];
+ const toUpdate = [];
for (const ability of abilities) {
const existing = actor.items.find(
i => i.type === "specialty" && i.name === ability.name
);
if (!existing) {
toCreate.push(ability);
- } else if (ability.img && existing.img !== ability.img) {
- // Update stale icon on existing item
- toUpdateImg.push({ _id: existing.id, img: ability.img });
+ } else {
+ const patch = {};
+ 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) {
- try { await actor.updateEmbeddedDocuments("Item", toUpdateImg); } catch(_) {}
+ if (toUpdate.length) {
+ try { await actor.updateEmbeddedDocuments("Item", toUpdate); } catch(_) {}
}
if (!toCreate.length) return;