Selaa lähdekoodia

Basic run logging. Working for Cairos and Scenario, but with some quircks.

Inigo Valentin 6 vuotta sitten
vanhempi
sitoutus
e5c57e7fd1

+ 66 - 0
application/Constant.php

@@ -61,6 +61,72 @@
         "GUILD" => 5,
     ];
 
+    $AREA_TYPE = [
+        'SCENARIO' => 1,
+        'CAIROS_DUNGEON' => 2,
+        'RIFT_DUNGEON' => 3,
+        'RIFT_RAID' => 4,
+        'DIMENSIONAL_HOLE' => 5,
+        'ARENA' => 6,
+        'GUILD_WAR' => 7,
+        'GUILD_SIEGE' => 8,
+        'TARTARUS_LABYRINTH' => 9,
+        'TRIAL_OF_ASCENSION' => 10,
+        'WORLD_BOSS' => 11,
+        'DIMENSIONAL_RIFT' => 12,
+    ];
+
+    $SCENARIO = [
+        'GAREN_FOREST' => 1,
+        'MT._SIZ' => 2,
+        'KABIR_RUINS' => 3,
+        'MT._WHITE_RAGON' => 4,
+        'TELAIN_FOREST' => 5,
+        'HYDENI_RUINS' => 6,
+        'TAMOR_DESERT' => 7,
+        'VROFAGUS_RUINS' => 8,
+        'FAIMON_VOLCANO' => 9,
+        'AIDEN_FOREST' => 10,
+        'FERUN_CASTLE' => 11,
+        'MT_RUNAR' => 12,
+        'CHIRUKA_REMAINS' => 13,
+    ];
+
+    $DUNGEON = [
+        'HALL_OF_DARK' => 1001,
+        'SANCTUARY_OF_DREAMING_FAIRIES' => 1101,
+        'ELLUNIA_REMAINS_FAIRY' => 1201,
+        'ELLUNIA_REMAINS_PIXIE' => 1202,
+        'HALL_OF_FIRE' => 2001,
+        'FOREST_OF_ROARING_BEASTS' => 2101,
+        'KARZHAN_REMAINS_WARBEAR' => 2201,
+        'KARZHAN_REMAINS_INUGAMI' => 2202,
+        'HALL_OF_WATER' => 3001,
+        'HALL_OF_WIND' => 4001,
+        'HALL_OF_MAGIC' => 5001,
+        'NECROPOLIS' => 6001,
+        'HALL_OF_LIGHT' => 7001,
+        'GIANTS_KEEP' => 8001,
+        'DRAGONS_LAIR' => 9001,
+
+    ];
+
+    $ELEMENTAL_RIFT_DUNGEON = [
+        'ICE_BEAST' => 1001,
+        'FIRE_BEAST' => 2001,
+        'WIND_BEAST' => 3001,
+        'LIGHT_BEAST' => 4001,
+        'DARK_BEAST' => 5001,
+    ];
+
+    $RAID_RIFT_DUNGEON = [
+        'LEVEL_1' => 1,
+        'LEVEL_2' => 2,
+        'LEVEL_3' => 3,
+        'LEVEL_4' => 4,
+        'LEVEL_5' => 5,
+    ];
+
     $BUILDING = [
         'SUMMONERS_TOWER' => 1,
         'SUMMONHENGE' => 2,

+ 4 - 0
application/Controller.php

@@ -57,6 +57,10 @@
                     require_once($path["page"] . "Guild_Page.php");
                     $page = new Guild_Page($db);
                 }
+                elseif (strtoupper($pars[0]) == "RUNS"){
+                    require_once($path["page"] . "Runs_Page.php");
+                    $page = new Runs_Page($db);
+                }
                 elseif (strtoupper($pars[0]) == "MONSTERS"){
                     if (count($pars) == 1){
                         require_once($path["page"] . "Monsters_Page.php");

+ 136 - 0
application/bin/run-log.py

@@ -0,0 +1,136 @@
+#!/usr/bin/python3
+
+import sqlite3
+import json
+import sys
+
+def readData():
+    try:
+        print(sys.argv[1])
+        data = json.loads(sys.argv[1])
+        return data
+    except Exception as e:
+        print("Error parsng data: " + str(e))
+        raise
+
+"""
+Opens the database file and deletes from the user tables
+
+:param name: The path to the sqlite database.
+:returns: Connection to the database.
+:raises IntegrityError: The queryes couldn't bre executed.
+:raises IOError: The sqlite file couldn't be created.
+"""
+def openDatabase(name):
+    print('Configuring database...')
+    try:
+        db = sqlite3.connect(name)
+        return db
+    except IOError as e:
+        print("I/O Error connecting to database " + name + ": " + str(e))
+        raise
+
+
+"""
+Inserts a row into the database.
+
+:param db: Connection to the database.
+:param table: Name of the table to insert into.
+:param values: List of values to insert.
+:raises IntegrityError: The insert query was unsuccesfull.
+"""
+def insert(db, table, values):
+    cursor = db.cursor()
+    placeholders = ''
+    for x in range(0, len(values)):
+        placeholders = placeholders + '?, '
+    placeholders = placeholders[:len(placeholders) - 2]
+    query = 'INSERT INTO ' + table + ' VALUES (' + placeholders + ')'
+    try:
+        cursor.execute(query, values)
+    except sqlite3.IntegrityError as e:
+        print('Error Inserting into ' + table + ' with values (' + str(values) + '): ' + str(e))
+        raise
+    cursor.close;
+
+"""
+Parses run data (tables run, run_party, run_drop_rune, run_drop_runecraft,
+run_drop_item, run_drop_unit, run_drop_sd, run_drop_unit_pieces,
+run_drop_shapeshifting).
+
+:param db: Sqlite database connection.
+:param data: JSON data.
+"""
+def parseRun(db, data):
+    print("Parsing run...")
+    cursor = db.cursor()
+    uid = data["uid"]
+    dtime = data["dtime"]
+    # Check if run has already been inserted.
+    cursor.execute("SELECT count(id) AS c FROM run WHERE uid = ? AND dtime = ?;", (uid, dtime))
+    if (cursor.fetchone()[0] > 0):
+        print("    Run already in database. Stopping....")
+        return;
+    area = data["area"]
+    stage = data["stage"]
+    difficulty = data["difficulty"]
+    win = data["win"]
+    time = data["time"]
+    mana = data["mana"]
+    energy = data["energy"]
+    crystal = data["crystal"]
+    helper = data["helper"]
+    cursor.execute("SELECT max(id) + 1 AS id FROM run;")
+    id = cursor.fetchone()[0]
+    insert(db, "run", (uid, id, dtime, area, stage, difficulty, win, time, mana, energy, crystal, helper))
+    if data["shapeshifting"] > 0:
+        insert(db, "run_drop_shapeshifting", (id, data["shapeshifting"]))
+    if data["sd"] > 0:
+        insert(db, "run_drop_sd", (id, data["sd"]))
+    if data["sd"] > 0:
+        insert(db, "run_drop_unit", (id, data["unit"]))
+    for rune in data["rune"]:
+        rune_id = rune["id"]
+        rune_type = rune["type"]
+        slot = rune["slot"]
+        stars = rune["stars"]
+        ancient = rune["ancient"]
+        quality = rune["quality"]
+        value = rune["value"]
+        efficiency = rune["efficiency"]
+        main_stat = rune["main_stat"]
+        main_stat_value = rune["main_stat_value"]
+        innate_stat = rune["innate_stat"]
+        innate_stat_value = rune["innate_stat_value"]
+        substat_1 = rune["substat_1"]
+        substat_1_value = rune["substat_1_value"]
+        substat_2 = rune["substat_2"]
+        substat_2_value = rune["substat_2_value"]
+        substat_3 = rune["substat_3"]
+        substat_3_value = rune["substat_3_value"]
+        substat_4 = rune["substat_4"]
+        substat_4_value = rune["substat_4_value"]
+        insert(db, "run_drop_rune", (id, rune_id, rune_type, slot, stars, ancient, quality, value, efficiency, main_stat, main_stat_value, innate_stat, innate_stat_value, substat_1, substat_1_value, substat_2, substat_2_value, substat_3, substat_3_value, substat_4, substat_4_value))
+    for item in data["item"]:
+        item_id = item["id"]
+        quantity = item["quantity"]
+        insert(db, "run_drop_item", (id, item_id, quantity))
+    for pieces in data["unit_pieces"]:
+        pieces_id = pieces["id"]
+        quantity = pieces["quantity"]
+        insert(db, "run_drop_unit_pieces", (id, pieces_id, quantity))
+    for party in data["party"]:
+        unit_id = party["unit_id"]
+        unit_master_id = party["unit_master_id"]
+        insert(db, "run_party", (id, unit_id, unit_master_id))
+    db.commit()
+
+"""
+Begin script
+"""
+data = readData()
+# Path relative to the PHP script, not this one.
+db = openDatabase('../../../../application/sw.sqlite')
+parseRun(db, data)
+
+

+ 134 - 0
application/bin/save_run.py

@@ -0,0 +1,134 @@
+#!/usr/bin/python3
+
+import sqlite3
+import json
+
+def readData(file):
+    try:
+        data = json.loads(sys.argv[1])
+        return data
+    except Error as e:
+        print("Error parsng data: " + str(e))
+        raise
+
+"""
+Opens the database file and deletes from the user tables
+
+:param name: The path to the sqlite database.
+:returns: Connection to the database.
+:raises IntegrityError: The queryes couldn't bre executed.
+:raises IOError: The sqlite file couldn't be created.
+"""
+def openDatabase(name):
+    global OPT_CREATE_DB
+    print('Configuring database...')
+    try:
+        db = sqlite3.connect(name)
+        return db
+    except IOError as e:
+        print("I/O Error connecting to database " + name + ": " + str(e))
+        raise
+
+
+"""
+Inserts a row into the database.
+
+:param db: Connection to the database.
+:param table: Name of the table to insert into.
+:param values: List of values to insert.
+:raises IntegrityError: The insert query was unsuccesfull.
+"""
+def insert(db, table, values):
+    cursor = db.cursor()
+    placeholders = ''
+    for x in range(0, len(values)):
+        placeholders = placeholders + '?, '
+    placeholders = placeholders[:len(placeholders) - 2]
+    query = 'INSERT INTO ' + table + ' VALUES (' + placeholders + ')'
+    try:
+        cursor.execute(query, values)
+    except sqlite3.IntegrityError as e:
+        print('Error Inserting into ' + table + ' with values (' + str(values) + '): ' + str(e))
+        raise
+    cursor.close;
+
+"""
+Parses run data (tables run, run_party, run_drop_rune, run_drop_runecraft,
+run_drop_item, run_drop_unit, run_drop_sd, run_drop_unit_pieces,
+run_drop_shapeshifting).
+
+:param db: Sqlite database connection.
+:param data: JSON data.
+"""
+def parseUnits(db, data):
+    print("Parsing run...")
+    cursor = db.cursor()
+    uid = data["uid"]
+    dtime = data["dtime"]
+    # Check if run has already been inserted.
+    cursor.execute("SELECT count(id) AS c FROM run WHERE uid = ? AND dtime = ?;", (uid, dtime))
+    if (cur.fetchone()["c"] > 0):
+        print("    Run already in database. Stopping....")
+        return;
+    area = data["area"]
+    stage = data["stage"]
+    difficulty = data["difficulty"]
+    win = data["win"]
+    time = data["time"]
+    mana = data["mana"]
+    energy = data["energy"]
+    crystal = data["crystal"]
+    helper = data["helper"]
+    cursor.execute("SELECT max(id) + 1 AS id FROM run;")
+    id = cur.fetchone()["c"]
+    insert(db, "run", (uid, id, dtime, area, stage, difficulty, win, time, mana, energy, crystal, helper))
+    if data["sapeshifting"] > 0:
+        insert(db, "run_drop_sapeshifting", (id, data["sapeshifting"]))
+    if data["sd"] > 0:
+        insert(db, "run_drop_sd", (id, data["sd"]))
+    if data["sd"] > 0:
+        insert(db, "run_drop_unit", (id, data["unit"]))
+    for rune in data["rune"]:
+        rune_id = rune["id"]
+        rune_type = rune["type"]
+        slot = rune["slot"]
+        stars = rune["stars"]
+        ancient = rune["ancient"]
+        quality = rune["quality"]
+        value = rune["value"]
+        efficiency = rune["efficiency"]
+        main_stat = rune["main_stat"]
+        main_stat_value = rune["main_stat_value"]
+        innate_stat = rune["innate_stat"]
+        innate_stat_value = rune["innate_stat_value"]
+        substat_1 = rune["substat_1"]
+        substat_1_value = rune["substat_1_value"]
+        substat_2 = rune["substat_2"]
+        substat_2_value = rune["substat_2_value"]
+        substat_3 = rune["substat_3"]
+        substat_3_value = rune["substat_3_value"]
+        substat_4 = rune["substat_4"]
+        substat_4_value = rune["substat_4_value"]
+        insert(db, "run_drop_rune", (id, rune_id, rune_type, slot, stars, ancient, quality, value, efficiency, main_stat, main_stat_value, innate_stat, innate_stat_value, substat_1, substat_1_value, substat_2, substat_2_value, substat_3, substat_3_value, substat_4, substat_4_value))
+    for item in data["item"]:
+        item_id = item["id"]
+        quantity = item["quantity"]
+        insert(db, "run_drop_item", (id, item_id, quantity))
+    for pieces in data["unit_pieces"]:
+        pieces_id = pieces["id"]
+        quantity = pieces["quantity"]
+        insert(db, "run_drop_unit_pieces", (id, pieces_id, quantity))
+    for party in data["party"]:
+        unit_id = party["unit_id"]
+        unit_master_id = party["unit_master_id"]
+        insert(db, "run_party", (id, unit_id, unit_master_id))
+    db.commit()
+
+"""
+Begin script
+"""
+data = readData()
+db = openDatabase('../sw.sqlite')
+parseRun(db, data)
+
+

+ 5 - 0
application/entity/Inventory.php

@@ -21,6 +21,11 @@
          */
         public $id;
 
+        /**
+         * Item name.
+         */
+        public $name;
+
         /**
          * Item type name.
          */

+ 251 - 0
application/entity/K_Area.php

@@ -0,0 +1,251 @@
+<?php
+
+    require_once($path["entity"] . "Entity.php");
+
+
+    /**
+     * An area.
+     *
+     * Represents an object from the table 'k_area'.
+     */
+    class K_Area extends Entity{
+
+        /**
+         * Area identifier (it may not be unique!).
+         */
+        public $id;
+
+        /**
+         * Area type.
+         */
+        public $type;
+
+        /**
+         * Area name.
+         */
+        public $name;
+
+        /**
+         * Constructor.
+         *
+         * Searches the database and retrieves the information about the
+         * run, populating it and it's items.
+         *
+         * @param SQLite3 $db Connection to the database.
+         * @param int $id Area identifier.
+         * @param int $type Area type (required).
+         */
+        public function __construct($db, $id, $type){
+            global $path;
+            parent::__construct($db);
+            $s = "
+                SELECT
+                  id,
+                  type,
+                  name
+                FROM k_area
+                WHERE id = $id
+                ORDER BY type = $type DESC;
+            ";
+            $q = $this->db->query($s);
+            $r = $q->fetchArray(SQLITE3_ASSOC);
+            if ($r){
+                $this->id = $r["id"];
+                $this->type = $r["type"];
+                $this->name = $r["name"];
+            }
+        }
+
+        /**
+         * Gets the path to the area image. The image must be in the
+         * img/content/area/ directory, and its name must be the monster id,
+         * padded with '0' to 9 digites, and the extension must be '.png'.
+         *
+         * @return path to the image, or a path to a fixed unknown image if it
+         *         doesn't exist.
+         */
+        public function get_image(){
+            global $base_dir;
+            global $path;
+            global $AREA_TYPE;
+            global $DUNGEON;
+            if (
+              (
+                $this->type == $AREA_TYPE["SCENARIO"] ||
+                $this->type == $AREA_TYPE["RIFT_DUNGEON"] ||
+                $this->type == $AREA_TYPE["DIMENSIONAL_HOLE"] ||
+                $this->type == $AREA_TYPE["RIFT_RAID"] ||
+                $this->type == $AREA_TYPE["ARENA"] ||
+                $this->type == $AREA_TYPE["GUILD_WAR"] ||
+                $this->type == $AREA_TYPE["GUILD_SIEGE"] ||
+                $this->type == $AREA_TYPE["TARTARUS_LABYRINTH"] ||
+                $this->type == $AREA_TYPE["TRIAL_OF_ASCENSION"] ||
+                $this->type == $AREA_TYPE["WORLD_BOSS"] ||
+                $this->type == $AREA_TYPE["DIMENSIONAL_RIFT"]
+              ) && file_exists($base_dir . "img/content/area/" . str_pad($this->id, 9, '0', STR_PAD_LEFT) . ".png")
+            ){
+                return $path["img"]["content"] . "area/" . str_pad($this->id, 9, '0', STR_PAD_LEFT) . ".png";
+            }
+            elseif ($this->type == $AREA_TYPE["CAIROS_DUNGEON"] && in_array($this->id, $DUNGEON)){
+                switch ($this->id){
+                    case $DUNGEON["DRAGONS_LAIR"]:
+                        switch ($this->stage){
+                            case 1:
+                            case 6:
+                                // Light
+                                if (file_exists($base_dir . "img/content/unit/00060204.png")){
+                                    return $path["img"]["content"] . "unit/00060204.png";
+                                }
+                                break;
+                                break;
+                            case 2:
+                            case 7:
+                                // Dark
+                                if (file_exists($base_dir . "img/content/unit/00060204.png")){
+                                    return $path["img"]["content"] . "unit/00060204.png";
+                                }
+                                break;
+                            case 3:
+                            case 8:
+                                // Wind
+                                if (file_exists($base_dir . "img/content/unit/00060203.png")){
+                                    return $path["img"]["content"] . "unit/00060203.png";
+                                }
+                                break;
+                            case 4:
+                            case 9:
+                                // Fire
+                                if (file_exists($base_dir . "img/content/unit/00060202.png")){
+                                    return $path["img"]["content"] . "unit/00060202.png";
+                                }
+                                break;
+                            case 5:
+                            case 10:
+                                // Water
+                                if (file_exists($base_dir . "img/content/unit/00060201.png")){
+                                    return $path["img"]["content"] . "unit/00060201.png";
+                                }
+                                break;
+                        }
+                        break;
+                    case $DUNGEON["GIANTS_KEEP"]:
+                        switch ($this->stage){
+                            case 1:
+                            case 6:
+                                // Wind
+                                if (file_exists($base_dir . "img/content/unit/00060303.png")){
+                                    return $path["img"]["content"] . "unit/00060303.png";
+                                }
+                                break;
+                                break;
+                            case 2:
+                            case 7:
+                                // Light
+                                if (file_exists($base_dir . "img/content/unit/00060304.png")){
+                                    return $path["img"]["content"] . "unit/00060304.png";
+                                }
+                                break;
+                            case 3:
+                            case 8:
+                                // Water
+                                if (file_exists($base_dir . "img/content/unit/00060301.png")){
+                                    return $path["img"]["content"] . "unit/00060301.png";
+                                }
+                                break;
+                            case 4:
+                            case 9:
+                                // Dark
+                                if (file_exists($base_dir . "img/content/unit/00060305.png")){
+                                    return $path["img"]["content"] . "unit/00060305.png";
+                                }
+                                break;
+                            case 5:
+                            case 10:
+                                // Fire
+                                if (file_exists($base_dir . "img/content/unit/00060302.png")){
+                                    return $path["img"]["content"] . "unit/00060302.png";
+                                }
+                                break;
+                        }
+                        break;
+                    case $DUNGEON["NECROPOLIS"]:
+                        switch ($this->stage){
+                            case 1:
+                            case 6:
+                                // Water
+                                if (file_exists($base_dir . "img/content/unit/000621301.png")){
+                                    return $path["img"]["content"] . "unit/000621301.png";
+                                }
+                                break;
+                                break;
+                            case 2:
+                            case 7:
+                                // Fire
+                                if (file_exists($base_dir . "img/content/unit/000621302.png")){
+                                    return $path["img"]["content"] . "unit/000621302.png";
+                                }
+                                break;
+                            case 3:
+                            case 8:
+                                // Wind
+                                if (file_exists($base_dir . "img/content/unit/000621303.png")){
+                                    return $path["img"]["content"] . "unit/000621303.png";
+                                }
+                                break;
+                            case 4:
+                            case 9:
+                                // Light
+                                if (file_exists($base_dir . "img/content/unit/000621304.png")){
+                                    return $path["img"]["content"] . "unit/000621304.png";
+                                }
+                                break;
+                            case 5:
+                            case 10:
+                                // Dark
+                                if (file_exists($base_dir . "img/content/unit/000621305.png")){
+                                    return $path["img"]["content"] . "unit/000621305.png";
+                                }
+                                break;
+                        }
+                        break;
+                    case $DUNGEON["HALL_OF_WATER"]:
+                        if (file_exists($base_dir . "img/content/unit/00060101.png")){
+                            return $path["img"]["content"] . "unit/00060101.png";
+                        }
+                        break;
+                    case $DUNGEON["HALL_OF_FIRE"]:
+                        if (file_exists($base_dir . "img/content/unit/00060102.png")){
+                            return $path["img"]["content"] . "unit/00060102.png";
+                        }
+                        break;
+                    case $DUNGEON["HALL_OF_WIND"]:
+                        if (file_exists($base_dir . "img/content/unit/00060103.png")){
+                            return $path["img"]["content"] . "unit/00060103.png";
+                        }
+                        break;
+                    case $DUNGEON["HALL_OF_LIGHT"]:
+                        if (file_exists($base_dir . "img/content/unit/00060104.png")){
+                            return $path["img"]["content"] . "unit/00060104.png";
+                        }
+                        break;
+                    case $DUNGEON["HALL_OF_DARK"]:
+                        if (file_exists($base_dir . "img/content/unit/00060105.png")){
+                            return $path["img"]["content"] . "unit/00060105.png";
+                        }
+                        break;
+                    case $DUNGEON["HALL_OF_MAGIC"]:
+                        if (file_exists($base_dir . "img/content/unit/00062004.png")){
+                            return $path["img"]["content"] . "unit/00062004.png";
+                        }
+                        break;
+                    default:
+                        if (file_exists($base_dir . "img/content/area/" . str_pad($this->id, 9, '0', STR_PAD_LEFT) . ".png")){
+                            return $path["img"]["content"] . "area/" . str_pad($this->id, 9, '0', STR_PAD_LEFT) . ".png";
+                        }
+                }
+            }
+            return $path["img"]["layout"] . "unknown.png";
+        }
+
+    }
+?>

+ 309 - 0
application/entity/Run.php

@@ -0,0 +1,309 @@
+<?php
+
+    require_once($path["entity"] . "Entity.php");
+    require_once($path["entity"] . "Unit.php");
+    require_once($path["entity"] . "K_Unit.php");
+    require_once($path["entity"] . "Rune.php");
+    require_once($path["entity"] . "Inventory.php");
+    require_once($path["entity"] . "K_Area.php");
+
+
+    /**
+     * A run.
+     *
+     * Represents an object from the table 'run'.
+     */
+    class Run extends Entity{
+
+        /**
+         * Player identifier.
+         */
+        public $uid;
+
+        /**
+         * Run identifier.
+         */
+        public $id;
+
+        /**
+         * Run start datestame.
+         */
+        public $dtime;
+
+        /**
+         * Area.
+         */
+        public $area;
+
+        /**
+         * Stage.
+         */
+        public $stage;
+
+        /**
+         * Difficulty (only scenario and dimension hole)
+         */
+        public $difficulty;
+
+        /**
+         * Win or lost run.
+         */
+        public $win;
+
+        /**
+         * Clear time.
+         */
+        public $time;
+
+        /**
+         * Reward mana.
+         */
+        public $mana;
+
+        /**
+         * Reward energy.
+         */
+        public $energy;
+
+        /**
+         * Reward crystal.
+         */
+        public $crystal;
+
+        /**
+         * Indicates if a frind/mentor helped.
+         */
+        public $helper;
+
+        /**
+         * Array of {@see Unit}s or {@see K_Unit}.
+         * If the unit is still owned, it will be a instance of Unit.
+         * If not, it will be a instance of K_Unit.
+         */
+        public $party = [];
+
+        /**
+         * List of {@see Inventory}, of dropped items.
+         */
+        public $item = [];
+
+        /**
+         * List of {@see K_Unit}s dropped.
+         */
+        public $unit = [];
+
+        /**
+         * List of {@see K_Unit}s whose secret dungeons were found on the run.
+         */
+        public $sd= [];
+
+        /**
+         * List of [{@see K_Unit}, quantity] of dropped summon pieces.
+         */
+        public $unit_piece = [];
+
+        /**
+         * Number of shapeshifting stones dropped;
+         */
+        public $shapeshifting = 0;
+
+        /**
+         * List of {@see Rune}s dropped.
+         */
+        public $rune= [];
+
+        /**
+         * Constructor.
+         *
+         * Searches the database and retrieves the information about the
+         * run, populating it and it's items.
+         *
+         * @param SQLite3 $db Connection to the database.
+         * @param int $id Run identifier.
+         */
+        public function __construct($db, $id){
+            global $path;
+            global $AREA_TYPE;
+            global $SCENARIO;
+            global $DUNGEON;
+            global $RAID_RIFT_DUNGEON;
+            global $ELEMENTAL_RIFT_DUNGEON;
+            parent::__construct($db);
+            $s = "
+                SELECT
+                  id,
+                  uid,
+                  dtime,
+                  area,
+                  stage,
+                  difficulty,
+                  win,
+                  time,
+                  mana,
+                  energy,
+                  crystal,
+                  helper
+                FROM run
+                WHERE id = $id;
+            ";
+            $q = $this->db->query($s);
+            $r = $q->fetchArray(SQLITE3_ASSOC);
+            $this->id = $r["id"];
+            $this->uid = $r["uid"];
+            $this->dtime = $r["dtime"];
+            $type = 0;
+            if (in_array($r["area"], $RAID_RIFT_DUNGEON) && $r["stage"] < 1){
+                $type = $AREA_TYPE["RIFT_RAID_DUNGEON"];
+            }
+            elseif (in_array($r["area"], $SCENARIO)){
+                $type = $AREA_TYPE["SCENARIO"];
+            }
+            elseif (in_array($r["area"], $ELEMENTAL_RIFT_DUNGEON) && $r["stage"] < 1){
+                $type = $AREA_TYPE["ELEMENTAL_RIFT_DUNGEON"];
+            }
+            elseif (in_array($r["area"], $DUNGEON)){
+                $type = $AREA_TYPE["CAIROS_DUNGEON"];
+            }
+            $this->area = new K_Area($this->db, $r["area"], $type);
+            $this->stage = $r["stage"];
+            $this->difficulty = $r["difficulty"];
+            $this->win = $r["win"];
+            $this->time = $r["time"];
+            $this->mana = $r["mana"];
+            $this->energy = $r["energy"];
+            $this->crystal = $r["crystal"];
+            $this->helper = $r["helper"];
+            $s = "
+                SELECT
+                  unit,
+                  k_unit,
+                  (SELECT count(id) FROM unit WHERE unit.unit = k_unit) AS owned
+                FROM run_party
+                WHERE run = $id;
+            ";
+            $q = $this->db->query($s);
+            while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+                if ($r["owned"] > 0){
+                    array_push($this->party, new Unit($this->db, $r["unit"], false));
+                }
+                else{
+                    array_push($this->party, new K_Unit($this->db, $r["k_unit"], false));
+                }
+            }
+            $s = "
+                SELECT
+                  item,
+                  quantity
+                FROM run_drop_item
+                WHERE run = $this->id;
+            ";
+            $q = $this->db->query($s);
+            while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+                $i = new K_Inventory($this->db, $r["item"]);
+                $i->amount =  $r["quantity"];
+                array_push($this->item, $i);
+            }
+            $s = "
+                SELECT unit
+                FROM run_drop_unit
+                WHERE run = $this->id;
+            ";
+            $q = $this->db->query($s);
+            while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+                array_push($this->unit, new K_Unit($this->db, $r["unit"]));
+            }
+            $s = "
+                SELECT unit
+                FROM run_drop_sd
+                WHERE run = $this->id;
+            ";
+            $q = $this->db->query($s);
+            while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+                array_push($this->sd, new K_Unit($this->db, $r["unit"]));
+            }
+            $s = "
+                SELECT
+                  unit,
+                  quantity
+                FROM run_drop_unit_pieces
+                WHERE run = $this->id;
+            ";
+            $q = $this->db->query($s);
+            while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+                $i = [
+                    "unit" => new K_Unit($this->db, $r["unit"]),
+                    "quantity" => $r["quantity"]
+                ];
+                array_push($this->unit_piece, $i);
+            }
+            $s = "
+                SELECT quantity
+                FROM run_drop_shapeshifting
+                WHERE run = $this->id;
+            ";
+            $q = $this->db->query($s);
+            $r = $q->fetchArray(SQLITE3_ASSOC);
+            if ($r){
+                $this->shapeshifting = $r["quantity"];
+            }
+            $s = "
+                SELECT
+                  id,
+                  type,
+                  slot,
+                  stars,
+                  ancient,
+                  quality,
+                  value,
+                  efficiency,
+                  main_stat,
+                  main_stat_value,
+                  innate_stat,
+                  innate_stat_value,
+                  substat_1,
+                  substat_1_value,
+                  substat_2,
+                  substat_2_value,
+                  substat_3,
+                  substat_3_value,
+                  substat_4,
+                  substat_4_value
+                FROM run_drop_rune
+                WHERE run = $this->id;
+            ";
+            $q = $this->db->query($s);
+            $r = $q->fetchArray(SQLITE3_ASSOC);
+            while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+                $rune = new Rune($this->id, $r["id"]);
+                if (!isset($rune->id)){
+                    $rune->id = $r["id"];
+                    $rune->assigned_to = null;
+                    $rune->type = $r["type"];
+                    $rune->slot = $r["slot"];
+                    $rune->stars = $r["stars"];
+                    $rune->ancient = $r["ancient"];
+                    $rune->quality = $r["quality"];
+                    $rune->original_quality = $r["quality"];
+                    $rune->value = $r["value"];
+                    $rune->efficiency = $r["efficiency"];
+                    // TODO: Calculate?
+                    $rune->max_efficiency = $r["efficiency"];
+                    $rune->main_stat = $r["main_stat"];
+                    $rune->main_stat_value = $r["main_stat_value"];
+                    $rune->innate_stat = $r["innate_stat"];
+                    $rune->innate_stat_value = $r["innate_stat_value"];
+                    $rune->substat_1 = $r["substat_1"];
+                    $rune->substat_1_value = $r["substat_1_value"];
+                    $rune->substat_2 = $r["substat_2"];
+                    $rune->substat_2_value = $r["substat_2_value"];
+                    $rune->substat_3 = $r["substat_3"];
+                    $rune->substat_3_value = $r["substat_3_value"];
+                    $rune->substat_4 = $r["substat_4"];
+                    $rune->substat_4_value = $r["substat_4_value"];
+                }
+                array_push($this->rune, $rune);
+            }
+        }
+
+    }
+?>

+ 0 - 5
application/helper/html.php

@@ -23,26 +23,21 @@
         }
         $html = "";
         if ($k->awakens_from == "" && $k->awakens_to == ""){
-            error_log("COND1");
             // No to, no from, silver monster.
             $star_class ="star_silver";
         }
         elseif ($k->awakens_from != ""){
-            error_log("COND2");
             // Already awakened
             if ($k->base_stars - $k->natural_stars == 1){
-                error_log("COND2.1");
                 // Normal awaken.
                 $star_class ="star_purple";
             }
             else{
-                error_log("COND2.2");
                 // Second awaken.
                 $star_class ="star_red";
             }
         }
         elseif ($k->awakens_to != ""){
-            error_log("COND3");
             // Awakeable.
             $star_class ="star_gold";
         }

+ 129 - 0
application/page/Runs_Page.php

@@ -0,0 +1,129 @@
+ <?php
+
+    require_once($path["page"] . "Page.php");
+    require_once($path["entity"] . "Run.php");
+
+    /**
+     * Run list page.
+     */
+    class Runs_Page extends Page{
+
+        /**
+         * Array of @{see Run}s.
+         */
+        public $runs = [];
+
+        /**
+         * The filters the page can handle.
+         */
+        public $filters = [
+            "area" => [],
+            "stage" => [],
+            "difficulty" => [],
+            "win_lose" => 0,
+            "helper" => false
+        ];
+
+        /**
+         * Constructor.
+         *
+         * Retrieves the data and initializes the variables.
+         *
+         * @param SQLite3 $db Connection to the database.
+         */
+        public function __construct($db){
+            global $path;
+            global $root;
+            parent::__construct($db);
+            $this->view = $path["view"] . "runs.php";
+            $this->parse_filters();
+            $s = $this->build_query();
+            $q = $this->db->query($s);
+            while ($r = $q->fetchArray(SQLITE3_ASSOC)){
+                array_push($this->runs, new Run($db, $r["id"]));
+            }
+            $this->title = "Runs - SWDB";
+            $this->description = "Run logs";
+            $this->canonical = $root . "runs/";
+        }
+
+        /**
+         * Parses the request looking for the selected filters, validates them
+         * and adds them to the $filters array.
+         */
+        private function parse_filters(){
+            if (isset($_GET["area"])){
+                $areas = $_GET["area"];
+                foreach ($areas as $area){
+                    array_push($this->filters["area"], intval($ara));
+                }
+            }
+            if (isset($_GET["stage"])){
+                $stages = $_GET["stage"];
+                foreach ($stages as $stage){
+                    array_push($this->filters["stage"], intval($ara));
+                }
+            }
+            if (isset($_GET["difficulty"])){
+                $difficultys = $_GET["difficulty"];
+                foreach ($difficultys as $difficulty){
+                    array_push($this->filters["difficulty"], intval($ara));
+                }
+            }
+            if (isset($_GET["win_lose"]) && intval($_GET["win_lose"]) >= 0 && intval($_GET["win_lose"]) <= 2){
+                $this->filters["win_lose"] = $_GET["win_lose"];
+            }
+            if (isset($_GET["helper"]) && $_GET["elper"] == "on"){
+                $this->filters["helper"] = true;
+            }
+            else{
+                $this->filters["helper"] = false;
+            }
+            return;
+        }
+
+        /**
+         * Builds the query to the run table using the selected or default
+         * filters.
+         * 
+         * @return The query to be executed.
+         */
+        private function build_query(){
+            $s = "
+                SELECT id
+                FROM run
+                WHERE 1 = 1
+             ";
+            if (count($this->filters["area"]) > 0){
+                $s = $s . " AND area IN ( ";
+                foreach($this->filters["area"] as $t){
+                    $s = $s . "'$t', ";
+                }
+                $s = $s . "'DUMMY0') ";
+            }
+            if (count($this->filters["stage"]) > 0){
+                $s = $s . " AND stage IN ( ";
+                foreach($this->filters["stage"] as $t){
+                    $s = $s . "'$t', ";
+                }
+                $s = $s . "'DUMMY0') ";
+            }
+            if (count($this->filters["difficulty"]) > 0){
+                $s = $s . " AND difficulty IN ( ";
+                foreach($this->filters["difficulty"] as $t){
+                    $s = $s . "'$t', ";
+                }
+                $s = $s . "'DUMMY0') ";
+            }
+            switch ($this->filters["win_lose"]){
+                case 1:
+                    $s = $s . " AND win = 1 ";
+                    break;
+                case 2:
+                    $s = $s . " AND win = 0 ";
+                    break;
+            }
+            return $s;
+        }
+    }
+?>

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

@@ -27,6 +27,9 @@
         <a href='/guild'>
             Guild
         </a>
+        <a href='/runs'>
+            Runs
+        </a>
         <a href='/report'>
             Reports
         </a>

+ 240 - 0
application/view/runs.php

@@ -0,0 +1,240 @@
+<?php
+    global $AREA_TYPE;
+    global $SCENARIO;
+?>
+<!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->title?></title>
+        <link rel='shortcut icon' href='<?=$page->favicon?>'/>
+        <!-- CSS files -->
+        <link rel='stylesheet' type='text/css' href='<?=$path["css"]?>ui.css'/>
+        <link rel='stylesheet' type='text/css' href='<?=$path["css"]?>runs.css'/>
+        <link rel='stylesheet' type='text/css' href='<?=$path["css"]?>filters.css'/>
+        <!-- Script files -->
+        <script src="<?=$path["js"]?>ui.js"></script>
+        <!-- Meta tags -->
+        <link rel='canonical' href='<?=$page->canonical?>'/>
+        <link rel='author' href='<?=$page->author?>'/>
+        <link rel='publisher' href='<?=$page->author?>'/>
+        <meta name='description' content='<?=$page->description?>'/>
+        <meta property='og:title' content='<?=$page->title?>'/>
+        <meta property='og:url' content='<?=$page->canonical?>'/>
+        <meta property='og:description' content='<?=$page->description?>'/>
+        <meta property='og:image' content='<?=$page->icon?>'/>
+        <meta property='og:site_name' content='<?=$page->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->title?>'/>
+        <meta name='twitter:description' content='<?=$page->description?>'/>
+        <meta name='twitter:image' content='<?=$page->icon?>'/>
+        <meta name='twitter:url' content='<?=$page->canonical?>'/>
+        <meta name='robots' content='index follow'/>
+    </head>
+    <body>
+<?php
+        include __DIR__ . "/inc/header.php";
+?>
+            <section class='filters'>
+                <h2>
+                    Rune filters
+                </h2>
+<?php
+                $filters = $page->filters;
+                //TODO
+                //include __DIR__ . "/inc/filter_runs.php";
+?>
+
+            </section> <!-- #filters -->
+            <section class='content'>
+                <article>
+                    <table id='runs'>
+                        <tr>
+                            <th>
+                                Area
+                            </th>
+                            <th>
+                                Date
+                            </th>
+                            <th>
+                                Team
+                            </th>
+                            <th>
+                                Time
+                            </th>
+                            <th>
+                                Reward
+                            </th>
+                        </tr>
+<?php
+                        foreach ($page->runs as $run){
+?>
+                            <tr>
+                                <td class='area'>
+                                    <div class='area'>
+                                        <img title='<?=$run->area->name?>' class='area' src='<?=$run->area->get_image()?>'/>
+<?php
+                                        if ($run->stage > 0){
+?>
+                                            <span class='stage'><?=$run->stage?></span>
+<?php
+                                        }
+                                        // TODO: Labyrinth difficulty
+                                        if (in_array($run->area->id, $SCENARIO)){
+?>
+                                            <span class='difficulty'>
+<?php
+                                                for ($i = 0; $i < $run->difficulty; $i ++){
+?>
+                                                    <img class='difficulty' src='<?=$path["img"]["layout"]?>icon/star.png'/>
+<?php
+                                                }
+?>
+                                            </span>
+<?php
+                                        }
+?>
+                                    </div>
+                                </td>
+                                <td class='date'>
+<?php
+                                    $date = date_create($run->dtime);
+?>
+                                    <?=date_format($date, "Y/m/d H:i:s")?>
+                                </td>
+                                <td class='team'>
+<?php
+                                    if ($run->helper == 1){
+?>
+                                        <div class='monster_panel helper'>
+                                            <img class='monster' title='Friend/Menstor' src='<?=$path["img"]["layout"]?>currency/socialpoint.png'/>
+                                        </div>
+<?php
+                                    }
+                                    foreach ($run->party as $party){
+                                        if ($party instanceof K_Unit){
+                                            $disabled = "disabled";
+                                        }
+                                        else{
+                                            $disabled = "";
+                                        }
+                                        if ($disabled == ""){
+?>
+                                            <a target='_blank' href='/monster/<?=$party->id?>'>
+<?php
+                                        }
+?>
+                                        <div class='monster_panel <?=$disabled?>'>
+                                            <?=html_unit_panel($party)?>
+                                        </div>
+<?php
+                                        if ($disabled == ""){
+?>
+                                            </a>
+<?php
+                                        }
+                                    }
+?>
+                                </td>
+                                <td class='time'>
+<?php
+                                    $s = floor($run->time / 1000);
+                                    $m = floor($s / 60);
+                                    $s = floor($s % 60);
+                                    $ms = ($run-> time - (1000 * 60 * $m) - (1000 * 60 * $s) / 100);
+                                    $s = str_pad($s, 2, '0', STR_PAD_LEFT);
+                                    $ms = str_pad($ms, 2, '0', STR_PAD_LEFT);
+?>
+                                    <?=$m?>:<?=$s?><span class='ms'>.<?=$ms?></span>
+                                </td>
+                                <td class='reward'>
+<?php
+                                    if ($run->energy > 0){
+?>
+                                        <div class='item'>
+                                            <img title='Energy' src='<?=$path["img"]["layout"]?>currency/energy.png'/>
+                                            <span><?=$run->energy?></span>
+                                        </div>
+<?php
+                                    }
+                                    if ($run->mana > 0){
+?>
+                                        <div class='item'>
+                                            <img title='Mana' src='<?=$path["img"]["layout"]?>currency/mana.png'/>
+                                            <span><?=$run->mana?></span>
+                                        </div>
+<?php
+                                    }
+                                    if ($run->crystal > 0){
+?>
+                                        <div class='item'>
+                                            <img title='Crystal' src='<?=$path["img"]["layout"]?>currency/crystal.png'/>
+                                            <span><?=$run->crystal?></span>
+                                        </div>
+<?php
+                                    }
+                                    foreach ($run->unit as $unit){
+?>
+                                        <div class='item'>
+                                            <img title='<?$unit->title?>' src='<?=$unit->get_image()?>'/>
+                                        </div>
+<?php
+                                    }
+                                    foreach ($run->item as $item){
+?>
+                                        <div class='item'>
+                                            <img title='<?$item->name?>' src='<?=$item->get_image()?>'/>
+                                        </div>
+<?php
+                                    }
+                                    foreach ($run->sd as $sd){
+?>
+                                        <div class='item'>
+                                            <img title='<?$sd->unit->title?>' src='<?=$sd->unit->get_image()?>'/>
+                                            <img title=' ' src='<?=$path["img"]["layout"]?>misc/mask-sd.png'/>
+                                            <span><?=$sq->quantity?></span>
+                                        </div>
+<?php
+                                    }
+                                    foreach ($run->unit_piece as $piece){
+?>
+                                        <div class='item'>
+                                            <img title='<?$piece->unit->title?>' src='<?=$piece->unit->get_image()?>'/>
+                                            <img title=' ' src='<?=$path["img"]["layout"]?>misc/mask-piece.png'/>
+                                            <span><?=$piece->quantity?></span>
+                                        </div>
+<?php
+                                    }
+                                    if ($run->shapeshifting > 0){
+?>
+                                        <div class='item'>
+                                            <img title='Shapeshifting Stones' src='<?=$path["img"]["layout"]?>currency/costumestone.png'/>
+                                            <span><?=$run->shapeshifting?></span>
+                                        </div>
+<?php
+                                    }
+                                    foreach ($run->rune as $rune){
+?>
+                                        <div class='item item_rune'>
+                                            <img title='Rune' src='src='<?=$path["img"]["layout"]?>rune/base.png'/>
+                                            <?=htm_rune_table($rune)?>
+                                        </div>
+<?php
+                                    }
+?>
+                                </td>
+                            </tr>
+<?php
+                        }
+?>
+                    </table>
+                </article>
+            </section>
+<?php
+        include __DIR__ . "/inc/footer.php";
+?>
+    </body>
+</html>

+ 55 - 0
data.sql

@@ -40,6 +40,61 @@ CREATE TABLE IF NOT EXISTS k_rune_craft_type(id INT, name TEXT, gem INT, inmemor
 
 CREATE TABLE IF NOT EXISTS k_rune_craft_range(gem INT, ancient INT, quality INT, stat INT, min INT, max INT);
 
+CREATE TABLE IF NOT EXISTS k_area_type(id, name TEXT, effect INT);
+
+CREATE TABLE IF NOT EXISTS k_area (id INT, type INT, name TEXT);
+
+INSERT INTO k_area_type VALUES (1, 'Scenario', null);
+INSERT INTO k_area_type VALUES (2, 'Cairos Dungeons', 2);
+INSERT INTO k_area_type VALUES (3, 'Rift Elemental Dungeon', 2);
+INSERT INTO k_area_type VALUES (4, 'Rift Raid', null);
+INSERT INTO k_area_type VALUES (5, 'Dimensional Hole', 2);
+INSERT INTO k_area_type VALUES (6, 'Arena', 3);
+INSERT INTO k_area_type VALUES (7, 'Guild War', 5);
+INSERT INTO k_area_type VALUES (8, 'Guild Siege', 5);
+INSERT INTO k_area_type VALUES (9, 'Tartarus Labyrinth', 5);
+INSERT INTO k_area_type VALUES (10, 'Trial of Ascension', null);
+INSERT INTO k_area_type VALUES (11, 'World Boss', null);
+INSERT INTO k_area_type VALUES (12, 'Dimensional Rift', null);
+
+INSERT INTO k_area VALUES (1, 1, 'Garen Forest');
+INSERT INTO k_area VALUES (2, 1, 'Mt. Siz');
+INSERT INTO k_area VALUES (3, 1, 'Kabir Ruins');
+INSERT INTO k_area VALUES (4, 1, 'Mt. White Ragon');
+INSERT INTO k_area VALUES (5, 1, 'Telain Forest');
+INSERT INTO k_area VALUES (6, 1, 'Hydeni Ruins');
+INSERT INTO k_area VALUES (7, 1, 'Tamor Desert');
+INSERT INTO k_area VALUES (8, 1, 'Vrofagus Ruins');
+INSERT INTO k_area VALUES (9, 1, 'Faimon Volcano');
+INSERT INTO k_area VALUES (10, 1, 'Aiden Forest');
+INSERT INTO k_area VALUES (11, 1, 'Ferun Castle');
+INSERT INTO k_area VALUES (12, 1, 'Mt Runar');
+INSERT INTO k_area VALUES (13, 1, 'Chiruka Remains');
+INSERT INTO k_area VALUES (1001, 2, 'Hall of Dark');
+INSERT INTO k_area VALUES (1101, 5, 'Sanctuary of Dreaming Fairies');
+INSERT INTO k_area VALUES (1201, 5, 'Ellunia Remains (Fairy)');
+INSERT INTO k_area VALUES (1202, 5, 'Ellunia Remains (Pixie)');
+INSERT INTO k_area VALUES (2001, 2, 'Hall of Fire');
+INSERT INTO k_area VALUES (2101, 5, 'Forest of Roaring Beasts');
+INSERT INTO k_area VALUES (2201, 5, 'Karzhan Remains (Warbear)');
+INSERT INTO k_area VALUES (2202, 5, 'Karzhan Remains (Inugami)');
+INSERT INTO k_area VALUES (3001, 2, 'Hall of Water');
+INSERT INTO k_area VALUES (4001, 2, 'Hall of Wind');
+INSERT INTO k_area VALUES (5001, 2, 'Hall of Magic');
+INSERT INTO k_area VALUES (6001, 2, 'Necropolis');
+INSERT INTO k_area VALUES (7001, 2, 'Hall of Light');
+INSERT INTO k_area VALUES (8001, 2, 'Giant''s Keep');
+INSERT INTO k_area VALUES (9001, 2, 'Dragon''s Lair');
+INSERT INTO k_area VALUES (1001, 3, 'Rift Dungeon - Ice Beast');
+INSERT INTO k_area VALUES (2001, 3, 'Rift Dungeon - Fire Beast');
+INSERT INTO k_area VALUES (3001, 3, 'Rift Dungeon - Wind Beast');
+INSERT INTO k_area VALUES (4001, 3, 'Rift Dungeon - Light Beast');
+INSERT INTO k_area VALUES (5001, 3, 'Rift Dungeon - Dark Beast');
+INSERT INTO k_area VALUES (1, 4, 'Rift of Worlds - level 1');
+INSERT INTO k_area VALUES (2, 4, 'Rift of Worlds - level 2');
+INSERT INTO k_area VALUES (3, 4, 'Rift of Worlds - level 3');
+INSERT INTO k_area VALUES (4, 4, 'Rift of Worlds - level 4');
+INSERT INTO k_area VALUES (5, 4, 'Rift of Worlds - level 5');
 
 INSERT INTO k_difficulty VALUES (0, 'Easy');
 INSERT INTO k_difficulty VALUES (1, 'Normal');

+ 2 - 7
install_base.py

@@ -397,23 +397,18 @@ def createDatabase(name):
                 stage INT,
                 difficulty INT,
                 win INT,
-                value INT,
                 time INT,
                 mana INT,
                 energy INT,
                 crystal INT,
-
-                sd INT,
-                unit_pieces INT,
-                unit_pieces_id INT,
-                unit_pieces_quantity INT
+                helper INT
             );
         ''')
         cursor.execute('''
             CREATE TABLE IF NOT EXISTS run_party(
                 run INT,
                 unit INT,
-                k_unit INT,
+                k_unit INT
             );
         ''')
         cursor.execute('''

+ 22 - 0
public/API/v1/log-run/index.php

@@ -0,0 +1,22 @@
+<?php
+    global $path;
+    error_log("API v1 log-run REQUEST");
+    foreach ($_POST as $param_name => $param_val) {
+        error_log("    Param: $param_name; Value: $param_val");
+    }
+    if (isset($_POST["data"])){
+        $data = $_POST["data"];
+        if (json_decode($data) === null){
+            echo ("ERROR: Invalid JSON");
+            error_log("API v1 log-run REQUEST INVALID JSON");
+        }
+        else{
+            error_log($_SERVER["DOCUMENT_ROOT"] . "/../application/bin/run-log.py " . escapeshellarg($data));
+            exec($_SERVER["DOCUMENT_ROOT"] . "/../application/bin/run-log.py " . escapeshellarg($data));
+        }
+    }
+    else{
+        echo ("ERROR: No POST data");
+        error_log("API v1 log-run REQUEST NO POST DATA");
+    }
+?>

+ 101 - 0
public/css/runs.css

@@ -0,0 +1,101 @@
+table#runs{
+    color: #ffffff;
+    border-collapse: collapse;
+    width: 100%;
+}
+
+table#runs td{
+    border-top: 0.1em solid #999999;
+}
+
+table#runs td.area div.area{
+    position: relative;
+    width: 2em;
+    height: 2em;
+}
+
+table#runs td.area div.area img.area{
+    position: absolute;
+    width: 2em;
+    height: 2em;
+    border: 0.2em solid #555555;
+    border-radius: 0.5em;
+    background-color: #777777;
+}
+
+table#runs td.area div.area span.stage{
+    position: absolute;
+    top: 1.3em;
+    left: 1em;
+    width: 2em;
+    text-align: right;
+    text-shadow: 0 0 0.3em #000, 0 0 0.3em #000, 0 0 0.3em #000, 0 0 0.2em #000, 0 0 0.1em #000, 0 0 0.1em #000;
+}
+
+table#runs td.area div.area span.difficulty{
+    position: absolute;
+    top: 0.3em;
+    left: 0.3em;
+}
+table#runs td.area div.area span.difficulty img.difficulty{
+    height: 0.7em;
+    width: 0.7em;
+    filter: sepia(100%) saturate(500%) hue-rotate(10deg) saturate(200%) drop-shadow(0 0 0.1em #000) drop-shadow(0 0 0.1em #000) drop-shadow(0 0 0.1em #000);
+}
+
+table#runs td.team{
+    font-size: 30%;
+}
+
+table#runs td.team div.helper{
+    vertical-align: top;
+    background-color: #777777;
+}
+
+table#runs td.team div.monster_panel.disabled{
+    opacity: 0.5;
+}
+
+table#runs td.time{
+    font-weight: bold;
+}
+
+table#runs td.time span.ms{
+    font-size: 70%;
+}
+
+table#runs td.reward div.item{
+    width: 1.6em;
+    height: 1.6em;
+    position: relative;
+    border: 0.2em solid #555555;
+    border-radius: 0.5em;
+    background-color: #777777;
+}
+
+table#runs td.reward div.item img{
+    width: 1.6em;
+    height: 1.6em;
+}
+
+table#runs td.reward div.item span{
+    position: absolute;
+    left: 0.6em;
+    top: 1.2em;
+    font-size: 75%;
+    width: 1.4em;
+    text-align: right;
+    text-shadow: 0 0 0.3em #000, 0 0 0.3em #000, 0 0 0.3em #000, 0 0 0.2em #000, 0 0 0.1em #000, 0 0 0.1em #000;
+}
+
+table#runs td.reward div.item_rune table.rune{
+    display: none;
+    position: absolute;
+    left: 1.8em;
+    top: 0;
+    font-size: 75%;
+}
+
+table#runs td.reward div.item_rune:hover table.rune{
+    display: block;
+}

+ 140 - 44
swex-plugin/swdb.js

@@ -11,9 +11,9 @@ module.exports = {
     },
     pluginName: 'SWDB',
     pluginDescription: 'Saves runs to SWDB.',
-    target_host: 'localhost',
+    target_host: 'http://localhost',
     target_port: '80',
-    target_url: 'upload/run',
+    target_url: '/API/v1/log-run',
     temp: {},
     proxy: null,
     config: null,
@@ -29,32 +29,35 @@ module.exports = {
         this.config = config;
         proxy.on('apiCommand', (req, resp) => {
             try {
+                wizardID = '';
+                command = '';
                 if (config.Config.Plugins[this.pluginName].enabled) {
-                    const { command, wizard_id: wizardID } = req;
-
+                    //const { command, wizard_id: wizardID } = req;
+                    command = req["command"];
+                    wizardID = req["wizard_id"];
+                }
                 if (!this.temp[wizardID]) {
                     this.temp[wizardID] = {};
                 }
-
                 if (command === 'BattleScenarioStart') {
                     proxy.log({ type: 'info', source: 'plugin', name: this.pluginName, message: `IVV RUN START: ${resp}` })
-                    for (var key in req) {
-                        proxy.log({ type: 'info', source: 'plugin', name: this.pluginName, message: `IVV REQ: ${key}: ${req[key]}` })
-                    }
+                    //for (var key in req) {
+                    //    proxy.log({ type: 'info', source: 'plugin', name: this.pluginName, message: `IVV REQ: ${key}: ${req[key]}` })
+                    //}
                     this.temp[wizardID].area = req.region_id;
                     this.temp[wizardID].stage = req.stage_no;
                     this.temp[wizardID].difficulty = req.difficulty;
                 }
 
                 if (command === 'BattleScenarioResult' || command === 'BattleDungeonResult' || command === 'BattleDimensionHoleDungeonResult') {
-                    proxy.log({ type: 'info', source: 'plugin', name: this.pluginName, message: `IVV RUN RESULT: ${resp}` })
+                    //proxy.log({ type: 'info', source: 'plugin', name: this.pluginName, message: `IVV RUN RESULT: ${resp}` })
                     //for (var key in req) {
                     //    this.log("info", `IVV REQ: ${key}: ${req[key]}`);
                     //}
                     //for (var key in resp) {
                     //    this.log("info", `IVV RES: ${key}: ${resp[key]}`);
                     //}
-                    this.parse(req, resp);
+                    this.parse_normal(req, resp);
                 }
 
                 if (command === 'BattleRiftOfWorldsRaidResult') {
@@ -112,7 +115,7 @@ module.exports = {
             item = crate.craft_stuff.item_master_id;
             quantity = crate.craft_stuff.item_quantity;
         }
-        //this.log('debug', `Item: ${item}, ${unit}, ${pieces}, ${quantity}, ${shapeshifting}`);
+        //this.log('DEBUG', `Item: ${item}, ${unit}, ${pieces}, ${quantity}, ${shapeshifting}`);
         drop = {item, unit, pieces, quantity, shapeshifting}
         return drop;
     },
@@ -195,10 +198,8 @@ module.exports = {
         const run_k_party = {};
         log = true;
 
-        run.uid = wizardID;
-
         if (command === 'BattleDungeonResult') {
-            if (eq.dungeon_id > 10000){
+            if (req.dungeon_id > 10000){
                 // Hall of Heroes or unknown
                 log = false;
             }
@@ -209,7 +210,7 @@ module.exports = {
 
         if (command === 'BattleScenarioResult') {
             for (var key in this.temp[wizardID]) {
-                this.log("debug", `IVV TEMP: ${key}: ${this.temp[wizardID][key]}`);
+                this.log("DEBUG", `IVV TEMP: ${key}: ${this.temp[wizardID][key]}`);
             }
             run.area = this.temp[wizardID].area;
             run.stage = this.temp[wizardID].stage;
@@ -224,10 +225,10 @@ module.exports = {
             run.stage = req.stage_id;
             run.difficulty = 0
         }
-
+        run.uid = wizardID;
         run.win = resp.win_lose; // 1/0 2 is lost in scenario
 
-        run.dtime = dateFormat(new Date(), 'yyyy-mm-dd HH:MM:SS');
+        run.dtime = dateFormat(new Date(), 'yyyy-mm-dd HH:MM:ss');
 
         const reward = resp.reward ? resp.reward : {};
         run.time = req.clear_time ? req.clear_time : 0;
@@ -235,6 +236,9 @@ module.exports = {
         run.mana = reward.mana ? reward.mana : 0;
         run.energy = reward.energy ? reward.energy : 0;
         run.crystal = reward.crystal ? reward.crystal : 0;
+        
+        // TODO: GET
+        run.helper = 0;
 
         run.rune = {};
         run.unit = 0;
@@ -250,9 +254,9 @@ module.exports = {
 
             if (reward.crate.rune) {
                 const rune = reward.crate.rune;
-                for (var key in rune) {
-                    this.proxy.log({ type: 'info', source: 'plugin', name: this.pluginName, message: `IVV RUNE: ${key}: ${rune[key]}` })
-                }
+                //for (var key in rune) {
+                //    this.proxy.log({ type: 'info', source: 'plugin', name: this.pluginName, message: `IVV RUNE: ${key}: ${rune[key]}` })
+                //}
                 run.rune.id = rune.rune_id;
                 run.rune.grade = rune.class;
                 run.rune.value = rune.sell_value;
@@ -260,7 +264,6 @@ module.exports = {
                 run.rune.slot = rune.slot_no;
                 run.rune.efficiency = gMapping.getRuneEfficiency(rune).current;
                 run.rune.quality = rune.rank;
-                // TODO: Maybe i need to do substrings to get stat and value
                 run.rune.main = rune.pri_eff[0];
                 run.rune.main_value = rune.pri_eff[1];
                 if (rune.prefix_eff){
@@ -300,7 +303,7 @@ module.exports = {
         }
 
         json_rune = '[';
-        if (run.rune.length() > 0){
+        if (run.rune.length > 0){
             json_rune += `"id": "${run.rune.id}", `;
             json_rune += `"grade": "${run.rune.grade}", `;
             json_rune += `"set": "${run.rune.set}", `;
@@ -319,23 +322,23 @@ module.exports = {
             json_rune += `"substat_3": "${run.rune.substat_3}", `;
             json_rune += `"substat_4_value": "${run.rune.substat_3_value}", `;
             json_rune += `"substat_4": "${run.rune.substat_4}", `;
-            json_rune += `"substat_4_value": "${run.rune.substat_4_value}", `;
+            json_rune += `"substat_4_value": "${run.rune.substat_4_value}"`;
         }
-        json_rune = json_party.slice(0, -1) + "]";
+        json_rune = json_rune + "]";
 
         json_item = '[';
-        if (run.item.length() > 0){
+        if (run.item.length > 0){
             json_item += `"id": "${run.item.id}", `;
-            json_item += `"quantity": "${run.item.quantity}", `;
+            json_item += `"quantity": "${run.item.quantity}"`;
         }
-        json_item = json_party.slice(0, -1) + "]";
+        json_item = json_item + "]";
 
         json_unit_pieces = '[';
-        if (run.unit_pieces.length() > 0){
+        if (run.unit_pieces.length > 0){
             json_unit_pieces += `"id": "${run.unit_pieces.id}", `;
-            json_unit_pieces += `"quantity": "${run.unit_pieces.quantity}", `;
+            json_unit_pieces += `"quantity": "${run.unit_pieces.quantity}" `;
         }
-        json_unit_pieces = json_party.slice(0, -1) + "]";
+        json_unit_pieces = json_unit_pieces + "]";
 
         json_party = '[';
         resp.unit_list.forEach((unit, i) => {
@@ -343,11 +346,12 @@ module.exports = {
         });
         json_party = json_party.slice(0, -1) + "]";
 
-        //this.log('debug', `IVV json_party: ${json_party}`)
+        //this.log('DEBUG', `IVV json_party: ${json_party}`)
 
         json = `{`;
+        json += `"uid": "${run.uid}", `;
         json += `"dtime": "${run.dtime}", `;
-        json += `"area": "${run.dungeon}", `;
+        json += `"area": "${run.area}", `;
         json += `"stage": "${run.stage}", `;
         json += `"difficulty": "${run.difficulty}", `;
         json += `"win": "${run.win}", `;
@@ -360,13 +364,105 @@ module.exports = {
         json += `"item": ${json_item}, `;
         json += `"unit": ${run.unit}, `;
         json += `"unit_pieces": ${json_unit_pieces}, `;
-        json += `"sapeshifting": ${run.sapeshifting}, `;
-        json += `"party": ${json_party}`;
+        json += `"shapeshifting": ${run.shapeshifting}, `;
+        json += `"party": ${json_party}`, ;
+        json += `"helper": "${run.helper}", `;
         json += `}`;
-        this.log('DEBUG', `json: ${json}` })
+        this.log('DEBUG', `json: ${json}`)
         //const filename = sanitize(`${wizardName}-${wizardID}-runs.csv`);
         //const filename = sanitize(`IVV-${wizardName}-${wizardID}-runs.csv`);
         //this.saveToFile(entry, filename, headers, proxy);
+        
+        // Make the request
+        //var inspect = require('eyespect').inspector();
+        var request = require('request')
+
+        var postData = {
+            name: 'data',
+            value: json
+        }
+
+        // npm install -S request eyespect
+        /*var url = this.target_host + ':' + this.target_port + '/' + this.target_url;
+        var options = {
+            method: 'post',
+            body: postData,
+            json: false, //json: true,
+            url: url
+        }
+        request(options, function (err, res, body) {
+            if (err) {
+                this.log('ERROR', `REQUEST ERROR ${err}, `)
+                //return
+            }
+            //var headers = res.headers
+            //var statusCode = res.statusCode
+            //inspect(headers, 'headers')
+            //inspect(statusCode, 'statusCode')
+            //inspect(body, 'body')
+        })*/
+        
+        /*const req_options = setRequestAuth(
+        {
+            json: true,
+            body: { data: { request: req, response: resp, __version: acceptedCommands.__version } }
+        },
+            wizard_id
+        );*/
+
+        // Send it
+        /*var url = this.target_host + ':' + this.target_port + '/' + this.target_url;
+        request.post(url, postData, (error, response, body) => {
+            if (error) {
+                this.log('error',`Error: ${error.message}`);
+                //return;
+            }
+
+            // Log message to proxy window
+            if (response.statusCode === 200) {
+                this.log('success',`${command} logged successfully`);
+            }
+            else {
+                if (response.statusCode == 401) {
+                    this.log('error',`SWARFARM Authentication failure: ${body.detail}`);
+                } else {
+                    this.log('error',`Error ${response.statusCode}: ${body.detail}`);
+                }
+            }
+        });*/
+        
+        var querystring = require('querystring');
+        var http = require('http');
+        var fs = require('fs');
+        var post_data = querystring.stringify({
+            'data' : json
+        });
+
+        // An object of options to indicate where to post to
+        var post_options = {
+            host: '192.168.1.101',//this.target_host,
+            port: this.target_port,
+            path: this.target_url,
+            method: 'POST',
+            headers: {
+                'Content-Type': 'application/x-www-form-urlencoded',
+                'Content-Length': Buffer.byteLength(post_data)
+            }
+        };
+
+        // Set up the request
+        var post_req = http.request(post_options, function(res) {
+            res.setEncoding('utf8');
+            res.on('data', function (chunk) {
+                this.log("DEBUG", 'Response: ' + chunk);
+            });
+        });
+
+        // post the data
+        post_req.write(post_data);
+        post_req.end();
+        
+        
     },
 
     /**
@@ -388,7 +484,7 @@ module.exports = {
 
         //const winLost = resp.win_lose === 1 ? 'Win' : 'Did not kill';
 
-        run.dtime = dateFormat(new Date(), 'yyyy-mm-dd HH:MM:SS');
+        run.dtime = dateFormat(new Date(), 'yyyy-mm-dd HH:MM:ss');
         //run.result = winLost;
 
         run.win  = resp.win_lose;
@@ -422,7 +518,7 @@ module.exports = {
                     },
                     sell_value: reward.crate.changestones[0].sell_value
                 };
-                run.drop= this.getItemRift(item, run.;
+                run.drop= this.getItemRift(item, run);
             }
             // Rune
             else if (reward.crate.rune) {
@@ -475,7 +571,7 @@ module.exports = {
                 if (resp.reward.crate.material) {
                     item = resp.reward.crate.material.item_master_id;
                     quantity = resp.reward.crate.material.item_quantity;
-                //}
+                }
                 //if (resp.reward.crate.summon_pieces) {
                 //    pieces = resp.reward.crate.summon_pieces.item_master_id;
                 //    quantity = resp.reward.crate.summon_pieces.item_quantity;
@@ -594,7 +690,7 @@ module.exports = {
         });
         json_party = json_party.slice(0, -1) + "]";
 
-        //this.log('debug', `IVV json_party: ${json_party}`)
+        //this.log('DEBUG', `IVV json_party: ${json_party}`)
 
         json = `{`;
         json += `"dtime": "${run.dtime}", `;
@@ -614,7 +710,7 @@ module.exports = {
         json += `"sapeshifting": ${run.sapeshifting}, `;
         json += `"party": ${json_party}`;
         json += `}`;
-        this.log('DEBUG', `json: ${json}` })
+        this.log('DEBUG', `json: ${json}`)
     },
 
     /**
@@ -637,7 +733,7 @@ module.exports = {
             map.drop = 'Enchanted Gem';
         }
         return map;
-    }
+    },
 
     /**
      * Prints a messageto the SWEX output.
@@ -646,8 +742,8 @@ module.exports = {
      * @param msg The message to log.
      */
     log(type, msg){
-        if (type != "debug" || this.config.Config.Plugins[this.pluginName].debug == true){
-            proxy.log({ type: type, source: 'plugin', name: this.pluginName, message: msg });
-        }
+        //if (type != "debug" || this.config.Config.Plugins[this.pluginName].debug == true){
+            this.proxy.log({ type: type, source: 'plugin', name: this.pluginName, message: msg });
+        //}
     }
 };