Форум: "Игры";
Текущий архив: 2004.10.17;
Скачать: [xml.tar.bz2];
ВнизGLScene как сделать консоль!! Найти похожие ветки
← →
FRick © (2004-05-31 13:32) [0]Во многих играх при нажатие на "~" вылазиет командная строка
гне можно вести код, кто знает помогите!!
← →
Iraizor © (2004-05-31 15:38) [1]ууу, это огромный кусок кода, тема для статьи ... тем более нужно в консоль команды забивать , ты думал это просто какую-то процедуру нужно вызавть ? это все пишется руками.... так что поизучай ddraw и dinput. и задавай уже более конкретные вопросы
← →
NailMan © (2004-05-31 15:49) [2]Не знаю как на GLScene, но на Директ3Д реализация визуализауии пишется просто, но с оговорками, а вот все остальное - интеграция интерфеса консоли в движок, влияние на различные параметры, распознавание команд и т.д., имхо, самая большая часть консоли.
У меня вот консоль реализована довольно стандартно, но до этого пришлось писать все элемены GUI, которые юзались в консоли, да и универсальный экзекутор команд немерянно весит в строках кода.
В принципе с графической частью можно не заморачиваться и сделать кончсоль в виде текста поверх основной картинки. Надо только описать типа TListBox, но виртуальный, т.е. элементов нет, а текст рендерящийся есть.
---
WBR, NailMan aka 2:5020/3337.13
← →
FRick © (2004-05-31 17:05) [3]"В принципе с графической частью можно не заморачиваться и сделать консоль в виде текста поверх основной картинки"
Мне имено надо заморочится с графикой!
А так я уже делал только выглядит это неочень красиво!
← →
Poison man (2004-05-31 17:05) [4]Я, честно сказать, не спец по консолям - ни разу их не делал... Но, понимая, что и мне когда-нибудь придется столкнуться с этой проблемой, выработал такое решение. Но не знаю, как оно вам понравится :)
Сама консоль в GLScene делается спрайтом, т.к. он постоянно "лицом к камере". На него текстурку для красоты какую-нить... Затем сам текст. Отслеживается нажатие всех клавиш в какой-нить проце, с помощью кой-каких примочек (см. примеры в GLScene) вводимый текст преобразуется в битмаповый (вроде бы). Его текстурой на спрайт поверх основного фона. Вот и вся графика. Главное за перерисовкой проследить.
А сама рабочая часть консоли выглядит в виде файла со списком команд, которые предусмотрены в игре. А затем компилятор проверяет команду, ее значение, и выставляет ее в игре.
← →
FRick © (2004-05-31 17:24) [5]Вот конкретный вопрос: например я запишу все команды в
console.ini в Stalker"е это так:
ai_dbg_brain off
ai_dbg_frustum off
ai_dbg_motion off
ai_debug off
unbindall
bind quit kESCAPE
bind wpn_1 k1
bind wpn_2 k2
bind wpn_3 k3
bind wpn_4 k4
bind wpn_5 k5
bind wpn_6 k6
bind wpn_7 k7
bind wpn_8 k8
bind wpn_9 k9
bind scores kTAB
bind wpn_next kQ
bind forward kW
bind use kE
bind wpn_reload kR
bind lstrafe kA
bind back kS
bind rstrafe kD
bind accel kLSHIFT
bind crouch kC
bind jump kSPACE
bind cam_1 kF1
bind cam_2 kF2
bind cam_3 kF3
bind cam_4 kF4
bind cam_zoom_out kSUBTRACT
bind cam_zoom_in kADD
bind screenshot kF12
bind up kUP
bind left kLEFT
bind right kRIGHT
bind down kDOWN
bind wpn_fire mouse1
bind wpn_zoom mouse2
cam_inert 1.00000
cam_slide_inert 0.00000
cl_act_depth 6
cl_act_stuck_depth 6
g_always_run on
g_god off
hud_crosshair on
hud_crosshair_dist off
hud_draw on
hud_fov 0.50000
hud_info off
hud_weapon on
mouse_invert off
mouse_sens 0.21000
mt_input off
mt_sound on
net_cl_pending_lim 2
net_cl_update_rate 60
net_name oles
net_port 5445
net_sv_pending_lim 2
net_sv_update_rate 60
ph_fps 100
ph_gravity 20.00000
ph_squeeze_velocity 0.50000
rs_anisotropic on
rs_antialias off
rs_antialias_tv off
rs_clear_bb off
rs_detail_density 0.15000
rs_fullscreen on
rs_gamma 1.00000
rs_geometry_lod 1.00000
rs_glows_per_frame 12
rs_ib_size 512
rs_max_fps 200.00000
rs_min_fps 20.00000
rs_no_v_sync on
rs_occ_reject 0.00300
rs_occlusion off
rs_overdraw off
rs_renormalize off
rs_skeleton_update 64
rs_ssa_discard 5.00000
rs_ssa_dontsort 4096.00000
rs_stats on
rs_triplebuffer off
rs_vb_size 512
rs_wireframe off
snd_cull 0.07000
snd_doppler 0.30000
snd_freq 22khz
snd_model Default
snd_occlusion on
snd_occlusion_scale 0.85000
snd_relaxtime 10
snd_rolloff 0.30000
snd_volume_eff 1.00000
snd_volume_master 0.00000
snd_volume_music 0.70000
texture_lod 1
texture_show_mips off
vid_bpp 16
vid_mode 800x600
Там еше все что ввели с клавы записывается в файл Engine.ini
как мне отслеживать эти вводы?
и как мне вызывать эти команды из файла?
И ещё вопрос(не в тему) как мне качать камеру чтобы эмитировать
реалистичную ходьбу?
← →
FRick © (2004-05-31 17:40) [6]И ещё такая есть штука ввёл значит ты букву "g" и нажал клавищу
"Tab" и при каждом нажатие у тебя промелькают слова консоли
на букву "g" как такое заюзать!!
← →
NailMan © (2004-05-31 18:30) [7]Во первых пример моей консоли мжно посмотреть в моей демке на моем сайте(2.5Мб, D3D9): www.cyborghome.ru/nailman в разделе Качалка.
> И ещё такая есть штука ввёл значит ты букву "g" и нажал
> клавищу
> "Tab" и при каждом нажатие у тебя промелькают слова консоли
> на букву "g" как такое заюзать!!
Я делаю так:
делаю несколько файлов(в демке они упакованы в ресурс моего формата), в которых записываю следующее(пример):
файл allcmdvar.ini(файл содержит имена всех команд и переменных)
DEV_COORDS
R_RENDERTYPE
GOD
SUMMON
DEV_FPS
HELP
...
Остальные файлы содержат отдельно команды и отдельно переменные. Эти файлы нужны для вывода в консоль по командам cmdlist и cvarlist соотвественно.
Когда создается консоль я гружу этот текстовый файл в TStringList(точнее мой собственный аналог этого класса).
Когда юзер жмет таб и командная строка не пуста, я делаю так:Procedure TUTGameConsole.ShowContextCommands(CX:String);
var i,z,B:integer;p:string;
begin
cx:=uppercase(cx);
z:=0;b:=0;
For i:=0 to ALLList.Count-1 do
begin
p:=copy(ALLList.Strings[i],1,length(CX));
if pos(cx,p)<>0 then begin inc(z);b:=i;end;
end;
if z=1 then begin cmd:="> "+ALLList.Strings[b]+" "; exit;end;
if z>1 then History.Add(GetCmdDesc("Avail_Cmd_for_prefix")+cx);
For i:=0 to ALLList.Count-1 do
begin
p:=copy(ALLList.Strings[i],1,length(CX));
if pos(cx,p)<>0 then History.Add(" "+ALLList.Strings[i]);
end;
end;
Т.е. общем листе переменных и команд ищется все строки содержащие введенные символы в командной строке. Они-то и добавляются в консоль списком.
Если строка(команда или переменная) с таким началом одна, то она дополняется до полного вида.
Все просто и тупо. Никаких извратов. Данные с клавы я беру директинпутом через немерянную функцию анализа введенной клавиши(включая рус/lat и шифт).
> Мне имено надо заморочится с графикой!
> А так я уже делал только выглядит это неочень красиво!
Все зависит от фантазии и умения пользоваться АПИ. В директе это делается очень просто, а как в обертках OGL не знаю. Наверно также - делается квад в экранных коодинатах и натягивается текстура или как у меня просто зеленого полупрозрачного цвета.
---
WBR, NailMan aka 2:5020/3337.13
← →
ПсихЪ (2004-05-31 22:28) [8]
> Все просто и тупо. Никаких извратов. Данные с клавы я беру
> директинпутом через немерянную функцию анализа введенной
> клавиши(включая рус/lat и шифт).
Поделись кусочком кода, коим ты это делаешь...
← →
КиТаЯц (2004-06-01 07:58) [9]Эка сильно тебе консоль хочется ;)
Мне тоже сначала хотелось, а потом понял, что иначе ну никак без консоли нельзя, ну очень трудно отлаживать... Ну, да ладно. Держи кусок кривого кода. У меня работает.const
com001: string = "exit";
com002: string = "quit";
com003: string = "go";
com004: string = "stop";
var
Console: boolean = False;
ComNow: string;
ComCom: array [1..256] of string;
public
HUDBack: TGLHUDSprite;
HUDText: TGLHUDText;
procedure TMainForm.FormResize(Sender: TObject); // это чтобу симпатично было
begin
if (HUDBack <> nil) then
with HUDBack do begin
Width := GLSceneViewer.Width;
Height := round(GLSceneViewer.Height/3);
Position.X:=round(GLSceneViewer.Width/2);
Position.Y:=round(GLSceneViewer.Height/6);
end;
end;
// а здесь типа рисуем консоль
procedure TMainForm.FormKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_ESCAPE then begin
if Console then Key := 192
else begin
Application.MainForm.Hide; close; // если консоль то убрать иначе выход
end;
end;
if Key = 192 then begin
Console:= not Console;
if Console then begin
GLCadencer.Enabled:= False;
HUDBack:= tglHUDSprite.Create(GLScene);
HUDBack:= tglHUDSprite(GLScene.Objects.AddNewChild(tglHUDSprite));
with HUDBack do begin
Width := GLSceneViewer.Width;
Height := GLSceneViewer.Height/3;
Position.X:=GLSceneViewer.Width/2;
Position.Y:=GLSceneViewer.Height/6;
Material.MaterialLibrary := GLMaterialLibrary;
Material.MaterialOptions:= [moIgnoreFog, moNoLighting];
HUDBack.Material.FrontProperties.Diffuse.Color:= clrDarkSlateGray;
//HUDBack.Material.FrontProperties.Diffuse.Color:= clrRed;
HUDBack.Material.BlendingMode := bmAdditive;
end; //with mt
HUDText:= tglHUDText.Create(GLScene);
HUDText:= tglHUDText(GLScene.Objects.AddNewChild(tglHUDText));
with HUDText do begin
Position.X:= 10; Position.Y:= 10;
BitmapFont := GlFont;
end; //with mt
end else begin
if HUDText <> nil then HUDText.Destroy;
if HUDBack <> nil then HUDBack.Destroy;
GLCadencer.Enabled:= True;
end;
end;
end;
// а здесь типа пишем текст
procedure TMainForm.FormKeyPress(Sender: TObject; var Key: Char);
var i: byte;
begin
if (not Console) then exit;
if Key = #13 then begin
if length(trim(ComNow)) = 0 then exit;
DoCommand(ComNow); // и выполняем команду
//HUDText.Text:=ComNow;
ComCom[high(ComCom)]:= ComNow;
HUDText.Text:=
ComCom[high(ComCom)-7]+#13
+ComCom[high(ComCom)-6]+#13
+ComCom[high(ComCom)-5]+#13
+ComCom[high(ComCom)-4]+#13
+ComCom[high(ComCom)-3]+#13
+ComCom[high(ComCom)-2]+#13
+ComCom[high(ComCom)-1]+#13
+ComCom[high(ComCom)]+#13
+">";
for I:=1 to high(ComCom)-1 do ComCom[i]:=ComCom[i+1];
ComNow:= "";
end else begin
case Key of
"`","~": ComNow:=ComNow;
Char(VK_BACK): SetLength(ComNow, Length(ComNow)-1);
Char(VK_UP): ComNow:=ComCom[high(ComCom)];
else ComNow:=ComNow+Key;
end; //case
HUDText.Text:=
ComCom[high(ComCom)-8]+#13
+ComCom[high(ComCom)-7]+#13
+ComCom[high(ComCom)-6]+#13
+ComCom[high(ComCom)-5]+#13
+ComCom[high(ComCom)-4]+#13
+ComCom[high(ComCom)-3]+#13
+ComCom[high(ComCom)-2]+#13
+ComCom[high(ComCom)-1]+#13
+">"+ComNow;
end;
end;
procedure TMainForm.DoCommand (InputText: string);
var TmpText: string;
begin
TmpText:= InputText;
InputText:= trim(AnsiLowerCase(InputText));
if InputText = com001 then Close
else if InputText = com002 then Close
else if InputText = com003 then GLCadencer.Enabled:= True
else if InputText = com004 then GLCadencer.Enabled:= False
// else if InputText = com005 then
// else if InputText = com006 then
// else if InputText = com007 then
else ComNow:= "Unknow command: ""+TmpText+""";
end;
Может забыл что... Ответь - заработало-нет, помогло-непомогло...
← →
NailMan © (2004-06-01 09:44) [10]
> Поделись кусочком кода, коим ты это делаешь...
http://www.cyborghome.ru/nailman/zakroma/sources/UTControls.pas
Мне лень было чего-то вырезать, только немного добавил чтобы модуль контрол манагера был полным.
---
WBR, NailMan aka 2:5020/3337.13
← →
NailMan © (2004-06-01 09:45) [11]Соотвественно искомая функция TControlsManager.ScanKeyBoard.
---
WBR, NailMan aka 2:5020/3337.13
← →
FRick © (2004-06-01 21:29) [12]>Может забыл что... Ответь - заработало-нет, помогло-непомогло...
всё работает только текст не вводится
здесь я выложил архив (100kb)
посмотри что к чему:
http://coder-gls.narod.ru/index.html
← →
FRick © (2004-06-01 22:46) [13]А нет всё разобрался Спасибо за помощь помог!
← →
FRick © (2004-06-02 00:51) [14]Как наладить автозапись всего введенного консоли
в файл например Console_log.txt я пробовал записывает только
последний параметор а мне надо в столбик!
← →
kerby © (2004-06-02 15:31) [15]лови линк ;)
http://caperaven.co.za/gls/tutorials/documents/console/console.zip
← →
FRick © (2004-06-02 16:09) [16]Хороший линк думаю разберусь!
← →
КиТаЯц (2004-06-03 13:02) [17]
> FRick © (02.06.04 00:51) [14]
> Как наладить автозапись всего введенного консоли
> в файл например Console_log.txt я пробовал записывает только
> последний параметор а мне надо в столбик!
Если о моем примере говориш, то наверняка ты просто записываеш значениеComNow: string;
а надо попорядку все изComCom: array [1..256] of string;
ну еще не забыть проверить на заполнение чтобы пустые строки не сохранялись.
← →
NailMan © (2004-06-03 13:51) [18]А зачем вообще одновременно писать и в консоль и в файл?
Для сохранения консоли можно сделать команду, например "CONDUMP" и навесить на нее сохранение того массива строк который есть в консоли, напрямую в текстовый файло.
А в файл имхо логично писать лог всех действий программы. Точнее надо сделать так, чтобы логгинг настраивался, т.е. командами консоли можно было включить/выключить логгинг каких-то действий(скажем логгинг загрузки текстур, создания объектов и т.д.) .
---
WBR, NailMan aka 2:5020/3337.13
← →
КиТаЯц (2004-06-03 14:05) [19]2 NailMan © (03.06.04 13:51) [18]
Респект! Согласен полностью. :)
← →
FRick © (2004-06-03 17:07) [20]>А зачем вообще одновременно писать и в консоль и в файл?
>Для сохранения консоли можно сделать команду, >например "CONDUMP" и навесить на нее сохранение того массива >строк который есть в консоли, напрямую в текстовый файло.
>А в файл имхо логично писать лог всех действий программы. >Точнее надо сделать так, чтобы логгинг настраивался, т.е. >командами консоли можно было включить/выключить логгинг каких->то действий(скажем логгинг загрузки текстур, создания объектов >и т.д.) .
Ты абсолютно прав!
Думаю всё надо делать через INI!
Я видел все твой примеры (очень понравились особенно GUI)
заметил что там многие значения не вбиты в EXE"шник
а аккуратно записаны в файлы и разложены по полочькам
и вобще у тебя всё по полочкам!
Я думаю что то что подразумевает комманда "CONDUMP" надо
повесить на выход из программы!
Я замети что все действия программы пишутся в UTLogging.txt
с фиксацией даты и времени что должно помочь при отладке!
Консоль я наладил скрестил два примера и забил туда каманды
работает, думаю что любая игра начинается с консоли!
И ещё я сделал чтобы при запуске стоял только Английский
язык!
Консоль я повесил на F12!
Как же наладить всё выше и выше перечисленное?
← →
NailMan © (2004-06-03 18:30) [21]FRick ©
> Я замети что все действия программы пишутся в UTLogging.txt
> с фиксацией даты и времени что должно помочь при отладке!
Ну не все действия записываются в лог, но большинство. Да и дата нужна в редких случаях. Идея лога - отловить на каком месте прога сглюкнула. Скажем(пример) должна быть такая последовательность в логе:
Log: -> Calculating movement capabilities...
Log: -> Scanning and counting engines...Ok
Log: -> Calculate Max Velocities...Ok
А лог прервался(с вылетом виоляции акцесса) на:Log: -> Calculating movement capabilities...
Log: -> Scanning and counting engines...Ok
Значит при калькуляции максимальной скорости актера произошла ошибочка. Так как у меня при этой калькуляции пробегаются все двигатели актера, то вероятно где-то прога вылезла за пределы массива двигателей и прога обращалась по левому указателю. Так я и нашел багу - лог сам сказал на каком месте была ошибка.
Вообще довольно сомнительно использовать саму консоль для вывода информации о загрузке ресурсов и т.д.(как например в кваке, HL и их клонах). Принципиально ширина консоли маленькая и текста туда влезет немного, а если детализировать лог(как у меня) буквально до мелочей, то будет очень проблематично что-то потом прочитать по этому логу(когда он сбросится в файло). И тем более если вылез AV, программа скорее всего даже и не сбросит данные из консоли в файл.
Я, например, консоль сделал для отладки он-лайн процессов(в будущем). Скажем для отладки и мониторинга AI(в том самом будущем). Или скажем очень помогла при исправлении бага с переключениями LOD на моделях.
Конечно же консоль, имхо, обязана выполнять роль альтернативных "Настроек". Скажем делается кофиг-файл и в реал-тайме можно настроить все что можно настроить(как в кваке).
Я так например пользую консоль при отладке рендерера и работы актеров.
Конечно же снабдить консоль большим количеством команд(работающих :-)) непросто, но реально. У меня например большинство команд почерпнуто из кваки(вплоть до синтаксиса) и есть даже контексная помощь по командам и переменным. Типа обижать юзверя нельзя - закон чести.
> Консоль я наладил скрестил два примера и забил туда каманды
> работает, думаю что любая игра начинается с консоли!
Я тоже сначала написал консоль(когда в пятый раз начал переписывать ядро :-) ), а потом уже все остальное - опыт четырех предыдущих ядер научил уму разуму.
> Как же наладить всё выше и выше перечисленное?
Что касаемо екзекуции команд и экзекуции конфиг-файла:
Все просто - надо писать парсер команды!
Как я реализовывал(теория):
Когда вылезает консоль весь инпут работает на консоль, т.е. отключеются крыс и джой, а обработка нажатых в DInput клавиш идет через вышеупомянутую TControlsManager.ScanKeyBoard.
На выходе этого метода я получаю код действия и символ(если была нажата символьная клавиша). В это случае я прибавляю к переменной CMD:String этот символ.
Когда юзверь жмет ентер, то команда попадает в парсер.
Парсер делает следующее:
- дробит строку на составляющие. Заполняет глобальный CmdLine:TStringList кусками командной строки разделенной пробелами. Т.е. например поступила команда "condump C:\console.txt". Парсер разбивает ее на "condump" и "C:\console.txt" и добавляет(предварительно сделав CmdLine.Clear) последовательно в CmdLine.
В последующем всегда подразумевается что CmdLine.Strings[0] это команда, а все остальные ее элементы есть параметры команды.
- полученная последовательность команды/параметров поступает в анализатор(самая большая часть парсера), где начинаются сравнения команды из CmdLine с конкретными значениями(При дроблении все обязательно переводится в верхний регистр). Например(реальный):If (cmdline.strings[0]="QUIT") or (cmdline.strings[0]="EXIT") then
begin
SetStatus(STAT_EXITCODE); //Делаем статус программы на принудительный выход
result:=EC_CONSOLECMD; //Типа нужно для выхода из цикла консоли
fittoregion; //не столь важно
exit;
end;
...
//другие специфичные команды не укладывающиеся под шаблон исполнения
...
ExecuteCommand; //Исполняем команду
AddCmdHistory(cmd); //Добавляем ее в историю("консоль")
fittoregion;
ResetCommand; //Обнуляем команду: типа CMD:="";
Ну и самая тут главная процедура ExecuteCommand. Вот типа что там насчет "CONDUMP"://------------------------------ CONDUMP -----------------------------
If cmdline.Strings[0]="CONDUMP" then
begin
//Если в команде отсутсвует параметр-имя файла, то динамим юзера нах
if cmdline.Count=1 then
begin
History.Add(getcmddesc("File_not_assigned"));
fittoregion;
ResetCommand;
exit;
end;
//если не указан точный путь к файлу, то по умолчанию будем его создавать
//в игровой папке "Logs", иначе по точному пути
If extractfilepath(cmdline.strings[1])="" then
tempstring:="Logs\"+cmdline.strings[1]
else tempstring:=cmdline.strings[1];
//Сохраняем всю историю команд в консоли.
History.SaveToFile(tempstring);
//пишем в консоль что успешно сохранили файл с листингом по такому-то пути в такой-то файл
History.Add(getcmddesc("Console_dumped_to_file")+uppercase(tempstring));
exit;
end;
Усе просто. Надо лишь всегда разделять большую задачу на несколько мелких и их уже выполнять. Главное написать правильное разделение команды от параметров(учитывая пробелы и множественные пробелы) и обязательно все в верхний регистр(или в нижний), чтобы не было гемора с сравнениями строк.
---
WBR, NailMan aka 2:5020/3337.13
← →
КиТаЯц (2004-06-04 07:19) [22]2 NailMan ©
> Главное написать правильное разделение команды от параметров(учитывая
> пробелы и множественные пробелы) и обязательно все в верхний
> регистр(или в нижний), чтобы не было гемора с сравнениями
> строк.
Я конечно и сам могу, но... всегда легче на готовом :) "правильное разделение команды от параметров" не подкинеш?
← →
NailMan © (2004-06-04 09:24) [23]КиТаЯц
> Я конечно и сам могу, но... всегда легче на готовом :) "правильное
> разделение команды от параметров" не подкинеш?Procedure TUTGameConsole.ExtractCommand(c:string);
var n:string;
begin
//Убираем приглашение строки
CMDLine.Clear;
if c="" then exit;
repeat
//если есть пробелы, то удалем их
If c[1]=" " then CutSpaceInString(c);
If pos(" ",c)<>0 then
begin n:=copy(c,1,pos(" ",c)-1);Delete(c,1,pos(" ",c));
CMDLine.Add(n);
end
else
begin
if c<>"" then
begin
n:=c; c:="";
CMDLine.Add(n);
end;
end;
until (length(c)=0);
end;
И вырезалкаProcedure CutSpaceInString(var c:string);
begin
repeat
delete(c,1,1);
until (copy(c,1,1)<>" ") or (length(c)=0);
end;
---
WBR, NailMan aka 2:5020/3337.13
← →
КиТаЯц (2004-06-04 10:11) [24]2 NailMan © (04.06.04 09:24) [23]
Спасибо! Я так понимаю, чтоCutSpaceInString(var c:string);
то же само, что стандарнаяTrimLeft();
;o)
← →
NailMan © (2004-06-04 10:48) [25]КиТаЯц
Возможно, просто мне проще было написать свое, дабы не подключать лишние модули.
---
WBR, NailMan aka 2:5020/3337.13
← →
FRick © (2004-06-05 18:48) [26]Спасибо вам за помощь
я пропаду на месяц
буду Апгрейдить комп!
← →
dimodim © (2004-06-15 06:55) [27]
> И ещё вопрос(не в тему) как мне качать камеру чтобы эмитировать
> реалистичную ходьбу?
Качай по оси X
Страницы: 1 вся ветка
Форум: "Игры";
Текущий архив: 2004.10.17;
Скачать: [xml.tar.bz2];
Память: 0.57 MB
Время: 0.044 c