processHit = function(attackData,advantage = "Norm"){
if(!is.null(attackData)){
attackRoll = switch(advantage,
Norm = roll('1d20'),
Adv = roll('2d20d1'),
DisAdv = roll('2d20dh1')
)
# attackRoll = roll('1d20')
attack = attackRoll + attackData$attack
if('' == attackData$damage){
attackData$damage = '0'
}
if(attackRoll == 1){
damageText = 'CRITICAL MISS'
damage = 0
} else if(attackRoll == 20){
critDamage = stringr::str_replace_all(attackData$damage, "\\d+(?=d|D)", function(m) as.integer(m) * 2)
damageText = capture.output({damage = diceSyntax::roll(critDamage)}) %>%
gsub('(\\[1\\] )|"','',.) %>% paste0(collapse = '\n')
} else{
damageText = capture.output({damage = diceSyntax::roll(attackData$damage)}) %>%
gsub('(\\[1\\] )|"','',.) %>% paste0(collapse = '\n')
}
roll = c('Attack'= attack, 'Damage' = damage,
crit = (attackRoll == 20)-(attackRoll == 1))
if(advantage == 'Norm'){
out = glue::glue("Pet's {attackData$name}:\n",
"{capture.output(roll) %>% paste0(collapse = '\n')}")
} else{
out = glue::glue("Pet's {attackData$name} {advantage}:\n",
"{capture.output(roll) %>% paste0(collapse = '\n')}")
}
} else{
out = ''
}
return(out)
}
monsters = monsteR::monsters
monsters$`Homonculus Servant` = list(
HP = 8, # minimum HP,
AC = 13,
abilityScores = c(Str = 4, Dex = 15, Con = 12, Int = 10, Wis = 10, Cha = 7),
saves = c(Str = -3, Dex = 4, Con = 1, Int = 0, Wis = 0, Cha = -2),
actions = list(
`Force Strike` = list(
name = 'Force Strike',
attack_bonus = 4,
damage_dice = '1d4',
damage_bonus = 2
)
)
)
monsters = c(monsters[length(monsters)],monsters[-length(monsters)])
monsters$`Steel Defender` = list(
HP = 17, # minimum hp
AC = 15, # change at level 15
abilityScores = c(Str = 14, Dex = 12, Con = 14, Int = 4, Wis = 10, Cha = 6),
saves = c(Str = 2, Dex = 3, Con = 4, Int = -3, Wis = 0, Cha = -2),
actions = list(
`Force-Empowered Rend`= list(
name = 'Force-Empowered Rend',
attack_bonus = 4,
damage_dice = '1d8',
damage_bonus = 2,
saves = c(Str = 2, Dex = 3, Con = 4, Int = -4, Wis = 0, Cha = -2)),
Repair = list(
name = 'Repair',
attack_bonus = 0,
damage_dice = '2d8',
damage_bonus = 2,
saves = c(Str = 2, Dex = 3, Con = 4, Int = -4, Wis = 0, Cha = -2)),
Mending = list(
name = 'Mending',
attack_bonus = 0,
damage_dice = '2d6',
damage_bonus = 0,
saves = c(Str = 2, Dex = 3, Con = 4, Int = -4, Wis = 0, Cha = -2))
)
)
skillAttributes = c('Str',
rep('Dex',3),
rep('Int',5),
rep('Wis',5),
rep('Cha',4))
monsters$`Steel Defender`$skills = stat2mod(monsters$`Steel Defender`$abilityScores)[skillAttributes]
monsters$`Homonculus Servant`$skills = stat2mod(monsters$`Homonculus Servant`$abilityScores)[skillAttributes]
names(monsters$`Steel Defender`$skills) = names(monsters$Aboleth$skills)
names(monsters$`Homonculus Servant`$skills) = names(monsters$Aboleth$skills)
monsters$`Steel Defender`$skills['Athletics'] = 4
monsters$`Steel Defender`$skills['Perception'] = 4
monsters$`Homonculus Servant`$skills['Perception'] = 4
monsters$`Homonculus Servant`$skills['Stealth'] = 4
monsters = c(monsters[length(monsters)],monsters[-length(monsters)])
monsters$`Animate Objects` =
list(actions =
list(
'Tiny' = list(
name = 'Tiny',
attack_bonus = 8,
damage_dice = '1d4',
damage_bonus = 5,
default_count = 10,
saves = c(Str = -3, Dex = 4, Con = 0, Int = -4, Wis = -4, Cha = -5)),
'Small' = list(
name = 'Small',
attack_bonus = 6,
damage_dice = '1d8',
damage_bonus = 2,
default_count = 10,
saves = c(Str = -2, Dex = 3, Con = 0, Int = -4, Wis = -4, Cha = -5)),
"Medium" = list(
name = 'Medium',
attack_bonus = 5,
damage_dice = '2d6',
damage_bonus = 1,
default_count = 5,
saves = c(Str = 0, Dex = 2, Con = 0, Int = -4, Wis = -4, Cha = -5)),
"Large" = list(
name = 'Large',
attack_bonus = 6,
damage_dice = '2d10',
damage_bonus = 2,
default_count = 2,
saves = c(Str = 2, Dex = 0, Con = 0, Int = -4, Wis = -4, Cha = -5)),
"Huge" = list(
name = 'Huge',
attack_bonus = 8,
damage_dice = '2d12',
damage_bonus = 2,
saves = c(Str = 4, Dex = -2, Con = 0, Int = -4, Wis = -4, Cha = -5))
),
HP = 80
)
monsters = c(monsters[length(monsters)],monsters[-length(monsters)])
petUI = function(id){
ns = NS(id)
tagList(
tags$script("Shiny.addCustomMessageHandler('resetInputValue', function(variableName){
Shiny.onInputChange(variableName, null);
});"),
wellPanel(style = 'overflow-y:scroll;max-height: 400px',
fluidRow(
column(6,
healthUI(ns('petHealth'))),
# htmlOutput(ns('HPSliderColorController')),
# div(class = ns('HPsliderDiv'),
# sliderInput(inputId = ns('healthSlider'), value = 1, label = 'HP',min = 0,max = 1,step = 1, width = '100%')
# ),
# div(
# numericInput(inputId = ns('increment'),
# label = '', value = 1,step =1, min = 1,
# width = '70px'),style="display:inline-block"),
# actionButton(ns('minusHealth'),'-', class = 'modButton'),
# actionButton(ns('plusHealth'),'+',class = 'modButton'),
# actionButton(ns('plusTempHealth'),'+ 0',
# class = 'modButton',style = 'background-color:#6959CD'),
# actionButton(ns('setHealth'),'Set', class = 'modButton')),
column(6,
shiny::selectInput(inputId = ns('monster'),label = 'Load SRD monster',choices = c('',names(monsters))),
numericInput(ns('AC'),label = 'AC',value = 10,width = '70px'),
radioGroupButtons(inputId = ns('advantage'),
choices = c('DisAdv','Norm','Adv'),
selected = 'Norm',
status = "primary"))
),
fluidRow(
column(7,
fluidRow(
div(textInput(ns('action1-name'),label = 'Attack',width = '140px'),style="display:inline-block"),
div(numericInput(ns('action1-attack'),label = 'Atk Bonus',value = 0,width = '70px'),style="display:inline-block"),
div(textInput(ns('action1-damage'), label = 'Damage',width = '90px'),style="display:inline-block"),
actionButton(ns('action1-button'),label = 'Attack!', class = 'weaponButton')
),
fluidRow(
div(textInput(ns('action2-name'),label = '',width = '140px'),style="display:inline-block"),
div(numericInput(ns('action2-attack'),label = '',value = 0,width = '70px'),style="display:inline-block"),
div(textInput(ns('action2-damage'), label = '',width = '90px'),style="display:inline-block"),
actionButton(ns('action2-button'),label = 'Attack!', class = 'weaponButton')
),
fluidRow(
div(textInput(ns('action3-name'),label = '',width = '140px'),style="display:inline-block"),
div(numericInput(ns('action3-attack'),label = '',value = 0,width = '70px'),style="display:inline-block"),
div(textInput(ns('action3-damage'), label = '',width = '90px'),style="display:inline-block"),
actionButton(ns('action3-button'),label = 'Attack!', class = 'weaponButton')
),
fluidRow(
div(textInput(ns('action4-name'),label = '',width = '140px'),style="display:inline-block"),
div(numericInput(ns('action4-attack'),label = '',value = 0,width = '70px'),style="display:inline-block"),
div(textInput(ns('action4-damage'), label = '',width = '90px'),style="display:inline-block"),
actionButton(ns('action4-button'),label = 'Attack!', class = 'weaponButton')
),
fluidRow(
div(textInput(ns('action5-name'),label = '',width = '140px'),style="display:inline-block"),
div(numericInput(ns('action5-attack'),label = '',value = 0,width = '70px'),style="display:inline-block"),
div(textInput(ns('action5-damage'), label = '',width = '90px'),style="display:inline-block"),
actionButton(ns('action5-button'),label = 'Attack!', class = 'weaponButton')
)),
column(5,
fluidRow(
actionButton(ns('str'), label='Str:',class = 'weaponButton',width = '60px',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
div(numericInput(ns('str-mod'), label = '', value = 0, width = '70px'),style="display:inline-block")
),
fluidRow(
actionButton(ns('dex'), label='Dex:',class = 'weaponButton',width = '60px',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
div(numericInput(ns('dex-mod'), label = '', value = 0, width = '70px'),style="display:inline-block")
),
fluidRow(
actionButton(ns('con'), label='Con:',class = 'weaponButton',width = '60px',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
div(numericInput(ns('con-mod'), label = '', value = 0, width = '70px'),style="display:inline-block")
),
fluidRow(
actionButton(ns('int'), label='Int:',class = 'weaponButton',width = '60px',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
div(numericInput(ns('int-mod'), label = '', value = 0, width = '70px'),style="display:inline-block")
),
fluidRow(
actionButton(ns('wis'), label='Wis:',class = 'weaponButton',width = '60px',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
div(numericInput(ns('wis-mod'), label = '', value = 0, width = '70px'),style="display:inline-block")
),
fluidRow(
actionButton(ns('cha'), label='Cha:',class = 'weaponButton',width = '60px',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
div(numericInput(ns('cha-mod'), label = '', value = 0, width = '70px'),style="display:inline-block")
),
fluidRow(
div(textInput(ns('extra1-name'),label = '',width = '120px'),style="display:inline-block"),
div(numericInput(ns('extra1-bonus'),label = '',value = 0,width = '70px'),style="display:inline-block"),
actionButton(ns('extra1-button'),label = div(img(src = 'icons/dice-twenty-faces-twenty.png',
height = 20,
width = 20)), class = 'weaponButton',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
),
fluidRow(
div(textInput(ns('extra2-name'),label = '',width = '120px'),style="display:inline-block"),
div(numericInput(ns('extra2-bonus'),label = '',value = 0,width = '70px'),style="display:inline-block"),
actionButton(ns('extra2-button'),label = div(img(src = 'icons/dice-twenty-faces-twenty.png',
height = 20,
width = 20)), class = 'weaponButton',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
),
fluidRow(
div(textInput(ns('extra3-name'),label = '',width = '120px'),style="display:inline-block"),
div(numericInput(ns('extra3-bonus'),label = '',value = 0,width = '70px'),style="display:inline-block"),
actionButton(ns('extra3-button'),label = div(img(src = 'icons/dice-twenty-faces-twenty.png',
height = 20,
width = 20)), class = 'weaponButton',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
),
fluidRow(
div(textInput(ns('extra4-name'),label = '',width = '120px'),style="display:inline-block"),
div(numericInput(ns('extra4-bonus'),label = '',value = 0,width = '70px'),style="display:inline-block"),
actionButton(ns('extra4-button'),label = div(img(src = 'icons/dice-twenty-faces-twenty.png',
height = 20,
width = 20)), class = 'weaponButton',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
),
fluidRow(
div(textInput(ns('extra5-name'),label = '',width = '120px'),style="display:inline-block"),
div(numericInput(ns('extra5-bonus'),label = '',value = 0,width = '70px'),style="display:inline-block"),
actionButton(ns('extra5-button'),label = div(img(src = 'icons/dice-twenty-faces-twenty.png',
height = 20,
width = 20)), class = 'weaponButton',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
),
fluidRow(
div(textInput(ns('extra6-name'),label = '',width = '120px'),style="display:inline-block"),
div(numericInput(ns('extra6-bonus'),label = '',value = 0,width = '70px'),style="display:inline-block"),
actionButton(ns('extra6-button'),label = div(img(src = 'icons/dice-twenty-faces-twenty.png',
height = 20,
width = 20)), class = 'weaponButton',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
),
fluidRow(
div(textInput(ns('extra7-name'),label = '',width = '120px'),style="display:inline-block"),
div(numericInput(ns('extra7-bonus'),label = '',value = 0,width = '70px'),style="display:inline-block"),
actionButton(ns('extra7-button'),label = div(img(src = 'icons/dice-twenty-faces-twenty.png',
height = 20,
width = 20)), class = 'weaponButton',
onclick = glue('Shiny.onInputChange("',ns('d20button'),'", this.id)')),
)
)
)
)
)
}
pet = function(input,output,session, char){
reset = reactiveVal(value = FALSE)
out = reactiveVal(value = '')
pet = reactiveValues(currentHealth = 1,
currentTempHP = 0,
maxHealth = 1)
petInitial = reactiveValues(currentHealth = 1,
currentTempHP = 0,
maxHealth = 1)
actions = reactiveVal(value =
list(action1 = list(name = 'Attack',
attack_bonus = 0,
damage_dice = 0,
damage_bonus = 0),
action1 = list(name = 'Attack',
attack_bonus = 0,
damage_dice = 0,
damage_bonus = 0),
action2 = list(name = 'Attack',
attack_bonus = 0,
damage_dice = 0,
damage_bonus = 0),
action3 = list(name = 'Attack',
attack_bonus = 0,
damage_dice = 0,
damage_bonus = 0),
action4 = list(name = 'Attack',
attack_bonus = 0,
damage_dice = 0,
damage_bonus = 0),
action5 = list(name = 'Attack',
attack_bonus = 0,
damage_dice = 0,
damage_bonus = 0)))
observeEvent(char$classInfo,{
if(any(char$classInfo[,'Archetype'] == 'Battle Smith')){
updateSelectInput(session = session,
inputId = 'monster',
label = 'Load SRD monster',
choices = c('',names(monsters)),
selected = 'Steel Defender')
}
},priority = -9999)
observeEvent(input$monster,{
if(any(char$classInfo[,'Class']=='Artificer')){
level = char$classInfo[char$classInfo[,'Class'] == 'Artificer','Level'] %>% as.numeric()
proficiency = char$proficiencyBonus
monsters$`Homonculus Servant`$HP = unname(level+ 1 + char$abilityMods['Int'])
monsters$`Homonculus Servant`$saves['Dex'] = 2 + char$proficiencyBonus
monsters$`Homonculus Servant`$skills['Perception'] = 2*char$proficiencyBonus
monsters$`Homonculus Servant`$skills['Stealth'] = 2 + char$proficiencyBonus
monsters$`Homonculus Servant`$actions$`Force Strike`$attack_bonus = unname(char$abilityMods['Int']) + char$proficiencyBonus
monsters$`Homonculus Servant`$actions$`Force Strike`$damage_bonus = char$proficiencyBonus
}
if(any(char$classInfo[,'Archetype'] == 'Battle Smith')){
level = char$classInfo[char$classInfo[,'Archetype'] == 'Battle Smith','Level'] %>% as.numeric()
proficiency = char$proficiencyBonus
monsters$`Steel Defender`$HP = unname(level*5+2+char$abilityMods['Int'])
monsters$`Steel Defender`$actions$`Force-Empowered Rend`$attack_bonus = unname(char$abilityMods['Int']) + char$proficiencyBonus
monsters$`Steel Defender`$actions$`Force-Empowered Rend`$damage_bonus = char$proficiencyBonus
monsters$`Steel Defender`$skills['Perception'] = 2*char$proficiencyBonus
monsters$`Steel Defender`$skills['Athletics'] = 2 + char$proficiencyBonus
monsters$`Steel Defender`$saves['Dex'] = 1 + char$proficiencyBonus
monsters$`Steel Defender`$saves['Con'] = 2 + char$proficiencyBonus
monsters$`Steel Defender`$actions$Repair$damage_bonus = char$proficiencyBonus
if(level>=15){
monsters$`Steel Defender`$AC = 17
}
}
if(!is.null(input$monster) && input$monster!= ''){
monster = monsters[[input$monster]]
pet$currentHealth = monster$HP
pet$maxHealth = monster$HP
petInitial$currentHealth = monster$HP
petInitial$maxHealth = monster$HP
validActions = monsteR:::attackable(monster$actions)
updateNumericInput(session,'AC',value = monster$AC)
updateNumericInput(session,'str-mod',value = stat2mod(monster$abilityScores['Str']) %>% unname)
updateNumericInput(session,'dex-mod',value = stat2mod(monster$abilityScores['Dex']) %>% unname)
updateNumericInput(session,'con-mod',value = stat2mod(monster$abilityScores['Con']) %>% unname)
updateNumericInput(session,'wis-mod',value = stat2mod(monster$abilityScores['Wis']) %>% unname)
updateNumericInput(session,'int-mod',value = stat2mod(monster$abilityScores['Int']) %>% unname)
updateNumericInput(session,'cha-mod',value = stat2mod(monster$abilityScores['Cha']) %>% unname)
specialSaves = (monster$saves - stat2mod(monster$abilityScores)) %>% {.[!. %in% 0]} %>% names %>% {monster$saves[.]}
specialSkills = (monster$skills - stat2mod(monster$abilityScores)[skillAttributes]) %>% {.[!. %in% 0]} %>% names %>% {monster$skills[.]}
extraPosition = 1
while(extraPosition<=7){
for(i in seq_along(specialSaves)){
updateTextInput(session,inputId = glue::glue('extra{extraPosition}-name'),
value = glue::glue('{names(specialSaves)[i]} save'))
updateNumericInput(session,glue::glue('extra{extraPosition}-bonus'),
value = unname(specialSaves)[i])
extraPosition = extraPosition + 1
}
for(i in seq_along(specialSkills)){
updateTextInput(session,inputId = glue::glue('extra{extraPosition}-name'),
value = glue::glue('{names(specialSkills)[i]}'))
updateNumericInput(session,glue::glue('extra{extraPosition}-bonus'),
value = unname(specialSkills)[i])
extraPosition = extraPosition + 1
}
if(extraPosition<=7){
for(i in seq(extraPosition, 7,by = 1)){
updateTextInput(session,inputId = glue::glue('extra{extraPosition}-name'),
value = '')
updateNumericInput(session,glue::glue('extra{extraPosition}-bonus'),
value = 0)
extraPosition = extraPosition+1
}
}
}
for(i in seq(1,5)){
if(!is.na(validActions[i])){
action = monster$actions[[validActions[i]]]
if(is.null(action$damage_bonus)){
action$damage_bonus = 0
}
updateTextInput(session,inputId = glue::glue('action{i}-name'), value = action$name)
updateNumericInput(session,inputId = glue::glue('action{i}-attack'), value = action$attack_bonus)
updateTextInput(session,
inputId = glue::glue('action{i}-damage'),
value = glue::glue("{action$damage_dice} {ifelse(action$damage_bonus >0, '+','')} {ifelse(action$damage_bonus != 0, action$damage_bonus, '')}"))
} else{
updateTextInput(session,inputId = glue::glue('action{i}-name'), value = '')
updateNumericInput(session,inputId = glue::glue('action{i}-attack'), value = 0)
updateTextInput(session,inputId = glue::glue('action{i}-damage'), value = '')
}
}
}
})
callModule(health,id = 'petHealth',
char = pet,
charInitial = petInitial)
observeEvent(input$`action1-button`,{
attackData = list(name = input$`action1-name`,
attack = input$`action1-attack`,
damage = input$`action1-damage`)
attack = processHit(attackData, input$advantage)
out(attack)
reset(TRUE)
})
observeEvent(input$d20button,{
print(input$d20button)
if(!is.null(input$d20button)){
roll = switch(input$advantage,
Norm = roll('1d20'),
Adv = roll('2d20d1'),
DisAdv = roll('2d20dh1')
)
baseButton = strsplit(input$d20button,'-') %>% {.[[1]]} %>% {.[length(.)]}
if(baseButton %in% c('str','dex','con','wis','int','cha')){
mod = input[[paste0(baseButton,'-mod')]]
rollName = baseButton %>% str_to_title()
} else{
whichExtra = str_extract(input$d20button,'(?<=extra)[0-9](?=\\-button$)') %>% as.integer()
mod = input[[glue::glue('extra{whichExtra}-bonus')]]
rollName = input[[glue::glue('extra{whichExtra}-name')]]
}
if(input$advantage !='Norm'){
text = glue::glue('Pet\'s {rollName} {input$advantage}:\n{roll+mod}')
} else{
text = glue::glue('Pet\'s {rollName}:\n{roll+mod}')
}
out(text)
reset(TRUE)
}
session$sendCustomMessage(type = 'resetInputValue',
message = session$ns('d20button'))
})
observeEvent(input$`action1-button`,{
attackData = list(name = input$`action1-name`,
attack = input$`action1-attack`,
damage = input$`action1-damage`)
attack = processHit(attackData, input$advantage)
out(attack)
reset(TRUE)
})
observeEvent(input$`action2-button`,{
attackData = list(name = input$`action2-name`,
attack = input$`action2-attack`,
damage = input$`action2-damage`)
attack = processHit(attackData, input$advantage)
out(attack)
reset(TRUE)
})
observeEvent(input$`action3-button`,{
attackData = list(name = input$`action3-name`,
attack = input$`action3-attack`,
damage = input$`action3-damage`)
attack = processHit(attackData, input$advantage)
out(attack)
reset(TRUE)
})
observeEvent(input$`action4-button`,{
attackData = list(name = input$`action4-name`,
attack = input$`action4-attack`,
damage = input$`action4-damage`)
attack = processHit(attackData, input$advantage)
out(attack)
reset(TRUE)
})
observeEvent(input$`action5-button`,{
attackData = list(name = input$`action5-name`,
attack = input$`action5-attack`,
damage = input$`action5-damage`)
attack = processHit(attackData, input$advantage)
out(attack)
reset(TRUE)
})
observe({
if(reset()){
out('')
reset(FALSE)
}
})
return(out)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.