Expression 2 для чайниковВ этом руководстве я расскажу о основах E2 для всех вас. Я постараюсь обьяснить настолько просто, насколько смогу, чтобы избежать лишних вопросов, на которые я отвечу.
Если, что-то будет непонятно пишите.
В данном руководстве представлены далеко не все команды, но дает вам все шансы лучше постичь нелегкую стезю изучения Expression 2. В конце концов не тупые же вы, чтобы если чего-нибудь нету не поискать в офф вики.
В данном руководстве:Первые 5-6 строк +
- Основная математика +
- Условия +
- Векторы +
- Объекты +
- Массивы +
- Строки +
- Команды поиска +
- Applyforce +
- Команды чата +
- Concmd (Консольные команды) +
- Детекторы +
- Консольные экраны +
- Голограммы -
- Wirelink -
Я начну с объяснения первых 5 строк кода, используемые в E2.@name
@inputs
@outputs
@persist
@trigger All
"@name" используется для того, чтобы вы могли обозначить свой чип, это название также отображается при наведении курсора на чип.
"@inputs" - это та строка, куда вы записываете входы вашего чипа. Вы не можете вписывать 2 входа с одинаковыми именами, а также они должны быть с большой буквы. Для множественных входов вы должны разделить их пробелом.
"@outputs"- это та строка, куда вы записываете выходы вашего чипа. Позволяет подклчить что-либо к чипу. Вы не можете вписывать 2 выхода с одинаковыми именами, а также они должны быть с большой буквы. Для множественных входов вы должны разделить их пробелом.
"@persist" -уникальная строка присутствующая только в Expression 2. Может быть использована для тех переменных, которые не требуется выводить, для личного удобства. Вы не можете вписывать 2 переменные с одинаковыми именами, а также они должны быть с большой буквы. Для множественных входов вы должны разделить их пробелом.
После "@trigger" перечисляются входы E2, при изменении которых он должен запускаться.
"@trigger none" - E2 вообще не будет реагировать на изменения значений на входах.
"@trigger all" - E2 будет запускаться при изменениях на любом входе.
Пропуск этой строки равносилен написанию "@trigger all"
После "@model" можно указать модель для Е2.
Все эти строки не обязательны к присутствию в Е2, если задумка автора Е2 того не требует.
Основная математикаПростейший код:
@name Add chip
@inputs A B
@outputs Result
Result=A+B
В этом простом чипе сложения чип берет значение "A" и прибавляет его к значению "B". Все, что вы подключите к чипу будет получать значение суммы "A" и "B".
Вы можете использовать множество математических функций.
Result=A-B (вычитание)
Result=A/B (деление)
Result=A%B (остаток от деления)
Result=A*B (умножение)
Result=A^B (возведение в степень)
УсловияУсловия – одно из наиболее простых вещей в E2, но он оказывает достаточно сильное влияние на код. Как пример можно привести простой кран. Когда он открыт, вода течет, когда закрыт, ничего не проходит.
Условие записывается
if (A) {Out = B} else {Out = C}
и читается : Если A=1, то на выход подается значение “B”, иначе(A не равно 1) “C”. Вот небольшой пример
@name Control
@inputs Button
@outputs Out
@persist
@trigger All
if(Button) {Out = 30} else {Out = 5}
В этом примере кнопка подключается к чипу. Если она нажата, чип выдаст 30, а если не нажата, то 5. Может использоваться для создания простейших дверей.
Вы так же можете использовать условия, без “else”
Когда вы заспавните чип, на выход подается 0. При нажатии кнопки, на выход подастся значение 30. Когда вы отключите кнопку, на выход все еще будет подаваться 30. Такая ситуация наблюдается потому, что вы не задали, что делать чипу если кнопка отжата. Можно использовать для залипаний, а также использовать другие условия, чтобы вернуть на выход 0.
В прошлом примере использовались фиксированные значения. Вот неплохой примерчик с переменными значениями.
@name Speed
@inputs Speedometer Button
@outputs Out
@persist
@trigger All
if(Button) {Out = Speedometer} else {Out = 0}
Данный чип будет выводить значение спидометра только, когда нажата кнопка.
Теперь разберем функцию “interval”, данная функция обновит чип через заданное число(в мс), 1000 – 1 одна секунда. Множеству функций это нужно для нормальной работы, как правило, тем, которые получают информацию для работы из “ мира”. Также может использоваться в таймерах и счетчиках. В некоторых местах вы можете использовать логические функции, но об этом позже.
В этом примере, когда счетчик доходит до 10 выход “Explode” получает значение 1. Я используем persists в этом чипе, потому что я не хочу, чтобы отображался счет таймера, только значение “Explode”.
В чипе присутствуют математические функции, которые нужны для работы условий, их немного.
"==" – тождественно (равно)
"!=" – не равно
Они используются в следующем чипе.
@name Timer
@inputs
@outputs Explode
@persist Time Count
@trigger All
interval(1000)
Time = Time + 1
if(Time == 10) {Explode = 1} else {Explode = 0}
При использовании "Time = Time + 1" запускается цикл, обновляющийся через заданный интервал.
Может так же записываться так.
1 не единственное число которое можно прибавлять таким способом, в функции
n может быть любым числом
Так же можно обнулить таймер.
@name Timer
@inputs
@outputs Explode
@persist Time Count
@trigger All
interval(1000)
Time = Time + 1
if(Time == 10) {Explode = 1, Time = 0} else {Explode = 0}
Добавлением запятой, я сообщаю условия, что должно выполняться два действия при срабатывании. Первое взрыв, а второе обнуление таймера. Можно делать так столько сколько нужно. Так же можно использовать "|", что значит “или”. Вот пример.
@name Or Test
@inputs Button Button2
@outputs Out
@persist
@trigger All
if(Button | Button2) {Out = 5} else {Out = 0}
Может быть расшифровано как , если Button=1 или Button2=1, на выход идет 5.
И напоследок немного информации о условиях, Также возможна обработка условия в условии. В данном примере таймер работает, только с нажатой кнопкой.
@name Timer
@inputs Button
@outputs Explode
@persist Time Count
@trigger All
interval(1000)
if(Button) {
Time = Time + 1
if(Time == 10) {Explode = 1} else {Explode = 0}
} else {Time = 0}
В условии может быть больше чем одна линия. Можно делать столько сколько угодно. Только код усложняется.
ВекторыОбычно векторы используются как координаты. И записываются как (X,Y,Z). Чтобы получше понять можно достать учебник по векторной алгебре.
Во входах и выходах или скрытых переменных нужно писать как V:vector, это сообщает, что вы больше не можете использовать V, для чисел, углов, или объектов. Для использование вектора вы должны использовать функции, записываемые : (имя функции) записанное сразу после имени переменной.
Вы не можете вычитать простые числа из вектора. Для того чтобы вычесть число из вектора, вы должны перевести это число в вектор и вычесть его.
Пример (500,153,120) + 8 не будет работать, вместо этого мы представляем число 8 как вектор и записываем
(500,153,120) + vec(8,8,8) = (508,161,128)
.
Числа не обязательно должны быть одинаковыми, вы можете записывать любые числа.
(500,153,120) + vec(100,120,180) = (600,273,300)
Когда вы используете векторы, необходимо указывать это в первых строчках, и записывается это, как V:vector. Ниже приведен неплохой пример, очень простой.
@name GPS
@inputs GPS:vector Button
@outputs Out:vector
@persist
@trigger All
if(Button) {Out = GPS} else {Out = vec()}
vec()=vec(0,0,0) он же нулевой вектор.Так же можно использовать некоторые команды для изменения отдельных частей вектора.
V:setX(#)
V:setY(#)
V:setZ(#)
Также можно извлечь из вектора отдельное число.
Out=V:x()
Out=V:y()
Out=V:z()
В следующем примере рассматривается выделение высоты из подаваемого векторного значения
@name Height
@inputs GPS:vector
@outputs Height
@persist
@trigger All
Height = GPS:z()
Поворот векторов – отдельная функция в E2. Используется не очень активно и плохо изучено. Обычно упирается в сложность объяснения. Когда вы поворачиваете вектор на угол, вы поворачиваете его относительно координаты 0,0,0( являющимся фактическим центром карты).
Пример:
vec(100,0,0):rotate(ang(180,0,0)) = vec(-100,0,0)
vec(100,0,0):rotate(ang(90,0,0)) = vec(0,0,0)
Обычно используется для создания HUD’ов и навигации. Я не уверен, что достаточно понятно объяснил. Так что лучшим примером будет код.
** Данный код содержит вещи, которые я еще не объяснял, но о них будет рассказано позже в данном руководстве. Вам следует обратить все свое внимание к пункту Rotate.**
Об этом не беспокойтесь.
Ang = угол поворота головы
Vec = ваше местонахождение
@name
@inputs
@outputs Rotate:vector
@persist Vec:vector Ang:angle
@trigger
runOnTick(1)
#не обращать внимания ---
if(first()) {
holoCreate(1)
}
Ang = owner():eye():toAngle()
Vec = owner():shootPos()
holoPos(1,Rotate)
# не обращать внимания ---
#Rotate
Rotate = Vec+ (vec(100,0,0):rotate(Ang))
В данном примере vec(100,0,0) показывает, на каком удалении точка от вас. Заметьте я поворачиваю только vec(100,0,0). Если бы я поворачивал Vec+vec(100,0,0) то это бы повернуло всю конструкцию относительно центра карты. Это не то, что нам нужно.
Отписывайтесь, если вам, что-либо непонятно в этой теме.
ОбъектыОбъекты достаточно часто используются в E2. Они часто используются для создания роботов, определения игроков и многих других вещей. Множество функций, использует объекты для манипуляции ими, также из объектов можно получить множество данных. Объектами могут быть: игрок, npc, предмет, транспорт или переменные сервера. В первых 5 строках должно быть объявлено об объектах как,
E:entity.Основными командами, получающие информацию из объектов, являются,
• E:pos
• E:height
• E:shootPos
• E:owner()
• etc…
Пока вы только начинаете учить объекты, вам лучше использовать Target Finder.
Презабавнейшая команда, которую я достаточно часто использую
Эта команда выдает как объект то, к чему прикреплен ваш чип.
Также
выдает как объект то, на что вы смотрите
Вот пример использования чипа, для извлечения вектора из target finder.
@name Vector
@inputs Player:entity
@outputs X Y Z
@persist
@trigger All
Location = Player:pos()
X = Location:x()
Y = Location:y()
Z = Location:z()
Для подключения надо подключить вход
Player к выходу target finder
1[entity].
Я рекомендую посмотреть на вики гаррисмода, во вкладке E2
МассивыМассивы это таблицы наполненые различной информацией, такой как строки, числа, объекты etc. Массив должен быть назван в первых 5-ти строках как R:array. Можно использовать для хранения информации
R:setString(#,String)
R:setEntity(#,Entity)
R:setNumber(#,Number)
R:setVector(#,Vector)
Решетки означают индекс(номер ячейки в которой будет храниться информация).
Вот небольшой пример
@name
@inputs
@outputs Out
@persist R:array
@trigger all
R:setNumber(5,500)
Out = R:number(5)
Я помещаю значение "500" в 5 ячейку массива. Затем я вызываю это число командой R:number(5).
В одном из последних обновлений wiremod появилась возможность создавать массивы следующим образом
@persists Ary:array Blah:array Yargle:array
Ary[1,number]= 100
Blah[56,vector]= vec(100,100,100)
Yargle[1337,string]= "Roflsocks"
СтрокиСтроки обычно используются для консольных экранов, чат-ботов и команд поиска. Строки должны быть обозначены в первых 5 строках как S:string. Все строки обязаны быть в кавычках.
Вот маленький пример, выводящий имя объекта, полученного с target finder, через S:print()
@name Test
@inputs Player:entity
@outputs
@persist Name:string
@trigger all
Name = Player:name()
if(Player:isAlive())
{
print("Where is "+Name)
}
Если создать чип без условия, он будет работать, но у вас не будет времени, чтобы подключить чип. Добавление if(Player:isAlive()) значит, что чип запустится только тогда, когда объект жив.
Когда вы создадите чип, он должен вывести голубые буквы в чат
“Where is Bob”
Подразумевает, что ваш target finder засек Боба.
Также возможно отрывать куски строк с командами left() и right()
Возьмем фразу за пример
"THISISATEST"
Вы получите
"THISISATEST":left(4) = "THIS"
"THISISATEST":right(4) = "TEST"
Вы также можете воспользоваться такой полезной функцией, как Explode, которая разбивает строку в массив.
Используется по такому принципу: S:explode(***)
*** = Разделитель символов.
Пример
Array = "This , Is , A , Test":explode(",")
Разобьется в
Array:string(1) = "This "
Array:string(2) = " Is "
Array:string(3) = " A "
Array:string(4) = " Test"
Команды поискаКоманды поиска используются для поиска игроков, предметов, NPC, транспорта, и элементов карты. Тема достаточно проста, чтобы использовать ее на свои нужды. Обязательно использовать interval() иначе вы будете получать только данные, полученные в момент создания чипа.
Вот основные команды
findByModel("String") – Поиск по модели.(когда будете в меню, кликните правой кнопкой мыши на нужном объекте, затем замените “string”)
findByPlayerName(“String”) – Поиск по имени игрока
findByClass(String) – Поиск по классу( "Player" "Npc_" "Prop" "Vehicle")
После поиска, вам нужно вывести информацию о найденном объекте, можно использовать команды find() или findResult()
findResult(#) - # is номер объекта, such например игрока. Если было найдено 3 игрока, findResult(2) зафиксирует второго.
find() – тоже самое, что и findResult(1)
Пример получения информации из объекта показан ниже.
@name
@inputs
@outputs
@persist Player:entity
@trigger All
interval(10)
findByClass("Player")
Player = findResult(2)
Чип получает информацию о втором подключившемся игроке. Теперь можно вывести позицию с помощью Player:pos() или высоту Player:height().
ApplyForceApplyForce много используется. Аналог вектороного трастера, однако он использует сам объект, как точку приложения силы
Можно заставить приложить силу на все предметы разом. Очень простая функция, если знать форумулу. Кстати вот она.
V = (Точка назначения) – (точка отправки)
P:applyForce((V*25 – P:vel()) * P:mass())P - предмет, а V вектор
Вот пример чипа
@name E2 Hat
@inputs
@outputs
@persist V:vector
@trigger All
interval(10)
V = owner():shootPos() + vec(0,0,50) - entity():pos()
entity():applyForce((V*25 - entity():vel()) * entity():mass())
entity() - это сам чип, а shootPos точка, куда стреляет владелец чипа.
Также есть applyAngForce, который позволяет вращать объект вокруг своей оси, это просто.
Угол состоит из 3-х компонентов тангаж(pitch), рыскание(yaw) и крен(roll),

давайте усовершенствуем предлагаемый выше чип
@name E2 Hat V2
@inputs
@outputs
@persist V:vector A:angle
@trigger All
interval(10)
V = owner():shootPos() + vec(0,0,50) - entity():pos()
A=ang(100,0,0)
entity():applyForce((V*25 - entity():vel()) * entity():mass())
entity():applyAngForce(A*5)
Данный чип будет летать в точку выстрела при этом постоянно крутясь (Если я ошибаюсь, пожалуйста поправьте меня).
Команды чата.Команды чата - простой текстоприемник. Эти команды связаны с использованием строк, как правило, не слишком много. Так как их немного, я приведу все
runOnChat(1) – Тоже самое, что и интервал, только он обновляет чип только, тогда, когда кто-нибудь скажет в чат любое сообщение.
chatClk() Подает на выход 1, когда в чат сказали, иначе принимает значение 0.
chatClk(E) Тоже самое, что и выше, но завязанное на одном игроке.
hideChat(N) Если N != 0, скрыть обрабатываемое сообщение.
lastSpoke() Выводит игрока, добавившего последнее сообщение в чат, как объект.
lastSaid() Выдает последнее сообщение, полученное из чата.
lastSaidWhen() Выводит время с последнего сообщения в секундах.
lastSaidTeam() Выдает 1, если сообщение было сказано в чат группы.
Также есть несколько команд, по действию совпадающих с указанными выше, но завязанных на определенных объектах(игроках).
chatClk(E)
E:lastSaid()
E:lastSaidWhen()
E:lastSaidTeam() Вот пример простого чипа, которое выдает значение “
1” на выход, при упоминании секретного слова “
ka-boom”
@name Explode
@outputs Explode
@trigger All
runOnChat(1)
if(lastSaid() == "ka-boom") {Explode = 1} else {Explode = 0}
Если кто-либо скажет “
ka-boom” в присутствии данного чипа на сервере значение выхода “
Explode” станет равным 1 до тех пор пака не будет сказано, что либо еще.
Также этот чип можно завязать на владельце
@name Explode
@outputs Explode
@trigger All
runOnChat(1)
if(owner():lastSaid() == "ka-boom") {Explode = 1} else {Explode = 0}
ConCmd(Консольные команды)"Concmd", сокращение от "Консольная команда", функция, позволяющая вводить любые команды в консоли g-mod, естесственно, если у вас нету доступа к вводимой команде вы не сможете использовать ее. Обычно используется в создании чат-ботов, также используется админами, для удобного управления своими серверами. Данная функция отключаемая, чтобы выключить ее необходимо ввести "wire_expression2_concmd 0. Для включения, вместо нуля ставится 1.
Если включен интервал, вы должны добавить дополнительное условие в чип. Будет неочень приятно, если чип "засрет" вам всю консоль. Этого можно избежать, используя "
~", чтобы чип следил только за измененниями значения и не реагировал на постоянное значение. Вот простой пример, в котором, при нажатии кнопки, заставляет вас говорить "Hi"
@name Concmd HI
@inputs Button
@outputs
@persist
@trigger All
if(Button & ~Button) {concmd("say Hi")}
Я предполагаю, что вы заметили
(Button & ~ Button),это логика, о которой я говорил ранее. Расшифровывается так:
Если на кнопку подается 1 и значение изменилось => concmd
Это предотвратит "засирание" консоли постоянно нажатой кнопкой, чип будет обновляться только тогда, когда занчение измениться и будет равно 1. Однако кнопка может изменить значение только два раза, при включении и выключении.
Поскольку concmd использует строки, он подчиняется тем же принципам. Можно добавлять, делить, резать, почему бы и нет. Вот пример.
@name Concmd HI
@inputs Butto
@outputs
@persist
@trigger All
if(Button & ~Button) {concmd("say Hi, I am "+owner():name())}
Не забудьте поставить пробел перед закрытием кавычек, иначе получится "HI, i amBob"(к примеру)
ДетекторыВстроенный детектор в разы лучше представленного в wire, хотя бы потому что в нем больше возможностей. Однако, команды тут чуть сложнее. Для начала объявите переменну. R:
ranger. Также вам понадобится interval, так как чип не может без указания сам себя обновить. Поскольку функция сложновата, я объясню в общем виде.
ranger(дистанция)- Создает детектор, исходящий от чипа и направленный вверх(если чип повернуть, то луч тоже будет повернут, так как считается верх модели)
ranger(range,x,y)- Создает детектор, луч которого идет из чипа в сторону локальных координат. То есть, если вы напишете
ranger(500,50,0), луч будет сдвинут вправо, т.к. X=50
rangerOffset(Vector,Vector) - Моя любимая функция. Создает луч детектора между двумя координатами
Вот несколько дополнительных настроек для детектора
rangerHitWater(N) - 1 / 0
rangerIgnoreWorld(N) - 1 / 0
rangerDefaultZero(N) - 1 / 0Следующие команды позволяют извлечь данные из детектора.
R:distance() - Выводит расстояние
R:position() - Выводит координату соприкосновения
R:entity() - Выводит зафиксированный объект
R:hit() - Если в луч детектора попал объект, выводит 1, иначе 0
Казалось бы, запоминать много, но вы будете редко использовать их вместе. Вот простой пример, как говорится "только заспавнь"
@name Trip mine
@inputs
@outputs Explode
@persist R:ranger
@trigger All
interval(10)
R = ranger(500)
rangerIgnoreWorld(1)
rangerDefaultZero(1)
Explode = R:hit()
Консольные экраныЯ, конечно понимаю, что это уже немного не актуально, но люди пользуются.
Консольные экраны(далее
«консоль») очень просты для изучения, и при умелом обращении могут предоставить вам много веселых часов. Обычно люди изучая консоль пишут огромный чип , а потом даже не могут подключить. Консоль необходимо подключать к чипу через wirelink(см далее), хотя некоторые особо выдающиеся личности писали на чистых входах.
*** Важно: не используйте interval, до тех пор пока вам это действительно не понадобится. Может вызвать жуткие лаги ***
Для начала установка
Создаем консоль, любого необходимого размера. Затем подключаем к чипу через
wirelink.Теперь пишем собственно чип. Помните, что
wirelink подключется к input. Подключайте выход консоли с параметром
[wirelink] к соответствующему входу на чипе. Не забудьте присоединить выход clk и reset к кнопкам В принципе все готово.
Существует множество комманд для консоли, некоторые сложнее других, но так как это руководство для начинающих, я покажу только основы..
S:writeString("string",Столбец,Строка,Цвет шрифта,цвет фона,Мигание(1/0)В принципе, можно не писать все это и писать
S:writeString("string",Столбец,Строка) и будет белый текст без фона
Так же существуют комманды, позволяющие напрямую обращаться к памяти консоли
S[2041,number]=1 — Очищает ваш экран
S[2042,number]=цвет(RGB)-- Меняет цвет экрана
Пожалуйста будьте аккуратны с этими коммандами, вы можете запороть весь чип и создать страшные лаги.Так, прежде чем начать я хочу рассказать вам как работает система цветов для консоли. Это обычная система RGB(255,255,255)(на время забудем о существовании alpha канала), но с максимальнам числом 9 и записывающиеся без запятых. Например,
красный будет – 900(9,0,0) , зеленый 90 и синий 9. Как вы заметили, если перед нужным нам числом стоят нули их просто пропускают. В принципе, если вы их запишите ничего страшного не будет, чип будет работать нормально, но вес чипу вы этим прибавите, да и работать он будет немного медленнее(скорее всего вы даже не заметите).
Вот простой пример
@name
@inputs S:wirelink
@outputs
@persist
@trigger all
S:writeString("Hello World",1,1,9,90,0)
Просто, не так ли? Данный чип решает классическую задачу: пишет “
Hello world”, синими буквами на зеленом фоне.
Теперь посложнее. Используем функцию прямого обращения к памяти и заменим цвет фона.
@name
@inputs S:wirelink
@outputs
@persist
@trigger all
S:writeCell(2042,999)
S:writeString("Hello World",1,1,9,90,0)
Перед тем, как вы начнете играть с этими коммандами, не забывайте очищать экран перед каждым изменением.
В этом примере уже не нужно каждый раз жать кнопку очищения
@name
@inputs S:wirelink Button
@outputs
@persist
@trigger all
S:writeCell(2042,999)
if(!Button) {
S:writeCell(2041,1)
S:writeString("Hello World",1,1,9,90,0)
} else {
S:writeCell(2041,1)
S:writeString("Goodby World",1,1,9,90,0)
}
Когда
Button==0, на экране написано "
Hello world", но когда значчение меняется на
1, экран пишет "
Goodby World" Для этой задачи необходимо очищать экран после каждой смены текстов. Иначе ваши надписи просто наложатся друг на друга и получится каша.
Очевидно, что с помощью консоли можно добавлять не только строки, но и другие значения, так вот например, чип показывающий ваше имя и количество HP.
@name
@inputs S:wirelink Button
@outputs
@persist
@trigger all
S:writeCell(2041,1)
S:writeCell(2042,999)
S:writeString("Hello,",1,1,9,90,0)
S:writeString(entity():owner():name(),1,2,9,90,0)
S:writeString("Your HP is ",+entity():owner():health(),1,3,9,90,0)
Вот примерный вид выходных данных для этого чипа:
"Hello,
Darkan Your HP is 100"Немногие люди любят совмещать клавиатуры и консоли. Это чуть посложнее, но я думаю, что моё руководство будет неполным без этого объяснения.
Клавиатуры не работают, так как мне бы хотелось, вместо нажатого символа он выводит число(которое является его кодом в таблице ASCII).
Итак, чтобы преобразовать этот ASCII-код в понятные глазу символы используют команду
toChar(), где в скобках обычно ставят клавиатуру
В следующем чипе мы выведем строку из памяти клавиатуры.
@name
@inputs S:wirelink Keyboard
@outputs
@persist String:string
@trigger all
if(first()) {
S:writeCell(2041,1)
S:writeCell(2042,999)
}
if(Keyboard & ~Keyboard) {String += toChar(Keyboard)}
S:writeString(String,2,2,9,90,0)
Вы должно быть заметили, что я ввел
if(first()) , это значит, что только если чип был только что создан, будет произведено действие. Я так сделал, потому что не хотел бы чтобы чип очищался каждые 100 милисекунд, если бы так было мы не смогли бы получить нормальные значения.
String = String + Letter. {String += toChar(Keyboard)}
По мере перевода буду выкладывать дальше.
Это перевод руководства по Exp2 от SpectreCat.