* @license https://www.gnu.org/licenses/gpl-3.0.en.html GNU General Public License V3 * @package SWDB */ require_once(PATH::ENTITY . "Entity.php"); require_once(PATH::ENTITY . "Monster.php"); require_once(PATH::ENTITY . "Team.php"); require_once(PATH::ENTITY . "Rune.php"); require_once(PATH::ENTITY . "Artifact.php"); require_once(PATH::ENTITY . "Building.php"); /** * A Unit. * * Represents an unit owned by the player. * * @category Entity */ class Unit extends Entity{ /** * @var int Owner's player ID. */ private $player; /** * @var int Unit ID. */ private $id; /** * @var int ID of the building the unit is in, if any. */ private $building; /** * @var Monster Base monster */ private $monster; /** * @var int Unit's current stars. */ private $stars; /** * @var int Unit's current stars. */ private $level; /** * @var int Total gained experience. */ private $experience; /** * @var int Gainex experience in the curent level. */ private $exp_gained; /** * @var int Experience gained by the hour, if in an EXP building. */ private $exp_gain_rate; /** * @var bool Internal flag to check if current level experience parameters have been loaded. */ private $flag_experience = false; /** * @var int Total experience for the current level. */ private $experience_level = 0; /** * @var int Experience remaining until next level. */ private $experience_level_up = 0; /** * @var bool Internal flag to inidicate if it has been checked that the unit is in the storage * building. */ private $flag_storage = false; /** * @var bool Indicates if the unit is in storage. */ private $in_storage = false; /** * @var string Unit's title. */ private $title; /** * @var int Transmogification ID. */ private $costume; /** * @var bool Internal flag to check if the source has been loaded into the entity. */ private $flag_source = false; /** * @var int Unit source ID. */ private $source_id; /** * @var Source Unit source. */ private $source; /** * @var DateTime Unit creation time. */ private $create_time; /** * @var string Name for Homunculus units. */ private $homunculus_name; /** * @var bool Indicates if the unit is locked. */ private $lock; /** * @var int[] The unit's base stats. */ private $base_stats = array( "hp" => 0, "atk" => 0, "def" => 0, "spd" => 0, "crr" => 0, "crd" => 0, "acc" => 0, "res" => 0, "ehp" => 0, "dmg" => 0, ); /** * @var int[] The unit's stat bonus gained from runes. */ private $rune_stats = array( "hp" => 0, "atk" => 0, "def" => 0, "spd" => 0, "crr" => 0, "crd" => 0, "acc" => 0, "res" => 0, "ehp" => 0, "dmg" => 0, ); /** * @var int[] The unit's stat bonus gained from artifacts. */ private $artifact_stats = array( "hp" => 0, "atk" => 0, "def" => 0, "spd" => 0, "crr" => 0, "crd" => 0, "acc" => 0, "res" => 0, "ehp" => 0, "dmg" => 0, ); /** * @var bool Internal flag to check if building stats have been loaded. */ private $flag_buildings = false; /** * @var int[] The unit's stat bonus gained from buildings. */ private $building_stats = array( "hp" => 0, "atk" => 0, "def" => 0, "spd" => 0, "crr" => 0, "crd" => 0, "acc" => 0, "res" => 0, "ehp" => 0, "dmg" => 0, ); /** * @var bool Internal flag to check if all the stats have been loaded. */ private $flag_stats = false; /** * @var int[] The unit's total stats. */ private $stats = array( "hp" => 0, "atk" => 0, "def" => 0, "spd" => 0, "crr" => 0, "crd" => 0, "acc" => 0, "res" => 0, "ehp" => 0, "dmg" => 0, ); /** * @var bool Internal flag to check if runes and artifacts have been loaded. */ private $flag_runes = false; /** * @var float Average rune eficiency. */ private $avg_rune_efficiency = 0.0; /** * @var Rune[] Equipped runes. */ private $rune = []; /** * @var Artifact Equiped archetype artifact. */ private $artifact_type; /** * @var Artifact Equipped elemental artifact. */ private $artifact_element; /** * @var bool Internla flag to check if the teams have been loaded into the entity. */ private $flag_teams = false; /** * @var Team [] The teams the unit is in. */ private $teams = []; /** * @var bool Internal flag to check if the skill levels have been loded. */ private $flag_skills = false; /** * @var int[] Skill levels. */ private $skill_levels = []; /** * Constructor. * * Searches the database and retrieves the information about the * unit, populating it and it's items. * * @param int $id Unit identifier. */ public function __construct($id){ $player = get_context()->get_player()->get_id(); $statement = get_context()->get_db()->prepare(" SELECT player, id, building, monster, stars, level, hp, attack, defense, speed, crit_rate, crit_damage, resistance, accuracy, experience, exp_gained, exp_gain_rate, costume, source, create_time, homunculus_name, lock FROM unit WHERE id = :id; "); $statement->bindValue(':id', $id, SQLITE3_TEXT); $statement->bindValue(':player', $player, SQLITE3_TEXT); $r = $statement->execute()->fetchArray(SQLITE3_ASSOC); if ($r){ $this->player = $r["player"]; $this->id = $r["id"]; if ($r["building"] != null && $r["building"] > 0){ $this->building = $r["building"]; } if (strlen($r["monster"]) > 0){ $this->monster = new Monster($r["monster"]); } $this->stars = $r["stars"]; $this->level = $r["level"]; $this->base_stats["hp"] = $r["hp"]; $this->base_stats["atk"] = $r["attack"]; $this->base_stats["def"] = $r["defense"]; $this->base_stats["spd"] = $r["speed"]; $this->base_stats["crr"] = $r["crit_rate"]; $this->base_stats["crd"] = $r["crit_damage"]; $this->base_stats["res"] = $r["resistance"]; $this->base_stats["acc"] = $r["accuracy"]; $this->base_stats["ehp"] = APPLICATION::calculate_ehp($r["hp"], $r["defense"]); $this->base_stats["dmg"] = APPLICATION::calculate_dmg($r["attack"], $r["crit_rate"], $r["crit_damage"]); $this->experience = $r["experience"]; $this->exp_gained = $r["exp_gained"]; $this->exp_gain_rate = $r["exp_gain_rate"]; $this->costume = $r["costume"]; if ($r["source"]){ $this->source_id = $r["source"]; } $this->create_time = new Datetime(); $this->create_time->setTimestamp($r["create_time"]); $this->homunculus_name = $r["homunculus_name"]; if ($this->monster->is_homunculus()){ $this->title = $this->homunculus_name; } else{ $this->title = $this->monster->get_title(); } $this->lock = $r["lock"]; } } /** * Loads the unit skills into the entity. * * Must be called only once before trying to get the skills. Sets the flag * {@link Unit:$flag_skills} to true. */ private function load_skills(){ $statement = get_context()->get_db()->prepare(" SELECT level FROM unit_skill, skill WHERE skill.id = unit_skill.skill AND unit_skill.unit = :unit ORDER BY slot; "); $statement->bindValue(':unit', $this->id, SQLITE3_TEXT); $q = $statement->execute(); while ($r = $q->fetchArray(SQLITE3_ASSOC)){ array_push($this->skill_levels, $r["level"]); } $this->flag_skills = true; } /** * Loads the unit's current level experience parameters into the entity. * * Must be called only once before trying to get experience parameters. Sets the flag * {@link Unit:$flag_experience} to true. */ private function load_experience(){ $statement = get_context()->get_db()->prepare(" SELECT exp FROM experience WHERE stars = :stars AND level = :level; "); $statement->bindValue(':stars', $this->stars, SQLITE3_TEXT); $statement->bindValue(':level', $this->level, SQLITE3_TEXT); $r = $statement->execute()->fetchArray(SQLITE3_ASSOC); if ($r){ $this->experience_level = $r["exp"]; $this->experience_level_up = $r["exp"] - $this->exp_gained; } $this->flag_experience = true; } /** * Loads the unit storage status into the entity. * * Must be called only once before trying to get the skills. Sets the flag * {@link Unit:$flag_storage} to true. */ private function load_storage(){ $statement = get_context()->get_db()->prepare(" SELECT 1 FROM unit, building WHERE unit.id = :id AND unit.player = :player AND building.player = :player AND unit.building = building.id AND building.buildable = :buildable; "); $statement->bindValue(':id', $this->id, SQLITE3_TEXT); $statement->bindValue(':player', $this->player, SQLITE3_TEXT); $statement->bindValue(':buildable', BUILDING_ID::MONSTER_STORAGE, SQLITE3_TEXT); $r = $statement->execute()->fetchArray(SQLITE3_ASSOC); if ($r){ $this->in_storage = true; } else{ $this->in_storage = false; } $this->flag_storage = true; } /** * Loads the building stats into the entity. * * Must be called only once before trying to get the building or total stats. Sets the flag * {@link Unit:$flag_buildings} to true. */ private function load_buildings(){ $statement = get_context()->get_db()->prepare(" SELECT affected_stat, bonus, element FROM decorative, decorative_level, decoration WHERE decoration.player = :player AND decorative.id = decoration.decorative AND decoration.decorative = decorative_level.decorative AND decoration.level = decorative_level.level AND area = :area AND affected_stat IN (:stat_atk, :stat_def, :stat_hp, :stat_spd, :stat_crd); "); $statement->bindValue(':player', $this->player, SQLITE3_TEXT); $statement->bindValue(':area', EFFECT_AREA_ID::GENERAL, SQLITE3_INTEGER); $statement->bindValue(':stat_atk', STAT_ID::ATK, SQLITE3_INTEGER); $statement->bindValue(':stat_def', STAT_ID::DEF, SQLITE3_INTEGER); $statement->bindValue(':stat_hp', STAT_ID::HP, SQLITE3_INTEGER); $statement->bindValue(':stat_spd', STAT_ID::SPD, SQLITE3_INTEGER); $statement->bindValue(':stat_crd', STAT_ID::CRIT_DMG, SQLITE3_INTEGER); $q = $statement->execute(); while ($r = $q->fetchArray(SQLITE3_ASSOC)){ switch($r["affected_stat"]){ case STAT_ID::DEF: $this->building_stats["def"] += ($this->base_stats["def"]* $r["bonus"] / 100); break; case STAT_ID::HP: $this->building_stats["hp"] += ($this->base_stats["hp"]* $r["bonus"] / 100); break; case STAT_ID::SPD: $this->building_stats["spd"] += ($this->base_stats["spd"]* $r["bonus"] / 100); break; case STAT_ID::CRIT_DMG: $this->building_stats["crd"] += $r["bonus"]; break; case STAT_ID::ATK: if(strlen($r["element"]) > 0 && $r["element"] == $this->monster->get_element()){ $this->building_stats["atk"] += ($this->base_stats["atk"]* $r["bonus"] / 100); } else{ $this->building_stats["atk"] += ($this->base_stats["atk"]* $r["bonus"] / 100); } break; } } $this->building_stats["hp"] = ceil($this->building_stats["hp"]); $this->building_stats["atk"] = ceil($this->building_stats["atk"]); $this->building_stats["def"] = ceil($this->building_stats["def"]); $this->building_stats["spd"] = ceil($this->building_stats["spd"]); $this->building_stats["ehp"] = APPLICATION::calculate_ehp($this->building_stats["hp"], $this->building_stats["def"]); $this->building_stats["dmg"] = APPLICATION::calculate_dmg($this->building_stats["atk"], $this->building_stats["crr"], $this->building_stats["crd"]); $this->flag_buildings = true; } /** * Loads the unit source into the entity. * * Must be called only once before trying to get the source. Sets the flag * {@link Unit:$flag_source} to true. */ private function load_source(){ if ($this->source_id != null){ $this->source = new Source($this->source_id); } $this->flag_source = true; } /** * Loads the unit runes and artifacts into the entity. * * Must be called only once before trying to the runes or artifact or their stats. Sets the * flag {@link Unit:$flag_skills} to true. */ private function load_runes(){ // Runes $statement = get_context()->get_db()->prepare(" SELECT rune FROM unit_rune WHERE unit = :unit AND rta = :rta; "); $statement->bindValue(':unit', $this->id, SQLITE3_TEXT); $statement->bindValue(':rta', get_context()->get_game_mode(), SQLITE3_TEXT); $q = $statement->execute(); while ($r = $q->fetchArray(SQLITE3_ASSOC)){ array_push($this->rune, new Rune($r["rune"])); } foreach ($this->rune as $rune){ foreach ($rune->get_stats() as $stat){ $this->sum_rune_stat($stat->get_stat(), $stat->get_value() + $stat->get_grind()); } } $this->rune_stats["atk"] = ceil($this->rune_stats["atk"]); $this->rune_stats["def"] = ceil($this->rune_stats["def"]); $this->rune_stats["hp"] = ceil($this->rune_stats["hp"]); $this->rune_stats["ehp"] = APPLICATION::calculate_ehp($this->rune_stats["hp"], $this->rune_stats["def"]); $this->rune_stats["dmg"] = APPLICATION::calculate_dmg($this->rune_stats["atk"], $this->rune_stats["crr"], $this->rune_stats["crd"]); // Artifacts $this->artifact = []; $statement = get_context()->get_db()->prepare(" SELECT artifact FROM unit_artifact WHERE unit = :unit AND rta = :rta; "); $statement->bindValue(':unit', $this->id, SQLITE3_TEXT); $statement->bindValue(':rta', get_context()->get_game_mode(), SQLITE3_TEXT); $q = $statement->execute(); while ($r = $q->fetchArray(SQLITE3_ASSOC)){ $artifact = new Artifact($r["artifact"]); if ($artifact->is_archetypal()){ $this->artifact_type = $artifact; } else{ $this->artifact_element = $artifact; } switch($artifact->get_stat()->get_stat()){ case ARTIFACT_STAT_ID::ATK: $this->artifact_stats["atk"] += $artifact->get_stat()->get_value(); break; case ARTIFACT_STAT_ID::DEF: $this->artifact_stats["def"] += $artifact->get_stat()->get_value(); break; case ARTIFACT_STAT_ID::HP: $this->artifact_stats["hp"] += $artifact->get_stat()->get_value(); break; } } $this->artifact_stats["ehp"] = APPLICATION::calculate_ehp($this->artifact_stats["hp"], $this->artifact_stats["def"]); $this->artifact_stats["dmg"] = APPLICATION::calculate_dmg($this->artifact_stats["atk"], $this->artifact_stats["crr"], $this->artifact_stats["crd"]); $this->flag_runes = true; } /** * Calculates the unit's total stats. * * Must be called only once before trying to get the stats. Sets the flag * {@link Unit:$flag_stats} to true. */ private function load_stats(){ if (! $this->flag_buildings){ $this->load_buildings(); } if (! $this->flag_runes){ $this->load_runes(); } $keys = ["atk", "def", "hp", "spd", "crr", "crd", "res", "acc", "ehp", "dmg"]; foreach ($keys as $key){ $this->stats[$key] = $this->base_stats[$key] + $this->rune_stats[$key] + $this->artifact_stats[$key] + $this->building_stats[$key]; } $this->flag_stats = true; } /** * Loads the unit teams into the entity. * * Must be called only once before trying to get the teams. Sets the flag * {@link Unit:$flag_teams} to true. */ private function load_teams(){ $statement = get_context()->get_db()->prepare(" SELECT DISTINCT team.id AS id FROM team, team_unit WHERE team.player = :player AND team.id = team_unit.team AND team_unit.unit = :unit; "); $statement->bindValue(':player', $this->player, SQLITE3_TEXT); $statement->bindValue(':unit', $this->id, SQLITE3_TEXT); $q = $statement->execute(); while ($r = $q->fetchArray(SQLITE3_ASSOC)){ array_push($this->teams, new Team($r["id"], false)); } $this->flag_teams = true; } /** * Calculates the stat increase by a rune property. * * @param string $stat Stat type. * @param int $value Stat value. * @param int $value Stat increase value. */ private function sum_rune_stat($stat, $value){ switch ($stat){ case RUNE_STAT_ID::ATK: $this->rune_stats["atk"] += $value; break; case RUNE_STAT_ID::DEF: $this->rune_stats["def"] += $value; break; case RUNE_STAT_ID::HP: $this->rune_stats["hp"] += $value; break; case RUNE_STAT_ID::SPD: $this->rune_stats["spd"] += $value; break; case RUNE_STAT_ID::CRR: $this->rune_stats["crr"] += $value; break; case RUNE_STAT_ID::CRD: $this->rune_stats["crd"] += $value; break; case RUNE_STAT_ID::ACC: $this->rune_stats["acc"] += $value; break; case RUNE_STAT_ID::RES: $this->rune_stats["res"] += $value; break; case RUNE_STAT_ID::ATK_P: $this->rune_stats["atk"] += ($this->base_stats["atk"] * $value / 100); break; case RUNE_STAT_ID::DEF_P: $this->rune_stats["def"] += ($this->base_stats["def"] * $value / 100); break; case RUNE_STAT_ID::HP_P: $this->rune_stats["hp"] += ($this->base_stats["hp"] * $value / 100); break; } } /** * Checks if the unit skills are completly maxed. * * @return bool True if all skills maxed, false otherwise. */ public function is_fully_skilled(){ if (! $this->flag_skills){ $this->load_skills(); } $maxed = true; $skills = $this->monster->get_skills(); for ($i = 0; $i < sizeof($skills); $i ++){ if ($skills[$i]->get_max_level() > $this->skill_levels[$i]){ $maxed = false; break; } } return $maxed; } /** * Retrieves the owner's player identifier. * * @return int Player ID. */ public function get_player(){ return $this->player; } /** * Retrieves the unit identifier. * * @return int Unit ID. */ public function get_id(){ return $this->id; } /** * Retrieves the identifier of the building the unit is in, if any. * * @return int Building ID, null if unit not in building. */ public function get_building_id(){ return $this->building; } /** * Retrieves the base monster. * * @return Monster The base monster. */ public function get_monster(){ return $this->monster; } /** * Retrieves the unit stars (grade). * * @return int Unit stars (1-6). */ public function get_stars(){ return $this->stars; } /** * Retrieves the unit current level. * * @return int Unit level. */ public function get_level(){ return $this->level; } /** * Retrieves the unit's base stats. * * It's an associative array with the following keys: * atk, def, hp, spd, crr, crd, acc, res, ehp, dmg * * @return int[] Unit stats. */ public function get_base_stats(){ return $this->base_stats; } /** * Retrieves the unit's stats provided by runes. * * It's an associative array with the following keys: * atk, def, hp, spd, crr, crd, acc, res, ehp, dmg * * @return int[] Rune stats. */ public function get_rune_stats(){ if (! $this->flag_runes){ $this->load_runes(); } return $this->rune_stats; } /** * Retrieves the unit's stats provided by artifacts. * * It's an associative array with the following keys: * atk, def, hp, spd, crr, crd, acc, res, ehp, dmg * Note that only "atk", "def", "hp", "dmg" and "ehp" can me non-zero. * * @return int[] Artifact stats. */ public function get_artifact_stats(){ if (! $this->flag_runes){ $this->load_runes(); } return $this->artifact_stats; } /** * Retrieves the unit's stat bonus provided by buildings. * * Only building bonuses are considered. * It's an associative array with the following keys: * atk, def, hp, spd, crr, crd, acc, res, ehp, dmg * * @return int[] Building stats. */ public function get_building_stats(){ if (! $this->flag_buildings){ $this->load_buildings(); } return $this->building_stats; } /** * Retrieves the unit's stats. * * It's an associative array with the following keys: * atk, def, hp, spd, crr, crd, acc, res, ehp, dmg * * Includes the unit base stats, the runes and artifact increases and the building bonuses. * * @return int[] Unit stats. */ public function get_stats(){ if (! $this->flag_stats){ $this->load_stats(); } return $this->stats; } /** * Retrieves the total experience earned by the unit. * * @return int Total experience. */ public function get_experience(){ return $this->experience; } /** * Retrieves the total experience earned by the unit since it's level is the current one.. * * @return int Current level experience. */ public function get_current_level_experience(){ return $this->exp_gained; } /** * Retrieves the experience being gained by the hour from a building. * * @return int Experience by the hour, 0 if not in an exp building. */ public function get_exp_gain_rate(){ return $this->exp_gain_rate; } /** * Retrieves the current transmogrification identifier. * * @return int Transmogification ID, null if none. */ public function get_costume(){ return $this->costume; } /** * Retrieves the $source the unit was obtained from. * * @return Source Unit source. */ public function get_source(){ if (! $this->flag_source){ $this->load_source(); } return $this->source; } /** * Gets the date the unit was obtained. * * If $format is supplied, a string in the desired format will be returned. If not, the raw * DateTime object will e returned. * * @param string $format A date format (optional). * @return DateTime | string The raw date, or a formatted version. */ public function get_creation_time($format = null){ if (null == $format){ return $this->create_time; } else{ return $this->create_time->format($format); } } /** * Retrieves the name if the unit is an homunculus. * * @return string Homunculus name, null if not Homunculus. */ public function get_homunculus_name(){ return $this->homunculus_name; } /** * Checks if the unit is locked. * * @return bool True if locked, false if unlocked. */ public function is_locked(){ return $this->lock; } /** * Retrieves the average rune efficiency. * * @return int Average efficiency of the runes. */ public function get_avg_rune_efficiency(){ if (! $this->flag_runes){ $this->load_runes(); } return $this->avg_rune_efficiency; } /** * Retrieves the currently equipped runes. * * @return Rune[] Equipped runes. */ public function get_runes(){ if (! $this->flag_runes){ $this->load_runes(); } return $this->rune; } /** * Retrieves equipped artifacts. * * Can be read with the indexes ARTIFACT_TYPE:ELEMENT and ARTIFACT_TYPE:ARCHETYPE. * * @see ARTIFACT_TYPE * @return Artifact[] Equiped artifact array. Null positions if not equipped. */ public function get_artifacts(){ if (! $this->flag_runes){ $this->load_runes(); } return [$this->artifact_element, $this->artifact_type]; } /** * Retrieves the teams the unit is in. * * @return Team[] Teams the unit is in. */ public function get_teams(){ if (! $this->flag_teams){ $this->load_teams(); } return $this->teams; } /** * Retrieves the skill levels. * * @return int[] Skill levels, ordered by skill slot. */ public function get_skill_levels(){ if (! $this->flag_skills){ $this->load_skills(); } return $this->skill_levels; } /** * Retrieves the total experience required for the current level. * * @return int Level experience. */ public function get_experience_level(){ if (! $this->flag_experience){ $this->load_experience(); } return $this->experience_level; } /** * Retrieves the experience required to level-up. * * @return int Experience to level-up. */ public function get_experience_level_up(){ if (! $this->flag_experience){ $this->load_experience(); } return $this->experience_level_up; } /** * Checks if the unit is in storage. * * @return bool True if in storage, false otherwise. */ public function is_in_storage(){ if (! $this->flag_storage){ $this->load_storage(); } return $this->in_storage; } /** * Retrieves the unit's title. * * If Homunculus,it will be it's name. * If awakened, it will be the awakened from name. * If unawakened, it will be . * * @return string The unit's title. */ public function get_title(){ return $this->title; } }