/*
* This file is part of RuneOptimizer.
*
* RuneOptimizer is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* RuneOptimizer is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* RuneOptimizer. If not, see .
*/
#include
#include
#include
#include
#include "RuneOptimizer.h"
/**
* Starts the program.
*
* Reads parameters and runs the appropiate functions.
*
* @param argc Argument count.
* @param argv Argument list.
* @return SUCCESS on success, other on error.
*/
int main(int argc, char *argv[]){
// Parse the arguments to find the command (index 1)
// If no arguments, end here
if (argc < 2){
fprintf(stderr, "No command specified\n");
return ERROR_INPUT_NO_COMMAND;
}
// Update command, TODO
if (strcmp(argv[1], "update") == 0){
fprintf(stderr, "Updating is still unimplemented\n");
return UNIMPLEMENTED;
}
// Optimize command, call the function with all the arguments to be
// processed there.
else if (strcmp(argv[1], "optimize") == 0){
int result = optimize(argc, argv);
return result;
}
// Help command. Call and return
else if (strcmp(argv[1], "help") == 0){
show_help();
return SUCCESS;
}
// Any other command is an error
else{
fprintf(stderr, "Invalid command specified: %s\n", argv[1]);
return ERROR_INPUT_INVALID_COMMAND;
}
}
int open_database(){
// The database location and name is hardcoded in the same directory.
int db_status = sqlite3_open("../data.sqlite", &db);
if (db_status != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return ERROR_DB_CANT_OPEN;
}
else{
return SUCCESS;
}
}
void show_help(){
printf("\nRune Optimizer v0.1\n");
printf("\n Usage:\n");
printf(" RuneOptimizer [command] [options]\n");
printf("\n\n Command: helps\n");
printf("\n Display this help text and exists. It has no options.\n");
printf("\n\n Command: update\n");
printf("\n Updates the information and builds a database. Currently unimplemented.\n");
printf("\n\n Command: optimize\n");
printf("\n Calculates an optimization for a unit.\n");
printf("\n Usage\n");
printf(" RuneOptimizer optmize [unit] [options]\n");
printf("\n [unit] can be a unit ID or a unit name (case sensitive)\n");
printf("\n Options: \n\n");
printf(" -h | --min_hp Minumum HP to consider in the optimization.\n");
printf(" It defaults to the unit's current value.\n");
printf(" -a | --min_atk Minumum ATK to consider in the optimization.\n");
printf(" It defaults to the unit's current value.\n");
printf(" -d | --min_def Minumum DEF to consider in the optimization.\n");
printf(" It defaults to the unit's current value.\n");
printf(" -s | --min_spd Minumum SPD to consider in the optimization.\n");
printf(" It defaults to the unit's current value.\n");
printf(" -c | --min_crr Minumum CRIT RATE to consider in the optimization.\n");
printf(" It defaults to the unit's current value.\n");
printf(" -d | --min_crd Minumum CRIT DAMAGE to consider in the optimization.\n");
printf(" It defaults to the unit's current value.\n");
printf(" -r | --min_res Minumum RES to consider in the optimization.\n");
printf(" It defaults to the unit's current value.\n");
printf(" -f | --min_acc Minumum ACC to consider in the optimization.\n");
printf(" It defaults to the unit's current value.\n");
printf(" -l | --level Level to consider the runes during the optimization.\n");
printf(" It only affects the rune main stats. Valid values are\n");
printf(" 'current', '12' and '15'. Default is 'current'\n");
printf(" -t | --stats ,... Stats than can be selected as mains for slots 2, 4 and 6.\n");
printf(" Only the selected stats will be included, so this option\n");
printf(" is mandatory. Accepted values are 'hp', 'atk', 'def',\n");
printf(" 'hpflat', 'atkflat', 'defflat', 'spd', 'crr', 'crd',\n");
printf(" 'res' and 'acc'. Values must be comma-separated, and up\n");
printf(" to 12 can be included.\n");
printf(" -e | --sets ,... Rune sets that than can be considered during the optimization.\n");
printf(" Only the selected sets will be included, so this option is\n");
printf(" mandatory. Accepted values the rune net names, lowercase.\n");
printf(" Values must be comma-separated, and up to 3 can be included.\n");
return;
}
int optimize(int argc, char *argv[]){
// Get unit identifier
if (argc < 3){
fprintf(stderr, "No unit specified for optimization\n");
return ERROR_INPUT_NO_UNIT;
}
// Initialize values for the data to be read form arguments.
int requested_level = 0;
char requested_level_column[9] = "current_";
int sets[3] = {0, 0, 0};
int requested_stats[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
struct Stats min_stats;
// Unsigned, so I cant use -1 as placeholder to check if its modified from
// arguments. I'm using just 1 (I don't think anybody will input values of
// 1), and later, if they are still 1, they are changed to 0.
min_stats.hp = 1;
min_stats.atk = 1;
min_stats.def = 1;
min_stats.spd = 1;
min_stats.crr = 1;
min_stats.crd = 1;
min_stats.res = 1;
min_stats.acc = 1;
// Loop command line arguments
for (int i = 3; i < argc; i ++){
// Rune level arguments
if (strcmp("--level", argv[i]) == 0 || strcmp("-l", argv[i]) == 0){
if (i < argc - 1){
if (strcmp("current", argv[i + 1]) == 0){
requested_level = 0;
strcpy(requested_level_column, "current_");
}
else if (strcmp("12", argv[i + 1]) == 0){
requested_level = 12;
strcpy(requested_level_column, "lv12_");
}
else if (strcmp("15", argv[i + 1]) == 0){
requested_level = 15;
strcpy(requested_level_column, "lv15_");
}
else{
fprintf(
stderr,
"Invalid option for argument %s: %s\n"
"Valid options are 'current', '12' or '15'\n",
argv[i], argv[i + 1]
);
return ERROR_INPUT_INVALID_LEVEL;
}
// Advance one position in argument reading
i ++;
}
else{
fprintf(
stderr,
"Argument %s requires a value.\n"
"Valid options are 'current', '12' or '15'\n", argv[i]
);
return ERROR_INPUT_NO_LEVEL;
}
}
// Rune sets argument
else if (strcmp("--sets", argv[i]) == 0 || strcmp("-e", argv[i]) == 0){
if (i < argc - 1){
// Separate string by commas
int j = 0;
// Returns first token
char *token = strtok(argv[i + 1], ",");
// Keep printing tokens while one of the
// delimiters present in the list, or until its complete
while (token != NULL && j < 3){
if (strcmp(token, "energy") == 0){
sets[j] = ENERGY;
}
else if (strcmp(token, "guard") == 0){
sets[j] = GUARD;
}
else if (strcmp(token, "swift") == 0){
sets[j] = SWIFT;
}
else if (strcmp(token, "blade") == 0){
sets[j] = BLADE;
}
else if (strcmp(token, "rage") == 0){
sets[j] = RAGE;
}
else if (strcmp(token, "focus") == 0){
sets[j] = FOCUS;
}
else if (strcmp(token, "endure") == 0){
sets[j] = ENDURE;
}
else if (strcmp(token, "fatal") == 0){
sets[j] = FATAL;
}
else if (strcmp(token, "despair") == 0){
sets[j] = DESPAIR;
}
else if (strcmp(token, "vampire") == 0){
sets[j] = VAMPIRE;
}
else if (strcmp(token, "violent") == 0){
sets[j] = VIOLENT;
}
else if (strcmp(token, "nemesis") == 0){
sets[j] = NEMESIS;
}
else if (strcmp(token, "will") == 0){
sets[j] = WILL;
}
else if (strcmp(token, "shield") == 0){
sets[j] = SHIELD;
}
else if (strcmp(token, "revenge") == 0){
sets[j] = REVENGE;
}
else if (strcmp(token, "destroy") == 0){
sets[j] = DESTROY;
}
else if (strcmp(token, "fight") == 0){
sets[j] = FIGHT;
}
else if (strcmp(token, "determination") == 0){
sets[j] = DETERMINATION;
}
else if (strcmp(token, "enhance") == 0){
sets[j] = ENHANCE;
}
else if (strcmp(token, "accuracy") == 0){
sets[j] = ACCURACY;
}
else if (strcmp(token, "tolerance") == 0){
sets[j] = TOLERANCE;
}
else{
fprintf(stderr, "Unknown rune set %s.\n", token);
return ERROR_INPUT_INVALID_SET;
}
token = strtok(NULL, ",");
j ++;
}
// Advance one position in argument reading
i ++;
}
else{
fprintf(
stderr,
"Argument %s requires a list of values separated by commas.\n",
argv[i]
);
return ERROR_INPUT_NO_SET;
}
}
// Accepted stats arguments
else if (strcmp("--stats", argv[i]) == 0 || strcmp("-t", argv[i]) == 0){
if (i < argc - 1){
// Separate string by commas
int j = 0;
// Returns first token
char *token = strtok(argv[i + 1], ",");
// Keep printing tokens while one of the
// delimiters present in the list, or until its complete
while (token != NULL && j < 3){
if (strcmp(token, "hp") == 0){
requested_stats[j] = HP_PERCENT;
}
else if (strcmp(token, "atk") == 0){
requested_stats[j] = ATK_PERCENT;
}
else if (strcmp(token, "def") == 0){
requested_stats[j] = DEF_PERCENT;
}
else if (strcmp(token, "hpflat") == 0){
requested_stats[j] = HP_FLAT;
}
else if (strcmp(token, "atkflat") == 0){
requested_stats[j] = ATK_FLAT;
}
else if (strcmp(token, "defflat") == 0){
requested_stats[j] = DEF_FLAT;
}
else if (strcmp(token, "spd") == 0){
requested_stats[j] = SPD;
}
else if (strcmp(token, "crr") == 0){
requested_stats[j] = CRR;
}
else if (strcmp(token, "crd") == 0){
requested_stats[j] = CRD;
}
else if (strcmp(token, "res") == 0){
requested_stats[j] = RES;
}
else if (strcmp(token, "acc") == 0){
requested_stats[j] = ACC;
}
else{
fprintf(stderr, "Unknown rune stat %s.\n", token);
return ERROR_INPUT_INVALID_STAT;
}
token = strtok(NULL, ",");
j ++;
}
// Advance one position in argument reading
i ++;
}
else{
fprintf(stderr, "Argument %s requires a list of values separated by commas.\n", argv[i]);
return ERROR_INPUT_NO_STAT;
}
}
// Minimum stats arguments.
else if (strcmp("--min-hp", argv[i]) == 0 || strcmp("-h", argv[i]) == 0){
if (i < argc - 1){
unsigned short stat_tmp = atoi(argv[i + 1]);
if (stat_tmp == 1){
stat_tmp = 0;
}
min_stats.hp = stat_tmp;
// Advance one position in argument reading
i ++;
}
else{
fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
return ERROR_INPUT_NO_HP;
}
}
else if (strcmp("--min-atk", argv[i]) == 0 || strcmp("-a", argv[i]) == 0){
if (i < argc - 1){
unsigned short stat_tmp = atoi(argv[i + 1]);
if (stat_tmp == 1){
stat_tmp = 0;
}
min_stats.atk = stat_tmp;
// Advance one position in argument reading
i ++;
}
else{
fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
return ERROR_INPUT_NO_ATK;
}
}
else if (strcmp("--min-def", argv[i]) == 0 || strcmp("-d", argv[i]) == 0){
if (i < argc - 1){
unsigned short stat_tmp = atoi(argv[i + 1]);
if (stat_tmp == 1){
stat_tmp = 0;
}
min_stats.def = stat_tmp;
// Advance one position in argument reading
i ++;
}
else{
fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
return ERROR_INPUT_NO_DEF;
}
}
else if (strcmp("--min-spd", argv[i]) == 0 || strcmp("-s", argv[i]) == 0){
if (i < argc - 1){
unsigned short stat_tmp = atoi(argv[i + 1]);
if (stat_tmp == 1){
stat_tmp = 0;
}
min_stats.spd = stat_tmp;
// Advance one position in argument reading
i ++;
}
else{
fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
return ERROR_INPUT_NO_SPD;
}
}
else if (strcmp("--min-crr", argv[i]) == 0 || strcmp("-c", argv[i]) == 0){
if (i < argc - 1){
unsigned short stat_tmp = atoi(argv[i + 1]);
if (stat_tmp == 1){
stat_tmp = 0;
}
else if (stat_tmp > 100){
stat_tmp = 100;
}
min_stats.crr = stat_tmp;
// Advance one position in argument reading
i ++;
}
else{
fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
return ERROR_INPUT_NO_CRR;
}
}
else if (strcmp("--min-crd", argv[i]) == 0 || strcmp("-d", argv[i]) == 0){
if (i < argc - 1){
unsigned short stat_tmp = atoi(argv[i + 1]);
if (stat_tmp == 1){
stat_tmp = 0;
}
min_stats.crd = stat_tmp;
// Advance one position in argument reading
i ++;
}
else{
fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
return ERROR_INPUT_NO_CRD;
}
}
else if (strcmp("--min-res", argv[i]) == 0 || strcmp("-r", argv[i]) == 0){
if (i < argc - 1){
unsigned short stat_tmp = atoi(argv[i + 1]);
if (stat_tmp == 1){
stat_tmp = 0;
}
else if (stat_tmp > 100){
stat_tmp = 100;
}
min_stats.atk = stat_tmp;
// Advance one position in argument reading
i ++;
}
else{
fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
return ERROR_INPUT_NO_RES;
}
}
else if (strcmp("--min-acc", argv[i]) == 0 || strcmp("-f", argv[i]) == 0){
if (i < argc - 1){
unsigned short stat_tmp = atoi(argv[i + 1]);
if (stat_tmp == 1){
stat_tmp = 0;
}
else if (stat_tmp > 85){
stat_tmp = 85;
}
min_stats.acc = stat_tmp;
// Advance one position in argument reading
i ++;
}
else{
fprintf(stderr, "Argument %s requires a numeric value.\n", argv[i]);
return ERROR_INPUT_NO_ACC;
}
}
}
// Now we can open the database.
if (SUCCESS != open_database()){
return ERROR_DB_CANT_OPEN;
}
// Read the unit from the databse
sqlite3_stmt *res;
char *sql = "SELECT "
"id, name, base_hp, base_atk, base_def, base_spd, base_crr, base_crd, "
"base_res, base_acc, current_hp, current_atk, current_def, current_spd, "
"current_crr, current_crd, current_res, current_acc "
"FROM units WHERE id = ? OR name = ?";
db_status = sqlite3_prepare_v2(db, sql, -1, &res, 0);
if (db_status != SQLITE_OK) {
fprintf(
stderr,
"Failed to execute statement to select unit: %s\n",
sqlite3_errmsg(db)
);
return ERROR_DB_UNIT;
}
sqlite3_bind_text(res, 1, argv[2], strlen(argv[2]), NULL);
sqlite3_bind_text(res, 2, argv[2], strlen(argv[2]), NULL);
// Fetch just one line
int step = sqlite3_step(res);
// Create a unit structure with the red data.
struct Unit unit;
if (step != SQLITE_ROW) {
fprintf(stderr, "Unit not found: %s\n", sqlite3_errmsg(db));
return ERROR_DB_UNIT_NOF_FOUND;
}
strcpy(unit.id, sqlite3_column_text(res, 0));
strcpy(unit.name, sqlite3_column_text(res, 1));
unit.base_hp = sqlite3_column_int(res, 2);
unit.base_atk = sqlite3_column_int(res, 3);
unit.base_def = sqlite3_column_int(res, 4);
unit.base_spd = sqlite3_column_int(res, 5);
unit.base_crr = sqlite3_column_int(res, 6);
unit.base_crd = sqlite3_column_int(res, 7);
unit.base_res = sqlite3_column_int(res, 8);
unit.base_acc = sqlite3_column_int(res, 9);
unit.current_hp = sqlite3_column_int(res, 10);
unit.current_atk = sqlite3_column_int(res, 11);
unit.current_def = sqlite3_column_int(res, 12);
unit.current_spd = sqlite3_column_int(res, 13);
unit.current_crr = sqlite3_column_int(res, 14);
unit.current_crd = sqlite3_column_int(res, 15);
unit.current_res = sqlite3_column_int(res, 16);
unit.current_acc = sqlite3_column_int(res, 17);
// Min stats that have the value 1 get overriden by the current stats.
if (min_stats.hp == 1){
min_stats.hp = unit.current_hp;
}
if (min_stats.atk == 1){
min_stats.atk = unit.current_atk;
}
if (min_stats.def == 1){
min_stats.def = unit.current_def;
}
if (min_stats.spd == 1){
min_stats.spd = unit.current_spd;
}
if (min_stats.crr == 1){
min_stats.crr = unit.current_crr;
}
if (min_stats.crd == 1){
min_stats.crd = unit.current_crd;
}
if (min_stats.res == 1){
min_stats.res = unit.current_res;
}
if (min_stats.acc == 1){
min_stats.acc = unit.current_acc;
}
// Calculate required rune count
struct Rune_Set_Count requested_set_count;
requested_set_count.energy = 0;
requested_set_count.guard = 0;
requested_set_count.swift = 0;
requested_set_count.blade = 0;
requested_set_count.rage = 0;
requested_set_count.focus = 0;
requested_set_count.endure = 0;
requested_set_count.fatal = 0;
requested_set_count.despair = 0;
requested_set_count.vampire = 0;
requested_set_count.violent = 0;
requested_set_count.nemesis = 0;
requested_set_count.will = 0;
requested_set_count.shield = 0;
requested_set_count.revenge = 0;
requested_set_count.destroy = 0;
requested_set_count.fight = 0;
requested_set_count.determination = 0;
requested_set_count.enhance = 0;
requested_set_count.accuracy = 0;
requested_set_count.tolerance = 0;
char total_requested_runes = 0;
for (int i = 0; i < 3; i ++){
switch (sets[i]){
case ENERGY:
requested_set_count.energy += 2;
total_requested_runes += 2;
break;
case GUARD:
requested_set_count.guard += 2;
total_requested_runes += 2;
break;
case SWIFT:
requested_set_count.swift += 4;
total_requested_runes += 4;
break;
case BLADE:
requested_set_count.blade += 2;
total_requested_runes += 2;
break;
case RAGE:
requested_set_count.rage += 4;
total_requested_runes += 4;
break;
case FOCUS:
requested_set_count.focus += 2;
total_requested_runes += 2;
break;
case ENDURE:
requested_set_count.endure += 2;
total_requested_runes += 2;
break;
case FATAL:
requested_set_count.fatal += 4;
total_requested_runes += 4;
break;
case DESPAIR:
requested_set_count.despair += 2;
total_requested_runes += 2;
break;
case VAMPIRE:
requested_set_count.vampire += 4;
total_requested_runes += 4;
break;
case VIOLENT:
requested_set_count.violent += 4;
total_requested_runes += 4;
break;
case NEMESIS:
requested_set_count.nemesis += 2;
total_requested_runes += 2;
break;
case WILL:
requested_set_count.will += 2;
total_requested_runes += 2;
break;
case SHIELD:
requested_set_count.shield += 2;
total_requested_runes += 2;
break;
case REVENGE:
requested_set_count.revenge += 2;
total_requested_runes += 2;
break;
case DESTROY:
requested_set_count.destroy += 2;
total_requested_runes += 2;
break;
case FIGHT:
requested_set_count.fight += 2;
total_requested_runes += 2;
break;
case DETERMINATION:
requested_set_count.determination += 2;
total_requested_runes += 2;
break;
case ENHANCE:
requested_set_count.enhance += 2;
total_requested_runes += 2;
break;
case ACCURACY:
requested_set_count.accuracy += 2;
total_requested_runes += 2;
break;
case TOLERANCE:
requested_set_count.tolerance += 2;
total_requested_runes += 2;
break;
}
}
printf("\n");
if (total_requested_runes != 6){
fprintf(stderr, "Invalid rune set combination.\n");
return ERROR_INPUT_INCOMPLETE_SETS;
}
// Create queries for each slot
char query_odd[2500] = "SELECT id, unit, type, ";
strcat(query_odd, requested_level_column);
strcat(query_odd, "hp_flat, ");
strcat(query_odd, requested_level_column);
strcat(query_odd, "atk_flat, ");
strcat(query_odd, requested_level_column);
strcat(query_odd, "def_flat, ");
strcat(query_odd, requested_level_column);
strcat(query_odd, "hp_percent, ");
strcat(query_odd, requested_level_column);
strcat(query_odd, "atk_percent, ");
strcat(query_odd, requested_level_column);
strcat(query_odd, "def_percent, ");
strcat(query_odd, requested_level_column);
strcat(query_odd, "spd, ");
strcat(query_odd, requested_level_column);
strcat(query_odd, "crr, ");
strcat(query_odd, requested_level_column);
strcat(query_odd, "crd, ");
strcat(query_odd, requested_level_column);
strcat(query_odd, "res, ");
strcat(query_odd, requested_level_column);
strcat(query_odd, "acc FROM runes WHERE slot = ? AND type IN (");
char cur_set[2];
char stat_set[2];
if (sets[0] != 0){
sprintf(cur_set, "%d", sets[0]);
strcat(query_odd, cur_set);
strcat(query_odd, ", ");
}
if (sets[1] != 0){
sprintf(cur_set, "%d", sets[1]);
strcat(query_odd, cur_set);
strcat(query_odd, ", ");
}
if (sets[2] != 0){
sprintf(cur_set, "%d", sets[2]);
strcat(query_odd, cur_set);
strcat(query_odd, ", ");
}
strcat(query_odd, "-1) ");
// TODO: Add team stuff
char query_even[1500] = "SELECT id, unit, type, ";
strcat(query_even, requested_level_column);
strcat(query_even, "hp_flat, ");
strcat(query_even, requested_level_column);
strcat(query_even, "atk_flat, ");
strcat(query_even, requested_level_column);
strcat(query_even, "def_flat, ");
strcat(query_even, requested_level_column);
strcat(query_even, "hp_percent, ");
strcat(query_even, requested_level_column);
strcat(query_even, "atk_percent, ");
strcat(query_even, requested_level_column);
strcat(query_even, "def_percent, ");
strcat(query_even, requested_level_column);
strcat(query_even, "spd, ");
strcat(query_even, requested_level_column);
strcat(query_even, "crr, ");
strcat(query_even, requested_level_column);
strcat(query_even, "crd, ");
strcat(query_even, requested_level_column);
strcat(query_even, "res, ");
strcat(query_even, requested_level_column);
strcat(query_even, "acc FROM runes WHERE slot = ? AND type IN (");
if (sets[0] != 0){
sprintf(cur_set, "%d", sets[0]);
strcat(query_even, cur_set);
strcat(query_even, ", ");
}
if (sets[1] != 0){
sprintf(cur_set, "%d", sets[1]);
strcat(query_even, cur_set);
strcat(query_even, ", ");
}
if (sets[2] != 0){
sprintf(cur_set, "%d", sets[2]);
strcat(query_even, cur_set);
strcat(query_even, ", ");
}
strcat(query_even, "-1) AND main_stat IN (");
for (int i = 0; i < 12; i ++){
if (requested_stats[i] != 0){
sprintf(stat_set, "%d", requested_stats[i]);
strcat(query_even, stat_set);
strcat(query_even, ", ");
}
else{
break;
}
}
strcat(query_even, "-1) ");
// TODO: Add team stuff
// Get data for all the runes from the database.
// Im using a 7 position array, and the index 0 is ignored. This is
// is because I REALLY NEED to use 1-indexes to match rune slots.
sqlite3_stmt *stmt_runes[7];
// Im assumming 600 runes per slot is a safe estimate. I hope it doesn't
// come back to bite me.
struct Rune runes[7][600];
db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[1], 0);
if (db_status != SQLITE_OK) {
printf("Error getting runes for slot 1: %s\n", sqlite3_errmsg(db));
return ERROR_DB_RUNES_SLOT;
}
sqlite3_bind_int(stmt_runes[1], 1, 1);
db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[3], 0);
if (db_status != SQLITE_OK) {
printf("Error getting runes for slot 3: %s\n", sqlite3_errmsg(db));
return ERROR_DB_RUNES_SLOT;
}
sqlite3_bind_int(stmt_runes[3], 1, 3);
db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[5], 0);
if (db_status != SQLITE_OK) {
printf("Error getting runes for slot 5: %s\n", sqlite3_errmsg(db));
return ERROR_DB_RUNES_SLOT;
}
sqlite3_bind_int(stmt_runes[5], 1, 5);
db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[2], 0);
if (db_status != SQLITE_OK) {
printf("Error getting runes for slot 2: %s\n", sqlite3_errmsg(db));
return ERROR_DB_RUNES_SLOT;
}
sqlite3_bind_int(stmt_runes[2], 1, 2);
db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[4], 0);
if (db_status != SQLITE_OK) {
printf("Error getting runes for slot 4: %s\n", sqlite3_errmsg(db));
return ERROR_DB_RUNES_SLOT;
}
sqlite3_bind_int(stmt_runes[4], 1, 4);
db_status = sqlite3_prepare_v2(db, query_odd, -1, &stmt_runes[6], 0);
if (db_status != SQLITE_OK) {
printf("Error getting runes for slot 6: %s\n", sqlite3_errmsg(db));
return ERROR_DB_RUNES_SLOT;
}
sqlite3_bind_int(stmt_runes[6], 1, 6);
// Get and populate all the runes
int rune_count[7];
for (int i = 1; i < 7; i ++){
int j = 0;
while (1 == 1){
int status = sqlite3_step(stmt_runes[i]);
if (status == SQLITE_ROW){
strcpy(runes[i][j].id, sqlite3_column_text(stmt_runes[i], 0));
strcpy(runes[i][j].unit, sqlite3_column_text(stmt_runes[i], 1));
runes[i][j].set = sqlite3_column_int(stmt_runes[i], 2);
runes[i][j].hp_flat = sqlite3_column_int(stmt_runes[i], 3);
runes[i][j].atk_flat = sqlite3_column_int(stmt_runes[i], 4);
runes[i][j].def_flat = sqlite3_column_int(stmt_runes[i], 5);
runes[i][j].hp_percent = sqlite3_column_int(stmt_runes[i], 6);
runes[i][j].atk_percent = sqlite3_column_int(stmt_runes[i], 7);
runes[i][j].def_percent = sqlite3_column_int(stmt_runes[i], 8);
runes[i][j].spd = sqlite3_column_int(stmt_runes[i], 9);
runes[i][j].crr = sqlite3_column_int(stmt_runes[i], 10);
runes[i][j].crd = sqlite3_column_int(stmt_runes[i], 11);
runes[i][j].res = sqlite3_column_int(stmt_runes[i], 12);
runes[i][j].acc = sqlite3_column_int(stmt_runes[i], 13);
j ++;
}
else{
break;
}
}
rune_count[i] = j;
}
// If any slot doesn't have matching runes, we can stop now.
for (int i = 1; i < 7; i ++){
if (rune_count[i] == 0){
printf("\n - No availbale runes for slot %d\n", i);
return SUCCESS;
}
}
printf("\n");
unsigned long max_combinations =
rune_count[1] *
rune_count[2] *
rune_count[3] *
rune_count[4] *
rune_count[5] *
rune_count[6];
// Print a nice summary before starting the long optimization.
// This is an output example:
//
// ----------------------------------- Requested rune sets:
// | Lushen | RAGE BLADE
// | ID: 7223811472 |
// ----------------------------------- Accepted stats:
// | STAT | BASE | CURR. | MIN. | ATK CRR CRD
// -----------------------------------
// | HP: | 9225 | 12262 | 10000 | Considering runes at level 15
// | ATK: | 900 | 2482 | 2482 |
// | DEF: | 461 | 703 | 703 | Runes considered by slot:
// | SPD: | 103 | 159 | 159 | 1: 27 2: 39 3: 25
// | CRR: | 15% | 79% | 79% | 4: 35 5: 24 6: 32
// | CRD: | 50% | 188% | 188% |
// | RES: | 15% | 35% | 35% | Total combinations: 707616000
// | ACC: | 0% | 11% | 10% |
// -----------------------------------
//
// Im breaking the 80-characters-line rule here, but only 'cause I'd like
// to remain sane.
printf(" ----------------------------------- Requested rune sets:\n");
printf(" | %*s | ", -31, unit.name);
for (int i = 0; i < 3; i ++){
if (sets[i] != 0){
printf(" %s ", set_names[sets[i]]);
}
else{
break;
}
}
printf("\n");
printf(" | ID: %*s |\n", -27, unit.id);
printf(" ----------------------------------- Accepted stats:\n");
printf(" | STAT | BASE | CURR. | MIN. | ");
for (int i = 0; i < 12; i ++){
if (requested_stats[i] != 0){
printf(" %s ", stat_names[requested_stats[i]]);
}
else{
break;
}
}
printf("\n");
printf(" -----------------------------------\n");
printf(" | HP: | %*d | %*d | %*d | ", 5, unit.base_hp, 5, unit.current_hp, 5, min_stats.hp);
if (requested_level == 0){
printf("Using runes at their current level\n");
}
else{
printf("Considering runes at level %d\n", requested_level);
}
printf(" | ATK: | %*d | %*d | %*d |\n", 5, unit.base_atk, 5, unit.current_atk, 5, min_stats.atk);
printf(" | DEF: | %*d | %*d | %*d | Runes considered by slot:\n", 5, unit.base_def, 5, unit.current_def, 5, min_stats.def);
printf(" | SPD: | %*d | %*d | %*d | 1:%*d 2:%*d 3:%*d\n", 5, unit.base_spd, 5, unit.current_spd, 5, min_stats.spd, 3, rune_count[1], 3, rune_count[2], 3, rune_count[3]);
printf(" | CRR: | %*d\% | %*d\% | %*d\% | 4:%*d 5:%*d 6:%*d\n", 5, unit.base_crr, 5, unit.current_crr, 5, min_stats.crr, 3, rune_count[4], 3, rune_count[5], 3, rune_count[6]);
printf(" | CRD: | %*d\% | %*d\% | %*d\% |\n", 5, unit.base_crd, 5, unit.current_crd, 5, min_stats.crd);
printf(" | RES: | %*d\% | %*d\% | %*d\% | Total combinations: %d\n", 5, unit.base_res, 5, unit.current_res, 5, min_stats.res, max_combinations);
printf(" | ACC: | %*d\% | %*d\% | %*d\% |\n", 5, unit.base_acc, 5, unit.current_acc, 5, min_stats.acc);
printf(" -----------------------------------\n");
// Now its time to loop all 6 'reels' of runes and try to match combos
printf("\n\n__Optimization progress___________________________\n", max_combinations);
// Initialize arrys and some counters
int index[7] = {0, 0, 0, 0, 0, 0};
unsigned long tested_combinations = 0;
unsigned long valid_sets = 0;
unsigned long result_count = 0;
Result results[1000];
while(
index[1] < rune_count[1] &&
index[2] < rune_count[2] &&
index[3] < rune_count[3] &&
index[4] < rune_count[4] &&
index[5] < rune_count[5] &&
index[6] < rune_count[6]
){
// Progress bar, 50 characters to 100%
if (
(tested_combinations + 1) %
(unsigned long)(max_combinations / 50)
== 0
){
printf("#");
// Line buffered! need to flush after every char.
fflush(stdout);
}
// Calculate rune sets at current indexes.
struct Rune_Set_Count set_count;
set_count.energy = 0;
set_count.guard = 0;
set_count.swift = 0;
set_count.blade = 0;
set_count.rage = 0;
set_count.focus = 0;
set_count.endure = 0;
set_count.fatal = 0;
set_count.despair = 0;
set_count.vampire = 0;
set_count.violent = 0;
set_count.nemesis = 0;
set_count.will = 0;
set_count.shield = 0;
set_count.revenge = 0;
set_count.destroy = 0;
set_count.fight = 0;
set_count.determination = 0;
set_count.enhance = 0;
set_count.accuracy = 0;
set_count.tolerance = 0;
for (int i = 1; i < 7; i ++){
switch (runes[i][index[i]].set){
case ENERGY:
set_count.energy ++;
break;
case GUARD:
set_count.guard ++;
break;
case SWIFT:
set_count.swift ++;
break;
case BLADE:
set_count.blade ++;
break;
case RAGE:
set_count.rage ++;
break;
case FOCUS:
set_count.focus ++;
break;
case ENDURE:
set_count.endure ++;
break;
case FATAL:
set_count.fatal ++;
break;
case DESPAIR:
set_count.despair ++;
break;
case VAMPIRE:
set_count.vampire ++;
break;
case VIOLENT:
set_count.violent ++;
break;
case NEMESIS:
set_count.nemesis ++;
break;
case WILL:
set_count.will ++;
break;
case SHIELD:
set_count.shield ++;
break;
case REVENGE:
set_count.revenge ++;
break;
case DESTROY:
set_count.destroy ++;
break;
case FIGHT:
set_count.fight ++;
break;
case DETERMINATION:
set_count.determination ++;
break;
case ENHANCE:
set_count.enhance ++;
break;
case ACCURACY:
set_count.accuracy ++;
break;
case TOLERANCE:
set_count.tolerance ++;
break;
}
}
// Compare with requested sets
if (
set_count.energy >= requested_set_count.energy &&
set_count.guard >= requested_set_count.guard &&
set_count.swift >= requested_set_count.swift &&
set_count.blade >= requested_set_count.blade &&
set_count.rage >= requested_set_count.rage &&
set_count.focus >= requested_set_count.focus &&
set_count.endure >= requested_set_count.endure &&
set_count.fatal >= requested_set_count.fatal &&
set_count.despair >= requested_set_count.despair &&
set_count.vampire >= requested_set_count.vampire &&
set_count.violent >= requested_set_count.violent &&
set_count.nemesis >= requested_set_count.nemesis &&
set_count.will >= requested_set_count.will &&
set_count.shield >= requested_set_count.shield &&
set_count.revenge >= requested_set_count.revenge &&
set_count.destroy >= requested_set_count.destroy &&
set_count.fight >= requested_set_count.fight &&
set_count.determination >= requested_set_count.determination &&
set_count.enhance >= requested_set_count.enhance &&
set_count.accuracy >= requested_set_count.accuracy &&
set_count.tolerance >= requested_set_count.tolerance
){
// The current runes form a valid set.
valid_sets ++;
// Calculate new stats
struct Stats stats;
stats.hp = unit.base_hp;
stats.atk = unit.base_atk;
stats.def = unit.base_def;
stats.spd = unit.base_spd;
stats.crr = unit.base_crr;
stats.crd = unit.base_crd;
stats.res = unit.base_res;
stats.acc = unit.base_acc;
for (int i = 1; i < 7; i ++){
stats.hp += runes[i][index[i]].hp_flat;
stats.atk += runes[i][index[i]].atk_flat;
stats.def += runes[i][index[i]].def_flat;
stats.hp += unit.base_hp * runes[i][index[i]].hp_percent / 100;
stats.atk +=
unit.base_hp * runes[i][index[i]].atk_percent / 100;
stats.def +=
unit.base_hp * runes[i][index[i]].def_percent / 100;
stats.spd += runes[i][index[i]].spd;
stats.crr += runes[i][index[i]].crr;
stats.crd += runes[i][index[i]].crd;
stats.res += runes[i][index[i]].res;
stats.acc += runes[i][index[i]].acc;
}
// Compare with minimum requeriments
if (
stats.hp >= min_stats.hp &&
stats.atk >= min_stats.atk &&
stats.def >= min_stats.def &&
stats.spd >= min_stats.spd &&
stats.crr >= min_stats.crr &&
stats.crd >= min_stats.crd &&
stats.res >= min_stats.res &&
stats.acc >= min_stats.acc
){
// This is a valid sets and all stats are above the minimum.
// Create a result with rune indexes, stats, and rating.
for (int i = 1; i < 7; i ++){
strcpy(
results[result_count].rune_ids[i - 1],
runes[i][index[i]].id
);
}
results[result_count].stats.hp = stats.hp;
results[result_count].stats.atk = stats.atk;
results[result_count].stats.def = stats.def;
results[result_count].stats.spd = stats.spd;
results[result_count].stats.crr = stats.crr;
results[result_count].stats.crd = stats.crd;
results[result_count].stats.res = stats.res;
results[result_count].stats.acc = stats.acc;
// One rating point per stat increase over current stats.
// Save for HP, with takes 15 for a raing point
results[result_count].rating = 0;
results[result_count].rating += ((stats.hp - unit.current_hp) / 15);
results[result_count].rating += (stats.atk - unit.current_atk);
results[result_count].rating += (stats.def - unit.current_def);
results[result_count].rating += (stats.spd - unit.current_spd);
results[result_count].rating += (stats.crr - unit.current_crr);
results[result_count].rating += (stats.crd - unit.current_crd);
results[result_count].rating += (stats.res - unit.current_res);
results[result_count].rating += (stats.acc - unit.current_acc);
result_count ++;
}
}
// Loop control. Rotate the reels 'right to left'
tested_combinations ++;
index[6] ++;
if (index[6] == rune_count[6]){
index[6] = 0;
index[5] ++;
}
if (index[5] == rune_count[5]){
index[5] = 0;
index[4] ++;
}
if (index[4] == rune_count[4]){
index[4] = 0;
index[3] ++;
}
if (index[3] == rune_count[3]){
index[3] = 0;
index[2] ++;
}
if (index[2] == rune_count[2]){
index[2] = 0;
index[1] ++;
}
//DEBUG: force exit with some results TODO
//if (result_count > 1){
// break;
//}
}
printf("\n");
if (result_count > 0){
// Yay! Some combinations matched te criteria.
printf("\n\n%d results found\n", result_count);
// Sort results
sort_results(results, result_count);
// Preview the best option
printf("\nPresenting best option:\n\n");
printf(" --------------------------\n");
printf(" | %*s |\n", -22, unit.name);
printf(" | ID: %*s |\n", -18, unit.id);
printf(" --------------------------\n");
printf(" | STAT | CURR. | NEW |\n");
printf(" --------------------------\n");
printf(" | HP: | %*d | %*d |\n", 5, unit.current_hp, 5, results[0].stats.hp);
printf(" | ATK: | %*d | %*d |\n", 5, unit.current_atk, 5, results[0].stats.atk);
printf(" | DEF: | %*d | %*d |\n", 5, unit.current_def, 5, results[0].stats.def);
printf(" | SPD: | %*d | %*d |\n", 5, unit.current_spd, 5, results[0].stats.spd);
printf(" | CRR: | %*d\% | %*d\% |\n", 5, unit.current_crr, 5, results[0].stats.crr);
printf(" | CRD: | %*d\% | %*d\% |\n", 5, unit.current_crd, 5, results[0].stats.crd);
printf(" | RES: | %*d\% | %*d\% |\n", 5, unit.current_res, 5, results[0].stats.res);
printf(" | ACC: | %*d\% | %*d\% |\n", 5, unit.current_acc, 5, results[0].stats.acc);
printf(" --------------------------\n");
// This bit may be hard to follow.
// I'm populating 8 lines of text with data, to display the runes in
// a nice table format.
//
// This is an output exaple:
//
// ------------------------------ ------------------------------ ------------------------------
// |6|RAGE | 23285581330| |1|BLADE | 22677809846| |2|BLADE | 21432847749|
// ------------------------------ ------------------------------ ------------------------------
// | Storage | +12 | | Storage | +12 | | Perna | +15 |
// ------------------------------ ------------------------------ ------------------------------
// | ACC 48 | | ATK_FLAT 118 | | SPD 42 |
// | RES 6 | | | | |
// | ATK_FLAT 19 | | RES 14 | | CRD 16 |
// | CRR 12 | | ACC 8 | | CRR 10 |
// | HP 14 | | HP_FLAT 580 | | ATK 7 + 3 |
// | CRD 14 | | CRR 10 | | DEF 7 + 3 |
// ------------------------------ ------------------------------ ------------------------------
//
// ------------------------------ ------------------------------ ------------------------------
// |5|RAGE | 26260912967| |4|RAGE | 27947761086| |3|RAGE | 27654723287|
// ------------------------------ ------------------------------ ------------------------------
// | Lushen | +15 | | Lushen | +15 | | Covenant | +12 |
// ------------------------------ ------------------------------ ------------------------------
// | HP_FLAT 2448 | | CRD 80 | | DEF_FLAT 118 |
// | | | | | HP_FLAT 167 |
// | CRR 16 | | CRR 6 | | RES 8 |
// | SPD 6 + 2 | | RES 11 | | DEF 11 |
// | CRD 11 | | SPD 18 | | CRD 18 |
// | ATK 6 + 5 | | ATK 8 | | CRR 12 |
// ------------------------------ ------------------------------ ------------------------------
//
// Again, Im breaking the 80-characters-line rule.
char present[8][130];
strcpy(present[0], "");
strcpy(present[1], "");
strcpy(present[2], "");
strcpy(present[4], "");
strcpy(present[5], "");
strcpy(present[6], "");
strcpy(present[7], "");
// Retrieve the rune stats from the database
printf("\n");
for (int i = 6; i != 0;){
char *rune_id = results[0].rune_ids[i - 1];
sqlite3_stmt *rune_res;
char *sql =
"SELECT runes.id, runes.slot, runes.type, units.id, units.name, runes.level "
"FROM runes LEFT JOIN units ON runes.unit = units.id "
"WHERE runes.id = ?";
db_status = sqlite3_prepare_v2(db, sql, -1, &rune_res, 0);
if (db_status == SQLITE_OK) {
sqlite3_bind_text(rune_res, 1, results[0].rune_ids[i - 1], strlen(results[0].rune_ids[i - 1]), NULL);
}
else {
fprintf(stderr, "Failed to execute statement to select rune: %s\n", sqlite3_errmsg(db));
return ERROR_DB_RUNES_RESULT;
}
int step = sqlite3_step(rune_res);
if (step == SQLITE_ROW) {
char tmp[64];
strcpy(tmp, "");
strcat(present[0], "|");
sprintf(tmp, "%*d", 1, sqlite3_column_int(rune_res, 1));
strcat(present[0], tmp);
strcat(present[0], "|");
sprintf(tmp, "%*s|", -13, set_names[sqlite3_column_int(rune_res, 2)]);
strcat(present[0], tmp);
sprintf(tmp, "%*s| ", 12, sqlite3_column_text(rune_res, 0));
strcat(present[0], tmp);
strcat(present[1], "|");
if (sqlite3_column_type(rune_res, 3) == SQLITE_NULL){
sprintf(tmp, " %*s", -14, "Storage");
strcat(present[1], tmp);
strcat(present[1], "|");
}
else{
sprintf(tmp, " %*s", -14, sqlite3_column_text(rune_res, 4));
strcat(present[1], tmp);
strcat(present[1], "|");
}
// Rune level
sprintf(tmp, " +%*s | ", 2, sqlite3_column_text(rune_res, 5));
strcat(present[1], tmp);
sqlite3_stmt *stats_res;
char *sql_stats =
"SELECT rune, slot, stat, value, enchant, grind "
"FROM rune_stats "
"WHERE rune = ?";
db_status = sqlite3_prepare_v2(db, sql_stats, -1, &stats_res, 0);
if (db_status == SQLITE_OK) {
sqlite3_bind_text(stats_res, 1, sqlite3_column_text(rune_res, 0), strlen(sqlite3_column_text(rune_res, 0)), NULL);
}
else {
fprintf(stderr, "Failed to execute statement to select rune stats: %s\n", sqlite3_errmsg(db));
return ERROR_DB_STATS_RESULT;
}
int curr_slot = -1;
while (1 == 1){
int status = sqlite3_step(stats_res);
//printf("STATUS: %d ", status);
if (status == SQLITE_ROW){
// Print empty lines for no-stats
while (sqlite3_column_int(stats_res, 1) != curr_slot){
sprintf(tmp, "| | ");
strcat(present[curr_slot + 3], tmp);
curr_slot ++;
}
if (sqlite3_column_int(stats_res, 1) == curr_slot){
strcat(present[curr_slot + 3], "| ");
sprintf(tmp, "%*s", -9, stat_names[sqlite3_column_int(stats_res, 2)]);
strcat(present[curr_slot + 3], tmp);
sprintf(tmp, " %*d", 6, sqlite3_column_int(stats_res, 3));
strcat(present[curr_slot + 3], tmp);
// Display grinds
if (sqlite3_column_int(stats_res, 5) > 0){
sprintf(tmp, " + %*d", -8, sqlite3_column_int(stats_res, 5));
}
else{
sprintf(tmp, " ");
}
strcat(present[curr_slot + 3], tmp);
strcat(present[curr_slot + 3], "| ");
}
curr_slot ++;
}
else{
break;
}
}
sqlite3_finalize(stats_res);
}
else{
fprintf(stderr, "BAD ROW: %s\n", sqlite3_errmsg(db));
}
sqlite3_finalize(rune_res);
// Loop control
// It's weird, but I wanna present the runes in the same format the
// game does:
//
// 6 1 2
// 5 4 3
// After slots 2 and 3, the generated strings are written.
if (i == 6){
//printf("-6->1-");
i = 1;
}
else if (i == 1){
//printf("-1->2-");
i = 2;
}
else if (i == 2){
// Print and reinitialize the strings
printf("------------------------------ ------------------------------ ------------------------------\n");
printf(present[0]);
printf("\n------------------------------ ------------------------------ ------------------------------\n");
printf(present[1]);
printf("\n------------------------------ ------------------------------ ------------------------------\n");
printf(present[2]);
printf("\n");
printf(present[3]);
printf("\n");
printf(present[4]);
printf("\n");
printf(present[5]);
printf("\n");
printf(present[6]);
printf("\n");
printf(present[7]);
printf("\n------------------------------ ------------------------------ ------------------------------\n");
strcpy(present[0], "");
strcpy(present[1], "");
strcpy(present[2], "");
strcpy(present[3], "");
strcpy(present[4], "");
strcpy(present[5], "");
strcpy(present[6], "");
strcpy(present[7], "");
//printf("-2->5-");
i = 5;
}
else if (i == 5){
//printf("-5->4-");
i = 4;
}
else if (i == 4){
//printf("-4->3-");
i = 3;
}
else if (i == 3){
// Print and exit loop
printf("\n------------------------------ ------------------------------ ------------------------------\n");
printf(present[0]);
printf("\n------------------------------ ------------------------------ ------------------------------\n");
printf(present[1]);
printf("\n------------------------------ ------------------------------ ------------------------------\n");
printf(present[2]);
printf("\n");
printf(present[3]);
printf("\n");
printf(present[4]);
printf("\n");
printf(present[5]);
printf("\n");
printf(present[6]);
printf("\n");
printf(present[7]);
printf("\n------------------------------ ------------------------------ ------------------------------\n");
//printf("-3->0-");
i = 0;
}
}
}
else{
printf("No results found\n");
}
sqlite3_close(db);
return SUCCESS;
}
void sort_results(Result results[1000], int total){
// Bubble sort, by descending rating.
int i, j;
Result temp;
for (i = 0; i < total - 1; i++)
{
for (j = 0; j < (total - 1-i); j++)
{
if (results[j].rating < results[j + 1].rating)
{
temp = results[j];
results[j] = results[j + 1];
results[j + 1] = temp;
}
}
}
}