Parcourir la source

Merge branch 'Optimizer' into master

Iñigo Valentin il y a 5 ans
Parent
commit
30961b4223

+ 13 - 2
application/Controller.php

@@ -125,8 +125,19 @@
                         action();
                         return;
                         break;
-                    case "OPTIMIZE":
-                        require_once(PATH::ACTION . "optimize.php");
+                    case "OPTIMIZE_GET_RUNE_LIST":
+                        require_once(PATH::ACTION . "optimize_get_rune_list.php");
+                        $response = action();
+                        if (strlen($response) > 1){
+                            echo($response);
+                            return;
+                        }
+                        else{
+                            return;
+                        }
+                        break;
+                    case "OPTIMIZE_GET_OPTIONS":
+                        require_once(PATH::ACTION . "optimize_get_options.php");
                         $response = action();
                         if (strlen($response) > 1){
                             echo($response);

+ 0 - 649
application/action/optimize.php

@@ -1,649 +0,0 @@
-<?php
-
-    /**
-     * File for the optimization action.
-     *
-     * Implements an action function to be called from the {@see Controller}.
-     * 
-     * @category Action
-     */
-
-    /**
-     * Executes the optimization action.
-     *
-     * Reads the POST parameters looking for the following KEYS:
-     *  mail
-     *  pass + currentPass
-     *  api
-     * Then it updates the selected info with the prameter vlue. Multiple
-     * itemas can be updated at the same time.
-     * 
-     * @return int|string 0 on success, negative values on error. If the API
-     * key has been updated, the new key.
-     * @category Action
-     * @global resource Database connection.
-     */
-    function action(){
-
-        global $db;
-        
-        $HARD_LIMIT = 100;
-
-        // Increase max execution time.
-        set_time_limit(60);
-        
-        $candidates = [
-            1 => [],
-            2 => [],
-            3 => [],
-            4 => [],
-            5 => [],
-            6 => []
-        ];
-        
-        $options = [];
-
-        $response = null;
-
-        $uid = filter_input(INPUT_POST, 'uid');
-        if ($uid == null){
-            return -1;
-        }
-
-        $unit_id = filter_input(INPUT_POST, 'unit');
-        if ($unit_id == null){
-            return -2;
-        }
-
-        // Sets
-        $sets = [];
-        foreach ($_POST["set"] as $x){
-            array_push($sets, intval($x));
-        }
-        if (sizeof($sets) < 2 || sizeof($sets) > 3){
-            return -3;
-        }
-
-        // Main stats in even slots.
-        $stats = [];
-        foreach ($_POST["stat"] as $x){
-            array_push($stats, intval($x));
-        }
-
-        $source = filter_input(INPUT_POST, 'source');
-        if ($source == null){
-            return -4;
-        }
-        
-        $limit = intval(filter_input(INPUT_POST, 'limit'));
-        if ($limit == 0){
-            return -5;
-        }
-
-        $tuning = filter_input(INPUT_POST, 'tuning');
-        if ($tuning == null){
-            return -6;
-        }
-        
-        $min = [
-            "attack" => intval(filter_input(INPUT_POST, 'min_attack')),
-            "defense" => intval(filter_input(INPUT_POST, 'min_defense')),
-            "hp" => intval(filter_input(INPUT_POST, 'min_hp')),
-            "speed" => intval(filter_input(INPUT_POST, 'min_speed')),
-            "crit_rate" => intval(filter_input(INPUT_POST, 'min_crit_rate')),
-            "crit_damage" => intval(filter_input(INPUT_POST, 'min_crit_damage')),
-            "accuracy" => intval(filter_input(INPUT_POST, 'min_accuracy')),
-            "resistance" => intval(filter_input(INPUT_POST, 'min_resistance')),
-            "ehp" => intval(filter_input(INPUT_POST, 'min_ehp')),
-            "dmg" => intval(filter_input(INPUT_POST, 'min_dmg'))
-        ];
-
-        // Get Buld query:
-        $base_s = "
-          SELECT
-            id,
-            slot
-          FROM rune
-          WHERE
-            uid = $uid AND
-            type IN (
-        ";
-        foreach ($sets as $set){
-            $base_s .= ($set . ",");
-        }
-        $base_s .= "-1) AND ";
-        switch ($source){
-            case 0: // Storage only (or itself)
-                $base_s .= "
-                  (
-                    assigned_to = $unit_id OR
-                    assigned_to IS NULL
-                  )";
-                break;
-            case 1: // Units in no teams (or itself)
-                $base_s .= "
-                  (
-                    assigned_to = $unit_id OR
-                    assigned_to IS NULL OR 
-                    assigned_to NOT IN (SELECT DISTINCT unit FROM team_unit)
-                  )
-                ";
-                break;
-            case 2: // Units in teams with 0 score (or itself)
-                $base_s .= "
-                  (
-                    assigned_to = $unit_id OR
-                    assigned_to IS NULL OR 
-                    assigned_to NOT IN (SELECT DISTINCT unit FROM team_unit) OR
-                    assigned_to NOT IN (
-                      SELECT DISTINCT unit 
-                      FROM
-                        team,
-                        team_unit
-                      WHERE
-                        team.id = team_unit.team AND
-                        team.score > 0
-                    )
-                  )
-                ";
-                break;
-            case 3: // Units with lower overall score (or itself)
-                // TODO
-                break;
-            case 4: // All runes - dont filter
-                $base_s .= " 1 = 1 ";
-                break;
-            default: // Invalid options - return nothing
-                $base_s .= " 1 = 0 ";
-        }
-        $s_order = " ORDER BY stars DESC, original_quality DESC, max_efficiency DESC, efficiency DESC, level DESC";
-
-        $s = ["", "", "", "", "", "", ""];
-        $s[1] = $base_s . " AND slot = 1 " . $s_order . " LIMIT 25";
-        $s[3] = $base_s . " AND slot = 3 " . $s_order . " LIMIT 25";
-        $s[5] = $base_s . " AND slot = 5 " . $s_order . " LIMIT 25";
-        $s[2] = $base_s . " AND slot = 2 AND main_stat = " . $stats[0] . $s_order . " LIMIT 20";
-        $s[4] = $base_s . " AND slot = 4 AND main_stat = " . $stats[1] . $s_order . " LIMIT 20";
-        $s[6] = $base_s . " AND slot = 6 AND main_stat = " . $stats[2] . $s_order . " LIMIT 20";
-
-        $total_candidates = 0;
-        for ($i = 1; $i <= 6; $i ++){
-            $q = $db->query($s[$i]);
-            while ($r = $q->fetchArray(SQLITE3_ASSOC)){
-                array_push($candidates[$i], new Rune($r["id"]));
-                $total_candidates ++;
-            }
-        }
-        $max_combinations = sizeof($candidates[1]) * sizeof($candidates[2]) * sizeof($candidates[3]) * sizeof($candidates[4]) * sizeof($candidates[5]) * sizeof($candidates[6]);
-        if ($max_combinations == 0){
-            $res = [
-                "total" => 0,
-                "options" => []
-            ];
-            $response = json_encode($res);
-            return $response;
-        }
-        elseif ($max_combinations > 125000000){
-            // This should never happen unles query limits are changed
-            http_response_code(413); // Payload too large;
-            die();
-            return;
-        }
-        
-        // Get the unit and its base stats
-        $unit = new Unit($unit_id, true, $uid);
-        $unit_attack = $unit->attack + $unit->artifact_attack + $unit->building_attack;
-        $unit_base_attack = $unit->attack;
-        $unit_defense = $unit->defense + $unit->artifact_defense + $unit->building_defense;
-        $unit_base_defense = $unit->defense;
-        $unit_hp = $unit->hp + $unit->artifact_hp + $unit->building_hp;
-        $unit_base_hp = $unit->hp;
-        $unit_speed = $unit->speed + $unit->artifact_speed + $unit->building_speed;
-        $unit_base_speed = $unit->speed;
-        $unit_crit_rate = $unit->crit_rate + $unit->artifact_crit_rate + $unit->building_crit_rate;
-        $unit_base_crit_rate = $unit->crit_rate;
-        $unit_crit_damage = $unit->crit_damage + $unit->artifact_crit_damage + $unit->building_crit_damage;
-        $unit_base_crit_damage = $unit->crit_damage;
-        $unit_accuracy = $unit->accuracy + $unit->artifact_accuracy + $unit->building_accuracy;
-        $unit_base_accuracy = $unit->accuracy;
-        $unit_resistance = $unit->resistance + $unit->artifact_resistance + $unit->building_resistance;
-        $unit_base_resistance = $unit->resistance;
-
-        $i = [0, 0, 0, 0, 0, 0, 0]; // 7, so I can start with 1
-        while (true){
-            
-            
-            // Check if set combination is valid
-            if (valid_sets($candidates, $i, $sets) == true){
-                // Do things
-                $iteration = [
-                    "attack" => $unit_attack,
-                    "defense" => $unit_defense,
-                    "hp" => $unit_hp,
-                    "speed" => $unit_speed,
-                    "crit_rate" => $unit_crit_rate,
-                    "crit_damage" => $unit_crit_damage,
-                    "accuracy" => $unit_accuracy,
-                    "resistance" => $unit_resistance,
-                    "ehp" => 0,
-                    "dmg" => 0,
-                ];
-                for ($r = 1; $r <= 6; $r ++){
-                    // Loop selected runes and calculate stats
-                    $main_value = $candidates[$r][$i[$r]]->main_stat_value;
-                    if ($tuning == 1){ // All +12
-                        if ($candidates[$r][$i[$r]]->level < 12){
-                            $main_value = $candidates[$r][$i[$r]]->get_main_stat_at_level(12);
-                        }
-                    }
-                    elseif ($tuning == 2){ // Even +15, Odd + 12
-                        if ($candidates[$r][$i[$r]]->level < 15 && $candidates[$r][$i[$r]]->slot % 2 == 0){
-                            $main_value = $candidates[$r][$i[$r]]->get_main_stat_at_level(15);
-                        }
-                        elseif ($candidates[$r][$i[$r]]->level < 12 && $candidates[$r][$i[$r]]->slot % 2 != 0){
-                            $main_value = $candidates[$r][$i[$r]]->get_main_stat_at_level(12);
-                        }
-                    }
-                    if ($tuning == 3){ // All +15
-                        if ($candidates[$r][$i[$r]]->level < 15){
-                            $main_value = $candidates[$r][$i[$r]]->get_main_stat_at_level(15);
-                        }
-                    }
-                    sum_rune_stat(
-                      $candidates[$r][$i[$r]]->main_stat,
-                      $main_value,
-                      $iteration,
-                      $unit_base_attack,
-                      $unit_base_defense,
-                      $unit_base_hp,
-                    );
-                    sum_rune_stat(
-                      $candidates[$r][$i[$r]]->innate_stat,
-                      $candidates[$r][$i[$r]]->innate_stat_value,
-                      $iteration,
-                      $unit_base_attack,
-                      $unit_base_defense,
-                      $unit_base_hp,
-                    );
-                    sum_rune_stat(
-                      $candidates[$r][$i[$r]]->substat_1,
-                      $candidates[$r][$i[$r]]->substat_1_value,
-                      $iteration,
-                      $unit_base_attack,
-                      $unit_base_defense,
-                      $unit_base_hp,
-                    );
-                    sum_rune_stat(
-                      $candidates[$r][$i[$r]]->substat_2,
-                      $candidates[$r][$i[$r]]->substat_2_value,
-                      $iteration,
-                      $unit_base_attack,
-                      $unit_base_defense,
-                      $unit_base_hp,
-                    );
-                    sum_rune_stat(
-                      $candidates[$r][$i[$r]]->substat_3,
-                      $candidates[$r][$i[$r]]->substat_3_value,
-                      $iteration,
-                      $unit_base_attack,
-                      $unit_base_defense,
-                      $unit_base_hp,
-                    );
-                    sum_rune_stat(
-                      $candidates[$r][$i[$r]]->substat_4,
-                      $candidates[$r][$i[$r]]->substat_4_value,
-                      $iteration,
-                      $unit_base_attack,
-                      $unit_base_defense,
-                      $unit_base_hp,
-                    );
-                }
-                
-                // Calculate effective_hp
-                $hp = $iteration["hp"];
-                $def = $iteration["defense"];
-                $ehp = ceil(((($def * 3.5) + 1140) * $hp) / 1000);
-                $iteration["ehp"] = $ehp;
-
-                // Calculate effective_dmg
-                $atk = $iteration["attack"];
-                $crr = $iteration["crit_rate"];
-                if ($crr > 100){
-                    $crr = 100;
-                }
-                $crd = $iteration["crit_damage"];
-                $edmg = ceil(($atk * (100 - $crr) / 100) + (($atk + ($atk * $crd / 100)) * $crr / 100));
-                $iteration["dmg"] = $edmg;
-
-                // Compare with filters:
-                if (
-                  $iteration["attack"] >= $min["attack"] &&
-                  $iteration["defense"] >= $min["defense"] &&
-                  $iteration["hp"] >= $min["hp"] &&
-                  $iteration["speed"] >= $min["speed"] &&
-                  $iteration["crit_rate"] >= $min["crit_rate"] &&
-                  $iteration["crit_damage"] >= $min["crit_damage"] &&
-                  $iteration["accuracy"] >= $min["accuracy"] &&
-                  $iteration["resistance"] >= $min["resistance"] &&
-                  $iteration["ehp"] >= $min["ehp"] &&
-                  $iteration["dmg"] >= $min["dmg"]
-                ){
-
-                    // Calculate the variation
-                    $variation = 0;
-                    $variation += (($iteration["attack"] - $unit->total_attack) * 1);
-                    $variation += (($iteration["defense"] - $unit->total_defense) * 1);
-                    $variation += (($iteration["hp"] - $unit->total_hp) * (1 / 15));
-                    $variation += (($iteration["speed"] - $unit->total_speed) * 1.5);
-                    $variation += (($iteration["crit_rate"] - $unit->total_crit_rate) * 0.85);
-                    $variation += (($iteration["crit_damage"] - $unit->total_crit_damage) * 0.9);
-                    $variation += (($iteration["accuracy"] - $unit->total_accuracy) * 0.85);
-                    $variation += (($iteration["resistance"] - $unit->total_resistance) * 0.85);
-                    $variation = ceil($variation);
-
-                    // Calculate efficiency
-                    $efficiency = 0;
-                    
-                    // Build the array
-                    $option = [
-                        "variation" => $variation,
-                        "runes" => [
-                            [
-                                "id" => $candidates[1][$i[1]]->id,
-                                "html" => (HTML::rune_table($candidates[1][$i[1]]))
-                            ],
-                            [
-                                "id" => $candidates[2][$i[2]]->id,
-                                "html" => (HTML::rune_table($candidates[2][$i[2]]))
-                            ],
-                            [
-                                "id" => $candidates[3][$i[3]]->id,
-                                "html" => (HTML::rune_table($candidates[3][$i[3]]))
-                            ],
-                            [
-                                "id" => $candidates[4][$i[4]]->id,
-                                "html" => (HTML::rune_table($candidates[4][$i[4]]))
-                            ],
-                            [
-                                "id" => $candidates[5][$i[5]]->id,
-                                "html" => (HTML::rune_table($candidates[5][$i[5]]))
-                            ],
-                            [
-                                "id" => $candidates[1][$i[1]]->id,
-                                "html" => HTML::rune_table($candidates[6][$i[6]])
-                            ]
-                        ],
-                        "stats" => [
-                            "attack" => ceil($iteration["attack"]),
-                            "defense" => ceil($iteration["defense"]),
-                            "hp" => ceil($iteration["hp"]),
-                            "speed" => ceil($iteration["speed"]),
-                            "crit_rate" => ceil($iteration["crit_rate"]),
-                            "crit_damage" => ceil($iteration["crit_damage"]),
-                            "accuracy" => ceil($iteration["accuracy"]),
-                            "attack" => ceil($iteration["attack"]),
-                            "resistance" => ceil($iteration["resistance"]),
-                            "ehp" => ceil($iteration["ehp"]),
-                            "dmg" => ceil($iteration["dmg"])
-                        ]
-                    ];
-                    array_push($options, $option);
-                    if (sizeof($options) > 5000){
-                        http_response_code(413); // Payload too large;
-                        die();
-                        return;
-                    }
-                }
-
-            }
-
-            // Increase counters
-            $i[6] ++;
-            if ($i[6] == sizeof($candidates[6])){
-                $i[6] = 0;
-                $i[5] ++;
-            }
-            if ($i[5] == sizeof($candidates[5])){
-                $i[5] = 0;
-                $i[4] ++;
-            }
-            if ($i[4] == sizeof($candidates[4])){
-                $i[4] = 0;
-                $i[3] ++;
-            }
-            if ($i[3] == sizeof($candidates[3])){
-                $i[3] = 0;
-                $i[2] ++;
-            }
-            if ($i[2] == sizeof($candidates[2])){
-                $i[2] = 0;
-                $i[1] ++;
-            }
-
-            // Calculate exit condition
-            if (
-                $i[1] >= sizeof($candidates[1]) - 1 &&
-                $i[2] >= sizeof($candidates[2]) - 1 &&
-                $i[3] >= sizeof($candidates[3]) - 1 &&
-                $i[4] >= sizeof($candidates[4]) - 1 &&
-                $i[5] >= sizeof($candidates[5]) - 1 &&
-                $i[6] >= sizeof($candidates[6]) - 1
-            ){
-                break;
-            }
-        }
-
-        // Sort options by variation
-        usort($options, "sortOptions");
-
-        // Limit the array to the top options
-        $original_options_size = sizeof($options);
-        while (sizeof($options) > $limit || sizeof($options) > $HARD_LIMIT){
-            array_pop($options);
-        }
-        
-        
-        $res = [
-            "total" => sizeof($options),
-            "skipped" => ($original_options_size - sizeof($options)),
-            "options" => $options
-        ];
-        
-        $response = json_encode($res);
-        if ($response == null){
-            return 0;
-        }
-        else{
-            return $response;
-        }
-    }
-    
-    /**
-     * Validates a rune combination.
-     *
-     * Checks that set numbers are OK.
-     *
-     * @param \Rune[][] $candidates Candidate runes.
-     * @param int[] $indexes Currently selected indexes.
-     */
-    function valid_sets($candidates, $indexes, $requested){
-        
-        $sets = [
-            RUNE_SET_ID::ENERGY => 0,
-            RUNE_SET_ID::GUARD => 0,
-            RUNE_SET_ID::SWIFT => 0,
-            RUNE_SET_ID::BLADE => 0,
-            RUNE_SET_ID::RAGE => 0,
-            RUNE_SET_ID::FOCUS => 0,
-            RUNE_SET_ID::ENDURE => 0,
-            RUNE_SET_ID::FATAL => 0,
-            RUNE_SET_ID::DESPAIR => 0,
-            RUNE_SET_ID::VAMPIRE => 0,
-            RUNE_SET_ID::VIOLENT => 0,
-            RUNE_SET_ID::NEMESIS => 0,
-            RUNE_SET_ID::WILL => 0,
-            RUNE_SET_ID::SHIELD => 0,
-            RUNE_SET_ID::REVENGE => 0,
-            RUNE_SET_ID::DESTROY => 0,
-            RUNE_SET_ID::FIGHT => 0,
-            RUNE_SET_ID::DETERMINATION => 0,
-            RUNE_SET_ID::ENHANCE => 0,
-            RUNE_SET_ID::ACCURACY => 0,
-            RUNE_SET_ID::TOLERANCE => 0
-        ];
-        for ($i = 1; $i <= 6; $i++){
-            $sets[$candidates[$i][$indexes[$i]]->type->id] ++;
-        }
-        for ($i = 0; $i < sizeof($sets); $i ++){
-            switch ($i){
-                case RUNE_SET_ID::ENERGY:
-                case RUNE_SET_ID::GUARD:
-                case RUNE_SET_ID::NEMESIS:
-                case RUNE_SET_ID::BLADE:
-                case RUNE_SET_ID::FOCUS:
-                case RUNE_SET_ID::ENDURE:
-                case RUNE_SET_ID::WILL:
-                case RUNE_SET_ID::SHIELD:
-                case RUNE_SET_ID::REVENGE:
-                case RUNE_SET_ID::DESTROY:
-                case RUNE_SET_ID::FIGHT:
-                case RUNE_SET_ID::DETERMINATION:
-                case RUNE_SET_ID::ENHANCE:
-                case RUNE_SET_ID::ACCURACY:
-                case RUNE_SET_ID::TOLERANCE:
-                    if ($sets[$i] % 2 != 0){
-                        return false;
-                    }
-                    break;
-                
-                case RUNE_SET_ID::DESPAIR:
-                case RUNE_SET_ID::SWIFT:
-                case RUNE_SET_ID::RAGE:
-                case RUNE_SET_ID::FATAL:
-                case RUNE_SET_ID::VAMPIRE:
-                case RUNE_SET_ID::VIOLENT:
-                    if ($sets[$i] != 4 && $sets[$i != 0]){
-                        return false;
-                    }
-                    break;
-            }
-        }
-
-        // Sets are valid, now check if they comply with filters
-        $requested_sets = [
-            RUNE_SET_ID::ENERGY => 0,
-            RUNE_SET_ID::GUARD => 0,
-            RUNE_SET_ID::SWIFT => 0,
-            RUNE_SET_ID::BLADE => 0,
-            RUNE_SET_ID::RAGE => 0,
-            RUNE_SET_ID::FOCUS => 0,
-            RUNE_SET_ID::ENDURE => 0,
-            RUNE_SET_ID::FATAL => 0,
-            RUNE_SET_ID::DESPAIR => 0,
-            RUNE_SET_ID::VAMPIRE => 0,
-            RUNE_SET_ID::VIOLENT => 0,
-            RUNE_SET_ID::NEMESIS => 0,
-            RUNE_SET_ID::WILL => 0,
-            RUNE_SET_ID::SHIELD => 0,
-            RUNE_SET_ID::REVENGE => 0,
-            RUNE_SET_ID::DESTROY => 0,
-            RUNE_SET_ID::FIGHT => 0,
-            RUNE_SET_ID::DETERMINATION => 0,
-            RUNE_SET_ID::ENHANCE => 0,
-            RUNE_SET_ID::ACCURACY => 0,
-            RUNE_SET_ID::TOLERANCE => 0
-        ];
-        for ($i = 0; $i < sizeof($requested); $i ++){
-            switch ($requested[$i]){
-                case RUNE_SET_ID::ENERGY:
-                case RUNE_SET_ID::GUARD:
-                case RUNE_SET_ID::NEMESIS:
-                case RUNE_SET_ID::BLADE:
-                case RUNE_SET_ID::FOCUS:
-                case RUNE_SET_ID::ENDURE:
-                case RUNE_SET_ID::WILL:
-                case RUNE_SET_ID::SHIELD:
-                case RUNE_SET_ID::REVENGE:
-                case RUNE_SET_ID::DESTROY:
-                case RUNE_SET_ID::FIGHT:
-                case RUNE_SET_ID::DETERMINATION:
-                case RUNE_SET_ID::ENHANCE:
-                case RUNE_SET_ID::ACCURACY:
-                case RUNE_SET_ID::TOLERANCE:
-                    $requested_sets[$requested[$i]] += 2;
-                    break;
-                case RUNE_SET_ID::DESPAIR:
-                case RUNE_SET_ID::SWIFT:
-                case RUNE_SET_ID::RAGE:
-                case RUNE_SET_ID::FATAL:
-                case RUNE_SET_ID::VAMPIRE:
-                case RUNE_SET_ID::VIOLENT:
-                    $requested_sets[$requested[$i]] += 4;
-                    break;
-            }
-        }
-
-        if ($sets === $requested_sets){
-            return true;
-        }
-        else{
-            return false;
-        }
-    }
-
-    /**
-     * Calculates the stat increase by a rune property.
-     * 
-     * @param string $stat Stat type.
-     * @param int $value Stat increase value.
-     * @param int[] Reference to iteration stats array.
-     */
-    function sum_rune_stat($stat, $value, &$iteration, $base_atk, $base_def, $base_hp){
-        switch ($stat){
-            case RUNE_STAT_ID::ATK:
-                $iteration["attack"] += $value;
-                break;
-            case RUNE_STAT_ID::DEF:
-                $iteration["defense"] += $value;
-                break;
-            case RUNE_STAT_ID::HP:
-                $iteration["hp"] += $value;
-                break;
-            case RUNE_STAT_ID::SPD:
-                $iteration["speed"] += $value;
-                break;
-            case RUNE_STAT_ID::CRR:
-                $iteration["crit_rate"] += $value;
-                break;
-            case RUNE_STAT_ID::CRD:
-                $iteration["crit_damage"] += $value;
-                break;
-            case RUNE_STAT_ID::ACC:
-                $iteration["accuracy"] += $value;
-                break;
-            case RUNE_STAT_ID::RES:
-                $iteration["resistance"] += $value;
-                break;
-            case RUNE_STAT_ID::ATK_P:
-                $iteration["attack"] += ($base_atk * $value / 100);
-                break;
-            case RUNE_STAT_ID::DEF_P:
-                $iteration["defense"] += ($base_def * $value / 100);
-                break;
-            case RUNE_STAT_ID::HP_P:
-                $iteration["hp"] += ($base_hp * $value / 100);
-                break;
-        }
-    }
-    
-    /**
-     * Function to sort arrays based on the "increment" key.
-     *
-     * @param $a An array
-     * @param $b Other array
-     * @return int Positive if $b before $a, negative otherwise. 
-     */
-    function sortOptions($a, $b) {
-        return $b["variation"] - $a["variation"];
-    }
-?>

+ 184 - 0
application/action/optimize_get_options.php

@@ -0,0 +1,184 @@
+<?php
+
+    /**
+     * File for the optimization action.
+     *
+     * Implements an action function to be called from the {@see Controller}.
+     * 
+     * @category Action
+     */
+
+    /**
+     * Executes the optimization action.
+     *
+     * Reads the POST parameters looking for the following KEYS:
+     *  mail
+     *  pass + currentPass
+     *  api
+     * Then it updates the selected info with the prameter vlue. Multiple
+     * itemas can be updated at the same time.
+     * 
+     * @return int|string 0 on success, negative values on error. If the API
+     * key has been updated, the new key.
+     * @category Action
+     * @global resource Database connection.
+     */
+    function action(){
+
+        global $db;
+
+        // Increase max execution time.
+        set_time_limit(60);
+        
+        $runes = [];
+
+        $response = null;
+
+        $uid = filter_input(INPUT_POST, 'uid');
+        if ($uid == null){
+            return -1;
+        }
+
+        $unit_id = filter_input(INPUT_POST, 'unit');
+        if ($unit_id == null){
+            return -2;
+        }
+        $unit = new Unit($unit_id, true, $uid);
+
+        $tuning = filter_input(INPUT_POST, 'tuning');
+        if ($tuning == null){
+            return -1;
+        }
+
+        $options = json_decode(filter_input(INPUT_POST, 'options'), true);
+        if ($options == null){
+            return -2;
+        }
+        foreach ($options as $option){
+            $set = [
+                "runes" => [],
+                "stats" => [
+                    "attack" => $unit->attack,
+                    "defense" => $unit->defense,
+                    "hp" => $unit->hp,
+                    "speed" => $unit->speed,
+                    "crit_rate" => $unit->crit_rate,
+                    "crit_damage" => $unit->crit_damage,
+                    "accuracy" => $unit->accuracy,
+                    "resistance" => $unit->resistance,
+                    "ehp" => 0,
+                    "dmg" => 0
+                ]
+            ];
+            foreach ($option["runes"] as $rune_option){
+                $rune = new Rune($rune_option["ID"]);
+                $r = [
+                    "id" => $rune_option["ID"],
+                    "html" => HTML::rune_table($rune)
+                ];
+                
+                // Calculate stat increment
+                // TODO: Main stat at level!
+                $stat_ids = [
+                    $rune->main_stat,
+                    $rune->innate_stat,
+                    $rune->substat_1,
+                    $rune->substat_2,
+                    $rune->substat_3,
+                    $rune->substat_4
+                ];
+                $main_value = $rune->main_stat_value;
+                switch ($tuning){
+                    case 1: // All +12
+                        if ($rune->level < 12){
+                            $main_value = $rune->get_main_stat_at_level(12);
+                        }
+                        break;
+                    case 2: // Even +15, Odd + 12
+                        if ($rune->level < 15 && $rune->slot % 2 == 0){
+                            $main_value = $rune->get_main_stat_at_level(15);
+                        }
+                        elseif ($rune->level < 12 && $rune->slot % 2 != 0){
+                            $main_value = $rune->get_main_stat_at_level(12);
+                        }
+                        break;
+                    case 3: // All +15
+                        if ($rune->level < 15){
+                            $main_value = $rune->get_main_stat_at_level(15);
+                        }
+                        break;
+                }
+                $stat_values = [
+                    $rune->main_stat_value,
+                    $rune->innate_stat_value,
+                    $rune->substat_1_value + $rune->substat_1_craft,
+                    $rune->substat_2_value + $rune->substat_2_craft,
+                    $rune->substat_3_value + $rune->substat_3_craft,
+                    $rune->substat_4_value + $rune->substat_4_craft
+                ];
+                for ($i = 0; $i < 6; $i ++){
+                    switch ($stat_ids[$i]){
+                        case RUNE_STAT_ID::ATK:
+                            $set["stats"]["attack"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::ATK_P:
+                            $set["stats"]["attack"] += ceil($unit->attack * $stat_values[$i] / 100);
+                            break;
+                        case RUNE_STAT_ID::DEF:
+                            $set["stats"]["defense"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::DEF_P:
+                            $set["stats"]["defense"] += ceil($unit->defense * $stat_values[$i] / 100);
+                            break;
+                        case RUNE_STAT_ID::HP:
+                            $set["stats"]["hp"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::HP_P:
+                            $set["stats"]["hp"] += ceil($unit->hp * $stat_values[$i] / 100);
+                            break;
+                        case RUNE_STAT_ID::SPD:
+                            $set["stats"]["speed"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::CRR:
+                            $set["stats"]["crit_rate"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::CRD:
+                            $set["stats"]["crit_damage"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::ACC:
+                            $set["stats"]["accuracy"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::CRR:
+                            $set["stats"]["resistance"] += $stat_values[$i];
+                            break;
+                    }
+                }
+
+                // Calculate EHP and DMG
+                $set["stats"]["ehp"] = ceil(((($set["stats"]["def"] * 3.5) + 1140) * $set["stats"]["hp"]) / 1000);
+                $atk = $set["stats"]["attack"];
+                $crr = $set["stats"]["crit_rate"];
+                if ($crr > 100){
+                    $crr = 100;
+                }
+                $crd = $set["stats"]["crit_damage"];
+                $edmg = ceil(($atk * (100 - $crr) / 100) + (($atk + ($atk * $crd / 100)) * $crr / 100));
+                $set["stats"]["dmg"] = $edmg;
+
+                // Add to the array
+                array_push($set["runes"], $r);
+                
+            }
+            array_push($runes, $set);
+        }
+
+        // Send back the response
+        $response = json_encode($runes);
+        if ($response == null){
+            return 0;
+        }
+        else{
+            return $response;
+        }
+    }
+?>

+ 361 - 0
application/action/optimize_get_rune_list.php

@@ -0,0 +1,361 @@
+<?php
+
+    /**
+     * File for the optimization action.
+     *
+     * Implements an action function to be called from the {@see Controller}.
+     * 
+     * @category Action
+     */
+
+    /**
+     * Executes the optimization action.
+     *
+     * Reads the POST parameters looking for the following KEYS:
+     *  mail
+     *  pass + currentPass
+     *  api
+     * Then it updates the selected info with the prameter vlue. Multiple
+     * itemas can be updated at the same time.
+     * 
+     * @return int|string 0 on success, negative values on error. If the API
+     * key has been updated, the new key.
+     * @category Action
+     * @global resource Database connection.
+     */
+    function action(){
+
+        global $db;
+        
+        $HARD_LIMIT = 100;
+
+        // Increase max execution time.
+        set_time_limit(60);
+        
+        $candidates = [
+            1 => [],
+            2 => [],
+            3 => [],
+            4 => [],
+            5 => [],
+            6 => []
+        ];
+        
+        $options = [];
+
+        $response = null;
+
+        $uid = filter_input(INPUT_POST, 'uid');
+        if ($uid == null){
+            return -1;
+        }
+
+        $unit_id = filter_input(INPUT_POST, 'unit');
+        if ($unit_id == null){
+            return -2;
+        }
+
+        // Sets
+        $sets = [];
+        foreach ($_POST["set"] as $x){
+            array_push($sets, intval($x));
+        }
+        if (sizeof($sets) < 2 || sizeof($sets) > 3){
+            return -3;
+        }
+
+        // Main stats in even slots.
+        $stats = [];
+        foreach ($_POST["stat"] as $x){
+            array_push($stats, intval($x));
+        }
+
+        $source = filter_input(INPUT_POST, 'source');
+        if ($source == null){
+            return -4;
+        }
+        
+        $limit = intval(filter_input(INPUT_POST, 'limit'));
+        if ($limit == 0){
+            return -5;
+        }
+
+        $tuning = filter_input(INPUT_POST, 'tuning');
+        if ($tuning == null){
+            return -6;
+        }
+        
+        $min = [
+            "attack" => intval(filter_input(INPUT_POST, 'min_attack')),
+            "defense" => intval(filter_input(INPUT_POST, 'min_defense')),
+            "hp" => intval(filter_input(INPUT_POST, 'min_hp')),
+            "speed" => intval(filter_input(INPUT_POST, 'min_speed')),
+            "crit_rate" => intval(filter_input(INPUT_POST, 'min_crit_rate')),
+            "crit_damage" => intval(filter_input(INPUT_POST, 'min_crit_damage')),
+            "accuracy" => intval(filter_input(INPUT_POST, 'min_accuracy')),
+            "resistance" => intval(filter_input(INPUT_POST, 'min_resistance')),
+            "ehp" => intval(filter_input(INPUT_POST, 'min_ehp')),
+            "dmg" => intval(filter_input(INPUT_POST, 'min_dmg'))
+        ];
+
+        // Instantiate the unit
+        $unit = new Unit($unit_id, true, $uid);
+
+        // Get build query:
+        $base_s = "
+          SELECT
+            id,
+            type,
+            slot,
+            level,
+            stars,
+            main_stat,
+            main_stat_value,
+            innate_stat,
+            innate_stat_value,
+            substat_1,
+            substat_1_value + substat_1_grind AS substat_1_value,
+            substat_2,
+            substat_2_value + substat_2_grind AS substat_2_value,
+            substat_3,
+            substat_3_value + substat_3_grind AS substat_3_value,
+            substat_4,
+            substat_4_value + substat_4_grind AS substat_4_value
+          FROM rune
+          WHERE
+            uid = $uid AND
+            type IN (
+        ";
+        foreach ($sets as $set){
+            $base_s .= ($set . ",");
+        }
+        $base_s .= "-1) AND ";
+        switch ($source){
+            case 0: // Storage only (or itself)
+                $base_s .= "
+                  (
+                    assigned_to = $unit_id OR
+                    assigned_to IS NULL
+                  )";
+                break;
+            case 1: // Units in no teams (or itself)
+                $base_s .= "
+                  (
+                    assigned_to = $unit_id OR
+                    assigned_to IS NULL OR 
+                    assigned_to NOT IN (SELECT DISTINCT unit FROM team_unit)
+                  )
+                ";
+                break;
+            case 2: // Units in teams with 0 score (or itself)
+                $base_s .= "
+                  (
+                    assigned_to = $unit_id OR
+                    assigned_to IS NULL OR 
+                    assigned_to NOT IN (SELECT DISTINCT unit FROM team_unit) OR
+                    assigned_to NOT IN (
+                      SELECT DISTINCT unit 
+                      FROM
+                        team,
+                        team_unit
+                      WHERE
+                        team.id = team_unit.team AND
+                        team.score > 0
+                    )
+                  )
+                ";
+                break;
+            case 3: // Units with lower overall score (or itself)
+                // TODO
+                break;
+            case 4: // All runes - dont filter
+                $base_s .= " 1 = 1 ";
+                break;
+            default: // Invalid options - return nothing
+                $base_s .= " 1 = 0 ";
+        }
+        $s_order = " ORDER BY stars DESC, original_quality DESC, max_efficiency DESC, efficiency DESC, level DESC";
+        $s_in = " main_stat IN (" . $stats[0] . ", " . $stats[1] . ", " . $stats[2] . ") ";
+
+        $s = ["", "", "", "", "", "", ""];
+        $s[1] = $base_s . " AND slot = 1 " . $s_order;
+        $s[3] = $base_s . " AND slot = 3 " . $s_order;
+        $s[5] = $base_s . " AND slot = 5 " . $s_order;
+        $s[2] = $base_s . " AND slot = 2 AND " . $s_in . $s_order;
+        $s[4] = $base_s . " AND slot = 4 AND " . $s_in . $s_order;
+        $s[6] = $base_s . " AND slot = 6 AND " . $s_in . $s_order;
+
+        $total_candidates = 0;
+        for ($i = 1; $i <= 6; $i ++){
+            $q = $db->query($s[$i]);
+            while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+                $rune = create_rune_array($unit, $r, $tuning);
+                //array_push($candidates[$i], new Rune($r["id"]));
+                array_push($candidates[$i], $rune);
+                $total_candidates ++;
+            }
+        }
+        $max_combinations = sizeof($candidates[1]) * sizeof($candidates[2]) * sizeof($candidates[3]) * sizeof($candidates[4]) * sizeof($candidates[5]) * sizeof($candidates[6]);
+        
+        // TODO: Build response
+        $response = json_encode($candidates);
+        if ($response == null){
+            return 0;
+        }
+        else{
+            return $response;
+        }
+    }
+
+    /**
+     * Creates an array with the stats provided by a rune on a unit.
+     *
+     * @param Unit $unit Target unit.
+     * @param SQLResult $r Rune main stat ID.
+     * @param int $stars Rune stars.
+     * @return int Value of the main stat at the selected level.
+     */
+    function create_rune_array($unit, $r, $tuning){
+        $rune = [
+            "ID" => $r["id"],
+            "TYPE" => $r["type"],
+            "ATK" => 0,
+            "DEF" => 0,
+            "HP" => 0,
+            "SPD" => 0,
+            "CRR" => 0,
+            "CRD" => 0,
+            "ACC" => 0,
+            "RES" => 0
+        ];
+        // Calculate main stat value based on requested level.
+        $level = $r["level"];
+        $main_value = $r["main_stat_value"];
+        switch ($tuning){
+            case 1: // All +12
+                if ($level < 12){
+                    $main_value = get_main_stat_at_level(12, $r["main_stat"], $r["stars"]);
+                }
+                break;
+            case 2: // Even +15, Odd + 12
+                if ($level < 15 && $r["slot"] % 2 == 0){
+                    $main_value = get_main_stat_at_level(15, $r["main_stat"], $r["stars"]);
+                }
+                elseif ($level < 12 && $r["slot"] % 2 != 0){
+                    $main_value = get_main_stat_at_level(12, $r["main_stat"], $r["stars"]);
+                }
+                break;
+            case 3: // All +15
+                if ($level < 15){
+                    $main_value = get_main_stat_at_level(15, $r["main_stat"], $r["stars"]);
+                }
+                break;
+        }
+        // Array to loop.
+        $stats = [
+            [
+                "STAT" => $r["main_stat"],
+                "VALUE" => $main_value
+            ],
+            [
+                "STAT" => $r["innate_stat"],
+                "VALUE" => $r["innate_stat_value"]
+            ],
+            [
+                "STAT" => $r["substat_1"],
+                "VALUE" => $r["substat_1_value"]
+            ],
+            [
+                "STAT" => $r["substat_2"],
+                "VALUE" => $r["substat_2_value"]
+            ],
+            [
+                "STAT" => $r["substat_3"],
+                "VALUE" => $r["substat_3_value"]
+            ],
+            [
+                "STAT" => $r["substat_4"],
+                "VALUE" => $r["substat_4_value"]
+            ]
+        ];
+        foreach ($stats as $stat){
+            switch ($stat["STAT"]){
+                case RUNE_STAT_ID::ATK:
+                    $rune["ATK"] += $stat["VALUE"];
+                    break;
+                case RUNE_STAT_ID::ATK_P:
+                    $rune["ATK"] += ($unit->attack * $stat["VALUE"] / 100);
+                    break;
+                case RUNE_STAT_ID::DEF:
+                    $rune["DEF"] += $stat["VALUE"];
+                    break;
+                case RUNE_STAT_ID::DEF_P:
+                    $rune["DEF"] += ($unit->defense * $stat["VALUE"] / 100);
+                    break;
+                case RUNE_STAT_ID::HP:
+                    $rune["HP"] += $stat["VALUE"];
+                    break;
+                case RUNE_STAT_ID::HP_P:
+                    $rune["HP"] += ($unit->hp * $stat["VALUE"] / 100);
+                    break;
+                case RUNE_STAT_ID::SPD:
+                    $rune["SPD"] += $stat["VALUE"];
+                    break;
+                case RUNE_STAT_ID::CRR:
+                    $rune["CRR"] += $stat["VALUE"];
+                    break;
+                case RUNE_STAT_ID::CRD:
+                    $rune["CRD"] += $stat["VALUE"];
+                    break;
+                case RUNE_STAT_ID::ACC:
+                    $rune["ACC"] += $stat["VALUE"];
+                    break;
+                case RUNE_STAT_ID::RES:
+                    $rune["RES"] += $stat["VALUE"];
+                    break;
+            }
+        }
+        return $rune;
+    }
+
+    /**
+     * Gets the value of the main stat at an arbitrary level.
+     *
+     * @param int $level Desired level.
+     * @param int $stat Rune main stat ID.
+     * @param int $stars Rune stars.
+     * @return int Value of the main stat at the selected level.
+     */
+    function get_main_stat_at_level($level, $stat, $stars){
+        $level = intval($level);
+        if ($level < 0 || $level > 15){
+            return 0;
+        }
+        switch ($stat){
+            case RUNE_STAT_ID::HP:
+                return RUNE_STAT_VALUES::HP[$stars][$level];
+            case RUNE_STAT_ID::HP_P:
+                return RUNE_STAT_VALUES::HP_P[$stars][$level];
+            case RUNE_STAT_ID::ATK:
+                return RUNE_STAT_VALUES::ATK[$stars][$level];
+            case RUNE_STAT_ID::ATK_P:
+                return RUNE_STAT_VALUES::ATK_P[$stars][$level];
+            case RUNE_STAT_ID::DEF:
+                return RUNE_STAT_VALUES::DEF[$stars][$level];
+            case RUNE_STAT_ID::DEF_P:
+                return RUNE_STAT_VALUES::DEF_P[$stars][$level];
+            case RUNE_STAT_ID::SPD:
+                return RUNE_STAT_VALUES::SPD[$stars][$level];
+            case RUNE_STAT_ID::CRR:
+                return RUNE_STAT_VALUES::CRR[$stars][$level];
+            case RUNE_STAT_ID::CRD:
+                return RUNE_STAT_VALUES::CRD[$stars][$level];
+            case RUNE_STAT_ID::RES:
+                return RUNE_STAT_VALUES::RES[$stars][$level];
+            case RUNE_STAT_ID::ACC:
+                return RUNE_STAT_VALUES::ACC[$stars][$level];
+            default:
+                return 0;
+        }
+    }
+?>

+ 463 - 45
application/view/optimizer_unit.php

@@ -54,6 +54,19 @@
                 "ehp": <?=$page->unit->effective_hp?>,
                 "dmg": <?=$page->unit->effective_dmg?>
             }
+            
+            var base_stats = {
+                "attack": <?=$page->unit->attack?>,
+                "defense": <?=$page->unit->defense?>,
+                "hp": <?=$page->unit->hp?>,
+                "speed": <?=$page->unit->speed?>,
+                "crit_rate": <?=$page->unit->crit_rate?>,
+                "crit_damage": <?=$page->unit->crit_damage?>,
+                "accuracy": <?=$page->unit->accuracy?>,
+                "resistance": <?=$page->unit->resistance?>,
+                "ehp": <?=$page->unit->effective_hp?>,
+                "dmg": <?=$page->unit->effective_dmg?>
+            }
 
 <?php
             // Get current rune sets
@@ -83,6 +96,25 @@
              * To calculate time.
              */
             var timestamp = 0;
+            
+            /**
+             * Enables or disables a new search.
+             *
+             * When disabled, the apply button will be disabled and a waiting
+             * icon will be shown.
+             *
+             * @param enable true to enable, false to disable.
+             */
+            function enableNewSearch(enable){
+                if (enable === true){
+                    document.getElementById('apply').removeAttribute('disabled');
+                    document.getElementById('wait').style.display = 'none';
+                }
+                else{
+                    document.getElementById('apply').setAttribute('disabled', 'disabled');
+                    document.getElementById('wait').style.display = 'block';
+                }
+            }
 
             /**
              * Updates the value next to the sliders when they change.
@@ -124,17 +156,33 @@
                 document.getElementById('min_' + stat).value = document.getElementById('min_' + stat).min;
             }
 
+            function log(text){
+                var log_window = document.getElementById('log_window');
+                var span = document.createElement('span');
+                span.setAttribute('class', 'log_message');
+                text = document.createTextNode(text);
+                span.appendChild(text);
+                log_window.appendChild(span);
+                log_window.scrollTo(0, 2 * log_window.offsetHeight);
+            }
+            function clearLog(){
+                const elements = document.querySelectorAll('.log_message');
+                Array.from(elements).forEach((element, index) => {
+                    element.remove(); 
+                });
+            }
+            
             /**
              * Starts the optimization.
              *
              * Checks for error in the combination of the parameters, and
              * shows an error window if there is an invalid conbination. If
-             * not, builds the url to calculate the new rune combinations,
-             * and presents th received response.
+             * not, builds the url to fetch all candidate runes.
              *
              * @return false on error, true on success.
              */
             function apply(){
+                log("Start optimization...");
 
                 var params = '';
                 var x;
@@ -229,17 +277,16 @@
                 var xhttp = new XMLHttpRequest();
                 xhttp.onreadystatechange = function() {
                     if (this.readyState == 4){
-                        document.getElementById('apply').removeAttribute('disabled');
-                        document.getElementById('wait').style.display = 'none';
                         if (this.status == 200) {
                             time = ((+ new Date()) - timestamp) / 1000;
-                            showOptimizations(this.response, time);
+                            calculateOptimizations(this.response, time);
                         }
-                        else if (this.status == 413) { // Payload too large
-                            showMessage('Error', 'Too many combination found. Plesase narow your criteria and retry.');
-                        }
-                        else if (this.status == 500) { // Server error
-                            showMessage('Error', 'Too many rune combination found. Plesase narow your criteria and retry.');
+                        else{
+                            enableNewSearch(true);
+                            var time = ((+ new Date()) - timestamp) / 1000;
+                            log('Error ' + this.status + '(' + time + 's)');
+                            
+                            showMessage('Error', 'Unexpected error ' + this.status);
                         }
                     }
                 };
@@ -253,35 +300,371 @@
                 while (container.firstChild) {
                     container.removeChild(container.firstChild);
                 }
-                document.getElementById('apply').setAttribute('disabled', 'disabled');
-                document.getElementById('wait').style.display = 'block';
+                enableNewSearch(false);
                 document.getElementById('results').style.display = 'none';
 
                 // Start timer
                 timestamp = + new Date();
 
                 // Make the request
-                xhttp.open('POST', '/action/optimize/', true);
+                xhttp.open('POST', '/action/optimize_get_rune_list/', true);
                 xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                 xhttp.send(params);
             }
 
             /**
-             * Parses the received content and creates the HTML with the new
-             * rune options.
+             * Checks if a rune combination is a valid set. To do so, checks:
+             *
+             * - That the sets are valid
+             * - That the sets are the same as requested
+             * - That it complies with the filters min values.
+             *
+             * @param candidates Array of 6 runes
+             * @return true if the set matches the criteria, false otherwise.
+             */
+            function valid_set(candidates){
+
+                // Number of applied sets with the current candidates.
+                var sets = {
+                    'RUNE_SET_<?=RUNE_SET_ID::ENERGY?>': 0, // ENERGY
+                    'RUNE_SET_<?=RUNE_SET_ID::GUARD?>': 0, // GUARD
+                    'RUNE_SET_<?=RUNE_SET_ID::SWIFT?>': 0, // SWIFT
+                    'RUNE_SET_<?=RUNE_SET_ID::BLADE?>': 0, // BLADE
+                    'RUNE_SET_<?=RUNE_SET_ID::RAGE?>': 0, // RAGE
+                    'RUNE_SET_<?=RUNE_SET_ID::FOCUS?>': 0, // FOCUS
+                    'RUNE_SET_<?=RUNE_SET_ID::ENDURE?>': 0, // ENDURE
+                    'RUNE_SET_<?=RUNE_SET_ID::FATAL?>': 0, // FATAL
+                    'RUNE_SET_<?=RUNE_SET_ID::DESPAIR?>': 0, // DESPAIR
+                    'RUNE_SET_<?=RUNE_SET_ID::VAMPIRE?>': 0, // VAMPIRE
+                    'RUNE_SET_<?=RUNE_SET_ID::VIOLENT?>': 0, // VIOLENT
+                    'RUNE_SET_<?=RUNE_SET_ID::NEMESIS?>': 0, // NEMESIS
+                    'RUNE_SET_<?=RUNE_SET_ID::WILL?>': 0, // WILL
+                    'RUNE_SET_<?=RUNE_SET_ID::SHIELD?>': 0, // SHIELD
+                    'RUNE_SET_<?=RUNE_SET_ID::REVENGE?>': 0, // REVENGE
+                    'RUNE_SET_<?=RUNE_SET_ID::DESTROY?>': 0, // DESTROY
+                    'RUNE_SET_<?=RUNE_SET_ID::FIGHT?>': 0, // FIGHT
+                    'RUNE_SET_<?=RUNE_SET_ID::DETERMINATION?>': 0, // DETERMINATION
+                    'RUNE_SET_<?=RUNE_SET_ID::ENHANCE?>': 0, // ENHANCE
+                    'RUNE_SET_<?=RUNE_SET_ID::ACCURACY?>': 0, // ACCURACY
+                    'RUNE_SET_<?=RUNE_SET_ID::TOLERANCE?>': 0 // TOLERANCE
+                };
+                for (var i = 0; i <= 5; i ++){
+                    sets['RUNE_SET_' + candidates[i].TYPE] ++;
+                }
+                // TEST I think i dont need to check for valid sets, only that
+                // they match the requested sets.
+                /*for (var i = 0; i < sets.length; i ++){
+                    switch (i){
+                        case <?=RUNE_SET_ID::ENERGY?>:
+                        case <?=RUNE_SET_ID::GUARD?>:
+                        case <?=RUNE_SET_ID::NEMESIS?>:
+                        case <?=RUNE_SET_ID::BLADE?>:
+                        case <?=RUNE_SET_ID::FOCUS?>:
+                        case <?=RUNE_SET_ID::ENDURE?>:
+                        case <?=RUNE_SET_ID::WILL?>:
+                        case <?=RUNE_SET_ID::SHIELD?>:
+                        case <?=RUNE_SET_ID::REVENGE?>:
+                        case <?=RUNE_SET_ID::DESTROY?>:
+                        case <?=RUNE_SET_ID::FIGHT?>:
+                        case <?=RUNE_SET_ID::DETERMINATION?>:
+                        case <?=RUNE_SET_ID::ENHANCE?>:
+                        case <?=RUNE_SET_ID::ACCURACY?>:
+                        case <?=RUNE_SET_ID::TOLERANCE?>:
+                            if (sets[i] % 2 != 0){
+                                return false;
+                            }
+                            break;
+                        
+                        case <?=RUNE_SET_ID::DESPAIR?>:
+                        case <?=RUNE_SET_ID::SWIFT?>:
+                        case <?=RUNE_SET_ID::RAGE?>:
+                        case <?=RUNE_SET_ID::FATAL?>:
+                        case <?=RUNE_SET_ID::VAMPIRE?>:
+                        case <?=RUNE_SET_ID::VIOLENT?>:
+                            if (sets[i] != 4 && sets[i] != 0){
+                                return false;
+                            }
+                            break;
+                    }
+                }*/
+                
+                // Its a valid set, but does it match the requested sets?
+                var requested_sets = {
+                    'RUNE_SET_<?=RUNE_SET_ID::ENERGY?>': 0, // ENERGY
+                    'RUNE_SET_<?=RUNE_SET_ID::GUARD?>': 0, // GUARD
+                    'RUNE_SET_<?=RUNE_SET_ID::SWIFT?>': 0, // SWIFT
+                    'RUNE_SET_<?=RUNE_SET_ID::BLADE?>': 0, // BLADE
+                    'RUNE_SET_<?=RUNE_SET_ID::RAGE?>': 0, // RAGE
+                    'RUNE_SET_<?=RUNE_SET_ID::FOCUS?>': 0, // FOCUS
+                    'RUNE_SET_<?=RUNE_SET_ID::ENDURE?>': 0, // ENDURE
+                    'RUNE_SET_<?=RUNE_SET_ID::FATAL?>': 0, // FATAL
+                    'RUNE_SET_<?=RUNE_SET_ID::DESPAIR?>': 0, // DESPAIR
+                    'RUNE_SET_<?=RUNE_SET_ID::VAMPIRE?>': 0, // VAMPIRE
+                    'RUNE_SET_<?=RUNE_SET_ID::VIOLENT?>': 0, // VIOLENT
+                    'RUNE_SET_<?=RUNE_SET_ID::NEMESIS?>': 0, // NEMESIS
+                    'RUNE_SET_<?=RUNE_SET_ID::WILL?>': 0, // WILL
+                    'RUNE_SET_<?=RUNE_SET_ID::SHIELD?>': 0, // SHIELD
+                    'RUNE_SET_<?=RUNE_SET_ID::REVENGE?>': 0, // REVENGE
+                    'RUNE_SET_<?=RUNE_SET_ID::DESTROY?>': 0, // DESTROY
+                    'RUNE_SET_<?=RUNE_SET_ID::FIGHT?>': 0, // FIGHT
+                    'RUNE_SET_<?=RUNE_SET_ID::DETERMINATION?>': 0, // DETERMINATION
+                    'RUNE_SET_<?=RUNE_SET_ID::ENHANCE?>': 0, // ENHANCE
+                    'RUNE_SET_<?=RUNE_SET_ID::ACCURACY?>': 0, // ACCURACY
+                    'RUNE_SET_<?=RUNE_SET_ID::TOLERANCE?>': 0 // TOLERANCE
+                };
+                var selected_1 = document.getElementById('set_1').options[document.getElementById('set_1').selectedIndex].value;
+                var selected_2 = document.getElementById('set_2').options[document.getElementById('set_2').selectedIndex].value;
+                var selected_3 = document.getElementById('set_3').options[document.getElementById('set_3').selectedIndex].value;
+                var requested_values = [
+                    document.getElementById('set_1').options[document.getElementById('set_1').selectedIndex].value,
+                    document.getElementById('set_2').options[document.getElementById('set_2').selectedIndex].value,
+                    document.getElementById('set_3').options[document.getElementById('set_3').selectedIndex].value
+                ];
+                for (var i = 0; i < 3; i ++){
+                    switch (requested_values[i]){
+                        case '<?=RUNE_SET_ID::ENERGY?>':
+                        case '<?=RUNE_SET_ID::GUARD?>':
+                        case '<?=RUNE_SET_ID::NEMESIS?>':
+                        case '<?=RUNE_SET_ID::BLADE?>':
+                        case '<?=RUNE_SET_ID::FOCUS?>':
+                        case '<?=RUNE_SET_ID::ENDURE?>':
+                        case '<?=RUNE_SET_ID::WILL?>':
+                        case '<?=RUNE_SET_ID::SHIELD?>':
+                        case '<?=RUNE_SET_ID::REVENGE?>':
+                        case '<?=RUNE_SET_ID::DESTROY?>':
+                        case '<?=RUNE_SET_ID::FIGHT?>':
+                        case '<?=RUNE_SET_ID::DETERMINATION?>':
+                        case '<?=RUNE_SET_ID::ENHANCE?>':
+                        case '<?=RUNE_SET_ID::ACCURACY?>':
+                        case '<?=RUNE_SET_ID::TOLERANCE?>':
+                            requested_sets['RUNE_SET_' + requested_values[i]] += 2;
+                            break;
+                        case '<?=RUNE_SET_ID::DESPAIR?>':
+                        case '<?=RUNE_SET_ID::SWIFT?>':
+                        case '<?=RUNE_SET_ID::RAGE?>':
+                        case '<?=RUNE_SET_ID::FATAL?>':
+                        case '<?=RUNE_SET_ID::VAMPIRE?>':
+                        case '<?=RUNE_SET_ID::VIOLENT?>':
+                            requested_sets['RUNE_SET_' + i] += 4;
+                            break;
+                    }
+                }
+
+                if (JSON.stringify(sets) !== JSON.stringify(requested_sets)){
+                    return false;
+                }
+
+                // Compare with filters
+                var new_stats = {
+                    "ATK": base_stats["attack"],
+                    "DEF": base_stats["defense"],
+                    "HP":  base_stats["hp"],
+                    "SPD": base_stats["speed"],
+                    "CRR": base_stats["crit_rate"],
+                    "CRD": base_stats["crit_damage"],
+                    "ACC": base_stats["accuracy"],
+                    "RES": base_stats["resistance"],
+                    "EHP": 0,
+                    "DMG": 0
+                }
+                for (var i = 0; i <= 5; i ++){
+                    new_stats["ATK"] += candidates[i].ATK;
+                    new_stats["DEF"] += candidates[i].DEF;
+                    new_stats["HP"]  += candidates[i].HP;
+                    new_stats["SPD"] += candidates[i].SPD;
+                    new_stats["CRR"] += candidates[i].CRR;
+                    new_stats["CRD"] += candidates[i].CRD;
+                    new_stats["ACC"] += candidates[i].ACC;
+                    new_stats["RES"] += candidates[i].RES;
+                }
+                new_stats["EHP"] = Math.ceil((((new_stats["DEF"] * 3.5) + 1140) * new_stats["HP"]) / 1000);
+                new_stats["DMG"] = Math.ceil((new_stats["ATK"] * (100 - new_stats["CRR"]) / 100) + ((new_stats["ATK"] + (new_stats["ATK"] * new_stats["CRD"] / 100)) * new_stats["CRR"] / 100));
+                if (
+                    new_stats["ATK"] >= document.getElementById('min_attack').value &&
+                    new_stats["DEF"] >= document.getElementById('min_defense').value &&
+                    new_stats["HP"]  >= document.getElementById('min_hp').value &&
+                    new_stats["SPD"] >= document.getElementById('min_speed').value &&
+                    new_stats["CRR"] >= document.getElementById('min_crit_rate').value &&
+                    new_stats["CRD"] >= document.getElementById('min_crit_damage').value &&
+                    new_stats["ACC"] >= document.getElementById('min_accuracy').value &&
+                    new_stats["RES"] >= document.getElementById('min_resistance').value &&
+                    new_stats["EHP"] >= document.getElementById('min_ehp').value &&
+                    new_stats["DMG"] >= document.getElementById('min_dmg').value
+                ){
+                    return true;
+                }
+                return false;
+            }
+
+            /**
+             * Parses the received runes and looks for valid candidates.
+             * Once it's done, it calls the server agai to get rune info.
              *
              * @param content JSON received from server.
              */
-            function showOptimizations(content, time){
+            async function calculateOptimizations(content, time){
                 var container = document.getElementById('optimizations');
                 var data = JSON.parse(content);
-                if (Number(data.total) == 0){
+
+                // Create a 0-index array grouped by slot
+                var candidates = [
+                    data[1],
+                    data[2],
+                    data[3],
+                    data[4],
+                    data[5],
+                    data[6]
+                ];
+
+                // Calculate total combinations.
+                var total_runes = data[1].length +  data[2].length +  data[3].length +  data[4].length +  data[5].length +  data[6].length;
+                var total_combinations = data[1].length *  data[2].length *  data[3].length *  data[4].length *  data[5].length *  data[6].length;
+                if (total_combinations == 0){
+                    var time = ((+ new Date()) - timestamp) / 1000;
+                    log('No rune combinations found (' + time + 's)');
+                    showMessage('No results', 'No runes found matching your criteria.');
+                }
+                var time = ((+ new Date()) - timestamp) / 1000;
+                log(total_runes + ' runes found (' + total_combinations + ' combinations) (' + time + 's)');
+                // TODO Prevent user on high numbers
+                var checked_combinations = 0;
+                
+                // Valid combinations that match the minimum requeriments
+                var valid_combinations = [];
+
+                // Counters to keep track on array indexes
+                var counters = [0, 0, 0, 0, 0, 0];
+                // Loop all posible combinations. When a valid set is found,
+                // add it to valid_combinations
+                while (true){
+
+                    if (valid_set([
+                      candidates[0][counters[0]],
+                      candidates[1][counters[1]],
+                      candidates[2][counters[2]],
+                      candidates[3][counters[3]],
+                      candidates[4][counters[4]],
+                      candidates[5][counters[5]]
+                    ])){
+                        valid_combinations.push({
+                            'score': 0,
+                            'runes': [
+                                candidates[0][counters[0]],
+                                candidates[1][counters[1]],
+                                candidates[2][counters[2]],
+                                candidates[3][counters[3]],
+                                candidates[4][counters[4]],
+                                candidates[5][counters[5]]
+                            ]
+                        });
+                    }
+
+                    // Increase counters
+                    checked_combinations ++;
+                    counters[5] ++;
+                    if (counters[5] >= candidates[5].length){
+                        counters[5] = 0;
+                        counters[4] ++;
+                    }
+                    if (counters[4] >= candidates[4].length){
+                        counters[4] = 0;
+                        counters[3] ++;
+                    }
+                    if (counters[3] >= candidates[3].length){
+                        counters[3] = 0;
+                        counters[2] ++;
+                    }
+                    if (counters[2] >= candidates[2].length){
+                        counters[2] = 0;
+                        counters[1] ++;
+                    }
+                    if (counters[1] >= candidates[1].length){
+                        counters[1] = 0;
+                        counters[0] ++;
+                    }
+                    if (counters[0] >= candidates[0].length){
+                        break;
+                    }
+                    
+                    /*if (checked_combinations % 1000 == 0){
+                        console.log("COMBINATIONS CHECKED: " + checked_combinations + " / " + total_combinations + "(" + (checked_combinations * 100 / total_combinations) + "%)");
+                        console.log("  0:  " + counters[0] + "/" + candidates[0].length);
+                        console.log("  1:  " + counters[1] + "/" + candidates[1].length);
+                        console.log("  2:  " + counters[2] + "/" + candidates[2].length);
+                        console.log("  3:  " + counters[3] + "/" + candidates[3].length);
+                        console.log("  4:  " + counters[4] + "/" + candidates[4].length);
+                        console.log("  5:  " + counters[5] + "/" + candidates[5].length);
+                    }*/
+                }
+
+                // If no valid combinations: exit now
+                if (valid_combinations.length == 0){
+                    enableNewSearch(true);
+                    var time = ((+ new Date()) - timestamp) / 1000;
+                    log('No suitable combination found (' + time + 's).');
                     showMessage('No results', 'No suitable combination found. Plesase relax your criteria and retry.');
+                }
+                // Foreach valid combination, rate it and sort the array
+                for (var i = 0; i < valid_combinations.length; i ++){
+                    for (var r = 0; r < 6; r ++){
+                        valid_combinations[i]['score'] += valid_combinations[i]["runes"][r]["ATK"];
+                        valid_combinations[i]['score'] += valid_combinations[i]["runes"][r]["DEF"];
+                        valid_combinations[i]['score'] += (valid_combinations[i]["runes"][r]["HP"] / 15);
+                        valid_combinations[i]['score'] += (valid_combinations[i]["runes"][r]["SPD"] * 1.5);
+                        valid_combinations[i]['score'] += (valid_combinations[i]["runes"][r]["CRR"] * 1.2);
+                        valid_combinations[i]['score'] += (valid_combinations[i]["runes"][r]["CRD"] * 1.2);
+                        valid_combinations[i]['score'] += (valid_combinations[i]["runes"][r]["ACC"] * 1.2);
+                        valid_combinations[i]['score'] += (valid_combinations[i]["runes"][r]["ATK"] * 1.2);
+                    }
+                }
+                var time = ((+ new Date()) - timestamp) / 1000;
+                log(valid_combinations.length + ' suitable combinations found (' + time + 's)');
+                valid_combinations.sort(function(a, b) {return b['score'] - a['score'];});
+                if (valid_combinations.length > document.getElementById('limit').value){
+                    var time = ((+ new Date()) - timestamp) / 1000;
+                    log('Selecting the ' + document.getElementById('limit').value + ' best combinations (' + time + 's)');
+                    valid_combinations = valid_combinations.slice(0, document.getElementById('limit').value);
+                }
+
+                // Request to fetch the runes.
+                var xhttp = new XMLHttpRequest();
+                xhttp.onreadystatechange = function() {
+                    if (this.readyState == 4){
+                        if (this.status == 200) {
+                            //time = ((+ new Date()) - timestamp) / 1000;
+                            presentOptions(this.response, timestamp);
+                        }
+                        else{
+                            enableNewSearch(true);
+                            showMessage('Error', 'Unexpected error ' + this.status);
+                        }
+                    }
+                };
+                params = 'uid=<?=$UID?>&unit=<?=$page->unit->id?>&tuning=' + document.getElementById('tuning').options[document.getElementById('tuning').selectedIndex].value;
+                params += '&options=' + JSON.stringify(valid_combinations);
+                xhttp.open('POST', '/action/optimize_get_options/', true);
+                xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+                xhttp.send(params);
+                return;
+            }
+
+            /**
+             * Displays the options.
+             *
+             * Builds a table with the calculated options and their scores.
+             * If none. It will show a message. In any case, it reenables the search
+             * button
+             *
+             * @param content Response from /action/optimize_get_options/
+             * @param timestamp Time counter
+             */
+            function presentOptions(content, timestamp){
+                var container = document.getElementById('optimizations');
+                var data = JSON.parse(content);
+                if (data.length == 0){
+                    enableNewSearch(true);
                     return;
                 }
-                document.getElementById('num_results').innerHTML = data.total;
-                document.getElementById('num_skipped').innerHTML = data.skipped;
-                document.getElementById('num_seconds').innerHTML = time;
                 var option;
                 var tr;
                 var td;
@@ -290,8 +673,8 @@
                 var stat_td;
                 var span;
                 var text;
-                for (i = 0; i < data.options.length; i++){
-                    option = data.options[i];
+                for (i = 0; i < data.length; i++){
+                    option = data[i];
                     tr = document.createElement('tr');
                     td = document.createElement('td');
                     td.setAttribute('class', 'option_stats');
@@ -671,6 +1054,20 @@
                     document.getElementById('results').style.display = 'block';
 
                 }
+                time = ((+ new Date()) - timestamp) / 1000;
+                log('Done in ' + time + ' seconds');
+                enableNewSearch(true);
+            }
+            
+            /**
+             * Function to sort rune combinations based on their score.
+             *
+             * @param a One combination.
+             * @param b Other combination.
+             * @return Numeric index for sorting.
+             */
+            function sortOptions(a, b) {
+                return b["score"] - a["score"];
             }
 
             /**
@@ -691,6 +1088,28 @@
             function closeMessage(){
                 document.getElementById('message_container').style.display = 'none';
             }
+            
+            /**
+             * DEBUG FUNCTION
+             *
+             * Sets filters to a known value for debugging.
+             */
+            function debug(){
+                document.getElementById('set_1').selectedIndex = 0;
+                document.getElementById('set_2').selectedIndex = 0;
+                document.getElementById('set_3').selectedIndex = 1;
+                removeFilter('attack');
+                removeFilter('defense');
+                removeFilter('speed');
+                removeFilter('crit_rate');
+                removeFilter('crit_damage');
+                removeFilter('accuracy');
+                removeFilter('resistance');
+                removeFilter('ehp');
+                removeFilter('dmg');
+                document.getElementById('lbl_min_hp').value = 13500;
+                updateMinValue('hp', document.getElementById('lbl_min_hp'));
+            }
         </script>
     </head>
     <body>
@@ -1004,7 +1423,6 @@
                     //foreach ($page->unit->rune as $rune){
                     foreach ($show_list as $i){
                         $rune = $page->unit->rune[$i - 1];
-                        $total_runes ++;
 ?>
                         <?=HTML::rune_table($rune)?>
 <?php
@@ -1211,9 +1629,15 @@
                             </td>
                         </tr>
                         <tr>
-                            <td class='empty'>
-                            </td>
-                            <td class='empty'>
+                            <td id='log' colspan='2' rowspan='4'>
+                                <div id='log_title'>
+                                    LOG
+                                    <span id='log_clear' onClick='clearLog();'>
+                                        clear
+                                    </span>
+                                </div>
+                                <div id='log_window'>
+                                </div>
                             </td>
                             <td class='empty'>
                             </td>
@@ -1232,10 +1656,10 @@
                             </td>
                         </tr>
                         <tr>
-                            <td class='empty'>
-                            </td>
-                            <td class='empty'>
-                            </td>
+                            <!--<td class='empty'>
+                            </td>-->
+                            <!--<td class='empty'>
+                            </td>-->
                             <td class='empty'>
                             </td>
                             <td class='name'>
@@ -1253,10 +1677,10 @@
                             </td>
                         </tr>
                         <tr>
-                            <td class='empty'>
-                            </td>
-                            <td class='empty'>
-                            </td>
+                            <!--<td class='empty'>
+                            </td>-->
+                            <!--<td class='empty'>
+                            </td>-->
                             <td class='empty'>
                             </td>
                             <td class='name'>
@@ -1274,10 +1698,10 @@
                             </td>
                         </tr>
                         <tr>
-                            <td class='empty'>
-                            </td>
-                            <td class='empty'>
-                            </td>
+                            <!--<td class='empty'>
+                            </td>-->
+                            <!--<td class='empty'>
+                            </td>-->
                             <td class='empty'>
                             </td>
                             <td class='name'>
@@ -1297,6 +1721,7 @@
                         <tr>
                             <td class='action'>
                                 <input id='apply' type='button' value='Apply' onClick='apply();'/>
+                                <!--<input id='apply' type='button' value='DEBUG' onClick='debug();'/>-->
                             </td>
                             <td class='empty'>
                                 <img id='wait' src='<?=URL::IMG["ICON"] . "wait.gif";?>'>
@@ -1317,10 +1742,6 @@
                                 </a>
                             </td>
                         </tr>
-
-
-
-
                     </table>
                 </article>
             </section>
@@ -1329,9 +1750,6 @@
                     Optimization results
                 </h2>
                 <article>
-                    <div id='results_info'>
-                        <span id='num_results'></span> results (<span id='num_skipped'></span> skipped) in <span id='num_seconds'></span> seconds
-                    </div>
                     <table id='table_optimizations'>
                         <tbody id='optimizations'>
                         </tbody>

+ 36 - 1
public/css/optimizer.css

@@ -223,7 +223,40 @@ section#parameters article img#wait{
     height: 2.5em;
     display: none;
 }
-
+section#parameters td#log{
+    border: 0.1em solid #000000;
+    border-radius: 0.3em;
+    vertical-align: bottom;
+    overflow-y: scroll;
+    position: relative;
+    background-color: #000000;
+}
+section#parameters td#log div#log_title{
+    position: absolute;
+    font-weight: bold;
+    color: #ffffff;
+    top: 0;
+    left: 0;
+    right: 0;
+    border-bottom: 0.1em solid #ffffff;
+    background-color: #000000;
+}
+section#parameters td#log div#log_title span#log_clear{
+    float: right;
+    cursor: pointer;
+    font-size: 90%;
+}
+section#parameters td#log div#log_window{
+    height: 6.5em;
+    overflow-y: scroll;
+    scrollbar-width: thin;
+}
+section#parameters td#log div#log_window span.log_message{
+    font-family: monospace;
+    display: block;
+    margin: 0 0 0 0.5em;
+    font-size: 90%;
+}
 section#results{
     display: none;
 }
@@ -293,6 +326,8 @@ section#results td.runes table.rune{
 section#results table#table_optimizations{
     border-collapse: collapse;
     margin: auto;
+    border-left: 0.1em solid #000000cc;
+    border-right: 0.1em solid #000000cc;
 }
 
 section#results td.option_stats, section#results td.runes{