From 082d3d66cee174ed458f279dbbbba54f9769573b Mon Sep 17 00:00:00 2001 From: njlyon0 Date: Fri, 3 May 2024 12:55:44 -0400 Subject: [PATCH] Tweaked `xp_pool` to use the DMG's party level-to-XP data if average party level is an integer but otherwise calculate the relevant XP manually --- R/xp_pool.R | 47 ++++++++++++++++++++++--------- man/xp_pool.Rd | 2 +- vignettes/dndr_99_dmg-vs-dndr.Rmd | 2 +- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/R/xp_pool.R b/R/xp_pool.R index 31bba75..c5cf856 100644 --- a/R/xp_pool.R +++ b/R/xp_pool.R @@ -2,7 +2,7 @@ #' #' @description Returns the total XP (experience points) of all creatures that would make an encounter the specified level of difficulty for a party of the supplied level. This 'pool' can be used by a GM (game master) to "purchase" monsters to identify how many a party is likely to be able to handle given their average level. NOTE: this does not take into account creature-specific abilities or traits so care should be taken if a monster has many such traits that modify its difficulty beyond its experience point value. #' -#' @param party_level (numeric) integer indicating the average party level. If all players are the same level, that level is the average party level +#' @param party_level (numeric) integer indicating the average party level. If all players are the same level, that level is the average party level. Non-integer values are supported but results will be slightly affected #' @param party_size (numeric) integer indicating how many player characters (PCs) are in the party #' @param difficulty (character) one of "easy", "medium", "hard", or "deadly" for the desired difficulty of the encounter. #' @@ -16,31 +16,50 @@ xp_pool <- function(party_level = NULL, party_size = NULL, difficulty = NULL){ # Error out if party_level or difficulty is unspecified - if(base::is.null(party_level) | base::is.null(party_size) | base::is.null(difficulty)) stop("At least one parameter is unspecified. See `?dndR::xp_pool()` for details") + if(base::is.null(party_level) | base::is.null(party_size) | base::is.null(difficulty)) + stop("At least one parameter is unspecified. See `?dndR::xp_pool()` for details") if(base::is.numeric(party_level) != TRUE | base::is.numeric(party_size) != TRUE) stop("Party level and party size must be a number") # Error out if too many party levels are provided - if(base::length(party_level) > 1) stop("Too many values provided. What is the *average* level of PCs in the party?") + if(base::length(party_level) > 1) + stop("Too many values provided. What is the *average* level of PCs in the party?") # Error out if difficulty is not supported if(!base::tolower(difficulty) %in% c('easy', 'medium', 'hard', 'deadly')) stop("Unrecognized difficulty level. Please use only one of 'easy', 'medium', 'hard', or 'deadly'") - # Identify the quadratic coefficients - ## (Did this manually and got a decent fit for the line) - a <- 8.216374 - b <- -26.49122 - c <- 43.27485 + # If party level is an integer, use the DMG's values + if(is.integer(party_level) == TRUE){ - # Calculate XP value for adventurers of this level (for an easy encounter) - base_xp_amount <- ( (a * (party_level^2)) + (b * party_level) + c ) + # Define XP / player values from DMG + xp_df <- data.frame('pc_level' = 1:20, + 'easy_xp' = c(25, 50, 75, 125, 250, 300, 350, 450, + 550, 600, 800, 1000, 1100, 1250, 1400, + 1600, 2000, 2100, 2400, 2800)) - # The formula for the line isn't perfect so we have another quick modification to make - if(party_level < 2) { easy_xp_amount <- base_xp_amount } - if(party_level >= 2 & party_level < 12) { easy_xp_amount <- (base_xp_amount + 55) } - if(party_level >= 12) { easy_xp_amount <- base_xp_amount } + # Grab the appropriate one + easy_xp_amount <- xp_df[xp_df$pc_level == party_level, ]$easy_xp + + # If party level is *not* an integer, calculate by hand + } else { + + # Identify the quadratic coefficients + ## (Did this manually and got a decent fit for the line) + a <- 8.216374 + b <- -26.49122 + c <- 43.27485 + + # Calculate XP value for adventurers of this level (for an easy encounter) + base_xp_amount <- ( (a * (party_level^2)) + (b * party_level) + c ) + + # The formula for the line isn't perfect so we have another quick modification to make + if(party_level < 2) { easy_xp_amount <- base_xp_amount } + if(party_level >= 2 & party_level < 12) { easy_xp_amount <- (base_xp_amount + 55) } + if(party_level >= 12) { easy_xp_amount <- base_xp_amount } + + } # Close player level integer vs. not conditional # Now multiply as needed for the desired difficulty (this is straight from the DMG's table) if(difficulty == "easy"){ single_xp_amount <- easy_xp_amount } diff --git a/man/xp_pool.Rd b/man/xp_pool.Rd index f89b7c4..5bbea3a 100644 --- a/man/xp_pool.Rd +++ b/man/xp_pool.Rd @@ -7,7 +7,7 @@ xp_pool(party_level = NULL, party_size = NULL, difficulty = NULL) } \arguments{ -\item{party_level}{(numeric) integer indicating the average party level. If all players are the same level, that level is the average party level} +\item{party_level}{(numeric) integer indicating the average party level. If all players are the same level, that level is the average party level. Non-integer values are supported but results will be slightly affected} \item{party_size}{(numeric) integer indicating how many player characters (PCs) are in the party} diff --git a/vignettes/dndr_99_dmg-vs-dndr.Rmd b/vignettes/dndr_99_dmg-vs-dndr.Rmd index 8d0f38d..dfb1d6a 100644 --- a/vignettes/dndr_99_dmg-vs-dndr.Rmd +++ b/vignettes/dndr_99_dmg-vs-dndr.Rmd @@ -66,7 +66,7 @@ ggplot(cr_actual, aes(x = cr, y = xp, shape = source)) + ### `xp_pool` vs. DMG -The DMG specifies the XP threshold *per player* for a given difficulty while my function asks for the *average* player level and the party size. This difference keeps the function streamlined and flexible for parties of any size. Rather than embedding the DMG's table for encounter XP, `xp_pool` actually uses the formula for the line defining the XP-party level curve implicit in the DMG. This has the added benefit of being able to handle non-integer values for average party_level. +The DMG specifies the XP threshold *per player* for a given difficulty while my function asks for the *average* player level and the party size. This difference keeps the function streamlined and flexible for parties of any size. If average party level is an integer, the DMG's table for the encounter XP to player level is used. Otherwise, `xp_pool` uses the formula for the line defining the XP-party level curve implicit in the DMG's table. This has the benefit of being able to handle parties where not all players are the same level. Below is a comparison of the DMG's XP-to-party level curve versus the one obtained by `xp_pool`.