$var = Settings::get($var); } $this->pkmn_img_width += $this->pkmn_img_padding; $this->pkmn_img_height += $this->pkmn_img_padding; } /** * Get icon stack of all icons besides Pokémon sprites. * * @return mixed[] Etc icon stack data. */ public function get_etc_icon_stack() { if (empty($this->etc_stack)) { $this->create_etc_icon_stack(); } return $this->etc_stack; } /** * Get all icons. * * @return mixed[] Icon data. */ public function get_all_icons() { return array_merge($this->pkmn_stack, $this->etc_stack); } /** * Get icon set info. * * @return mixed[] Etc icon set data. */ public function get_etc_icon_sets() { if (empty($this->etc_sets)) { $this->create_etc_icon_stack(); } return $this->etc_sets; } /** * Creates a special type of data structure specially suited * for the SCSS styler, JS generator and overview generator. */ public function create_icon_type_tree() { $icons = $this->get_all_icons(); $pkmn_sect_size = $this->get_pkmn_icon_stack_size(); $tree = array(); foreach ($icons as $icon) { $subvariation = $icon['subvariation']; $subvariation = isset($subvariation) ? $subvariation : '.'; $type = $icon['type']; $idx = $icon['idx']; $set = @$icon['set']; $original = @$icon['original']; $slug = $icon['slug']; $variation = $icon['variation']; $version = $icon['version']; $is_standard = $variation == '.'; // All icons other than Pokémon sprites need to be offset // with the height of the Pokémon sprite section. $x = $icon['fit']['x']; $y = $icon['fit']['y']; if ($type != 'pkmn') { $y += $pkmn_sect_size['h']; } $icon_info = array( 'std' => $is_standard, 'w' => $icon['w'], 'h' => $icon['h'], 'x' => $x + $this->pkmn_img_padding, 'y' => $y + $this->pkmn_img_padding, 'type' => $type, 'original' => $original, 'set' => $set, 'idx' => $idx, 'id' => $icon['id'], ); if ($type == 'pkmn') { $tree['pkmn'][$slug][$variation][$version][$subvariation] = $icon_info; } else { $tree['etc'][$set][$slug] = $icon_info; } } $this->type_tree = $tree; } /** * Returns the type tree data structure for our icons. * * @return mixed[] Pokémon icon type tree data. */ public function get_icon_type_tree() { if (empty($this->type_tree)) { $this->create_icon_type_tree(); } return $this->type_tree; } /** * Determines and returns the sizes of the Pokémon icon sets. * * @return mixed[] Pokémon icon set sizes. */ public function get_set_sizes() { if (!empty($this->set_sizes)) { return $this->set_sizes; } $sizes = array(); // The Pokémon icon sizes are always static. $sizes['pkmn'] = array( 'w' => $this->pkmn_img_width - $this->pkmn_img_padding, 'h' => $this->pkmn_img_height - $this->pkmn_img_padding, ); // Define the sizes for other items. if (empty($this->type_tree['etc'])) { $this->type_tree['etc'] = array(); } foreach ($this->type_tree['etc'] as $slug => $icons) { $w = 0; $h = 0; foreach ($icons as $icon) { // Define the size if we're at the first iteration. // Continue if it's the same. // If a different size is found than the one that // was defined before, this section will be determined // to have variable image sizes. $same = ($w === $icon['w'] && $h === $icon['h']); $zero = ($w === 0 && $h === 0); if ($same) { continue; } if ($zero) { $w = $icon['w']; $h = $icon['h']; } else { // Images have variable sizes. Save an empty array only. $sizes[$slug] = array(); continue(2); } } $sizes[$slug] = array( 'w' => $w, 'h' => $h ); } $this->set_sizes = $sizes; return $this->set_sizes; } /** * Creates an icon stack of the etc sprites. */ private function create_etc_icon_stack() { // This stack will contain icons from all other icon sets. $stack = array(); // Save an array of sets. $sets = array(); $etc_icon_sets = Settings::get('etc_icon_sets'); $dir_base = Settings::get('dir_base'); $file_exts = Settings::get('file_exts'); // Start off where the Pokémon stack ended. $n = count($this->pkmn_stack); if (Settings::get('include_icon_sets')) { foreach ($etc_icon_sets as $set) { $dir = $dir_base.$set.'/'; try { $dir_it = new \DirectoryIterator($dir); } catch (Exception $e) { print(I18n::lf('icon_dir_failure', array($dir))); continue; } foreach ($dir_it as $file) { // Some checks to ensure it's a valid image. if ($file->isDot()) { continue; } if ($file->isDir()) { continue; } $fn = $file->getFilename(); $fn_bits = explode('.', $fn); $fn_ext = strtolower(trim(end($fn_bits))); if (!in_array($fn_ext, $file_exts)) { continue; } $size = getimagesize($dir.$fn); $fn_slug = slugify(implode('.', array_slice($fn_bits, 0, -1))); $var = $this->get_icon_var_name($set, $fn_slug); $n += 1; $stack[$n] = array( 'type' => 'etc', 'var' => $var, 'set' => $set, 'section' => 'other', 'id' => $n, 'slug' => $fn_slug, 'w' => $size[0], 'h' => $size[1], 'file' => $dir.$fn, ); $sets[$set][] = $n; } } } // Sort icons by size. uasort($stack, array($this, 'etc_max_w_h_sort')); // Deep convert $stack to a StdClass rather than a regular array. // This is in order to be able to use it with the GrowingPacker, which // only accepts a StdClass as input. $stack = json_decode(json_encode($stack), false); // Make sure to pass the width of the Pokémon section as // the initial width. If we're not including Pokémon icons, // set it to a static value. $pkmn_sect_size = $this->get_pkmn_icon_stack_size(); $initial_width = @$pkmn_sect_size['w']; if (!$initial_width) { $initial_width = $this->pkmn_img_width * $this->pkmn_row_count; } // Initialize our packing algorithm and feed the icons. // Permit horizontal growth only if we're not including Pokémon icons. $packer = new GrowingPacker(); $packer->fit($stack, $initial_width, null, false, true); // Convert back to array. $stack = json_decode(json_encode($stack), true); // Remove extraneous data from the stack. foreach ($stack as $n => $icon) { unset($stack[$n]['fit']['right']); unset($stack[$n]['fit']['down']); unset($stack[$n]['fit']['used']); } $this->etc_stack = $stack; $this->etc_sets = $sets; $this->etc_sect_width = intval($packer->root->w); $this->etc_sect_height = intval($packer->root->h); } /** * Sort by size (descending), then id (ascending). * * This is used to prepare data for use with the GrowingPacker class, * which works best when the input data is sorted by max(width, height). * In cases of identical size, we sort by the id number to keep things of * the same size in their originally intended order. * * @param mixed $a Left comparison. * @param mixed $b Right comparison. */ private function etc_max_w_h_sort($a, $b) { // Take either the width or the height, whichever is the largest. $a_size = max($a['w'], $a['h']); $b_size = max($b['w'], $b['h']); // If the items have the same size, if ($a_size == $b_size) { // Check their series number instead. $a_id = $a['id']; $b_id = $b['id']; if ($a_id == $b_id) { return 0; } // Ascending return ($a_id < $b_id) ? -1 : 1; } // Descending return ($a_size > $b_size) ? -1 : 1; } /** * Sorts the icons so that the standard variation is always at the top. * * This is necessary to properly link duplicates to the original icon. * Must be used with uksort(). * * @param mixed $a Left comparison. * @param mixed $b Right comparison. */ private function pkmn_variation_sort($a, $b) { // '.' must always go above the rest. $val = $a == '.' ? -1 : ($b == '.' ? 1 : 0); if ($val == 0) { // In all other cases, use alphabetic order. return ($a < $b) ? -1 : 1; } else { return $val; } } /** * Parse Pokémon data file. * * @param string $file Filename. * @param string $range Range variable (undocumented; debugging only). */ public function parse_data_file($file, $range=null) { $this->pkmn_data = $this->slice_icons(json_decode(file_get_contents($file), true), $range); } /** * Parse icons data file. * * @param string $file Filename. * @param string $range Range variable (undocumented; debugging only). */ private function slice_icons($data, $range) { if (isset($range)) { $range = str_replace(',', '-', $range); $range = explode('-', $range); $range[0] = $range[0] === '0' ? 1 : $range[0]; if ($range[1] <= $range[0]) { $range[1] = $range[0]; } $range = array( intval($range[0]) - 1, intval($range[1]) - intval($range[0]) + 1 ); return array_slice( $data, $range[0], $range[1] ); } return $data; } /** * Parse icons data file. * * @param string $file Filename. * @param string $range Range variable (undocumented; debugging only). */ public function parse_icons_data_file($file, $range=null) { $this->icon_data = $this->slice_icons(json_decode(file_get_contents($file), true), $range); } /** * Checks whether Pokémon data is loaded and non-empty. */ public function has_pkmn_data() { return !empty($this->pkmn_data); } /** * Checks whether icon data is loaded and non-empty. */ public function has_icon_data() { return !empty($this->icon_data); } /** * Checks whether Pokémon images can be found. */ public function has_pkmn_images() { // Check to see if the images are there. $test_img = @reset($this->pkmn_data); $final_img = ( Settings::get('dir_base'). Settings::get('dir_pkmn'). Settings::get('dir_pkmn_regular'). $test_img['slug'][Settings::get('img_slug_lang')]. '.png' ); return is_file($final_img); } /** * Checks whether icon images can be found. */ public function has_icon_images() { // Check to see if the images are there. $test_group = 'medicine'; $test_item = @reset($this->icon_data['icons'][$test_group]); $test_img = ( Settings::get('dir_base'). $test_group.'/'. $test_item['icon']['filename'] ); return is_file($test_img); } /** * Returns parsed Pokémon data. * * @return mixed[] Parsed Pokémon data. */ public function get_pkmn_data() { return $this->pkmn_data; } /** * Returns parsed icon data. * * @return mixed[] Parsed icon data. */ public function get_icon_data() { return $this->icon_data; } /** * Returns x/y coordinates for the next Pokémon icon. * * @param int $inc Whether to increment the counter. * @return mixed[] Coordinates. */ private function get_pkmn_icon_fit($inc=true) { $width = $this->pkmn_img_width; $height = $this->pkmn_img_height; $row_count = $this->pkmn_row_count; if ($inc != false) { $this->counter += 1; } $x = ($this->counter % $row_count) * $width; $y = floor($this->counter / $row_count) * $height; $fit = array( 'fit' => array( 'x' => $x, 'y' => $y, ), ); return $fit; } /** * Returns the total size of the Pokémon icon stack. * * @return int[] Pokémon icon stack size. */ public function get_pkmn_icon_stack_size() { if (!empty($this->pkmn_sect_width)) { return array( 'w' => $this->pkmn_sect_width, 'h' => $this->pkmn_sect_height, ); } $fit = $this->get_pkmn_icon_fit(false); if ($this->counter > $this->pkmn_row_count) { $width = ($this->pkmn_img_width * $this->pkmn_row_count); } else { $width = $fit['fit']['x'] + $this->pkmn_img_width; } // See if the width is at least the size of 32 Pokémon icons. // Note that padding has already been added to pkmn_img_width. $min_width = (($this->pkmn_img_width) * $this->pkmn_row_count) + $this->pkmn_img_padding; if ($width < $min_width) { $width = $min_width; } $height = $fit['fit']['y'] + $this->pkmn_img_height; $this->pkmn_sect_width = intval($width); $this->pkmn_sect_height = intval($height); return array( 'w' => $this->pkmn_sect_width, 'h' => $this->pkmn_sect_height, ); } /** * Returns the total size of the etc icon stack. * * @return int[] Etc icon stack size. */ public function get_etc_icon_stack_size() { return array( 'w' => $this->etc_sect_width + $this->img_padding, 'h' => $this->etc_sect_height + $this->img_padding, ); } /** * Returns the total size of the Pokémon stack plus the etc icon * stack below it. * * @return int[] Full icon stack size. */ public function get_combined_stack_size() { $include_pkmn = Settings::get('include_pkmn'); $include_icon_sets = Settings::get('include_icon_sets'); $pkmn_size = $this->get_pkmn_icon_stack_size(); $etc_size = $this->get_etc_icon_stack_size(); $width = $include_pkmn ? $pkmn_size['w'] : $etc_size['w']; $height = $include_pkmn ? $pkmn_size['h'] : 0; $height += $include_icon_sets ? $etc_size['h'] : 0; return array( 'w' => $width + $this->pkmn_img_padding, 'h' => $height + $this->pkmn_img_padding, ); } /** * Creates an icon stack of the Pokémon sprites. * * This stack can then be fed to a number of other systems, * including the sprite image and SCSS/JS generators. */ public function create_pkmn_icon_stack() { // Loop through the available Pokémon and adding each of their // variants and forms to a stack. // Initialize the sprite stack. $this->pkmn_stack = array(); // List of sprite variants. $this->sprites_variants = array(); // Include regular Pokémon, and shiny Pokémon if set. $this->versions = array(); if (Settings::get('include_pkmn_nonshiny')) { $this->versions[] = 'regular'; } if (Settings::get('include_pkmn_shiny')) { $this->versions[] = 'shiny'; } $this->counter = -1; // If we're skipping Pokémon sprite icons, $pkmn_data will be empty. foreach ($this->pkmn_data as $id => $pkmn) { $stack_items = $this->get_pkmn_stack_items($id, $pkmn); foreach ($stack_items as $item) { $this->pkmn_stack[] = $item; } } // Add the special items (e.g. egg, unknown). if (Settings::get('include_special_icons')) { $special_items = $this->get_special_stack_items(); $this->pkmn_stack = array_merge($this->pkmn_stack, $special_items); } } /** * Returns the special items for the Pokémon stack. * Currently, just "egg", "egg-manaphy" and "unknown". * These have no shiny icons. */ public function get_special_stack_items() { // Unhatched egg. $egg = array( 'idx' => null, 'slug' => array( 'eng' => 'egg', 'jpn' => 'tamago', ), 'icons' => array( '.' => array(), ), 'name' => array( 'eng' => 'Egg', 'jpn' => 'タマゴ', 'jpn_ro' => 'Tamago', ), ); // Manaphy egg. $egg_manaphy = array( 'idx' => null, 'slug' => array( 'eng' => 'egg-manaphy', 'jpn' => 'tamago-manaphy', ), 'icons' => array( '.' => array(), ), 'name' => array( 'eng' => 'Manaphy Egg', 'jpn' => 'マナフィのタマゴ', 'jpn_ro' => 'Manaphy Tamago', ), ); // Unknown (not Unown) or glitch Pokémon. $unknown = array( 'idx' => null, 'slug' => array( 'eng' => 'unknown', 'jpn' => 'fumei', ), 'icons' => array( '.' => array(), ), 'name' => array( 'eng' => 'Unknown', 'jpn' => 'ふめい', 'jpn_ro' => 'Fumei', ), ); return array_merge( $this->get_pkmn_stack_items('egg', $egg, true), $this->get_pkmn_stack_items('egg-manaphy', $egg_manaphy, true), $this->get_pkmn_stack_items('unknown', $unknown, true) ); } /** * Returns the icon stack (and generates it if it doesn't exist). * * @return mixed[] Pokémon icon stack data. */ public function get_pkmn_icon_stack() { if (empty($this->pkmn_stack)) { $this->create_pkmn_icon_stack(); } return $this->pkmn_stack; } /** * Returns the standard icons that need to be referred to * in the case of duplicates. * * @return mixed[] Pokémon icon stack data (only of standard icons). */ public function get_pkmn_std_icons() { if (empty($this->pkmn_stack)) { $this->create_pkmn_icon_stack(); } return $this->sprites_variants; } /** * Produces and returns a variable name for an icon * based on its attributes. * * @param string $type Type identifier. * @param string $slug Slug name. * @param string $variation Variation. * @param string $subvariation Subvariation. * @param string $version Version. * @return string Variable name. */ private function get_icon_var_name($type, $slug, $variation=null, $subvariation=null, $version=null) { $items = array(); $items[] = $type; if ($type == 'pkmn') { $items[] = $slug[Settings::get('pkmn_language_slugs')]; if ($variation != '.') { $items[] = $variation; } if ($subvariation != '.') { $items[] = $subvariation; } if ($version != 'regular') { $items[] = $version; } } else { $items[] = $slug; } return implode('-', $items); } /** * Returns stack items for a specific Pokémon. * * @return mixed[] Pokémon icon stack data. */ private function get_pkmn_stack_items($id, $pkmn, $special=false) { // Base info that's the same for each stack item. $base_info = array( 'id' => $id, 'idx' => $pkmn['idx'], 'type' => 'pkmn', 'w' => $this->pkmn_img_width, 'h' => $this->pkmn_img_height, 'slug' => $pkmn['slug'][Settings::get('pkmn_language_slugs')], 'slug_img' => $pkmn['slug'][Settings::get('img_slug_lang')], 'section' => 'pkmn', 'name_display' => $pkmn['name'][Settings::get('pkmn_language')], ); // Retrieve some variables from the settings. $vars = array( 'dir_base', 'dir_pkmn', 'dir_pkmn_female', 'dir_pkmn_right', 'include_pkmn_forms', 'include_pkmn_right', ); foreach ($vars as $var) { $$var = Settings::get($var); } // Keep this Pokémon's stack items and return them at the end. $tmp_stack = array(); // Sort the variations to ensure the standard variation // comes first. The data file should already be pre-sorted, // but we're making sure. uksort($pkmn['icons'], array($this, 'pkmn_variation_sort')); foreach ($pkmn['icons'] as $icon => $icon_data) { // Zygarde has '10' and '50' formes. Make sure the icon name is a string. $icon = (string)$icon; // If this is a special icon (no variations, no shiny version), // add only the plain icon and continue. // This should be only 'egg', 'egg-manaphy' and 'unknown'. if ($special) { $var = $this->get_icon_var_name( 'pkmn', $pkmn['slug'], $icon, '.', 'regular' ); $pkmn_info = array_merge($base_info, array( 'version' => 'regular', 'subvariation' => null, 'is_duplicate_from' => false, ), $this->get_pkmn_icon_fit(), array( 'variation' => $icon, 'var' => $var, 'file' => ( $dir_base. $dir_pkmn. Settings::get('dir_pkmn_special'). $pkmn['slug'][Settings::get('img_slug_lang')]. '.png' ), ) ); $tmp_stack[] = $pkmn_info; continue; } // Loop through each icon twice: once for regular versions, and // (if requested), once more for shiny versions. // Every variation is added to the stack. foreach ($this->versions as $version) { // Refer to either the regular or shiny icon directory. $version_dir = Settings::get('dir_pkmn_'.$version); $is_duplicate = !empty($icon_data['is_duplicate_from']); $info = array( 'version' => $version, 'subvariation' => null, 'is_duplicate_from' => $icon_data['is_duplicate_from'], ); // If this is a duplicate, keep a reference // to the standard icon. if ($icon_data['is_duplicate_from']) { $info['original'] = @$this->sprites_variants[$id][$version][$icon_data['is_duplicate_from']]; } $is_standard = $icon == '.'; // Don't include non-standard forms // if we've got that turned off. if ($include_pkmn_forms != true && !$is_standard) { continue; } // The standard version is indicated by a single period. // All other variants are present as slug-variant.png. $variation = $is_standard ? '' : '-'.$icon; // Produce a variable name. $var = $this->get_icon_var_name( 'pkmn', $pkmn['slug'], $icon, '.', $version ); // Add to the stack $pkmn_info = array_merge($base_info, $info, $this->get_pkmn_icon_fit(!$is_duplicate), array( 'variation' => $icon, 'var' => $var, 'file' => ( $dir_base. $dir_pkmn. $version_dir. $pkmn['slug'][Settings::get('img_slug_lang')]. $variation.'.png' ), ) ); $tmp_stack[] = $pkmn_info; // Save this icon in the variant list. // We need to re-use it for variations that // don't have their own icon. $this->sprites_variants[$id][$version][$icon] = $pkmn_info; // Female variant if ($include_pkmn_forms && $icon_data['has_female']) { $var = $this->get_icon_var_name( 'pkmn', $pkmn['slug'], $icon, 'female', $version ); $tmp_stack[] = array_merge($base_info, $info, $this->get_pkmn_icon_fit(!$is_duplicate), array( 'variation' => $icon, 'var' => $var, 'subvariation' => 'female', 'file' => ( $dir_base. $dir_pkmn. $version_dir. $dir_pkmn_female. $pkmn['slug'][Settings::get('img_slug_lang')]. '.png' ), ) ); } // Right-facing variant if ($include_pkmn_forms && $icon_data['has_right'] && $include_pkmn_right > 0) { $var = $this->get_icon_var_name( 'pkmn', $pkmn['slug'], $icon, 'right', $version ); $tmp_stack[] = array_merge($base_info, $info, $this->get_pkmn_icon_fit(!$is_duplicate), array( 'variation' => $icon, 'var' => $var, 'subvariation' => 'right', 'file' => ( $dir_base. $dir_pkmn. $version_dir. $dir_pkmn_right. $pkmn['slug'][Settings::get('img_slug_lang')]. $variation.'.png' ), ) ); } if ($include_pkmn_forms && !$icon_data['has_right'] && $include_pkmn_right == 2) { // If there's no right-facing variant, but we // want all icons to have both left and right versions, // we'll have to flip the regular image. $var = $this->get_icon_var_name( 'pkmn', $pkmn['slug'], $icon, 'flipped', $version ); $tmp_stack[] = array_merge($base_info, $info, $this->get_pkmn_icon_fit(!$is_duplicate), array( 'variation' => $icon, 'var' => $var, 'subvariation' => 'flipped', 'file' => ( $dir_base. $dir_pkmn. $version_dir. $pkmn['slug'][Settings::get('img_slug_lang')]. $variation.'.png' ), ) ); } } } return $tmp_stack; } }