Pull to refresh

Обзор физики в играх Sonic. Часть 1: твердые тайлы

Reading time 15 min
Views 44K
Original author: info.sonicretro.org
image

От переводчика: этот пост — перевод одной из частей масштабного обзора физики (Sonic Physics Guide) в играх серии Sonic the Hedgehog для Sega Genesis/Mega Drive и Sonic CD. В следующих частях рассматриваются такие темы: бег, прыжки, вращение, потеря колец, поведение под водой, суперскорость, специальные возможности, камера, анимации и некоторые другие. Так как частей много (14 штук), в конце поста я добавил опрос. Стоит ли продолжать — решать вам.

Ссылки на другие части серии:
Часть 2: бег
Части 3 и 4: прыжки и вращение
Части 5 и 6: потеря колец и нахождение под водой
Части 7 и 8: пружины и штуковины, суперскорости



Примечание

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

Введение

Что такое твердые тайлы? В зонах (уровнях), из которых состоят игры про Соника, очень много твердых объектов, поэтому зона потребовала бы слишком много памяти, если окружение было бы целиком составлено из твердых объектов, каждый из которых занимает 64 байта RAM. Был сделан умный ход — зона создается из тайлов, поэтому все, что требуется — знать, является ли тайл твердым (непроницаемым), или нет.

Возможно, вы знаете, что зоны разбиты на блоки размером 128x128 пикселей (или 256x256 пикселей в Sonic 1 и Sonic CD), которые, в свою очередь, разбиты на тайлы в 16x16 пикселей; они также разбиты на более мелкие тайлы размером 8x8 пикселей. Вся магия обработки твердых элементов происходит на уровне тайлов 16x16, поэтому в этом обзоре нас будут интересовать только они.

Взаимодействие и столкновения Соника с этими твердыми тайлами и образуют базовый движок игры. Они определяют способ работы с полами, стенами, потолками, скатами и петлями. Поскольку это объемная и сложная тема, мой обзор будет отличаться от других руководств по физике игр Sonic, но я постараюсь ограничить рассуждения до минимума.

Стояние

image

Если принять положение земли по оси Y равным 736 ($02E0), то Соник стоит над ней в координате 716 ($02CC). Это на 20 пикселей выше уровня земли.

Толкание

Соник должен останавливаться при ударе об стену. Этого можно достичь, проверяя коллизию с линией. Линия коллайдера (от переводчика: здесь и далее в статье используется термин sensor, я выбрал, как мне кажется, адекватный аналог) должна находиться не на его позиции на оси Y, а немного ниже, иначе она «не заметит» короткие шаги. Она также не может быть слишком низко, иначе она «заметит» склоны и кривые, от которых Соник не должен отталкиваться. Достаточным будет ее расположение в координате Y+4 от расположения Соника, потому что обычно не бывает лестниц ниже 16 пикселей в высоту (когда такие лестницы есть, то они состоят из твердых объектов, а не тайлов).

Насколько широкой должна быть линия коллайдера?

image

Если принять координату X левой стороны стены равной 704 ($02C0), то Соник не должен оказаться ближе 693 ($02B5). Если принять координату X правой стороны стены равной 831 ($033F), то Соник не должен оказаться ближе 842 ($034A). Это составляет разницу в 11 пикселей в обоих направлениях.

Поэтому линия коллайдера должна иметь ширину 20 пикселей, и быть растянутой от X-10 до X+10 Соника. При каждом обнаружении твердого тайла Соник должен «выталкиваться» установкой координаты тайла минус (или плюс) 11, при этом скорость перемещения по земле должна обнуляться. (Его нельзя вытолкнуть только на 10 пикселей, в противном случае точка с координатами X+10 будет по-прежнему находиться внутри граничного пикселя тайла. При этом будет зарегистрирована постоянная коллизия, и он прилипнет к стене.)

Так как граница тайла минус положение Соника должно равняться 11, между центром Соника и границей тайла есть всего 10 свободных пикселей. Одиннадцатый пиксель от Соника будет уже самой границей тайла. Поэтому Соник в действительности имеет ширину 20 пикселей.

Падение

Соник должен иметь возможность сбегать с площадок. Он не может вести себя как койот Wile E. Coyote из мультфильмов, не замечающий, что под ним ничего нет.



Это означает, что Соник также должен проверять наличие под ним твердых тайлов. Этого можно достичь, добавив еще две линии коллайдеров, направленных вниз. Один (A) должен быть с левой стороны Соника, в координате X-9. Другой (B) — с правой стороны, в X+9. Они должны начинаться с его положения по оси Y и опускаться не менее чем на 16 пикселей ниже от его ног на уровне земли, которая расположена в Y+20 (но не слишком низко, или он будет сваливаться при спуске с низких ступенек или лестниц, чего бы нам не хотелось).

Если коллайдеры A и B не обнаруживают твердых тайлов, Соник «падает» — устанавливается флаг, сообщающий движку, что он находится в воздухе.

Мы помним, что при столкновении со стенами Соник имеет ширину 20 пикселей. Однако коллайдеры обнаружения земли находятся всего в 18 пикселях от центра. Получается, что Соник «худее» на 2 пикселя при сбегании с площадок, чем при ударах о стены.

Балансирование на краю

Хорошая деталь — Соник переходит в анимацию балансирования, находясь близко к краю площадки. Это случается, только когда он остановился (его скорость движения по земле равна 0).

Откуда об этом узнает движок? Все просто — в момент, когда активен только один из коллайдеров, Соник находится на краю. Если A активен, а B — нет, то площадка находится справа от него. В противном случае площадка слева.

Однако если Соник начнет балансировать, сразу же, как только один из коллайдеров ничего не обнаружит, он начнет балансировать «рано», и это будет выглядеть глупо. Поэтому это случается, когда активен только один коллайдер, и его положение по оси X больше, чем граница твердого тайла, обнаруженного активным коллайдером.



Если принять координату правого края площадки равной 2655 ($0A5F), Соник начнет балансировать только в 2656 ($0A60). Он упадет в точке 2665 ($0A69), когда оба коллайдера ничего не обнаружат.

В Sonic 2 и Sonic CD если площадка находится в противоположном от взгляда Соника направлении, то включается вторая анимация балансирования.

В Sonic 2, Sonic 3 и Sonic & Knuckles Соник имеет третью анимацию балансирования, когда он еще ближе к краю площадки. С учетом установленных выше значений, она включается, когда он в координате 2662 ($0A66).

Примечание: при балансировании некоторые возможности недоступны (прижимание к земле, смотрение вверх, вращение и т.д.). В Sonic 3 & Knuckles игрок может прижиматься к земле и вращаться (но не смотреть вверх) при балансировании на земле, но не при балансировании на объекте.

Скаты и кривые

Sonic the Hedgehog стала одной из первых игр, использующих кривые поверхности и петли с оборотом на 360 градусов. В большинстве игр той эры окружение полностью создавалось из блоков (и иногда из склонов).

Возможность работы с плавно изменяющими форму объектами — это один из фундаментальных аспектов новизны и привлекательности игр про Соника. К сожалению, это, наверно, самый сложный для воссоздания в фанатских играх аспект.

Как же он работает?

Маски высот

При каждом обнаружении коллайдерами A или B твердого тайла они возвращают высоту этого тайла.

Как находится высота тайла?

Каждый тайл имеет ассоциированное с ним значение, привязанное к маске, хранящейся в памяти. Маска — это простой массив из 16 значений высоты в диапазоне от 0 ($00) до 16 ($10) и значения угла.



Например, эта маска высот имеет массив высот 0 0 1 2 2 3 4 5 5 6 6 7 8 9 9 9 и угол 232 ($00 00 01 02 02 03 04 05 05 06 06 07 08 09 09 09 и угол $E8).

Какое используется значение массива высот? Вычитаем положение X тайла из положения X коллайдера. Результат будет используемым индексом массива высот.

Если найденное значение высоты равно 16 ($10), то коллайдер должен проверить еще один тайл выше первого найденного, и определить его значение высоты.

Какой бы коллайдер ни обнаружил самую большую высоту, координата Y Соника устанавливается равной этой высоте минус 20 пикселей. Его угол также устанавливается равным углу твердого тайла, вернувшего самую большую высоту.

Если коллайдер не обнаружил твердый тайл, по умолчанию возвращается уровень ног (Y+20).

Ошибки при использовании этого метода

К сожалению, из-за использования этого метода в оригинальном движке есть пара раздражающих багов.

Если Соник стоит на наклонном площадке, один из коллайдеров не обнаружит тайл, и вернет высоту уровня ног. Это приводит к тому, что Соник стоит в неправильном положении.



Соник поднимается вместе с коллайдером B при перемещении вправо. Когда B «падает» с площадки, Соник по умолчанию перемещается на уровень коллайдера A. Затем он поднимается вместе с коллайдером A при дальнейшем движении вправо. Поэтому он сначала будет подниматься, падать и снова подниматься при сбегании с площадки.

Есть всего несколько областей, в которых это заметно, но такой баг присутствует во всех частях игры для Genesis/Mega Drive, и это выглядит довольно неаккуратно.

Второй баг возникает в случае наличия двух тайлов противоположных склонов одного над другим, таких как низкие холмы на уровнях Green Hill Zone и Marble Zone.



Коллайдер B начинает спускаться со склона вправо, но Соник все еще по умолчанию ориентируется на уровень предыдущего склона, обнаруженного коллайдером A. Поскольку такие склоны довольно невысокие, это приводит к опусканию Соника в середине примерно на 1 пиксель.

Но это еще не все. Соник получает данные об угле от самого высоко расположенного коллайдера, поэтому даже если похоже, что он должен быть под углом склона справа (потому что он ближе к нему), он по-прежнему будет иметь угол склона слева. При прыжке он будет прыгать под этим углом, двигаясь назад, а не вперед, как ожидается.

Перемещение под углами

Все это очень хорошо и замечательно — Соник плавно движется по поверхности с различными высотами. Однако движку нужно сделать еще кое-что. Для реалистичности скорость Соника должна снижаться на поверхностях под углом.

Существует два способа, которыми углы могут влиять на скорость Соника. Первый гарантирует, что он не пробегает по холму за то же время, что и по плоской земле той же ширины. Второй замедляет его при движении вверх по холму и ускоряет его при движении вниз. Давайте рассмотрим каждый из них по очереди.

Три переменных скорости

Если бы Соник был обычным платформером, использующим только блоки, для него понадобились бы только две переменные скорости: движение по оси X (Xsp) и по оси Y (Ysp), горизонтальная и вертикальная составляющие скорости Соника. Ускорение (acc), замедление (dec) и трение (frc) добавляются к Xsp; скорость прыжка (jmp) и гравитация (grv) добавляются к Ysp (когда Соник находится в воздухе).

Однако при использовании скатов, когда Соник движется по скату, он одновременно перемещается горизонтально и вертикально. Это означает, что и Xsp, и Ysp имеют ненулевые значения. Простое добавление acc, dec или frc к Xsp больше не работает; представьте, что Соник пытается бежать вверх по стене — прибавление к горизонтальной скорости будет бесполезным, ведь он пытается двигаться вверх.

Хитрость заключается в задействовании третьей переменной скорости (что и делает оригинальный движок), назовем ее скоростью перемещения по земле (Gsp). Это скорость Соника вдоль земли вне зависимости от угла. Значения acc, dec и frc прибавляются к Gsp, а не к Xsp или Ysp.

При нахождении на земле Xsp и Ysp извлекаются из Gsp на каждом шаге перед перемещением Соника. Думаю, здесь будет уместен пример на псевдокоде:

Xsp = Gsp*cos(angle);
Ysp = Gsp*-sin(angle);
 
X += Xsp;
Y += Ysp;

Вне зависимости от изменения угла скорость Gsp сохраняется, поэтому движок всегда знает, с какой скоростью Соник «по-настоящему» движется.

Коэффициент ската

Теперь Соник может обрабатывать взаимодействие с любыми холмами, сохраняя точную скорость. Однако ему по-прежнему необходимо замедляться при подъеме и ускоряться при спуске.

К счастью, этого легко добиться, воспользовавшись понятием коэффициента ската (slp). Просто будем прибавлять slp*sin(angle) к Gsp в начале каждого шага. Значение slp при беге всегда равно 0.125 ($0020), но отличается при вращении. Когда Соник вращается, поднимаясь вверх по холму (знак Gsp не равен знаку sin(angle)), slp равен 0.078125 ($001E). При вращении вниз по холму (знак Gsp равен знаку sin(angle)) slp равен 0.3125 ($0050).

Примечание: похоже, что в Sonic 1 величина slp не прибавляется, если Соник останавливается и находится в цикле стояния/ожидания. Однако в Sonic 3 & Knuckles величина slp прибавляется даже в этом случае, поэтому Соник не может стоять на крутых скатах — ему приходится спуститься по ним назад.

Прыжки под углами

Угол, под которым стоит Соник в момент прыжка, также влияет на прыжок. Он не может просто присвоить Ysp значение jmp, ему необходимо отпрыгнуть от угла, под которым он стоит. Поэтому значение jmp нужно присвоить и Xsp, и Ysp, воспользовавшись cos() и sin() для получения правильных значений.

Еще немного псевдокода:

Xsp -= jmp*sin(angle);
Ysp -= jmp*cos(angle);

Переключение режимов

Итак, Соник может бегать по холмам, склонам и площадкам, и это уже неплохо. Но этого недостаточно. Он не может проделать путь с земли к стенам и потолку без дополнительного кода.

Почему? Коллайдеры земли A и B проверяют препятствия непосредственно под ними, находя высоту земли. Они никак не могут обработать переход к стенам, потому что вся схема рассчитана на перемещение ровно вверх и вниз по оси Y.

Как решить эту проблему? Использованием четырех различных режимов движения. Это требует небольшого объяснения.

Четыре режима

Довольно логично предположить, что если Соник может перемещаться на 360 градусов, движок примерно одинаковым образом обрабатывает все 360 градусов. На самом же деле движок разделяет углы на четыре квадранта, что значительно упрощает работу.

Для лучшего понимания того, о чем я говорю, представим более простой платформер без полных петель, только с низкими холмами и склонами. Все, что нужно сделать персонажу после горизонтального перемещения — переместиться вверх или вниз, пока он не достигнет уровня пола. Затем нужно замерить угол пола. Угол используется для снижения Gsp, и ни для чего более. Персонаж по-прежнему всегда будет двигаться горизонтально и перемещаться строго вверх и вниз для достижения уровня пола.

В основном так же происходит и в играх про Соника. Только если угол становится слишком крутым, Соник меняет «квадрант», переключаясь из режима пола в режим правой стены (затем в режим потолка, левой стены, и обратно в режим пола, и так далее). В любой момент времени и в любом отдельном режиме Соник ведет себя как в обычном платформере. Магия возникает при комбинировании этих четырех режимов с умно сделанным плавным переключением между ними.

Но как и когда Соник переключается между режимами?

Если при нахождении в режиме пола обнаружен угол больше 224 ($E0), движок переключается в режим правой стены. В основном все остается прежним, только коллайдеры проверяют правую сторону вместо низа, и Соник перемещается на уровень «пола» горизонтально, а не вертикально.

Если при нахождении в режиме правой стены обнаруживается угол меньше 224 ($E0), движок переключается обратно в режим пола.

Другие переходы работают аналогичным образом.

Может возникнуть справедливый вопрос — где находятся коллайдеры земли, когда мы находимся в режиме правой стены? Они расположены там же, но повернуты на 90 градусов. Коллайдер A находится относительно центра Соника в Y+9 вместо X-9. Коллайдер B находится в Y-9 вместо X+9. Линии коллайдеров становятся не вертикальными, а горизонтальными, растянувшись на 16 пикселей за пределы уровня его ног (который теперь на 20 пикселей «ниже» Соника, в координате X+20).

Да, поскольку коллайдеры перемещаются, Соник может быть «вытолкнут» на новое положение в шаге, на котором переключается режим. Однако перемещение составляет всего несколько пикселей, и при обычной игре совсем незаметно.

Еще один аспект: как я уже сказал, твердые тайлы состоят из массивов высот. Ключевое слово здесь «высота». Как они ведут себя в режиме правой стены? Довольно удивительным образом — оказывается, в оригинальном движке каждый твердый тайл имеет два дополняющих друг друга массива высот; один используется для горизонтального, а другой — для вертикального перемещения.

А как насчет режимов левой стены и потолка? Разве не нужны четыре массива высот? Нет, потому что тайлы такой формы используют обычные массивы высот, только инвертированные. В режиме потолка Соник знает, что найденное значение высоты должно использоваться для перемещения его вниз, а не вверх.

Благодаря этим четырем режимам Соник может перемещаться по всем видам контуров, внутренних и внешних кривых, и так далее. Вот пара примеров изображений со значениями углов, которые объяснят вам, о чем мы говорим:





Падение со стен и потолков

Находясь в режиме правой стены, левой стены или потолка, Соник падает, когда абсолютное значение Gsp становится ниже 2.5 ($0280) (Gsp в этот момент приравнивается к 0, однако Xsp и Ysp не изменяются, поэтому Соник может продолжить двигаться по своей траектории в воздухе). Это происходит, даже когда под ним есть земля.

Соскальзывание назад

Когда Соник падает указанным выше способом, таймеру блокировки горизонтального управления присваивается значение 31 ($1E) (таймер не начнет обратный отсчет, пока Соник не прикоснется к земле). Пока значение таймера не равно нулю, а Соник находится на земле, он не дает игроку изменять скорость Соника кнопками «вправо» и «влево». Значение таймера уменьшается на 1 с каждым шагом, поэтому блокировка длится примерно полсекунды. В течение этого времени на движение влияют только slp и скорость, с которой Соник упал на землю, поэтому Соник будет соскальзывать вниз по скату.

Состояние нахождения в воздухе

Когда Соник находится в воздухе, не нужно волноваться об углах, Gsp, slp и прочем. Все, что необходимо — перемещаться с использованием Xsp и Ysp до обнаружения контакта с землей, после чего перейти в режим нахождения на земле.

Однако Соник имеет необычный набор коллайдеров для движения в воздухе, который стоит рассмотреть подробно.

Коллайдеры нахождения в воздухе

Горизонтальный коллайдер

Соник должен ударяться о стены так же, как он делает это, находясь на земле. Поэтому имеется линия горизонтального коллайдера, исходящая из его положения Y и растянувшаяся от X-10 до X+10. При столкновении с твердым тайлом Соник «выталкивается» к границе тайла плюс/минус 11, как и находясь на земле.

Разница в том, что эта линия коллайдера шире, чем вертикальные коллайдеры A и B, обнаруживающие наличие земли (мы вернемся к ним чуть ниже). Это означает, что линия горизонтального коллайдера может обнаружить блок, мимо которого пролетает Соник, даже если у него совсем нет Xsp. Поэтому он проскальзывает с твердых предметов при ударах об их крайние грани.

Когда Соник обнаруживает столкновение с этой линией горизонтального коллайдера, его Xsp обнуляется только если он двигается по направлению к стене, а не от нее. В противном случае он бы приклеивался к стенам, пролетая мимо них.

Вертикальные коллайдеры

Коллайдеры A и B Соника действуют в воздухе примерно так же, как и на земле. Разница в том, что при обнаружении твердого тайла высота Соника не устанавливается сразу же равной высоте, обнаруженной для тайла, минус 20 пикселей. Вместо этого такая высота устанавливается только когда он уже ниже этой высоты. Иначе он бы прилип к полу при приближении к нему.

Если коллайдеры растянуты настолько ниже его ног, почему они не могут обнаруживать землю снова в шаге, в котором он прыгает, и не позволять ему никогда оторваться от пола? Все просто: он игнорирует их, кроме тех моментов, когда Ysp равна или больше нуля, поэтому он обнаруживает землю только при движении вниз.

Поскольку Соник в воздухе может перемещаться как вверх, так и вниз, должно быть еще два коллайдера, проверяющих сверху (C и D), чтобы он мог ударяться о потолок и кривые, расположенные выше него. (C и D являются точными зеркальными отражениями A и B — они имеют то же положение X и длину, только направлены не вниз, а вверх.) Соник обнаруживает потолки и отталкивается от них, двигаясь как вверх, так и вниз, в отличие от полов, которые обнаруживаются только при движении вниз. Можно удариться о «потолок» (который на самом деле является нижней частью блока) при движении вниз, прижавшись к стене, в которой есть зазор, или прыгнув к стороне верхней кривой.

Прыжки «через» пол

Есть площадки, которые Соник может пролетать «насквозь» при прыжках вверх. Они часто встречаются в холмистых зеленых зонах, таких как Green Hill Zone, Emerald Hill Zone, Palmtree Panic Zone и так далее. Твердые тайлы, составляющие такие площадки, помечаются движком как имеющие особый тип, который может обнаруживаться только коллайдерами A и B. Они игнорируются коллайдерами C и D, а также линией горизонтального коллайдера.

Повторное обнаружение земли

Когда Соник находится на земле, Xsp и Ysp получаются из Gsp. Когда он падает или иным образом отрывается от земли, Xsp и Ysp уже содержат нужные значения для продолжения траектории в воздухе. Но когда Соник приземляется, Gsp должна быть рассчитана из Xsp и Ysp, имевшихся на момент приземления. Можно подумать, что для получения точного значения используются cos() и sin(), но это не так. В действительности происходит нечто гораздо более простое, при этом алгоритмы при ударе об искривленный потолок и при приземлении на искривленную землю различаются, поэтому я рассмотрю их отдельно.

При движении вниз

Если угол обнаруженной земли находится в диапазоне 240-255 ($F0~$FF) (и их зеркальных отражений 0-15 ($00~$0F)), Gsp присваивается значение Xsp. Если угол находится в диапазоне 224-239 ($E0~$EF) (16-31 ($10~$1F)), Gsp присваивается значение Xsp, но только если абсолютное значение Xsp больше, чем Ysp. В противном случае Gsp равна Ysp*0.5*-sign(cos(angle)). Если угол находится в диапазоне 192-223 ($C0~$DF) (32-63 ($20~$3F)), Gsp равна Xsp, если абсолютное значение Xsp больше Ysp. Иначе Gsp присваивается значение Ysp*-sign(cos(angle)).

При движении вверх

Если угол обнаруженного потолка находится в диапазоне 160-191 ($A0~BF) (64-95 ($40~$5F)), Соник прикрепляется к потолку, а Gsp принимает значение Ysp*-sign(cos(angle)). Если угол находится в диапазоне 96-159 ($60~9F), Соник ударяется головой, как об обычный потолок, и не прикрепляется к нему. Ysp обнуляется, а Xsp не изменяется.

Справка: преобразование углов

В играх на Genesis/Mega Drive используются значения углов в шестнадцатеричном формате, от 0 ($00) до 255 ($FF), поэтому круг разделен не на 360 частей, как мы привыкли, а на 256. Что еще хуже, в отличие от углов в других языках, например, GML, они считаются против часовой стрелки, поэтому 32 ($20) равен не 45°, как должен, а 315°.

Для преобразования оригинальных шестнадцатеричных углов в углы, которые можно использовать в GML, воспользуйтесь этой формулой (записана псевдокодом):

return (256-hex_angle)*1.40625;

Примечания

  • Соник может тормозить («скрипя» ногами) только в режиме пола.
  • Соник не может прыгать, если над ним есть низкий потолок. Если обнаружена коллизия в Y-25 с линией коллайдера, расположенной от X-9 до X+9, то Соник не начнет прыгать.
  • В разные моменты Соник имеет разную высоту. Когда он стоит, бежит, падает или взлетает на пружинной платформе, он имеет высоту 40 пикселей. Его положение по оси Y всегда является его центром, поэтому он всегда стоит в 20 пикселях над землей (и в 20 пикселях под потолками, когда стукается об них). Однако при прыжках и вращении он имеет высоту всего 30 пикселей, и поднят на 15 пикселей над землей (а также опущен на 15 пикселей ниже потолка, и т.д.). В шаге, в котором Соник вращается или прыгает, движок добавляет 5 к его положению Y, поэтому несмотря на то, что он становится короче и его центр меняет положение, положение нижней точки не изменяется. При завершении вращения или приземлении после прыжка 5 пикселей вычитается из Y. Система камеры также должна учитывать это смещение, иначе при изменении высоты Соника вид будет также изменяться.
  • Коллайдеры A, B, C и D, описанные в этом обзоре, расположены в координатах X-9 и X+9. Это справедливо только при ходьбе, падении, отталкивании от пружинной платформы и так далее — всегда, когда он не свернут в клубок. Если Соник вращается или прыгает, они находятся в координатах X-7 и X+7. Однако его линия горизонтального коллайдера всегда остается одинаковой.


Update: добавил значения в десятеричном виде.
Only registered users can participate in poll. Log in, please.
Стоит ли продолжать перевод следующих частей этого обзора?
91.45% Да, интересно 460
8.55% Не, скукота 43
503 users voted. 101 users abstained.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+51
Comments 5
Comments Comments 5

Articles