diff --git a/styles/zero-engine.css b/styles/zero-engine.css index 7809002..26ab30b 100644 --- a/styles/zero-engine.css +++ b/styles/zero-engine.css @@ -4513,6 +4513,36 @@ body > .ze-pause-root .ze-sat-inner { border-radius: 10px; padding: 1px 6px; white-space: nowrap; flex-shrink: 0; text-shadow: 0 0 6px rgba(0,200,255,0.6); } +/* Initiative roll row */ +.group-npc-tool .gnpc-init-row { + display: flex; align-items: center; gap: 6px; margin: 3px 0 1px; +} +.group-npc-tool .gnpc-init-btn { + background: rgba(50,200,100,0.12) !important; border: 1px solid #33aa66 !important; + color: #44cc88 !important; font-weight: bold; font-size: 10px; padding: 2px 6px; + border-radius: 3px; cursor: pointer; flex-shrink: 0; +} +.group-npc-tool .gnpc-init-btn:hover { background: rgba(50,200,100,0.28) !important; } +.group-npc-tool .gnpc-init-pool { + font-size: 11px; color: #55bb77; font-family: monospace; flex-shrink: 0; +} +.group-npc-tool .gnpc-init-result { + display: flex; align-items: center; gap: 2px; flex-wrap: wrap; min-height: 18px; +} +.group-npc-tool .gnpc-die { + display: inline-flex; align-items: center; justify-content: center; + width: 15px; height: 15px; border-radius: 3px; + font-size: 9px; font-weight: bold; line-height: 1; + background: rgba(255,255,255,0.07); border: 1px solid #2a2a2a; color: #777; +} +.group-npc-tool .gnpc-die.gnpc-die-success { + background: rgba(50,200,100,0.22); border-color: #44cc88; color: #44ff88; + box-shadow: 0 0 5px rgba(68,204,136,0.45); +} +.group-npc-tool .gnpc-init-successes { + font-size: 11px; font-weight: bold; color: #44cc88; + font-family: monospace; margin-left: 4px; flex-shrink: 0; +} .group-npc-tool .gnpc-count-sel { background: rgba(255,255,255,0.08); border: 1px solid #444; color: #eee; border-radius: 3px; padding: 2px 4px; font-size: 12px; diff --git a/system.json b/system.json index bf2e641..523a036 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.7.2", + "version": "1.7.3", "compatibility": { "minimum": 13, "verified": "14.359" diff --git a/zero-engine.mjs b/zero-engine.mjs index f81cedc..fafba5e 100644 --- a/zero-engine.mjs +++ b/zero-engine.mjs @@ -6420,40 +6420,46 @@ class SLAGroupNPCTool extends Application { // ── Threat-level scaling ─────────────────────────────────────────────────── // Scales AGI (initiative) as the primary target; STR, HP, ARM, DMG also - // creep up at higher levels. Level 1 = no change; level 10 = elite tier. + // creep up at higher levels. Level 1 = baseline (no stat changes). + // initPool is ALWAYS set: max(5, agi) + threatLevel gives at least 6d6 at + // level 1 so every NPC can match players on initiative. _applyThreatBonus(npc) { const lvl = Math.max(1, Math.min(10, this._threatLevel ?? 1)); - if (lvl <= 1) return npc; + npc.threatBonus = lvl; - // Per-level bonus table: [agiBonus, strBonus, witBonus, hpPct, armorBonus, dmgBonus] - const TABLE = [ - [0, 0, 0, 0.00, 0, 0], // 1 — baseline - [1, 0, 0, 0.00, 0, 0], // 2 — quick - [1, 0, 1, 0.00, 0, 0], // 3 — alert - [2, 1, 1, 0.00, 0, 0], // 4 — dangerous - [2, 1, 1, 0.25, 0, 1], // 5 — veteran - [3, 2, 1, 0.25, 1, 1], // 6 — hardened - [3, 2, 2, 0.50, 1, 1], // 7 — elite - [4, 3, 2, 0.50, 1, 2], // 8 — apex - [4, 3, 3, 0.75, 2, 2], // 9 — legendary - [5, 4, 3, 1.00, 2, 3], // 10 — mythic - ]; - const [agiB, strB, witB, hpPct, armB, dmgB] = TABLE[lvl - 1]; + if (lvl > 1) { + // Per-level bonus table: [agiBonus, strBonus, witBonus, hpPct, armorBonus, dmgBonus] + const TABLE = [ + [0, 0, 0, 0.00, 0, 0], // 1 — baseline (unused; only entered for lvl > 1) + [1, 0, 0, 0.00, 0, 0], // 2 — quick + [1, 0, 1, 0.00, 0, 0], // 3 — alert + [2, 1, 1, 0.00, 0, 0], // 4 — dangerous + [2, 1, 1, 0.25, 0, 1], // 5 — veteran + [3, 2, 1, 0.25, 1, 1], // 6 — hardened + [3, 2, 2, 0.50, 1, 1], // 7 — elite + [4, 3, 2, 0.50, 1, 2], // 8 — apex + [4, 3, 3, 0.75, 2, 2], // 9 — legendary + [5, 4, 3, 1.00, 2, 3], // 10 — mythic + ]; + const [agiB, strB, witB, hpPct, armB, dmgB] = TABLE[lvl - 1]; - npc.agi = Math.min(8, npc.agi + agiB); - npc.str = Math.min(8, npc.str + strB); - npc.wit = Math.min(8, npc.wit + witB); - npc.armor = Math.min(8, npc.armor + armB); - npc.damage = Math.min(8, npc.damage + dmgB); + npc.agi = Math.min(8, npc.agi + agiB); + npc.str = Math.min(8, npc.str + strB); + npc.wit = Math.min(8, npc.wit + witB); + npc.armor = Math.min(8, npc.armor + armB); + npc.damage = Math.min(8, npc.damage + dmgB); - // HP: add a percentage of the base max (rounded up) - if (hpPct > 0) { - const extra = Math.ceil(npc.hpMax * hpPct); - npc.hpMax += extra; - npc.hp += extra; + if (hpPct > 0) { + const extra = Math.ceil(npc.hpMax * hpPct); + npc.hpMax += extra; + npc.hp += extra; + } } - npc.threatBonus = lvl; // store so the card can show a badge + // Initiative dice pool: base AGI (floor 5) + one bonus die per threat level. + // A level-1 Carrion (AGI 2) → max(2,5)+1 = 6d6; level-3 Carrion → max(3,5)+3 = 8d6. + // This guarantees NPCs can contest player initiative at all threat tiers. + npc.initPool = Math.max(5, npc.agi) + lvl; return npc; } @@ -6662,6 +6668,14 @@ class SLAGroupNPCTool extends Application { +