Created
January 4, 2018 19:22
-
-
Save looki/ad8d187f6c224c8408ead3d1370fb7dd to your computer and use it in GitHub Desktop.
Super old Knytt Stories map reader and renderer classes for PHP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class KnyttBinReader | |
{ | |
private $rootDir = ''; | |
private $binSize = 0; | |
private $files = null; | |
//Destructor | |
function __destruct() | |
{ | |
unset($files); | |
} | |
//Read .knytt.bin | |
public function Read($file, $skipSongs=true) | |
{ | |
$this->rootDir = ''; | |
$this->binSize = 0; | |
$this->files = null; | |
//File doesn't exist, quit | |
if(!file_exists($file)) return false; | |
//Read size | |
$this->binSize = filesize($file); | |
//Open file for reading | |
$handle = fopen($file, 'r'); | |
//Check for indicator 'NF' | |
if(fread($handle, 2) != 'NF') return false; | |
//Get root directory | |
while(ord($chr = fread($handle, 1))) | |
$this->rootDir .= $chr; | |
//Skip number of files (incorrect anyway?) | |
$fileCount = fread($handle, 4); | |
//Loop through file | |
while(ftell($handle) < $this->binSize) | |
{ | |
//Skip NF | |
fread($handle, 2); | |
$filePath = ''; | |
//Add character to file path until we hit a terminator | |
while(ord($chr = fread($handle, 1))) | |
$filePath .= ($chr == '\\') ? '/' : $chr; | |
//Read size of following file | |
$sizeBytes = fread($handle, 4); | |
$this->files[$filePath]['size'] = ord($sizeBytes{0}) + ord($sizeBytes{1})*256 + ord($sizeBytes{2})*65536 + ord($sizeBytes{3})*16777216; | |
//Read file | |
if(!$skipSongs) | |
$this->files[$filePath]['data'] = fread($handle, $this->files[$filePath]['size']); | |
else | |
{ | |
if(substr($filePath, 0, 5)!='Music' && substr($filePath, 0, 8)!='Ambiance') | |
$this->files[$filePath]['data'] = fread($handle, $this->files[$filePath]['size']); | |
else | |
$this->files[$filePath]['data'] = fseek($handle, $this->files[$filePath]['size'], SEEK_CUR); | |
} | |
} | |
//Done. Close file | |
fclose($handle); | |
return $fileCount; | |
} | |
//Save | |
public function SaveFile($file, $output='') | |
{ | |
//File not found, quit | |
if(!isset($this->files[$file])) return false; | |
//If no output path is specified, use file name | |
if(!$output) $output = basename($file); | |
//Write file | |
$handle = fopen($output, 'w'); | |
fwrite($handle, $this->files[$file]['data']); | |
fclose($handle); | |
return true; | |
} | |
//Get root directory | |
public function GetRoot() | |
{ | |
return $this->rootDir; | |
} | |
//Get file | |
public function GetFile($file) | |
{ | |
//File not found, quit | |
if(!isset($this->files[$file])) return false; | |
return $this->files[$file]['data']; | |
} | |
//Get file size | |
public function GetFileSize($file) | |
{ | |
//File not found, quit | |
if(!isset($this->files[$file])) return 0; | |
return $this->files[$file]['size']; | |
} | |
//Get list of filenames | |
public function GetFileList() | |
{ | |
//No files read yet, quit | |
if(!isset($this->files)) return false; | |
return array_keys($this->files); | |
} | |
//Get list of filenames that metch regexp, e.g. /.png$/i | |
public function GetFilesRegexp($regexp) | |
{ | |
//No files read yet, quit | |
if(!isset($this->files)) return false; | |
$return = array(); | |
$keys = array_keys($this->files); | |
//Loop through all files and check | |
foreach($keys as $k => $v) | |
{ | |
if(preg_match($regexp, $v)) | |
$return[$k] = $v; | |
} | |
return $return; | |
} | |
public function ReadINI($file) | |
{ | |
//File not found, quit | |
if(!isset($this->files[$file])) return false; | |
$lines = explode("\n", $this->files[$file]['data']); | |
$return = array(); | |
$inSect = false; | |
foreach($lines as $line) | |
{ | |
$line = trim($line); | |
if(!$line || $line[0]=="; ") | |
continue; | |
if($line[0] == '[' && $endIdx = strpos($line, ']')) | |
{ | |
$inSect = substr($line, 1, $endIdx-1); | |
continue; | |
} | |
if(!strpos($line, '=')) | |
continue; | |
$tmp = explode('=', $line, 2); | |
if($inSect) | |
$return[$inSect][trim($tmp[0])] = ltrim($tmp[1]); | |
else | |
$return[trim($tmp[0])] = ltrim($tmp[1]); | |
} | |
return $return; | |
} | |
} | |
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/* | |
This class reads Map.bin files which contain all the information on every single room in the level. | |
Plus, it can render rooms of the level using the php_gd plug-in. Also, for rendering multiple rooms, | |
there is a cache feature that allows you to re-use already used graphic files which heavily decreases | |
the time it takes to render all rooms. This is crucial for rendering, say, the whole map of a level! | |
Usage: | |
$km = new KnyttMap; | |
$km->LoadMap('Map.bin'); | |
$im->RenderScreen('x1000y1000'); | |
header('Content-type: image/png'); | |
imagepng($im->GetCanvas()); | |
Interaction with KnyttBinReader: | |
$km = new KnyttMap; | |
$km->LoadData($kb->GetFile('Map.bin')); | |
*/ | |
define('KM_TILESETS', 0); | |
define('KM_OBJECTS', 1); | |
define('KM_GRADIENTS', 2); | |
define('KM_IMAGE', 0); | |
define('KM_ALPHA', 1); | |
class KnyttMap | |
{ | |
private $mapData; | |
private $maps; //Light version of $mapData, only contains map names as Array that contains X and Y. | |
private $mapBoundaries; | |
private $mapFolder; | |
private $dataFolder; // = './Data' | |
private $ownDataFolder; // = './' | |
private $canvas; | |
private $cache; | |
private $ini; | |
//Callbacks | |
private $getDataCB = null; | |
private $customDataCB = null; | |
public $renderFilter; | |
//Constructor. Specify map, data, and custom data folder. | |
function __construct($mapfolder = './', $datafolder = './Data/', $ownfolder = './') | |
{ | |
$this->mapFolder = $mapfolder == '' ? $mapfolder : str_replace('//', '/', $mapfolder.'/'); | |
$this->dataFolder = $datafolder == '' ? $datafolder : str_replace('//', '/', $datafolder.'/'); | |
$this->ownDataFolder = $ownfolder == '' ? $ownfolder : str_replace('//', '/', $ownfolder.'/'); | |
$this->cache = null; | |
} | |
//Delete all imagess. | |
function __destruct() | |
{ | |
if($this->canvas) | |
imagedestroy($this->canvas); | |
if(is_array($cache = $this->cache[KM_TILESETS])) | |
foreach($cache as $img) | |
imagedestroy($img[KM_IMAGE]); | |
if(is_array($cache = $this->cache[KM_OBJECTS])) | |
foreach($cache as $img) | |
imagedestroy($img[KM_IMAGE]); | |
if(is_array($cache = $this->cache[KM_GRADIENTS])) | |
foreach($cache as $img) | |
imagedestroy($img); | |
} | |
//Get a setting of a screen: Tileset A/B, Ambiance A/B, Music and Gradient. | |
public function GetSetting($screen, $setting) | |
{ | |
return $this->mapData[$screen]['settings'][$setting]; | |
} | |
//Get a tile or object on a screen. | |
public function GetTile($screen, $layer, $tile) | |
{ | |
return ord($this->mapData[$screen]['data'][$layer][$tile]); | |
} | |
//Get the path of a resource, checks for custom files first. | |
public function GetData($path) | |
{ | |
//No callback | |
if($this->getDataCB == null) | |
{ | |
if(file_exists($this->ownDataFolder.$path)) | |
return $this->ownDataFolder.$path; | |
} | |
//Use callback | |
else | |
{ | |
if(call_user_func($this->customDataCB, $this->ownDataFolder.$path)) | |
return $this->ownDataFolder.$path; | |
} | |
//Use default data | |
if(file_exists($this->dataFolder.$path)) | |
return $this->dataFolder.$path; | |
//Error | |
else return false; | |
} | |
//Return the key of a screen in the map array. | |
public function GetScreen($screen) | |
{ | |
$key = -1; | |
for($i = 0; $i < count($this->maps); $i++) | |
{ | |
//Found, break loop | |
if($screen == 'x'.$this->maps[$i][0].'y'.$this->maps[$i][1]) | |
{ | |
$key = $i; | |
break; | |
} | |
} | |
return $key; | |
} | |
//Check if a screen specified by its name exists. | |
public function ScreenExists($screen) | |
{ | |
return $this->getScreen($screen) > -1; | |
} | |
//Load a Map.bin file. | |
//$map - Map.bin file to load | |
//$calcBounds - If true, GetMapBoundaries() will return the boundaries of the map | |
//$useAbs - If true, an absolute path instead of a path relative to the map folder in the constructor will be used. | |
//$wantedMaps - If specified, only the specified rooms will be returned (e.g. array('x1000y1000')) | |
//Return value: An array with all the available data about every room. | |
public function LoadMap($map = 'Map.bin', $calcBounds = false, $useAbs = false, $wantedMaps = null) | |
{ | |
//Attach $mapFolder if not using absolute path. | |
if(!$useAbs) | |
$map = $this->mapFolder.$map; | |
//Check for a valid gz file | |
$gz = fopen($map, 'r'); | |
$magic = fread($gz, 2); | |
fclose($gz); | |
//Check for magic number | |
if(ord($magic[0]) != 31 || ord($magic[1]) != 139) | |
return false; | |
//Read map and store data | |
$mapContents = ''; | |
$gz = gzopen($map, 'r'); | |
while(!gzeof($gz)) | |
$mapContents .= gzgetc($gz); | |
gzclose($gz); | |
//Simple check: first map name begins with 'x'? | |
if($mapContents[0]!='x') | |
return false; | |
$this->mapData = array(); | |
$this->maps = array(); | |
$mapLength = strlen($mapContents); | |
//$pos is used to run through the whole map file. | |
$pos = 0; | |
//While $pos is inside of the file... | |
while($pos < $mapLength) | |
{ | |
//Hold position to get mapname. | |
$hold = $pos; | |
//Find 0-byte to terminate the string. | |
$pos = strpos($mapContents, chr(0), $pos); | |
//Get mapname. | |
$mapName = substr($mapContents, $hold, $pos-$hold); | |
//Skip 0-byte and length of the workspace stored as long integer (4 bytes), whose value is constant: 3006. | |
$pos += 5; | |
$mapData = substr($mapContents, $pos, 3006); | |
//Skip data. | |
$pos += 3006; | |
//Put data; | |
$lightName = explode('y', substr($mapName, 1)); | |
//Invalid map | |
if(count($lightName) != 2) | |
return false; | |
if($wantedMaps == null || in_array('x'.$lightName[0].'y'.$lightName[1], $wantedMaps)) | |
{ | |
array_push($this->maps, $lightName); | |
array_push($this->mapData, | |
//Name | |
array( | |
'name' => $mapName, | |
//Screen data | |
'data' => array( | |
//Layers, layers 4-7 need twice as many bytes as layers 0-3 since they need to store the object and bank IDs. | |
0 => substr($mapData, 0, 250), | |
1 => substr($mapData, 250, 250), | |
2 => substr($mapData, 500, 250), | |
3 => substr($mapData, 750, 250), | |
4 => substr($mapData, 1000, 500), | |
5 => substr($mapData, 1500, 500), | |
6 => substr($mapData, 2000, 500), | |
7 => substr($mapData, 2500, 500) | |
), | |
//Settings | |
'settings' => array( | |
'tilesetA' => ord($mapData{3000}), | |
'tilesetB' => ord($mapData{3001}), | |
'ambianceA' => ord($mapData{3002}), | |
'ambianceB' => ord($mapData{3003}), | |
'music' => ord($mapData{3004}), | |
'gradient' => ord($mapData{3005}), | |
) | |
) | |
); | |
} | |
//Repeats until parsing is done. | |
} | |
//Get boundaries of the map. | |
if($calcBounds) | |
{ | |
$bl = 9999; $bt = 9999; $br = 0; $bb = 0; | |
for($i = 0; $i < count($this->maps); $i++) | |
if($this->maps[$i][0] < $bl) $bl = $this->maps[$i][0]; | |
for($i = 0; $i < count($this->maps); $i++) | |
if($this->maps[$i][0] > $br) $br = $this->maps[$i][0]; | |
for($i = 0; $i < count($this->maps); $i++) | |
if($this->maps[$i][1] < $bt) $bt = $this->maps[$i][1]; | |
for($i = 0; $i < count($this->maps); $i++) | |
if($this->maps[$i][1] > $bb) $bb = $this->maps[$i][1]; | |
//Store data | |
$this->mapBoundaries = array('left' => $bl, 'right' => $br, 'top' => $bt, 'bottom' => $bb, | |
'width' => $br-$bl+1, 'height' => $bb-$bt+1); | |
} | |
else $this->mapBoundaries = null; | |
return true; | |
} | |
//Load a map from a compressed string. Useful in combination with my KnyttBinReader class. | |
//Parameters are the same as LoadMap() | |
public function LoadData($mapContents, $calcBounds = false, $wantedMaps = null) | |
{ | |
//Write data to a temporary file | |
$tmp = tempnam('/tmp', 'ksm'); | |
$handle = fopen($tmp, 'w'); | |
fwrite($handle, $mapContents); | |
fclose($handle); | |
//Load temporary file | |
$output = $this->LoadMap($tmp, $calcBounds, true, $wantedMaps); | |
unlink($tmp); | |
return $output; | |
} | |
//This is the default callback for ::RenderScreen. Change it via ::SetDataCB. | |
private function GetPNG($path, $wantAlpha = true) | |
{ | |
//Read file | |
$png = file_get_contents($path); | |
$image = imagecreatefromstring($png); | |
//We want alpha information. | |
if($wantAlpha) | |
{ | |
//Check for alpha channel | |
$alpha = (ord($png[25])==6); | |
//Return array with information | |
return array(0=>$image, 1=>$alpha); | |
} | |
else | |
return $image; | |
} | |
//Render a specific screen. | |
//$screen - The coordinates of the screen, e.g. 'x1000y1000' | |
//$removeInternal - Doesn't render special invisible objects like 'Sign A'. | |
//$removeGhosts - Doesn't render ghosts (actual ghosts found in the Ghost bank). | |
//Returns true on success, otherwise false. | |
public function RenderScreen($screen, $removeInternal = true, $removeGhosts = true) | |
{ | |
//Check for custom callback | |
if($this->getDataCB == null) | |
$readCallback = array($this, 'GetPNG'); | |
else | |
$readCallback = $this->getDataCB; | |
//Find map in array. | |
$key = $this->getScreen($screen); | |
//Map found! | |
if($key >= 0) | |
{ | |
//Delete old image if existent; allocate new one | |
if($this->canvas) | |
imagedestroy($this->canvas); | |
$this->canvas = imagecreatetruecolor(600, 240); | |
//Draw gradient | |
$gradientID = $this->GetSetting($key, 'gradient'); | |
$gradientPath = $this->GetData('Gradients/Gradient'.$gradientID.'.png'); | |
//Use cache if array allocated | |
if(is_array($this->cache[KM_GRADIENTS])) | |
{ | |
//Store if not done yet | |
if(!isset($this->cache[KM_GRADIENTS][$gradientID])) | |
$this->cache[KM_GRADIENTS][$gradientID] = call_user_func($readCallback, $gradientPath, false); | |
//Copy image address for later use | |
$gradient = $this->cache[KM_GRADIENTS][$gradientID]; | |
} | |
//No cache | |
else | |
{ | |
$gradient = call_user_func($readCallback, $gradientPath, false); | |
} | |
//Draw gradient | |
$gradientWidth = imagesx($gradient); | |
$loop = ceil(600/$gradientWidth); | |
for($i = 0; $i < $loop; $i++) | |
imagecopy($this->canvas, $gradient, $i*$gradientWidth, 0, 0, 0, $gradientWidth, 240); | |
//Free memory | |
if(!is_array($this->cache[KM_GRADIENTS])) | |
imagedestroy($gradient); | |
//Load tilesets | |
$tilePathA = $this->GetData('Tilesets/Tileset'.$this->GetSetting($key, 'tilesetA').'.png'); | |
$tilePathB = $this->GetData('Tilesets/Tileset'.$this->GetSetting($key, 'tilesetB').'.png'); | |
//Use cache if array allocated | |
if(is_array($this->cache[KM_TILESETS])) | |
{ | |
$tileA = $this->GetSetting($key, 'tilesetA'); | |
$tileB = $this->GetSetting($key, 'tilesetB'); | |
//Store tileset A | |
if(!isset($this->cache[KM_TILESETS][$tileA])) | |
{ | |
$this->cache[KM_TILESETS][$tileA] = call_user_func($readCallback, $tilePathA); | |
//If there's no alpha channel, set transparent color to pink | |
if(!$this->cache[KM_TILESETS][$tileA][KM_ALPHA]) | |
$this->MakeTransparent($this->cache[KM_TILESETS][$tileA][KM_IMAGE]); | |
} | |
//Store tileset B | |
if(!isset($this->cache[KM_TILESETS][$tileB])) | |
{ | |
$this->cache[KM_TILESETS][$tileB] = call_user_func($readCallback, $tilePathB); | |
//If there's no alpha channel, set transparent color to pink | |
if(!$this->cache[KM_TILESETS][$tileB][KM_ALPHA]) | |
$this->MakeTransparent($this->cache[KM_TILESETS][$tileB][KM_IMAGE]); | |
} | |
//Copy image address for later use | |
$tileset[0] = $this->cache[KM_TILESETS][$tileA]; | |
$tileset[1] = $this->cache[KM_TILESETS][$tileB]; | |
} | |
//No cache | |
else | |
{ | |
$tileset[0] = call_user_func($readCallback, $tilePathA); | |
$tileset[1] = call_user_func($readCallback, $tilePathB); | |
//If there's no alpha channel, set transparent color to pink | |
if(!$tileset[0][KM_ALPHA]) | |
$this->MakeTransparent($tileset[0][KM_IMAGE]); | |
if(!$tileset[1][KM_ALPHA]) | |
$this->MakeTransparent($tileset[1][KM_IMAGE]); | |
} | |
if($tileset[0] && $tileset[1]) | |
{ | |
for($lay = 0; $lay < 4; $lay++) | |
{ | |
for($i = 0; $i < 250; $i++) | |
{ | |
//Tile is visible and layer isn't listed in renderFilter | |
if($this->GetTile($key, $lay, $i)%128 > 0 && strpos('x'.$this->renderFilter, (string)$lay) === false) | |
{ | |
//Tileset A or B? | |
$a_or_b = floor($this->getTile($key, $lay, $i)/128); | |
//Alpha | |
if($tileset[$a_or_b][KM_ALPHA]) | |
{ | |
//Copy tile into canvas | |
imagecopy($this->canvas, | |
$tileset[$a_or_b][KM_IMAGE], //Tileset | |
($i%25)*24, floor($i/25)*24, // Destination XY | |
(($this->getTile($key, $lay, $i)%128)%16)*24, //Source X | |
floor(($this->getTile($key, $lay, $i)%128)/16)*24, // Source Y | |
24, 24); //Size | |
} | |
else | |
{ | |
//Copy tile into canvas | |
imagecopymerge($this->canvas, | |
$tileset[$a_or_b][KM_IMAGE], //Tileset | |
($i%25)*24, floor($i/25)*24, // Destination XY | |
(($this->getTile($key, $lay, $i)%128)%16)*24, //Source X | |
floor(($this->getTile($key, $lay, $i)%128)/16)*24, // Source Y | |
24, 24, 100); //Size | |
} | |
} | |
} | |
} | |
//Free memory | |
if(!is_array($this->cache[KM_TILESETS])) | |
{ | |
imagedestroy($tileset[0][KM_IMAGE]); | |
imagedestroy($tileset[1][KM_IMAGE]); | |
} | |
} | |
else | |
return false; | |
//Draw objects | |
for($lay = 4; $lay < 8; $lay++) | |
{ | |
for($i = 0; $i < 250; $i++) | |
{ | |
//Object is visible and layer isn't listed in renderFilter | |
if($this->GetTile($key, $lay, $i) > 0 && strpos('x'.$this->renderFilter, (string)$lay) === false) | |
{ | |
//Check if object exists | |
$objectBank = $this->GetTile($key, $lay, $i+250); | |
$objectID = $this->GetTile($key, $lay, $i); | |
//Custom object | |
if($objectBank==255) | |
{ | |
//Get custom object's settings | |
$ini = $this->ini['Custom Object '.$objectID]; | |
$coPath = $this->GetData('Custom Objects/'.$ini['Image']); | |
if($coPath===false) | |
continue; | |
$tilew = $ini['Tile Width']?(int)$ini['Tile Width']:24; | |
$tileh = $ini['Tile Height']?(int)$ini['Tile Height']:24; | |
$ox = (int)$ini['Offset X']; | |
$oy = (int)$ini['Offset Y']; | |
$frame = (int)$ini['Init AnimFrom']; | |
if(is_array($this->cache[KM_OBJECTS])) | |
{ | |
//Store if not done yet | |
if(!isset($this->cache[KM_OBJECTS][$objectID+65280])) | |
{ | |
$co = call_user_func($readCallback, $coPath, false); | |
$this->cache[KM_OBJECTS][$objectID+65280] = $co; | |
} | |
//Copy image address for later use | |
$co = $this->cache[KM_OBJECTS][$objectID+65280]; | |
} | |
//No cache | |
else | |
{ | |
$co = call_user_func($readCallback, $coPath, false); | |
} | |
//Calculate width & source XY | |
$cow = imagesx($co); | |
$srcx = ($frame%($cow/$tilew))*$tilew; | |
$srcy = floor($frame/($cow/$tilew))*$tileh; | |
//Draw custom object | |
imagecopy($this->canvas, $co, //Object | |
($i%25)*24+12-floor($tilew/2)+$ox, floor($i/25)*24+12-floor($tileh/2)+$oy, //Destination XY | |
$srcx, $srcy, //Source XY | |
$tilew, $tileh); //Size | |
//Free memory | |
if(!is_array($this->cache[KM_OBJECTS])) | |
imagedestroy($co); | |
} | |
//In-built object | |
else | |
{ | |
//Filter | |
$drawObj = true; | |
if($removeInternal) | |
{ | |
$b = $objectBank; | |
$o = $objectID; | |
//System | |
if($b == 0 && ($o == 2 || ($o >= 11 && $o <= 20) || $o >= 25)) | |
$drawObj = false; | |
// Ghost [X] Wall | |
elseif($b == 12 && $o == 17) | |
$drawObj = false; | |
//Invisible | |
elseif($b == 16) | |
$drawObj = false; | |
//Fly A B | |
elseif($b == 2 && ($o == 3 || $o == 4)) | |
$drawObj = false; | |
//Decoration | |
elseif($b == 8 && ($o >= 15 && $o <= 17)) | |
$drawObj = false; | |
//Nature FX | |
elseif($b == 7 && ($o == 1 || $o == 10 || $o == 12 || $o == 14 || $o == 16 || $o == 3 || $o == 6 || $o == 8)) | |
$drawObj = false; | |
//Ghosts | |
elseif($b == 12 && $removeGhosts) | |
$drawObj = false; | |
//Robots | |
elseif($b == 13 && ($o == 7 || $o == 10)) | |
$drawObj = false; | |
//Robots (redirections) | |
elseif($b == 13) | |
{ | |
//Laser | |
if($o == 8 || $o == 11) | |
$objectID++; | |
} | |
//Objects & Areas (redirections) | |
elseif($b == 15) | |
{ | |
//Password switches | |
if($o >= 14 && $o <= 21) | |
$objectID = 13; | |
//Disappearing blocks | |
elseif($o >= 8 && $o <= 11) | |
$objectID -= 7; | |
//Blue blocks | |
elseif($o == 6) | |
$drawObj = false; | |
elseif($o == 7) | |
$objectID = 6; | |
} | |
//Traps (redirections) | |
elseif($b == 6 && $o == 6) | |
$b = 8; | |
} | |
if($drawObj) | |
{ | |
$objectPath = $this->getData('Objects/Bank'.$objectBank.'/Object'.$objectID.'.png'); | |
if($objectPath) | |
{ | |
if(is_array($this->cache[KM_OBJECTS])) | |
{ | |
//Store if not done yet | |
if(!isset($this->cache[KM_OBJECTS][$objectID+$objectBank*256])) | |
{ | |
$obj = call_user_func($readCallback, $objectPath); | |
//If there's no alpha channel, set the ransparent color to pink | |
if(!$obj[KM_ALPHA]) $this->MakeTransparent($obj[KM_IMAGE]); | |
$this->cache[KM_OBJECTS][$objectID+$objectBank*256] = $obj; | |
} | |
//Copy image address for later use | |
$obj = $this->cache[KM_OBJECTS][$objectID+$objectBank*256]; | |
$objsx = imagesx($obj[KM_IMAGE]); | |
$objsy = imagesy($obj[KM_IMAGE]); | |
} | |
//No cache | |
else | |
{ | |
$obj = call_user_func($readCallback, $objectPath); | |
$objsx = imagesx($obj[KM_IMAGE]); | |
$objsy = imagesy($obj[KM_IMAGE]); | |
//If there's no alpha channel, set the ransparent color to pink | |
if(!$obj[KM_ALPHA]) $this->MakeTransparent($obj[KM_IMAGE]); | |
} | |
//Image uses alpha channel. | |
if($obj[KM_ALPHA]) | |
{ | |
imagecopy($this->canvas, $obj[KM_IMAGE], //Object | |
($i%25)*24, floor($i/25)*24, // Destination XY | |
0, 0, | |
$objsx, $objsy); //Size | |
} | |
//Image's transparency is based on pink. | |
else | |
{ | |
imagecopymerge($this->canvas, $obj[KM_IMAGE], //Object | |
($i%25)*24, floor($i/25)*24, // Destination XY | |
0, 0, | |
$objsx, $objsy, 100); //Size | |
} | |
//Free memory | |
if(!is_array($this->cache[KM_OBJECTS])) | |
imagedestroy($obj[KM_IMAGE]); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
return $key >= 0; | |
} | |
//Returns the canvas rendered to by ::RenderScreen. | |
public function GetCanvas() | |
{ | |
return $this->canvas; | |
} | |
//Returns an array with the map's dimensions and boundaries. | |
public function GetMapBoundaries() | |
{ | |
return $this->mapBoundaries; | |
} | |
//Returns an array with each map's name and data (tiles, objects, ambiance...). | |
public function GetMapData() | |
{ | |
return $this->mapData; | |
} | |
//Returns an array of all map names. | |
public function GetMaps() | |
{ | |
return $this->maps; | |
} | |
//Use a cache array. This is useful for rendering several screens at once - the renderer won't reload tilesets every time. | |
//$types - An array with the data types to cache. Array(KM_TILESETS, KM_OBJECTS, KM_GRADIENTS) would cache all types of data. | |
public function UseCache($types) | |
{ | |
if(is_array($this->cache)) | |
return false; | |
//Allocate arrays | |
foreach($types as $t) | |
$this->cache[$t] = array(); | |
return true; | |
} | |
//Returns a copy of the cache array. | |
public function GetCache() | |
{ | |
return $this->cache; | |
} | |
//Set a callback for reading data (e.g. a tileset). See ::RenderScreen, default is ::GetPNG. | |
public function SetDataCB($callback) | |
{ | |
$this->getDataCB = $callback; | |
} | |
//Set a callback for deciding whether to use custom data or not. See ::GetData. | |
public function SetCustomDataCB($callback) | |
{ | |
$this->customDataCB = $callback; | |
} | |
//Set World.ini (as array!) for custom objects. | |
public function SetINI($ini) | |
{ | |
if(is_array($ini)) | |
$this->ini = $ini; | |
else | |
$this->ini = null; | |
} | |
//Set pink to transparent. | |
private function MakeTransparent($img) | |
{ | |
$pink = imagecolorexact($img, 255, 0, 255); | |
//Image is not indexed, try to allocate pink | |
if($pink == -1) | |
$pink = imagecolorallocate($img, 255, 0, 255); | |
imagecolortransparent($img, $pink); | |
} | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment