{ "cells": [ { "cell_type": "markdown", "id": "e46c04f1-a95e-4301-8763-10c69d6aff06", "metadata": {}, "source": [ "# Bloodrager\n", "Leshy Bloodrager barbarian, critfisher build\n", "\n", "**Feats:** **1.** Extended Reach, **2.** Bloodrager Dedication, **4.** Rising Blood Magic, **6.** Siphon Magic, **10.** Hematocritical, **12.** Surging Blood Magic, **18.** Exultant Blood Magic\n", "\n", "**Spells**: **cantrips** ignition or live wire, electric arc; **1st** Sure Strike; **2nd** Brine Dragon Bile; **3rd** Haste or Blazing Bolt or Breathe Fire or Fireball or Organsight (signature)\n", "\n", "**Equipment:** Rooting Flaming Greatpick or Greatsword, (Greater) Phantasmal Doorknob" ] }, { "cell_type": "code", "execution_count": null, "id": "c3602921-1799-4266-b8f6-cf40eeba4157", "metadata": {}, "outputs": [], "source": [ "import xarray\n", "\n", "import pathfinder2e_stats as pf2" ] }, { "cell_type": "code", "execution_count": null, "id": "0ceac684-4804-40f8-a186-ca6d48ffd485", "metadata": {}, "outputs": [], "source": [ "level = 14\n", "\n", "spell_slot_rank = (\n", " pf2.level2rank(level, dedication=True) - 1\n", ") # max - 1, recoverable with Siphon Magic\n", "use_hematocritical = level >= 10\n", "use_rooting_rune = level >= 7 # clumsy 1 on a crit\n", "use_flaming_rune = level >= 10\n", "use_sword = False # off-guard to ranged spells on a crit\n", "use_greater_phantasmal_doorknob = level >= 10 # off-guard to ranged spells on a crit" ] }, { "cell_type": "markdown", "id": "4a542e98-cd01-40e7-90a9-78388148409d", "metadata": {}, "source": [ "## Attack bonus progrssion\n", "Weapon vs. spell vs. Organsight Medicine checks" ] }, { "cell_type": "code", "execution_count": null, "id": "d220b5e7-b670-4280-8172-5cb3df1bfb2b", "metadata": {}, "outputs": [], "source": [ "atk_bonus = xarray.Dataset(\n", " {\n", " \"weapon\": pf2.tables.SIMPLE_PC.weapon_attack_bonus.barbarian.sum(\"component\"),\n", " \"spell\": pf2.tables.SIMPLE_PC.spell_attack_bonus.barbarian.sum(\"component\"),\n", " \"organsight\": (\n", " pf2.tables.PC.ability_bonus.boosts.sel(initial=1, drop=True)\n", " + pf2.tables.PC.skill_proficiency.others.sel(priority=1, drop=True)\n", " + pf2.tables.PC.skill_item_bonus.medicine\n", " + pf2.tables.PC.level\n", " + 2 # Circumstance\n", " ),\n", " }\n", ")\n", "atk_bonus.to_pandas()" ] }, { "cell_type": "markdown", "id": "f61e233d-39cb-4606-bcf3-06cbda57e565", "metadata": {}, "source": [ "### Let's select 3 standard targets:\n", "- level -2 henchman, all defenses are low\n", "- at-level monster, all defenses are moderate\n", "- level +2 boss, all defenses are high" ] }, { "cell_type": "code", "execution_count": null, "id": "c907f9ac-0a9c-4973-9b17-ea9ba38e0d74", "metadata": {}, "outputs": [], "source": [ "rank = pf2.level2rank(level)\n", "defenses = pf2.tables.SIMPLE_NPC[[\"AC\", \"saving_throws\", \"recall_knowledge\"]].sel(\n", " level=level\n", ")\n", "AC = defenses.AC\n", "saves = defenses.saving_throws\n", "defenses.to_array(\"kind\").to_pandas()" ] }, { "cell_type": "markdown", "id": "553396f9-df0a-449d-8e6b-7e9cff7f2bfa", "metadata": {}, "source": [ "## Build damage profiles for weapon and spells" ] }, { "cell_type": "code", "execution_count": null, "id": "159ccd3d-13ec-45b9-9924-65ef8eb9aa6a", "metadata": {}, "outputs": [], "source": [ "STR = (\n", " pf2.tables.PC.ability_bonus.boosts.sel(initial=4) + pf2.tables.PC.ability_bonus.apex\n", ")\n", "weapon_specialization = pf2.tables.PC.weapon_specialization.barbarian\n", "rage_weapon = pf2.tables.PC.rage.bloodrager_weapon\n", "weapon_dmg_bonus = (STR + weapon_specialization + rage_weapon).sel(level=level).item()\n", "rage_bleed = pf2.tables.PC.rage.bloodrager_bleed.sel(level=level).item()\n", "weapon_dice = pf2.tables.PC.weapon_dice.striking_rune.sel(level=level).item()\n", "\n", "if use_sword:\n", " # Greatsword with extended reach\n", " weapon = pf2.armory.swords.greatsword(weapon_dice, weapon_dmg_bonus).reduce_die()\n", "else:\n", " # Greatpick with extended reach\n", " weapon = pf2.armory.picks.greatpick(weapon_dice, weapon_dmg_bonus).reduce_die()\n", " if level >= 5:\n", " weapon += pf2.armory.picks.critical_specialization(2)\n", "\n", "if use_flaming_rune:\n", " weapon += pf2.armory.runes.flaming()\n", "\n", "weapon += pf2.Damage(\"bleed\", 0, 0, rage_bleed, persistent=True)\n", "weapon" ] }, { "cell_type": "code", "execution_count": null, "id": "72ff4276-aeaa-4d2d-9dd2-9a1ce6dffa7d", "metadata": {}, "outputs": [], "source": [ "def rage_spell(\n", " level: int, type_: str, *, persistent: bool = False, drained: int = 2\n", ") -> dict[pf2.DoS, list[pf2.Damage]]:\n", " raw = pf2.tables.PC.rage.bloodrager_spells.sel(level=level, drained=drained).item()\n", " d = pf2.Damage(type_, 0, 0, raw, persistent=persistent)\n", " return {\n", " pf2.DoS.critical_success: [d.copy(multiplier=2)],\n", " pf2.DoS.success: [d],\n", " pf2.DoS.failure: [d],\n", " }\n", "\n", "\n", "ignition_melee = pf2.armory.cantrips.ignition(rank, melee=True) + rage_spell(\n", " level, \"fire\"\n", ")\n", "ignition_melee" ] }, { "cell_type": "code", "execution_count": null, "id": "40aa17aa-33f5-4f74-ad50-779e3b0f0af4", "metadata": {}, "outputs": [], "source": [ "ignition_ranged = pf2.armory.cantrips.ignition(rank, melee=False) + rage_spell(\n", " level, \"fire\"\n", ")\n", "ignition_ranged" ] }, { "cell_type": "code", "execution_count": null, "id": "ca51308f-f20b-48e3-9081-58dc60d69d2b", "metadata": {}, "outputs": [], "source": [ "live_wire = pf2.armory.cantrips.live_wire(rank) + rage_spell(level, \"electricity\")\n", "live_wire" ] }, { "cell_type": "code", "execution_count": null, "id": "e0ab619f-1398-4bc7-9c85-b40a2c865943", "metadata": {}, "outputs": [], "source": [ "electric_arc = pf2.armory.cantrips.electric_arc(rank)\n", "electric_arc" ] }, { "cell_type": "code", "execution_count": null, "id": "6c861789-29eb-4bc1-9337-ea0bbf9af629", "metadata": {}, "outputs": [], "source": [ "breathe_fire = pf2.armory.spells.breathe_fire(spell_slot_rank)\n", "breathe_fire" ] }, { "cell_type": "code", "execution_count": null, "id": "d4543d9c-e6b3-437f-b8ee-2867fe818872", "metadata": {}, "outputs": [], "source": [ "brine_dragon_bile = pf2.armory.spells.brine_dragon_bile(spell_slot_rank) + rage_spell(\n", " level, \"acid\", persistent=True\n", ")\n", "brine_dragon_bile" ] }, { "cell_type": "code", "execution_count": null, "id": "c67da46a-b448-4b32-8836-8867278531e1", "metadata": {}, "outputs": [], "source": [ "blazing_bolt_1action = pf2.armory.spells.blazing_bolt(\n", " spell_slot_rank, actions=1\n", ") + rage_spell(level, \"fire\")\n", "blazing_bolt_1action" ] }, { "cell_type": "code", "execution_count": null, "id": "e412ccc8-30e0-4dc6-a4cb-d1dec2c4a716", "metadata": {}, "outputs": [], "source": [ "blazing_bolt_3actions = pf2.armory.spells.blazing_bolt(\n", " spell_slot_rank, actions=3\n", ") + rage_spell(level, \"fire\")\n", "blazing_bolt_3actions" ] }, { "cell_type": "code", "execution_count": null, "id": "4b26c770-53eb-4be4-b7bc-05efb125528c", "metadata": {}, "outputs": [], "source": [ "organsight = pf2.armory.spells.organsight(spell_slot_rank)\n", "organsight" ] }, { "cell_type": "markdown", "id": "75f2af58-1978-49d5-bfe3-337ffef707dc", "metadata": {}, "source": [ "## Attack routine\n", "- Strike (with flank) -> Hematocritical if crit -> spell, _or_\n", "- (if hasted) Sure Strike -> Strike (with flank) -> Hematocritical -> spell\n", "\n", "Spell is one of:\n", "- Ignition (melee with flank)\n", "- Ignition (ranged due to reach)\n", "- Live Wire\n", "- Electric Arc (1-2 targets)\n", "- Breathe Fire / Fireball\n", "- Blazing Bolt (1-2-3 actions)\n", "- (out of round) Brine Dragon Bile\n", "\n", "Spells from slots are at maximum rank -1, so that they can be cycled with Syphon Magic." ] }, { "cell_type": "code", "execution_count": null, "id": "d8d78b31-0115-47ca-a218-41a05a79a786", "metadata": {}, "outputs": [], "source": [ "sure_strike = xarray.DataArray(\n", " [False, True, False],\n", " dims=[\"Sure Strike\"],\n", " coords={\"Sure Strike\": [\"Normal\", \"Sure Strike\", \"Only on melee crit\"]},\n", ")" ] }, { "cell_type": "markdown", "id": "6eca2c16-15d9-41cf-85f8-cd11d43637a6", "metadata": {}, "source": [ "- Dimensions ``challenge`` and ``sure strike`` represent a what-if analysis.\n", " Roll dice only once and compare the results against different situations.\n", "- For AoE spells with a saving throw (Electric Arc, Breathe Fire) roll damage\n", " only once, but roll saving throw individually for every target.\n", "- For Blazing Bolt, roll both attack and damage individually for every target." ] }, { "cell_type": "code", "execution_count": null, "id": "65ea5a48-ef05-49e9-8129-a8c1993daedd", "metadata": {}, "outputs": [], "source": [ "pf2.set_config(\n", " check_independent_dims=[\"AoE_target\", \"BB_target\"],\n", " check_dependent_dims=[\"challenge\", \"Sure Strike\"],\n", " damage_independent_dims=[\"BB_target\"],\n", " damage_dependent_dims=[\"challenge\", \"Sure Strike\", \"AoE_target\"],\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "6ec09383-c373-49e6-8529-5d3af73a0b93", "metadata": {}, "outputs": [], "source": [ "strike = pf2.damage(\n", " pf2.check(\n", " bonus=atk_bonus.weapon.sel(level=level).item(),\n", " DC=AC - 2,\n", " fortune=sure_strike,\n", " ),\n", " weapon,\n", ")" ] }, { "cell_type": "markdown", "id": "58d96131-4da7-4950-9a0d-e69cb4685235", "metadata": {}, "source": [ "## What are the chances of a critical hit on the initial weapon strike?" ] }, { "cell_type": "code", "execution_count": null, "id": "d92eaa13-930d-4ea8-a257-40d083d27438", "metadata": {}, "outputs": [], "source": [ "melee_crit = strike.outcome == pf2.DoS.critical_success\n", "melee_crit.loc[{\"Sure Strike\": \"Only on melee crit\"}] = True\n", "melee_crit.mean(\"roll\").round(3).to_pandas() * 100.0" ] }, { "cell_type": "markdown", "id": "2a5587ae-46bd-462e-9cb2-70925190c04b", "metadata": {}, "source": [ "## The conditions of the next spell change depending on the strike and equipment\n", "- If the strike was critical, we can use Hematocritical\n", "- If the weapon was rooting, the target is now Clumsy 1\n", "- If the weapon was a sword, th target is now off-guard even if not flanked" ] }, { "cell_type": "code", "execution_count": null, "id": "cf28ac40-7241-4be6-aa4b-995a8173313e", "metadata": {}, "outputs": [], "source": [ "hematocritical = melee_crit if use_hematocritical else xarray.DataArray(False)\n", "clumsy = melee_crit if use_rooting_rune else xarray.DataArray(0)\n", "ranged_off_guard = (\n", " 2 * melee_crit\n", " if (use_sword or use_greater_phantasmal_doorknob)\n", " else xarray.DataArray(0)\n", ")" ] }, { "cell_type": "markdown", "id": "a94aa453-c7b8-4425-a9fa-612af82f362d", "metadata": {}, "source": [ "## Roll damage for the spells" ] }, { "cell_type": "code", "execution_count": null, "id": "ce5b1502-da17-49e4-8e44-fa2f8ce63198", "metadata": {}, "outputs": [], "source": [ "ignition_melee_dmg = pf2.damage(\n", " pf2.check(\n", " atk_bonus.spell.sel(level=level) - 5,\n", " DC=AC - 2 - clumsy,\n", " fortune=hematocritical,\n", " ),\n", " ignition_melee,\n", ")\n", "\n", "ignition_ranged_dmg = pf2.damage(\n", " pf2.check(\n", " atk_bonus.spell.sel(level=level) - 5,\n", " DC=AC - clumsy - ranged_off_guard,\n", " fortune=hematocritical,\n", " ),\n", " ignition_ranged,\n", ")\n", "\n", "live_wire_dmg = pf2.damage(\n", " pf2.check(\n", " atk_bonus.spell.sel(level=level) - 5,\n", " DC=AC - clumsy - ranged_off_guard,\n", " fortune=hematocritical,\n", " ),\n", " live_wire,\n", ")\n", "\n", "AoE_target = xarray.DataArray(\n", " [1, 0, 0],\n", " dims=[\"AoE_target\"],\n", " coords={\"AoE_target\": [\"Strike target\", \"target 2\", \"target 3\"]},\n", ")\n", "\n", "electric_arc_dmg = pf2.damage(\n", " pf2.check(\n", " saves - AoE_target[:2] * clumsy,\n", " DC=atk_bonus.spell.sel(level=level) + 10,\n", " misfortune=hematocritical,\n", " ),\n", " electric_arc,\n", ").rename({\"AoE_target\": \"target\"}) # Align with Blazing Bolt target later\n", "\n", "breathe_fire_dmg = pf2.damage(\n", " pf2.check(\n", " saves - AoE_target * clumsy,\n", " DC=atk_bonus.spell.sel(level=level) + 10,\n", " misfortune=hematocritical,\n", " ),\n", " breathe_fire,\n", ").rename({\"AoE_target\": \"target\"})" ] }, { "cell_type": "markdown", "id": "45653991-b68b-46d2-9286-b816b39aa5d8", "metadata": {}, "source": [ "We need to use a different dimension from before because\n", "the above AoEs have dependent damage rolls (roll only once for all targets),\n", "whereas Blazing Bolt is independent (roll separately for each target).\n", "See ``set_config`` above." ] }, { "cell_type": "code", "execution_count": null, "id": "b6433e2f-7ed6-4324-a952-41e60c848c6e", "metadata": {}, "outputs": [], "source": [ "bb_target = AoE_target.rename({\"AoE_target\": \"BB_target\"})\n", "\n", "blazing_bolt_check = pf2.check(\n", " atk_bonus.spell.sel(level=level) - 5,\n", " DC=AC - (clumsy + ranged_off_guard) * bb_target,\n", " fortune=hematocritical,\n", ")\n", "blazing_bolt_1action_dmg = pf2.damage(\n", " blazing_bolt_check,\n", " blazing_bolt_1action,\n", ").isel(BB_target=0, drop=True)\n", "\n", "blazing_bolt_23actions_dmg = pf2.damage(\n", " blazing_bolt_check,\n", " blazing_bolt_3actions,\n", ").rename({\"BB_target\": \"target\"})" ] }, { "cell_type": "markdown", "id": "5f20e853-ff3d-43fe-b15e-7c1686d4bc2c", "metadata": {}, "source": [ "### Also show:\n", "- A second iterative strike\n", "- a standalone 3-actions Blazing Bolt\n", "- an out-of-round Brine Dragon Bile\n", "- additional damage from Organsight, applied to the initial and iterative Strike on each round" ] }, { "cell_type": "code", "execution_count": null, "id": "a81ffce2-ccbd-47fa-986f-320340ac54cf", "metadata": {}, "outputs": [], "source": [ "strike2 = pf2.damage(\n", " pf2.check(\n", " bonus=atk_bonus.spell.sel(level=level).item() - 5,\n", " DC=AC - 2 - clumsy,\n", " ),\n", " weapon,\n", ")\n", "\n", "blazing_bolt_23actions_noMAP_dmg = pf2.damage(\n", " pf2.check(\n", " atk_bonus.spell.sel(level=level),\n", " DC=AC,\n", " independent_dims={\"BB_target\": 3},\n", " ),\n", " blazing_bolt_3actions,\n", ").rename({\"BB_target\": \"target\"})\n", "\n", "brine_dragon_bile_dmg = pf2.damage(\n", " pf2.check(\n", " atk_bonus.spell.sel(level=level),\n", " DC=AC,\n", " ),\n", " brine_dragon_bile,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "378b5b60-cb06-443d-8179-845a4c2b694c", "metadata": {}, "outputs": [], "source": [ "organsight_check = pf2.check(\n", " atk_bonus.organsight.sel(level=level),\n", " DC=defenses.recall_knowledge,\n", ")\n", "\n", "organsight_check[\"recall_knowledge_outcome\"] = organsight_check.outcome\n", "organsight_check[\"strike_outcome\"] = xarray.concat(\n", " [\n", " strike.outcome,\n", " xarray.where(\n", " strike.outcome >= pf2.DoS.success,\n", " pf2.DoS.no_roll,\n", " strike2.outcome,\n", " ),\n", " ],\n", " dim=\"strike\",\n", ")\n", "organsight_check[\"outcome\"] = xarray.where(\n", " organsight_check.outcome >= pf2.DoS.success,\n", " xarray.where(\n", " organsight_check.strike_outcome >= pf2.DoS.success,\n", " organsight_check.strike_outcome,\n", " pf2.DoS.no_roll,\n", " ),\n", " pf2.DoS.no_roll,\n", ")\n", "organsight_check.coords[\"strike\"] = [\"initial\", \"iterative\"]\n", "# \"Only on melee crit\" makes no sense here\n", "organsight_check = organsight_check.isel({\"Sure Strike\": slice(2)})\n", "organsight_dmg = pf2.damage(\n", " organsight_check,\n", " organsight,\n", " independent_dims=[\"strike\"],\n", ")" ] }, { "cell_type": "markdown", "id": "bd5167d0-a70f-4682-aa5a-56b72770952e", "metadata": {}, "source": [ "## Mean damage for every action" ] }, { "cell_type": "code", "execution_count": null, "id": "8c7a8d2a-145e-42f8-a67d-60433a44343e", "metadata": {}, "outputs": [], "source": [ "rows = {\n", " \"Weapon Strike (flanked)\": strike,\n", " \"Iterative Weapon Strike (flanked) (MAP-5)\": strike2,\n", " \"Organsight (first strike)\": organsight_dmg.isel(strike=0, drop=True),\n", " \"Organsight (iterative strike)\": organsight_dmg.isel(strike=1, drop=True),\n", " \"Ignition (melee, flanked) (MAP-5)\": ignition_melee_dmg,\n", " \"Ignition (ranged) (MAP-5)\": ignition_ranged_dmg,\n", " \"Live Wire (MAP-5)\": live_wire_dmg,\n", " \"Electric Arc (1 target)\": electric_arc_dmg.isel(target=slice(1)),\n", " \"Electric Arc (2 targets)\": electric_arc_dmg,\n", " \"Breathe Fire (1 target)\": breathe_fire_dmg.isel(target=slice(1)),\n", " \"Breathe Fire (2 targets)\": breathe_fire_dmg.isel(target=slice(2)),\n", " \"Breathe Fire (3 targets)\": breathe_fire_dmg,\n", " \"Blazing Bolt > (MAP-5)\": blazing_bolt_1action_dmg,\n", " \"Blazing Bolt >> (MAP-5)\": blazing_bolt_23actions_dmg.isel(target=slice(2)),\n", " \"Blazing Bolt >>> (MAP-5)\": blazing_bolt_23actions_dmg,\n", " \"Blazing Bolt >>> (standalone)\": blazing_bolt_23actions_noMAP_dmg,\n", " \"Brine Dragon Bile (standalone)\": brine_dragon_bile_dmg,\n", "}\n", "\n", "damages = []\n", "for dmg in rows.values():\n", " dmg = dmg.total_damage.mean(\"roll\")\n", " if \"target\" in dmg.dims:\n", " dmg = dmg.sum(\"target\")\n", " damages.append(dmg)\n", "\n", "total_damage = xarray.concat(damages, dim=\"activity\", join=\"outer\", coords=\"minimal\")\n", "total_damage.coords[\"activity\"] = list(rows)\n", "\n", "total_damage.loc[\n", " {\"activity\": total_damage.activity[0], \"Sure Strike\": \"Only on melee crit\"}\n", "] = float(\"nan\")\n", "total_damage.loc[\n", " {\"activity\": total_damage.activity[-2:], \"Sure Strike\": \"Only on melee crit\"}\n", "] = float(\"nan\")\n", "total_damage.loc[\n", " {\"activity\": total_damage.activity[-3:], \"Sure Strike\": \"Sure Strike\"}\n", "] = float(\"nan\")\n", "total_damage.stack(col=[\"challenge\", \"Sure Strike\"]).to_pandas().round(1)" ] }, { "cell_type": "markdown", "id": "b9be311d-fd14-475d-8078-54d0e9dca6f8", "metadata": {}, "source": [ "## Outcome probability for the initial Strike" ] }, { "cell_type": "code", "execution_count": null, "id": "1daa5aa6-6129-4c2d-9b53-10a9144f51ac", "metadata": {}, "outputs": [], "source": [ "(\n", " pf2.outcome_counts(strike)\n", " .isel({\"Sure Strike\": slice(2)})\n", " .stack(col=[\"challenge\", \"Sure Strike\"])\n", " .to_pandas()\n", " .round(3)\n", " * 100.0\n", ")" ] }, { "cell_type": "markdown", "id": "f540abfb-88d6-423e-ad96-6bd4894f1de8", "metadata": {}, "source": [ "## Outcome probability for the iterative Strike" ] }, { "cell_type": "code", "execution_count": null, "id": "ef8992c4-856b-408d-be6c-71f71f8a3e12", "metadata": {}, "outputs": [], "source": [ "(\n", " pf2.outcome_counts(strike2)\n", " .stack(col=[\"challenge\", \"Sure Strike\"])\n", " .to_pandas()\n", " .round(3)\n", " * 100.0\n", ")" ] }, { "cell_type": "markdown", "id": "16fb286b-b7ce-4822-bde0-d6764110f37e", "metadata": {}, "source": [ "## Outcome probability for Ignition (melee)" ] }, { "cell_type": "code", "execution_count": null, "id": "941c255a-7d95-4c3e-a0a3-0f18e6ba537b", "metadata": {}, "outputs": [], "source": [ "(\n", " pf2.outcome_counts(ignition_melee_dmg)\n", " .stack(col=[\"challenge\", \"Sure Strike\"])\n", " .to_pandas()\n", " .round(3)\n", " * 100.0\n", ")" ] }, { "cell_type": "markdown", "id": "af5fd4eb-a660-4727-ba8b-75529dfc287d", "metadata": {}, "source": [ "## Outcome probability for Electric Arc / Breathe Fire / Fireball" ] }, { "cell_type": "code", "execution_count": null, "id": "f4dba450-ce2b-4460-8d40-dc866445d2fe", "metadata": {}, "outputs": [], "source": [ "(\n", " pf2.outcome_counts(electric_arc_dmg)\n", " .stack(row=[\"target\", \"outcome\"])\n", " .stack(col=[\"challenge\", \"Sure Strike\"])\n", " .to_pandas()\n", " .round(3)\n", " * 100.0\n", ")" ] }, { "cell_type": "markdown", "id": "2287e888-9d49-4787-aa26-2056432769c7", "metadata": {}, "source": [ "## Outcome probability for Organsight\n", "### Recall Knowledege" ] }, { "cell_type": "code", "execution_count": null, "id": "00e41230-f244-414c-8c30-75a24d7a5668", "metadata": {}, "outputs": [], "source": [ "(\n", " pf2.outcome_counts(organsight_dmg.recall_knowledge_outcome).to_pandas().T.round(3)\n", " * 100.0\n", ")" ] }, { "cell_type": "markdown", "id": "445af2ed-c926-4e58-bdfa-e75287eb28e6", "metadata": {}, "source": [ "### Triggering Strike\n", "The iterative strike is \"no roll\" whenever the initial strike connects, as the damage from Organsight can only be discharged once per round." ] }, { "cell_type": "code", "execution_count": null, "id": "8dd8bef2-2ccb-43ac-97d4-6217e561fa86", "metadata": {}, "outputs": [], "source": [ "(\n", " pf2.outcome_counts(organsight_dmg.strike_outcome)\n", " .stack(row=[\"strike\", \"outcome\"])\n", " .stack(col=[\"challenge\", \"Sure Strike\"])\n", " .to_pandas()\n", " .round(3)\n", " * 100.0\n", ")" ] }, { "cell_type": "markdown", "id": "8a595986-7339-42f7-956f-c345504ac413", "metadata": {}, "source": [ "### Combined chance to apply Organsight" ] }, { "cell_type": "code", "execution_count": null, "id": "69d293b4-3ae7-4c32-b9ab-bbe773a0b51b", "metadata": {}, "outputs": [], "source": [ "(\n", " pf2.outcome_counts(organsight_dmg)\n", " .stack(row=[\"strike\", \"outcome\"])\n", " .stack(col=[\"challenge\", \"Sure Strike\"])\n", " .to_pandas()\n", " .round(3)\n", " * 100.0\n", ")" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.5" } }, "nbformat": 4, "nbformat_minor": 5 }