{ "cells": [ { "cell_type": "markdown", "id": "b6e5b96d-48f9-4574-849c-29e4eb21baef", "metadata": {}, "source": [ "# Godbreaker\n", "The {prd_feats}`Godbreaker <6049>` feat (Monk 20, Wrestler 20) is the ultimate Super Sayan move - but how likely are you to pull it off completely?\n", "\n", "Let's compare these builds:\n", "\n", "1. Monk (**2.** Crushing Grab, **6.** Whilrling Throw, **19.** Perfected Form, **20.** Godbreaker)\n", "2. Fighter (**2.** Wrestler Dedication, **4.** Crushing Grab, **8.** Whirling Throw, **10.** Agile Grace, **20.** Godbreaker)\n", "3. Ranger (**1.** Flurry Edge, **2.** Wrestler Dedication, **4.** Crushing Grab, **8.** Whirling Throw, **20.** Godbreaker)\n", "4. Any other martial, e.g. animal barbarians (**2.** Wrestler Dedication, **4.** Crushing Grab, **8.** Whirling Throw, **20.** Godbreaker)\n", "5. Any spellcaster (**2.** Wrestler Dedication, **4.** Crushing Grab, **8.** Whirling Throw, **20.** Godbreaker)\n", "\n", "Assume that:\n", "- All use +3 Greater Fearsome Keen Handwraps of Mighty Blows\n", "- All martials have an Apex item for +1 STR; start at +4 STR and increase it at all bumps\n", "- Non-martials start at +3 STR and increase it at all bumps\n", "- All perform the second and third strike with an agile unarmed attack\n", "- The turn starts with the target already grabbed\n", "\n", "For the sake of tidiness we're skipping damage calculations." ] }, { "cell_type": "code", "execution_count": null, "id": "b2e6b9cc-930c-4a84-8ba7-62a7e278ffe3", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import xarray\n", "\n", "import pathfinder2e_stats as pf2" ] }, { "cell_type": "code", "execution_count": null, "id": "a18fa88b-9671-4aa7-a702-e4ff51f1cdf5", "metadata": {}, "outputs": [], "source": [ "# Keen rune?\n", "keen = True\n", "\n", "# Fearsome, Greater Fearsome, Crushing, or Greater Crushing Rune?\n", "# What's the status penalty to AC on a critical hit?\n", "fearsome = -2\n", "\n", "# How much help is the attacker getting from the other party members?\n", "# Bestial Mutagen or Fury Cocktail for an extra +1 item bonus to attack\n", "# Aid or Albatross Curse for +1 circumstance bonus\n", "# Bless or Corageous Anthem for +1 status bonus\n", "# Heroism for +1 (3rd), +2 (6th), +3 (9th) status bonus\n", "party_help = {\n", " \"dims\": [\"party help\"],\n", " \"coords\": {\"party help\": [\"nothing\", \"everything\"]},\n", "}\n", "extra_atk_bonus = xarray.DataArray([0, 5], **party_help)\n", "\n", "# Did another party member apply Frightened, sickened, or clumsy\n", "# when the attacker's turn begins?\n", "initial_AC_status_penalty = xarray.DataArray([0, -2], **party_help)" ] }, { "cell_type": "code", "execution_count": null, "id": "1ab92d08-974b-44c1-b74a-0a139c690665", "metadata": {}, "outputs": [], "source": [ "atk = (\n", " pf2.tables.SIMPLE_PC.weapon_attack_bonus[\n", " [\"monk\", \"fighter\", \"ranger\", \"barbarian\", \"wizard\"]\n", " ]\n", " .sum(\"component\")\n", " .sel(mastery=True, level=20)\n", " .to_array(\"class\")\n", ")\n", "atk.to_pandas().to_frame(\"attack bonus\")" ] }, { "cell_type": "markdown", "id": "a3efb658-f924-4a2c-a85e-c8257d310070", "metadata": {}, "source": [ "Three standard targets:\n", "- level 18 with low AC\n", "- level 20 with moderate AC\n", "- level 22 with high AC\n", "\n", "The target starts grabbed and remains grabbed throughout all strikes.\n", "If a strike fails, the grab is lost and the strikes end." ] }, { "cell_type": "code", "execution_count": null, "id": "2a489a1f-7799-4115-ae6b-028502e0f222", "metadata": {}, "outputs": [], "source": [ "AC = pf2.tables.SIMPLE_NPC.AC.sel(level=20) - 2\n", "AC.to_pandas().to_frame(\"AC\")" ] }, { "cell_type": "code", "execution_count": null, "id": "3a7866c0-6854-42c1-a6da-853a84921d17", "metadata": {}, "outputs": [], "source": [ "MAP = xarray.DataArray(\n", " [\n", " [0, -4, -8], # Monk\n", " [0, -3, -6], # Fighter with Agile Grace\n", " [0, -3, -6], # Flurry ranger\n", " [0, -4, -8], # Any other martial\n", " [0, -4, -8], # Any spellcaster\n", " ],\n", " dims=[\"class\", \"strike\"],\n", " coords={\"class\": atk.coords[\"class\"], \"strike\": [1, 2, 3]},\n", ")\n", "MAP.to_pandas()" ] }, { "cell_type": "markdown", "id": "c080f4e6-a63a-48ed-b361-3e84b7256dcc", "metadata": {}, "source": [ "**Perfected form** (level 19 monk feature):\n", "On your first Strike of your turn, if you roll lower than 10,\n", "you can treat the attack roll as a 10. This is a fortune effect." ] }, { "cell_type": "code", "execution_count": null, "id": "f2b3adde-ed78-41d8-8ed6-c4a461d058df", "metadata": {}, "outputs": [], "source": [ "perfected_form = xarray.DataArray(\n", " np.zeros((3, 5), dtype=bool),\n", " dims=[\"strike\", \"class\"],\n", " coords={\"strike\": [1, 2, 3], \"class\": atk.coords[\"class\"]},\n", ")\n", "perfected_form[0, 0] = True\n", "perfected_form.to_pandas()" ] }, { "cell_type": "code", "execution_count": null, "id": "d3bf1f8f-a851-480c-a448-d3486f2fc48c", "metadata": {}, "outputs": [], "source": [ "kwargs = dict(\n", " bonus=atk + MAP + extra_atk_bonus,\n", " perfected_form=perfected_form,\n", " keen=keen,\n", " # Roll independently for the three strikes...\n", " independent_dims=[\"strike\"],\n", " # ... but roll only once across the different what-if scenarios\n", " # and apply the result of the d20 to different bonuses and DCs.\n", " dependent_dims=[\"class\", \"challenge\", \"party help\"],\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "817bd857-9b09-41aa-ac5b-59afa1916d72", "metadata": {}, "outputs": [], "source": [ "AC_status1 = initial_AC_status_penalty\n", "strike1 = pf2.check(DC=AC + AC_status1, **kwargs).outcome.sel(strike=1, drop=True)" ] }, { "cell_type": "code", "execution_count": null, "id": "465861c2-ad14-41b0-9fd6-abf71234be3c", "metadata": {}, "outputs": [], "source": [ "# Critical hits trigger the fearsome rune\n", "AC_status2 = np.minimum(\n", " xarray.where(strike1 == pf2.DoS.critical_success, fearsome, 0),\n", " AC_status1,\n", ")\n", "strike2 = xarray.where(\n", " strike1 >= pf2.DoS.success,\n", " pf2.check(DC=AC + AC_status2, **kwargs).outcome.sel(strike=2, drop=True),\n", " pf2.DoS.no_roll,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "28440e76-d936-46dc-a5c7-41f81c2f93e5", "metadata": {}, "outputs": [], "source": [ "# Critical hits trigger the fearsome rune\n", "AC_status3 = np.minimum(\n", " xarray.where(strike2 == pf2.DoS.critical_success, fearsome, 0),\n", " AC_status2,\n", ")\n", "strike3 = xarray.where(\n", " strike2 >= pf2.DoS.success,\n", " pf2.check(DC=AC + AC_status3, **kwargs).outcome.sel(strike=3, drop=True),\n", " pf2.DoS.no_roll,\n", ")" ] }, { "cell_type": "code", "execution_count": null, "id": "d9c992bd-f233-44b9-a788-afc29050f611", "metadata": {}, "outputs": [], "source": [ "strikes = xarray.concat([strike1, strike2, strike3], dim=\"strike\")\n", "strikes.coords[\"strike\"] = [1, 2, 3]\n", "counts = pf2.outcome_counts(strikes)" ] }, { "cell_type": "markdown", "id": "42385a51-2ae5-4e22-bdea-633cb8624adc", "metadata": {}, "source": [ "## Probability to obtain at least a hit on each strike\n", "- A hit on the second strike can only happen if you hit on the first.\n", "- A hit on the third strike, and consecutive final slam into the ground, can only happen if you hit on the first two.\n", "- The probability to fully complete the Godbreaker three-action activity is equal to the probability of hitting on the third strike." ] }, { "cell_type": "code", "execution_count": null, "id": "e45a915d-a2c2-47b8-bdc3-1af166809eee", "metadata": {}, "outputs": [], "source": [ "(\n", " counts.sel(outcome=[\"Critical success\", \"Success\"])\n", " .sum(\"outcome\")\n", " .stack(row=[\"challenge\", \"class\"], column=[\"party help\", \"strike\"])\n", " .to_pandas()\n", " .round(3)\n", " * 100\n", ")" ] }, { "cell_type": "markdown", "id": "5971320c-0742-4bb4-a30b-215f4ef198cc", "metadata": {}, "source": [ "## Aside\n", "Godbreaker has the following extra damage and effects, compared to three iterative strikes:\n", "- 0 hits: 10 falling damage\n", "- 1 hit: 20 falling damage\n", "- 2 hits: 30 falling damage\n", "- 3 hits: 40 falling damage + Crushing Grab + 1 strike damage + grapple continues into next round" ] }, { "cell_type": "markdown", "id": "84c04f17-8e8c-4d75-ae4d-fdb0eea829a4", "metadata": {}, "source": [ "## Probability of completing the Godbreaker\n", "This is just a selection of `strike=3` from the previous table, for ease of reading." ] }, { "cell_type": "code", "execution_count": null, "id": "c70fd074-b650-4eaa-949a-0c85fb6fd286", "metadata": {}, "outputs": [], "source": [ "(\n", " counts.sel(outcome=[\"Critical success\", \"Success\"])\n", " .sum(\"outcome\")\n", " .sel(strike=3)\n", " .stack(row=[\"party help\", \"challenge\"])\n", " .to_pandas()\n", " .round(3)\n", " * 100\n", ").T" ] }, { "cell_type": "markdown", "id": "e73ae792-bd1a-4a65-bf05-a0a9f4c4e4ad", "metadata": {}, "source": [ "## Conclusions\n", "- The Godbreaker activity has a _very_ slim chance of complete success as a baseline, with a big risk of feeling underwhelming.\n", "- Party assistance makes an enormous difference.\n", "- The various classes compare as follows:\n", " 1. **Fighter/Wrestlers** are by far the best at applying GodBreaker in all situations, thanks to their higher baseline attack bonus plus Agile Grace;\n", " 2. **Flurry Ranger/Wrestlers** come second thanks to their low MAP;\n", " 3. **Monks**, for which this feat was designed, are only third best despite their Perfected Form;\n", " 4. Other martials and spellcasters should just avoid this feat due to their inability to consistently connect 3 strikes in a row with MAP." ] } ], "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 }