Карты/Уровни игры warzone2100




Оглавление:

Структура карт
Чтение ландшафта
Чтение объектов JSON
Чтение объектов в бинарном формате
Составление картографии текстурами








Структура карт


Как происходит чтение карты:

Для мультиплеера первым делом игра ищет и читает файл addon.lev в data/mp/, после проверяет *.addon.lev или *.xplayers.lev для добавленных в игру карт
Для сценарной компании это gamedesc.lev

Добавленные в игру карты *.wz - по сути обычный zip-файл со средней степенью сжатия, содержащий как ресурсы карты, так и медиафайлы

В файле *.lev игра ищет параметры:
// - пропускаются, это ремарка (например авто комментарии от FlaME)
level - имя карты, отображаемой в интерфейсах
players - кол-во игроков, для сортировки в листинге карт интерфейса
type - режим игры (14-SKIRMISH, 18-SKIRMISH RESEARCH 2, 19-SKIRMISH RESEARCH 3 и 12,15,16 для сценариев или компании)
dataset - идентификатор шаблона загрузок нескольких data файлов *.wrf (установлены в addon.lev)
game - путь к файлу карты *.gam
data - путь к одиночному data файлу *.wrf
---
в data файлах *.wrf описаны назначения и пути всех медиафайлов использующихся в карте(статы, звуки, текстуры, модельки, скрипты, тексты)
по сути, можно создать такую карту, которая полностью изменит как вид, так и тип игры, карту которая совсем будет не похожа на warzone2100
файл *.gam используется для сохранённой игры , а при загрузке карты не используется, зато используется путь к нему (-4 байта) как базовый путь к ресурсам карты.
Используются 52 первых байта из файла *.gam при загрузке любой карты, в этом файле сохраняется информация о разрешённой площади карты (Map scroll)
Подробнее о файле *.gam в game.cpp функции deserializesaveGameV19Data()

Далее, используя параметр game, отрезая последних 4 байта ".gam", находится основной путь к ресурсам карты,
то есть имя файла *.gam должно полностью совпадать с каталогом ресурсов карты.

В ресурсах карты игра ищет файлы *.bjo, это очень старого формата базы данных по дополнительным объектам, которые присутствуют на карте(до ветки версий 3.x)
Их полное описание я возможно напишу позже и приведу скрипты как читать и десериализовать эти данные
Если файлов *.bjo нет, то игра загружает файлы droid.json, features.json и struct.json (в версии 3.1.5 это были *.ini файлы)
Игра версии 3.2+ не сможет прочитать объекты в *.ini файлах, так же как и игра 3.1.5 не сможет прочитать *.json, однако все версии читают *.bjo
Если лежат и те и другие типы данных(bjo и json/ini), игра будет использовать *.bjo

Так же игра загружает и начинает читать файлы о ландшафте ttypes.ttp и game.map.





Чтение ландшафта



Файл game.map состоит из 3-х частей: Параметры карты, информация о ландшафте, информация о обороне для ИИ(gateways)
Файл ttypes.ttp содержит структуру типов ландшафта и читается первым. Далее читается game.map


Возьму задачу, нарисовать схематично карту высот используя не C++, а что ни будь по проще, например python, php или javascript.
Остановлюсь пожалуй на php5, даже усложним немного задачу, и попробуем решить её максимально просто и понятно.
Задача: Прочитать и понять как читаются файлы ttypes.ttp и game.map
Нарисовать карту высот, поверх неё, нанести плитки с водой, плитки с горными вершинами(те, что невозможно преодолеть "Cliff Face"),
а так как в файле есть ещё и gateways, их тоже нарисуем схематично поверх карты высот.
Итак, для примера я возьму встроенную в игру карту 2c-Roughness, на ней есть как вода, так и gateways.
Создадим текстовый файл, назовём его например readmap.php
Запускать файл будем интерпретатором php из командной строки, я работаю с bash в linux, поэтому для меня это "php readmap.php 2c-Roughness/"
Где первым указываю интерпретатор это php, вторым файл, который мы создали и наполним кодом, и третьим это каталог с ресурсами карты, в котором и хранятся ttypes.ttp и game.map
Итак, сам код php
(для подсветки синтаксиса ниже используется JavaScript, так что если он выключен у Вас в браузере, то код будет сливаться):

//Путь к каталогу ресурсам карты
$map = $argv[1];

//-------\\
//ЧТЕНИЕ ТИПОВ ЛАНДШАФТА
// ttypes.ttp
//-------\\

//Узнаём какой размер файла ttypes.ttp
$ttypLen = filesize("$map/ttypes.ttp");

//Открываем на чтение ttypes.ttp, структуру типов ландшафта
$file_handler = fopen("$map/ttypes.ttp", 'r');

//Читаем заголовок, первые 4 байта файла, у файла ttypes.ttp он всегда будет "ttyp"
$header = fread($file_handler, 4);

//Если заголовок не удалось прочитать, что-то пошло не так, останов.
if($header != "ttyp") {
	print "Неверный файл типов ландшафта ttypes.ttp";
	fclose($file_handler);
	exit();
}

//Если заголовок верен, значит мы уверены что открыли нужный файл, читаем его по плану
//Далее чтение будет происходить в бинарном режиме со смещением указателя внутри файла (автоматически)

//Читаем первый после заголовка без-знаковый long 32 бита, порядок little endian (u32le, 4 байта), это версия файла/карты
//и распаковываем его средствами самого php
$ver = unpack('V', fread($file_handler, 4))[1];

//Читаем второй u32le, тут содержится информация о количестве типов ландшафта
//эта информация нам понадобится что бы в цикле считать всю структуру.
$num = unpack('V', fread($file_handler, 4))[1];

//Создаём массив куда сохраним структуру типов ландшафта
$terrainTypes = array();

//Запускаем цикл, до количества типов, которое прочитали выше
for($i=0;$i<$num;$i++){

//	Заполняем массив читая по без-знаковому 16 битному, порядок little endian (2 байта)
	$terrainTypes[$i] = unpack('v', fread($file_handler, 2))[1];
}

//Выводим информацию.
//Заголовок как есть
print "Header: $header\n";
//Версия файла
print "Version: $ver\n";
//Кол-во типов
print "Number: $num\n\n";

//Получаем информацию, на какой позиции файла у нас находится указатель
//Если всё верно прочитали, указатель должен находиться в конце файла.
$ttypHandler = ftell($file_handler);

//После того, как цикл завершился мы проверяем, весь ли файл был прочитан
if($ttypHandler != $ttypLen){

//	Если указатель не в конце файла, что-то пошло не так, останов.
	print "Прочитано: $ttypHandler\n";
	print "Размер файла: $ttypLen\n";
	fclose($file_handler);
	exit();
}

//Закрываем файл.
fclose($file_handler);

//Очищаем старые переменные, кроме $map и $terrainTypes, само собой они нам будут нужны при чтении самого ландшафта.
unset($file_handler, $ttypHandler, $ttypLen, $header, $ver, $num);

//-------\\
//ЧТАНИЕ ЛАНДШАФТА\\
// game.map
//-------\\

//Узнаём какой размер файла game.map
$mapLen = filesize("$map/game.map");

//Открываем на чтение game.map
$file_handler = fopen("$map/game.map", 'r');

//Читаем заголовок, первые 4 байта файла, у файла game.map он всегда будет "map "
$header = fread($file_handler, 4);

//Если заголовок не удалось прочитать, что-то пошло не так, останов.
//Заголовок состоит из 4 байт "map " пробел в конце является частью заголовка
if($header != "map "){
	print "Неверный файл ландшафта game.map";
	fclose($file_handler);
	exit();
}

//Так же читем первый после заголовка u32le(4 байта), это версия файла/карты
$ver = unpack('V', fread($file_handler, 4))[1];

//Ландшафт состоит из плиток, величина одной плитки равна одной структуре в игре.
//Например нефтевышка занимает целиком всю ландшафтную плитку.
//Лаборатория занимает 4 плитки размер 2 на 2.
//Завод занимает 9 плиток, размером 3 на 3.
//Высота плитки указывается высотой координаты левого верхнего угла плитки
//Далее плитка делится на треугольники и высоты сглаживаются движком игры.
//Разрешение координат внутри одной плитки составляют 128x128

//Следующие 8 байт хранят в себе информацию о размерах сетки ландшафта в плитках

//Читаем ширину карты (по "x", слева направо)
$width  = unpack('V', fread($file_handler, 4))[1];
//Читаем длину карты (по "y", сверху вниз)
$length = unpack('V', fread($file_handler, 4))[1];

//Маска битового сдвига для получении текстуры и типа ландшафта.
//Данная информация прописана в коде игры map.h
//Там также прописаны и другие маски, но мы их не берём в данном примере за ненадобностью.
$tileMask = 0x01ff;

//Тип ландшафта
//Данная информация прописана в коде игры map.h, и не настраиваемая (hardcoded)
$typesTerr = array(
"sand",
"sandybrush",
"bakedearth",
"greenmud",
"redbrush",
"pinkrock",
"road",
"water",
"clifface",
"rubble",
"sheetice",
"slush",
"max"
);

//Создаём массивы для наполнения
//Массив для водных плиток
$water = array();
//Массив для непроходимых горных плиток
$cliff = array();
//Массив для карты высот
$height = array();

//Далее идёт вторая часть файла game.map

//Получив информацию по количеству плиток в ширину и длину,
//запускаем цикл который пробежит по каждой плитке читая на ходу из файла.
for($i=0; $i < $width*$length; $i++){

//	Бинарная структура файла разбита по 3 байта на плитку и выглядит следующим образом:
//	2 байта самые информативные, в которых путём побитового сдвига содержатся:
//	информация о плитке, выравнивание по x, выравнивание по y, маска поворота, сдвиг поворота, направление деления плитки на треугольники, текстура плитки
//	в данном примере, нам нужны биты отвечающие за номер текстуры "$tileMask", другие маски не берём
//	1 байт - высота плитки (по "z", с глубины на поверхность)
//	Плитки указаны поочерёдно, без координат плоскости.


//	Читаем информацию о плитке, распаковываем как u16le
	$tileInfo = unpack('v', fread($file_handler, 2))[1];
//	Из прочитанных байт получаем информацию о текстуре путём сдвига бит по маске
	$texture = ($tileInfo & $tileMask);
//	Используя массив структуры типов из файла ttypes.ttp получаем тип ландшафта по текстуре
	$terrain = $terrainTypes[$texture];
	
//	Читаем информацию о высоте плитки
//	Т.к. это 8-бит, высота может быть от 0 до 255 (очень легко будет нарисовать карту высот)
	$tileHeight = unpack('C', fread($file_handler, 1))[1];


//	Заполняем массивы интересующей нас информацией
//	Если тип плитки соответствует воде
	if($typesTerr[$terrain] == "water") $water[$i] = true;
	
//	Если тип плитки соответствует горной вершине
	if($typesTerr[$terrain] == "clifface") $cliff[$i] = true;
	
//	Записываем высоту каждой плитки
//	Координата высоты указывает на высоту не всей плитки, а её левого верхнего угла
//	а конкретнее треугольника, левого верхнего угла плитки, на которые плитка поделится перед отрисовкой.
//	Т.к. плитка может иметь углы разной высоты.
	$height[$i] = $tileHeight;

}



//Далее идёт третья часть файла game.map, в котором содержится информация о gateway
//Продолжаем читать файл там, где остановился указатель после цикла.

//Получаем версию информации о gateway
$g_ver = unpack('V', fread($file_handler, 4))[1];
//Получаем кол-во gateway на карте
$g_num = unpack('V', fread($file_handler, 4))[1];

//Создаём массив для объектов gateway
$gateways = array();

//Создаём цикл по кол-ву gateway
for($i=0;$i<$g_num;$i++){

//	Бинарная структура разбита по 4 байта, которые указывают начало и конец gateway в координатах плоскости (x1, y1 - x2, y2)
//	gateway могут быть только строго линиями, горизонтальными или вертикальными.
	$x1  = unpack('C', fread($file_handler, 1))[1];
	$y1  = unpack('C', fread($file_handler, 1))[1];
	$x2  = unpack('C', fread($file_handler, 1))[1];
	$y2  = unpack('C', fread($file_handler, 1))[1];
	
//	Заполняем массив gateways, массивом координат каждой gateway
	$gateways[] = array($x1,$y1,$x2,$y2);
}


//После всего получаем информацию, на какой позиции файла у нас находится указатель
//Если всё верно прочитали, указатель должен находиться в конце файла.
$mapHandler = ftell($file_handler);

//После того, как все циклы завершились мы проверяем, весь ли файл был прочитан
if($mapHandler != $mapLen){

//	Если указатель не в конце файла, значит мы наткнулись на очень старый формат карты.
//Такие как sk-rush, sk-rush2, sk-cockpit и т.д.,
//в которых осталась информация о заголовках зонирований(версии игр 2.3), их было две версии "1" и "2"
//так как мы читаем карты в формате .wz, то скорее всего остатки будут версии "2" и в данный момент
//эта информация бесполезна и игрой полностью игнорируется, но мы можем прочитать хотя-бы заголовки:
	print "\nПрочитано: $mapHandler\n";
	print "Размер файла карты: $mapLen\n";
	print "Найден старый psZoneHeader\n";
	print "version: ".unpack('v', fread($file_handler, 2))[1]."\n";
	print "numZones: ".unpack('v', fread($file_handler, 2))[1]."\n";
	print "numEquivZones: ".unpack('v', fread($file_handler, 2))[1]."\n";
	print "pad: ".unpack('v', fread($file_handler, 2))[1]."\n";
	//Проверим где указатель сейчас
	$mapHandler = ftell($file_handler);
	//Выводим информацию, сколько осталось непрочитанных байт файла
	print "Осталось ".($mapLen-$mapHandler)." байт, игнорируем\n\n";
	
//	парсить мы их не будем, просто пропустим и закроем файл недочитанным до конца.
}

//Выводим информацию.
//Заголовок как есть
print "Header: $header\n";
//Версия данных ландшафта
print "Terrain Version: $ver\n";
//Размер карты
print "Map Size: $width x $length\n";
//Версия данных gateway
print "Gateway Version: $g_ver\n";
//Кол-во gateway
print "Gateway num: $g_num\n";

//Закрываем файл.
fclose($file_handler);
//Очищаем ненужные более переменные, оставляем только созданные массивы и некоторые параметры карты, такие как размер и кол-во gateway
unset($file_handler, $mapHandler, $mapLen, $x1, $y1, $x2, $y2, $header, $ver, $g_ver, $terrain, $texture, $tileInfo, $tileHeight, $typesTerr, $tileMask);

//-------\\
//РИСУЕМ СХЕМУ
//Тут я буду использовать модуль работы с графикой GD для PHP5.

// Создаём изображение размером в карту, где один пиксель изображения будет равен одной плитке карты.
$gd = imagecreatetruecolor($width, $length);

//Вновь запускаем цикл аналогичный чтению ландшафта, только на этот раз мы будем рисовать всё, что прочитали.
//(конечно код можно было оптимизировать, и в предыдущем цикле сразу применить рисование в GD, но для примера так будет нагляднее)
for($i=0; $i < $width*$length; $i++){
//	Используя математику переводим порядковое исчесление в координаты плоскости ;)
//	Вычесляем позицию X и Y на данный момент цикла
	$x = $i % $width;
	$y = floor($i / $width);
	
//	По приоритету сначала смотрим, есть ли на данной позиции плитка горного хребта, если есть рисуем жёлтый пиксель и переходим к сл. циклу
	if(@$cliff[$i]){
		imagesetpixel($gd, $x, $y, imagecolorallocate($gd, 255, 255, 0));
		continue;
	}
	
//	Так же проверяем воду, рисуем пиксель синего оттенка, возьмём схематичный цвет из самой игры, и переходим к сл. циклу
	if(@$water[$i]) { 
		imagesetpixel($gd, $x, $y, imagecolorallocate($gd, 63,104,154)); 
		continue; 
	}
	
//	Если выше на данной позиции нет интересующих нас плиток, то рисуем карту высоты
//	Т.к. высота обозначена от 0 до 255, то трудностей тут не будет, просто берём номер высоты
	$h = $height[$i];
//	И рисуем его как три канала цвета RGB, от чего получится 255 оттенков серого %)
	imagesetpixel($gd, $x, $y, imagecolorallocate($gd, $h, $h, $h));
}

//Осталось нарисовать поверх gateway
//Сделаем всё красиво, пусть наш gateway имеет ярко обозначенные точки начала и конца, и линию с другим оттенком соединяющие эти две точки
//Цвет для внутренней соединяющей линии (светло-красный)
$color1 = imagecolorallocate($gd, 255,128,128);
//Цвет для начало и конца gateway (красный)
$color2 = imagecolorallocate($gd, 255,0,0);

//Запускаем отдельный цикл для gateway
//тк. они уже имеют координаты плоскости, просто наносим их на имеющуюся картинку
for($i = 0; $i < $g_num; $i++){
	
//	Отдельно берём координаты gateway из массива
	list($x1, $y1, $x2, $y2) = $gateways[$i];
	
//	Рисуем линию gateway светло-красным
	imageline($gd, $x1, $y1, $x2, $y2 , $color1);
//	Рисуем точки начала и конца gateway красным
	imagesetpixel($gd, $x1, $y1, $color2);
	imagesetpixel($gd, $x2, $y2, $color2);
}

//Очищаем переменные
unset($cliff, $water, $color1, $color2, $x, $x1, $x2, $y, $y1, $y2, $i, $h);


//Тут будет вставка для чтения структур, дроидов и объектов


//сохраняем нарисованную картинку
imagepng($gd, "output.png");

//Очищаем указатели на модуль с графикой
imagedestroy($gd);


Можно скопировать весь код и вставить в файл php, и запустить на чтение любой карты.
После чтения карты 2c-Roughness данным скриптом php, на stdout я получил следующую информацию о карте:

Header: ttyp
Version: 8
Number: 90

Header: map 
Terrain Version: 10
Map Size: 96 x 96
Gateway Version: 1
Gateway num: 6

А так же рядом создался файл с рисунком output.png, который выглядит так:

Это увеличенная копия в 4 раза, тут мы видим общую карту высот, тёмные участки - это низины, светлые - вершины
Жёлтые границы - это горные непроходимые вершины и склоны, синеватого оттенка - вода, обычно она в низинах, но не всегда
И видим gateway, как горизонтальные, так и вертикальные. Тут их всего 6 штук по 3 с каждой стороны.
Ландшафт был успешно прочитан простым скриптом на php.



Чтение объектов JSON


Современный движок игры (3.2+, 3.3+) использует базу данных формата JSON для игровых объектов,
поэтому сейчас мы разберём как отрисовать объекты на уже имеющуюся схему.
Мы продолжим редактировать уже готовый код, начнём встраивать строки перед отрисовкой схемы GD модулем,
это строка 318, именно тут будем добавлять новый код.

Чтение структур


//--Структуры--\\

//Опрделим постройки, которые больше одной плитки:

//Массив содержащий самые большие постройки 3х3 плитки
$structPlayer3x3 = array(
"A0LightFactory",			//Завод
"A0VTolFactory1"			//СВВП-завод (VTOL)
);

//Массив содержащий среднии постройки 2х2 плитки
$structPlayer2x2 = array(
"A0CommandCentre",			//КЦ
"A0PowerGenerator",			//Генератор
"A0ResearchFacility",		//Лаборатория
"A0Sat-linkCentre",			//Спутник-Сенсор
"A0LasSatCommand",			//Спутник-Лазер
"X-Super-Cannon",			//Крепость
"X-Super-MassDriver",		//Крепость
"X-Super-Missile",			//Крепость
"X-Super-Rocket",			//Крепость
"A0ComDroidControl"			//Ретранслятор коммандных юнитов
);

//Единственная нестандартная постройка 1x2
$structPlayer1x2 = array(
"A0CyborgFactory"			//Киборг-завод
);


//--Активные постройки--\\
//Только "управляемые" постройки игроков и мусорщиков
//Заброшенные города сюда не входят

//Открываем на чтение struct.json и декодируем базу данных JSON в массив
$struct = json_decode(file_get_contents("$map/struct.json"));


//Предварительно определяем цветовую гамму нашей схемы для кажого элемента
$colPlayer = imagecolorallocate($gd, 0, 255, 0);	//Игроки - Зелёный
$colScav = imagecolorallocate($gd, 192, 0, 192);	//Мусорщики - тёмно-розовый
$colOil = imagecolorallocate($gd, 0, 64, 255);		//Занятая нефтеточка - тёмно-синий

//Цикл по каждому объекту в нашей базе данных
foreach ($struct as $struct_id => $struct_data) {
	
	//У каждого объекта ВСЕГДА есть позиция
	//позиция хранится в абсолютных координатах, а не в координатах плит.
	//Как я уже писал выше, плитка имеет внутреннии коориданы 128x128
	//поэтому определить координату самой плитки делением на 128 - не составит труда
	//Т.к. объекты размещаются на границе плиток,
	//мы немного сместим на один шаг назад,
	//что бы наша растровая схема имела привичный вид
	//и каждый пиксель ложился именно на "плитку", а не на границу
	$x = (ceil($struct_data->{'position'}[0]/128)-1);
	$y = (ceil($struct_data->{'position'}[1]/128)-1);
	
	//Далее будут условия, при которых мы отрисовываем пиксель на схеме
	
	
	//Условие: Ищем большие заводы из массива, и рисуем на схеме
	if(in_array($struct_data->{'name'}, $structPlayer3x3)){
		//Рисуем прямоугольник
		imagerectangle($gd, $x-1, $y-1, $x+1, $y+1, $colPlayer); //Здания 3x3 расстягиваются в оба направления от точки постановки
		//Заолняем центр пикселем тоже, что бы не было "бубликов"
		imagesetpixel($gd, $x, $y, $colPlayer);
		//Дальше незачем бежать, сразу возвращаемся к началу цикла
		continue;
	}
	
	//Ищем здания 2х2
	if(in_array($struct_data->{'name'}, $structPlayer2x2)){
		imagerectangle($gd, $x, $y, $x+1, $y+1, $colPlayer); //Остальные здания расстягиваются "вперёд" от точки постановки
		continue;
	}
	
	//Ищем здания 2х2
	if(in_array($struct_data->{'name'}, $structPlayer1x2)){
		imagerectangle($gd, $x, $y, $x, $y+1, $colPlayer);
		continue;
	}
	
	
	//Тут продолжим писать условия

	
}

Сразу запустим, и проверим что получилось:

Отлично, всё на своих местах.
Видны 2 завода, 2 киборг-завода и строения, такие как лаборатории и генераторы, но их отличить на этой схеме мы не сможем.
Только если разукрасить в отдельные цвета, но этим заморачиваться мы не будем.
Итак, дополним все интересующие нас условия, в итоге код чтения объектов построек должен выглядеть так:

//--Структуры--\\

//Опрделим постройки, которые больше одной плитки:

//Массив содержащий самые большие постройки 3х3 плитки
$structPlayer3x3 = array(
"A0LightFactory",			//Завод
"A0VTolFactory1"			//СВВП-завод (VTOL)
);

//Массив содержащий среднии постройки 2х2 плитки
$structPlayer2x2 = array(
"A0CommandCentre",			//КЦ
"A0PowerGenerator",			//Генератор
"A0ResearchFacility",		//Лаборатория
"A0Sat-linkCentre",			//Спутник-Сенсор
"A0LasSatCommand",			//Спутник-Лазер
"X-Super-Cannon",			//Крепость
"X-Super-MassDriver",		//Крепость
"X-Super-Missile",			//Крепость
"X-Super-Rocket",			//Крепость
"A0ComDroidControl"			//Ретранслятор коммандных юнитов
);

//Единственная нестандартная постройка 1x2
$structPlayer1x2 = array(
"A0CyborgFactory"			//Киборг-завод
);


//--Активные постройки--\\
//Только "управляемые" постройки игроков и мусорщиков
//Заброшенные города сюда не входят

//Открываем на чтение struct.json и декодируем базу данных JSON в массив
$struct = json_decode(file_get_contents("$map/struct.json"));


//Предварительно определяем цветовую гамму нашей схемы для кажого элемента
$colPlayer = imagecolorallocate($gd, 0, 255, 0);	//Игроки - Зелёный
$colScav = imagecolorallocate($gd, 192, 0, 192);	//Мусорщики - тёмно-розовый
$colOil = imagecolorallocate($gd, 0, 64, 255);		//Занятая нефтеточка - тёмно-синий

//Цикл по каждому объекту в нашей базе данных
foreach ($struct as $struct_id => $struct_data) {
	
	//У каждого объекта ВСЕГДА есть позиция
	//позиция хранится в абсолютных координатах, а не в координатах плит.
	//Как я уже писал выше, плитка имеет внутреннии коориданы 128x128
	//поэтому определить координату самой плитки делением на 128 - не составит труда
	//Т.к. объекты размещаются на границе плиток,
	//мы немного сместим на один шаг назад,
	//что бы наша растровая схема имела привичный вид
	//и каждый пиксель ложился именно на "плитку", а не на границу
	$x = (ceil($struct_data->{'position'}[0]/128)-1);
	$y = (ceil($struct_data->{'position'}[1]/128)-1);
	
	//Ищем большие заводы из массива, и рисуем на схеме
	if(in_array($struct_data->{'name'}, $structPlayer3x3)){
		//Рисуем прямоугольник
		imagerectangle($gd, $x-1, $y-1, $x+1, $y+1, $colPlayer); //Здания 3x3 расстягиваются в оба направления от точки постановки
		//Заолняем центр пикселем тоже, что бы не было "бубликов"
		imagesetpixel($gd, $x, $y, $colPlayer);
		//Дальше незачем бежать, сразу возвращаемся к началу цикла
		continue;
	}
	
	//Ищем здания 2х2
	if(in_array($struct_data->{'name'}, $structPlayer2x2)){
		imagerectangle($gd, $x, $y, $x+1, $y+1, $colPlayer); //Остальные здания расстягиваются "вперёд" от точки постановки
		continue;
	}
	
	//Ищем здания 2х2
	if(in_array($struct_data->{'name'}, $structPlayer1x2)){
		imagerectangle($gd, $x, $y, $x, $y+1, $colPlayer);
		continue;
	}

	//Тут небольшой логический трюк, все остальные объекты,
	//которые имеют параметр "startpos" - принадлежат игроку
	//однако мы не будем рисовать пиксель игрока на предустановленных занятых нефтеточках
	//т.к. нефтеточки мы отобразим отдельным цветом
	if(isset($struct_data->{'startpos'}) && $struct_data->{'name'} != "A0ResourceExtractor"){
		imagesetpixel($gd, $x, $y, $colPlayer);
		continue;
	}
	
	//Тоже самое, отрисовывам все постройки мусорщиков, кроме предустановленных занятых ими нефтеточках
	if(isset($struct_data->{'player'}) && $struct_data->{'player'} == 'scavenger' && $struct_data->{'name'} != "A0ResourceExtractor"){
		imagesetpixel($gd, $x, $y, $colScav);
		continue;
	}
	
	//Любые предустановленные занятые нефтеточки, отрисовываем другим цветом
	//этим же цветом в базе данных feature мы будем рисовать свободные нефтеточки
	if($struct_data->{'name'} == "A0ResourceExtractor"){
		imagesetpixel($gd, $x, $y, $colOil);
		continue;
	}

}

//Завершили работать с базой данных, очищаем все переменные, кроме определённых цветов, они нам ещё понадобятся
unset($struct, $structPlayer3x3, $structPlayer2x2, $structPlayer1x2, $struct_id, $struct_data, $x, $y);

Вот что получилось:

Видны все строения игроков, включая стены.
Видны нефтеточки, в данный момент ТОЛЬКО предустановленно-занятые.
Видны постройки мусорщиков.

Чтение разных объектов (feature)


Продлжаем писать код, там где остановились, после unset(), но так же до модуля отрисовки GD функции imagepng()

//--Различные объекты (feature)--\\

//Открываем на чтение struct.json
$feat = json_decode(file_get_contents("$map/feature.json"));

//Так же предварительно определяем цветовую гамму
$colBarrel = imagecolorallocate($gd, 0, 0, 128);		//Бочка с нефтью - тёмно-тёмно-синий
$colTree = imagecolorallocate($gd, 92, 128, 0);		//Деревья - тёмно-зелёный
$colOther = imagecolorallocate($gd, 92, 64, 0);		//Остальной "мусор" (нейтральные постройки, камни и т.д.) - тёмно-коричневый

//Цикл по каждому объекту в нашей базе данных
foreach ($feat as $feat_id => $feat_data) {

	//Координаты объектов
	$x = (ceil($feat_data->{'position'}[0]/128)-1);
	$y = (ceil($feat_data->{'position'}[1]/128)-1);

	//Отрисовываем нефтеточки
	if($feat_data->{'name'} == 'OilResource') {
		imagesetpixel($gd, $x, $y, $colOil);
		continue;
	}

	//Тут продолжим добавлять условия
	
}

Проверим, что нефтеточки теперь видны везде и одним цветом:

Всё отлично!
Итак, финальный код чтения объектов выглядит так:

//--Различные объекты (feature)--\\

//Открываем на чтение struct.json
$feat = json_decode(file_get_contents("$map/feature.json"));

//Так же предварительно определяем цветовую гамму
$colBarrel = imagecolorallocate($gd, 0, 0, 128);		//Бочка с нефтью - тёмно-тёмно-синий
$colTree = imagecolorallocate($gd, 92, 128, 0);		//Деревья - тёмно-зелёный
$colOther = imagecolorallocate($gd, 92, 64, 0);		//Остальной "мусор" (нейтральные постройки, камни и т.д.) - тёмно-коричневый

//Цикл по каждому объекту в нашей базе данных
foreach ($feat as $feat_id => $feat_data) {

	//Координаты объектов
	$x = (ceil($feat_data->{'position'}[0]/128)-1);
	$y = (ceil($feat_data->{'position'}[1]/128)-1);

	//Отрисовываем нефтеточки
	if($feat_data->{'name'} == 'OilResource') {
		imagesetpixel($gd, $x, $y, $colOil);
		continue;
	}
	
	//По стандарту, деревья начинаются на имя "Tree", с заглавной первой буквой
	//поэтому мы проверяем первые 4 байта, и рисуем деревья
	//они полезны на схеме, могут показать, где автором карты
	//были закрыты те или иные проходы
	//Но объект дерева можно переназначить, и назвать по другому,
	//тогда это условие не отработает, но мы не будем заморачиваться
	if(substr($feat_data->{'name'}, 0, 4) == 'Tree'){
		imagesetpixel($gd, $x, $y, $colTree);
		continue;
	}

	//Отрисуем так же предустановленные бочки с нефтью
	if($feat_data->{'name'} == 'OilDrum'){
		imagesetpixel($gd, $x, $y, $colBarrel);
		continue;
	}
	
	//Всё остальное, что не деревья, мы отрисуем немного другим цветом,
	//ведь мы не заморачиваемся ;)
	imagesetpixel($gd, $x, $y, $colOther);
	
}

//С базой закончили, очищаем переменные
unset($feat, $feat_id, $feat_data, $x, $y);

Вот что получилось:

Теперь мы видим все нефтеточки, занятые и не занятые одним цветом.
Видим все деревья, и проходы которые они перекрывают.
Так же видим прочий "мусор", особенно на больших базах мусорщиков.

Чтение дроидов


//--Дроиды--\\

//Открываем на чтение базу droid.json
$droid = json_decode(file_get_contents("$map/droid.json"));

//Определяем цвет для армии мусорщиков
$colScavDroid = imagecolorallocate($gd, 255, 64, 255);	//Мусорщики - розовый

//Цикл по каждому объекту в нашей базе данных
foreach ($droid as $droid_id => $droid_data) {

	//Координаты объектов
	$x = (ceil($droid_data->{'position'}[0]/128)-1);
	$y = (ceil($droid_data->{'position'}[1]/128)-1);
	
	//Строителей отрисуем цветом игрока
	//в игровых статах, использются оба имени шаблона, 
	//и даже во встроенных картах используются шаблоны то ConstructorDroid то ConstructionDroid
	//учитываем эти костыли и мы тут.
	if(isset($droid_data->{'startpos'}) && ($droid_data->{'template'} == 'ConstructorDroid' || $droid_data->{'template'} == 'ConstructionDroid') ) {
		imagesetpixel($gd, $x, $y, $colPlayer);
		continue;
	}
	
	//Так же отрисуем остальное войско цветом игрока
	//Примечание: условие про строителей можно или вообще закомментировать,
	//т.к. тут мы используем тот же цвет, или использовать ТУТ для остальных
	//дроидов игрока(армии) другой цвет
	if(isset($droid_data->{'startpos'})){
		imagesetpixel($gd, $x, $y, $colPlayer);
		continue;
	}
	
	//Дроиды мусорщиков
	if(isset($droid_data->{'player'}) && $droid_data->{'player'} == 'scavenger'){
		imagesetpixel($gd, $x, $y, $colScavDroid);
		continue;
	}
	
}

//С базой закончили, очищаем переменные
unset($droid, $droid_id, $droid_data, $x, $y);

//Очещаем предустановленные цвета
unset($colPlayer, $colScav, $colOil, $colBarrel, $colTree, $colOther, $colScavDroid);

Завершённая схема выглядит так:

2c-roughness


2c-startup


4c-pyramidal



Полный код:


//Путь к каталогу ресурсам карты
$map = $argv[1];

//-------\\
//ЧТЕНИЕ ТИПОВ ЛАНДШАФТА
// ttypes.ttp
//-------\\

//Узнаём какой размер файла ttypes.ttp
$ttypLen = filesize("$map/ttypes.ttp");

//Открываем на чтение ttypes.ttp, структуру типов ландшафта
$file_handler = fopen("$map/ttypes.ttp", 'r');

//Читаем заголовок, первые 4 байта файла, у файла ttypes.ttp он всегда будет "ttyp"
$header = fread($file_handler, 4);

//Если заголовок не удалось прочитать, что-то пошло не так, останов.
if($header != "ttyp") {
	print "Неверный файл типов ландшафта ttypes.ttp";
	fclose($file_handler);
	exit();
}

//Если заголовок верен, значит мы уверены что открыли нужный файл, читаем его по плану
//Далее чтение будет происходить в бинарном режиме со смещением указателя внутри файла (автоматически)

//Читаем первый после заголовка без-знаковый long 32 бита, порядок little endian (u32le, 4 байта), это версия файла/карты
//и распаковываем его средствами самого php
$ver = unpack('V', fread($file_handler, 4))[1];

//Читаем второй u32le, тут содержится информация о количестве типов ландшафта
//эта информация нам понадобится что бы в цикле считать всю структуру.
$num = unpack('V', fread($file_handler, 4))[1];

//Создаём массив куда сохраним структуру типов ландшафта
$terrainTypes = array();

//Запускаем цикл, до количества типов, которое прочитали выше
for($i=0;$i<$num;$i++){

//	Заполняем массив читая по без-знаковому 16 битному, порядок little endian (2 байта)
	$terrainTypes[$i] = unpack('v', fread($file_handler, 2))[1];
}

//Выводим информацию.
//Заголовок как есть
print "Header: $header\n";
//Версия файла
print "Version: $ver\n";
//Кол-во типов
print "Number: $num\n\n";

//Получаем информацию, на какой позиции файла у нас находится указатель
//Если всё верно прочитали, указатель должен находиться в конце файла.
$ttypHandler = ftell($file_handler);

//После того, как цикл завершился мы проверяем, весь ли файл был прочитан
if($ttypHandler != $ttypLen){

//	Если указатель не в конце файла, что-то пошло не так, останов.
	print "Прочитано: $ttypHandler\n";
	print "Размер файла: $ttypLen\n";
	fclose($file_handler);
	exit();
}

//Закрываем файл.
fclose($file_handler);

//Очищаем старые переменные, кроме $map и $terrainTypes, само собой они нам будут нужны при чтении самого ландшафта.
unset($file_handler, $ttypHandler, $ttypLen, $header, $ver, $num);

//-------\\
//ЧТАНИЕ ЛАНДШАФТА\\
// game.map
//-------\\

//Узнаём какой размер файла game.map
$mapLen = filesize("$map/game.map");

//Открываем на чтение game.map
$file_handler = fopen("$map/game.map", 'r');

//Читаем заголовок, первые 4 байта файла, у файла game.map он всегда будет "map "
$header = fread($file_handler, 4);

//Если заголовок не удалось прочитать, что-то пошло не так, останов.
//Заголовок состоит из 4 байт "map " пробел в конце является частью заголовка
if($header != "map "){
	print "Неверный файл ландшафта game.map";
	fclose($file_handler);
	exit();
}

//Так же читем первый после заголовка u32le(4 байта), это версия файла/карты
$ver = unpack('V', fread($file_handler, 4))[1];

//Ландшафт состоит из плиток, величина одной плитки равна одной структуре в игре.
//Например нефтевышка занимает целиком всю ландшафтную плитку.
//Лаборатория занимает 4 плитки размер 2 на 2.
//Завод занимает 9 плиток, размером 3 на 3.
//Высота плитки указывается высотой координаты левого верхнего угла плитки
//Далее плитка делится на треугольники и высоты сглаживаются движком игры.
//Разрешение координат внутри одной плитки составляют 128x128

//Следующие 8 байт хранят в себе информацию о размерах сетки ландшафта в плитках

//Читаем ширину карты (по "x", слева направо)
$width  = unpack('V', fread($file_handler, 4))[1];
//Читаем длину карты (по "y", сверху вниз)
$length = unpack('V', fread($file_handler, 4))[1];

//Маска битового сдвига для получении текстуры и типа ландшафта.
//Данная информация прописана в коде игры map.h
//Там также прописаны и другие маски, но мы их не берём в данном примере за ненадобностью.
$tileMask = 0x01ff;

//Тип ландшафта
//Данная информация прописана в коде игры map.h, и не настраиваемая (hardcoded)
$typesTerr = array(
"sand",
"sandybrush",
"bakedearth",
"greenmud",
"redbrush",
"pinkrock",
"road",
"water",
"clifface",
"rubble",
"sheetice",
"slush",
"max"
);

//Создаём массивы для наполнения
//Массив для водных плиток
$water = array();
//Массив для непроходимых горных плиток
$cliff = array();
//Массив для карты высот
$height = array();

//Далее идёт вторая часть файла game.map

//Получив информацию по количеству плиток в ширину и длину,
//запускаем цикл который пробежит по каждой плитке читая на ходу из файла.
for($i=0; $i < $width*$length; $i++){

//	Бинарная структура файла разбита по 3 байта на плитку и выглядит следующим образом:
//	2 байта самые информативные, в которых путём побитового сдвига содержатся:
//	информация о плитке, выравнивание по x, выравнивание по y, маска поворота, сдвиг поворота, направление деления плитки на треугольники, текстура плитки
//	в данном примере, нам нужны биты отвечающие за номер текстуры "$tileMask", другие маски не берём
//	1 байт - высота плитки (по "z", с глубины на поверхность)
//	Плитки указаны поочерёдно, без координат плоскости.


//	Читаем информацию о плитке, распаковываем как u16le
	$tileInfo = unpack('v', fread($file_handler, 2))[1];
//	Из прочитанных байт получаем информацию о текстуре путём сдвига бит по маске
	$texture = ($tileInfo & $tileMask);
//	Используя массив структуры типов из файла ttypes.ttp получаем тип ландшафта по текстуре
	$terrain = $terrainTypes[$texture];
	
//	Читаем информацию о высоте плитки
//	Т.к. это 8-бит, высота может быть от 0 до 255 (очень легко будет нарисовать карту высот)
	$tileHeight = unpack('C', fread($file_handler, 1))[1];


//	Заполняем массивы интересующей нас информацией
//	Если тип плитки соответствует воде
	if($typesTerr[$terrain] == "water") $water[$i] = true;
	
//	Если тип плитки соответствует горной вершине
	if($typesTerr[$terrain] == "clifface") $cliff[$i] = true;
	
//	Записываем высоту каждой плитки
//	Координата высоты указывает на высоту не всей плитки, а её левого верхнего угла
//	а конкретнее треугольника, левого верхнего угла плитки, на которые плитка поделится перед отрисовкой.
//	Т.к. плитка может иметь углы разной высоты.
	$height[$i] = $tileHeight;

}



//Далее идёт третья часть файла game.map, в котором содержится информация о gateway
//Продолжаем читать файл там, где остановился указатель после цикла.

//Получаем версию информации о gateway
$g_ver = unpack('V', fread($file_handler, 4))[1];
//Получаем кол-во gateway на карте
$g_num = unpack('V', fread($file_handler, 4))[1];

//Создаём массив для объектов gateway
$gateways = array();

//Создаём цикл по кол-ву gateway
for($i=0;$i<$g_num;$i++){

//	Бинарная структура разбита по 4 байта, которые указывают начало и конец gateway в координатах плоскости (x1, y1 - x2, y2)
//	gateway могут быть только строго линиями, горизонтальными или вертикальными.
	$x1  = unpack('C', fread($file_handler, 1))[1];
	$y1  = unpack('C', fread($file_handler, 1))[1];
	$x2  = unpack('C', fread($file_handler, 1))[1];
	$y2  = unpack('C', fread($file_handler, 1))[1];
	
//	Заполняем массив gateways, массивом координат каждой gateway
	$gateways[] = array($x1,$y1,$x2,$y2);
}


//После всего получаем информацию, на какой позиции файла у нас находится указатель
//Если всё верно прочитали, указатель должен находиться в конце файла.
$mapHandler = ftell($file_handler);

//После того, как все циклы завершились мы проверяем, весь ли файл был прочитан
if($mapHandler != $mapLen){

//	Если указатель не в конце файла, значит мы наткнулись на очень старый формат карты.
//Такие как sk-rush, sk-rush2, sk-cockpit и т.д.,
//в которых осталась информация о заголовках зонирований(версии игр 2.3), их было две версии "1" и "2"
//так как мы читаем карты в формате .wz, то скорее всего остатки будут версии "2" и в данный момент
//эта информация бесполезна и игрой полностью игнорируется, но мы можем прочитать хотя-бы заголовки:
	print "\nПрочитано: $mapHandler\n";
	print "Размер файла карты: $mapLen\n";
	print "Найден старый psZoneHeader\n";
	print "version: ".unpack('v', fread($file_handler, 2))[1]."\n";
	print "numZones: ".unpack('v', fread($file_handler, 2))[1]."\n";
	print "numEquivZones: ".unpack('v', fread($file_handler, 2))[1]."\n";
	print "pad: ".unpack('v', fread($file_handler, 2))[1]."\n";
	//Проверим где указатель сейчас
	$mapHandler = ftell($file_handler);
	//Выводим информацию, сколько осталось непрочитанных байт файла
	print "Осталось ".($mapLen-$mapHandler)." байт, игнорируем\n\n";
	
//	парсить мы их не будем, просто пропустим и закроем файл недочитанным до конца.
}

//Выводим информацию.
//Заголовок как есть
print "Header: $header\n";
//Версия данных ландшафта
print "Terrain Version: $ver\n";
//Размер карты
print "Map Size: $width x $length\n";
//Версия данных gateway
print "Gateway Version: $g_ver\n";
//Кол-во gateway
print "Gateway num: $g_num\n";

//Закрываем файл.
fclose($file_handler);
//Очищаем ненужные более переменные, оставляем только созданные массивы и некоторые параметры карты, такие как размер и кол-во gateway
unset($file_handler, $mapHandler, $mapLen, $x1, $y1, $x2, $y2, $header, $ver, $g_ver, $terrain, $texture, $tileInfo, $tileHeight, $typesTerr, $tileMask);

//-------\\
//РИСУЕМ СХЕМУ
//Тут я буду использовать модуль работы с графикой GD для PHP5.

// Создаём изображение размером в карту, где один пиксель изображения будет равен одной плитке карты.
$gd = imagecreatetruecolor($width, $length);

//Вновь запускаем цикл аналогичный чтению ландшафта, только на этот раз мы будем рисовать всё, что прочитали.
//(конечно код можно было оптимизировать, и в предыдущем цикле сразу применить рисование в GD, но для примера так будет нагляднее)
for($i=0; $i < $width*$length; $i++){
//	Используя математику переводим порядковое исчесление в координаты плоскости ;)
//	Вычесляем позицию X и Y на данный момент цикла
	$x = $i % $width;
	$y = floor($i / $width);
	
//	По приоритету сначала смотрим, есть ли на данной позиции плитка горного хребта, если есть рисуем жёлтый пиксель и переходим к сл. циклу
	if(@$cliff[$i]){
		imagesetpixel($gd, $x, $y, imagecolorallocate($gd, 255, 255, 0));
		continue;
	}
	
//	Так же проверяем воду, рисуем пиксель синего оттенка, возьмём схематичный цвет из самой игры, и переходим к сл. циклу
	if(@$water[$i]) { 
		imagesetpixel($gd, $x, $y, imagecolorallocate($gd, 63,104,154)); 
		continue; 
	}
	
//	Если выше на данной позиции нет интересующих нас плиток, то рисуем карту высоты
//	Т.к. высота обозначена от 0 до 255, то трудностей тут не будет, просто берём номер высоты
	$h = $height[$i];
//	И рисуем его как три канала цвета RGB, от чего получится 255 оттенков серого %)
	imagesetpixel($gd, $x, $y, imagecolorallocate($gd, $h, $h, $h));
}

//Осталось нарисовать поверх gateway
//Сделаем всё красиво, пусть наш gateway имеет ярко обозначенные точки начала и конца, и линию с другим оттенком соединяющие эти две точки
//Цвет для внутренней соединяющей линии (светло-красный)
$color1 = imagecolorallocate($gd, 255,128,128);
//Цвет для начало и конца gateway (красный)
$color2 = imagecolorallocate($gd, 255,0,0);

//Запускаем отдельный цикл для gateway
//тк. они уже имеют координаты плоскости, просто наносим их на имеющуюся картинку
for($i = 0; $i < $g_num; $i++){
	
//	Отдельно берём координаты gateway из массива
	list($x1, $y1, $x2, $y2) = $gateways[$i];
	
//	Рисуем линию gateway светло-красным
	imageline($gd, $x1, $y1, $x2, $y2 , $color1);
//	Рисуем точки начала и конца gateway красным
	imagesetpixel($gd, $x1, $y1, $color2);
	imagesetpixel($gd, $x2, $y2, $color2);
}

unset($cliff, $water, $color1, $color2, $x, $x1, $x2, $y, $y1, $y2, $i, $h);

//-------\\
//ЧТАНИЕ ОБЪЕКТОВ\\
//--Структуры--\\

//Опрделим постройки, которые больше одной плитки:

//Массив содержащий самые большие постройки 3х3 плитки
$structPlayer3x3 = array(
"A0LightFactory",			//Завод
"A0VTolFactory1"			//СВВП-завод (VTOL)
);

//Массив содержащий среднии постройки 2х2 плитки
$structPlayer2x2 = array(
"A0CommandCentre",			//КЦ
"A0PowerGenerator",			//Генератор
"A0ResearchFacility",		//Лаборатория
"A0Sat-linkCentre",			//Спутник-Сенсор
"A0LasSatCommand",			//Спутник-Лазер
"X-Super-Cannon",			//Крепость
"X-Super-MassDriver",		//Крепость
"X-Super-Missile",			//Крепость
"X-Super-Rocket",			//Крепость
"A0ComDroidControl"			//Ретранслятор коммандных юнитов
);

//Единственная нестандартная постройка 1x2
$structPlayer1x2 = array(
"A0CyborgFactory"			//Киборг-завод
);


//--Активные постройки--\\
//Только "управляемые" постройки игроков и мусорщиков
//Заброшенные города сюда не входят

//Открываем на чтение struct.json и декодируем базу данных JSON в массив
$struct = json_decode(file_get_contents("$map/struct.json"));


//Предварительно определяем цветовую гамму нашей схемы для кажого элемента
$colPlayer = imagecolorallocate($gd, 0, 255, 0);	//Игроки - Зелёный
$colScav = imagecolorallocate($gd, 192, 0, 192);	//Мусорщики - тёмно-розовый
$colOil = imagecolorallocate($gd, 0, 64, 255);		//Занятая нефтеточка - тёмно-синий

//Цикл по каждому объекту в нашей базе данных
foreach ($struct as $struct_id => $struct_data) {
	
	//У каждого объекта ВСЕГДА есть позиция
	//позиция хранится в обсалютных координатах, а не в координатах плит.
	//Как я уже писал выше, плитка имеет внутреннии коориданы 128x128
	//поэтому определить координату самой плитки делением на 128 - не составит труда
	//Т.к. объекты размещаются на границе плиток,
	//мы немного сместим на один шаг назад,
	//что бы наша растровая схема имела привичный вид
	//и каждый пиксель ложился именно на "плитку", а не на границу
	$x = (ceil($struct_data->{'position'}[0]/128)-1);
	$y = (ceil($struct_data->{'position'}[1]/128)-1);
	
	//Ищем большие заводы из массива, и рисуем на схеме
	if(in_array($struct_data->{'name'}, $structPlayer3x3)){
		//Рисуем прямоугольник
		imagerectangle($gd, $x-1, $y-1, $x+1, $y+1, $colPlayer); //Здания 3x3 расстягиваются в оба направления от точки постановки
		//Заолняем центр пикселем тоже, что бы не было "бубликов"
		imagesetpixel($gd, $x, $y, $colPlayer);
		//Дальше незачем бежать, сразу возвращаемся к началу цикла
		continue;
	}
	
	//Ищем здания 2х2
	if(in_array($struct_data->{'name'}, $structPlayer2x2)){
		imagerectangle($gd, $x, $y, $x+1, $y+1, $colPlayer); //Остальные здания расстягиваются "вперёд" от точки постановки
		continue;
	}
	
	//Ищем здания 2х2
	if(in_array($struct_data->{'name'}, $structPlayer1x2)){
		imagerectangle($gd, $x, $y, $x, $y+1, $colPlayer);
		continue;
	}

	//Тут небольшой логический трюк, все остальные объекты,
	//которые имеют параметр "startpos" - принадлежат игроку
	//однако мы не будем рисовать пиксель игрока на предустановленных занятых нефтеточках
	//т.к. нефтеточки мы отобразим отдельным цветом
	if(isset($struct_data->{'startpos'}) && $struct_data->{'name'} != "A0ResourceExtractor"){
		imagesetpixel($gd, $x, $y, $colPlayer);
		continue;
	}
	
	//Тоже самое, отрисовывам все постройки мусорщиков, кроме предустановленных занятых ими нефтеточках
	if(isset($struct_data->{'player'}) && $struct_data->{'player'} == 'scavenger' && $struct_data->{'name'} != "A0ResourceExtractor"){
		imagesetpixel($gd, $x, $y, $colScav);
		continue;
	}
	
	//Любые предустановленные занятые нефтеточки, отрисовываем другим цветом
	//этим же цветом в базе данных feature мы будем рисовать свободные нефтеточки
	if($struct_data->{'name'} == "A0ResourceExtractor"){
		imagesetpixel($gd, $x, $y, $colOil);
		continue;
	}

}

//Завершили работать с базой данных, очищаем все переменные, кроме определённых цветов, они нам ещё понадобятся
unset($struct, $structPlayer3x3, $structPlayer2x2, $structPlayer1x2, $struct_id, $struct_data, $x, $y);

//--Различные объекты (feature)--\\

//Открываем на чтение struct.json
$feat = json_decode(file_get_contents("$map/feature.json"));

//Так же предварительно определяем цветовую гамму
$colBarrel = imagecolorallocate($gd, 0, 0, 128);		//Бочка с нефтью - тёмно-тёмно-синий
$colTree = imagecolorallocate($gd, 92, 128, 0);		//Деревья - тёмно-зелёный
$colOther = imagecolorallocate($gd, 92, 64, 0);		//Остальной "мусор" (нейтральные постройки, камни и т.д.) - тёмно-коричневый

//Цикл по каждому объекту в нашей базе данных
foreach ($feat as $feat_id => $feat_data) {

	//Координаты объектов
	$x = (ceil($feat_data->{'position'}[0]/128)-1);
	$y = (ceil($feat_data->{'position'}[1]/128)-1);

	//Отрисовываем нефтеточки
	if($feat_data->{'name'} == 'OilResource') {
		imagesetpixel($gd, $x, $y, $colOil);
		continue;
	}
	
	//По стандарту, деревья начинаются на имя "Tree", с заглавной первой буквой
	//поэтому мы проверяем первые 4 байта, и рисуем деревья
	//они полезны на схеме, могут показать, где автором карты
	//были закрыты те или иные проходы
	//Но объект дерева можно переназначить, и назвать по другому,
	//тогда это условие не отработает, но мы не будем заморачиваться
	if(substr($feat_data->{'name'}, 0, 4) == 'Tree'){
		imagesetpixel($gd, $x, $y, $colTree);
		continue;
	}

	//Отрисуем так же предустановленные бочки с нефтью
	if($feat_data->{'name'} == 'OilDrum'){
		imagesetpixel($gd, $x, $y, $colBarrel);
		continue;
	}
	
	//Всё остальное, что не деревья, мы отрисуем немного другим цветом,
	//ведь мы не заморачиваемся ;)
	imagesetpixel($gd, $x, $y, $colOther);
	
}

//С базой закончили, очищаем переменные
unset($feat, $feat_id, $feat_data, $x, $y);


//--Дроиды--\\

//Открываем на чтение базу droid.json
$droid = json_decode(file_get_contents("$map/droid.json"));

//Определяем цвет для армии мусорщиков
$colScavDroid = imagecolorallocate($gd, 255, 64, 255);	//Мусорщики - розовый

//Цикл по каждому объекту в нашей базе данных
foreach ($droid as $droid_id => $droid_data) {

	//Координаты объектов
	$x = (ceil($droid_data->{'position'}[0]/128)-1);
	$y = (ceil($droid_data->{'position'}[1]/128)-1);
	
	//Строителей отрисуем цветом игрока
	//в игровых статах, использются оба имени шаблона, 
	//и даже во встроенных картах используются шаблоны то ConstructorDroid то ConstructionDroid
	//учитываем эти костыли и мы тут.
	if(isset($droid_data->{'startpos'}) && ($droid_data->{'template'} == 'ConstructorDroid' || $droid_data->{'template'} == 'ConstructionDroid') ) {
		imagesetpixel($gd, $x, $y, $colPlayer);
		continue;
	}
	
	//Так же отрисуем остальное войско цветом игрока
	//Примечание: условие про строителей можно или вообще закомментировать,
	//т.к. тут мы используем тот же цвет, или использовать ТУТ для остальных
	//дроидов игрока(армии) другой цвет
	if(isset($droid_data->{'startpos'})){
		imagesetpixel($gd, $x, $y, $colPlayer);
		continue;
	}
	
	//Дроиды мусорщиков
	if(isset($droid_data->{'player'}) && $droid_data->{'player'} == 'scavenger'){
		imagesetpixel($gd, $x, $y, $colScavDroid);
		continue;
	}
	
}

//С базой закончили, очищаем переменные
unset($droid, $droid_id, $droid_data, $x, $y);

//Очещаем предустановленные цвета
unset($colPlayer, $colScav, $colOil, $colBarrel, $colTree, $colOther, $colScavDroid);

//сохраняем нарисованную картинку
imagepng($gd, "output.png");
//Очищаем указатели на модуль с графикой
imagedestroy($gd);

Чтение объектов в бинарном формате



...продолжение следует

Составление картографии текстурами



...
Затравочка ;)

2c-startup


4c-pyramidal