Pārlūkot izejas kodu

The optimizer now does almost all CPU intensive work client-side.

Iñigo Valentin 5 gadi atpakaļ
vecāks
revīzija
f8657f1e63

+ 22 - 0
application/Controller.php

@@ -136,6 +136,28 @@
                             return;
                         }
                         break;
+                    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);
+                            return;
+                        }
+                        else{
+                            return;
+                        }
+                        break;
                     case "EDIT_PROFILE":
                         require_once(PATH::ACTION . "edit_profile.php");
                         $response = action();

+ 154 - 0
application/action/optimize_get_options.php

@@ -0,0 +1,154 @@
+<?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);
+        error_log("ATK" . $unit->attack);
+
+        $options = json_decode(filter_input(INPUT_POST, 'options'), true);
+        if ($options == null){
+            error_log("NO JSON");
+            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
+                $stat_ids = [
+                    $rune->main_stat,
+                    $rune->innate_stat,
+                    $rune->substat_1,
+                    $rune->substat_2,
+                    $rune->substat_3,
+                    $rune->substat_4
+                ];
+                $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;
+                    }
+                }
+                $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;
+                // TODO calculte ehp, dmg
+                array_push($set["runes"], $r);
+                
+            }
+            array_push($runes, $set);
+        }
+        $response = json_encode($runes);
+        if ($response == null){
+            return 0;
+        }
+        else{
+            return $response;
+        }
+    }
+?>

+ 362 - 0
application/action/optimize_get_rune_list.php

@@ -0,0 +1,362 @@
+<?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 ++){
+        error_log($s[$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(15, $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;
+        }
+    }
+?>

+ 398 - 5
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
@@ -129,8 +142,7 @@
              *
              * 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.
              */
@@ -231,6 +243,7 @@
                     if (this.readyState == 4){
                         document.getElementById('apply').removeAttribute('disabled');
                         document.getElementById('wait').style.display = 'none';
+                        //console.log(this.response);
                         if (this.status == 200) {
                             time = ((+ new Date()) - timestamp) / 1000;
                             showOptimizations(this.response, time);
@@ -261,11 +274,199 @@
                 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);
             }
 
+            function valid_set(candidates){
+            
+                // Debug section: Look out for current runes
+                if (
+                    candidates[0].ID == '18127229790' //&&
+                    //candidates[1].ID == '16037693193' &&
+                    //candidates[2].ID == '18831259497' &&
+                    //candidates[3].ID == '19634059762' &&
+                    //candidates[4].ID == '19057400217' &&
+                    //candidates[5].ID == '20095751566'
+                ){
+                    console.log("CURRENT SET!!");
+                }
+            
+                // 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] ++;
+                }
+                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;
+                    }
+                }
+
+                //console.log("VALID SET!");
+                if (JSON.stringify(sets) !== JSON.stringify(requested_sets)){
+                    return false;
+                }
+                //console.log("VALID REQUESTED SET!");
+                
+                // 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));
+                //console.log("HP " + new_stats["HP"] + "/" + document.getElementById('min_hp').value);
+                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
+                ){
+                    //console.log("IMPROVING SET!");
+                    return true
+                }
+                return false;
+            }
+            
             /**
              * Parses the received content and creates the HTML with the new
              * rune options.
@@ -275,6 +476,175 @@
             function showOptimizations(content, time){
                 var container = document.getElementById('optimizations');
                 var data = JSON.parse(content);
+                //console.log(data);
+                var candidates = [
+                    data[1],
+                    data[2],
+                    data[3],
+                    data[4],
+                    data[5],
+                    data[6]
+                ];
+                var total_combinations = data[1].length *  data[2].length *  data[3].length *  data[4].length *  data[5].length *  data[6].length;
+                var checked_combinations = 0;
+                // Valic combinations that match the minimum requeriments
+                var valid_combinations = [];
+                console.log("TOTAL COMBINATIONS: " + total_combinations);
+                // TODO Prevent user on high numbers
+                // Counters to keep track on array indexes
+                var counters = [0, 0, 0, 0, 0, 0];
+                
+                
+                while (true){
+                
+                    // Check loop exit condition
+                    if (
+                        counters[0] >= candidates[0].length &&
+                        counters[1] >= candidates[1].length &&
+                        counters[2] >= candidates[2].length &&
+                        counters[3] >= candidates[3].length &&
+                        counters[4] >= candidates[4].length &&
+                        counters[5] >= candidates[5].length
+                    ){
+                        console.log("BREAK BY condition");
+                        break;
+                    }
+                    
+                    // TODO DEBUG Exit a 3
+                    if (checked_combinations >= 3000000){
+                        console.log("BREAK BY hard limit");
+                        break;
+                    }
+                
+                    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){
+                        console.log("BREAK BY increments");
+                        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);
+                    }*/
+                    
+                    
+                    
+                }
+                
+                // 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);
+                    }
+                }
+                console.log("CHECKED: " + checked_combinations + "/" + total_combinations);
+                valid_combinations.sort(function(a, b) {return b['score'] - a['score'];});
+                valid_combinations = valid_combinations.slice(0, document.getElementById('limit').value);
+                console.log("SIZE: " + valid_combinations.length);
+//                 for (var i = 0; i < valid_combinations.length; i ++){
+//                     console.log(valid_combinations[i]['score']);
+//                 }
+                
+                // Request to fetch the runes.
+                // TODO
+                // Make the request
+                var xhttp = new XMLHttpRequest();
+                xhttp.onreadystatechange = function() {
+                    if (this.readyState == 4){
+                        console.log("RUNES RECEIVED");
+                        console.log(this.response);
+                        if (this.status == 200) {
+                            //time = ((+ new Date()) - timestamp) / 1000;
+                            presentOptions(this.response, time);
+                        }
+                        /*document.getElementById('apply').removeAttribute('disabled');
+                        document.getElementById('wait').style.display = 'none';
+                        //console.log(this.response);
+                        if (this.status == 200) {
+                            time = ((+ new Date()) - timestamp) / 1000;
+                            showOptimizations(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.');
+                        }*/
+                    }
+                };
+                params = 'uid=<?=$UID?>&unit=<?=$page->unit->id?>';
+                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);
+                // Send the request
+                
+                
+                
+                return;
+                
+                
+            }
+            function presentOptions(content, time){
+                var container = document.getElementById('optimizations');
+                var data = JSON.parse(content);
+                
+                
                 if (Number(data.total) == 0){
                     showMessage('No results', 'No suitable combination found. Plesase relax your criteria and retry.');
                     return;
@@ -290,8 +660,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');
@@ -672,6 +1042,10 @@
 
                 }
             }
+            
+            function sortOptions(a, b) {
+                return b["score"] - a["score"];
+            }
 
             /**
              * Displays a pop-up message.
@@ -691,6 +1065,24 @@
             function closeMessage(){
                 document.getElementById('message_container').style.display = 'none';
             }
+            
+            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'));
+                //apply();
+            }
         </script>
     </head>
     <body>
@@ -1297,6 +1689,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";?>'>