/* * 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; } } } }