Files
aowow/setup/tools/filegen/complexImg.func.php
Sarjuuk 0cb5d6b896 CLISetup
* added msg-level INFO
  * changed some WARN-level messages to INFO
Util
  * added function to handle directories aowow wants to write to / read from
SiteConfig
  * group options to be less cluttered
  * allow empty strings (numerical values must still at least be 0)
  * renamed account related config values to be make more sense
  * make cache path configurable
  * make session save path configurable - use this to avoid the garbage collect cron job on Debian or Ubuntu, that cleans sessions and only depends on your php.ini (NOTE: putting this inside a web-enabled directory is a risk!)
2015-07-09 21:04:00 +02:00

720 lines
29 KiB
PHP

<?php
/*
generate maps - code for extracting regular maps for AoWoW
This file is a part of AoWoW project.
Copyright (C) 2010 Mix <ru-mangos.ru>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
if (!defined('AOWOW_REVISION'))
die('illegal access');
if (!CLI)
die('not in cli mode');
// note: for the sake of simplicity, this function handles all images, that must be stitched together (which are mostly maps)
$reqDBC = ['talenttab', 'chrclasses', 'worldmapoverlay', 'worldmaparea'];
function complexImg()
{
if (isset(FileGen::$cliOpts['help']))
{
echo "\n";
echo "available options for subScript 'complexImg':\n"; // modeMask
echo "--talentbgs (backgrounds for talent calculator)\n"; // 0x01
echo "--maps (generates worldmaps)\n"; // 0x02
echo "--spawn-maps (creates alphaMasks of each zone to check spawns against)\n"; // 0x04
echo "--artwork (optional: imagery from /glues/credits (not used, skipped by default))\n"; // 0x08
echo "--area-maps (optional: renders maps with highlighted subZones for each area)\n"; // 0x10
return true;
}
$mapWidth = 1002;
$mapHeight = 668;
$threshold = 95; // alpha threshold to define subZones: set it too low and you have unspawnable areas inside a zone; set it too high and the border regions overlap
$runTime = ini_get('max_execution_time');
$locStr = null;
$dbcPath = CLISetup::$srcDir.'%sDBFilesClient/';
$imgPath = CLISetup::$srcDir.'%sInterface/';
$destDir = 'static/images/wow/';
$success = true;
$paths = ['WorldMap/', 'TalentFrame/', 'Glues/Credits/'];
$modeMask = 0x7; // talentBGs, regular maps, spawn-related alphaMaps
$createAlphaImage = function($w, $h)
{
$img = imagecreatetruecolor($w, $h);
imagesavealpha($img, true);
imagealphablending($img, false);
$bgColor = imagecolorallocatealpha($img, 0, 0, 0, 127);
imagefilledrectangle($img, 0, 0, imagesx($img) - 1, imagesy($img) - 1, $bgColor);
imagecolortransparent($img, $bgColor);
imagealphablending($img, true);
imagecolordeallocate($img, $bgColor);
return $img;
};
// prefer manually converted PNG files (as the imagecreatefromblp-script has issues with some formats)
// alpha channel issues observed with locale deDE Hilsbrad and Elwynn - maps
// see: https://github.com/Kanma/BLPConverter
$loadImageFile = function($path)
{
$result = null;
$file = $path.'.png';
if (CLISetup::fileExists($file))
{
CLISetup::log('manually converted png file present for '.$path.'.', CLISetup::LOG_INFO);
$result = imagecreatefrompng($file);
}
if (!$result)
{
$file = $path.'.blp';
if (CLISetup::fileExists($file))
$result = imagecreatefromblp($file);
}
return $result;
};
$assembleImage = function($baseName, $order, $w, $h) use ($loadImageFile)
{
$dest = imagecreatetruecolor($w, $h);
imagesavealpha($dest, true);
imagealphablending($dest, false);
$_h = $h;
foreach ($order as $y => $row)
{
$_w = $w;
foreach ($row as $x => $suffix)
{
$src = $loadImageFile($baseName.$suffix);
if (!$src)
{
CLISetup::log(' - complexImg: tile '.$baseName.$suffix.'.blp missing.', CLISetup::LOG_ERROR);
unset($dest);
return null;
}
imagecopyresampled($dest, $src, 256 * $x, 256 * $y, 0, 0, min($_w, 256), min($_h, 256), min($_w, 256), min($_h, 256));
$_w -= 256;
unset($src);
}
$_h -= 256;
}
return $dest;
};
$writeImage = function($name, $ext, $src, $w, $h, $done)
{
$ok = false;
$dest = imagecreatetruecolor($w, $h);
imagesavealpha($dest, true);
imagealphablending($dest, false);
imagecopyresampled($dest, $src, 0, 0, 0, 0, $w, $h, imagesx($src), imagesy($src));
switch ($ext)
{
case 'jpg':
$ok = imagejpeg($dest, $name.'.'.$ext, 85);
break;
case 'png':
$ok = imagepng($dest, $name.'.'.$ext);
break;
default:
CLISetup::log($done.' - unsupported file fromat: '.$ext, CLISetup::LOG_WARN);
}
imagedestroy($dest);
if ($ok)
{
chmod($name.'.'.$ext, Util::FILE_ACCESS);
CLISetup::log($done.' - image '.$name.'.'.$ext.' written', CLISetup::LOG_OK);
}
else
CLISetup::log($done.' - could not create image '.$name.'.'.$ext, CLISetup::LOG_ERROR);
return $ok;
};
$createSpawnMap = function($img, $zoneId) use ($mapHeight, $mapWidth, $threshold)
{
CLISetup::log(' - creating spawn map');
$tmp = imagecreate(1000, 1000);
$cbg = imagecolorallocate($tmp, 255, 255, 255);
$cfg = imagecolorallocate($tmp, 0, 0, 0);
for ($y = 0; $y < 1000; $y++)
{
for ($x = 0; $x < 1000; $x++)
{
$a = imagecolorat($img, ($x * $mapWidth) / 1000, ($y * $mapHeight) / 1000) >> 24;
imagesetpixel($tmp, $x, $y, $a < $threshold ? $cfg : $cbg);
}
}
imagepng($tmp, 'setup/generated/alphaMaps/' . $zoneId . '.png');
imagecolordeallocate($tmp, $cbg);
imagecolordeallocate($tmp, $cfg);
imagedestroy($tmp);
};
$checkSourceDirs = function($sub, &$missing = []) use ($imgPath, $dbcPath, $paths, &$modeMask)
{
$hasMissing = false;
foreach ($paths as $idx => $subDir)
{
if ($idx == 0 && !($modeMask & 0x16)) // map related
continue;
else if ($idx == 1 && !($modeMask & 0x1)) // talentBGs
continue;
else if ($idx == 2 && !($modeMask & 0x8)) // artwork
continue;
$p = sprintf($imgPath, $sub).$subDir;
if (!CLISetup::fileExists($p))
{
$hasMissing = true;
$missing[] = $p;
}
}
if ($modeMask & 0x17)
{
$p = sprintf($dbcPath, $sub);
if (!CLISetup::fileExists($p))
{
$hasMissing = true;
$missing[] = $p;
}
}
return !$hasMissing;
};
// do not change order of params!
if ($_ = FileGen::hasOpt('talentbgs', 'maps', 'spawn-maps', 'artwork', 'area-maps'))
$modeMask = $_;
foreach (CLISetup::$expectedPaths as $xp => $__)
{
if ($xp) // if in subDir add trailing slash
$xp .= '/';
if ($checkSourceDirs($xp, $missing))
{
$locStr = $xp;
break;
}
}
// if no subdir had sufficient data, diaf
if ($locStr === null)
{
CLISetup::log('one or more required directories are missing:', CLISetup::LOG_ERROR);
foreach ($missing as $m)
CLISetup::log(' - '.$m, CLISetup::LOG_ERROR);
return;
}
/**************/
/* TalentTabs */
/**************/
if ($modeMask & 0x01)
{
if (CLISetup::writeDir($destDir.'hunterpettalents/') && CLISetup::writeDir($destDir.'talents/backgrounds/'))
{
// [classMask, creatureFamilyMask, tabNr, textureStr]
$tTabs = DB::Aowow()->select('SELECT tt.creatureFamilyMask, tt.textureFile, tt.tabNumber, cc.fileString FROM dbc_talenttab tt LEFT JOIN dbc_chrclasses cc ON cc.Id = (LOG(2, tt.classMask) + 1)');
$order = array(
['-TopLeft', '-TopRight'],
['-BottomLeft', '-BottomRight']
);
if ($tTabs)
{
$sum = 0;
$total = count($tTabs);
CLISetup::log('Processing '.$total.' files from TalentFrame/ ...');
foreach ($tTabs as $tt)
{
ini_set('max_execution_time', 30); // max 30sec per image (loading takes the most time)
$sum++;
$done = ' - '.str_pad($sum.'/'.$total, 8).str_pad('('.number_format($sum * 100 / $total, 2).'%)', 9);
if ($tt['creatureFamilyMask']) // is PetCalc
{
$size = [244, 364];
$name = $destDir.'hunterpettalents/bg_'.(log($tt['creatureFamilyMask'], 2) + 1);
}
else
{
$size = [204, 554];
$name = $destDir.'talents/backgrounds/'.strtolower($tt['fileString']).'_'.($tt['tabNumber'] + 1);
}
if (!isset(FileGen::$cliOpts['force']) && file_exists($name.'.jpg'))
{
CLISetup::log($done.' - file '.$name.'.jpg was already processed');
continue;
}
$im = $assembleImage(sprintf($imgPath, $locStr).'TalentFrame/'.$tt['textureFile'], $order, 256 + 44, 256 + 75);
if (!$im)
{
CLISetup::log(' - could not assemble file '.$tt['textureFile'], CLISetup::LOG_ERROR);
continue;
}
if (!$writeImage($name, 'jpg', $im, $size[0], $size[1], $done))
$success = false;
}
}
else
$success = false;
ini_set('max_execution_time', $runTime);
}
else
$success = false;
}
/************/
/* Worldmap */
/************/
if ($modeMask & 0x16)
{
$mapDirs = array(
['maps/%snormal/', 'jpg', 488, 325],
['maps/%soriginal/', 'jpg', 0, 0], // 1002, 668
['maps/%ssmall/', 'jpg', 224, 163],
['maps/%szoom/', 'jpg', 772, 515]
);
// as the js expects them
$baseLevelFix = array(
// WotLK maps
// Halls of Stone; The Nexus; Violet Hold; Gundrak; Obsidian Sanctum; Eye of Eternity; Vault of Archavon; Trial of the Champion; The Forge of Souls; Pit of Saron; Halls of Reflection
4264 => 1, 4265 => 1, 4415 => 1, 4416 => 1, 4493 => 0, 4500 => 1, 4603 => 1, 4723 => 1, 4809 => 1, 4813 => 1, 4820 => 1,
// Cata maps for WotLK instances
// TheStockade; TheBloodFurnace; Ragefire; TheUnderbog; TheBotanica; WailingCaverns; TheSlavePens; TheShatteredHalls; HellfireRamparts; RazorfenDowns; RazorfenKraul; ManaTombs
// ShadowLabyrinth; TheTempleOfAtalHakkar (simplified layout); BlackTemple; TempestKeep; MoltenCore; GruulsLair; CoilfangReservoir; MagtheridonsLair; OnyxiasLair; SunwellPlateau;
717 => 1, 3713 => 1, 2437 => 1, 3716 => 1, 3847 => 1, 718 => 1, 3717 => 1, 3714 => 1, 3562 => 1, 722 => 1, 491 => 1, 3792 => 1,
3789 => 1, 1477 => 1, 3959 => 0, 3845 => 1, 2717 => 1, 3923 => 1, 3607 => 1, 3836 => 1, 2159 => 1, 4075 => 0
);
$wmo = DB::Aowow()->select('SELECT *, worldMapAreaId AS ARRAY_KEY, Id AS ARRAY_KEY2 FROM dbc_worldmapoverlay WHERE textureString <> ""');
$wma = DB::Aowow()->select('SELECT * FROM dbc_worldmaparea');
if (!$wma || !$wmo)
{
$success = false;
CLISetup::log(' - could not read required dbc files: WorldMapArea.dbc ['.count($wma).' entries]; WorldMapOverlay.dbc ['.count($wmo).' entries]', CLISetup::LOG_ERROR);
return;
}
// fixups...
foreach ($wma as &$a)
{
if ($a['areaId'])
continue;
switch ($a['Id'])
{
case 13: $a['areaId'] = -6; break; // Kalimdor
case 14: $a['areaId'] = -3; break; // Eastern Kingdoms
case 466: $a['areaId'] = -2; break; // Outland
case 485: $a['areaId'] = -5; break; // Northrend
}
}
array_unshift($wma, ['Id' => -1, 'areaId' => -1, 'nameINT' => 'World'], ['Id' => -4, 'areaId' => -4, 'nameINT' => 'Cosmic']);
$sumMaps = count(CLISetup::$localeIds) * count($wma);
CLISetup::log('Processing '.$sumMaps.' files from WorldMap/ ...');
foreach (CLISetup::$localeIds as $progressLoc => $l)
{
// create destination directories
$dirError = false;
foreach ($mapDirs as $md)
if (!CLISetup::writeDir($destDir . sprintf($md[0], strtolower(Util::$localeStrings[$l]).'/')))
$dirError = true;
if ($modeMask & 0x04)
if (!CLISetup::writeDir('setup/generated/alphaMaps'))
$dirError = true;
if ($dirError)
{
$success = false;
CLISetup::log(' - complexImg: could not create map directories for locale '.$l.'. skipping...', CLISetup::LOG_ERROR);
continue;
}
// source for mapFiles
$mapSrcDir = null;
$locDirs = array_filter(CLISetup::$expectedPaths, function($var) use ($l) { return !$var || $var == $l; });
foreach ($locDirs as $mapLoc => $__)
{
if ($mapLoc) // and trailing slash again
$mapLoc .= '/';
$p = sprintf($imgPath, $mapLoc).$paths[0];
if (CLISetup::fileExists($p))
{
CLISetup::log(' - using files from '.($mapLoc ?: '/').' for locale '.Util::$localeStrings[$l], CLISetup::LOG_INFO);
$mapSrcDir = $p.'/';
break;
}
}
if ($mapSrcDir === null)
{
$success = false;
CLISetup::log(' - no suitable localized map files found for locale '.$l, CLISetup::LOG_ERROR);
continue;
}
foreach ($wma as $progressArea => $areaEntry)
{
$curMap = $progressArea + count($wma) * $progressLoc;
$progress = ' - ' . str_pad($curMap.'/'.($sumMaps), 10) . str_pad('('.number_format($curMap * 100 / $sumMaps, 2).'%)', 9);
$wmaId = $areaEntry['Id'];
$zoneId = $areaEntry['areaId'];
$textureStr = $areaEntry['nameINT'];
$path = $mapSrcDir.$textureStr;
if (!CLISetup::fileExists($path))
{
$success = false;
CLISetup::log('worldmap file '.$path.' missing for selected locale '.Util::$localeStrings[$l], CLISetup::LOG_ERROR);
continue;
}
$fmt = array(
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
);
CLISetup::log($textureStr . " [" . $zoneId . "]");
$overlay = $createAlphaImage($mapWidth, $mapHeight);
// zone has overlays (is in open world; is not multiLeveled)
if (isset($wmo[$wmaId]))
{
CLISetup::log(' - area has '.count($wmo[$wmaId]).' overlays');
foreach ($wmo[$wmaId] as &$row)
{
$i = 1;
$y = 0;
while ($y < $row['h'])
{
$x = 0;
while ($x < $row['w'])
{
$img = $loadImageFile($path . '/' . $row['textureString'] . $i);
if (!$img)
{
CLISetup::log(' - complexImg: tile '.$path.'/'.$row['textureString'].$i.'.blp missing.', CLISetup::LOG_ERROR);
break 2;
}
imagecopy($overlay, $img, $row['x'] + $x, $row['y'] + $y, 0, 0, imagesx($img), imagesy($img));
// prepare subzone image
if ($modeMask & 0x10)
{
if (!isset($row['maskimage']))
{
$row['maskimage'] = $createAlphaImage($row['w'], $row['h']);
$row['maskcolor'] = imagecolorallocatealpha($row['maskimage'], 255, 64, 192, 64);
}
for ($my = 0; $my < imagesy($img); $my++)
for ($mx = 0; $mx < imagesx($img); $mx++)
if ((imagecolorat($img, $mx, $my) >> 24) < $threshold)
imagesetpixel($row['maskimage'], $x + $mx, $y + $my, $row['maskcolor']);
}
imagedestroy($img);
$x += 256;
$i++;
}
$y += 256;
}
}
// create spawn-maps if wanted
if ($modeMask & 0x04)
$createSpawnMap($overlay, $zoneId);
}
// check, if the current zone is multiLeveled
// if there are also files present without layer-suffix assume them as layer: 0
$multiLeveled = false;
$multiLevel = 0;
do
{
if (!CLISetup::filesInPath('/'.$textureStr.'\/'.$textureStr.($multiLevel + 1).'_\d\.blp/i', true))
break;
$multiLevel++;
$multiLeveled = true;
}
while ($multiLevel < 18); // Karazhan has 17 frickin floors
// check if we can create base map anyway
$file = $path.'/'.$textureStr.'1.blp';
$hasBaseMap = CLISetup::fileExists($file);
CLISetup::log(' - area has '.($multiLeveled ? $multiLevel . ' levels' : 'only base level'));
$map = null;
for ($i = 0; $i <= $multiLevel; $i++)
{
ini_set('max_execution_time', 120); // max 120sec per image
$file = $path.'/'.$textureStr;
if (!$i && !$hasBaseMap)
continue;
// if $multiLeveled also suffix -0 to baseMap if it exists
if ($i && $multiLeveled)
$file .= $i.'_';
$doSkip = 0x0;
$outFile = [];
foreach ($mapDirs as $idx => $info)
{
$outFile[$idx] = $destDir . sprintf($info[0], strtolower(Util::$localeStrings[$l]).'/') . $zoneId;
$floor = $i;
if ($zoneId == 4100) // ToCStratholme: map order fix
$floor += 1;
if ($multiLeveled && !(isset($baseLevelFix[$zoneId]) && $i == $baseLevelFix[$zoneId]))
$outFile[$idx] .= '-'.$floor;
if (!isset(FileGen::$cliOpts['force']) && file_exists($outFile[$idx].'.'.$info[1]))
{
CLISetup::log($progress.' - file '.$outFile[$idx].'.'.$info[1].' was already processed');
$doSkip |= (1 << $idx);
}
}
if ($doSkip == 0xF)
continue;
$map = $assembleImage($file, $fmt, $mapWidth, $mapHeight);
if (!$map)
{
$success = false;
CLISetup::log(' - could not create image resource for map '.$zoneId.($multiLevel ? ' level '.$i : ''));
continue;
}
if (!$multiLeveled)
{
imagecopymerge($map, $overlay, 0, 0, 0, 0, imagesx($overlay), imagesy($overlay), 100);
imagedestroy($overlay);
}
// create map
if ($modeMask & 0x02)
{
foreach ($mapDirs as $idx => $info)
{
if ($doSkip & (1 << $idx))
continue;
if (!$writeImage($outFile[$idx], $info[1], $map, $info[2] ?: $mapWidth, $info[3] ?: $mapHeight, $progress))
$success = false;
}
}
}
// also create subzone-maps
if ($map && isset($wmo[$wmaId]) && $modeMask & 0x10)
{
foreach ($wmo[$wmaId] as &$row)
{
$doSkip = 0x0;
$outFile = [];
foreach ($mapDirs as $idx => $info)
{
$outFile[$idx] = $destDir . sprintf($info[0], strtolower(Util::$localeStrings[$l]).'/') . $row['areaTableId'];
if (!isset(FileGen::$cliOpts['force']) && file_exists($outFile[$idx].'.'.$info[1]))
{
CLISetup::log($progress.' - file '.$outFile[$idx].'.'.$info[1].' was already processed');
$doSkip |= (1 << $idx);
}
}
if ($doSkip == 0xF)
continue;
$subZone = imagecreatetruecolor($mapWidth, $mapHeight);
imagecopy($subZone, $map, 0, 0, 0, 0, imagesx($map), imagesy($map));
imagecopy($subZone, $row['maskimage'], $row['x'], $row['y'], 0, 0, imagesx($row['maskimage']), imagesy($row['maskimage']));
foreach ($mapDirs as $idx => $info)
{
if ($doSkip & (1 << $idx))
continue;
if (!$writeImage($outFile[$idx], $info[1], $subZone, $info[2] ?: $mapWidth, $info[3] ?: $mapHeight, $progress))
$success = false;
}
imagedestroy($subZone);
}
}
if ($map)
imagedestroy($map);
}
}
}
/***********/
/* Credits */
/***********/
if ($modeMask & 0x08) // optional tidbits (not used by default)
{
if (CLISetup::writeDir($destDir.'Interface/Glues/Credits/'))
{
// tile ordering
$order = array(
1 => array(
[1]
),
2 => array(
[1],
[2]
),
4 => array(
[1, 2],
[3, 4]
),
6 => array(
[1, 2, 3],
[4, 5, 6]
),
8 => array(
[1, 2, 3, 4],
[5, 6, 7, 8]
)
);
$imgGroups = [];
$srcPath = sprintf($imgPath, $locStr).'Glues/Credits/';
$files = CLISetup::filesInPath($srcPath);
foreach ($files as $f)
{
if (preg_match('/([^\/]+)(\d).blp/i', $f, $m))
{
if ($m[1] && $m[2])
{
if (!isset($imgGroups[$m[1]]))
$imgGroups[$m[1]] = $m[2];
else if ($imgGroups[$m[1]] < $m[2])
$imgGroups[$m[1]] = $m[2];
}
}
}
// errör-korrekt
$imgGroups['Desolace'] = 4;
$imgGroups['BloodElf_Female'] = 6;
$total = count($imgGroups);
$sum = 0;
CLISetup::log('Processing '.$total.' files from Glues/Credits/...');
foreach ($imgGroups as $file => $fmt)
{
ini_set('max_execution_time', 30); // max 30sec per image (loading takes the most time)
$sum++;
$done = ' - '.str_pad($sum.'/'.$total, 8).str_pad('('.number_format($sum * 100 / $total, 2).'%)', 9);
$name = $destDir.'Interface/Glues/Credits/'.$file;
if (!isset(FileGen::$cliOpts['force']) && file_exists($name.'.png'))
{
CLISetup::log($done.' - file '.$name.'.png was already processed');
continue;
}
if (!isset($order[$fmt]))
{
CLISetup::log(' - pattern for file '.$name.' not set. skipping', CLISetup::LOG_WARN);
continue;
}
$im = $assembleImage($srcPath.$file, $order[$fmt], count($order[$fmt][0]) * 256, count($order[$fmt]) * 256);
if (!$im)
{
CLISetup::log(' - could not assemble file '.$name, CLISetup::LOG_ERROR);
continue;
}
if (!$writeImage($name, 'png', $im, count($order[$fmt][0]) * 256, count($order[$fmt]) * 256, $done))
$success = false;
}
ini_set('max_execution_time', $runTime);
}
else
$success = false;
}
return $success;
}
?>