mirror of
https://github.com/RussellChamp/armsman.git
synced 2026-10-27 22:42:30 +00:00
608 lines
28 KiB
C++
608 lines
28 KiB
C++
#include "mainwindow.h"
|
|
#include "ui_mainwindow.h"
|
|
#include "qmath.h"
|
|
|
|
MainWindow::MainWindow(QWidget *parent) :
|
|
QMainWindow(parent),
|
|
ui(new Ui::MainWindow)
|
|
{
|
|
ui->setupUi(this);
|
|
MainWindow::calculate_stuff();
|
|
ui->warnings->setText("Armsman has been preloaded with an example character. Change the weapon, feats, and stats then select an attack below and click 'Calculate Damage.' Please send any comments or bug reports to RussellChamp@gmail.com");
|
|
|
|
//Start several elements off invisible
|
|
ui->attack_great_cleave_targets_label->setVisible(false);
|
|
ui->attack_great_cleave_targets->setVisible(false);
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
delete ui;
|
|
}
|
|
|
|
int get_hitchance(int hit_bonus, int target_ac)
|
|
{
|
|
//(20 + 1 + hit_bonus - target_ac) * 100 / 20
|
|
//+1 since ties HIT
|
|
//min 5% (crit hit), max 95% (crit miss)
|
|
int hit_percent = qBound(5, (21 + hit_bonus - target_ac)*5, 95);
|
|
|
|
return hit_percent;
|
|
}
|
|
|
|
int get_critchance(int hit_bonus, int crit_confirm_bonus,int crit_min, int target_ac, bool total_chance = false)
|
|
{
|
|
//when total_chance is set, it will return the average crit chance for *any* attack and not just successful attacks
|
|
//successful hit rolls = the number of sides on a d20 that would result in a successful hit
|
|
//successful hit rolls = 21 + hit_bonus - target_ac (max 19 (crit miss), min 1 (crit hit))
|
|
int successful_hits = qBound(1, 21 + hit_bonus - target_ac, 19);
|
|
//out of those, (21 - crit_min) will be crits (max successful hits, min should be 1)
|
|
int crits = qBound(1, 21 - crit_min, successful_hits);
|
|
//so the chance will be
|
|
int crit_chance = crits * 100 / successful_hits; //of all successful hits
|
|
|
|
//eg. hit chance: (21 + 10 hit bonus - 21 ac) = 10 successful hits
|
|
//eg. crit chance 19-20/x2 : 21 ac - 19 crit min = 2 crit hits
|
|
//2 crit hits out of 10 successful hits = 20% of all successful hits are crits
|
|
|
|
// crit confirm: get_hitchance(hit_bonus+crit_confirm_bonus, target_ac) to confirm crit
|
|
//eg. (21 + 10 hit bonus + 4 crit confirm bonus - 21 ac) * 100 / 20 = 70% crit confirm
|
|
//crit_chance * crit_confirm
|
|
//20% * 70% = 14% of all successful hits are confirmed crits
|
|
|
|
if(total_chance)
|
|
return crit_chance * get_hitchance(hit_bonus + crit_confirm_bonus, target_ac) * get_hitchance(hit_bonus, target_ac) / 10000;
|
|
else
|
|
return crit_chance * get_hitchance(hit_bonus + crit_confirm_bonus, target_ac) / 100;
|
|
}
|
|
|
|
float MainWindow::get_damage(bool crit = false)
|
|
{
|
|
float damage = 0;
|
|
|
|
//Damage multiplied by critical hits
|
|
if(ui->feat_arcanestrike->checkState())
|
|
damage += ui->feat_arcanestrike_bonus->value();
|
|
if(ui->feat_weaponspecialization->checkState())
|
|
damage += 2;
|
|
if(ui->feat_greaterweaponspecialization->checkState())
|
|
damage += 2;
|
|
if(ui->feat_weapontraining->checkState())
|
|
damage += ui->feat_weapontraining_bonus->value();
|
|
if(ui->weapon_melee->isChecked())
|
|
{
|
|
if(ui->weapon_melee_twohanded->checkState())
|
|
damage += qFloor(1.5 * ui->stat_str_mod->value());
|
|
else
|
|
damage += ui->stat_str_mod->value();
|
|
if(ui->attack_type->currentText() == "Power Attack")
|
|
{
|
|
if(ui->weapon_melee_twohanded->checkState())
|
|
damage += qFloor((4 + ui->attack_1_bonus->value())/4)*3;
|
|
else
|
|
damage += qFloor((4 + ui->attack_1_bonus->value())/4)*2;
|
|
}
|
|
}
|
|
else if(ui->weapon_ranged->isChecked() && ui->weapon_ranged_composite->isChecked())
|
|
damage += qMin(ui->weapon_ranged_composite_bonus->value(), ui->stat_str_mod->value());
|
|
|
|
damage += ui->weapon_bonus->value();
|
|
damage += ui->misc_bonus_damage_crit->value();
|
|
|
|
//average damage for the weapon's die roll will be the number of dies rolled
|
|
//times the average damage per die, which can be simplified to the number of
|
|
//die sides + 1 then divided by 2
|
|
damage += ui->weapon_die_count->value() * (ui->weapon_die_value->currentText().toInt() + 1)/2.0;
|
|
|
|
if(crit)
|
|
{
|
|
//multiply for crit damage
|
|
damage *= ui->crit_multiplier->value();
|
|
|
|
//special damage only during crits
|
|
if(ui->weapon_flamingburst->checkState())
|
|
damage += qBound(1, ui->crit_multiplier->value() - 1, 3) * 5.5; //(crit multiplier - 1)d10
|
|
if(ui->weapon_thundering->checkState())
|
|
damage += qBound(1, ui->crit_multiplier->value() - 1, 3) * 4.5; //(crit multiplier - 1)d8
|
|
}
|
|
|
|
//Damage that is NOT multiplied during a crit hit.
|
|
if(ui->weapon_flaming->checkState())
|
|
damage += 3.5; //1d6
|
|
if(ui->weapon_holy->checkState())
|
|
damage += 7; //2d6
|
|
if(ui->weapon_vicious->checkState())
|
|
damage += 7; //2d6
|
|
if(ui->weapon_anarchic->checkState())
|
|
damage += 7; //2d6
|
|
if(ui->weapon_wounding->checkState())
|
|
damage += 1; //1 point bleed
|
|
if(ui->feat_sneakattack->isChecked())
|
|
damage += ui->feat_sneak_attack_bonus->value() * 3.5; //sneak bonus d6
|
|
damage += ui->misc_bonus_damage_noncrit->value();
|
|
|
|
return damage;
|
|
}
|
|
|
|
void MainWindow::on_pushButton_calculate_clicked()
|
|
{
|
|
calculate_stuff();
|
|
}
|
|
|
|
void MainWindow::calculate_stuff()
|
|
{
|
|
get_warnings();
|
|
|
|
QString results = "";
|
|
QString attack_type = ui->attack_type->currentText();
|
|
QString weapon = "";
|
|
QString weapon_damage = "";
|
|
QString attacks = ""; //bonuses on each attack
|
|
QString hit_chance = ""; //the chance each attack will hit
|
|
QString crit_chance = ""; //the chance that a successful hit will be a crit
|
|
QString crit_chance_all = ""; //the chance that *any* attack will be a crit
|
|
int enemy_ac = 0;
|
|
int attacks_num = 0; //not really used. will probably phase out
|
|
int hit_bonus = 0; //the total to-hit bonus applied to all attacks
|
|
|
|
if(ui->weapon_masterwork->checkState())
|
|
weapon.append("Masterwork ");
|
|
if(ui->weapon_bonus->value() > 0)
|
|
weapon.append("+" + QString::number(ui->weapon_bonus->value()) + " ");
|
|
weapon.append(ui->weapon_name->text());
|
|
|
|
weapon_damage.append(" (");
|
|
weapon_damage.append(QString::number(ui->weapon_die_count->value()) + "d" + ui->weapon_die_value->currentText() + " ");
|
|
|
|
int weapon_damage_bonus = 0;
|
|
if(ui->weapon_melee->isChecked())
|
|
{
|
|
if(ui->weapon_melee_twohanded->checkState())
|
|
weapon_damage_bonus += qFloor(1.5 * ui->stat_str_mod->value());
|
|
else
|
|
weapon_damage_bonus += ui->stat_str_mod->value();
|
|
if(ui->attack_type->currentText() == "Power Attack")
|
|
{
|
|
if(ui->weapon_melee_twohanded->checkState())
|
|
weapon_damage_bonus += qFloor((4 + ui->attack_1_bonus->value())/4)*3;
|
|
else
|
|
weapon_damage_bonus += qFloor((4 + ui->attack_1_bonus->value())/4)*2;
|
|
hit_bonus -= qFloor((4 + ui->attack_1_bonus->value())/4);
|
|
}
|
|
}
|
|
else if(ui->weapon_ranged->isChecked() && ui->weapon_ranged_composite->isChecked())
|
|
{
|
|
if(ui->stat_str_mod->value() < ui->weapon_ranged_composite_bonus->value())
|
|
hit_bonus -= 2;
|
|
weapon_damage_bonus += qMin(ui->weapon_ranged_composite_bonus->value(), ui->stat_str_mod->value());
|
|
}
|
|
|
|
weapon_damage_bonus += ui->weapon_bonus->value();
|
|
if(ui->feat_weaponspecialization->checkState())
|
|
weapon_damage_bonus += 2;
|
|
if(ui->feat_greaterweaponspecialization->checkState())
|
|
weapon_damage_bonus += 2;
|
|
if(ui->feat_weapontraining->checkState())
|
|
{
|
|
weapon_damage_bonus += ui->feat_weapontraining_bonus->value();
|
|
hit_bonus += ui->feat_weapontraining_bonus->value();
|
|
}
|
|
if(ui->feat_arcanestrike->checkState())
|
|
weapon_damage_bonus += ui->feat_arcanestrike_bonus->value();
|
|
|
|
weapon_damage_bonus += ui->misc_bonus_damage_crit->value() + ui->misc_bonus_damage_noncrit->value();
|
|
weapon_damage.append("+ " + QString::number(weapon_damage_bonus));
|
|
|
|
if(ui->weapon_flaming->checkState())
|
|
weapon_damage.append(" + 1d6 (Flaming)");
|
|
if(ui->weapon_flamingburst->checkState())
|
|
weapon_damage.append(" + " + QString::number(qBound(1, ui->crit_multiplier->value() - 1, 3)) + "d10 on crit (Burst)");
|
|
if(ui->weapon_thundering->checkState())
|
|
weapon_damage.append(" + " + QString::number(qBound(1, ui->crit_multiplier->value() - 1, 3)) + "d8 on crit (Thundering)");
|
|
if(ui->weapon_holy->checkState())
|
|
weapon_damage.append(" + 2d6 (Un/Holy)");
|
|
if(ui->weapon_anarchic->checkState())
|
|
weapon_damage.append(" + 2d6 (Anarchic/Axiomatic)");
|
|
if(ui->weapon_vicious->checkState())
|
|
weapon_damage.append(" + 2d6 (Vicious)");
|
|
if(ui->feat_sneakattack->isChecked())
|
|
weapon_damage.append(" + " + QString::number(ui->feat_sneak_attack_bonus->value()) + "d6 (Sneak Attack)");
|
|
if(ui->weapon_wounding->checkState())
|
|
weapon_damage.append(" +1 bleed (Wounding, stacks)");
|
|
if(ui->feat_bleedingattack->isChecked())
|
|
weapon_damage.append(" + " + QString::number(ui->feat_sneak_attack_bonus->value()) + " bleed (Bleeding Attack)");
|
|
|
|
weapon_damage.append(")");
|
|
|
|
enemy_ac = ui->enemy_ac->value();
|
|
|
|
hit_bonus += ui->misc_bonus_attack->value() + ui->weapon_bonus->value();
|
|
if(ui->weapon_masterwork->checkState() && ui->weapon_bonus->value() == 0)
|
|
hit_bonus++;
|
|
if(ui->feat_weaponfocus->checkState())
|
|
hit_bonus++;
|
|
if(ui->feat_greaterweaponfocus->checkState())
|
|
hit_bonus++;
|
|
if(ui->weapon_melee->isChecked())
|
|
{
|
|
if(ui->feat_weaponfinesse->checkState() && ui->stat_dex_mod->value() > ui->stat_str_mod->value())
|
|
hit_bonus += ui->stat_dex_mod->value();
|
|
else
|
|
hit_bonus += ui->stat_str_mod->value();
|
|
}
|
|
else if(ui->weapon_ranged->isChecked())
|
|
hit_bonus += ui->stat_dex_mod->value();
|
|
|
|
int crit_min_bound = ui->crit_range_min->value();
|
|
if(ui->feat_improvedcritical->checkState() || ui->weapon_keen->checkState())
|
|
crit_min_bound = qMax(1, crit_min_bound * 2 - 21); //21 - (21 - crit_min_bound) * 2
|
|
int crit_confirm_bonus = 0;
|
|
if(ui->feat_criticalfocus->checkState())
|
|
crit_confirm_bonus += 4;
|
|
|
|
if(ui->buff_haste->isChecked())
|
|
hit_bonus += 1;
|
|
|
|
if(attack_type == "Rapid Shot")
|
|
hit_bonus -= 2;
|
|
|
|
int guidance_bonus = 0;
|
|
if(ui->buff_guidance->isChecked()) //only works on first attack
|
|
guidance_bonus = 1;
|
|
|
|
attacks.append("+" + QString::number(ui->attack_1_bonus->value() + hit_bonus + guidance_bonus));
|
|
hit_chance.append(QString::number(get_hitchance(ui->attack_1_bonus->value() + hit_bonus + guidance_bonus, enemy_ac)) + "%");
|
|
crit_chance.append(QString::number(get_critchance(ui->attack_1_bonus->value() + hit_bonus + guidance_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)) + "%");
|
|
crit_chance_all.append(QString::number(get_critchance(ui->attack_1_bonus->value() + hit_bonus + guidance_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac, true)) + "%");
|
|
|
|
|
|
attacks_num++;
|
|
|
|
if((attack_type == "Full Attack" || attack_type == "Power Attack" || attack_type == "Vital Strike" || attack_type == "Improved Vital Strike"
|
|
|| attack_type == "Greater Vital Strike" || attack_type == "Rapid Shot" || attack_type == "Manyshot") && ui->attack_2_bonus->value() > 0)
|
|
{
|
|
if((attack_type == "Full Attack" || attack_type == "Rapid Shot") && (ui->weapon_speed->isChecked() || ui->buff_haste->isChecked()))
|
|
{ //perform an extra attack during "Full Attack" or "Rapid Shot" when you either have a weapon with speed or are haste'd
|
|
attacks.append("/+" + QString::number(ui->attack_1_bonus->value() + hit_bonus));
|
|
hit_chance.append("/" + QString::number(get_hitchance(ui->attack_1_bonus->value() + hit_bonus, enemy_ac)) + "%");
|
|
crit_chance.append("/" + QString::number(get_critchance(ui->attack_1_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)) + "%");
|
|
crit_chance_all.append("/" + QString::number(get_critchance(ui->attack_1_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac, true)) + "%");
|
|
attacks_num++;
|
|
}
|
|
if(attack_type == "Rapid Shot")
|
|
{ //perform an extra attack when using "Rapid Shot"
|
|
attacks.append("/+" + QString::number(ui->attack_1_bonus->value() + hit_bonus));
|
|
hit_chance.append("/" + QString::number(get_hitchance(ui->attack_1_bonus->value() + hit_bonus, enemy_ac)) + "%");
|
|
crit_chance.append("/" + QString::number(get_critchance(ui->attack_1_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)) + "%");
|
|
crit_chance_all.append("/" + QString::number(get_critchance(ui->attack_1_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac, true)) + "%");
|
|
attacks_num++;
|
|
}
|
|
|
|
attacks.append("/+" + QString::number(ui->attack_2_bonus->value() + hit_bonus));
|
|
attacks_num++;
|
|
hit_chance.append("/" + QString::number(get_hitchance(ui->attack_2_bonus->value() + hit_bonus, enemy_ac)) + "%");
|
|
crit_chance.append("/" + QString::number(get_critchance(ui->attack_2_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)) + "%");
|
|
crit_chance_all.append("/" + QString::number(get_critchance(ui->attack_2_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac, true)) + "%");
|
|
|
|
if(ui->attack_3_bonus->value() > 0)
|
|
{
|
|
attacks.append("/+" + QString::number(ui->attack_3_bonus->value() + hit_bonus));
|
|
attacks_num++;
|
|
hit_chance.append("/" + QString::number(get_hitchance(ui->attack_3_bonus->value() + hit_bonus, enemy_ac)) + "%");
|
|
crit_chance.append("/" + QString::number(get_critchance(ui->attack_3_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)) + "%");
|
|
crit_chance_all.append("/" + QString::number(get_critchance(ui->attack_3_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac, true)) + "%");
|
|
|
|
if(ui->attack_4_bonus->value() > 0)
|
|
{
|
|
attacks.append("/+" + QString::number(ui->attack_4_bonus->value() + hit_bonus));
|
|
attacks_num++;
|
|
hit_chance.append("/" + QString::number(get_hitchance(ui->attack_4_bonus->value() + hit_bonus, enemy_ac)) + "%");
|
|
crit_chance.append("/" + QString::number(get_critchance(ui->attack_4_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)) + "%");
|
|
crit_chance_all.append("/" + QString::number(get_critchance(ui->attack_4_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac, true)) + "%");
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
results.append("Weapon: " + weapon + weapon_damage + "\nAttack(s): " + attacks +
|
|
"\nHit Chance: " + hit_chance + " of attacks" +
|
|
"\nCrit Chance " + crit_chance + " of successful hits" +
|
|
"\n " + crit_chance_all + " of all attacks" +
|
|
"\n----- " + ui->attack_type->currentText() + " -----" +
|
|
"\n");
|
|
|
|
float damage_noncrit = get_damage();
|
|
float damage_crit = get_damage(true);
|
|
float manyshot_bonus = 0;
|
|
float damage_expected = 0;
|
|
float damage_total = 0;
|
|
|
|
if(attack_type == "Vital Strike")
|
|
{
|
|
//roll the damage dice again for the vital strike
|
|
//TODO: figure out which (if ANY) bonuses should be multiplied for vital strike (eg the weapon damage bonus)
|
|
//for now, I am assuming that NO bonuses are multiplied
|
|
damage_noncrit += ui->weapon_die_count->value() * (ui->weapon_die_value->currentText().toInt() + 1)/2.0;
|
|
damage_crit += ui->weapon_die_count->value() * (ui->weapon_die_value->currentText().toInt() + 1)/2.0;
|
|
}
|
|
else if(attack_type == "Improved Vital Strike")
|
|
{
|
|
//roll the damage dice twice more for improved vital strike
|
|
damage_noncrit += ui->weapon_die_count->value() * (ui->weapon_die_value->currentText().toInt() + 1);
|
|
damage_crit += ui->weapon_die_count->value() * (ui->weapon_die_value->currentText().toInt() + 1);
|
|
}
|
|
else if(attack_type == "Greater Vital Strike")
|
|
{
|
|
//roll the damage dice thrice more for greater vital strike
|
|
damage_noncrit += 1.5 * ui->weapon_die_count->value() * (ui->weapon_die_value->currentText().toInt() + 1);
|
|
damage_crit += 1.5 * ui->weapon_die_count->value() * (ui->weapon_die_value->currentText().toInt() + 1);
|
|
}
|
|
else if(attack_type == "Deadly Stroke")
|
|
{
|
|
if(!ui->feat_greaterweaponfocus->checkState())
|
|
ui->warnings->setText("Warning: Greater Weapon Focus is required to perform Deadly Stroke");
|
|
//deal double the normal damage. additional damage is not multiplied on a crit
|
|
damage_crit += damage_noncrit;
|
|
damage_noncrit *= 2;
|
|
}
|
|
|
|
|
|
if(attack_type == "Manyshot")
|
|
{
|
|
if(ui->stat_dex_mod->value() < 3)
|
|
ui->warnings->setText("Warning: Manyshot requires a dex of at least 17");
|
|
manyshot_bonus = damage_noncrit;
|
|
if(ui->feat_sneakattack->isChecked())
|
|
manyshot_bonus -= 3.5 * ui->feat_sneak_attack_bonus->value(); //sneak is only counted once
|
|
results.append("Average damage (non-crit): " + QString::number(damage_noncrit + manyshot_bonus, 'g', 4) + " (first shot) / " + QString::number(damage_noncrit, 'g', 4) + " (other shots)" +
|
|
"\nAverage damage (crit): " + QString::number(damage_crit + manyshot_bonus, 'g', 4) + " (first shot) / " + QString::number(damage_crit, 'g', 4) + " (other shots)" +
|
|
"\n");
|
|
damage_expected = (damage_noncrit + manyshot_bonus + (damage_crit - damage_noncrit) * get_critchance(ui->attack_1_bonus->value() + hit_bonus + guidance_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)/100.0) * get_hitchance(ui->attack_1_bonus->value() + hit_bonus + guidance_bonus, enemy_ac)/100.0;
|
|
}
|
|
else
|
|
{
|
|
results.append("Average damage (non-crit): " + QString::number(damage_noncrit, 'g', 4) +
|
|
"\nAverage damage (crit): " + QString::number(damage_crit, 'g', 4) +
|
|
"\n");
|
|
//Expected damage per turn = (average non-crit damage + bonus crit damage * chance to crit)* chance to hit
|
|
// = (non-crit + (crit - non-crit) * crit chance) * hit chance
|
|
damage_expected = (damage_noncrit + (damage_crit - damage_noncrit) * get_critchance(ui->attack_1_bonus->value() + hit_bonus + guidance_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)/100.0) * get_hitchance(ui->attack_1_bonus->value() + hit_bonus + guidance_bonus, enemy_ac)/100.0;
|
|
}
|
|
damage_total = damage_expected;
|
|
results.append("Expected damage: " + QString::number(damage_expected, 'g', 4));
|
|
|
|
|
|
if(attack_type == "Full Attack" || attack_type == "Power Attack" || attack_type == "Vital Strike" || attack_type == "Improved Vital Strike"
|
|
|| attack_type == "Greater Vital Strike" || attack_type == "Rapid Shot" || attack_type == "Manyshot")
|
|
{
|
|
if((attack_type == "Full Attack" || attack_type == "Rapid Shot") && (ui->weapon_speed->isChecked() || ui->buff_haste->isChecked()))
|
|
{
|
|
damage_expected = (damage_noncrit + (damage_crit - damage_noncrit) * get_critchance(ui->attack_1_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)/100.0) * get_hitchance(ui->attack_1_bonus->value() + hit_bonus, enemy_ac)/100.0;
|
|
results.append("/" + QString::number(damage_expected, 'g', 4));
|
|
damage_total += damage_expected;
|
|
}
|
|
if(attack_type == "Rapid Shot")
|
|
{
|
|
damage_expected = (damage_noncrit + (damage_crit - damage_noncrit) * get_critchance(ui->attack_1_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)/100.0) * get_hitchance(ui->attack_1_bonus->value() + hit_bonus, enemy_ac)/100.0;
|
|
results.append("/" + QString::number(damage_expected, 'g', 4));
|
|
damage_total += damage_expected;
|
|
}
|
|
|
|
if(ui->attack_2_bonus->value() > 0)
|
|
{
|
|
damage_expected = (damage_noncrit + (damage_crit - damage_noncrit) * get_critchance(ui->attack_2_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)/100.0) * get_hitchance(ui->attack_2_bonus->value() + hit_bonus, enemy_ac)/100.0;
|
|
results.append("/" + QString::number(damage_expected, 'g', 4));
|
|
damage_total += damage_expected;
|
|
}
|
|
if(ui->attack_3_bonus->value() > 0)
|
|
{
|
|
damage_expected = (damage_noncrit + (damage_crit - damage_noncrit) * get_critchance(ui->attack_3_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)/100.0) * get_hitchance(ui->attack_3_bonus->value() + hit_bonus, enemy_ac)/100.0;
|
|
results.append("/" + QString::number(damage_expected, 'g', 4));
|
|
damage_total += damage_expected;
|
|
}
|
|
if(ui->attack_4_bonus->value() > 0)
|
|
{
|
|
damage_expected = (damage_noncrit + (damage_crit - damage_noncrit) * get_critchance(ui->attack_4_bonus->value() + hit_bonus, crit_confirm_bonus, crit_min_bound, enemy_ac)/100.0) * get_hitchance(ui->attack_4_bonus->value() + hit_bonus, enemy_ac)/100.0;
|
|
results.append("/" + QString::number(damage_expected, 'g', 4));
|
|
damage_total += damage_expected;
|
|
}
|
|
results.append(" = " + QString::number(damage_total, 'g', 4) + " per turn\n");
|
|
}
|
|
else if (attack_type == "Cleave")
|
|
{
|
|
//cleave damage can only be attempted when the first target is hit
|
|
float damage_cleave_expected = damage_expected * get_hitchance(ui->attack_1_bonus->value() + hit_bonus, enemy_ac)/100.0;
|
|
damage_total += damage_cleave_expected;
|
|
results.append("/" + QString::number(damage_cleave_expected, 'g', 4) + " = " + QString::number(damage_total, 'g', 4) + " per turn\n");
|
|
}
|
|
else if (attack_type == "Great Cleave")
|
|
{
|
|
//theoretical maximum targets should be 8 (completely surrounding the player)
|
|
//the rest of the maths should be like normal "Cleave"
|
|
|
|
float hitchance = get_hitchance(ui->attack_1_bonus->value() + hit_bonus, enemy_ac)/100.0;
|
|
float damage_cleave_expected = damage_expected;
|
|
|
|
for(int targets_left = ui->attack_great_cleave_targets->value() - 1; targets_left > 0; --targets_left)
|
|
{
|
|
damage_cleave_expected *= hitchance;
|
|
damage_total += damage_cleave_expected;
|
|
results.append("/" + QString::number(damage_cleave_expected, 'g', 4)); //3rd target
|
|
}
|
|
|
|
results.append(" = " + QString::number(damage_total, 'g', 4) + " per turn (" + QString::number(ui->attack_great_cleave_targets->value()) + " targets)\n");
|
|
}
|
|
else if(attack_type == "Deadly Stroke")
|
|
{
|
|
results.append(" + 1 Con bleed per turn");
|
|
}
|
|
ui->result_text->setPlainText(results);
|
|
}
|
|
|
|
void MainWindow::get_warnings()
|
|
{
|
|
QString warning = "";
|
|
int BAB = ui->attack_1_bonus->value();
|
|
|
|
if(ui->weapon_bonus->value() > 5)
|
|
warning.append(" Weapon bonus must be <= 5");
|
|
|
|
if(ui->feat_weaponfocus->checkState() && BAB < 1)
|
|
warning.append(" Weapon Focus requires BAB >= 1");
|
|
|
|
if(ui->feat_weaponspecialization->checkState() && BAB < 4)
|
|
warning.append(" Weapon Specialization requires Fighter level 4");
|
|
|
|
if(ui->feat_weaponfocus->checkState() && BAB < 8)
|
|
warning.append(" Weapon Focus requires Fighter level 8");
|
|
|
|
if(ui->feat_improvedcritical->checkState() && BAB < 8)
|
|
warning.append(" Improved Critical requires BAB >= 8");
|
|
|
|
if(ui->feat_criticalfocus->checkState() && BAB < 9)
|
|
warning.append(" Critical Focus requires BAB >= 9");
|
|
|
|
if(ui->feat_greaterweaponspecialization->checkState() && BAB < 12)
|
|
warning.append(" Greater Weapon Specialization requires Fighter level 12");
|
|
|
|
if(! warning.isEmpty())
|
|
{
|
|
ui->warnings->setText("Warning: " + warning);
|
|
}
|
|
}
|
|
|
|
void MainWindow::on_feat_arcanestrike_bonus_valueChanged(int bonus)
|
|
{
|
|
if(bonus == 0 && ui->feat_arcanestrike->checkState())
|
|
{
|
|
ui->feat_arcanestrike->click();
|
|
}
|
|
else if(! ui->feat_arcanestrike->checkState())
|
|
{
|
|
ui->feat_arcanestrike->click();
|
|
}
|
|
this->get_warnings();
|
|
}
|
|
|
|
void MainWindow::on_feat_weapontraining_bonus_valueChanged(int bonus)
|
|
{
|
|
if(bonus == 0 && ui->feat_weapontraining->checkState())
|
|
{
|
|
ui->feat_weapontraining->click();
|
|
}
|
|
else if(! ui->feat_weapontraining->checkState())
|
|
{
|
|
ui->feat_weapontraining->click();
|
|
}
|
|
this->get_warnings();
|
|
}
|
|
|
|
void MainWindow::on_feat_weaponfocus_clicked(bool checked)
|
|
{
|
|
if(checked == false)
|
|
{
|
|
if(ui->feat_greaterweaponfocus->checkState())
|
|
ui->feat_greaterweaponfocus->click();
|
|
if(ui->feat_weaponspecialization->checkState())
|
|
ui->feat_weaponspecialization->click();
|
|
if(ui->feat_greaterweaponspecialization->checkState())
|
|
ui->feat_greaterweaponspecialization->click();
|
|
}
|
|
this->get_warnings();
|
|
}
|
|
|
|
void MainWindow::on_feat_greaterweaponfocus_clicked(bool checked)
|
|
{
|
|
if(checked == true && ! ui->feat_weaponfocus->checkState())
|
|
ui->feat_weaponfocus->click();
|
|
this->get_warnings();
|
|
}
|
|
|
|
void MainWindow::on_feat_weaponspecialization_clicked(bool checked)
|
|
{
|
|
if(checked == true &&! ui->feat_weaponfocus->checkState())
|
|
ui->feat_weaponfocus->click();
|
|
if(checked == false && ui->feat_greaterweaponspecialization->checkState())
|
|
ui->feat_greaterweaponspecialization->click();
|
|
this->get_warnings();
|
|
}
|
|
|
|
void MainWindow::on_feat_greaterweaponspecialization_clicked(bool checked)
|
|
{
|
|
if(checked == true)
|
|
{
|
|
if(! ui->feat_weaponspecialization->checkState())
|
|
ui->feat_weaponspecialization->click();
|
|
if(! ui->feat_weaponfocus->checkState())
|
|
ui->feat_weaponfocus->click();
|
|
}
|
|
this->get_warnings();
|
|
}
|
|
|
|
void MainWindow::on_stat_str_mod_valueChanged()
|
|
{
|
|
this->get_warnings();
|
|
}
|
|
|
|
void MainWindow::on_weapon_flaming_clicked(bool checked)
|
|
{
|
|
if(! checked && ui->weapon_flamingburst->checkState())
|
|
ui->weapon_flamingburst->click();
|
|
}
|
|
|
|
void MainWindow::on_weapon_flamingburst_clicked(bool checked)
|
|
{
|
|
if(checked && ! ui->weapon_flaming->checkState())
|
|
ui->weapon_flaming->click();
|
|
}
|
|
|
|
void MainWindow::on_weapon_ranged_clicked(bool checked)
|
|
{
|
|
if(checked)
|
|
{
|
|
ui->weapon_melee_modifiers->setProperty("visible", false);
|
|
ui->weapon_keen->setChecked(false); ui->weapon_keen->setEnabled(false);
|
|
ui->weapon_vicious->setChecked(false); ui->weapon_vicious->setEnabled(false);
|
|
ui->weapon_wounding->setChecked(false); ui->weapon_wounding->setEnabled(false);
|
|
|
|
ui->attack_type->clear();
|
|
ui->attack_type->insertItems(0, QStringList() << "Basic Attack" << "Full Attack" << "Rapid Shot" << "Manyshot");
|
|
ui->attack_type->setCurrentIndex(1);
|
|
}
|
|
}
|
|
|
|
void MainWindow::on_weapon_melee_clicked(bool checked)
|
|
{
|
|
if(checked)
|
|
{
|
|
ui->weapon_keen->setEnabled(true);
|
|
ui->weapon_vicious->setEnabled(true);
|
|
ui->weapon_wounding->setEnabled(true);
|
|
|
|
ui->attack_type->clear();
|
|
ui->attack_type->insertItems(0, QStringList() << "Basic Attack" << "Full Attack" << "Power Attack" << "Cleave" << "Great Cleave" <<
|
|
"Vital Strike" << "Improved Vital Strike" << "Greater Vital Strike" << "Deadly Stroke");
|
|
ui->attack_type->setCurrentIndex(1);
|
|
}
|
|
}
|
|
|
|
void MainWindow::on_attack_type_activated(const QString &attack_type)
|
|
{
|
|
if(attack_type == "Great Cleave")
|
|
{
|
|
ui->attack_great_cleave_targets_label->setVisible(true);
|
|
ui->attack_great_cleave_targets->setVisible(true);
|
|
}
|
|
else
|
|
{
|
|
ui->attack_great_cleave_targets_label->setVisible(false);
|
|
ui->attack_great_cleave_targets->setVisible(false);
|
|
}
|
|
}
|
|
|
|
|
|
void MainWindow::on_about_clicked()
|
|
{
|
|
ui->warnings->setText("Armsman was written by Russell Champoux in February 2013 and is distributed freely\nSend comments, suggestions, bug reports/fixes, or Paypal donations to RussellChamp@gmail.com");
|
|
}
|