Attacks can now be rolled with advantage from the stats tab

TODO the action viewer as well still
This commit is contained in:
Stefan Zermatten
2022-02-25 13:44:09 +02:00
parent f79a6d98ec
commit 59c69a46a8
6 changed files with 159 additions and 67 deletions

View File

@@ -24,16 +24,18 @@ export default function applyAction(node, {creature, targets, scope, log}){
const failed = spendResources({prop, log, scope});
if (failed) return;
const attack = prop.attackRoll || prop.attackRollBonus;
// Attack if there is an attack roll
if (prop.attackRoll && prop.attackRoll.calculation){
if (attack && attack.calculation){
if (targets.length){
targets.forEach(target => {
applyAttackToTarget({prop, target, scope, log});
applyAttackToTarget({attack, target, scope, log});
// Apply the children, but only to the current target
applyChildren(node, {targets: [target], scope, log});
});
} else {
applyAttackWithoutTarget({prop, scope, log});
applyAttackWithoutTarget({attack, scope, log});
applyChildren(node, {creature, targets, scope, log});
}
} else {
@@ -41,44 +43,34 @@ export default function applyAction(node, {creature, targets, scope, log}){
}
}
function applyAttackWithoutTarget({prop, scope, log}){
function applyAttackWithoutTarget({attack, scope, log}){
delete scope['$attackHit'];
delete scope['$attackMiss'];
delete scope['$criticalHit'];
delete scope['$criticalMiss'];
delete scope['$attackRoll'];
recalculateCalculation(prop.attackRoll, scope, log);
recalculateCalculation(attack, scope, log);
let value = rollDice(1, 20)[0];
scope['$attackRoll'] = {value};
let criticalHitTarget = scope.criticalHitTarget?.value || 20;
let criticalHit = value >= criticalHitTarget;
if (criticalHit){
scope['$criticalHit'] = {value: true};
scope['$attackHit'] = {value: true};
} else {
let criticalMiss = value === 1;
if (criticalMiss){
scope['$criticalMiss'] = 1;
log.content.push({
name: 'Critical Miss!',
});
scope['$attackMiss'] = {value: true};
} else {
// Untargeted attacks hit by default
scope['$attackHit'] = {value: true}
}
let {
resultPrefix,
result,
criticalHit,
criticalMiss,
} = rollAttack(attack, scope);
let name = criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit';
if (attack.advantage === 1 || scope['$attackAdvantage']){
name += ' (Advantage)';
} else if(attack.advantage === -1 || scope['$attackDisadvantage']){
name += ' (Disadvantage)';
}
let result = value + prop.attackRoll.value;
scope['$attackRoll'] = {value: result};
log.content.push({
name: criticalHit ? 'Critical Hit!' : 'To Hit',
value: `1d20 [${value}] + ${prop.attackRoll.value} = ` + result,
name,
value: `${resultPrefix} **${result}**`,
});
}
function applyAttackToTarget({prop, target, scope, log}){
function applyAttackToTarget({attack, target, scope, log}){
delete scope['$attackHit'];
delete scope['$attackMiss'];
delete scope['$criticalHit'];
@@ -86,26 +78,30 @@ function applyAttackToTarget({prop, target, scope, log}){
delete scope['$attackDiceRoll'];
delete scope['$attackRoll'];
recalculateCalculation(prop.attackRoll, scope, log);
recalculateCalculation(attack, scope, log);
let {
resultPrefix,
result,
criticalHit,
criticalMiss,
} = rollAttack(attack, scope);
const value = rollDice(1, 20)[0];
scope['$attackDiceRoll'] = {value};
const criticalHitTarget = scope.criticalHitTarget?.value || 20;
const criticalHit = value >= criticalHitTarget;
const criticalMiss = value === 1;
if (criticalHit) scope['$criticalHit'] = {value: true};
if (criticalMiss) scope['$criticalMiss'] = {value: true};
const result = value + prop.attackRoll.value;
scope['$attackRoll'] = {value: result};
if (target.variables.armor){
const armor = target.variables.armor.value;
const name = criticalHit ? 'Critical Hit!' :
criticalMiss ? 'Critical miss!' :
result > armor ? 'Hit!' :
'Miss!'
let name = criticalHit ? 'Critical Hit!' :
criticalMiss ? 'Critical Miss!' :
result > armor ? 'Hit!' : 'Miss!';
if (attack.advantage === 1 || scope['$attackAdvantage']){
name += ' (Advantage)';
} else if(attack.advantage === -1 || scope['$attackDisadvantage']){
name += ' (Disadvantage)';
}
log.content.push({
name,
value: `1d20 {${value}} + ${prop.attackRoll.value} = ` + result,
value: `${resultPrefix} **${result}**`,
});
if ((result > armor) || (criticalHit)){
scope['$attackHit'] = true;
@@ -118,12 +114,62 @@ function applyAttackToTarget({prop, target, scope, log}){
value:'Target has no `armor`',
});
log.content.push({
name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical miss!' : 'To Hit',
value: `1d20 {${value}} + ${prop.attackRoll.value} = ` + result,
name: criticalHit ? 'Critical Hit!' : criticalMiss ? 'Critical Miss!' : 'To Hit',
value: `${resultPrefix} **${result}**`,
});
}
}
function rollAttack(attack, scope){
let value, resultPrefix;
if (attack.advantage === 1 || scope['$attackAdvantage']){
const [a, b] = rollDice(2, 20);
if (a >= b) {
value = a;
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] + ${attack.value} = `;
} else {
value = b;
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] + ${attack.value} = `;
}
} else if (attack.advantage === -1 || scope['$attackDisadvantage']){
const [a, b] = rollDice(2, 20);
if (a <= b) {
value = a;
resultPrefix = `1d20 [ ${a}, ~~${b}~~ ] + ${attack.value} = `;
} else {
value = b;
resultPrefix = `1d20 [ ~~${a}~~, ${b} ] + ${attack.value} = `;
}
} else {
value = rollDice(1, 20)[0];
resultPrefix = `1d20 [${value}] + ${attack.value} = `
}
scope['$attackRoll'] = {value};
const result = value + attack.value;
const {criticalHit, criticalMiss} = applyCrits(value, scope);
return {resultPrefix, result, value, criticalHit, criticalMiss};
}
function applyCrits(value, scope){
let criticalHitTarget = scope.criticalHitTarget?.value || 20;
let criticalHit = value >= criticalHitTarget;
let criticalMiss;
if (criticalHit){
scope['$criticalHit'] = {value: true};
scope['$attackHit'] = {value: true};
} else {
criticalMiss = value === 1;
if (criticalMiss){
scope['$criticalMiss'] = 1;
scope['$attackMiss'] = {value: true};
} else {
// Untargeted attacks hit by default
scope['$attackHit'] = {value: true}
}
}
return {criticalHit, criticalMiss};
}
function applyChildren(node, args){
node.children.forEach(child => applyProperty(child, args));
}

View File

@@ -48,17 +48,27 @@ export default function applySavingThrow(node, {creature, targets, scope, log}){
let value, values, resultPrefix;
if (save.advantage === 1){
values = rollDice(2, 20).sort().reverse();
value = values[0];
resultPrefix = `Advantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
const [a, b] = rollDice(2, 20);
if (a >= b) {
value = a;
resultPrefix = `Advantage: 1d20 [ ${a}, ~~${b}~~ ] + ${save.value} = `;
} else {
value = b;
resultPrefix = `Advantage: 1d20 [ ~~${a}~~, ${b} ] + ${save.value} = `;
}
} else if (save.advantage === -1){
values = rollDice(2, 20).sort();
value = values[0];
resultPrefix = `Disadvantage: 1d20 [${values[0]},~~${values[1]}~~] + ${save.value} = `
const [a, b] = rollDice(2, 20);
if (a <= b) {
value = a;
resultPrefix = `Advantage: 1d20 [ ${a}, ~~${b}~~ ] + ${save.value} = `;
} else {
value = b;
resultPrefix = `Advantage: 1d20 [ ~~${a}~~, ${b} ] + ${save.value} = `;
}
} else {
values = rollDice(1, 20);
value = values[0];
resultPrefix = `1d20 [${value}] + ${save.value} = `
resultPrefix = `1d20 [ ${value} ] + ${save.value} = `
}
scope['$saveDiceRoll'] = {value};
const result = value + save.value || 0;

View File

@@ -24,13 +24,17 @@ const doAction = new ValidatedMethod({
type: String,
regEx: SimpleSchema.RegEx.Id,
},
scope: {
type: Object,
blackbox: true,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 10,
timeInterval: 5000,
},
run({actionId, targetIds = []}) {
run({actionId, targetIds = [], scope}) {
let action = CreatureProperties.findOne(actionId);
// Check permissions
let creature = getRootCreatureAncestor(action);
@@ -69,7 +73,7 @@ const doAction = new ValidatedMethod({
});
// Do the action
doActionWork({creature, targets, properties, ancestors, method: this});
doActionWork({creature, targets, properties, ancestors, method: this, methodScope: scope});
// Recompute all involved creatures
computeCreature(creature._id);
@@ -82,7 +86,7 @@ const doAction = new ValidatedMethod({
export default doAction;
export function doActionWork({
creature, targets, properties, ancestors, method
creature, targets, properties, ancestors, method, methodScope = {}
}){
// get the docs
const ancestorScope = getAncestorScope(ancestors);
@@ -102,6 +106,7 @@ export function doActionWork({
const scope = {
...creature.variables,
...ancestorScope,
...methodScope
}
applyProperty(propertyForest[0], {
creature,