Quellcode durchsuchen

New URL structure to diferenciate player and nos player data. Multiple accounts working.

Iñigo Valentin vor 5 Jahren
Ursprung
Commit
f23a25d31b
75 geänderte Dateien mit 3386 neuen und 3177 gelöschten Zeilen
  1. 102 3
      application/API/v1/profile/PUT.php
  2. 351 265
      application/Controller.php
  3. 78 0
      application/action/Action.php
  4. 291 0
      application/action/Add_Team_Action.php
  5. 48 0
      application/action/Change_Game_Mode_Action.php
  6. 44 0
      application/action/Delete_Team_Action.php
  7. 97 0
      application/action/Edit_Profile_Action.php
  8. 103 0
      application/action/Login_Action.php
  9. 37 0
      application/action/Logout_Action.php
  10. 215 0
      application/action/Optimize_Options_Action.php
  11. 551 0
      application/action/Optmimize_List_Action.php
  12. 59 0
      application/action/Rate_Team_Action.php
  13. 120 0
      application/action/Save_Team_Action.php
  14. 0 40
      application/action/change_game_mode.php
  15. 0 34
      application/action/delete_team.php
  16. 0 89
      application/action/edit_profile.php
  17. 0 94
      application/action/login.php
  18. 0 26
      application/action/logout.php
  19. 0 231
      application/action/new_team.php
  20. 0 197
      application/action/optimize_get_options.php
  21. 0 530
      application/action/optimize_get_rune_list.php
  22. 0 48
      application/action/rate_team.php
  23. 0 108
      application/action/save_run_team.php
  24. 10 5
      application/page/Artifacts_Page.php
  25. 10 4
      application/page/Enchantments_Page.php
  26. 30 45
      application/page/Error_Page.php
  27. 12 6
      application/page/Guild_Page.php
  28. 13 6
      application/page/Login_Page.php
  29. 18 4
      application/page/Monster_Page.php
  30. 10 4
      application/page/Monsters_Page.php
  31. 9 4
      application/page/Optimizer_Page.php
  32. 9 4
      application/page/Optimizer_Unit_Page.php
  33. 228 9
      application/page/Page.php
  34. 10 5
      application/page/Player_Page.php
  35. 8 4
      application/page/Profile_Page.php
  36. 9 6
      application/page/Report_Page.php
  37. 10 4
      application/page/Report_Rune_Enchantment_Page.php
  38. 10 4
      application/page/Report_Skillup_Page.php
  39. 10 4
      application/page/Runes_Page.php
  40. 9 4
      application/page/Runs_Page.php
  41. 9 4
      application/page/Teams_Page.php
  42. 16 4
      application/page/Unit_Page.php
  43. 9 5
      application/page/Units_Page.php
  44. 3 33
      application/view/artifacts.php
  45. 5 33
      application/view/enchantments.php
  46. 5 17
      application/view/error.php
  47. 5 33
      application/view/guild.php
  48. 5 34
      application/view/help.php
  49. 0 143
      application/view/inc/header.php
  50. 78 0
      application/view/inc/header_app.php
  51. 136 0
      application/view/inc/header_player.php
  52. 4 27
      application/view/login.php
  53. 4 33
      application/view/monster.php
  54. 4 34
      application/view/monsters.php
  55. 5 33
      application/view/optimizer.php
  56. 8 36
      application/view/optimizer_unit.php
  57. 6 34
      application/view/player.php
  58. 4 34
      application/view/profile.php
  59. 5 33
      application/view/report.php
  60. 5 33
      application/view/report_rune_enchantment.php
  61. 5 33
      application/view/report_skillup.php
  62. 5 33
      application/view/runes.php
  63. 6 34
      application/view/runs.php
  64. 3 33
      application/view/stats.php
  65. 6 34
      application/view/teams.php
  66. 5 33
      application/view/unit.php
  67. 5 33
      application/view/units.php
  68. 0 12
      public/css/catalog.css
  69. 131 119
      public/css/monster.css
  70. 0 232
      public/css/monster_info.css
  71. 7 134
      public/css/monsters.css
  72. 0 0
      public/css/player.css
  73. 27 57
      public/css/ui.css
  74. 220 0
      public/css/unit.css
  75. 139 0
      public/css/units.css

+ 102 - 3
application/API/v1/profile/PUT.php

@@ -1512,9 +1512,108 @@ try{
             $statement->bindValue(":player", $player_id, SQLITE3_INTEGER);
             $statement->bindValue(":api_key", $key, SQLITE3_TEXT);
             if (1 != $statement->execute()->fetchArray(SQLITE3_ASSOC)["c"]){
-                header("HTTP/1.1 401 Invalid credentials.");
-                syslog(LOG_INFO, "[APIv1] HTTP/1.1 401 Invalid credentials.");
-                return 401;
+                // Player doesnt exists. If there is a user, create player.
+                $statement = $db->prepare("
+                  SELECT id
+                  FROM user
+                  WHERE api_key = :api_key;");
+                $statement->bindValue(":api_key", $key, SQLITE3_TEXT);
+                $r = $statement->execute()->fetchArray(SQLITE3_ASSOC);
+                if (! $r){
+                    header("HTTP/1.1 401 Invalid credentials.");
+                    syslog(LOG_INFO, "[APIv1] HTTP/1.1 401 Invalid credentials.");
+                    return 401;
+                }
+                $statement = $db->prepare("
+                  INSERT INTO player (
+                    id,
+                    user,
+                    server,
+                    country,
+                    lang,
+                    name,
+                    mana,
+                    crystal,
+                    level,
+                    experience,
+                    energy,
+                    energy_max,
+                    energy_per_min,
+                    arena_energy,
+                    rep,
+                    social_point,
+                    honor_point,
+                    guild_point,
+                    darkportal_energy,
+                    dimension_energy,
+                    costume_point,
+                    honor_medal,
+                    honor_mark,
+                    event_coin,
+                    storage_slots,
+                    island,
+                    joined,
+                    top_rank_arena,
+                    top_rank_world_arena,
+                    top_rank_special_league,
+                    top_rank_gw,
+                    top_rank_siege,
+                    top_rank_wboss,
+                    top_rank_toan,
+                    top_rank_toah,
+                    top_rank_toal,
+                    public,
+                    mode,
+                    last_access
+                  ) VALUES (
+                    :id,
+                    :user,
+                    :server,
+                    :country,
+                    :lang,
+                    '', --name,
+                    0, --mana,
+                    0, --crystal,
+                    1, --level,
+                    0, --experience,
+                    0, --energy,
+                    0, --energy_max,
+                    0, --energy_per_min,
+                    0, --arena_energy,
+                    null, --rep,
+                    0, --social_point,
+                    0, --honor_point,
+                    0, --guild_point,
+                    0, --darkportal_energy,
+                    0, --dimension_energy,
+                    0, --costume_point,
+                    0, --honor_medal,
+                    0, --honor_mark,
+                    0, --event_coin,
+                    0, --storage_slots,
+                    0, --island,
+                    null, --joined,
+                    0, --top_rank_arena,
+                    0, --top_rank_world_arena,
+                    0, --top_rank_special_league,
+                    0, --top_rank_gw,
+                    0, --top_rank_siege,
+                    0, --top_rank_wboss,
+                    0, --top_rank_toan,
+                    0, --top_rank_toah,
+                    0, --top_rank_toal,
+                    0, --public,
+                    0, --mode,
+                    null --last_access,
+                )");
+                $statement->bindValue(":id", $json->{"wizard_id"}, SQLITE3_INTEGER);
+                $statement->bindValue(":user", $r["id"], SQLITE3_INTEGER);
+                // TODO: Default server EUROPE, detect and fix
+                $statement->bindValue(":server", 2, SQLITE3_INTEGER);
+                $statement->bindValue(":country", $json->{"wizard_info"}->{"wizard_last_country"}, SQLITE3_TEXT);
+                $statement->bindValue(":lang", $json->{"wizard_info"}->{"wizard_last_lang"}, SQLITE3_TEXT);
+                error_log("CREATE NEW PLAYER: " . $statement->getSQL(true));
+                $statement->execute();
             }
             break;
         case "GetUnitCollection":

+ 351 - 265
application/Controller.php

@@ -39,6 +39,11 @@ class Controller extends Context{
      * @var Context Publicly available context.
      */
     private $context;
+    
+    private $command = "";
+    
+    private $status = 201;
+    private $message = "OK";
 
     /**
      * Retrieves the application context.
@@ -60,6 +65,12 @@ class Controller extends Context{
         $this->db = DB::start();
         $this->context->set_db($this->db);
     }
+    
+    private function execute_action($action){
+        require_once(PATH::ACTION . $action . ".php");
+        action();
+        return;
+    }
 
     /**
      * Handles the request parameters.
@@ -77,16 +88,8 @@ class Controller extends Context{
                 array_push($this->params, $p);
             }
         }
-
-        if (
-          count($this->params) > 1 && 
-          strtoupper($this->params[0]) == "API"
-        ){
-            // For API calls, skip authentication and end here.
-            return;
-        }
-
-        // Authentication
+        
+        // Chack if a user is authenticated.
         if (array_key_exists("user_token", $_COOKIE) && array_key_exists("user_id", $_COOKIE)){
             // Authenticated user, check token.
             $statement = $this->db->prepare("
@@ -102,109 +105,98 @@ class Controller extends Context{
             $r = $statement->execute()->fetchArray(SQLITE3_ASSOC);
             if ($r["c"] == 0){
                 // Token is invalid, expired, clear cookies, session, and logout.
-                require_once(PATH::ACTION . "logout.php");
-                action();
+                execute_action("logout");
                 return;
             }
             else{
                 // Token is valid, renew session, and assign user.
                 $_SESSION["user_id"] = $_COOKIE["user_id"];
                 $_SESSION["session"] = true;
-                $this->user = new User($_COOKIE["user_id"]);
-                $this->context->set_user($this->user);
+                $this->context->set_user(new User($_COOKIE["user_id"]));
             }
-
+            
         }
         else{
             // Unauthenticated user. Continue
         }
         
-        if (
-            count($this->params) > 1 &&
-            strtoupper($this->params[0]) == "ACTION"
-            ){
-                // For ACTION calls, skip user detection and end here.
-                return;
-        }
-
-        // Validate URL (check if it has the player ID).
-        $this->player = null;
-        if (sizeof($this->params) > 0 && preg_match("/^[0-9]{6,12}$/", $this->params[0]) == 1){
-            // Player ID passed in URL. Check it in database
-            $statement = $this->db->prepare("
-              SELECT
-                id,
-                user,
-                public
-              FROM player
-              WHERE id = :id
-            ");
-            $statement->bindValue(":id", $this->params[0], SQLITE3_TEXT);
-            $r = $statement->execute()->fetchArray(SQLITE3_ASSOC);
-            if ($r){
-                // The player exists...
-                if (null != $this->user && $r["user"] == $this->user->get_id()){
-                    // ... and it belongs to the user. Proceed.
-                    $this->player = new Player($r["id"]);
-                    $this->context->set_player($this->player);
-                }
-                elseif (null == $this->user || $r["user"] != $this->user->get_id()){
-                    // ... and it doesn't belong to the user...
-                    if ($r["public"] == 1){
-                        // ... and it belongs to a public profile. Proceed.
-                        $this->player = new Player($r["id"]);
-                        $this->context->set_player($this->player);
-                    }
-                    else{
-                        // ... and it belongs to a private profile. Deny access.
-                        // TODO
-                    }
-                }
-            }
+        $auth_required = false;
+        $player_id = null;
+        
+        // Obtain the command and process it
+        if (sizeof($this->params) > 0){
+            $this->command = strtoupper($this->params[0]);
         }
-        else{
-            // Player ID is not passed as URL...
-            if (null == $this->user){
-                // ... and no user logged in. Go to the landing page.
-                require_once(PATH::PAGE . "Landing_Page.php");
-                $page = new Landing_Page();
-                require_once($page->get_view());
-                return;
-            }
-            else{
-                // ... and the user is logged in. Get last accessed account.
-                $statement = $this->db->prepare("
-                  SELECT id
-                  FROM player
-                  WHERE user = :user
-                  ORDER BY last_access DESC
-                ");
-                $statement->bindValue(":user", $this->user->get_id(), SQLITE3_INTEGER);
-                $r = $statement->execute()->fetchArray(SQLITE3_ASSOC);
-                if ($r){
-                    header("Location: /" . $r["id"] . parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH));
-                    return;
+        switch ($this->command){
+            case "": // Main page
+                break;
+            case "API": // An API call
+                return; // End preparation here, the API controller will deal with everything.
+                break;
+            case "ACTION": // Execute an action
+                $auth_required = true;
+                break;
+            case "PLAYER": // A player page
+                if (sizeof($this->params) > 1){
+                    $player_id = $this->params[1];
                 }
                 else{
-                    require_once(PATH::PAGE . "Landing_Page.php");
-                    $page = new Landing_Page();
-                    require_once($page->get_view());
+                    $this->status = 400;
+                    $this->message = "Bad request";
                     return;
                 }
+                break;
+            case "LOGIN": // The login page
+                break;
+            case "REGISTER": // Registration page
+                break;
+            case "SEARCH": // Search results
+                break;
+            case "DUNGEON": // Dungeon static pages
+                break;
+            case "BUILDING": // Building static pages
+                break;
+            case "FUSION": // Fusion static pages
+                break;
+            case "HELP": // Help static page
+                break;
+            case "ERROR": // Error page
+                break;
+            default: // Unknown page
+                
+        }
+        
+        if ($player_id != null){
+            $this->context->set_player(new Player($player_id));
+            if ($this->context->get_player()->get_id() == null){
+                // TODO: error 404;
+                $this->status = 404;
+                $this->message = "Requested player doesn't exist";
+                return;
             }
         }
+        
+        if ($auth_required && $this->context->get_user() == null){
+            $this->status = 401;
+            $this->message = "Authentication required to perform this action";
+            return;
+        }
 
         // Get game mode
-        $statement = $this->db->prepare("SELECT mode FROM player WHERE id = :id;");
-        $statement->bindValue(":id", $this->player->get_id(), SQLITE3_INTEGER);
-        $r = $statement->execute()->fetchArray(SQLITE3_ASSOC);
-        if ($r && $r["mode"] == GAME_MODE_ID::RTA){
-            $this->mode = GAME_MODE_ID::RTA;
+        if ($this->context->get_player() == null){
+            $this->context->set_game_mode(GAME_MODE_ID::NORMAL);
         }
         else{
-            $this->mode = GAME_MODE_ID::NORMAL;
+            $statement = $this->db->prepare("SELECT mode FROM player WHERE id = :id;");
+            $statement->bindValue(":id", $this->context->get_player()->get_id(), SQLITE3_INTEGER);
+            $r = $statement->execute()->fetchArray(SQLITE3_ASSOC);
+            if ($r && $r["mode"] == GAME_MODE_ID::RTA){
+                $this->context->set_game_mode(GAME_MODE_ID::RTA);
+            }
+            else{
+                $this->context->set_game_mode(GAME_MODE_ID::NORMAL);
+            }
         }
-        $this->context->set_game_mode($this->mode);
 
     }
 
@@ -215,201 +207,295 @@ class Controller extends Context{
      * @return void|number
      */
     public function action(){
-        // API call: Use API controller
-        if (count($this->params) > 0 && strtoupper($this->params[0]) == "API"){
-            if (count($this->params) == 1){
-                // TODO: API MAIN PAGE
-            }
-            else{
-                $version = strtolower($this->params[1]);
-                $api_params = $this->params;
-                array_shift($api_params);
-                array_shift($api_params);
-                switch ($version){
-                    case "v1":
-                        require_once(__DIR__ . "/API/v1/API_Controller.php");
-                        new API_Controller($this->params);
-                        break;
-                }
-            }
-            return;
+        
+        // If there was an error in preparation, it's time to throw it.
+        if ($this->status >= 400 && $this->status < 500){
+            $page = new Error_Page($this->status, $this->message);
+            require_once($page->get_view());
+            http_response_code(404);
+            return 404;
         }
-        // Special case: Actions.
-        // Not redirecting to page, just execute a function
-        // TODO: Secure actions
-        elseif (count($this->params) > 1 && strtoupper($this->params[0]) == "ACTION"){
-            switch (strtoupper($this->params[1])){
-                case "CHANGE_GAME_MODE":
-                    require_once(PATH::ACTION . "change_game_mode.php");
-                    action();
-                    return;
-                    break;
-                case "LOGIN":
-                    require_once(PATH::ACTION . "login.php");
-                    action();
-                    return;
-                    break;
-                case "LOGOUT":
-                    require_once(PATH::ACTION . "logout.php");
-                    action();
-                    return;
-                    break;
-                case "NEW_TEAM":
-                    require_once(PATH::ACTION . "new_team.php");
-                    action();
-                    return;
-                    break;
-                case "DELETE_TEAM":
-                    require_once(PATH::ACTION . "delete_team.php");
-                    action();
-                    return;
-                    break;
-                case "RATE_TEAM":
-                    require_once(PATH::ACTION . "rate_team.php");
-                    action();
-                    return;
-                    break;
-                case "SAVE_RUN_TEAM":
-                    require_once(PATH::ACTION . "save_run_team.php");
-                    action();
-                    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();
-                    if (strlen($response) > 1){
-                        echo($response);
+        
+        // Process the command
+        error_log("SWITCH COMMAND: " . $this->command);
+        $page = null;
+        switch ($this->command){
+            case "": // Main page
+                // Two options:
+                //  If user authenticated, retrieve last used player acocunt and load main page
+                //  If not, login page.
+                if ($this->context->get_user() == null){
+                    require_once(PATH::PAGE . "Login_Page.php");
+                    $page = new Login_Page();
+                }
+                else{
+                    $statement = $this->context->get_db()->prepare("
+                        SELECT id
+                        FROM player
+                        WHERE user = :user
+                        ORDER BY last_access DESC
+                        LIMIT 1;
+                    ");
+                    $statement->bindValue(":user", $this->context->get_user()->get_id(), SQLITE3_INTEGER);
+                    $r = $statement->execute()->fetchArray(SQLITE3_ASSOC);
+                    if ($r){
+                        header("Location: " . URL::BASE . "player/" . $r["id"]);
                         return;
+                        //$this->context->set_player(new Player($r["id"]));
+                        //require_once(PATH::PAGE . "Player_Page.php");
+                        //$page = new Player_Page();
                     }
                     else{
-                        return;
+                        // TODO: weird case: user with no players
                     }
-                    break;
-                default:
-                    require_once(PATH::PAGE . "Error_Page.php");
-                    $page = new Error_Page(404);
-                    http_response_code(404);
-            }
-        }
-
-        // Select the model to load.
-        array_shift($this->params); // Remove first one, should be player_id
-        if (sizeof($this->params) == 0){
-            require_once(PATH::PAGE . "Home_Page.php");
-            $page = new Home_Page();
-        }
-        else{
-            if (strtoupper($this->params[0]) == "RUNES"){
-                require_once(PATH::PAGE . "Runes_Page.php");
-                $page = new Runes_Page();
-            }
-            elseif (strtoupper($this->params[0]) == "ENCHANTMENTS"){
-                require_once(PATH::PAGE . "Enchantments_Page.php");
-                $page = new Enchantments_Page();
-            }
-            elseif (strtoupper($this->params[0]) == "ARTIFACTS"){
-                require_once(PATH::PAGE . "Artifacts_Page.php");
-                $page = new Artifacts_Page();
-            }
-            elseif (strtoupper($this->params[0]) == "GUILD"){
-                require_once(PATH::PAGE . "Guild_Page.php");
-                $page = new Guild_Page();
-            }
-            elseif (strtoupper($this->params[0]) == "RUNS"){
-                require_once(PATH::PAGE . "Runs_Page.php");
-                $page = new Runs_Page();
-            }
-            elseif (strtoupper($this->params[0]) == "STATS"){
-                require_once(PATH::PAGE . "Stats_Page.php");
-                $page = new Stats_Page();
-            }
-            elseif (strtoupper($this->params[0]) == "TEAMS"){
-                require_once(PATH::PAGE . "Teams_Page.php");
-                $page = new Teams_Page();
-            }
-            elseif (strtoupper($this->params[0]) == "PROFILE"){
-                require_once(PATH::PAGE . "Profile_Page.php");
-                $page = new Profile_Page();
-            }
-            elseif (strtoupper($this->params[0]) == "UNITS"){
+                }
+                break;
+            case "API": // An API call
                 if (count($this->params) == 1){
-                    require_once(PATH::PAGE . "Units_Page.php");
-                    $page = new Units_Page();
+                    // TODO: Redirect to current version
                 }
                 else{
-                    require_once(PATH::PAGE . "Unit_Page.php");
-                    $page = new Unit_Page($this->params[1]);
+                    $version = strtolower($this->params[1]);
+                    $api_params = $this->params;
+                    array_shift($api_params);
+                    array_shift($api_params);
+                    switch ($version){
+                        case "v1":
+                            require_once(__DIR__ . "/API/v1/API_Controller.php");
+                            new API_Controller($this->params);
+                            break;
+                        default:
+                            require_once(PATH::PAGE . "Error_Page.php");
+                            $page = new Error_Page(404, "API version doesn't exist.");
+                            require_once($page->get_view());
+                            http_response_code(404);
+                            return 404;
+                    }
                 }
-            }
-            elseif (strtoupper($this->params[0]) == "MONSTERS"){
-                if (count($this->params) == 1){
-                    require_once(PATH::PAGE . "Monsters_Page.php");
-                    $page = new Monsters_Page();
+                break;
+            case "ACTION": // Execute an action
+                // If no action specified
+                if (sizeof($this->params) < 2){
+                    require_once(PATH::PAGE . "Error_Page.php");
+                    $page = new Error_Page(400, "No action specified.");
+                    require_once($page->get_view());
+                    http_response_code(400);
+                    return 400;
                 }
-                else{
-                    require_once(PATH::PAGE . "Monster_Page.php");
-                    $page = new Monster_Page($this->params[1]);
+                
+                // Process action
+                $action = strtoupper($this->params[1]);
+                switch ($action){
+                    // Special cases where the output must be printed:
+                    case "OPTIMIZE_LIST":
+                        require_once(PATH::ACTION . "Optimize_List_Action.php");
+                        $action = new Optimize_List_Action();
+                        break;
+                    case "OPTIMIZE_OPTIONS":
+                        require_once(PATH::ACTION . "Optimize_Options_Action.php");
+                        $action = new Optimize_Options_Action();
+                        break;
+                    case "CHANGE_GAME_MODE":
+                        require_once(PATH::ACTION . "Change_Game_Mode_Action.php");
+                        $action = new Change_Game_Mode_Action();
+                        break;
+                    case "LOGIN":
+                        require_once(PATH::ACTION . "Login_Action.php");
+                        $action = new Login_Action();
+                        break;
+                    case "LOGOUT":
+                        require_once(PATH::ACTION . "Logout_Action.php");
+                        $action = new Logout_Action();
+                        break;
+                    case "ADD_TEAM":
+                        require_once(PATH::ACTION . "Add_Team_Action.php");
+                        $action = new Add_Team_Action();
+                        break;
+                    case "EDIT_PROFILE":
+                        require_once(PATH::ACTION . "Edit_Profile_Action.php");
+                        $action = new Edit_Profile_Action();
+                        break;
+                    case "DELETE_TEAM":
+                        require_once(PATH::ACTION . "Delete_Team_Action.php");
+                        $action = new Delete_Team_Action();
+                        break;
+                    case "RATE_TEAM":
+                        require_once(PATH::ACTION . "rate_team.php");
+                        $action = new Rate_Team_Action();
+                        break;
+                    case "SAVE_TEAM":
+                        require_once(PATH::ACTION . "Save_Team_Action.php");
+                        $action = new Save_Team_Action();
+                        break;
+                    default:
+                        require_once(PATH::PAGE . "Error_Page.php");
+                        $page = new Error_Page(404, "Action not found");
+                        require_once($page->get_view());
+                        http_response_code(404);
+                        return;
                 }
-            }
-            elseif (strtoupper($this->params[0]) == "REPORT"){
-                if (count($this->params) == 1){
-                    require_once(PATH::PAGE . "Report_Page.php");
-                    $page = new Report_Page();
+                $action->execute();
+                if ($action->get_code() >= 400 && $action->get_code() < 500){
+                    require_once(PATH::PAGE . "Error_Page.php");
+                    $page = new Error_Page($action->get_code(), $action->get_message());
+                    require_once($page->get_view());
+                    http_response_code($action->get_code());
                 }
-                elseif (strtoupper($this->params[1]) == "SKILLUP"){
-                    require_once(PATH::PAGE . "Report_Skillup_Page.php");
-                    $page = new Report_Skillup_Page($this->params[1]);
+                if (strlen($action->get_output()) > 0){
+                    echo($action->get_output());
                 }
-                elseif (strtoupper($this->params[1]) == "RUNE_ENCHANTMENT"){
-                    require_once(PATH::PAGE . "Report_Rune_Enchantment_Page.php");
-                    $page = new Report_Rune_Enchantment_Page($this->params[1]);
+                return $action->get_code();
+                break;
+            case "PLAYER": // A player page
+                error_log("CASE PLAYER");
+                if ($this->context->get_player() == null || $this->context->get_player()->get_id() == null){
+                    require_once(PATH::PAGE . "Error_Page.php");
+                    $page = new Error_Page(404, "Player not found");
+                    require_once($page->get_view());
+                    http_response_code(404);
+                    return;
                 }
-            }
-            elseif (strtoupper($this->params[0]) == "OPTIMIZER"){
-                if (count($this->params) == 1){
-                    require_once(PATH::PAGE . "Optimizer_Page.php");
-                    $page = new Optimizer_Page();
+                if (
+                  $this->context->get_player()->is_public() == false &&
+                  (
+                      $this->context->get_user() == null ||
+                    $this->context->get_player()->get_user() != $this->context->get_user()->get_id()
+                  )
+                ){
+                    require_once(PATH::PAGE . "Error_Page.php");
+                    $page = new Error_Page(401, "Player profile set to private");
+                    require_once($page->get_view());
+                    http_response_code(401);
                 }
-                else{
-                    require_once(PATH::PAGE . "Optimizer_Unit_Page.php");
-                    $page = new Optimizer_Unit_Page($this->params[1]);
+                $subcommand = "";
+                if (sizeof($this->params) > 2){
+                    $subcommand = strtoupper($this->params[2]);
                 }
-            }
+                $page = null;
+                switch ($subcommand){
+                    case "": // Player profile
+                        require_once(PATH::PAGE . "Player_Page.php");
+                        $page = new Player_Page($this->context->get_player()->get_id());
+                        break;
+                    case "RUNES":
+                        require_once(PATH::PAGE . "Runes_Page.php");
+                        $page = new Runes_Page();
+                        break;
+                    case "ENCHANTMENTS":
+                        require_once(PATH::PAGE . "Enchantments_Page.php");
+                        $page = new Enchantments_Page();
+                        break;
+                    case "ARTIFACTS":
+                        require_once(PATH::PAGE . "Artifacts_Page.php");
+                        $page = new Artifacts_Page();
+                        break;
+                    case "GUILD":
+                        require_once(PATH::PAGE . "Guild_Page.php");
+                        $page = new Guild_Page();
+                        break;
+                    case "RUNS":
+                        require_once(PATH::PAGE . "Runs_Page.php");
+                        $page = new Runs_Page();
+                        break;
+                    /*case "STATS":
+                        require_once(PATH::PAGE . "Stats_Page.php");
+                        $page = new Stats_Page();
+                        break;*/
+                    case "TEAMS":
+                        require_once(PATH::PAGE . "Teams_Page.php");
+                        $page = new Teams_Page();
+                        break;
+                    case "UNITS":
+                        if (sizeof($this->params) > 3){
+                            require_once(PATH::PAGE . "Unit_Page.php");
+                            $page = new Unit_Page($this->params[3]);
+                        }
+                        else{
+                            require_once(PATH::PAGE . "Units_Page.php");
+                            $page = new Units_Page();
+                        }
+                        break;
+                    case "REPORT":
+                        if (sizeof($this->params) > 3){
+                            if (strtoupper($this->params[3]) == "SKILLUP"){
+                                require_once(PATH::PAGE . "Report_Skillup_Page.php");
+                                $page = new Report_Skillup_Page();
+                            }
+                            elseif (strtoupper($this->params[3]) == "RUNE_ENCHANTMENT"){
+                                require_once(PATH::PAGE . "Report_Rune_Enchantment_Page.php");
+                                $page = new Report_Rune_Enchantment_Page();
+                            }
+                        }
+                        else{
+                            require_once(PATH::PAGE . "Report_Page.php");
+                            $page = new Report_Page();
+                        }
+                        break;
+                    case "OPTIMIZER":
+                        if (sizeof($this->params) > 3){
+                            require_once(PATH::PAGE . "Optimizer_Unit_Page.php");
+                            $page = new Optimizer_Unit_Page($this->params[3]);
+                        }
+                        else{
+                            require_once(PATH::PAGE . "Optimizer_Page.php");
+                            $page = new Optimizer_Page();
+                        }
+                    break;
+                }
+                break;
+            case "LOGIN": // The login page
+                require_once(PATH::PAGE . "Login_Page.php");
+                $page = new Login_Page();
+                break;
+            case "REGISTER": // Registration page
+                require_once(PATH::PAGE . "Register_Page.php");
+                $page = new Register_Page();
+                break;
+            case "SEARCH": // Search results
+                require_once(PATH::PAGE . "Search_Page.php");
+                $page = new Search_Page();
+                break;
+            case "DUNGEON": // Dungeon static pages
+                require_once(PATH::PAGE . "Login_Page.php");
+                $page = new Dungeon_Page();
+                break;
+            case "BUILDING": // Building static pages
+                require_once(PATH::PAGE . "Building_Page.php");
+                $page = new Building_Page();
+                break;
+            case "FUSION": // Fusion static pages
+                require_once(PATH::PAGE . "Fusion_Page.php");
+                $page = new Fusion_Page();
+                break;
+            case "HELP": // Help static page
+                require_once(PATH::PAGE . "Help_Page.php");
+                $page = new Help_Page();
+                break;
         }
-
-        // Load the view, or set an error code.
-        if (isset($page)){
-            require_once($page->get_view());
-            return 200;
+        
+        // If page is not set, it's a not found.
+        if ($page == null){
+            require_once(PATH::PAGE . "Error_Page.php");
+            $page = new Error_Page(404, "Page not found");
         }
-        else{
+        
+        // Once the page is loaded, chack if it can be seen
+        // TODO verify
+        if (! $page->is_public() && $this->get_user() == null){
             require_once(PATH::PAGE . "Error_Page.php");
-            $page = new Error_Page(404);
-            require_once($page->get_view());
-            http_response_code(404);
-            return 404;
+            $page = new Error_Page(401, "Authentication required");
+            http_response_code(401);
         }
+        
+        if ($page->get_code() >= 400 && $page->get_code() < 600){
+            require_once(PATH::PAGE . "Error_Page.php");
+            $page = new Error_Page($page->get_code(), $page->get_message());
+            http_response_code($page->get_code());
+        }
+        
+        
+        // Load the view, or set an error code.
+        require_once($page->get_view());
+        http_response_code($page->get_code());
     }
 }

+ 78 - 0
application/action/Action.php

@@ -0,0 +1,78 @@
+<?php
+/**
+ * Action superclass file.
+ *
+ * Provides a class to be extended for individual actions.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package SWDB
+ */
+
+/**
+ * Action superclass.
+ *
+ * Every action must inherit from this one.
+ *
+ * @category Action
+ */
+abstract class Action{
+
+    /**
+     * @var int HTTP status code returned by the action. 0 before executing.
+     */
+    protected $code = 0;
+    
+    /**
+     * @var string HTTP status message. Empty before executing.
+     */
+    protected $message = "";
+    
+    /**
+     * @var string Output returned by the action. Empty before excuting.
+     */
+    protected $output = "";
+    
+    /**
+     * Executes the action.
+     * 
+     * To be implemented in the individual actions.
+     */
+    abstract public function execute();
+
+    /**
+     * Retrieves the HTTP status code returned by the action.
+     * 
+     * If the action has not been executed yet, the code will be 0.
+     *
+     * @return int HTTP status code.
+     */
+    public function get_code(){
+        return $this->code;
+    }
+    
+
+    /**
+     * Retrieves the HTTP status message returned by the action.
+     * 
+     * If the action has not been executed yet, the message will be empty.
+     *
+     * @return string HTTP status message.
+     */
+    public function get_message(){
+        return $this->message;
+    }
+    
+    /**
+     * Retrieves the output buffered by the action.
+     *
+     * If the action has not been executed yet or it didn't buffer anything, it will be empty.
+     *
+     * @return string Action output.
+     */
+    public function get_output(){
+        return $this->output;
+    }
+
+
+}

+ 291 - 0
application/action/Add_Team_Action.php

@@ -0,0 +1,291 @@
+<?php
+/**
+ * File for the team creation action.
+ *
+ * Implements an action function to be called from the {@see Controller}.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package SWDB
+ */
+
+require_once(PATH::ACTION . "Action.php");
+
+/**
+ * Logs the user out and redirects to the login page.
+ *
+ * Reads the POST parameters looking for the following KEYS:
+ *  player: The owner's player ID.
+ *  area_type: The area type ID ({@see AREA_TYPE_ID}).
+ *  area: The area ID.
+ *  stage: Optional, the stage ID.
+ *  difficulty: Optional, the difficulty ID ({@see DIFFICULTY_ID})
+ *  ids: Unit IDs of the party.
+ *  name: Team name.
+ *  description: Optional, team description.
+ * Validates the data and creates the team in the database.
+ * 
+ * @category Action
+ * @todo: Document properly
+ */
+class Add_Team_Action extends Action{
+    
+    /**
+     * Executes the action.
+     */
+    function execute(){
+
+        $player = filter_input(INPUT_POST, 'player');
+        $area_type = filter_input(INPUT_POST, 'area_type');
+        $area = filter_input(INPUT_POST, 'area');
+        $stage = filter_input(INPUT_POST, 'stage');
+        $difficulty = filter_input(INPUT_POST, 'difficulty');
+        if ($difficulty == ''){
+            $difficulty = null;
+        }
+        $ids = filter_input(INPUT_POST, 'ids');
+        $name = filter_input(INPUT_POST, 'name');
+        $leader_id = filter_input(INPUT_POST, 'leader');
+        $total_front = filter_input(INPUT_POST, 'front');
+        $description = filter_input(INPUT_POST, 'description');
+        $score = 0;
+        $id = preg_split("/[\s,]+/", $ids);
+        $id_count = sizeof($id);
+        if ($name == null || strlen($name) == 0){
+            $this->code = 400;
+            $this->message = "POST parameter 'name' not specified or invalid.";
+            return;
+        }
+        switch ($area_type){
+            case AREA_TYPE_ID::SCENARIO:
+                if ($stage < 0 || $stage > 7){
+                    $this->code = 400;
+                    $this->message = "Invalid stage for the selected area.";
+                    return;
+                }
+                if (!($difficulty == 0 || APPLICATION::valid_id("DIFFICULTY_ID", $difficulty) || $difficulty == DIFFICULTY_ID::EASY)){
+                    $this->code = 400;
+                    $this->message = "Invalid difficulty for the selected area.";
+                    return;
+                }
+                if ($id_count < 1 || $id_count > 4){
+                    $this->code = 400;
+                    $this->message = "Invalid number of units for the selected area.";
+                    return;
+                }
+                if (!APPLICATION::valid_id("SCENARIO_ID", $area)){
+                    $this->code = 400;
+                    $this->message = "Invalid area.";
+                    return;
+                }
+                break;
+            case AREA_TYPE_ID::CAIROS_DUNGEON:
+                if ($stage < 0){
+                    $this->code = 400;
+                    $this->message = "Invalid stage for the selected area.";
+                    return;
+                }
+                if ($stage > 12 && (DUNGEON_ID::GIANTS_KEEP == $area || DUNGEON_ID::DRAGONS_LAIR == $area || DUNGEON_ID::NECROPOLIS == $area)){
+                    $this->code = 400;
+                    $this->message = "Invalid stage for the selected area.";
+                    return;
+                }
+                if ($stage > 10 && ($area != DUNGEON_ID::GIANTS_KEEP && $area != DUNGEON_ID::DRAGONS_LAIR && $area != DUNGEON_ID::NECROPOLIS)){
+                    $this->code = 400;
+                    $this->message = "Invalid stage for the selected area.";
+                    return;
+                }
+                $difficulty = null;
+                if ($id_count < 1 || $id_count > 5){
+                    $this->code = 400;
+                    $this->message = "Invalid number of units for the selected area.";
+                    return;
+                }
+                if (!APPLICATION::valid_id("DUNGEON_ID", $area)){
+                    $this->code = 400;
+                    $this->message = "Invalid area.";
+                    return;
+                }
+                break;
+            case AREA_TYPE_ID::RIFT_DUNGEON:
+                $stage = null;
+                $difficulty = null;
+                if ($id_count < 1 || $id_count > 6){
+                    $this->code = 400;
+                    $this->message = "Invalid number of units for the selected area.";
+                    return;
+                }
+                if (!APPLICATION::valid_id("ELEMENTAL_RIFT_DUNGEON_ID", $area)){
+                    $this->code = 400;
+                    $this->message = "Invalid area.";
+                    return;
+                }
+                break;
+            case AREA_TYPE_ID::RIFT_RAID:
+                if ($stage < 0 || $stage > 5){
+                    $this->code = 400;
+                    $this->message = "Invalid stage for the selected area.";
+                    return;
+                }
+                $difficulty = null;
+                if ($id_count < 1 || $id_count > 6){
+                    $this->code = 400;
+                    $this->message = "Invalid number of units for the selected area.";
+                    return;
+                }
+                $area = null;
+                break;
+            case AREA_TYPE_ID::ARENA:
+                $area = null;
+                $stage = null;
+                $difficulty = null;
+                break;
+            case AREA_TYPE_ID::GUILD_WAR:
+                $area = null;
+                $stage = null;
+                $difficulty = null;
+                break;
+            case AREA_TYPE_ID::GUILD_SIEGE:
+                $area = null;
+                $stage = null;
+                $difficulty = null;
+                break;
+            case AREA_TYPE_ID::TARTARUS_LABYRINTH:
+                if ($difficulty != null && !APPLICATION::valid_id("DIFFICULTY_ID", $difficulty)){
+                    $this->code = 400;
+                    $this->message = "Invalid difficulty for the selected area.";
+                    return;
+                }
+                if ($id_count < 1 || $id_count > 5){
+                    $this->code = 400;
+                    $this->message = "Invalid number of units for the selected area.";
+                    return;
+                }
+                if (!APPLICATION::valid_id("LABYRINTH_STAGES_ID", $stage)){
+                    $this->code = 400;
+                    $this->message = "Invalid area.";
+                    return;
+                }
+                break;
+            case AREA_TYPE_ID::TRIAL_OF_ASCENSION:
+                if ($difficulty != null && $difficulty != DIFFICULTY_ID::NORMAL && $difficulty != DIFFICULTY_ID::HARD){
+                    $difficulty = null;
+                }
+                $stage = null;
+                if ($id_count < 1 || $id_count > 5){
+                    $this->code = 400;
+                    $this->message = "Invalid number of units for the selected area.";
+                    return;
+                }
+                $area = null;
+                break;
+            case AREA_TYPE_ID::DIMENSIONAL_HOLE:
+                if ($stage < 0 || $stage > 5){
+                    $this->code = 400;
+                    $this->message = "Invalid stage for the selected area.";
+                    return;
+                }
+                $difficulty = null;
+                if ($id_count < 1 || $id_count > 4){
+                    $this->code = 400;
+                    $this->message = "Invalid number of units for the selected area.";
+                    return;
+                }
+                if (!APPLICATION::valid_id("DUNGEON_ID", $area)){
+                    $this->code = 400;
+                    $this->message = "Invalid area.";
+                    return;
+                }
+                break;
+            case AREA_TYPE_ID::DIMENSIONAL_RIFT:
+                if ($difficulty != 0 && $difficulty != DIFFICULTY_ID::NORMAL && $difficulty != DIFFICULTY_ID::HARD){
+                    $this->code = 400;
+                    $this->message = "Invalid difficulty for the selected area.";
+                    return;
+                }
+                $stage = null;
+                if ($id_count < 1 || $id_count > 5){
+                    $this->code = 400;
+                    $this->message = "Invalid number of units for the selected area.";
+                    return;
+                }
+                $area = null;
+                break;
+            default:
+                return 400;
+        }
+        $unique_id = array_unique($id);
+        if ($id_count != sizeof($unique_id)){
+            $this->code = 400;
+            $this->message = "Duplicated units in the team.";
+            return;
+        }
+        $s = "SELECT CASE WHEN MAX(id) IS NULL THEN 1 ELSE MAX(id) + 1 END AS id FROM team;";
+        $q = get_context()->get_db()->query($s);
+        $r = $q->fetchArray(SQLITE3_ASSOC);
+        $team_id = $r["id"];
+        $statement = get_context()->get_db()->prepare("
+          INSERT INTO team (
+            player,
+            id,
+            name,
+            description,
+            area_type,
+            area,
+            stage,
+            difficulty,
+            score
+          ) VALUES (
+            :player,
+            :id,
+            :name,
+            :description,
+            :area_type,
+            :area,
+            :stage,
+            :difficulty,
+            :score
+          );
+        ");
+        $statement->bindValue(':player', $player, SQLITE3_TEXT);
+        $statement->bindValue(':id', $team_id, SQLITE3_TEXT);
+        $statement->bindValue(':name', $name, SQLITE3_TEXT);
+        $statement->bindValue(':description', $description, SQLITE3_TEXT);
+        $statement->bindValue(':area_type', $area_type, SQLITE3_TEXT);
+        $statement->bindValue(':area', $area, SQLITE3_TEXT);
+        $statement->bindValue(':stage', $stage, SQLITE3_TEXT);
+        $statement->bindValue(':difficulty', $difficulty, SQLITE3_TEXT);
+        $statement->bindValue(':score', $score, SQLITE3_TEXT);
+        $res = $statement->execute();
+        if (! $res){
+            $this->code = 500;
+            $this->message = "Internal error.";
+            return;
+        }
+        
+        $j = 0;
+        foreach (explode(',', $ids) as $i){
+            $leader = 0;
+            if ((string) $i == (string) $leader_id){
+                $leader = 1;
+            }
+            $front = 0;
+            if ($j < $total_front){
+                $front = 1;
+            }
+            $statement = get_context()->get_db()->prepare("
+              INSERT INTO team_unit (team, unit, leader, front)
+              VALUES (:team, :unit, :leader, :front);
+            ");
+            $statement->bindValue(':team', $team_id, SQLITE3_TEXT);
+            $statement->bindValue(':unit', $i, SQLITE3_TEXT);
+            $statement->bindValue(':leader', $leader, SQLITE3_TEXT);
+            $statement->bindValue(':front', $front, SQLITE3_TEXT);
+            $statement->execute();
+            $j ++;
+        }
+        $this->code = 204;
+        $this->message = "No content.";
+        return;
+    }
+}

+ 48 - 0
application/action/Change_Game_Mode_Action.php

@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * File for changing the game mode.
+ *
+ * Implements an action function to be called from the {@see Controller}.
+ *
+ * @category Action
+ */
+
+require_once(PATH::ACTION . "Action.php");
+
+/**
+ * Sets the score for a team.
+ *
+ * Reads the POST parameters looking for the following KEYS:
+ *  player
+ *  team
+ *  score
+ * Validates the data and sets the score.
+ */
+class Change_Game_Mode_Action extends Action{
+    
+    /**
+     * Executes the action.
+     * @todo: secure
+     */
+    public function execute(){
+        $player = filter_input(INPUT_GET, "player");
+        $mode = filter_input(INPUT_GET, "mode");
+        if (mode != GAME_MODE_ID::NORMAL && mode != GAME_MODE_ID::RTA){
+            $this->code = 400;
+            $this->message = "POST parameter 'mode' not specified or invalid.";
+            return;
+        }
+        $statement = get_context()->get_db()->prepare("
+          UPDATE player
+          SET mode = :mode
+          WHERE id = :id;
+        ");
+        $statement->bindValue(":mode", $mode, SQLITE3_TEXT);
+        $statement->bindValue(":id", $player, SQLITE3_TEXT);
+        $statement->execute();
+        $this->code = 204;
+        $this->message = "No content.";
+        return;
+    }
+}

+ 44 - 0
application/action/Delete_Team_Action.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * File for the team deletion action.
+ *
+ * Implements an action function to be called from the {@see Controller}.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package SWDB
+ */
+
+require_once(PATH::ACTION . "Action.php");
+
+/**
+ * Deletes a team.
+ *
+ * Reads the POST parameters looking for the following KEYS:
+ *  player: the team owner's player id.
+ *  id: the team id.
+ * 
+ * @category Action
+ */
+class Delete_Team_Action extends Action{
+
+    /**
+     * Executes the action.
+     * @todo secure
+     */
+    function execute(){
+    
+        $player = filter_input(INPUT_POST, 'player');
+        $id = filter_input(INPUT_POST, 'id');
+        $statement = get_context()->get_db()->prepare('DELETE FROM team WHERE player = :player AND id = :id;');
+        $statement->bindValue(':player', $player, SQLITE3_TEXT);
+        $statement->bindValue(':id', $id, SQLITE3_TEXT);
+        $statement->execute();
+        $statement = get_context()->get_db()->prepare('DELETE FROM team_unit WHERE team = :id;');
+        $statement->bindValue(':id', $id, SQLITE3_TEXT);
+        $statement->execute();
+        $this->code = 204;
+        $this->message = "No content.";
+        return;
+    }
+}

+ 97 - 0
application/action/Edit_Profile_Action.php

@@ -0,0 +1,97 @@
+<?php
+/**
+ * File for the profile edit action.
+ *
+ * Implements an action function to be called from the {@see Controller}.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package SWDB
+ */
+
+require_once(PATH::ACTION . "Action.php");
+
+/**
+ * Executes the profile update.
+ *
+ * Reads the POST parameters looking for the following KEYS:
+ *  mail: Optional, email to update.
+ *  pass: Optional, password to update.
+ *  currentPass: Mandatory for changing password, current one.
+ *  api: Any value, indicates that the api key is to be changed.
+ * Then it updates the selected info with the prameter vlue. Multiple itemss can be updated at the
+ * same time.
+ * 
+ * @return int|string 201 on success, HTTP error codes on failure. If the API key has been updated,
+ * the new key is returned instead.
+ * @category Action
+ */
+class Edit_Profile_Action extends Action{
+    
+    /**
+     * Executes the action.
+     */
+    function execute(){
+        // Get the current user ID.
+        $user = get_context()->get_user()->get_id();
+    
+        if (filter_input(INPUT_POST, "mail")){
+            $mail = filter_input(INPUT_POST, 'mail');
+            if (! filter_var($mail, FILTER_VALIDATE_EMAIL)){
+                $this->code = 400;
+                $this->message = "Invalid email address.";
+                return;
+            }
+            $s = get_context()->get_db()->prepare('UPDATE user SET mail = :mail WHERE id = :user;');
+            $s->bindValue(':user', $user, SQLITE3_TEXT);
+            $s->bindValue(':mail', $mail, SQLITE3_TEXT);
+            $this->code = 500;
+            $this->message = "Internal error.";
+            return;
+        }
+    
+        if (filter_input(INPUT_POST, "pass")){
+            $pass = sha1(filter_input(INPUT_POST, 'pass'));
+            $currentPass = sha1(filter_input(INPUT_POST, 'currentPass'));
+            $s = get_context()->get_db()->prepare('SELECT COUNT(id) AS count FROM user WHERE id = :id AND password = :currentPass;');
+            $s->bindValue(':id', $user, SQLITE3_TEXT);
+            $s->bindValue(':currentPass', $currentPass, SQLITE3_TEXT);
+            $q = $s->execute();
+            $r = $q->fetchArray(SQLITE3_ASSOC);
+            if ($r["count"] != 1){
+                $this->code = 500;
+                $this->message = "Internal error.";
+                return;
+            }
+            $s = get_context()->get_db()->prepare('UPDATE user SET password = :pass WHERE id = :id AND password = :currentPass;');
+            $s->bindValue(':id', $user, SQLITE3_TEXT);
+            $s->bindValue(':pass', $pass, SQLITE3_TEXT);
+            $s->bindValue(':currentPass', $currentPass, SQLITE3_TEXT);
+            if(!$s->execute()){
+                $this->code = 500;
+                $this->message = "Internal error.";
+                return;
+            }
+        }
+    
+        if (filter_input(INPUT_POST, "api")){
+            $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+            $charactersLength = 16;
+            $api = '';
+            for ($i = 0; $i < $charactersLength; $i++) {
+                $api .= $characters[rand(0, $charactersLength - 1)];
+            }
+            $s = get_context()->get_db()->prepare('UPDATE user SET api_key = :api WHERE id = :id;');
+            $s->bindValue(':id', $user, SQLITE3_TEXT);
+            $s->bindValue(':api', $api, SQLITE3_TEXT);
+            if(! $s->execute()){
+                $this->code = 500;
+                $this->message = "Internal error.";
+                return;
+            }
+            $this->code = 200;
+            $this->message = "OK.";
+            $this->output = $api;
+        }
+    }
+}

+ 103 - 0
application/action/Login_Action.php

@@ -0,0 +1,103 @@
+<?php
+/**
+ * File for the login action.
+ *
+ * Implements an action function to be called from the {@see Controller}.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package SWDB
+ */
+
+require_once(PATH::ACTION . "Action.php");
+
+/**
+ * Logs the user in.
+ *
+ * Reads the POST parameters looking for the following KEYS:
+ *  uname: username or email.
+ *  password: password.
+ * If there is a match, it generates a token and sets cookies and session variablesand
+ * redirects to the user homepage.
+ *
+ * @category Action
+ */
+class Login_Action extends Action{
+    
+    /**
+     * Executes the action.
+     */
+    function execute(){
+
+        if (!isset($_POST['uname'], $_POST['password'])){
+            $this->code = 401;
+            $this->message = "Invalid username / password.";
+            header("Location: " . URL::BASE . "login/");
+            return;
+        }
+        $uname = SQLite3::escapeString($_POST['uname']);
+        if (strlen($uname) == 0){
+            $this->code = 401;
+            $this->message = "Invalid username / password.";
+            header("Location: " . URL::BASE . "login/");
+            return;
+        }
+        $password = SQLite3::escapeString($_POST['password']);
+        if (strlen($password) == 0){
+            $this->code = 401;
+            $this->message = "Invalid username / password.";
+            header("Location: " . URL::BASE . "login/");
+            return;
+        }
+        $password = hash('sha256', $password);
+        $statement = get_context()->get_db()->prepare("
+          SELECT
+            id,
+            name
+          FROM user
+          WHERE
+            (
+              upper(name) = upper(:name) OR
+              upper(mail) = upper(:mail)
+            ) AND
+            password = :password;
+        ");
+        $statement->bindValue(":name", $uname, SQLITE3_TEXT);
+        $statement->bindValue(":mail", $uname, SQLITE3_TEXT);
+        $statement->bindValue(":password", $password, SQLITE3_TEXT);
+        $statement->execute();
+        $r = $statement->execute()->fetchArray(SQLITE3_ASSOC);
+        if (!$r){
+            $this->code = 401;
+            $this->message = "Invalid username / password.";
+            header("Location: " . URL::BASE . "login/");
+            return;
+        }
+        $user = $r["id"];
+    
+        // Generate token
+        //$_COOKIE["user_token"]) && isset($COOKIE["user_id"])
+        $token = bin2hex(random_bytes(32));
+        $expiry = time() + 5 * 24 * 60 * 60; // 5 days
+        $statement = get_context()->get_db()->prepare("
+          INSERT INTO token (user, token, expiry) VALUES
+          (:user, :token, :expiry);
+        ");
+        $statement->bindValue(":user", $user, SQLITE3_INTEGER);
+        $statement->bindValue(":token", hash('sha256', $token), SQLITE3_TEXT);
+        $statement->bindValue(":expiry", $expiry, SQLITE3_INTEGER);
+        $statement->execute();
+    
+        setcookie("user_id", $user, $expiry, "/");
+        setcookie("user_token", $token, $expiry, "/");
+        session_regenerate_id();
+        $_SESSION['session'] = true;
+        $_SESSION['user_id'] = $user;
+        //header("Location: " . URL::BASE . "/$user/");
+        // TODO: Get player ID here?
+        $this->code = 204;
+        $this->message = "No content.";
+        header("Location: " . URL::BASE);
+        return;
+    }
+}

+ 37 - 0
application/action/Logout_Action.php

@@ -0,0 +1,37 @@
+<?php
+/**
+ * File for the logout action.
+ *
+ * Implements an action function to be called from the {@see Controller}.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package SWDB
+ */
+
+require_once(PATH::ACTION . "Action.php");
+
+/**
+ * Logs the user out and redirects to the login page.
+ *
+ * Invalidates cookies and session variables.
+ * 
+ * @category Action
+ */
+class Logout_Action extends Action{
+    
+    /**
+     * Executes the action.
+     */
+    function execute(){
+        setcookie("user_id", null, time() - 3600);
+        setcookie("user_token", null, time() - 3600);
+        session_regenerate_id();
+        $_SESSION['session'] = false;
+        $_SESSION['user_id'] = "";
+        $this->code = 204;
+        $this->message = "No content.";
+        header("Location: /");
+        return;
+    }
+}

+ 215 - 0
application/action/Optimize_Options_Action.php

@@ -0,0 +1,215 @@
+<?php
+/**
+ * File for the team creation action.
+ *
+ * Implements an action function to be called from the {@see Controller}.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package SWDB
+ */
+
+require_once(PATH::ACTION . "Action.php");
+
+/**
+ * Executes the optimization action.
+ *
+ * Reads the POST parameters looking for the following KEYS:
+ *  player: The player ID.
+ *  unit: The unit ID.
+ *  tuning: 1: All +12; 2: Even +15, odd + 12; 3: All +15.
+ *  options: JSON with rune sets.
+ *
+ * @category Action
+ */
+class Optimize_Options_Action extends Action{
+    
+    /**
+     * Executes the action.
+     */
+    function execute(){
+
+        // Increase max execution time.
+        set_time_limit(60);
+    
+        $runes = [];
+    
+        $response = null;
+    
+        $player = filter_input(INPUT_POST, 'player');
+        if ($player == null){
+            $this->code = 400;
+            $this->message = "POST parameter 'player' not specified or invalid.";
+            return;
+        }
+    
+        $unit_id = filter_input(INPUT_POST, 'unit');
+        if ($unit_id == null){
+            $this->code = 400;
+            $this->message = "POST parameter 'unit' not specified or invalid.";
+            return;
+        }
+        $unit = new Unit($unit_id, true, $player);
+        $base_stats = $unit->get_base_stats();
+        $building_stats = $unit->get_building_stats();
+        $artifact_stats = $unit->get_artifact_stats();
+        //$total_stats = $unit->get_stats();
+    
+        $tuning = filter_input(INPUT_POST, 'tuning');
+        if ($tuning == null){
+            $this->code = 400;
+            $this->message = "POST parameter 'tuning' not specified or invalid.";
+            return;
+        }
+    
+        $options = json_decode(filter_input(INPUT_POST, 'options'), true);
+        if ($options == null){
+            $this->code = 400;
+            $this->message = "POST parameter 'options' not specified or invalid.";
+            return;
+        }
+        foreach ($options as $option){
+            $set = [
+                "runes" => [],
+                "stats" => [
+                    "atk" => $base_stats["atk"],
+                    "def" => $base_stats["def"],
+                    "hp"  => $base_stats["hp"],
+                    "spd" => $base_stats["spd"],
+                    "crr" => $base_stats["crr"],
+                    "crd" => $base_stats["crd"],
+                    "acc" => $base_stats["acc"],
+                    "res" => $base_stats["res"],
+                    "ehp" => $base_stats["ehp"],
+                    "dmg" => $base_stats["dmg"]
+                ]
+            ];
+            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->get_main_stat()->get_stat(),
+                    ($rune->get_innate_stat() != null ? $rune->get_innate_stat()->get_stat() : null),
+                    (sizeof($rune->get_substats()) > 0 ? $rune->get_substats()[0]->get_stat() : null),
+                    (sizeof($rune->get_substats()) > 1 ? $rune->get_substats()[1]->get_stat() : null),
+                    (sizeof($rune->get_substats()) > 2 ? $rune->get_substats()[2]->get_stat() : null),
+                    (sizeof($rune->get_substats()) > 3 ? $rune->get_substats()[3]->get_stat() : null)
+                ];
+                $main_value = $rune->get_main_stat()->get_value();
+                switch ($tuning){
+                    case 1: // All +12
+                        if ($rune->get_level() < 12){
+                            $main_value = $rune->get_main_stat_at_level(12);
+                        }
+                        break;
+                    case 2: // Even +15, Odd + 12
+                        if ($rune->get_level() < 15 && $rune->get_slot() % 2 == 0){
+                            $main_value = $rune->get_main_stat_at_level(15);
+                        }
+                        elseif ($rune->get_level() < 12 && $rune->get_slot() % 2 != 0){
+                            $main_value = $rune->get_main_stat_at_level(12);
+                        }
+                        break;
+                    case 3: // All +15
+                        if ($rune->get_level() < 15){
+                            $main_value = $rune->get_main_stat_at_level(15);
+                        }
+                        break;
+                }
+                $stat_values = [
+                    $main_value,
+                    ($stat_ids[1] != null ? $rune->get_innate_stat()->get_value() : 0),
+                    ($stat_ids[2] != null ? $rune->get_substats()[0]->get_value() : 0),
+                    ($stat_ids[3] != null ? $rune->get_substats()[1]->get_value() : 0),
+                    ($stat_ids[4] != null ? $rune->get_substats()[2]->get_value() : 0),
+                    ($stat_ids[5] != null ? $rune->get_substats()[3]->get_value() : 0)
+                ];
+                for ($i = 0; $i < 6; $i ++){
+                    switch ($stat_ids[$i]){
+                        case RUNE_STAT_ID::ATK:
+                            $set["stats"]["atk"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::ATK_P:
+                            $set["stats"]["atk"] += ceil($base_stats["atk"] * $stat_values[$i] / 100);
+                            break;
+                        case RUNE_STAT_ID::DEF:
+                            $set["stats"]["def"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::DEF_P:
+                            $set["stats"]["def"] += ceil($base_stats["def"] * $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($base_stats["hp"] * $stat_values[$i] / 100);
+                            break;
+                        case RUNE_STAT_ID::SPD:
+                            $set["stats"]["spd"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::CRR:
+                            $set["stats"]["crr"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::CRD:
+                            $set["stats"]["crd"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::ACC:
+                            $set["stats"]["acc"] += $stat_values[$i];
+                            break;
+                        case RUNE_STAT_ID::RES:
+                            $set["stats"]["res"] += $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"]["atk"];
+                $crr = $set["stats"]["crr"];
+                if ($crr > 100){
+                    $crr = 100;
+                }
+                $crd = $set["stats"]["crd"];
+                $edmg = ceil(($atk * (100 - $crr) / 100) + (($atk + ($atk * $crd / 100)) * $crr / 100));
+                $set["stats"]["dmg"] = $edmg;
+                //error_log("attack before extras: " . $set["stats"]["atk"]);
+                
+                // Add artifacts and buildings
+                $set["stats"]["atk"] += ($building_stats["atk"] + $artifact_stats["atk"]);
+                $set["stats"]["def"] += ($building_stats["def"] + $artifact_stats["def"]);
+                $set["stats"]["hp"]  += ($building_stats["hp"]  + $artifact_stats["hp"]);
+                $set["stats"]["spd"] += ($building_stats["spd"] + $artifact_stats["spd"]);
+                $set["stats"]["crr"] += ($building_stats["crr"] + $artifact_stats["crr"]);
+                $set["stats"]["crd"] += ($building_stats["crd"] + $artifact_stats["crd"]);
+                $set["stats"]["acc"] += ($building_stats["acc"] + $artifact_stats["acc"]);
+                $set["stats"]["res"] += ($building_stats["res"] + $artifact_stats["res"]);
+                $set["stats"]["ehp"] += ($building_stats["ehp"] + $artifact_stats["ehp"]);
+                $set["stats"]["dmg"] += ($building_stats["dmg"] + $artifact_stats["dmg"]);
+                //error_log("attack after extras: " . $set["stats"]["atk"]);
+                // Add to the array
+                array_push($set["runes"], $r);
+    
+            }
+            array_push($runes, $set);
+        }
+    
+        // Send back the response
+        $response = json_encode($runes);
+        if ($response == null){
+            $this->code = 204;
+            $this->message = "No combinations found.";
+        }
+        else{
+            $this->code = 200;
+            $this->message = "OK.";
+            $this->output = $response;
+        }
+        return;
+    }
+}

+ 551 - 0
application/action/Optmimize_List_Action.php

@@ -0,0 +1,551 @@
+<?php
+/**
+ * File for the rune optimization action.
+ *
+ * Implements an action function to be called from the {@see Controller}.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package SWDB
+ */
+
+require_once(PATH::ACTION . "Action.php");
+
+/**
+ * Selects the runes for optimization.
+ *
+ * Reads the POST parameters looking for the following KEYS:
+ *  player: Player ID.
+ *  mode: Game mode ({@see GAME_MODE_ID}).
+ *  unit: Unit ID.
+ *  source: Source of the runes to select (assigned, unassigned, ...).
+ *  tuning: 1: All +12; 2: Even +15, odd + 12; 3: All +15.
+ *  set[]: Selected rune sets ({@see RUNE_SET_ID}).
+ *  
+ * Then it selects all the runes matching the cryteria.
+ * 
+ * @category Action
+ */
+class Optimize_List_Action extends Action{
+
+    /**
+     * Executes the action.
+     */
+    public function execute(){
+    
+        // Increase max execution time.
+        set_time_limit(60);
+    
+        $candidates = [
+            1 => [],
+            2 => [],
+            3 => [],
+            4 => [],
+            5 => [],
+            6 => []
+        ];
+    
+        $response = null;
+    
+        $player = filter_input(INPUT_POST, 'player');
+        if ($player == null){
+            $this->code = 400;
+            $this->message = "POST parameter 'player' not specified.";
+            return;
+        }
+        $game_mode = GAME_MODE_ID::NORMAL;
+        if (filter_input(INPUT_POST, 'mode') == GAME_MODE_ID::RTA){
+            $game_mode = GAME_MODE_ID::RTA;
+        }
+    
+        $unit_id = filter_input(INPUT_POST, 'unit');
+        if ($unit_id == null){
+            $this->code = 400;
+            $this->message = "POST parameter 'unit' not specified.";
+            return;
+        }
+    
+        // Sets
+        $sets = [];
+        foreach ($_POST["set"] as $x){
+            array_push($sets, intval($x));
+        }
+        if (sizeof($sets) < 2 || sizeof($sets) > 3){
+            $this->code = 400;
+            $this->message = "POST parameter 'set' not specified or invalid. Two or three rune sets must be specified. Broken sets are not supported.";
+            return;
+        }
+        
+        $focus = [];
+        if (isset($_POST["focus_0"]) && intval($_POST["focus_0"]) > 0){
+            array_push($focus, intval($_POST["focus_0"]));
+        }
+        if (isset($_POST["focus_1"]) && intval($_POST["focus_1"]) > 0){
+            array_push($focus, intval($_POST["focus_1"]));
+        }
+        if (isset($_POST["focus_2"]) && intval($_POST["focus_2"]) > 0){
+            array_push($focus, intval($_POST["focus_2"]));
+        }
+        // Remove doubles and sort. The order will be HP, ATK, DEF, SPD, CRR, CRD, RES, ACC
+        $focus = array_unique($focus, SORT_NUMERIC);
+    
+        // 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){
+            $this->code = 400;
+            $this->message = "POST parameter 'source' not specified.";
+            return;
+        }
+    
+        $tuning = filter_input(INPUT_POST, 'tuning');
+        if ($tuning == null){
+            $this->code = 400;
+            $this->message = "POST parameter 'tuning' not specified.";
+            return;
+        }
+    
+        // Instantiate the unit
+        $unit = new Unit($unit_id);
+    
+        // Get build query:
+        $base_s = "
+          SELECT
+            id,
+            type,
+            slot,
+            level,
+            stars,
+            (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::MAIN . " ) AS main_stat,
+            (SELECT value FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::MAIN . " ) AS main_stat_value,
+            (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::INNATE . " ) AS innate_stat,
+            (SELECT value FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::INNATE . " ) AS innate_stat_value,
+            (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_1 . " ) AS substat_1,
+            (SELECT value + grind FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_1 . " ) AS substat_1_value,
+            (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_2 . " ) AS substat_2,
+            (SELECT value + grind FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_2 . " ) AS substat_2_value,
+            (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_3 . " ) AS substat_3,
+            (SELECT value + grind FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_3 . " ) AS substat_3_value,
+            (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_4 . " ) AS substat_4,
+            (SELECT value + grind FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_4 . " ) AS substat_4_value
+          FROM rune
+          WHERE
+            player = :player AND
+            type IN (
+        ";
+        foreach ($sets as $set){
+            $base_s .= ($set . ",");
+        }
+        $base_s .= "-1) AND ";
+        switch ($source){
+            case 0: // Storage only (or itself)
+                $base_s .= "
+                  (
+                    rune.id IN (SELECT DISTINCT rune FROM unit_rune WHERE unit = :unit_id AND rta = " . $game_mode . ")
+                    OR
+                    rune.id NOT IN (SELECT DISTINCT rune FROM unit_rune WHERE rta = " . $game_mode . ")
+                  )";
+                break;
+            case 1: // Units in no teams (or itself)
+                $base_s .= "
+                  (
+                    rune.id IN (SELECT DISTINCT rune FROM unit_rune WHERE unit = :unit_id AND rta = " . $game_mode . ")
+                    OR rune.id NOT IN (SELECT DISTINCT rune FROM unit_rune WHERE rta = " . $game_mode . ")
+                    OR rune.id NOT IN (SELECT DISTINCT rune FROM unit_rune WHERE rta = " . $game_mode . " AND unit NOT IN (SELECT DISTINCT unit FROM team_unit))
+                  )
+                ";
+                break;
+            case 2: // Units in teams with 0 score (or itself)
+                $base_s .= "
+                  (
+                    rune.id IN (SELECT DISTINCT rune FROM unit_rune WHERE unit = :unit_id AND rta = " . $game_mode . ")
+                    OR rune.id NOT IN (SELECT DISTINCT rune FROM unit_rune WHERE rta = " . $game_mode . ")
+                    OR rune.id NOT IN (
+                      SELECT DISTINCT rune 
+                      FROM unit_rune 
+                      WHERE
+                        rta = " . $game_mode . "
+                        AND unit 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 (:stat_0, :stat_1, :stat_2) ";
+        $s_main = [
+            " main_stat = :stat_0 ",
+            " main_stat = :stat_1 ",
+            " main_stat = :stat_2 ",
+        ];
+        
+        // Process focuses
+        $focus_stats = [];
+        foreach ($focus as $f){
+            switch ($f){
+                case STAT_ID::HP:
+                    array_push($focus_stats, RUNE_STAT_ID::HP);
+                    array_push($focus_stats, RUNE_STAT_ID::HP_P);
+                    break;
+                case STAT_ID::ATK:
+                    array_push($focus_stats, RUNE_STAT_ID::ATK);
+                    array_push($focus_stats, RUNE_STAT_ID::ATK_P);
+                    break;
+                case STAT_ID::DEF:
+                    array_push($focus_stats, RUNE_STAT_ID::DEF);
+                    array_push($focus_stats, RUNE_STAT_ID::DEF_P);
+                    break;
+                case STAT_ID::SPD:
+                    array_push($focus_stats, RUNE_STAT_ID::SPD);
+                    break;
+                case STAT_ID::CRR:
+                    array_push($focus_stats, RUNE_STAT_ID::CRR);
+                    break;
+                case STAT_ID::CRD:
+                    array_push($focus_stats, RUNE_STAT_ID::CRD);
+                    break;
+                case STAT_ID::ACC:
+                    array_push($focus_stats, RUNE_STAT_ID::ACC);
+                    break;
+                case STAT_ID::RES:
+                    array_push($focus_stats, RUNE_STAT_ID::RES);
+                    break;
+            }
+        }
+    
+        $focus_cond = ["", "", "", "", "", "", ""];
+        if (sizeof($focus) > 0){
+            
+            // Slot 1, no ATK flat, no DEF flat, no DEF %
+            $focus_slot = $focus_stats;
+            /*if (array_search(RUNE_STAT_ID::ATK, $focus_slot) !== false){
+                $focus_slot = array_diff($focus_slot, [RUNE_STAT_ID::ATK])//array_splice($focus_slot, array_search(RUNE_STAT_ID::ATK, $focus_slot), 1);
+            }
+            if (array_search(RUNE_STAT_ID::DEF, $focus_slot) !== false){
+                $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::DEF, $focus_slot), 1);
+            }
+            if (array_search(RUNE_STAT_ID::DEF_P, $focus_slot) !== false){
+                $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::DEF_P, $focus_slot), 1);
+            }*/
+            $focus_slot = array_diff($focus_slot, [RUNE_STAT_ID::ATK, RUNE_STAT_ID::DEF, RUNE_STAT_ID::DEF_P]);
+            if (sizeof($focus_slot) > 0){
+                $focus_cond[1] .= "
+                  -- FOCUS 1
+                  AND (
+                    rune.id IN (
+                      SELECT DISTINCT rune FROM rune_stat WHERE slot != :slot_main AND stat IN
+                      (
+                ";
+                foreach ($focus_slot as $stat){
+                    $focus_cond[1] .= $stat . ", ";
+                }
+                $focus_cond[1] = rtrim($focus_cond[1], ", ");
+                $focus_cond[1] .= "
+                      )
+                    )
+                  )  -- END FOCUS 1
+                ";
+            }
+            
+            // Slot 3, no ATK flat, no ATK %, no DEF flat.
+            $focus_slot = $focus_stats;
+            /*if (array_search(RUNE_STAT_ID::ATK, $focus_slot) !== false){
+                $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::ATK, $focus_slot), 1);
+            }
+            if (array_search(RUNE_STAT_ID::ATK_P, $focus_slot) !== false){
+                $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::ATK_P, $focus_slot), 1);
+            }
+            if (array_search(RUNE_STAT_ID::DEF, $focus_slot) !== false){
+                $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::DEF, $focus_slot), 1);
+            }*/
+            $focus_slot = array_diff($focus_slot, [RUNE_STAT_ID::ATK, RUNE_STAT_ID::ATK_P, RUNE_STAT_ID::DEF]);
+            if (sizeof($focus_slot) > 0){
+                $focus_cond[3] .= "
+                  -- FOCUS 3
+                  AND (
+                    rune.id IN (
+                      SELECT DISTINCT rune FROM rune_stat WHERE slot != :slot_main AND stat IN
+                      (
+                ";
+                foreach ($focus_slot as $stat){
+                    $focus_cond[3] .= $stat . ", ";
+                }
+                $focus_cond[3] = rtrim($focus_cond[3], ", ");
+                $focus_cond[3] .= "
+                      )
+                    )
+                  )  -- END FOCUS 3
+                ";
+            }
+            
+            // Slot 5, no HP flat.
+            $focus_slot = $focus_stats;
+            /*if (array_search(RUNE_STAT_ID::HP, $focus_slot) !== false){
+                $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::HP, $focus_slot), 1);
+            }*/
+            $focus_slot = array_diff($focus_slot, [RUNE_STAT_ID::HP]);
+            if (sizeof($focus_slot) > 0){
+                $focus_cond[5] .= "
+                  -- FOCUS 5
+                  AND (
+                    rune.id IN (
+                      SELECT DISTINCT rune FROM rune_stat WHERE slot != :slot_main AND stat IN
+                      (
+                ";
+                foreach ($focus_slot as $stat){
+                    $focus_cond[5] .= $stat . ", ";
+                }
+                $focus_cond[5] = rtrim($focus_cond[5], ", ");
+                $focus_cond[5] .= "
+                      )
+                    )
+                  ) -- END FOCUS 5
+                ";
+            }
+            
+            // TODO: Focus for even slots.
+            // Slot 2, all stat, but include main
+            // Slot 5, no HP flat.
+            $focus_slot = $focus_stats;
+            if (sizeof($focus_slot) > 0){
+                $focus_cond[2] .= "
+                  -- FOCUS EVEN
+                  AND (
+                    rune.id IN (
+                      SELECT DISTINCT rune FROM rune_stat WHERE stat IN
+                      (
+                ";
+                foreach ($focus_slot as $stat){
+                    $focus_cond[2] .= $stat . ", ";
+                }
+                $focus_cond[2] = rtrim($focus_cond[2], ", ");
+                $focus_cond[2] .= "
+                      )
+                    )
+                  ) -- END FOCUS EVEN
+                ";
+            }
+            
+        }
+        
+        //TODO DEBUG
+        //$focus_cond[2] = "";
+    
+        $s = ["", "", "", "", "", "", ""];
+        $s[1] = $base_s . " AND slot = 1 " . $focus_cond[1] . $s_order . " LIMIT :limit_odd;";
+        $s[3] = $base_s . " AND slot = 3 " . $focus_cond[3] . $s_order . " LIMIT :limit_odd;";
+        $s[5] = $base_s . " AND slot = 5 " . $focus_cond[5] . $s_order . " LIMIT :limit_odd;";
+        $s[2] = $base_s . " AND slot = 2 AND " . $s_main[0] . $focus_cond[2] . $s_order . " LIMIT :limit_even;";
+        $s[4] = $base_s . " AND slot = 4 AND " . $s_main[1] . $focus_cond[2] . $s_order . " LIMIT :limit_even;";
+        $s[6] = $base_s . " AND slot = 6 AND " . $s_main[2] . $focus_cond[2] . $s_order . " LIMIT :limit_even;";
+    
+        $total_candidates = 0;
+        for ($i = 1; $i <= 6; $i ++){
+            //error_log($s[$i]);
+            $statement = get_context()->get_db()->prepare($s[$i]);
+            $statement->bindValue(':player', $player, SQLITE3_TEXT);
+            $statement->bindValue(':unit_id', $unit_id, SQLITE3_TEXT);
+            $statement->bindValue(':stat_0', $stats[0], SQLITE3_TEXT);
+            $statement->bindValue(':stat_1', $stats[1], SQLITE3_TEXT);
+            $statement->bindValue(':stat_2', $stats[2], SQLITE3_TEXT);
+            $statement->bindValue(':slot_main', RUNE_STAT_SLOT_ID::MAIN, SQLITE3_INTEGER);
+            $statement->bindValue(':limit_even', 150, SQLITE3_INTEGER);
+            $statement->bindValue(':limit_odd', 100, SQLITE3_INTEGER);
+            //error_log($statement->getSQL(true));
+            $q = $statement->execute();
+            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]);
+    
+        // Build response
+        $response = json_encode($candidates);
+        if ($response == null){
+            $this->code = 201;
+            $this->message = "No valid combinations found";
+            return;
+        }
+        else{
+            $this->code = 200;
+            $this->message = "OK";
+            $this->output = $response;
+            return;
+        }
+    }
+    
+    /**
+     * Creates an array with the stats provided by a rune on a unit.
+     *
+     * @param Unit $unit Target unit.
+     * @param int $r Rune main stat ID.
+     * @param int $stars Rune stars.
+     * @return int Value of the main stat at the selected level.
+     */
+    private 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->get_base_stats()["atk"] * $stat["VALUE"] / 100);
+                    break;
+                case RUNE_STAT_ID::DEF:
+                    $rune["DEF"] += $stat["VALUE"];
+                    break;
+                case RUNE_STAT_ID::DEF_P:
+                    $rune["DEF"] += ($unit->get_base_stats()["def"] * $stat["VALUE"] / 100);
+                    break;
+                case RUNE_STAT_ID::HP:
+                    $rune["HP"] += $stat["VALUE"];
+                    break;
+                case RUNE_STAT_ID::HP_P:
+                    $rune["HP"] += ($unit->get_base_stats()["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.
+     */
+    private 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;
+        }
+    }
+}

+ 59 - 0
application/action/Rate_Team_Action.php

@@ -0,0 +1,59 @@
+<?php
+/**
+ * File for the team score modification action.
+ *
+ * Implements an action function to be called from the {@see Controller}.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package SWDB
+ */
+
+require_once(PATH::ACTION . "Action.php");
+
+/**
+ * Sets the score for a team.
+ *
+ * Reads the POST parameters looking for the following KEYS:
+ *  player: Owner's player ID.
+ *  team: Team ID.
+ *  score: New score.
+ * Validates the data and sets the score.
+ *
+ * @category Action
+ */
+class Rate_Team_Action extends Action{
+    
+    /**
+     * Executes the action.
+     */
+    function execute(){
+        $player_id = filter_input(INPUT_GET, "player");
+        $statement = get_context()->get_db()->prepare("
+            SELECT count(user) AS c FROM player WHERE id = :player AND user = :user");
+        $statement->bindValue(":player", $player_id, SQLITE3_TEXT);
+        $statement->bindValue(":user", get_context()->get_user()->get_id(), SQLITE3_TEXT);
+        if ($statement->execute()->fetchArray(SQLITE3_ASSOC)["c"] != 1){
+            // The user doesn't own the team.
+            $this->code = 401;
+            $this->message = "Unauthorized.";
+            return;
+        }
+        $team = filter_input(INPUT_GET, "team");
+        $score = intval(filter_input(INPUT_GET, "score"));
+        $statement = get_context()->get_db()->prepare("
+          UPDATE team
+          SET score = :score
+          WHERE
+            player = :player AND
+            id = :team;
+        ");
+        $statement->bindValue(":score", $score, SQLITE3_TEXT);
+        $statement->bindValue(":player", $player_id, SQLITE3_TEXT);
+        $statement->bindValue(":team", $team, SQLITE3_TEXT);
+        $statement->execute();
+        $this->code = 204;
+        $this->message = "No content";
+        return;
+    }
+}

+ 120 - 0
application/action/Save_Team_Action.php

@@ -0,0 +1,120 @@
+<?php
+/**
+ * File for the run team saving action.
+ *
+ * Implements an action function to be called from the {@see Controller}.
+ *
+ * @author Iñigo Valentin <i@inigovalentin.com>
+ * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
+ * @package SWDB
+ */
+
+require_once(PATH::ACTION . "Action.php");
+
+/**
+ * Saves a run party as a team.
+ *
+ * Reads the POST parameters looking for the following KEYS:
+ *  player: Player ID.
+ *  run: Run ID.
+ *  name: New team name.
+ *  description: New team description.
+ *  score: New team score, defaults to 0.
+ * Validates the data and creates the team in the database.
+ * 
+ * @category Action
+ */
+class Save_Team_Action extends Action{
+    
+    /**
+     * Executes the action.
+     */
+    function execute(){
+        $player = filter_input(INPUT_POST, 'player');
+        $run = filter_input(INPUT_POST, 'run');
+        $name = filter_input(INPUT_POST, 'name');
+        $description = filter_input(INPUT_POST, 'description');
+        $score = intval(filter_input(INPUT_POST, 'score'));
+        if ($name == null || strlen($name) == 0){
+            return 400;
+        }
+        $s = "SELECT CASE WHEN MAX(id) IS NULL THEN 1 ELSE MAX(id) + 1 END AS id FROM team;";
+        $q = get_context()->get_db()->query($s);
+        $r = $q->fetchArray(SQLITE3_ASSOC);
+        $team_id = $r["id"];
+        $statement = get_context()->get_db()->prepare("
+          SELECT
+            run.area AS area,
+            run.stage AS stage,
+            run.difficulty AS difficulty,
+            area.type AS area_type
+          FROM
+            run,
+            area
+          WHERE
+            run.player = :player AND
+            run.id = :run AND
+            area.id = run.area AND
+            run.helper = 0;
+        ");
+        $statement->bindValue(':player', $player, SQLITE3_TEXT);
+        $statement->bindValue(':run', $run, SQLITE3_TEXT);
+        $r = $statement->execute()->fetchArray(SQLITE3_ASSOC);
+        if (! $r){
+            $this->code = 204;
+            $this->message = "Run not found";
+            return;
+        }
+        $statement = get_context()->get_db()->prepare("
+          INSERT INTO team (
+            player,
+            id,
+            name,
+            description,
+            area_type,
+            area,
+            stage,
+            difficulty,
+            score
+          ) VALUES (
+            :player,
+            :id,
+            :name,
+            :description,
+            :area_type,
+            :area,
+            :stage,
+            :difficulty,
+            :score);
+          ");
+        $statement->bindValue(':player', $player, SQLITE3_TEXT);
+        $statement->bindValue(':id', $team_id, SQLITE3_TEXT);
+        $statement->bindValue(':name', $name, SQLITE3_TEXT);
+        $statement->bindValue(':description', $description, SQLITE3_TEXT);
+        $statement->bindValue(':area_type', $r["area_type"], SQLITE3_TEXT);
+        $statement->bindValue(':area', $r["area"], SQLITE3_TEXT);
+        $statement->bindValue(':stage', $r["stage"], SQLITE3_TEXT);
+        $statement->bindValue(':difficulty', $r["difficulty"], SQLITE3_TEXT);
+        $statement->bindValue(':score', $score, SQLITE3_INTEGER);
+        $res = $statement->execute();
+        if (! $res){
+            $this->code = 204;
+            $this->message = "Internal error";
+            return;
+        }
+        $s = "SELECT unit FROM data.run_party WHERE run = $run AND unit IS NOT NULL;";
+        $q = get_context()->get_db()->query($s);
+        while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+            $statement = get_context()->get_db()->prepare("
+              INSERT INTO data.team_unit (team, unit)
+              VALUES (:team, :unit)
+            ");
+            $statement->bindValue(':team', $team_id, SQLITE3_TEXT);
+            $statement->bindValue(':unit', $r["unit"], SQLITE3_TEXT);
+            $statement->execute();
+        }
+        $this->code = 204;
+        $this->message = "No content";
+        return;
+    }
+}

+ 0 - 40
application/action/change_game_mode.php

@@ -1,40 +0,0 @@
-<?php
-
-/**
- * File for changing the game mode.
- *
- * Implements an action function to be called from the {@see Controller}.
- *
- * @category Action
- */
-
-/**
- * Sets the score for a team.
- *
- * Reads the POST parameters looking for the following KEYS:
- *  player
- *  team
- *  score
- * Validates the data and sets the score.
- *
- * @return int 201 on success, HTTP error codes on failure.
- * @category Action
- */
-function action(){
-    $player = filter_input(INPUT_GET, "player");
-    $mode = filter_input(INPUT_GET, "mode");
-    if (mode != GAME_MODE_ID::NORMAL && mode != GAME_MODE_ID::RTA){
-        return 400;
-    }
-    $statement = get_context()->get_db()->prepare("
-      UPDATE player
-      SET mode = :mode
-      WHERE id = :id;
-    ");
-    $statement->bindValue(":mode", $mode, SQLITE3_TEXT);
-    $statement->bindValue(":id", $player, SQLITE3_TEXT);
-    $statement->execute();
-    header("HTTP/1.1 201 OK");
-    die();
-    return 201;
-}

+ 0 - 34
application/action/delete_team.php

@@ -1,34 +0,0 @@
-<?php
-/**
- * File for the team deletion action.
- *
- * Implements an action function to be called from the {@see Controller}.
- *
- * @author Iñigo Valentin <i@inigovalentin.com>
- * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
- * @package SWDB
- */
-
-/**
- * Deletes a team.
- *
- * Reads the POST parameters looking for the following KEYS:
- *  player: the team owner's player id.
- *  id: the team id.
- * 
- * @return int 201 on success, HTTP error status codes on failure.
- * @category Action
- */
-function action(){
-
-    $player = filter_input(INPUT_POST, 'player');
-    $id = filter_input(INPUT_POST, 'id');
-    $statement = get_context()->get_db()->prepare('DELETE FROM team WHERE player = :player AND id = :id;');
-    $statement->bindValue(':player', $player, SQLITE3_TEXT);
-    $statement->bindValue(':id', $id, SQLITE3_TEXT);
-    $statement->execute();
-    $statement = get_context()->get_db()->prepare('DELETE FROM team_unit WHERE team = :id;');
-    $statement->bindValue(':id', $id, SQLITE3_TEXT);
-    $statement->execute();
-    return 201;
-}

+ 0 - 89
application/action/edit_profile.php

@@ -1,89 +0,0 @@
-<?php
-/**
- * File for the profile edit action.
- *
- * Implements an action function to be called from the {@see Controller}.
- *
- * @author Iñigo Valentin <i@inigovalentin.com>
- * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
- * @package SWDB
- */
-
-/**
- * Executes the profile update.
- *
- * Reads the POST parameters looking for the following KEYS:
- *  mail: Optional, email to update.
- *  pass: Optional, password to update.
- *  currentPass: Mandatory for changing password, current one.
- *  api: Any value, indicates that the api key is to be changed.
- * Then it updates the selected info with the prameter vlue. Multiple itemss can be updated at the
- * same time.
- * 
- * @return int|string 201 on success, HTTP error codes on failure. If the API key has been updated,
- * the new key is returned instead.
- * @category Action
- */
-function action(){
-
-    $response = null;
-
-    // Get the current user ID.
-    $user = get_context()->get_user()->get_id();
-
-    if (filter_input(INPUT_POST, "mail")){
-        $mail = filter_input(INPUT_POST, 'mail');
-        if (!filter_var($mail, FILTER_VALIDATE_EMAIL)){
-            return -1;
-        }
-        $s = get_context()->get_db()->prepare('UPDATE user SET mail = :mail WHERE id = :user;');
-        $s->bindValue(':user', $user, SQLITE3_TEXT);
-        $s->bindValue(':mail', $mail, SQLITE3_TEXT);
-        if(!$s->execute()){
-            return 500;
-        }
-    }
-
-    if (filter_input(INPUT_POST, "pass")){
-        $pass = sha1(filter_input(INPUT_POST, 'pass'));
-        $currentPass = sha1(filter_input(INPUT_POST, 'currentPass'));
-        $s = get_context()->get_db()->prepare('SELECT COUNT(id) AS count FROM user WHERE id = :id AND password = :currentPass;');
-        $s->bindValue(':id', $user, SQLITE3_TEXT);
-        $s->bindValue(':currentPass', $currentPass, SQLITE3_TEXT);
-        $q = $s->execute();
-        $r = $q->fetchArray(SQLITE3_ASSOC);
-        if ($r["count"] != 1){
-            return -500;
-        }
-        $s = get_context()->get_db()->prepare('UPDATE user SET password = :pass WHERE id = :id AND password = :currentPass;');
-        $s->bindValue(':id', $user, SQLITE3_TEXT);
-        $s->bindValue(':pass', $pass, SQLITE3_TEXT);
-        $s->bindValue(':currentPass', $currentPass, SQLITE3_TEXT);
-        if(!$s->execute()){
-            return -4;
-        }
-    }
-
-    if (filter_input(INPUT_POST, "api")){
-        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
-        $charactersLength = 16;
-        $api = '';
-        for ($i = 0; $i < $charactersLength; $i++) {
-            $api .= $characters[rand(0, $charactersLength - 1)];
-        }
-        $s = get_context()->get_db()->prepare('UPDATE user SET api_key = :api WHERE id = :id;');
-        $s->bindValue(':id', $user, SQLITE3_TEXT);
-        $s->bindValue(':api', $api, SQLITE3_TEXT);
-        if(! $s->execute()){
-            return 500;
-        }
-        $response = $api;
-    }
-
-    if ($response == null){
-        return 201;
-    }
-    else{
-        return $response;
-    }
-}

+ 0 - 94
application/action/login.php

@@ -1,94 +0,0 @@
-<?php
-/**
- * File for the login action.
- *
- * Implements an action function to be called from the {@see Controller}.
- *
- * @author Iñigo Valentin <i@inigovalentin.com>
- * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
- * @package SWDB
- */
-
-/**
- * Logs the user in.
- *
- * Reads the POST parameters looking for the following KEYS:
- *  uname: username or email.
- *  password: password.
- * If there is a match, it generates a token and sets cookies and session variablesand
- * redirects to the user homepage.
- *
- * @return int 201 on success, HTTP error codes on failure.
- * @category Action
- */
-function action(){
-
-    if (!isset($_POST['uname'], $_POST['password'])){
-        header("Location: " . URL::BASE);
-        //exit();
-        return 400;
-    }
-    $uname = SQLite3::escapeString($_POST['uname']);
-    if (strlen($uname) == 0){
-        header("Location: " . URL::BASE);
-        //exit();
-        return 400;
-    }
-    $password = SQLite3::escapeString($_POST['password']);
-    if (strlen($password) == 0){
-        header("Location: " . URL::BASE);
-        return 400;
-        //exit();
-    }
-    $password = hash('sha256', $password);
-    $statement = get_context()->get_db()->prepare("
-      SELECT
-        id,
-        name
-      FROM user
-      WHERE
-        (
-          upper(name) = upper(:name) OR
-          upper(mail) = upper(:mail)
-        ) AND
-        password = :password;
-    ");
-    $statement->bindValue(":name", $uname, SQLITE3_TEXT);
-    $statement->bindValue(":mail", $uname, SQLITE3_TEXT);
-    $statement->bindValue(":password", $password, SQLITE3_TEXT);
-    $statement->execute();
-    $r = $statement->execute()->fetchArray(SQLITE3_ASSOC);
-    if (!$r){
-        header("Location: " . URL::BASE);
-        //exit();
-        //die();
-        return 400;
-    }
-    $user = $r["id"];
-
-    // Generate token
-    //$_COOKIE["user_token"]) && isset($COOKIE["user_id"])
-    $token = bin2hex(random_bytes(32));
-    $expiry = time() + 5 * 24 * 60 * 60; // 5 days
-    $statement = get_context()->get_db()->prepare("
-      INSERT INTO token (user, token, expiry) VALUES
-      (:user, :token, :expiry);
-    ");
-    $statement->bindValue(":user", $user, SQLITE3_INTEGER);
-    $statement->bindValue(":token", hash('sha256', $token), SQLITE3_TEXT);
-    $statement->bindValue(":expiry", $expiry, SQLITE3_INTEGER);
-    $statement->execute();
-
-    setcookie("user_id", $user, $expiry, "/");
-    setcookie("user_token", $token, $expiry, "/");
-    session_regenerate_id();
-    $_SESSION['session'] = true;
-    $_SESSION['user_id'] = $user;
-    //header("Location: " . URL::BASE . "/$user/");
-    // TODO: Get player ID here?
-    header("Location: " . URL::BASE);
-    exit();
-    die();
-    return 201;
-}
-?>

+ 0 - 26
application/action/logout.php

@@ -1,26 +0,0 @@
-<?php
-/**
- * File for the logout action.
- *
- * Implements an action function to be called from the {@see Controller}.
- *
- * @author Iñigo Valentin <i@inigovalentin.com>
- * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
- * @package SWDB
- */
-
-/**
- * Logs the user out and redirects to the login page.
- *
- * Invalidates cookies and session variables.
- * 
- * @category Action
- */
-function action(){
-    setcookie("user_id", null, time() - 3600);
-    setcookie("user_token", null, time() - 3600);
-    session_regenerate_id();
-    $_SESSION['session'] = false;
-    $_SESSION['user_id'] = "";
-    header("Location: /");
-}

+ 0 - 231
application/action/new_team.php

@@ -1,231 +0,0 @@
-<?php
-/**
- * File for the team creation action.
- *
- * Implements an action function to be called from the {@see Controller}.
- *
- * @author Iñigo Valentin <i@inigovalentin.com>
- * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
- * @package SWDB
- */
-
-/**
- * Logs the user out and redirects to the login page.
- *
- * Reads the POST parameters looking for the following KEYS:
- *  player: The owner's player ID.
- *  area_type: The area type ID ({@see AREA_TYPE_ID}).
- *  area: The area ID.
- *  stage: Optional, the stage ID.
- *  difficulty: Optional, the difficulty ID ({@see DIFFICULTY_ID})
- *  ids: Unit IDs of the party.
- *  name: Team name.
- *  description: Optional, team description.
- * Validates the data and creates the team in the database.
- * 
- * @return int 201 on success, HTTP error status codes on failure.
- * @category Action
- */
-function action(){
-
-    $player = filter_input(INPUT_POST, 'player');
-    $area_type = filter_input(INPUT_POST, 'area_type');
-    $area = filter_input(INPUT_POST, 'area');
-    $stage = filter_input(INPUT_POST, 'stage');
-    $difficulty = filter_input(INPUT_POST, 'difficulty');
-    if ($difficulty == ''){
-        $difficulty = null;
-    }
-    $ids = filter_input(INPUT_POST, 'ids');
-    $name = filter_input(INPUT_POST, 'name');
-    $leader_id = filter_input(INPUT_POST, 'leader');
-    $total_front = filter_input(INPUT_POST, 'front');
-    $description = filter_input(INPUT_POST, 'description');
-    $score = 0;
-    $id = preg_split("/[\s,]+/", $ids);
-    $id_count = sizeof($id);
-    if ($name == null || strlen($name) == 0){
-        return 400;
-    }
-    switch ($area_type){
-        case AREA_TYPE_ID::SCENARIO:
-            if ($stage < 0 || $stage > 7){
-                return 400;
-            }
-            if (!($difficulty == 0 || APPLICATION::valid_id("DIFFICULTY_ID", $difficulty) || $difficulty == DIFFICULTY_ID::EASY)){
-                return 400;
-            }
-            if ($id_count < 1 || $id_count > 4){
-                return 400;
-            }
-            if (!APPLICATION::valid_id("SCENARIO_ID", $area)){
-                return 400;
-            }
-            break;
-        case AREA_TYPE_ID::CAIROS_DUNGEON:
-            if ($stage < 0){
-                return 400;
-            }
-            if ($stage > 12 && (DUNGEON_ID::GIANTS_KEEP == $area || DUNGEON_ID::DRAGONS_LAIR == $area || DUNGEON_ID::NECROPOLIS == $area)){
-                return 400;
-            }
-            if ($stage > 10 && ($area != DUNGEON_ID::GIANTS_KEEP && $area != DUNGEON_ID::DRAGONS_LAIR && $area != DUNGEON_ID::NECROPOLIS)){
-                return 400;
-            }
-            $difficulty = null;
-            if ($id_count < 1 || $id_count > 5){
-                return 400;
-            }
-            if (!APPLICATION::valid_id("DUNGEON_ID", $area)){
-                return 400;
-            }
-            break;
-        case AREA_TYPE_ID::RIFT_DUNGEON:
-            $stage = null;
-            $difficulty = null;
-            if ($id_count < 1 || $id_count > 6){
-                return 400;
-            }
-            if (!APPLICATION::valid_id("ELEMENTAL_RIFT_DUNGEON_ID", $area)){
-                return 400;
-            }
-            break;
-        case AREA_TYPE_ID::RIFT_RAID:
-            if ($stage < 0 || $stage > 5){
-                return 400;
-            }
-            $difficulty = null;
-            if ($id_count < 1 || $id_count > 6){
-                return 400;
-            }
-            $area = null;
-            break;
-        case AREA_TYPE_ID::ARENA:
-            $area = null;
-            $stage = null;
-            $difficulty = null;
-            break;
-        case AREA_TYPE_ID::GUILD_WAR:
-            $area = null;
-            $stage = null;
-            $difficulty = null;
-            break;
-        case AREA_TYPE_ID::GUILD_SIEGE:
-            $area = null;
-            $stage = null;
-            $difficulty = null;
-            break;
-        case AREA_TYPE_ID::TARTARUS_LABYRINTH:
-            if ($difficulty != null && !APPLICATION::valid_id("DIFFICULTY_ID", $difficulty)){
-                return 400;
-            }
-            if ($id_count < 1 || $id_count > 5){
-                return 400;
-            }
-            if (!APPLICATION::valid_id("LABYRINTH_STAGES_ID", $stage)){
-                return 400;
-            }
-            break;
-        case AREA_TYPE_ID::TRIAL_OF_ASCENSION:
-            if ($difficulty != null && $difficulty != DIFFICULTY_ID::NORMAL && $difficulty != DIFFICULTY_ID::HARD){
-                $difficulty = null;
-            }
-            $stage = null;
-            if ($id_count < 1 || $id_count > 5){
-                return 400;
-            }
-            $area = null;
-            break;
-        case AREA_TYPE_ID::DIMENSIONAL_HOLE:
-            if ($stage < 0 || $stage > 5){
-                return 400;
-            }
-            $difficulty = null;
-            if ($id_count < 1 || $id_count > 4){
-                return 400;
-            }
-            if (!APPLICATION::valid_id("DUNGEON_ID", $area)){
-                return 400;
-            }
-            break;
-        case AREA_TYPE_ID::DIMENSIONAL_RIFT:
-            if ($difficulty != 0 && $difficulty != DIFFICULTY_ID::NORMAL && $difficulty != DIFFICULTY_ID::HARD){
-                return 400;
-            }
-            $stage = null;
-            if ($id_count < 1 || $id_count > 5){
-                return 400;
-            }
-            $area = null;
-            break;
-        default:
-            return 400;
-    }
-    $unique_id = array_unique($id);
-    if ($id_count != sizeof($unique_id)){
-        return 400;
-    }
-    $s = "SELECT CASE WHEN MAX(id) IS NULL THEN 1 ELSE MAX(id) + 1 END AS id FROM team;";
-    $q = get_context()->get_db()->query($s);
-    $r = $q->fetchArray(SQLITE3_ASSOC);
-    $team_id = $r["id"];
-    $statement = get_context()->get_db()->prepare("
-      INSERT INTO team (
-        player,
-        id,
-        name,
-        description,
-        area_type,
-        area,
-        stage,
-        difficulty,
-        score
-      ) VALUES (
-        :player,
-        :id,
-        :name,
-        :description,
-        :area_type,
-        :area,
-        :stage,
-        :difficulty,
-        :score
-      );
-    ");
-    $statement->bindValue(':player', $player, SQLITE3_TEXT);
-    $statement->bindValue(':id', $team_id, SQLITE3_TEXT);
-    $statement->bindValue(':name', $name, SQLITE3_TEXT);
-    $statement->bindValue(':description', $description, SQLITE3_TEXT);
-    $statement->bindValue(':area_type', $area_type, SQLITE3_TEXT);
-    $statement->bindValue(':area', $area, SQLITE3_TEXT);
-    $statement->bindValue(':stage', $stage, SQLITE3_TEXT);
-    $statement->bindValue(':difficulty', $difficulty, SQLITE3_TEXT);
-    $statement->bindValue(':score', $score, SQLITE3_TEXT);
-    $res = $statement->execute();
-    if (!$res){
-        return 500;
-    }
-
-    $j = 0;
-    foreach (explode(',', $ids) as $i){
-        $leader = 0;
-        if ((string) $i == (string) $leader_id){
-            $leader = 1;
-        }
-        $front = 0;
-        if ($j < $total_front){
-            $front = 1;
-        }
-        $statement = get_context()->get_db()->prepare("
-          INSERT INTO team_unit (team, unit, leader, front)
-          VALUES (:team, :unit, :leader, :front);
-        ");
-        $statement->bindValue(':team', $team_id, SQLITE3_TEXT);
-        $statement->bindValue(':unit', $i, SQLITE3_TEXT);
-        $statement->bindValue(':leader', $leader, SQLITE3_TEXT);
-        $statement->bindValue(':front', $front, SQLITE3_TEXT);
-        $statement->execute();
-        $j ++;
-    }
-    return 201;
-}

+ 0 - 197
application/action/optimize_get_options.php

@@ -1,197 +0,0 @@
-<?php
-/**
- * File for the team creation action.
- *
- * Implements an action function to be called from the {@see Controller}.
- *
- * @author Iñigo Valentin <i@inigovalentin.com>
- * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
- * @package SWDB
- */
-
-/**
- * Executes the optimization action.
- *
- * Reads the POST parameters looking for the following KEYS:
- *  player: The player ID.
- *  unit: The unit ID.
- *  tuning: 1: All +12; 2: Even +15, odd + 12; 3: All +15.
- *  options: JSON with rune sets.
- *
- * @return int|string a JSON with the runes on success, negative integers on error. 0 if no errors
- * ocurred, but no results were found.
- * @category Action
- */
-function action(){
-
-    // Increase max execution time.
-    set_time_limit(60);
-
-    $runes = [];
-
-    $response = null;
-
-    $player = filter_input(INPUT_POST, 'player');
-    if ($player == null){
-        return -1;
-    }
-
-    $unit_id = filter_input(INPUT_POST, 'unit');
-    if ($unit_id == null){
-        return -2;
-    }
-    $unit = new Unit($unit_id, true, $player);
-    $base_stats = $unit->get_base_stats();
-    $building_stats = $unit->get_building_stats();
-    $artifact_stats = $unit->get_artifact_stats();
-    $total_stats = $unit->get_stats();
-
-    $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" => [
-                "atk" => $base_stats["atk"],
-                "def" => $base_stats["def"],
-                "hp"  => $base_stats["hp"],
-                "spd" => $base_stats["spd"],
-                "crr" => $base_stats["crr"],
-                "crd" => $base_stats["crd"],
-                "acc" => $base_stats["acc"],
-                "res" => $base_stats["res"],
-                "ehp" => $base_stats["ehp"],
-                "dmg" => $base_stats["dmg"]
-            ]
-        ];
-        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->get_main_stat()->get_stat(),
-                ($rune->get_innate_stat() != null ? $rune->get_innate_stat()->get_stat() : null),
-                (sizeof($rune->get_substats()) > 0 ? $rune->get_substats()[0]->get_stat() : null),
-                (sizeof($rune->get_substats()) > 1 ? $rune->get_substats()[1]->get_stat() : null),
-                (sizeof($rune->get_substats()) > 2 ? $rune->get_substats()[2]->get_stat() : null),
-                (sizeof($rune->get_substats()) > 3 ? $rune->get_substats()[3]->get_stat() : null)
-            ];
-            $main_value = $rune->get_main_stat()->get_value();
-            switch ($tuning){
-                case 1: // All +12
-                    if ($rune->get_level() < 12){
-                        $main_value = $rune->get_main_stat_at_level(12);
-                    }
-                    break;
-                case 2: // Even +15, Odd + 12
-                    if ($rune->get_level() < 15 && $rune->get_slot() % 2 == 0){
-                        $main_value = $rune->get_main_stat_at_level(15);
-                    }
-                    elseif ($rune->get_level() < 12 && $rune->get_slot() % 2 != 0){
-                        $main_value = $rune->get_main_stat_at_level(12);
-                    }
-                    break;
-                case 3: // All +15
-                    if ($rune->get_level() < 15){
-                        $main_value = $rune->get_main_stat_at_level(15);
-                    }
-                    break;
-            }
-            $stat_values = [
-                $main_value,
-                ($stat_ids[1] != null ? $rune->get_innate_stat()->get_value() : 0),
-                ($stat_ids[2] != null ? $rune->get_substats()[0]->get_value() : 0),
-                ($stat_ids[3] != null ? $rune->get_substats()[1]->get_value() : 0),
-                ($stat_ids[4] != null ? $rune->get_substats()[2]->get_value() : 0),
-                ($stat_ids[5] != null ? $rune->get_substats()[3]->get_value() : 0)
-            ];
-            for ($i = 0; $i < 6; $i ++){
-                switch ($stat_ids[$i]){
-                    case RUNE_STAT_ID::ATK:
-                        $set["stats"]["atk"] += $stat_values[$i];
-                        break;
-                    case RUNE_STAT_ID::ATK_P:
-                        $set["stats"]["atk"] += ceil($base_stats["atk"] * $stat_values[$i] / 100);
-                        break;
-                    case RUNE_STAT_ID::DEF:
-                        $set["stats"]["def"] += $stat_values[$i];
-                        break;
-                    case RUNE_STAT_ID::DEF_P:
-                        $set["stats"]["def"] += ceil($base_stats["def"] * $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($base_stats["hp"] * $stat_values[$i] / 100);
-                        break;
-                    case RUNE_STAT_ID::SPD:
-                        $set["stats"]["spd"] += $stat_values[$i];
-                        break;
-                    case RUNE_STAT_ID::CRR:
-                        $set["stats"]["crr"] += $stat_values[$i];
-                        break;
-                    case RUNE_STAT_ID::CRD:
-                        $set["stats"]["crd"] += $stat_values[$i];
-                        break;
-                    case RUNE_STAT_ID::ACC:
-                        $set["stats"]["acc"] += $stat_values[$i];
-                        break;
-                    case RUNE_STAT_ID::RES:
-                        $set["stats"]["res"] += $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"]["atk"];
-            $crr = $set["stats"]["crr"];
-            if ($crr > 100){
-                $crr = 100;
-            }
-            $crd = $set["stats"]["crd"];
-            $edmg = ceil(($atk * (100 - $crr) / 100) + (($atk + ($atk * $crd / 100)) * $crr / 100));
-            $set["stats"]["dmg"] = $edmg;
-            error_log("attack before extras: " . $set["stats"]["atk"]);
-            
-            // Add artifacts and buildings
-            $set["stats"]["atk"] += ($building_stats["atk"] + $artifact_stats["atk"]);
-            $set["stats"]["def"] += ($building_stats["def"] + $artifact_stats["def"]);
-            $set["stats"]["hp"]  += ($building_stats["hp"]  + $artifact_stats["hp"]);
-            $set["stats"]["spd"] += ($building_stats["spd"] + $artifact_stats["spd"]);
-            $set["stats"]["crr"] += ($building_stats["crr"] + $artifact_stats["crr"]);
-            $set["stats"]["crd"] += ($building_stats["crd"] + $artifact_stats["crd"]);
-            $set["stats"]["acc"] += ($building_stats["acc"] + $artifact_stats["acc"]);
-            $set["stats"]["res"] += ($building_stats["res"] + $artifact_stats["res"]);
-            $set["stats"]["ehp"] += ($building_stats["ehp"] + $artifact_stats["ehp"]);
-            $set["stats"]["dmg"] += ($building_stats["dmg"] + $artifact_stats["dmg"]);
-            error_log("attack after extras: " . $set["stats"]["atk"]);
-            // 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;
-    }
-}

+ 0 - 530
application/action/optimize_get_rune_list.php

@@ -1,530 +0,0 @@
-<?php
-/**
- * File for the rune optimization action.
- *
- * Implements an action function to be called from the {@see Controller}.
- *
- * @author Iñigo Valentin <i@inigovalentin.com>
- * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
- * @package SWDB
- */
-
-/**
- * Selects the runes for optimization.
- *
- * Reads the POST parameters looking for the following KEYS:
- *  player: Player ID.
- *  mode: Game mode ({@see GAME_MODE_ID}).
- *  unit: Unit ID.
- *  source: Source of the runes to select (assigned, unassigned, ...).
- *  tuning: 1: All +12; 2: Even +15, odd + 12; 3: All +15.
- *  set[]: Selected rune sets ({@see RUNE_SET_ID}).
- *  
- * Then it selects all the runes matching the cryteria.
- * 
- * @return int|string a JSON with the runes on success, negative integers on error. 0 if no errors
- * ocurred, but no results were found.
- * @category Action
- */
-function action(){
-
-    // Increase max execution time.
-    set_time_limit(60);
-
-    $candidates = [
-        1 => [],
-        2 => [],
-        3 => [],
-        4 => [],
-        5 => [],
-        6 => []
-    ];
-
-    $response = null;
-
-    $player = filter_input(INPUT_POST, 'player');
-    if ($player == null){
-        return -1;
-    }
-    $game_mode = GAME_MODE_ID::NORMAL;
-    if (filter_input(INPUT_POST, 'mode') == GAME_MODE_ID::RTA){
-        $game_mode = GAME_MODE_ID::RTA;
-    }
-
-    $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;
-    }
-    
-    $focus = [];
-    if (isset($_POST["focus_0"]) && intval($_POST["focus_0"]) > 0){
-        array_push($focus, intval($_POST["focus_0"]));
-    }
-    if (isset($_POST["focus_1"]) && intval($_POST["focus_1"]) > 0){
-        array_push($focus, intval($_POST["focus_1"]));
-    }
-    if (isset($_POST["focus_2"]) && intval($_POST["focus_2"]) > 0){
-        array_push($focus, intval($_POST["focus_2"]));
-    }
-    // Remove doubles and sort. The order will be HP, ATK, DEF, SPD, CRR, CRD, RES, ACC
-    $focus = array_unique($focus, SORT_NUMERIC);
-
-    // 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;
-    }
-
-    $tuning = filter_input(INPUT_POST, 'tuning');
-    if ($tuning == null){
-        return -6;
-    }
-
-    // Instantiate the unit
-    $unit = new Unit($unit_id);
-
-    // Get build query:
-    $base_s = "
-      SELECT
-        id,
-        type,
-        slot,
-        level,
-        stars,
-        (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::MAIN . " ) AS main_stat,
-        (SELECT value FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::MAIN . " ) AS main_stat_value,
-        (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::INNATE . " ) AS innate_stat,
-        (SELECT value FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::INNATE . " ) AS innate_stat_value,
-        (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_1 . " ) AS substat_1,
-        (SELECT value + grind FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_1 . " ) AS substat_1_value,
-        (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_2 . " ) AS substat_2,
-        (SELECT value + grind FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_2 . " ) AS substat_2_value,
-        (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_3 . " ) AS substat_3,
-        (SELECT value + grind FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_3 . " ) AS substat_3_value,
-        (SELECT stat FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_4 . " ) AS substat_4,
-        (SELECT value + grind FROM rune_stat WHERE rune = rune.id AND slot = " . RUNE_STAT_SLOT_ID::SUBSTAT_4 . " ) AS substat_4_value
-      FROM rune
-      WHERE
-        player = :player AND
-        type IN (
-    ";
-    foreach ($sets as $set){
-        $base_s .= ($set . ",");
-    }
-    $base_s .= "-1) AND ";
-    switch ($source){
-        case 0: // Storage only (or itself)
-            $base_s .= "
-              (
-                rune.id IN (SELECT DISTINCT rune FROM unit_rune WHERE unit = :unit_id AND rta = " . $game_mode . ")
-                OR
-                rune.id NOT IN (SELECT DISTINCT rune FROM unit_rune WHERE rta = " . $game_mode . ")
-              )";
-            break;
-        case 1: // Units in no teams (or itself)
-            $base_s .= "
-              (
-                rune.id IN (SELECT DISTINCT rune FROM unit_rune WHERE unit = :unit_id AND rta = " . $game_mode . ")
-                OR rune.id NOT IN (SELECT DISTINCT rune FROM unit_rune WHERE rta = " . $game_mode . ")
-                OR rune.id NOT IN (SELECT DISTINCT rune FROM unit_rune WHERE rta = " . $game_mode . " AND unit NOT IN (SELECT DISTINCT unit FROM team_unit))
-              )
-            ";
-            break;
-        case 2: // Units in teams with 0 score (or itself)
-            $base_s .= "
-              (
-                rune.id IN (SELECT DISTINCT rune FROM unit_rune WHERE unit = :unit_id AND rta = " . $game_mode . ")
-                OR rune.id NOT IN (SELECT DISTINCT rune FROM unit_rune WHERE rta = " . $game_mode . ")
-                OR rune.id NOT IN (
-                  SELECT DISTINCT rune 
-                  FROM unit_rune 
-                  WHERE
-                    rta = " . $game_mode . "
-                    AND unit 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 (:stat_0, :stat_1, :stat_2) ";
-    $s_main = [
-        " main_stat = :stat_0 ",
-        " main_stat = :stat_1 ",
-        " main_stat = :stat_2 ",
-    ];
-    
-    // Process focuses
-    $focus_stats = [];
-    foreach ($focus as $f){
-        switch ($f){
-            case STAT_ID::HP:
-                array_push($focus_stats, RUNE_STAT_ID::HP);
-                array_push($focus_stats, RUNE_STAT_ID::HP_P);
-                break;
-            case STAT_ID::ATK:
-                array_push($focus_stats, RUNE_STAT_ID::ATK);
-                array_push($focus_stats, RUNE_STAT_ID::ATK_P);
-                break;
-            case STAT_ID::DEF:
-                array_push($focus_stats, RUNE_STAT_ID::DEF);
-                array_push($focus_stats, RUNE_STAT_ID::DEF_P);
-                break;
-            case STAT_ID::SPD:
-                array_push($focus_stats, RUNE_STAT_ID::SPD);
-                break;
-            case STAT_ID::CRR:
-                array_push($focus_stats, RUNE_STAT_ID::CRR);
-                break;
-            case STAT_ID::CRD:
-                array_push($focus_stats, RUNE_STAT_ID::CRD);
-                break;
-            case STAT_ID::ACC:
-                array_push($focus_stats, RUNE_STAT_ID::ACC);
-                break;
-            case STAT_ID::RES:
-                array_push($focus_stats, RUNE_STAT_ID::RES);
-                break;
-        }
-    }
-
-    $focus_cond = ["", "", "", "", "", "", ""];
-    if (sizeof($focus) > 0){
-        
-        // Slot 1, no ATK flat, no DEF flat, no DEF %
-        $focus_slot = $focus_stats;
-        /*if (array_search(RUNE_STAT_ID::ATK, $focus_slot) !== false){
-            $focus_slot = array_diff($focus_slot, [RUNE_STAT_ID::ATK])//array_splice($focus_slot, array_search(RUNE_STAT_ID::ATK, $focus_slot), 1);
-        }
-        if (array_search(RUNE_STAT_ID::DEF, $focus_slot) !== false){
-            $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::DEF, $focus_slot), 1);
-        }
-        if (array_search(RUNE_STAT_ID::DEF_P, $focus_slot) !== false){
-            $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::DEF_P, $focus_slot), 1);
-        }*/
-        $focus_slot = array_diff($focus_slot, [RUNE_STAT_ID::ATK, RUNE_STAT_ID::DEF, RUNE_STAT_ID::DEF_P]);
-        if (sizeof($focus_slot) > 0){
-            $focus_cond[1] .= "
-              -- FOCUS 1
-              AND (
-                rune.id IN (
-                  SELECT DISTINCT rune FROM rune_stat WHERE slot != :slot_main AND stat IN
-                  (
-            ";
-            foreach ($focus_slot as $stat){
-                $focus_cond[1] .= $stat . ", ";
-            }
-            $focus_cond[1] = rtrim($focus_cond[1], ", ");
-            $focus_cond[1] .= "
-                  )
-                )
-              )  -- END FOCUS 1
-            ";
-        }
-        
-        // Slot 3, no ATK flat, no ATK %, no DEF flat.
-        $focus_slot = $focus_stats;
-        /*if (array_search(RUNE_STAT_ID::ATK, $focus_slot) !== false){
-            $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::ATK, $focus_slot), 1);
-        }
-        if (array_search(RUNE_STAT_ID::ATK_P, $focus_slot) !== false){
-            $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::ATK_P, $focus_slot), 1);
-        }
-        if (array_search(RUNE_STAT_ID::DEF, $focus_slot) !== false){
-            $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::DEF, $focus_slot), 1);
-        }*/
-        $focus_slot = array_diff($focus_slot, [RUNE_STAT_ID::ATK, RUNE_STAT_ID::ATK_P, RUNE_STAT_ID::DEF]);
-        if (sizeof($focus_slot) > 0){
-            $focus_cond[3] .= "
-              -- FOCUS 3
-              AND (
-                rune.id IN (
-                  SELECT DISTINCT rune FROM rune_stat WHERE slot != :slot_main AND stat IN
-                  (
-            ";
-            foreach ($focus_slot as $stat){
-                $focus_cond[3] .= $stat . ", ";
-            }
-            $focus_cond[3] = rtrim($focus_cond[3], ", ");
-            $focus_cond[3] .= "
-                  )
-                )
-              )  -- END FOCUS 3
-            ";
-        }
-        
-        // Slot 5, no HP flat.
-        $focus_slot = $focus_stats;
-        /*if (array_search(RUNE_STAT_ID::HP, $focus_slot) !== false){
-            $focus_slot = array_splice($focus_slot, array_search(RUNE_STAT_ID::HP, $focus_slot), 1);
-        }*/
-        $focus_slot = array_diff($focus_slot, [RUNE_STAT_ID::HP]);
-        if (sizeof($focus_slot) > 0){
-            $focus_cond[5] .= "
-              -- FOCUS 5
-              AND (
-                rune.id IN (
-                  SELECT DISTINCT rune FROM rune_stat WHERE slot != :slot_main AND stat IN
-                  (
-            ";
-            foreach ($focus_slot as $stat){
-                $focus_cond[5] .= $stat . ", ";
-            }
-            $focus_cond[5] = rtrim($focus_cond[5], ", ");
-            $focus_cond[5] .= "
-                  )
-                )
-              ) -- END FOCUS 5
-            ";
-        }
-        
-        // TODO: Focus for even slots.
-        // Slot 2, all stat, but include main
-        // Slot 5, no HP flat.
-        $focus_slot = $focus_stats;
-        if (sizeof($focus_slot) > 0){
-            $focus_cond[2] .= "
-              -- FOCUS EVEN
-              AND (
-                rune.id IN (
-                  SELECT DISTINCT rune FROM rune_stat WHERE stat IN
-                  (
-            ";
-            foreach ($focus_slot as $stat){
-                $focus_cond[2] .= $stat . ", ";
-            }
-            $focus_cond[2] = rtrim($focus_cond[2], ", ");
-            $focus_cond[2] .= "
-                  )
-                )
-              ) -- END FOCUS EVEN
-            ";
-        }
-        
-    }
-    
-    //TODO DEBUG
-    $focus_cond[2] = "";
-
-    $s = ["", "", "", "", "", "", ""];
-    $s[1] = $base_s . " AND slot = 1 " . $focus_cond[1] . $s_order . " LIMIT :limit_odd;";
-    $s[3] = $base_s . " AND slot = 3 " . $focus_cond[3] . $s_order . " LIMIT :limit_odd;";
-    $s[5] = $base_s . " AND slot = 5 " . $focus_cond[5] . $s_order . " LIMIT :limit_odd;";
-    $s[2] = $base_s . " AND slot = 2 AND " . $s_main[0] . $focus_cond[2] . $s_order . " LIMIT :limit_even;";
-    $s[4] = $base_s . " AND slot = 4 AND " . $s_main[1] . $focus_cond[2] . $s_order . " LIMIT :limit_even;";
-    $s[6] = $base_s . " AND slot = 6 AND " . $s_main[2] . $focus_cond[2] . $s_order . " LIMIT :limit_even;";
-
-    $total_candidates = 0;
-    for ($i = 1; $i <= 6; $i ++){
-        //error_log($s[$i]);
-        $statement = get_context()->get_db()->prepare($s[$i]);
-        $statement->bindValue(':player', $player, SQLITE3_TEXT);
-        $statement->bindValue(':unit_id', $unit_id, SQLITE3_TEXT);
-        $statement->bindValue(':stat_0', $stats[0], SQLITE3_TEXT);
-        $statement->bindValue(':stat_1', $stats[1], SQLITE3_TEXT);
-        $statement->bindValue(':stat_2', $stats[2], SQLITE3_TEXT);
-        $statement->bindValue(':slot_main', RUNE_STAT_SLOT_ID::MAIN, SQLITE3_INTEGER);
-        $statement->bindValue(':limit_even', 150, SQLITE3_INTEGER);
-        $statement->bindValue(':limit_odd', 100, SQLITE3_INTEGER);
-        //error_log($statement->getSQL(true));
-        $q = $statement->execute();
-        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]);
-
-    // 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 int $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->get_base_stats()["atk"] * $stat["VALUE"] / 100);
-                break;
-            case RUNE_STAT_ID::DEF:
-                $rune["DEF"] += $stat["VALUE"];
-                break;
-            case RUNE_STAT_ID::DEF_P:
-                $rune["DEF"] += ($unit->get_base_stats()["def"] * $stat["VALUE"] / 100);
-                break;
-            case RUNE_STAT_ID::HP:
-                $rune["HP"] += $stat["VALUE"];
-                break;
-            case RUNE_STAT_ID::HP_P:
-                $rune["HP"] += ($unit->get_base_stats()["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;
-    }
-}

+ 0 - 48
application/action/rate_team.php

@@ -1,48 +0,0 @@
-<?php
-/**
- * File for the team score modification action.
- *
- * Implements an action function to be called from the {@see Controller}.
- *
- * @author Iñigo Valentin <i@inigovalentin.com>
- * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
- * @package SWDB
- */
-
-/**
- * Sets the score for a team.
- *
- * Reads the POST parameters looking for the following KEYS:
- *  player: Owner's player ID.
- *  team: Team ID.
- *  score: New score.
- * Validates the data and sets the score.
- *
- * @return int 201 on success, HTTP erro status codes on error.
- * @category Action
- */
-function action(){
-    $player_id = filter_input(INPUT_GET, "player");
-    $statement = get_context()->get_db()->prepare("
-        SELECT count(user) AS c FROM player WHERE id = :player AND user = :user");
-    $statement->bindValue(":player", $player_id, SQLITE3_TEXT);
-    $statement->bindValue(":user", get_context()->get_user()->get_id(), SQLITE3_TEXT);
-    if ($statement->execute()->fetchArray(SQLITE3_ASSOC)["c"] != 1){
-        // The user doesn't own the team.
-        return 401;
-    }
-    $team = filter_input(INPUT_GET, "team");
-    $score = intval(filter_input(INPUT_GET, "score"));
-    $statement = get_context()->get_db()->prepare("
-      UPDATE team
-      SET score = :score
-      WHERE
-        player = :player AND
-        id = :team;
-    ");
-    $statement->bindValue(":score", $score, SQLITE3_TEXT);
-    $statement->bindValue(":player", $player_id, SQLITE3_TEXT);
-    $statement->bindValue(":team", $team, SQLITE3_TEXT);
-    $statement->execute();
-    return 201;
-}

+ 0 - 108
application/action/save_run_team.php

@@ -1,108 +0,0 @@
-<?php
-/**
- * File for the run team saving action.
- *
- * Implements an action function to be called from the {@see Controller}.
- *
- * @author Iñigo Valentin <i@inigovalentin.com>
- * @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3
- * @package SWDB
- */
-
-/**
- * Saves a run party as a team.
- *
- * Reads the POST parameters looking for the following KEYS:
- *  player: Player ID.
- *  run: Run ID.
- *  name: New team name.
- *  description: New team description.
- *  score: New team score, defaults to 0.
- * Validates the data and creates the team in the database.
- * 
- * @return int 201 on success, HTTP erro status codes on error.
- * @category Action
- */
-function action(){
-    $player = filter_input(INPUT_POST, 'player');
-    $run = filter_input(INPUT_POST, 'run');
-    $name = filter_input(INPUT_POST, 'name');
-    $description = filter_input(INPUT_POST, 'description');
-    $score = intval(filter_input(INPUT_POST, 'score'));
-    if ($name == null || strlen($name) == 0){
-        return 400;
-    }
-    $s = "SELECT CASE WHEN MAX(id) IS NULL THEN 1 ELSE MAX(id) + 1 END AS id FROM team;";
-    $q = get_context()->get_db()->query($s);
-    $r = $q->fetchArray(SQLITE3_ASSOC);
-    $team_id = $r["id"];
-    $statement = get_context()->get_db()->prepare("
-      SELECT
-        run.area AS area,
-        run.stage AS stage,
-        run.difficulty AS difficulty,
-        area.type AS area_type
-      FROM
-        run,
-        area
-      WHERE
-        run.player = :player AND
-        run.id = :run AND
-        area.id = run.area AND
-        run.helper = 0;
-    ");
-    $statement->bindValue(':player', $player, SQLITE3_TEXT);
-    $statement->bindValue(':run', $run, SQLITE3_TEXT);
-    $r = $statement->execute()->fetchArray(SQLITE3_ASSOC);
-    if (!$r){
-        return 404;
-    }
-    $statement = get_context()->get_db()->prepare("
-      INSERT INTO team (
-        player,
-        id,
-        name,
-        description,
-        area_type,
-        area,
-        stage,
-        difficulty,
-        score
-      ) VALUES (
-        :player,
-        :id,
-        :name,
-        :description,
-        :area_type,
-        :area,
-        :stage,
-        :difficulty,
-        :score);
-      ");
-    $statement->bindValue(':player', $player, SQLITE3_TEXT);
-    $statement->bindValue(':id', $team_id, SQLITE3_TEXT);
-    $statement->bindValue(':name', $name, SQLITE3_TEXT);
-    $statement->bindValue(':description', $description, SQLITE3_TEXT);
-    $statement->bindValue(':area_type', $r["area_type"], SQLITE3_TEXT);
-    $statement->bindValue(':area', $r["area"], SQLITE3_TEXT);
-    $statement->bindValue(':stage', $r["stage"], SQLITE3_TEXT);
-    $statement->bindValue(':difficulty', $r["difficulty"], SQLITE3_TEXT);
-    $statement->bindValue(':score', $score, SQLITE3_INTEGER);
-    $res = $statement->execute();
-    if (!$res){
-        return 500;
-    }
-    $s = "SELECT unit FROM data.run_party WHERE run = $run AND unit IS NOT NULL;";
-    $q = get_context()->get_db()->query($s);
-    while ($r = $q->fetchArray(SQLITE3_ASSOC)){
-        $statement = get_context()->get_db()->prepare("
-          INSERT INTO data.team_unit (team, unit)
-          VALUES (:team, :unit)
-        ");
-        $statement->bindValue(':team', $team_id, SQLITE3_TEXT);
-        $statement->bindValue(':unit', $r["unit"], SQLITE3_TEXT);
-        $statement->execute();
-    }
-    return 201;
-}
-?>

+ 10 - 5
application/page/Artifacts_Page.php

@@ -55,16 +55,21 @@ class Artifacts_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "artifacts.php";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("artifacts.php");
+        $this->set_title("Artifacts");
+        $this->set_description(get_context()->get_player()->get_name() . "'s artifacts");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/artifacts/");
+        $this->add_css("artifacts.css");
+        
         $this->parse_filters();
         $result_set = $this->prepare_statement()->execute();
         while ($result = $result_set->fetchArray(SQLITE3_ASSOC)){
             array_push($this->artifacts, new Artifact($result["id"]));
         }
-
-        $this->title = "Artifacts - SWDB";
-        $this->description = "Artifact inventory";
-        $this->canonical = URL::BASE . "artifacts/";
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 
     /**

+ 10 - 4
application/page/Enchantments_Page.php

@@ -53,7 +53,14 @@ class Enchantments_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "enchantments.php";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("enchantments.php");
+        $this->set_title("Enchantments");
+        $this->set_description(get_context()->get_player()->get_name() . "'s enchantments");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/enchantments/");
+        $this->add_css("enchantments.css");
+
         $this->parse_filters();
         $result_set = $this->prepare_statement()->execute();
         while ($result = $result_set->fetchArray(SQLITE3_ASSOC)){
@@ -66,9 +73,8 @@ class Enchantments_Page extends Page{
                 array_push($this->grindstones, $enchantment);
             }
         }
-        $this->title = "Enchantments - SWDB";
-        $this->description = "Enchantment inventory";
-        $this->canonical = URL::BASE . "enchantments/";
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 
     /**

+ 30 - 45
application/page/Error_Page.php

@@ -18,62 +18,47 @@ require_once(PATH::PAGE . "Page.php");
  */
 class Error_Page extends Page{
 
-    /**
-     * @var int Error code.
-     */
-    private $code = 500;
-    
-    /**
-     * @var string Error description.
-     */
-    private $message = "";
-
     /**
      * Constructor.
      *
      * Retrieves the data and initializes the variables.
      * 
      * @param int $code HTTP Error code. Optional, default 500.
+     * @param string $messages HTTP Error message. Optional, default null.
      */
-    public function __construct($code = null){
-        $this->view = PATH::VIEW . "error.php";
-        $this->title = "Error - SWDB";
+    public function __construct($code = null, $message = null){
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("error.php");
+        $this->set_title("Error");
+        $this->set_canonical("");
+        $this->add_css("error.css");
 
         if (null == $code || intval($code) < 200 || intval($code) > 599){
-            $code = 500;
+            $this->set_code(500);
         }
-        switch(intval($code)){
-            case 400:
-                $this->message = "Invalid request";
-                break;
-            case 404:
-                $this->message = "Not found";
-                break;
-            case 500:
-                $this->message = "Server error";
-                break;
-            default:
-                $this->message = "Unknown error";
+        else{
+            $this->set_code($code);
         }
-        $this->code = intval($code);
-    }
-
-    /**
-     * Retrieves the error code.
-     *
-     * @return int HTTP error code
-     */
-    public function get_code(){
-        return $this->code;
-    }
-    
-    /**
-     * Retrieves the error description.
-     *
-     * @return int HTTP error description.
-     */
-    public function get_message(){
-        return $this->message;
+        if (null != $message){
+            $this->set_message($message);
+        }
+        else{
+            switch(intval($this->get_code())){
+                case 400:
+                    $this->set_message("Invalid request");
+                    break;
+                case 404:
+                    $this->set_message("Not found");
+                    break;
+                case 500:
+                    $this->set_message("Server error");
+                    break;
+                default:
+                    $this->set_message("Unknown error");
+            }
+        }
+        $this->set_description("Error " . $this->get_code() . ": " . $this->get_message());
     }
 
 }

+ 12 - 6
application/page/Guild_Page.php

@@ -41,7 +41,12 @@ class Guild_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "guild.php";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("guild.php");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/guild/");
+        $this->add_css("guild.css");
+        
         $statement = get_context()->get_db()->prepare("
           SELECT id
           FROM guild
@@ -53,8 +58,8 @@ class Guild_Page extends Page{
         if ($result){
             $this->in_guild = true;
             $this->guild = new Guild($result["id"]);
-            $this->title = $this->guild->get_name() . " - SWDB";
-            $this->description = $this->guild->get_name() . " Guild details";
+            $this->set_title($this->guild->get_name());
+            $this->set_description($this->guild->get_name() . " Guild");
             $statement = get_context()->get_db()->prepare("SELECT id FROM guild_skill_group ORDER BY id;");
             $skill_result_set = $statement->execute();
             while ($result = $skill_result_set->fetchArray(SQLITE3_ASSOC)){
@@ -63,10 +68,11 @@ class Guild_Page extends Page{
         }
         else{
             $this->in_guild = false;
-            $this->title = "Guild - SWDB";
-            $this->description = "No guild";
+            $this->set_title("Guild");
+            $this->set_description("The player is not in a guild");
         }
-        $this->canonical = URL::BASE . "guild/";
+        $this->set_code(200);
+        $this->set_message("OK");
     }
     
     /**

+ 13 - 6
application/page/Landing_Page.php → application/page/Login_Page.php

@@ -12,11 +12,11 @@
 require_once(PATH::PAGE . "Page.php");
 
 /**
- * Landing (login) page model.
+ * Login page model.
  *
  * @category Page
  */
-class Landing_Page extends Page{
+class Login_Page extends Page{
 
     /**
      * @var bool Indicates if the user has been redirected to this page after an error.
@@ -35,14 +35,21 @@ class Landing_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "landing.php";
-        $this->title = "Login - SWDB";
-        $this->description = "Login to SWDB";
-        $this->canonical = URL::BASE . "landing/";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("login.php");
+        $this->set_title("Login");
+        $this->set_description("Login to SWDB");
+        $this->set_canonical("");
+        $this->add_css("landing.css");
+        
         if (http_response_code() == 401){
             $this->is_error = true;
             $this->error_message = "Invalid credentials";
         }
+        
+        $this->set_code(200);
+        $this->set_message("OK");
     }
     
     /**

+ 18 - 4
application/page/Monster_Page.php

@@ -57,9 +57,22 @@ class Monster_Page extends Page{
      * @param int $id Monster id. Can be any of the awakening stages
      */
     public function __construct($id){
-        $this->view = PATH::VIEW . "monster.php";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("monster.php");
+        $this->set_canonical("monster/" . $id);
+        $this->add_css("monster.css");
+        
         $title = "";
         $monster = new Monster($id);
+        if ($monster == null || $monster->get_id() == null){
+            $this->set_code(404);
+            $this->set_message("Monster not found");
+        }
+        else{
+            $this->set_code(200);
+            $this->set_message("OK");
+        }
         $minimum_id = 0;
         if (! $monster->can_awaken()){
             // Silver star monster.
@@ -130,9 +143,10 @@ class Monster_Page extends Page{
             array_push($this->family, new Monster($result["id"]));
         }
 
-        $this->title = $title ." - SWDB";
-        $this->description = $title . " info - SWDB";
-        $this->canonical = URL::BASE . "monster_info/" . $id;
+        $this->set_description(get_context()->get_player()->get_name() . "'s enchantments");
+        
+        $this->set_title($title);
+        $this->set_description($title . " details");
     }
     
     /**

+ 10 - 4
application/page/Monsters_Page.php

@@ -45,7 +45,14 @@ class Monsters_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "monsters.php";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("monsters.php");
+        $this->set_title("Monsters");
+        $this->set_description("Catalog of all monsters");
+        $this->set_canonical("monsters/");
+        $this->add_css("monsters.css");
+        
         $this->parse_filters();
         $result_set = $this->prepare_statement()->execute();
         while ($result = $result_set->fetchArray(SQLITE3_ASSOC)){
@@ -61,9 +68,8 @@ class Monsters_Page extends Page{
         while ($result = $result_set->fetchArray(SQLITE3_ASSOC)){
             array_push($this->monsters_open, $result["monster"]);
         }
-        $this->title = "Monsters - SWDB";
-        $this->description = "Catalog of all monsters";
-        $this->canonical = URL::BASE . "catalog/";
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 
     /**

+ 9 - 4
application/page/Optimizer_Page.php

@@ -31,10 +31,13 @@ class Optimizer_Page extends Page{
      */
     public function __construct(){
         
-        $this->view = PATH::VIEW . "optimizer.php";
-        $this->title = "Optimizer - SWDB";
-        $this->description = "Optimizer";
-        $this->canonical = URL::BASE . "optimizer/";
+        parent::__construct();
+        $this->set_public(false);
+        $this->set_view("optimizer.php");
+        $this->set_title("Optimizer");
+        $this->set_description("Rune optimizer");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/optimizer/");
+        $this->add_css("optimizer.css");
         
         // Get teams
         $statement = get_context()->get_db()->prepare("
@@ -44,6 +47,8 @@ class Optimizer_Page extends Page{
         while ($result = $result_set->fetchArray(SQLITE3_ASSOC)){
             array_push($this->teams, new Team($result["id"]));
         }
+        $this->set_code(200);
+        $this->set_message("OK");
         
     }
     /**

+ 9 - 4
application/page/Optimizer_Unit_Page.php

@@ -32,11 +32,16 @@ class Optimizer_Unit_Page extends Page{
      * @param string $id Selected unit id.
      */
     public function __construct($id){
-        $this->view = PATH::VIEW . "optimizer_unit.php";
+        parent::__construct();
+        $this->set_public(false);
+        $this->set_view("optimizer_unit.php");
         $this->unit = new Unit($id);
-        $this->title = "Optimize " . $this->unit->get_title() . " - SWDB";
-        $this->description = "Optimize " . $this->unit->get_title();
-        $this->canonical = URL::BASE . "optimizer/$id";
+        $this->set_title("Optimize " . $this->unit->get_title());
+        $this->set_description("Optimize " . $this->unit->get_title() . "'s runes");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/optimize/" . $this->unit->get_id());
+        $this->add_css("optimizer.css");
+        $this->set_code(200);
+        $this->set_message("OK");
     }
     /**
      * Retrieves the unit to optimize.

+ 228 - 9
application/page/Page.php

@@ -18,45 +18,65 @@
  */
 abstract class Page{
 
+    /**
+     * @var bool Indicates if the page can be viewed publicly, os it's only for the owner.
+     */
+    private $public = false;
+    
+    /**
+     * @var string[] List of CSS files required for the page.
+     */
+    private $css = [];
+    
     /**
      * @var string Path to the view associated with the page.
      */
-    protected $view;
+    private $view;
 
     /**
      * @var string Title of the page, in the defined language.
      */
-    protected $title;
+    private $title;
 
     /**
      * @var string Site name.
      */
-    protected $name;
+    private $name;
 
     /**
      * @var string Page description, in the defined language.
      */
-    protected $description;
+    private $description;
 
     /**
      * @var string URL to the site favicon.
      */
-    protected $favicon;
+    private $favicon;
 
     /**
      * @var string URL to the page icon or main image.
      */
-    protected $icon;
+    private $icon;
 
     /**
      * @var string Canonical URL.
      */
-    protected $canonical;
+    private $canonical;
 
     /**
      * @var string Autjhor URL.
      */
-    protected $author;
+    private $author;
+    
+    /**
+     * @var integer HTTP status code.
+     */
+    private $code = 0;
+    
+    /**
+     * @var string HTTP status message.
+     */
+    private $message = "";
 
     /**
      * Constructor.
@@ -66,7 +86,91 @@ abstract class Page{
         $this->icon = URL::IMG["LOGO"] . "sw.png";
         $this->name = "SWDB";
         $this->author = URL::BASE;
+        $this->add_css("ui.css");
+        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
+            $this->add_css("ui-rta.css");
+        }
+    }
+    
+
+    /**
+     * Sets the page visibility to public or private.
+     *
+     * A public page is considered if one of the following conditions apply.
+     *  - Doesn't present info from any player (i.e. static pages, such as the catalog.)
+     *  - When refering to a plblic profile, it can be accessed by users other than the owner, or
+     *    unregistered users.
+     *
+     * Note that for the second condition, this must be set to true even if the player profile
+     * is private.
+     *
+     * @param bool True for public pages, false for private ones.
+     */
+    protected function set_public($public){
+        if ($public === true){
+            $this->public = true;
+        }
+        else{
+            $this->public = false;
+        }
+    }
+
+    /**
+     * Checks if the page is public.
+     * 
+     * A public page is considered if one of the following conditions apply.
+     *  - Doesn't present info from any player (i.e. static pages, such as the catalog.)
+     *  - When refering to a plblic profile, it can be accessed by users other than the owner, or
+     *    unregistered users.
+     *    
+     * Note that for the second condition, this will still return true even if the player profile
+     * is private.
+     *
+     * @return bool True for public pages, false for private ones.
+     */
+    public function is_public(){
+        return $this->public;
     }
+    
+    /**
+     * Adds a CSS file to the list.
+     *
+     * @param string $css Css file. Can be the absolute URL, or just the filename.
+     */
+    protected function add_css($css){
+        if (strripos($css, URL::CSS) === false){
+            $file = URL::CSS . $css;
+        }
+        else{
+            $file = $css;
+        }
+        array_push($this->css, $file);
+    }
+
+    /**
+     * Retrieves the CSS file URLs for the page.
+     *
+     * @return string[] CSS files (by absolute URL.)
+     */
+    public function get_css(){
+        return $this->css;
+    }
+    
+    /**
+     * Sets the page view.
+     *
+     * @param string $view View file. Can be the relative path, or just the filename.
+     */
+    protected function set_view($view){
+        if (strripos($view, PATH::VIEW) === false){
+            $file = PATH::VIEW . $view;
+        }
+        else{
+            $file = $view;
+        }
+        $this->view = $file;
+    }
+
     /**
      * Retrieves the page view file.
      *
@@ -76,6 +180,17 @@ abstract class Page{
         return $this->view;
     }
 
+    /**
+     * Sets the page title.
+     * 
+     * The string " - SWDB" will always be added at the end.
+     *
+     * @param string $title Page title.
+     */
+    protected function set_title($title){
+        $this->title = htmlentities($title, ENT_QUOTES) . " - SWDB";
+    }
+
     /**
      * Retrieves the page title.
      *
@@ -93,6 +208,17 @@ abstract class Page{
     public function get_name(){
         return $this->name;
     }
+    
+    /**
+     * Sets the page description.
+     *
+     * The string " - Summoners War DataBase" will always be added at the end.
+     *
+     * @param string $description Page description text.
+     */
+    protected function set_description($description){
+        $this->description = htmlentities($description, ENT_QUOTES) . " - Summoners War DataBase";
+    }
 
     /**
      * Retrieves the page description
@@ -120,6 +246,21 @@ abstract class Page{
     public function get_icon(){
         return $this->icon;
     }
+    
+    /**
+     * Sets the page canonical URL.
+     *
+     * @param string $canonical Canonical URL, absolute or relative.
+     */
+    protected function set_canonical($canonical){
+        if (strripos($canonical, URL::BASE) === false){
+            $file = URL::BASE . $canonical;
+        }
+        else{
+            $file = $canonical;
+        }
+        $this->canonical = $file;
+    }
 
     /**
      * Retrieves the canonical URL to the page.
@@ -139,5 +280,83 @@ abstract class Page{
         return $this->author;
     }
 
+    /**
+     * Sets the HTTP status code for the page to return.
+     *
+     * @param int HTTP status code.
+     */
+    protected function set_code($code){
+        if (is_int($code) && $code >= 200 && $code <= 500){
+            $this->code = $code;
+        }
+    }
+    
+    /**
+     * Retrieves the page HTTP status code.
+     * 
+     * @return int HTTP status code
+     */
+    public function get_code(){
+        return $this->code;
+    }
+    
+    /**
+     * Sets the HTTP status message for the page to return.
+     *
+     * @param string HTTP status message.
+     */
+    protected function set_message($mesage){
+        $this->message = strval($mesage);
+    }
+    
+    /**
+     * Retrieves the page HTTP status message.
+     *
+     * @return string HTTP status code
+     */
+    public function get_message(){
+        return $this->message;
+    }
+    
+    /**
+     * Generates the content to be inserted in the HTML <head> section.
+     * 
+     * It must go between the <head> and </head> tags, and anything can go behind it before closing
+     * the section. It includes page title, description, css files and metadata.
+     *  
+     * @return string HTML head content.
+     */
+    public function generate_head(){
+        $head = "
+          <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
+          <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
+          <title>" . $this->get_title() . "</title>
+          <link rel='shortcut icon' href='" . $this->get_favicon() . "'/>
+          <!-- CSS files -->";
+        foreach ($this->get_css() as $css){
+            $head .= "
+          <link rel='stylesheet' type='text/css' href='$css'/>";
+        }
+        $head .= "
+          <!-- Meta tags -->
+          <link rel='canonical' href='" . $this->get_canonical() . "'/>
+          <link rel='author' href='" . $this->get_author() . "'/>
+          <link rel='publisher' href='" . $this->get_author() . "'/>
+          <meta name='description' content='" . $this->get_description() . "'/>
+          <meta property='og:title' content='" . $this->get_title() . "'/>
+          <meta property='og:url' content='" . $this->get_canonical() . "'/>
+          <meta property='og:description' content='" . $this->get_description() . "'/>
+          <meta property='og:image' content='" . $this->get_icon() . "'/>
+          <meta property='og:site_name' content='" . $this->get_name() . "'/>
+          <meta property='og:type' content='website'/>
+          <meta property='og:locale' content='en'/>
+          <meta name='twitter:card' content='summary'/>
+          <meta name='twitter:title' content='" . $this->get_title() . "'/>
+          <meta name='twitter:description' content='" . $this->get_description() . "'/>
+          <meta name='twitter:image' content='" . $this->get_icon() . "'/>
+          <meta name='twitter:url' content='" . $this->get_canonical() . "'/>
+          <meta name='robots' content='index follow'/>\n";
+        return $head;
+    }
+
 }
-?>

+ 10 - 5
application/page/Home_Page.php → application/page/Player_Page.php

@@ -17,7 +17,7 @@ require_once(PATH::ENTITY . "Player.php");
  *
  * @category Page
  */
-class Home_Page extends Page{
+class Player_Page extends Page{
 
     /**
      * Constructor.
@@ -25,9 +25,14 @@ class Home_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "home.php";
-        $this->title = get_context()->get_player()->get_name() . " - SWDB";
-        $this->description = get_context()->get_player()->get_name() . " - Summoners War DataBase";
-        $this->canonical = URL::BASE;
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("player.php");
+        $this->set_title(get_context()->get_player()->get_name());
+        $this->set_description(get_context()->get_player()->get_name() . "'s profile");
+        $this->set_canonical("");
+        $this->add_css("player.css");
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 }

+ 8 - 4
application/page/Profile_Page.php

@@ -24,10 +24,14 @@ class Profile_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "profile.php";
-        $this->title = get_context()->get_user()->get_name() ." - SWDB";
-        $this->description = get_context()->get_user()->get_name() . " user profile";
-        $this->canonical = URL::BASE . "profile/";
+        parent::__construct();
+        $this->set_public(false);
+        $this->set_view("profile.php");
+        $this->set_title(get_context()->get_user()->get_name() . "'s profile");
+        $this->set_description(get_context()->get_user()->get_name() . "'s profile settings");
+        $this->set_canonical("profile/" . get_context()->get_user()->get_id());
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 
 }

+ 9 - 6
application/page/Report_Page.php

@@ -25,11 +25,14 @@ class Report_Page extends Page{
      *
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "report.php";
-
-        $this->title = "Reports - SWDB";
-        $this->description = "Usefull reports for summoners";
-        $this->canonical = URL::BASE . "report";
-        $this->title = "Reports - SWDB";
+        parent::__construct();
+        $this->set_public(false);
+        $this->set_view("report.php");
+        $this->set_title("Reports");
+        $this->set_description("Reports for summoners");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/report/");
+        $this->add_css("report.css");
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 }

+ 10 - 4
application/page/Report_Rune_Enchantment_Page.php

@@ -85,7 +85,14 @@ class Report_Rune_Enchantment_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "report_rune_enchantment.php";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("report_rune_enchantment.php");
+        $this->set_title("Rune enchantment");
+        $this->set_description("Find runes that can be upgraded via enchantments");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/report/rune_enchantment//");
+        $this->add_css("report_rune_enchantment.css");
+        
         $prev_monster = "";
         $prev_rune = "";
         $upgrade = null;
@@ -127,9 +134,8 @@ class Report_Rune_Enchantment_Page extends Page{
         // Last entries
         array_push($upgrade["upgrade"], $rupgrade);
         array_push($this->upgrades, $upgrade);
-        $this->title = "Rune enchantment - SWDB";
-        $this->description = "Find runes that can be upgraded via enchantments.";
-        $this->canonical = URL::BASE . "report/rune_enchantment/";
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 
     /**

+ 10 - 4
application/page/Report_Skillup_Page.php

@@ -48,15 +48,21 @@ class Report_Skillup_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "report_skillup.php";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("report_skillup.php");
+        $this->set_title("Urgent skill-ups");
+        $this->set_description("FMonster in need of skilling up");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/report/report_skillup/");
+        $this->add_css("report_skillup.css");
+        
         $this->parse_filters();
         $result_set = $this->prepare_statement()->execute();
         while ($result = $result_set->fetchArray(SQLITE3_ASSOC)){
             array_push($this->units, new Unit($result["id"]));
         }
-        $this->title = "Urgent skill-ups - SWDB";
-        $this->description = "Monster in need of skilling up";
-        $this->canonical = URL::BASE . "report/skillups";
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 
     /**

+ 10 - 4
application/page/Runes_Page.php

@@ -58,7 +58,14 @@ class Runes_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "runes.php";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("runes.php");
+        $this->set_title("Runes");
+        $this->set_description(get_context()->get_player()->get_name() . "'s runes");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/runes/");
+        $this->add_css("runes.css");
+        
         $this->parse_filters();
         // TODO: Group hardcoded
         $this->filters["GROUP"] = 1;
@@ -66,9 +73,8 @@ class Runes_Page extends Page{
         while ($result = $result_set->fetchArray(SQLITE3_ASSOC)){
             array_push($this->runes, new Rune($result["id"]));
         }
-        $this->title = "Runes - SWDB";
-        $this->description = "Rune inventory";
-        $this->canonical = URL::BASE . "runes/";
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 
     /**

+ 9 - 4
application/page/Runs_Page.php

@@ -45,15 +45,20 @@ class Runs_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "runs.php";
+        parent::__construct();
+        $this->set_public(false);
+        $this->set_view("runs.php");
+        $this->set_title("Runs");
+        $this->set_description(get_context()->get_player()->get_name() . "'s run logs");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/runs/");
+        $this->add_css("runs.css");
         $this->parse_filters();
         $result_set = $this->prepare_statement()->execute();
         while ($result = $result_set->fetchArray(SQLITE3_ASSOC)){
             array_push($this->runs, new Run($result["id"]));
         }
-        $this->title = "Runs - SWDB";
-        $this->description = "Run logs";
-        $this->canonical = URL::BASE . "runs/";
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 
     /**

+ 9 - 4
application/page/Teams_Page.php

@@ -43,10 +43,13 @@ class Teams_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "teams.php";
-        $this->title = "Teams - SWDB";
-        $this->description = "Teams";
-        $this->canonical = URL::BASE . "teams/";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("teams.php");
+        $this->set_title("Teams");
+        $this->set_description(get_context()->get_player()->get_name() . "'s teams");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/teams/");
+        $this->add_css("teams.css");
 
         $this->parse_filters();
         $result_set = $this->prepare_statement()->execute();
@@ -65,6 +68,8 @@ class Teams_Page extends Page{
         while ($result = $result_set->fetchArray(SQLITE3_ASSOC)){
             array_push($this->units, new Unit($result["id"], false));
         }
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 
     /**

+ 16 - 4
application/page/Unit_Page.php

@@ -32,11 +32,23 @@ class Unit_Page extends Page{
      * @param string $id Selected unit id
      */
     public function __construct($id){
-        $this->view = PATH::VIEW . "unit.php";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("unit.php");
         $this->unit = new Unit($id, true);
-        $this->title = $this->unit->get_title() ." - SWDB";
-        $this->description = "Owned " . $this->unit->get_title();
-        $this->canonical = URL::BASE . "monster/" . $id;
+        if ($this->unit == null || $this->unit->get_id() == null){
+            $this->set_code(404);
+            $this->set_message("Unit not found");
+        }
+        else{
+            $this->set_code(200);
+            $this->set_message("OK");
+        }
+        $this->set_title($this->unit->get_title());
+        $this->set_description(get_context()->get_player()->get_name() . "'s " . $this->unit->get_title());
+        $this->set_canonical(get_context()->get_player()->get_id() . "/unit/" . $this->unit->get_id());
+        $this->add_css("unit.css");
+        
     }
 
     /**

+ 9 - 5
application/page/Units_Page.php

@@ -50,10 +50,13 @@ class Units_Page extends Page{
      * Retrieves the data and initializes the variables.
      */
     public function __construct(){
-        $this->view = PATH::VIEW . "units.php";
-        $this->title = "Units - SWDB";
-        $this->description = "Owned units";
-        $this->canonical = URL::BASE . "units/";
+        parent::__construct();
+        $this->set_public(true);
+        $this->set_view("units.php");
+        $this->set_title("Units");
+        $this->set_description(get_context()->get_player()->get_name() . "'s units");
+        $this->set_canonical(get_context()->get_player()->get_id() . "/units/");
+        $this->add_css("units.css");
 
         $this->parse_filters();
         $result_set = $this->prepare_statement()->execute();
@@ -67,7 +70,8 @@ class Units_Page extends Page{
         while ($r_eff = $q_eff->fetchArray(SQLITE3_ASSOC)){
             array_push($this->effects, new Effect($r_eff["id"]));
         }
-        
+        $this->set_code(200);
+        $this->set_message("OK");
     }
 
     /**

+ 3 - 33
application/view/artifacts.php

@@ -15,42 +15,12 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>artifacts.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
     </head>
     <body>
 <?php
-        include __DIR__ . "/inc/header.php";
+        include __DIR__ . "/inc/header_app.php";
+        include __DIR__ . "/inc/header_player.php";
 ?>
         <aside class='filters'>
             <h2>

+ 5 - 33
application/view/enchantments.php

@@ -15,38 +15,7 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>enchantments.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
         <script>
             /**
              * Hack to treat all type selects as one.
@@ -86,9 +55,12 @@
         </script>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside class='filters'>
             <h2>
                 Enchantments

+ 5 - 17
application/view/error.php

@@ -15,27 +15,15 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>error.css'/>
-        <!-- Meta tags -->
-        <meta name='robots' content='noindex nofollow'/>
+        <?=$page->generate_head()?>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside>
         </aside>
         <main>

+ 5 - 33
application/view/guild.php

@@ -15,43 +15,15 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>guild.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside>
 <?php
             if (! $page->in_guild()){

+ 5 - 34
application/view/help.php

@@ -5,7 +5,7 @@
      * Contains the view layout and some code to present the data.
      *
      * @category View
-     * @var Page $page The page model.
+     * @var Help_Page $page The page model.
      * @var int get_context()->get_game_mode() Game mode.
      */
     // TODO: Implement
@@ -13,43 +13,14 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>help.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
 ?>
+        </header>
         <div id='content'>
             <div class='section'>
                 <h3 class='section_title'>

+ 0 - 143
application/view/inc/header.php

@@ -1,143 +0,0 @@
-<?php
-    /**
-     * Site header view.
-     *
-     * To be included from all views.
-     *
-     * @category View
-     */
-?>
-<script>
-    function setMode(rta){
-        if (rta === true){
-            mode = <?=GAME_MODE_ID::RTA?>;
-        }
-        else if (rta === false){
-            mode = <?=GAME_MODE_ID::NORMAL?>;
-        }
-        else{
-            return false;
-        }
-        params = '?player=<?=(get_context()->get_player() != null) ? get_context()->get_player()->get_id() : "0"?>&mode=' + mode;
-        var xhttp = new XMLHttpRequest();
-        xhttp.onreadystatechange = function() {
-            if (this.readyState == 4){
-                if (this.status == 201){
-                    window.location.reload(false);
-                }
-            }
-        };
-        xhttp.open('GET', '/action/change_game_mode/' + params, true);
-        xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
-        xhttp.send();
-        return true;
-    }
-</script>
-<header>
-<?php
-    if ($_SESSION['session'] == true && get_context()->get_user() != null){
-?>
-        <nav id='menu'>
-            <table>
-                <tr>
-                    <td rowspan='2'>
-                        <div id='logo'>
-                            <img id='logo_img' src='<?=URL::IMG["LOGO"]?>sw.png' alt=''/>
-                            <h1 id='logo_text'>
-                                <span class='large'>D</span>
-                                <span class='small'>ata</span>
-                                <span class='large'>B</span>
-                                <span class='small'>ase</span>
-                            </h1>
-                        </div>
-                    </td>
-                    <td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/'>
-                            Home
-                        </a>
-                    </td>
-                    <td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/units/'>
-                            Units
-                        </a>
-                    </td>
-                    <td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/runes/'>
-                            Runes
-                        </a>
-                    </td>
-                    <td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/artifacts/'>
-                            Artifacts
-                        </a>
-                    </td>
-                    <td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/enchantments/'>
-                            Enchantments
-                        </a>
-                    </td>
-                    <td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/teams/'>
-                            Teams
-                        </a>
-                    </td>
-                    <td class='last profile'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/profile/'>
-                            <?=get_context()->get_player()->get_name()?>
-                        </a>
-                    </td>
-                </tr>
-                <tr>
-                    <td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/monsters/'>
-                            Monsters
-                        </a>
-                    </td>
-                    <td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/guild/'>
-                            Guild
-                        </a>
-                    </td>
-                    <td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/runs/'>
-                            Runs
-                        </a>
-                    </td>
-                    <td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/report/'>
-                            Reports
-                        </a>
-                    </td>
-                    <td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/optimizer/'>
-                            Optimizer
-                        </a>
-                    </td>
-                    <td class='no_item'>
-                    </td>
-                    <!--<td class='item'>
-                        <a href='/<?=get_context()->get_player()->get_id()?>/stats/'>
-                            Stats
-                        </a>
-                    </td>-->
-                    <td class='last mode'>
-                        <div id='mode_switch'>
-                            <span class='mode'>
-                                N<span class='desktop'>ormal</span>
-                            </span>
-                            <label class='switch'>
-                                <input type="checkbox" <?=(get_context()->get_game_mode() == GAME_MODE_ID::RTA ? "checked='cheecked'" : "")?> onChange='setMode(this.checked);'/>
-                                <span class="slider round"></span>
-                            </label>
-                            <span class='mode'>
-                                R<span class='desktop'>TA</span>
-                            </span>
-                        </div>
-                    </td>
-                </tr>
-            </table>
-        </nav>
-<?php
-    }
-?>
-</header>

+ 78 - 0
application/view/inc/header_app.php

@@ -0,0 +1,78 @@
+<?php
+    /**
+     * Site header view.
+     *
+     * To be included from all views.
+     *
+     * @category View
+     */
+
+    if ($_SESSION['session'] == true && get_context()->get_user() != null){
+        $logged_in = true;
+        //$user_id = get_context()->get_user()->get_id();
+    }
+    else{
+        $logged_in = false;
+        //$user_id = null;
+    }
+?>
+<nav class='menu' id='menu_app'>
+    <table>
+        <tr>
+            <td>
+                <h1 id='logo_text'>
+                    SWDB
+                </h1>
+            </td>
+            <td class='item'>
+                <a href='/monsters/'>
+                    Monsters
+                </a>
+            </td>
+            <td class='item'>
+                <a href='/dungeons/'>
+                    Dungeons
+                </a>
+            </td>
+            <td class='item'>
+                <a href='/buildings/'>
+                    Buildings
+                </a>
+            </td><td class='item'>
+                <a href='/fusion/'>
+                    Fusion
+                </a>
+            </td>
+<?php
+                if (! $logged_in){
+?>
+                <td class='item'>
+                    <a href='/login/'>
+                        Login
+                    </a>
+                </td>
+                <td class='item'>
+                    <a href='/register/'>
+                        Register
+                    </a>
+                </td>
+<?php
+                }
+                else{
+?>
+                    <td class='item'>
+                        <a href='/<?=get_context()->get_user()->get_id()?>/'>
+                            Account
+                        </a>
+                    </td>
+                    <td class='item'>
+                        <a href='/action/logout/'>
+                            Log Out
+                        </a>
+                    </td>
+<?php
+                }
+?>
+        </tr>
+    </table>
+</nav>

+ 136 - 0
application/view/inc/header_player.php

@@ -0,0 +1,136 @@
+<?php
+    /**
+     * Site header view.
+     *
+     * To be included from all views.
+     *
+     * @category View
+     */
+
+if (get_context()->get_player() != null){
+?>
+    <script>
+        function setMode(rta){
+            if (rta === true){
+                mode = <?=GAME_MODE_ID::RTA?>;
+            }
+            else if (rta === false){
+                mode = <?=GAME_MODE_ID::NORMAL?>;
+            }
+            else{
+                return false;
+            }
+            params = '?player=<?=(get_context()->get_player() != null) ? get_context()->get_player()->get_id() : "0"?>&mode=' + mode;
+            var xhttp = new XMLHttpRequest();
+            xhttp.onreadystatechange = function() {
+                if (this.readyState == 4){
+                    if (this.status == 201){
+                        window.location.reload(false);
+                    }
+                }
+            };
+            xhttp.open('GET', '/action/change_game_mode/' + params, true);
+            xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+            xhttp.send();
+            return true;
+        }
+    </script>
+    <nav class='menu' id='menu_player'>
+        <table>
+            <tr>
+                <td rowspan='2'>
+                    <select id='player_select'>
+                        <option selected='selected' value='<?=get_context()->get_player()->get_id()?>'>
+                            <?=get_context()->get_player()->get_name()?>
+                        </option>
+                    </select>
+                </td>
+                <td class='item'>
+                    <a href='/player/<?=get_context()->get_player()->get_id()?>/'>
+                        Profile
+                    </a>
+                </td>
+                <td class='item'>
+                    <a href='/player/<?=get_context()->get_player()->get_id()?>/units/'>
+                        Units
+                    </a>
+                </td>
+                <td class='item'>
+                    <a href='/player/<?=get_context()->get_player()->get_id()?>/runes/'>
+                        Runes
+                    </a>
+                </td>
+                <td class='item'>
+                    <a href='/player/<?=get_context()->get_player()->get_id()?>/artifacts/'>
+                        Artifacts
+                    </a>
+                </td>
+                <td class='item'>
+                    <a href='/player/<?=get_context()->get_player()->get_id()?>/enchantments/'>
+                        Enchantments
+                    </a>
+                </td>
+                <td class='item'>
+                    <a href='/player/<?=get_context()->get_player()->get_id()?>/teams/'>
+                        Teams
+                    </a>
+                </td>
+                <!-- <td class='last profile'>
+                    <a href='/<?=get_context()->get_player()->get_id()?>/profile/'>
+                        <?=get_context()->get_player()->get_name()?>
+                    </a>
+                </td>-->
+                <!-- <td class='item'>
+                    <a href='/<?=get_context()->get_player()->get_id()?>/monsters/'>
+                        Monsters
+                    </a>
+                </td>-->
+                <td class='item'>
+                    <a href='/<?=get_context()->get_player()->get_id()?>/guild/'>
+                        Guild
+                    </a>
+                </td>
+                <!-- TODO: Check if player == user -->
+                <td class='item'>
+                    <a href='/<?=get_context()->get_player()->get_id()?>/runs/'>
+                        Runs
+                    </a>
+                </td>
+                <!-- TODO: Check if player == user -->
+                <td class='item'>
+                    <a href='/<?=get_context()->get_player()->get_id()?>/report/'>
+                        Reports
+                    </a>
+                </td>
+                <!-- TODO: Check if player == user -->
+                <td class='item'>
+                    <a href='/<?=get_context()->get_player()->get_id()?>/optimizer/'>
+                        Optimizer
+                    </a>
+                </td>
+                <td class='no_item'>
+                </td>
+                <!--<td class='item'>
+                    <a href='/<?=get_context()->get_player()->get_id()?>/stats/'>
+                        Stats
+                    </a>
+                </td>-->
+                <td class='last mode'>
+                    <div id='mode_switch'>
+                        <span class='mode'>
+                            N<span class='desktop'>ormal</span>
+                        </span>
+                        <label class='switch'>
+                            <input type="checkbox" <?=(get_context()->get_game_mode() == GAME_MODE_ID::RTA ? "checked='cheecked'" : "")?> onChange='setMode(this.checked);'/>
+                            <span class="slider round"></span>
+                        </label>
+                        <span class='mode'>
+                            R<span class='desktop'>TA</span>
+                        </span>
+                    </div>
+                </td>
+            </tr>
+        </table>
+    </nav>
+<?php
+}

+ 4 - 27
application/view/landing.php → application/view/login.php

@@ -9,41 +9,18 @@
  * @package SWDB
  * @category View
  * @property Landing_Page $page The page model.
- * @var Landing_Page $page The page model.
+ * @var Login_Page $page The page model.
  */
 ?>
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>landing.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
     </head>
     <body>
 <?php
-        include __DIR__ . "/inc/header.php";
+        include __DIR__ . "/inc/header_app.php";
+        include __DIR__ . "/inc/header_player.php";
 ?>
         <aside>
         </aside>

+ 4 - 33
application/view/monster.php

@@ -16,43 +16,14 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>monster_info.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
 ?>
+        </header>
         <aside>
             <h2>
                 <?=str_replace(" - SWDB", "", $page->get_title())?>

+ 4 - 34
application/view/monsters.php

@@ -15,44 +15,14 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>monsters.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
-
+        <?=$page->generate_head()?>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
 ?>
+        </header>
         <aside id='filters' class='content filters'>
             <h2 id='filters_title'>
                 Catalog

+ 5 - 33
application/view/optimizer.php

@@ -15,38 +15,7 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>optimizer.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
         <script>
             units = [];
 <?php
@@ -176,9 +145,12 @@
         </script>
     </head>
     <body onLoad='calculateScores();'>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside>
             <h2>
                 Optimizer

+ 8 - 36
application/view/optimizer_unit.php

@@ -15,38 +15,7 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>optimizer.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
         <script>
 
             /**
@@ -518,7 +487,7 @@
 
                 // Make the request
                 //console.log("SENDING REQUEST TO");
-                xhttp.open('POST', '/action/optimize_get_rune_list/', true);
+                xhttp.open('POST', '/action/optimize_list/', true);
                 xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                 xhttp.send(params);
             }
@@ -817,7 +786,7 @@
                 };
                 params = 'player=<?=get_context()->get_player()->get_id()?>&unit=<?=$page->get_unit()->get_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.open('POST', '/action/optimize_options/', true);
                 xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                 xhttp.send(params);
                 return;
@@ -830,7 +799,7 @@
              * If none. It will show a message. In any case, it reenables the search
              * button
              *
-             * @param content Response from /action/optimize_get_options/
+             * @param content Response from /action/optimize_options/
              * @param timestamp Time counter
              */
             function presentOptions(content, timestamp){
@@ -1299,9 +1268,12 @@
         </script>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside>
             <h2>
                 Optimize <?=$page->get_unit()->get_title()?>

+ 6 - 34
application/view/home.php → application/view/player.php

@@ -9,49 +9,21 @@
  * @package SWDB
  * @category View
  * @property Home_Page $page The page model.
- * @var Home_Page $page The page model.
+ * @var Player_Page $page The page model.
  */
 ?>
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>home.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside>
             <h2>
                 <?=get_context()->get_player()->get_name()?>

+ 4 - 34
application/view/profile.php

@@ -15,38 +15,7 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>profile.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
         <script>
 
             /**
@@ -205,10 +174,11 @@
             }
         </script>
     </head>
-    <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
 ?>
+        </header>
         <aside>
             <h2>
                 <?=get_context()->get_user()->get_name()?>

+ 5 - 33
application/view/report.php

@@ -15,43 +15,15 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>report.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
     </head>
     <body id='body_reports'>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside>
             <h2>
                 Reports

+ 5 - 33
application/view/report_rune_enchantment.php

@@ -15,38 +15,7 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>report_rune_enchantment.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
         <script>
 
             /**
@@ -106,9 +75,12 @@
         </script>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside>
             <h2>
                 Report filters

+ 5 - 33
application/view/report_skillup.php

@@ -15,43 +15,15 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>report_skillup.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
     </head>
     <body id='body_report_skillup'>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside class='content filters' id='filters'>
             <h2 id='filters_title'>
                 Report filters

+ 5 - 33
application/view/runes.php

@@ -15,38 +15,7 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>runes.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
         <script>
 
             /**
@@ -105,9 +74,12 @@
         </script>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside class='content filters' id='filters'>
             <h2 id='filters_title'>
                 Rune filters

+ 6 - 34
application/view/runs.php

@@ -15,38 +15,7 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>runs.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
         <script>
 
             /**
@@ -84,7 +53,7 @@
                         saveForm(null, false);
                     }
                 };
-                xhttp.open("POST", "/action/save_run_team/", true);
+                xhttp.open("POST", "/action/save_team/", true);
                 xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                 xhttp.send('player=<?=get_context()->get_player()->get_id()?>&run=' + id + '&name=' + encodeURI(name) + '&description=' + encodeURI(description));
             }
@@ -147,9 +116,12 @@
         </script>
     </head>
     <body onload='selectAreaType();'>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside>
             <h2>
                 Logged runs

+ 3 - 33
application/view/stats.php

@@ -15,38 +15,7 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>stats.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
         <script>
 
             function selectAreaType(){
@@ -528,7 +497,8 @@
     </head>
     <body onload='populateDiagrams();selectAreaType();initialValues();'>
 <?php
-        include __DIR__ . "/inc/header.php";
+        include __DIR__ . "/inc/header_app.php";
+        include __DIR__ . "/inc/header_player.php";
 ?>
         <aside class='filters'>
             <h2>

+ 6 - 34
application/view/teams.php

@@ -15,38 +15,7 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>teams.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
         <script>
             units = [];
             max_units = 0;
@@ -223,7 +192,7 @@
                         }
                     }
                 };
-                xhttp.open("POST", "/action/new_team/", true);
+                xhttp.open("POST", "/action/add_team/", true);
                 xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                 xhttp.send(request);
 
@@ -704,9 +673,12 @@
         </script>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside>
             <h2>
                 Teams

+ 5 - 33
application/view/unit.php

@@ -15,43 +15,15 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>monster.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside>
             <h2>
                 <?=$page->get_unit()->get_title()?>

+ 5 - 33
application/view/units.php

@@ -13,43 +13,15 @@
 <!DOCTYPE html>
 <html lang='en'>
     <head>
-        <meta content='text/html; charset=utf-8' http-equiv='content-type'/>
-        <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1'/>
-        <title><?=$page->get_title()?></title>
-        <link rel='shortcut icon' href='<?=$page->get_favicon()?>'/>
-        <!-- CSS files -->
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui.css'/>
-<?php
-        if (get_context()->get_game_mode() == GAME_MODE_ID::RTA){
-?>
-            <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>ui-rta.css'/>
-<?php
-        }
-?>
-        <link rel='stylesheet' type='text/css' href='<?=URL::CSS?>monsters.css'/>
-        <!-- Meta tags -->
-        <link rel='canonical' href='<?=$page->get_canonical()?>'/>
-        <link rel='author' href='<?=$page->get_author()?>'/>
-        <link rel='publisher' href='<?=$page->get_author()?>'/>
-        <meta name='description' content='<?=$page->get_description()?>'/>
-        <meta property='og:title' content='<?=$page->get_title()?>'/>
-        <meta property='og:url' content='<?=$page->get_canonical()?>'/>
-        <meta property='og:description' content='<?=$page->get_description()?>'/>
-        <meta property='og:image' content='<?=$page->get_icon()?>'/>
-        <meta property='og:site_name' content='<?=$page->get_name()?>?>'/>
-        <meta property='og:type' content='website'/>
-        <meta property='og:locale' content='en'/>
-        <meta name='twitter:card' content='summary'/>
-        <meta name='twitter:title' content='<?=$page->get_title()?>'/>
-        <meta name='twitter:description' content='<?=$page->get_description()?>'/>
-        <meta name='twitter:image' content='<?=$page->get_icon()?>'/>
-        <meta name='twitter:url' content='<?=$page->get_canonical()?>'/>
-        <meta name='robots' content='index follow'/>
+        <?=$page->generate_head()?>
     </head>
     <body>
+        <header>
 <?php
-        include __DIR__ . "/inc/header.php";
+            include __DIR__ . "/inc/header_app.php";
+            include __DIR__ . "/inc/header_player.php";
 ?>
+        </header>
         <aside id='filters' class='content filters'>
             <h2 id='filters_title'>
                 Monsters

+ 0 - 12
public/css/catalog.css

@@ -1,12 +0,0 @@
-section#list article{
-    margin-bottom: 1.5em;
-}
-section#list article div.monster{
-    display: inline-block;
-    font-size: 60%;
-    margin: 0.2em;
-}
-
-section#list article div.closed{
-    filter: brightness(50%);
-}

+ 131 - 119
public/css/monster.css

@@ -1,125 +1,137 @@
-aside div#monster_info{
+aside nav{
+    display: block;
     text-align: center;
 }
-aside div#monster_info div#monster_details{
-    display: block;
+aside div.monster_panel{
+    display: inline-block;
     font-size: 70%;
 }
-@media only screen and (max-width: 788px){
-    aside div#monster_info div#monster_details{
-        display: inline-block;
-        vertical-align: top;
-        text-align: center;
-    }
+
+aside nav#family div.monster_panel{
+    font-size: 50%;
 }
-aside div#monster_info div#monster_details div#teams{
-    text-align: left;
-    margin: 1em;
+section h2 span.family{
+    float: right;
 }
-@media only screen and (max-width: 788px){
-    aside div#monster_info div#monster_details div#teams{
-        margin: 0.2em;
-    }
-    aside div#monster_info div#monster_details div#teams ul{
-        margin: 0.2em;
-    }
+
+section h2 span.family img.family{
+    max-width: 2em;
+    max-height: 2em;
+    margin: -0.5em;
+    width: 90%;
+    height: 90%;
 }
-aside div#monster_info div#monster_details div#teams h4{
-    margin: 0.2em auto;
+
+section h2 span.family img.family.current{
+    max-width: 2.5em;
+    max-height: 2.5em;
+    margin: -0.5em;
+    width: 100%;
+    height: 100%;
 }
-aside div#monster_info div#monster_details a#optimize{
-    display: block;
-    margin: auto 30%;
+
+section h2 span.family img.family.disabled{
+    filter: sepia(100%) saturate(10%);
+    opacity: 0.5;
 }
-aside div#monster_info div#exp{
-    border: 0.3em solid var(--ui-color-border);
-    border-radius: 0.5em;
-    height: 1em;
-    background-color: #00000088;
+
+article.stage{
+    margin: 0.5em;
     position: relative;
-    font-size: 100%;
-    margin: auto 25%;
-}
-@media only screen and (max-width: 788px){
-    aside div#monster_info div#exp{
-        display: none;
-    }
-}
-aside div#monster_info div#exp span#exp_bar{
-    height: 1em;
-    border-radius: 0.4em;
-    display: block;
-    background-color: #cc0000;
+    padding: 0.3em 0.1em;
 }
-aside div#monster_info div#exp span#exp_text{
-    display: block;
-    text-align: center;
-    position: absolute;
-    bottom: 0;
-    margin: auto;
-    width: 100%;
-    font-weight: bold;
-    font-size: 0.8em;
-    text-shadow: 0 0 0.1em #000000, 0 0 0.1em #000000, 0 0 0.1em #000000, 0 0 0.1em #000000, 0 0 0.1em #000000;
-}
-section#monster article#stats{
+
+article.stage div.profile{
     display: inline-block;
     vertical-align: top;
 }
-section#monster article#stats table{
-    font-size: 90%;
-    vertical-align: top;
-    display: inline-block;
+
+article.stage table.stats{
+    font-size: 75%;
     border: 0.1em solid black;
     color: #ffffff;
-    margin: auto;
+    margin: 0.4em;
     border-collapse: collapse;
     border-radius: 0.4em;
+    display: inline-block;
+    vertical-align: top;
+    max-width: 10em;
 }
-section#monster article#stats table th{
+
+article.stage table.stats td, article.stage table.stats th{
+    padding: 0.01em 0.5em;
+    border: 0.1em solid #000000;
+}
+
+article.stage table.stats th{
     font-weight: bold;
     background-color: #00000066;
 }
-section#monster article#stats table td{
+
+article.stage table.stats td{
     font-weight: bold;
     background-color: #ffffff33;
     text-align: right;
     font-family: monospace;
     font-size: 120%;
 }
-section#monster article#stats table td span.percent{
-    margin: 0 0 0 -0.7em;
-    font-size: 80%;
-}
-section#monster article#stats table td, th{
-    padding: 0.2em;
-    border: 0.1em solid #000000;
-}
-section#monster article#stats table td.extra{
-    color: #aaaaff;
+
+article.stage div.extra{
+    display: inline-block;
 }
-section#monster article#stats table td.total{
-    color: #f2c920;
-    font-weight: bold;
+
+article.stage div.extra div.sources span{
+    display: block;
+    font-size: 70%;
+    margin: 0.5em 0 0 0;
+    color: #ffffff;
 }
-section#monster article#artifacts{
+
+article.stage div.extra div.sources div.source_panel{
     display: inline-block;
-    vertical-align: top;
 }
-section#monster article#artifacts table{
+article.stage div.extra div.sources div.source_panel img{
+    width: 2em;
+    height: 2em;
+    border: 0.1em solid #f2c920;
+    border-radius: 0.3em;
+}
+
+article.stage div.extra div.essences span{
     display: block;
+    font-size: 70%;
+    margin: 0.5em 0 0 0;
+    color: #ffffff;
 }
-section#monster article#runes{
+
+article.stage div.extra div.essences div.essence{
+    position: relative;
     display: inline-block;
     vertical-align: top;
-    width: 50em; /*Force two rows*/
+    border-style: solid;
+    border-width: 0.1em;
+    border-radius: 15%;
+    width: 1.5em;
+    height: 1.5em;
+    margin-bottom: 0.1em;
+}
+article.stage div.extra div.essences div.essence img{
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 1.5em;
+    height: 1.5em;
 }
-@media only screen and (max-width: 788px){
-    section#monster article#runes{
-        width: 100%;
-    }
+article.stage div.extra div.essences div.essence span{
+    position: absolute;
+    font-size: 80%;
+    bottom: 0em;
+    right: 0em;
+    color: #ffffff;
+    text-shadow: 0 0 0.1em #000000, 0 0 0.1em #000000, 0 0 0.2em #000000, 0 0 0.2em #000000;
 }
-section#monster article#skills > table{
+
+article.stage table.skills{
     display: block;
     font-size: 90%;
     border: 0.1em solid black;
@@ -131,15 +143,17 @@ section#monster article#skills > table{
     width: 90%;
 }
 @media screen and (max-width : 990px){
-    section#monster article#skills > table{
+    article.stage table.skills{
         width: 100%;
     }
 }
-section#monster article#skills > table th{
+
+article.stage table.skills th{
     font-weight: bold;
     background-color: #00000066;
 }
-section#monster article#skills > table > tbody >  tr > td{
+
+article.stage table.skills > tbody >  tr > td{
     vertical-align: top;
     padding: 0.2em 0.5em;
     border-right: 0.1em solid #00000088;
@@ -147,40 +161,43 @@ section#monster article#skills > table > tbody >  tr > td{
     border-top: 0.2em solid #00000088;
     border-bottom: 0.2em solid #00000088;
 }
-section#monster article#skills > table td.skill_img img{
+
+article.stage table.skills td.skill_img img{
     width: 4em;
     height: 4em;
     border: 0.15em solid var(--ui-color-border);
     border-radius: 0.4em;
 }
-section#monster article#skills > table span.skill_level{
-    font-size: 100%;
-    top: 2.8em;
-    display: block;
-    width: 3.5em;
-    margin: -1.5em auto 0.5em auto;
-    text-align: right;
-    text-shadow: 0 0 0.2em #000000, 0 0 0.2em #000000, 0 0 0.4em #000000, 0 0 0.4em #000000, 0 0 0.6em #000000, 0 0 0.6em #000000, 0 0 0.8em #000000, 0 0 0.8em #000000;
-}
-section#monster article#skills > table span.skill_level.maxed{
-    color: #f2c920;
-}
-section#monster article#skills > table td.skill_text{
+
+article.stage table.skills td.skill_text{
     text-align: left;
 }
-section#monster article#skills > table td.skill_text h4{
+
+article.stage table.skills td.skill_text h4{
     text-shadow: 0 0 0.2em #000000, 0 0 0.2em #000000, 0 0 0.4em #000000;
     margin: 0 0 0.5em 0;
     display: block;
     font-weight: bold;
 }
-section#monster article#skills > table td.skill_text p{
+
+article.stage table.skills td.skill_text p{
     margin: 0 0 0 0;
 }
-section#monster article#skills > table td.skill_extra{
+
+/*article.stage table.skills span.skill_description{
+    font-size: 80%;
+    font-style: italic;
+    margin: 0;
+    padding: 0;
+    display: block;
+    line-height: 0.95em;
+}*/
+
+article.stage table.skills td.skill_extra{
     text-align: center;
 }
-section#monster article#skills > table td.skill_text span.formula{
+
+article.stage table.skills td.skill_text span.formula{
     font-size: 80%;
     color: #cccccc;
     font-weight: bold;
@@ -188,33 +205,28 @@ section#monster article#skills > table td.skill_text span.formula{
     font-family: monospace;
     white-space: nowrap;
 }
-section#monster article#skills > table td.skill_text div.effects img{
+
+
+article.stage table.skills td.skill_text div.effects img{
     width: 1.6em;
     height: 1.6em;
     border: 0.1em solid #000000;
     border-radius: 0.5em;
 }
-section#monster article#skills > table td.skill_ups table{
+
+article.stage table.skills td.skill_ups table{
     font-size: 80%;
     font-family: monospace;
 }
-section#monster article#skills > table td.skill_ups table td{
+article.stage table.skills td.skill_ups table td{
     white-space: nowrap;
-    color: #999999;
-}
-section#monster article#skills > table td.skill_ups td.aquired{
-    color: #efefef;
 }
-section#monster article#skills > table td.skill_ups table td.level{
+
+article.stage table.skills td.skill_ups table td.level{
     display: inline-block;
     text-align: left;
 }
-section#monster article#skills > table td.skill_ups table td.upgrade{
+article.stage table.skills td.skill_ups table td.upgrade{
     font-style: italic;
     text-align: left;
 }
-/*section#monster article#profile td#skill_averages{
-    font-size: 90%;
-    font-style: italic;
-    border-top: 0.1em solid #cccccc;
-}*/

+ 0 - 232
public/css/monster_info.css

@@ -1,232 +0,0 @@
-aside nav{
-    display: block;
-    text-align: center;
-}
-aside div.monster_panel{
-    display: inline-block;
-    font-size: 70%;
-}
-
-aside nav#family div.monster_panel{
-    font-size: 50%;
-}
-section h2 span.family{
-    float: right;
-}
-
-section h2 span.family img.family{
-    max-width: 2em;
-    max-height: 2em;
-    margin: -0.5em;
-    width: 90%;
-    height: 90%;
-}
-
-section h2 span.family img.family.current{
-    max-width: 2.5em;
-    max-height: 2.5em;
-    margin: -0.5em;
-    width: 100%;
-    height: 100%;
-}
-
-section h2 span.family img.family.disabled{
-    filter: sepia(100%) saturate(10%);
-    opacity: 0.5;
-}
-
-article.stage{
-    margin: 0.5em;
-    position: relative;
-    padding: 0.3em 0.1em;
-}
-
-article.stage div.profile{
-    display: inline-block;
-    vertical-align: top;
-}
-
-article.stage table.stats{
-    font-size: 75%;
-    border: 0.1em solid black;
-    color: #ffffff;
-    margin: 0.4em;
-    border-collapse: collapse;
-    border-radius: 0.4em;
-    display: inline-block;
-    vertical-align: top;
-    max-width: 10em;
-}
-
-article.stage table.stats td, article.stage table.stats th{
-    padding: 0.01em 0.5em;
-    border: 0.1em solid #000000;
-}
-
-article.stage table.stats th{
-    font-weight: bold;
-    background-color: #00000066;
-}
-
-article.stage table.stats td{
-    font-weight: bold;
-    background-color: #ffffff33;
-    text-align: right;
-    font-family: monospace;
-    font-size: 120%;
-}
-
-article.stage div.extra{
-    display: inline-block;
-}
-
-article.stage div.extra div.sources span{
-    display: block;
-    font-size: 70%;
-    margin: 0.5em 0 0 0;
-    color: #ffffff;
-}
-
-article.stage div.extra div.sources div.source_panel{
-    display: inline-block;
-}
-article.stage div.extra div.sources div.source_panel img{
-    width: 2em;
-    height: 2em;
-    border: 0.1em solid #f2c920;
-    border-radius: 0.3em;
-}
-
-article.stage div.extra div.essences span{
-    display: block;
-    font-size: 70%;
-    margin: 0.5em 0 0 0;
-    color: #ffffff;
-}
-
-article.stage div.extra div.essences div.essence{
-    position: relative;
-    display: inline-block;
-    vertical-align: top;
-    border-style: solid;
-    border-width: 0.1em;
-    border-radius: 15%;
-    width: 1.5em;
-    height: 1.5em;
-    margin-bottom: 0.1em;
-}
-article.stage div.extra div.essences div.essence img{
-    position: absolute;
-    top: 0;
-    left: 0;
-    width: 1.5em;
-    height: 1.5em;
-}
-article.stage div.extra div.essences div.essence span{
-    position: absolute;
-    font-size: 80%;
-    bottom: 0em;
-    right: 0em;
-    color: #ffffff;
-    text-shadow: 0 0 0.1em #000000, 0 0 0.1em #000000, 0 0 0.2em #000000, 0 0 0.2em #000000;
-}
-
-article.stage table.skills{
-    display: block;
-    font-size: 90%;
-    border: 0.1em solid black;
-    color: #ffffff;
-    margin: 0.5em auto;
-    border-collapse: collapse;
-    border-radius: 0.4em;
-    vertical-align: top;
-    width: 90%;
-}
-@media screen and (max-width : 990px){
-    article.stage table.skills{
-        width: 100%;
-    }
-}
-
-article.stage table.skills th{
-    font-weight: bold;
-    background-color: #00000066;
-}
-
-article.stage table.skills > tbody >  tr > td{
-    vertical-align: top;
-    padding: 0.2em 0.5em;
-    border-right: 0.1em solid #00000088;
-    border-left: 0.1em solid #00000088;
-    border-top: 0.2em solid #00000088;
-    border-bottom: 0.2em solid #00000088;
-}
-
-article.stage table.skills td.skill_img img{
-    width: 4em;
-    height: 4em;
-    border: 0.15em solid var(--ui-color-border);
-    border-radius: 0.4em;
-}
-
-article.stage table.skills td.skill_text{
-    text-align: left;
-}
-
-article.stage table.skills td.skill_text h4{
-    text-shadow: 0 0 0.2em #000000, 0 0 0.2em #000000, 0 0 0.4em #000000;
-    margin: 0 0 0.5em 0;
-    display: block;
-    font-weight: bold;
-}
-
-article.stage table.skills td.skill_text p{
-    margin: 0 0 0 0;
-}
-
-/*article.stage table.skills span.skill_description{
-    font-size: 80%;
-    font-style: italic;
-    margin: 0;
-    padding: 0;
-    display: block;
-    line-height: 0.95em;
-}*/
-
-article.stage table.skills td.skill_extra{
-    text-align: center;
-}
-
-article.stage table.skills td.skill_text span.formula{
-    font-size: 80%;
-    color: #cccccc;
-    font-weight: bold;
-    font-style: italic;
-    font-family: monospace;
-    white-space: nowrap;
-}
-
-
-article.stage table.skills td.skill_text div.effects img{
-    width: 1.6em;
-    height: 1.6em;
-    border: 0.1em solid #000000;
-    border-radius: 0.5em;
-}
-
-article.stage table.skills td.skill_ups table{
-    font-size: 80%;
-    font-family: monospace;
-}
-article.stage table.skills td.skill_ups table td{
-    white-space: nowrap;
-}
-
-article.stage table.skills td.skill_ups table td.level{
-    display: inline-block;
-    text-align: left;
-}
-article.stage table.skills td.skill_ups table td.upgrade{
-    font-style: italic;
-    text-align: left;
-}

+ 7 - 134
public/css/monsters.css

@@ -1,139 +1,12 @@
-aside form.filter table td.label{
-    min-width: 5em;
+section#list article{
+    margin-bottom: 1.5em;
 }
-aside form.filter table input#filter_name{
-    width: 10em;
-}
-aside form.filter table select#filter_effect{
-    font-size: 60%;
-    overflow-y: always;
-    scrollbar-width: thin;
-    width: 11em;
-}
-aside form.filter table select#filter_effect_option{
-    margin-left: 1em;
-}
-
-
-section#list{
-    display: block;
-    vertical-align: top;
-}
-
-section#list div.monster{
-    display: inline-block;
-    position: relative;
-    font-size: 80%;
-}
-
-section#list div.monster div.monster_tooltip{
-    position: absolute;
-    top: 0;
-    left: 6em;
-    z-index: 10;
-    display: none;
-    background: var(--color-monster-tooltip-background);
-    border: 0.2em solid var(--ui-color-border);
-    border-radius: 0.5em;
-    padding: 0.5em;
-}
-
-section#list div.monster:hover div.monster_tooltip{
-    display: block;
-}
-@media screen and (max-width : 990px){
-    section#list div.monster:hover div.monster_tooltip{
-        display: none;
-    }
-}
-
-section#list div.monster div.monster_tooltip h4{
-    font-weight: bold;
-    text-shadow: 0 0 0.2em #000000, 0 0 0.2em #000000, 0 0 0.4em #000000;
-    margin: 0.1em;
-    display: block;
-}
-
-section#list div.monster div.monster_tooltip table.stats{
-    font-size: 80%;
-    border: 0.1em solid black;
-    color: #ffffff;
-    border-collapse: collapse;
-}
-
-section#list div.monster div.monster_tooltip table.stats th{
-    font-weight: bold;
-    border-bottom: 0.1em solid black;
-    background-color: var(--color-monster-tooltip-header);
-}
-
-section#list div.monster div.monster_tooltip table.stats tr{
-    line-height: 0.8em;
-}
-
-section#list div.monster div.monster_tooltip table.stats td{
-    border: 0.1em solid #00000055;
-    border-bottom: 0.1em solid black;
-    color: #cccccc;
-    text-align: right;
-    font-family: monospace;
-    font-size: 120%;
-    min-width: 4.5em;
-}
-
-section#list div.monster div.monster_tooltip table.stats td span.percent{
-    margin: 0 0 0 -0.7em;
-    font-size: 80%;
-}
-
-section#list div.monster div.monster_tooltip table.stats td.extra{
-    color: #aaaaff;
-}
-
-section#list div.monster div.monster_tooltip table.stats td.total{
-    color: #f2c920;
-    font-weight: bold;
-    min-width: 4.5em;
-}
-
-section#list div.monster div.monster_tooltip div.skills{
-    text-align: center;
-}
-
-section#list div.monster div.monster_tooltip div.skills div.skill{
+section#list article div.monster{
     display: inline-block;
-    vertical-align: top;
-    max-width: 22%;
-    margin: 0.5em;
-    font-size: 70%;
-    color: #ffffff;
-    text-align: center;
-}
-
-section#list div.monster div.monster_tooltip div.skills div.skill img{
-    width: 3em;
-    height: 3em;
-    border: 0.3em solid var(--ui-color-border);
-    border-radius: 0.5em;
-}
-section#list div.monster div.monster_tooltip div.skills div.skill span.skill_level{
-    font-size: 130%;
-    position: relative;
-    bottom: 1.4em;
-    width: 3em;
-    margin: auto;
-    display: block;
-    text-align: right;
-    padding-right: 0.4em;
-    text-shadow: 0 0 0.2em #000000, 0 0 0.2em #000000, 0 0 0.4em #000000, 0 0 0.4em #000000, 0 0 0.6em #000000, 0 0 0.6em #000000, 0 0 0.8em #000000, 0 0 0.8em #000000;
-}
-
-section#list div.monster div.monster_tooltip div.skills div.skill span.skill_level.maxed{
-    color: #f2c920;
+    font-size: 60%;
+    margin: 0.2em;
 }
 
-section#list div.monster div.monster_tooltip div.skills div.skill span.skill_name{
-    text-shadow: 0 0 0.2em #000000, 0 0 0.2em #000000, 0 0 0.4em #000000;
-    margin-top: -1.8em;
-    display: block;
+section#list article div.closed{
+    filter: brightness(50%);
 }

+ 0 - 0
public/css/home.css → public/css/player.css


+ 27 - 57
public/css/ui.css

@@ -180,7 +180,6 @@ a:hover{
     text-shadow: 0 0 0.1em var(--ui-color-a-shadow);
 }
 header{
-    border-bottom: var(--header-border);
     border-bottom-right-radius: var(--header-border-radius);
     border-bottom-left-radius: var(--header-border-radius);
     margin: 0 0 1em 0;
@@ -192,58 +191,12 @@ header{
     top: 0;
     z-index: 1000;
 }
-@media only screen and (max-width: 788px){
-    header{
-        position: initial;
-        grid-area: none;
-        margin: 0;
-        height: 4.2em;
-    }
-}
-header div#logo{
-    font-size: 100%;
-    margin: auto;
-    display: inline-block;
-    vertical-align: middle;
-    width: 9em;
-    text-align: center;
-    float: left;
-}
-header div#logo img#logo_img{
-    max-width: 9em;
-    max-height: 6em;
-}
-header div#logo h1#logo_text{
-    font-style: italic;
-    margin: -1.2em auto -0.7em auto;
-    text-shadow: 0 0 0.1em var(--ui-color-a-shadow), 0 0 0.1em var(--ui-color-a-shadow);
-}
-header div#logo h1#logo_text span.large{font-size: 90%;}
-header div#logo h1#logo_text span.small{
-    font-size: 60%;
-    margin-left: -0.5em;
-}
-@media screen and (max-width : 768px){
-    header div#logo{
-        position: absolute;
-        top: 0em;
-        right: 0em;
-    }
-    header div#logo img#logo_img{
-        max-width: 5em;
-        max-height: 3em;
-    }
-    header div#logo h1#logo_text span.large{
-        /*font-size: 50%;
-        margin: -0.2em;*/
-    }
-    header div#logo h1#logo_text span.small{/*display: none;*/}
-}
-header nav#menu{
+
+header nav.menu{
     display: block;
-    height: 4.5em;
+    border-bottom: var(--header-border);
 }
-header nav#menu table td{
+header nav.menu table td{
     font-size: 100%;
     font-weight: bold;
     text-transform: capitalize;
@@ -254,31 +207,48 @@ header nav#menu table td{
     vertical-align: middle;
 }
 @media screen and (max-width : 768px){
-    header nav#menu table td{
+    header nav.menu table td{
         font-size: 85%;
     }
 }
-header nav#menu table td.item{
+header nav.menu table td.item{
     border: var(--nav-separator);
     text-align: center;
+    padding: 0;
 }
-header nav#menu table td.last{
+header nav.menu table td.last{
     width: 100%;
     text-align: right;
     padding-right: 0.5em;
 }
-header nav#menu table td.item a{
+header nav.menu table td.item a{
     color: var(--nav-color);
     text-shadow: var(--nav-shadow);
     transition: all .4s ease-in-out;
     display: block;
-    padding: 0.3em 0.1em;
+    padding: 0.3em 0.6em;
 }
-header nav#menu table td.item a:hover{
+header nav.menu table td.item a:hover{
     background-color: var(--nav-background-hover);
     color: var(--nav-color-hover);
     text-shadow: var(--nav-shadow-hover);
 }
+
+header nav#menu_app h1#logo_text{
+    text-transform: none;
+    font-style: italic;
+    margin: 0em 10px;
+    text-shadow: 0 0 0.1em var(--ui-color-a-shadow), 0 0 0.1em var(--ui-color-a-shadow);
+    line-height: 0.6;
+    text-align: center;
+    width: 150px;
+}
+
+header nav#menu_player select#player_select{
+    width: 150px ; /* Px, the same as the logo*/
+    margin: 0em 10px;
+}
+
 header table td.profile nav#profile{
     float: right;
     margin-right: 0.5em;

+ 220 - 0
public/css/unit.css

@@ -0,0 +1,220 @@
+aside div#monster_info{
+    text-align: center;
+}
+aside div#monster_info div#monster_details{
+    display: block;
+    font-size: 70%;
+}
+@media only screen and (max-width: 788px){
+    aside div#monster_info div#monster_details{
+        display: inline-block;
+        vertical-align: top;
+        text-align: center;
+    }
+}
+aside div#monster_info div#monster_details div#teams{
+    text-align: left;
+    margin: 1em;
+}
+@media only screen and (max-width: 788px){
+    aside div#monster_info div#monster_details div#teams{
+        margin: 0.2em;
+    }
+    aside div#monster_info div#monster_details div#teams ul{
+        margin: 0.2em;
+    }
+}
+aside div#monster_info div#monster_details div#teams h4{
+    margin: 0.2em auto;
+}
+aside div#monster_info div#monster_details a#optimize{
+    display: block;
+    margin: auto 30%;
+}
+aside div#monster_info div#exp{
+    border: 0.3em solid var(--ui-color-border);
+    border-radius: 0.5em;
+    height: 1em;
+    background-color: #00000088;
+    position: relative;
+    font-size: 100%;
+    margin: auto 25%;
+}
+@media only screen and (max-width: 788px){
+    aside div#monster_info div#exp{
+        display: none;
+    }
+}
+aside div#monster_info div#exp span#exp_bar{
+    height: 1em;
+    border-radius: 0.4em;
+    display: block;
+    background-color: #cc0000;
+}
+aside div#monster_info div#exp span#exp_text{
+    display: block;
+    text-align: center;
+    position: absolute;
+    bottom: 0;
+    margin: auto;
+    width: 100%;
+    font-weight: bold;
+    font-size: 0.8em;
+    text-shadow: 0 0 0.1em #000000, 0 0 0.1em #000000, 0 0 0.1em #000000, 0 0 0.1em #000000, 0 0 0.1em #000000;
+}
+section#monster article#stats{
+    display: inline-block;
+    vertical-align: top;
+}
+section#monster article#stats table{
+    font-size: 90%;
+    vertical-align: top;
+    display: inline-block;
+    border: 0.1em solid black;
+    color: #ffffff;
+    margin: auto;
+    border-collapse: collapse;
+    border-radius: 0.4em;
+}
+section#monster article#stats table th{
+    font-weight: bold;
+    background-color: #00000066;
+}
+section#monster article#stats table td{
+    font-weight: bold;
+    background-color: #ffffff33;
+    text-align: right;
+    font-family: monospace;
+    font-size: 120%;
+}
+section#monster article#stats table td span.percent{
+    margin: 0 0 0 -0.7em;
+    font-size: 80%;
+}
+section#monster article#stats table td, th{
+    padding: 0.2em;
+    border: 0.1em solid #000000;
+}
+section#monster article#stats table td.extra{
+    color: #aaaaff;
+}
+section#monster article#stats table td.total{
+    color: #f2c920;
+    font-weight: bold;
+}
+section#monster article#artifacts{
+    display: inline-block;
+    vertical-align: top;
+}
+section#monster article#artifacts table{
+    display: block;
+}
+section#monster article#runes{
+    display: inline-block;
+    vertical-align: top;
+    width: 50em; /*Force two rows*/
+}
+@media only screen and (max-width: 788px){
+    section#monster article#runes{
+        width: 100%;
+    }
+}
+section#monster article#skills > table{
+    display: block;
+    font-size: 90%;
+    border: 0.1em solid black;
+    color: #ffffff;
+    margin: 0.5em auto;
+    border-collapse: collapse;
+    border-radius: 0.4em;
+    vertical-align: top;
+    width: 90%;
+}
+@media screen and (max-width : 990px){
+    section#monster article#skills > table{
+        width: 100%;
+    }
+}
+section#monster article#skills > table th{
+    font-weight: bold;
+    background-color: #00000066;
+}
+section#monster article#skills > table > tbody >  tr > td{
+    vertical-align: top;
+    padding: 0.2em 0.5em;
+    border-right: 0.1em solid #00000088;
+    border-left: 0.1em solid #00000088;
+    border-top: 0.2em solid #00000088;
+    border-bottom: 0.2em solid #00000088;
+}
+section#monster article#skills > table td.skill_img img{
+    width: 4em;
+    height: 4em;
+    border: 0.15em solid var(--ui-color-border);
+    border-radius: 0.4em;
+}
+section#monster article#skills > table span.skill_level{
+    font-size: 100%;
+    top: 2.8em;
+    display: block;
+    width: 3.5em;
+    margin: -1.5em auto 0.5em auto;
+    text-align: right;
+    text-shadow: 0 0 0.2em #000000, 0 0 0.2em #000000, 0 0 0.4em #000000, 0 0 0.4em #000000, 0 0 0.6em #000000, 0 0 0.6em #000000, 0 0 0.8em #000000, 0 0 0.8em #000000;
+}
+section#monster article#skills > table span.skill_level.maxed{
+    color: #f2c920;
+}
+section#monster article#skills > table td.skill_text{
+    text-align: left;
+}
+section#monster article#skills > table td.skill_text h4{
+    text-shadow: 0 0 0.2em #000000, 0 0 0.2em #000000, 0 0 0.4em #000000;
+    margin: 0 0 0.5em 0;
+    display: block;
+    font-weight: bold;
+}
+section#monster article#skills > table td.skill_text p{
+    margin: 0 0 0 0;
+}
+section#monster article#skills > table td.skill_extra{
+    text-align: center;
+}
+section#monster article#skills > table td.skill_text span.formula{
+    font-size: 80%;
+    color: #cccccc;
+    font-weight: bold;
+    font-style: italic;
+    font-family: monospace;
+    white-space: nowrap;
+}
+section#monster article#skills > table td.skill_text div.effects img{
+    width: 1.6em;
+    height: 1.6em;
+    border: 0.1em solid #000000;
+    border-radius: 0.5em;
+}
+section#monster article#skills > table td.skill_ups table{
+    font-size: 80%;
+    font-family: monospace;
+}
+section#monster article#skills > table td.skill_ups table td{
+    white-space: nowrap;
+    color: #999999;
+}
+section#monster article#skills > table td.skill_ups td.aquired{
+    color: #efefef;
+}
+section#monster article#skills > table td.skill_ups table td.level{
+    display: inline-block;
+    text-align: left;
+}
+section#monster article#skills > table td.skill_ups table td.upgrade{
+    font-style: italic;
+    text-align: left;
+}
+/*section#monster article#profile td#skill_averages{
+    font-size: 90%;
+    font-style: italic;
+    border-top: 0.1em solid #cccccc;
+}*/

+ 139 - 0
public/css/units.css

@@ -0,0 +1,139 @@
+aside form.filter table td.label{
+    min-width: 5em;
+}
+aside form.filter table input#filter_name{
+    width: 10em;
+}
+aside form.filter table select#filter_effect{
+    font-size: 60%;
+    overflow-y: always;
+    scrollbar-width: thin;
+    width: 11em;
+}
+aside form.filter table select#filter_effect_option{
+    margin-left: 1em;
+}
+
+
+section#list{
+    display: block;
+    vertical-align: top;
+}
+
+section#list div.monster{
+    display: inline-block;
+    position: relative;
+    font-size: 80%;
+}
+
+section#list div.monster div.monster_tooltip{
+    position: absolute;
+    top: 0;
+    left: 6em;
+    z-index: 10;
+    display: none;
+    background: var(--color-monster-tooltip-background);
+    border: 0.2em solid var(--ui-color-border);
+    border-radius: 0.5em;
+    padding: 0.5em;
+}
+
+section#list div.monster:hover div.monster_tooltip{
+    display: block;
+}
+@media screen and (max-width : 990px){
+    section#list div.monster:hover div.monster_tooltip{
+        display: none;
+    }
+}
+
+section#list div.monster div.monster_tooltip h4{
+    font-weight: bold;
+    text-shadow: 0 0 0.2em #000000, 0 0 0.2em #000000, 0 0 0.4em #000000;
+    margin: 0.1em;
+    display: block;
+}
+
+section#list div.monster div.monster_tooltip table.stats{
+    font-size: 80%;
+    border: 0.1em solid black;
+    color: #ffffff;
+    border-collapse: collapse;
+}
+
+section#list div.monster div.monster_tooltip table.stats th{
+    font-weight: bold;
+    border-bottom: 0.1em solid black;
+    background-color: var(--color-monster-tooltip-header);
+}
+
+section#list div.monster div.monster_tooltip table.stats tr{
+    line-height: 0.8em;
+}
+
+section#list div.monster div.monster_tooltip table.stats td{
+    border: 0.1em solid #00000055;
+    border-bottom: 0.1em solid black;
+    color: #cccccc;
+    text-align: right;
+    font-family: monospace;
+    font-size: 120%;
+    min-width: 4.5em;
+}
+
+section#list div.monster div.monster_tooltip table.stats td span.percent{
+    margin: 0 0 0 -0.7em;
+    font-size: 80%;
+}
+
+section#list div.monster div.monster_tooltip table.stats td.extra{
+    color: #aaaaff;
+}
+
+section#list div.monster div.monster_tooltip table.stats td.total{
+    color: #f2c920;
+    font-weight: bold;
+    min-width: 4.5em;
+}
+
+section#list div.monster div.monster_tooltip div.skills{
+    text-align: center;
+}
+
+section#list div.monster div.monster_tooltip div.skills div.skill{
+    display: inline-block;
+    vertical-align: top;
+    max-width: 22%;
+    margin: 0.5em;
+    font-size: 70%;
+    color: #ffffff;
+    text-align: center;
+}
+
+section#list div.monster div.monster_tooltip div.skills div.skill img{
+    width: 3em;
+    height: 3em;
+    border: 0.3em solid var(--ui-color-border);
+    border-radius: 0.5em;
+}
+section#list div.monster div.monster_tooltip div.skills div.skill span.skill_level{
+    font-size: 130%;
+    position: relative;
+    bottom: 1.4em;
+    width: 3em;
+    margin: auto;
+    display: block;
+    text-align: right;
+    padding-right: 0.4em;
+    text-shadow: 0 0 0.2em #000000, 0 0 0.2em #000000, 0 0 0.4em #000000, 0 0 0.4em #000000, 0 0 0.6em #000000, 0 0 0.6em #000000, 0 0 0.8em #000000, 0 0 0.8em #000000;
+}
+
+section#list div.monster div.monster_tooltip div.skills div.skill span.skill_level.maxed{
+    color: #f2c920;
+}
+
+section#list div.monster div.monster_tooltip div.skills div.skill span.skill_name{
+    text-shadow: 0 0 0.2em #000000, 0 0 0.2em #000000, 0 0 0.4em #000000;
+    margin-top: -1.8em;
+    display: block;
+}