Criando um rolo de impressão de 5ª edição em AnyDice: como lidar com dano adicional no primeiro golpe?

4

Eu tenho tentado criar um gerador de probabilidade completo para as jogadas de ataque D & D de quinta edição no AnyDice, incluindo recursos como vantagem / desvantagem, diferentes faixas de crit, bônus de dano crítico etc. Eu me deparei com um problema com habilidades que só causam dano adicional uma vez por rodada, como o Ataque Furtivo.

Pseudocódigo:

lots of variables
parse variables into dice \advantage=1 turns the attack roll into [email protected] etc\

function: attack ROLL:n {
 if crit fail {return:0}
 if crit hit {return: damage +crit damage}
 if normal hit {return: damage}
 if normal miss {return: 0}
}

function: multiattack NUMBER_OF_ATTACKS:n {
 TOTAL:0
 loop N over {1..NUMBER_OF_ATTACKS} {
  TOTAL:TOTAL+[attack ROLL]
 }
}

output [result of multiattack function]

O que eu quero fazer é adicionar bônus de dano ao primeiro hit, se houver algum. Como as funções AnyDice não afetam variáveis externas e terminam imediatamente em "resultado" e funções booleanas só funcionam com números e não conjuntos ou dados, eu não encontrei uma boa maneira de fazer uma variável capaz de dizer com precisão se um acerto já caiu .

Se eu definir um valor dentro da função de ataque, ele será redefinido para esse valor na próxima rodada do loop. As variáveis dentro de uma função não mudam para fora, então chamar a variável novamente não fará diferença (a menos que eu tenha perdido alguma coisa) e não encontrei uma maneira de fazer isso como parte da função auxiliar. Qualquer conselho seria muito apreciado.

    
por Daniel 15.01.2018 / 03:10

1 resposta

O que você precisa fazer é escrever uma função para "congelar" suas jogadas de dados e chamá-la recursivamente para cada ataque (sem sucesso), algo como este exemplo básico :

ATK:  1d20  \ attack roll: use [email protected] for advantage or [email protected] for disadvantage \
HIT:    +0  \ bonus to attack roll \
AC:      8  \ target AC: attack hits if ATK + HIT >= AC \
CRIT:   20  \ minimum attack roll to crit \
DMG:   1d4  \ damage rolled on successful hit \
BONUS: 1d6  \ bonus damage on first hit \

function: attack ROLL:n {
  \ return the number of times damage is applied on an attack roll of ROLL \
  if ROLL <= 1 { result: 0 }     \ natural 1 is always a miss \
  if ROLL >= CRIT { result: 2 }  \ critical hit deals damage damage twice \
  result: ROLL + HIT >= AC
}

function: multiattack N:n times {
  result: [roll [attack ATK] to multiattack N times]
}
function: roll FIRSTHIT:n to multiattack N:n times {
  if N <= 0 {
    result: 0
  }
  if FIRSTHIT > 0 {
    DAMAGE: FIRSTHIT d (DMG + BONUS)
    loop I over {2..N} {
      DAMAGE: DAMAGE + [attack ATK] d DMG
    }
    result: DAMAGE
  } else {
    result: [roll [attack ATK] to multiattack N-1 times]
  }
}

output [multiattack 3 times]

A função principal [multiattack N:n times] apenas chama a função auxiliar [roll FIRSTHIT:n to multiattack N:n times] . O primeiro argumento ( FIRSTHIT ) para a função auxiliar é dado por [attack ATK] , que retorna o número de vezes que o dano deve ser aplicado para este teste de ataque (ou seja, 0 por erro, 1 por impacto e 2 por crit). / p>

Como esse argumento está marcado como numérico (com :n ) na definição da função, AnyDice chama internamente a função auxiliar com cada valor de retorno possível de [attack ATK] e pondera seus resultados de acordo com as probabilidades dos resultados. Isto significa que, embora o resultado de [attack ATK] seja uma matriz (enviesada), dentro da função auxiliar seu valor é "congelado" como FIRSTHIT , que é um número fixo e, portanto, pode ser usado, e. em if condicionais. (Veja a seção "Funções" da documentação do AnyDice para mais detalhes).

A função auxiliar em si só verifica se o ataque é atingido (ou seja, se FIRSTHIT > 0 ) e, em caso afirmativo, aplica o dano desse ataque (mais o bônus, se houver) e de quaisquer ataques restantes (sem o bônus). Por outro lado, se o ataque errar, a função auxiliar chama-se recursivamente com uma nova jogada de ataque e com o número de ataques restantes reduzido em um.

A expressão FIRSTHIT d (DMG + BONUS) pode valer a pena discutir: ela simplesmente rola o dano normal e de bônus FIRSTHIT vezes e soma os resultados. (Por outro lado, FIRSTHIT * (DMG + BONUS) renderia o resultado de rolar o dano normal e bônus uma vez e multiplicá-lo por FIRSTHIT .) Assim, se FIRSTHIT for igual a 2, o dano (incluindo qualquer ataque furtivo ou outros bônus) ) será lançado duas vezes, como as regras 5e especificam para ocorrências críticas . Da mesma forma, dentro do loop, a expressão [attack ATK] d DMG fornece o resultado de rolar DMG ou zero, uma ou duas vezes, de acordo com as probabilidades dadas pelo [attack ATK] roll.

Na verdade, todo o código dentro do bloco condicional if FIRSTHIT > 0 pode ser substituído pela seguinte expressão:

result: FIRSTHIT d (DMG + BONUS) + (N-1) d ([attack ATK] d DMG)

Aqui, (N-1) d ([attack ATK] d DMG) dá o resultado da execução de N-1 de jogadas de ataque, onde cada ataque lida com DMG damage [attack ATK] times.

Além disso, vale a pena observar que AnyDice normalmente permite que as chamadas de função sejam aninhadas em apenas 10 níveis de profundidade (uma delas é usada pela função wrapper neste exemplo e uma pela chamada [attack ATK] ). Se você gostaria de apoiar multiattacks com mais de 8 jogadas de ataque sucessivas, você deve aumentar o limite de recursividade, por exemplo. assim:

set "maximum function depth" to 999

No entanto, para fazer uma recursão profunda em um tempo razoável, é essencial certificar-se de que a função auxiliar se chame apenas em um valor específico de sua parâmetros fixos (por exemplo, neste caso, quando FIRSTHIT é zero). Se puder recorrer a duas ou mais entradas possíveis, o número de diferentes sequências possíveis de chamadas recursivas que o AnyDice precisa verificar aumentará exponencialmente com a profundidade de recursão.

Esta é a razão pela qual o código acima usa o resultado de [attack ATK] (que pode ser apenas 0, 1 ou 2) como o parâmetro para a função auxiliar, em vez de passar a rolagem de ataque ATK (que pode ser qualquer coisa entre 1 e 20) diretamente como parâmetro. Embora o fazer desse jeito funcione bem para um pequeno número de ataques, ele fica muito lento muito rapidamente à medida que a profundidade da recursão é aumentada.

Na verdade, poderíamos otimizar o código um pouco mais pré-calculando os resultados de [attack ATK] e atribuindo-os a um global morrer personalizado. Isso aceleraria ligeiramente o código e também salvaria um nível de aninhamento de função.

Note também que o código acima assume que o dano adicional será sempre aplicado no primeiro sucesso, independentemente de ser um crítico ou não. Isto é provavelmente ótimo na maioria das situações práticas, mas em princípio, um jogador com um número suficientemente grande de ataques restantes e um acerto e / ou mudança crítica suficientes podem ser melhor salvando seu Ataque Furtivo para depois se eles acertarem um acerto inicial não crítico, no pressuposto de que eles provavelmente irão lançar um crit (ou pelo menos outro acerto normal) mais tarde. A modelagem de tais estratégias avançadas é possível em AnyDice (geralmente também por meio de recursão), mas mais complicada.

    
15.01.2018 / 15:58