To better illustrate what I meant in above posts, I wrote some pseudo code below. But keyword there is "pseudo" since I'm not an expert on DnD 5e rules or BG3 homebrew variant, so this is just simplified example that should still show how there could be little change in existing code.
Lets assume that current simplified version of BG3 attack code looks like this:
// is it crit, hit or miss - with chance to hit used to display hover over target
(isCrit, isHit, hitProb) attackRoll(...) {
/* big chunk of code that consider player attributes, proficiencies, feats, position ... to determine:
/* Bonus on attack roll ( + from proficiencies, - from feats like GWM )
/* number of Advantages/Disadvantages
/* enemy target AC
Target = AC-Bonus
hitProb= 21/20 - Max(2,Min(20,Target))/20 // critical hits/misses and huge AC targets fix, so pMin=1/20, pMax=19/20
d= random(20)
if (Advantages>0) and (Disadvantages==0) {
d= max( d, random(20) ) // if advantage, use better of two rolls
hitProb = 1- (1-hitProb)^2 // hit probability is opposite of miss, and we miss only if we miss both rolls in advantage
}
if (Advantages==0) and (Disadvantages>0) {
d= min( d, random(20) ) // if disadvantage, use worse of two rolls
hitProb = hitProb^2 // we hit only if both rolls would hit in disadvantage
}
if d==20 return (isCrit=true, isHit=true, hitProb) // critical hit
if d==1 return (isCrit=true, isHit=false, hitProb) // critical miss
isHit= d >= Target
return (isCrit=false, isHit, hitProb )
}
// amount of damage
(damage) damageRoll(isCrit, ...) {
/* chunk of code to determine
/* D=12 or 6 or 4 ... how many sided dice this weapon uses
/* Rolls= 1 or 2 ... is this single roll like 1d12, 1d10, 1d4 or double like 2d6
/* Bonus = weapon enchants, player proficiencies and ability modifiers, GWM +10 etc...
damage= Bonus
for i=1..Rolls {
d= random(D)
if GWF and (d<=2) then d=random(D) // repeat roll in case of GWF
damage = damage+d
}
if isCrit then damage= 2*damage
return ( damage )
}
// overall attack
(isCrit, isHit, Damage) Attack(...) {
isCrit, isHit, hitProb = attackRoll(...)
if (isHit){
Damage= damageRoll( isCrit, hitProb, ...)
if isCrit print "CRITICAL HIT for " else print "damage for "
print Damage
} else {
Damage=0
if isCrit print "CRITICAL MISS" else print "MISS"
}
return (isCrit, isHit, Damage)
}
Above example uses common practice with two roll games, where first roll determine hit/miss/crit chance and second roll determine damage value. Function "attackRoll" has dual usage here, since BG3 uses something like that both to calculate and display hit chance when hovering over enemy target ( in which case BG3 would use 'hitProb' return value), and when player actually attack ( when BG3 uses isCrit and isHit value ).
Now, only change needed to implement my suggested "reducedRandomOption" is in "damageRoll" function, and other two functions can remain EXACTLY same in my example ( obviously, in real BG3 code they may still need small changes, but this is just an example):
// amount of damage
(damage) damageRoll(hitProb, isCrit, ...) {
/* chunk of code to determine
/* D=12 or 6 or 4 ... how many sided dice this weapon uses
/* Rolls= 1 or 2 ... is this single roll like 1d12, 1d10, 1d4 or double like 2d6
/* Bonus = weapon enchants, player proficiencies and ability modifiers, GWM +10 etc...
if (isCrit or reducedRandomOption ==OFF or EnemyNPC ){ // old calculation
damage= Bonus
for i=1..Rolls {
d= random(D)
if GWF and (d<=2) then d=random(D) // repeat roll in case of GWF
damage = damage+d
}
if isCrit then damage= 2*damage
return damage
}
// reducedRandomOption==ON calculation , and no crit
damage= (1+D)/2
if GWF {
p1= 4/D^2 // probability to get 1 or 2
damage= p1*1.5 + (1-p1)*(3+D)/2
}
return ROUND( ( Rolls*damage + Bonus ) * hitProb )
}
Even here, majority of code lines remains the same ( keep in mind that "chunk of code to determine" part is largest part there, with dozens of lines of code not shown here needed to determine specific player attributes or game situation , and that part also remains unchanged ). Old random range damage calculation is still same and used if reducedRandomOption is disabled (default) or even if enabled but player got critical hit (or if same code is used for NPC damage calculation).
Actual change is new average damage calculation, represented by last 5 lines of code. It returns rounded value, where that round function can either do simple round or that probabilistic round I explained before. Keep in mind that I wrote this in notepad, without full knowledge about BG3 DnD 5e rules and with great deal of guessing and simplification about what current BG3 code looks like, so this is "pseudo" code both as in "no specific programming language used" and as in "not actual current BG3 code used". It should still illustrate several points that I made before, like:
- actual code change could/should be relatively small
- critical damage and enemy/NPC damage remains unchanged