Перейти к содержимому

Поиск по сайту

Результаты поиска по тегам 'opencomputers'.

  • Поиск по тегам

    Введите теги через запятую.
  • Поиск по автору

Тип публикаций


Блоги

  • Робот Байт
  • Fingercomp's Playground
  • 1Ridav' - блог
  • Totoro Cookies
  • Блог cyber01
  • IncluderWorld
  • KelLiN' - блог
  • Крутой блог
  • eutomatic blog
  • Programist135 Soft
  • Сайт в сети OpenNet
  • PieLand
  • Очумелые ручки
  • Блог недоблоггера
  • В мире Майнкрафт
  • LaineBlog
  • Квантовый блог
  • Блог qwertyMAN'а
  • some blog name
  • Дача Игоря
  • Путешествия Xytabich'а
  • Рецепты программирования
  • Шкодим по крупному
  • 123

Форумы

  • Программирование
    • Программы
    • База знаний
    • Разработчикам
    • Вопросы
  • Игровой раздел
    • Игровые серверы
    • Моды и плагины
    • Жалобы
    • Ивенты и конкурсы
    • Файлы
  • Общение
    • Задать вопрос
    • Обратная связь
    • Беседка
    • Шкатулка
  • Технический раздел
    • Корзина

Группы продуктов

Нет результатов для отображения.


Искать результаты в...

Искать результаты, которые...


Дата создания

  • Начать

    Конец


Последнее обновление

  • Начать

    Конец


Фильтр по количеству...

Зарегистрирован

  • Начать

    Конец


Группа


AIM


MSN


Сайт


ICQ


Yahoo


Jabber


Skype


ВКонтакте


Gtalk


Facebook


Twitter


Город


Интересы

Найдено 252 результата

  1. У многих новичков возникает вопрос - где писать код для OpenComputers? Кто-то советует VS, кто-то сторонник IDEA, кому-то по душе Atom, а кто-то вообще пишет код сразу в игре. Я решил пополнить этот список вариантов собственным решением. Вашему вниманию представляется интегрированная среда разработки (ИСР) под OpenComputers - OCLIDE. Проект находится на стадии публичного альфа-тестирования, поэтому критика приветствуется. На данный момент в ИСР присутствуют: - Подсветка Lua-синтаксиса - Система проектов - (нестабильная) Интеграция с OCEmu (билд с OpenOS 1.6) В планах на будущее: - Автодополнение кода - Добавление эмулятора на базе Ocelot Brain - Адаптация кода под JVM на Linux и Mac - Опции настройки цветовой палитры редактора и используемого стандарта Lua - Поддержка сторонних переводов приложения Ссылка на проект: https://github.com/Vladg24YT/Oclide/ Скриншот:
  2. В 2015-2016 году тут разрабатывался файловый менеджер Midday Commander, который по неизвестным причинам оказался заброшен. Увы, он много чего не умеет, есть неприятные баги, однако других ФМ под компьютеры я не встречал (может плохо искал). Собственно, это и послужило появлением моего форка от оригинального MC. И имя ему Midday Commander Plus! Репозиторий на GitHub Основные нововведения: Поддержка видеорежима 160x50 Тени у окон (как у Norton-а) Поддержка манипулятора "Мышь" (в том числе колесика) Мультиязычность (языковые данные вынесены в отдельный .mcl файл) Поддержка тем (данные о цветах элементов вынесены в отдельный .mct файл) Система ассоциаций Параметры программы расположены в конфиг файле - /etc/mc.cfg Скачать: pastebin run pc73b8bB С программой поставляются: Файлы mcl для русского и английского языка (по умолчанию в конфиге стоит английский) Три mct темы - "Standart", "Redstone" и "Darkness" (увы, я не дизайнер, поэтому отсутствие вырвиглазности в темах не гарантировано) Краткая справка: Коротко про ассоциации:
  3. Disclaimer: 1. Ниже в этой теме может встречаться код, который может нанести ущерб вашему серверу. Эти образцы предоставлены лишь в ознакомительных целях! Я не несу ответственности за положенные ими сервера Майнкрафта. 2. Я не залазил глубоко в исходники OpenComputers, поэтому могу где-то ошибаться. Одна из часто встречающихся проблем в OpenComputers - превышение таймаута выполнения программы. При этом выбрасывается ошибка TLWY (too long without yielding). При лагах сервера эта ошибка может возникать совершенно случайным образом, иногда даже в консоли OpenOS. Иногда компьютер может вообще выключиться с синим экраном. Чтобы создать стабильную систему, проблему TLWY надо решить. У меня были такие идеи: 1. Обёртки из pcall. Это будет некрасиво выглядеть и понизит максимальную глубину рекурсии. 2. Принудительная вставка yield в код. Проблемы такого подхода - потеря множества событий и необходимость парсинга кода. 3. Принудительное прерывание кода по обращениям к глобальным переменным. Проблема - невозможность отследить вызов анонимной функции. Например, так не прервать код (function(f) return f(f) end)(function(f) return f(f) end). 4. Интерпретировать байткод Lua. Самое доступное решение, но надо знать формат байткода и обновлять с выходом каждой версии Lua. 5. Переписать виртуальную машину Lua так, чтобы она сама уступала управление другим потокам (тогда ошибка TLWY уйдёт в прошлое). Для этого надо договориться с разработчиками OpenComputers и уметь программировать на Java. Есть ли какие-то варианты избежания TLWY, которые я упустил?
  4. В моде OpenComputers есть интересное устройство, которое позволяет определить плотность блока на расстоянии. Но вот беда, данные он выдает довольно шумные и чем больше расстояние, тем больше шума. Чтобы определить подлинную плотность блока, можно просканировать его несколько раз, а результат усреднить. Шум, мешающий сканированию, имеет вероятностную природу. И после нескольких сканирований можно статистически найти, какая вероятней всего плотность у блока. За один тик мы можем просканировать 64 блока. Чтобы проанализировать всю доступную область (65 x 65 x 64) сотней итераций, нам понадобится 422500 тиков, что равно 21125 секунд или 352 минуты, то есть без малого 6 часов. Но сколько раз надо сканировать? Сто? Тысячу? Нам открыто тайное знание и есть точный ответ. Один. Всего за одно сканирование мы можем найти руду среди любых других блоков. Если хочется абсолютной уверенности, придется сделать пару магических пассов и просканировать повторно. Начнем с теории. Для начала откроем код мода и найдем функцию geolyzer.scan, она располагается [здесь] src/main/scala/li/cil/oc/integration/vanilla/EventHandlerVanilla.scala и называется onGeolyzerScan() Просмотрев код, мы можем понять, что функция принимает параметры, по этим параметрам сканирует блоки в мире. Делает разные проверки вроде world.blockExists(x, y, z) && !world.isAirBlock(x, y, z), чтобы убедится, что блок есть. Потом получает информацию о блоке по координатам, делает еще несколько проверок (опять проверить, что блок все-таки есть block != null, проверяет дополнительные параметры: includeReplaceable, isFluid(block), block.isReplaceable(world, blockPos.x, blockPos.y, blockPos.z)) Потом происходит измерение расстояния до блока. И в конце берется плотность, смешивается с шумом и расстоянием. Результат добавляется к таблице блоков и отправляется игроку. Вроде-бы ничего необычного. Шум, расстояние, плотность. Нам и так известна зависимость силы шума от расстояния. И вот тут начинается волшебство. Рассмотрим поподробнее код вычисления итоговой плотности блока. e.data(index) = e.data(index) * distance * Settings.get.geolyzerNoise + block.getBlockHardness(world, x, y, z) Коротко можно это записать в виде формулы: R = G * D * N + H G - это сгенерированный шум. D - расстояние до блока. N - множитель шума из конфига (стандартно - 2). H - настоящая плотность. R - результат работы геосканера. Если мы попробуем в качестве эксперимента отнять от результата предполагаемую плотность, то ничего нового не узнаем. Если обратим все операции с известными значениями, то получим только шум. А можем ли мы так же разобрать формулу шума? Давайте попробуем. Несколькими строками выше [ссылка]. Можно наблюдать получение массива случайных байт. val noise = new Array[Byte](e.data.length) world.rand.nextBytes(noise) Далее следует нормализация значений. noise.map(_ / 128f / 33f).copyToArray(e.data) Хм. Так-так-так. Если мы это все обьеденим с предыдущей формулой, то получится что-то вроде такого: R = G(RANDOM_BYTE / 128 / 33) * D * N + H И что это нам дает? А то, что исходное псевдослучайное число имеет жесткую дискретность. ГПСЧ дает случайные числа типа byte, а это только 256 значений (-128, +127). Нам известны все значения, кроме H и RANDOM_BYTE, что нам это дает? Мы можем предположить значение H и обратить всю формулу. (R - H) / D / N * 128 * 33 Для стандартного конфига можно сократить до: 2112 * (R - H) / D А теперь тайное знание для тех, кто не понял самостоятельно. Мы взяли желаемую плотность блока (например 3 для руды). Подставили вместо H. Получили случайное значение. Можем легко определить, угадали ли плотность или нет. Из-за дискретности случайных значений генератора, распределение вероятностей для блоков с разной плотностью не одинаковое. Перейдем к практике. Вот код простого скрипта, который в заданном радиусе ищет блоки с нужной плотностью. Результат выводится на голопроектор. local sqrt = math.sqrt local component = require('component') local geolyzer = component.geolyzer local hologram = component.hologram local function distance(x, y, z) return sqrt(x^2 + y^2 + z^2) end local function magic(R, H, D) return 2112 * (R - H) / D % 1 end local function visualize(hardness, elevation, size) hologram.clear() hologram.setScale(9) local blocks, result for x = -size, size do for z = -size, size do blocks = geolyzer.scan(x, z, elevation, 1, 1, 32) for i_y = 1, 32 do result = magic(blocks[i_y], hardness, distance(x, i_y+elevation-1, z)) if blocks[i_y] ~= 0 and (result > 0.9998 or result < 0.00005) then hologram.set(x+24, i_y, z+24, true) end end end end end local hrd, ele, siz = table.unpack({...}) hrd = hrd or 3 ele = ele or -32 siz = siz or 16 visualize(hrd, ele, siz) А вот результат: При сканировании заметны артефакты. Когда разные плотности близки на целочисленных расстояниях, позникают коллизии. Это можно частично компенсировать, если есть блок кандидат на ошибку. На любом расстоянии можно рассчитать абсолютный минимальный и максимальный уровень шума. С расстоянием, у близких плотностей пересечение значений увеличивается, но если плотность блока не в области пересечений, то можно точно определить к какой области он относится. Пересечение плотностей руды (3) и камня (1.5), точками обозначены три сканирования блока руды. Результаты обратного вычисления для разных плотностей хорошо это демонстрируют. Для компенсации артефактов надо ввести дополнительное условие: полученный RANDOM_BYTE должен быть в диапазоне -128:127. Вот финальный скрипт и результат. local sqrt = math.sqrt local component = require('component') local geolyzer = component.geolyzer local hologram = component.hologram local function distance(x, y, z) return sqrt(x^2 + y^2 + z^2) end local function magic(R, H, D) return 2112 * (R - H) / D end local function visualize(hardness, elevation, size) hologram.clear() hologram.setScale(9) local blocks, result for x = -size, size do for z = -size, size do blocks = geolyzer.scan(x, z, elevation, 1, 1, 32) for i_y = 1, 32 do result = magic(blocks[i_y], hardness, distance(x, i_y+elevation-1, z)) if blocks[i_y] ~= 0 and result > -128 and result < 127 and (result%1 > 0.9998 or result%1 < 0.0002) then hologram.set(x+24, i_y, z+24, true) end end end end end local hrd, ele, siz = table.unpack({...}) hrd = hrd or 3 ele = ele or -32 siz = siz or 16 visualize(hrd, ele, siz) Для более точного определения плотности можно сделать два сканирования. Одно сместить относительно другого так, чтобы расстояния с артефактами не совпадали. Чтобы не выполнять тяжелую операцию sqrt, можно создать словарь, где [x^2 + y^2 + z^2] = sqrt(x^2 + y^2 + z^2), всего понадобится 1742 уникальных значений. P.S. Пост является компиляцией знаний из [этой] темы. Собрал, чтобы перевести и опубликовать на официальном форуме. Автор идеи хакнуть геосканер - @eu_tomat
  5. Итак, чет вштырило мне написать хоть какой-то нормальную глядяелку для опенкомпов, что бы можно было просто адекватно открыть страничку и ее смотреть, даже на текстовом уровне. Так что первое что приходит на ум это вспомнить как выглядит сам HTML. Википедия говорит: И тут уже получается что нужно писать текстовый движок который умеет читать эту разметку (еще не забываем что она иногда бывает сломаной) Для начала я вспомнил что HTML очень похож на XML и начал гуглить на эту тему либы. Нашлась либа xml2lua, но НО. В XML никогда не было тегов без закрытия. Так что либа благополучно шлет нас в пень когда мы читаем содержимое head. Так что задав вопросы Яндексу я получил нормальную либу lua-htmlparser. И она вполне норм работает. Причем можно получить данные какие захочешь. Что очень упрощает работу. И сама либа работает без бубнов под OC. Так что следующий шаг это сделать удобный установщик для либы и уже писать скрипт для отображения, проблемы скорее всего всплывут на этапе интерфейса, так что будем страдать :/
  6. Последняя версия : 2.2 Команда для установки : pastebin run ngQT9YF8 Системные требования: Корпус компьютера, екран - 1 Tier Процесор - 1 Tier Видеокарта - 1 Tier Память - 1.5 Tier (x1) Жёрсткий диск - 1 Tier Дисковод, клавиатура, Lua BIOS - должны присутствовать Краткое описание: Inerpat - примитивная система для запуска на любом ведре, и инструмент для создание и отладки EEPROM кода. Также она поможет в ситуации, когда ваш компьютер превратился в "обожённый строительный материал" Специальные клавиши: ALT - Открыть главное меню CTRL - Открыть меню файла/папки. В редакторе - меню сохранения. TAB - Навигация по доступным файловым системам Скриншоты:
  7. Решил сделать типа Google Maps на геосканере. Только при сканирование местности с большими погрешностями он может различать только землю и воду(у ней с лавой одинаковая прочность). https://pastebin.com/9B8NXab8
  8. ПЛАН БИТВЫ 1) Схема 2) Пояснение Внутри банка - кабинки. Внутри кабинок - терминалы. Внутри терминалов - программа, которая общается с клиентом, узнает, какого черта ему нужно и затем общается с банковским серваком на предмет возможностей и толщины кошелька клиента. Возле терминалов - сундуки, в которые клиент кладет свой товар. И откуда забирает купленое. За стеклянной стенкой - орава роботов. Роботы берут товары, которые продал клиент, из сундука, пересчитывают и увозят в "хранилище" (или просто выкидывают в дезинтегратор, все равно у админа креатив). Затем они получают в "хранилище" покупки клиента и везут их обратно. Командует парадом - "шеф", банковский сервак. Он следит за прайсами (которые отправляет на дисплеи в вестибюль), отвечает на запросы терминала и роботов и без зазрения совести генерирует из воздуха новые товары в "хранилище", пользуясь читкодами и дебаг-платой. Кроме того он ведет базу данных клиентов, записывая состояние счета каждого. Общение идет по беспроводной связи, строго по внутренним адресам сетевых плат. Ни байта наружу, ни байта извне. Итого: для работы банка потребуется написать три программы. 1) Терминал. По большей части - это просто графический интерфейс, который действия юзера преобразует в запросы к "шефу". 2) Робот. Робот передвигается по банку, отвечая на вызов клиента и перевозя товары. Он проверяет ID товаров, и их количество и передает эту инфу "шефу". 3) Сервер. Сам "шеф". Командует парадом.
  9. Сделал для ОС поддержку любых http-методов. Чтобы всякие веб сервисы использовать полноценно. https://github.com/MightyPirates/OpenComputers/pull/3374 Есть одна проблема - не получается заюзать через apache http прокси из MinecraftServer.getServer.getServerProxy. Кто шарит, помогите, плиз.
  10. jammer312

    GPSv2

    Я уже закидывал сюда похожую прогу года три назад, но она кривая-косая, не очень надежная, ну и геморная в плане развертки. Эта версия более компактная (и простая в постройке), использует упрощенные вычисления и более эффективный механизм передачи сообщения меж компонентами; помимо этого используются особенности микроконтроллеров для автоматической конфигурации системы. Код в репозитории, для контроллера и периферии. Для постройки одного модуля нужно 3 микроконтроллера второго уровня и один компьютер второго уровня, начинка одинаковая, в каждом нужен процессор, плата оперативной памяти и беспроводная плата второго уровня. Операционная система на компьютер не нужна. В данном контексте компьютер будет контроллером, микроконтроллеры - периферией. Код для них пишется на eeprom. Помимо кода на eeprom можно записать параметры работы (в сегмент данных), для периферии это просто одно число - порт, на котором ожидаются запросы, для контроллера эта строка вида "port: \d+, key: \w+, anchor: \{-?\d, -?\d, -?\d\}", в ней записаны порт, на котором ожидаются запросы и с которого отправляются ответы, ключ-пароль (для настройки координат через беспроводные сообщения, об этом потом), а также якорь - координаты компьютера. По умолчанию используется 312 порт, ключ-пароль admin, и координаты {0, 0, 0}. Схема постройки простая, нужно установить где-либо компьютер-контроллер, а потом вплотную к трем его сторонам установить микроконтроллеры-периферию, при этом не должно быть такого, что контроллер расположен между двумя периферийными микроконтроллерами (если с одной стороны есть микроконтроллер, на противоположную ставить нельзя). Помимо этого лицевые стороны микроконтроллеров (одна из 6 сторон имеет иную картинку, и, что важно, к ней не присоединяются провода) не должны смотреть на контроллер. После постройки нужно единожды включить каждый из 4 компонентов, они при этом сами быстро выключатся. Это нужно для настройки модема, для того, чтобы компонент просыпался при получении определенного сообщения. После этого система уже готова к работе, но при этом не знает (если это не было заранее записано в сегмент данных eeprom контроллера) своих координат, и будет отвечать на все запросы относительными. Для включения системы достаточно отправить сообщение "gps_wake", при этом система запустится, сконфигурируется, и после этого отброадкастит сообщение "gps_ready". Такое же сообщение, но на адрес того, от кого пришло "gps_wake", отправится в случае, если система уже активна на момент получения запроса. Для того, чтобы сообщить координаты контроллеру, нужно отправить сообщение "gps_anchor", после которого идет ключ-пароль (записан в сегменте данных eeprom контроллера), после которого идут координаты (3 числа) контроллера. Координаты при этом запишутся на eeprom, т.е. при выключении-включении они все так же будут известны контроллеру. Для использования системы достаточно отброадкастить "gps_request", контроллер ответит (именно тому, от кого запрос) сообщением "gps_response", после которого идут 3 числа - координаты x, y, z. Таймаут обработки запроса - 1 секунда, если вдруг 1 секунда прошла, а ответа не пришло - что-то не так. При этом контроллер при таймауте запроса забывает все его данные, поэтому кривой запрос (например, отправленный сквозь толстую стену, которая снизила мощность сигнала настолько, что он дошел лишь до части системы) не будет "портить" последующие. Имеет смысл "включить" систему перед первым запросом (послать "gps_wake" и дождаться ответа "gps_ready"), на непосредственно включение уйдет до примерно 2 секунд (вообще с потолка взял, на деле должно шустрее, но внутри таймаут привязки стоит именно 2 секунды, после этого он отправляет 3 сообщения периферии и посылает сообщение о готовности), включенная же сразу ответит. Картинка для референса:
  11. Многие, кто играл в майн с древних времен, помнят, что был такой замечательный мод RedPower2. Помимо всяких крутых механизмов там были компьютеры, работающие на forth-системе. Функционал, правда не богатый, можно было только мигать цветными кабелями. Мод развития не получил, автор пропал. Потом был мод NedoComputers, но он тоже не долго прожил и особого распространения не получил. Есть идея, написать виртуальную машину для OpenComputers. Язык Forth невероятно примитивен, синтаксис простой и лаконичный, базовая система легко уместится на EEPROM. Но есть пара вопросов в реализации. Так как придется писать интерпретатор/компилятор на языке высокого уровня, надо чем-то пожертвовать или отойти от стандарта. Язык плотно работает со стеком. Есть стек данных и стек возвратов (второй пока не трогаем). Адресация 16 бит, следовательно, диапазон памяти = 64КБ. Отсюда имеем первую проблему, придется дробить float64 и имитировать 16 битные числа. Можно не дробить, памяти у нас более чем достаточно. Хотя, в более новых стандартах, реализована работа с 32 и 64 битными числами, написанная на самом Форте. Можно это обыграть, используя стандартный функционал Lua. Еще из-за особенностей выравнивания памяти, у чисел с плавающей точкой отдельный стек и отдельная адресация. Это можно тоже игнорировать и запихнуть float'ы в стек данных (а может и нельзя, тут пока не понятно). Вообще, все это описывается самим Фортом, но имея уже готовый интерфейс к математическому сопроцессору, было бы глупо писать всякие sqrt/sin/tanh жонглированием на стеке. Еще стандарт ANS94 требует много лишнего, вроде доступа к ассемблеру, своеобразной работы с железом и мусорных функций. Поэтому, лучше видится стандарт FORTH-83, он описывает язык очень обобщенно. Только немного расширить его до реалий опенкомпов. Ссылки: стандарт 83 года FORTH-83, слова стандарт 94 года ANS94, слова краткое введение в синтаксис
  12. https://pastebin.com/4BXZP7FZ Использует шрифт Брайля Скрины: Пример:
  13. привет игроки! не знаю зачем но я написал константы. они замораживают значение переменной. код: const={} setmetatable(const,{__call=function(self,key) rawset(self,key,{rawget(_G,key)}) rawset(_G,key,nil) end}) setmetatable(_G,{__index=function(self,key) if const[key] then return const[key][1] else return rawget(self,key) end end,__newindex=function(self,key,value) if not const[key] then rawset(self,key,value) end end}) пример: pi=math.pi const "pi" pi="error" print(pi) --> 3.1415926535898 ещё один пример: const "_" _=123 print(_) --> nil
  14. https://pastebin.com/LFAEzkP6 Делал погода назад. Откопал в бекапах.
  15. Описание: необходима программа автокрафта для крафтов с нестандартной сеткой крафта, к примеру, как на экстремальном верстаке из мода avaritia, но не только на него Награда: (остаёмся верны себе) Большое человеческое Спасибо! Ибо я так и не развился на вашем сервере Оборудование: любое из opencomputers, computercraft, ae2 и еще 200 модов в сборке (желательно без роботов, они странно себя ведут на сервере) Требования к ПО: возможность задать рецепт самому, желательно (не знаю, реализуемо ли) забор необходимых ресурсов из МЭ сети и крафт всего для заказанного ресурса, минимальный интерфейс Связь: писать в эту тему, можно в личку Других подходящих програм я не нашел (или плохо искал), Для прохождения сборки нужно много ресурсов крафтящихся на верстаках с огромной сеткой крафта, руками всё крафтить крайне долго, автоматизации таких крафтов нет, точнее, что бы до нее добраться, уже нужна куча составных ресурсов Предлагаю сделать простейший интерфейс основанный на написании цифры Скан рецепта Список сохраненных рецептов 1. какойлиборецепт (при выборе рецепта начинается крафт) 1.(этот пункт если по другому никак, и если получится сделать сопутствующие крафты для основного рецепта, главное чтоб эти сопутствующие крафты не были видны в основном списке, или сделать для них отдельный пункт) Очистка верстака при какой либо ошибке Ещё что нибудь Предлагаю реализовать сам автокрафт на основе скана инвентаря как из моего предыдущего заказа, и сохранять ее в памяти, к примеру в ПК (если такой способ подойдет), так же необходима возможность или заказывать необходимый сопутствующий рецепт прямо в МЭ сети, или возможность автокрафта необходимого без заказа рецепта, с забором нужных предметов из сети или из сундука(так как для крафта нужны пластины из тинкерс констракт, желательно заказ в мэ, или возможность сделать автокрафт пластин в этой проге (имеется мод tinker I/O), или банально на экране писать "положите в сундук/МЭсеть то-то то-то") Вроде всё написал:)
  16. Начал собирать планшет, предварительно установил на жёсткий диск ос. Вот сборка. Но при запуске он просто показывает чёрный экран. Попробовал положить его в зарядное устройство и заново установить систему через компьютер, но он не предлагает выбор, куда именно я хочу её установить. В чём причина и как это исправить? Попробовал установить OpenOS через дискету и это окошко просто сжалось как моя пятая точка.
  17. Я вот не могу понять, мы с другом делаем казино и он говорит для "какого-то" блока(не помню название) нужно добавить компьютер в приват(из мода OpenComputers) я решил задать вопрос, правда ли это, помогите пожалуйста)
  18. Магазине приложений есть несколько антивирусов, они все работали одинаково: они сканировали сам lua-файл У них есть большая уязвимость: вирус может обойти защиту Например: local gqd = component local wdg = gqd.eeprom local hdwui = wgd.set hdwui("вирусный код") Я решил сделать по другому - перезаписать функции filesystem.open, filesystem.remove, filesystem.rename, eeprom.set Код местами очень нубский, простите Видео: https://drive.google.com/file/d/1yawVj2U-shAC-9G0Ic1VkqD6VRW8mmkW/view?usp=sharing и https://drive.google.com/file/d/1B_F3xOzaVJRnBZ87akXeiRDPQ8UKqMnj/view FileProtector Beta.zip
  19. Для тех, кто спешит: Потестировать онлайн: https://ocelot.fomalhaut.me/ Скачать на комп и потестировать: Ocelot Desktop На форуме давно мелькают упоминания Ocelot. Это эмулятор OpenComputers, который находится в разработке примерно с 2015 года, был несколько раз переписан и наконец увидел свет в закрытом альфа-тесте зимой 2018. Я немного отвлекся на другие проекты (привет Stem), но теперь возвращаюсь к разработке Ocelot, и с гордостью предствляю вам тизер-анонс и, по совместительству, открытый альфа-тест Ocelot. Ещё один эмулятор? Да. Будем честны. Нормального эмулятора OpenComputers не существует. Те что есть - полны костылей, не совсем соответствуют реальному моду, сложны в установке, заброшены... и так далее. Ocelot - это решение всех этих проблем. Основная идея Ocelot - взять уже существующий код мода OpenComputers, тщательно отделить всё не нужное (Майнкрафт), затем осторожно переписать то что получилось с поправкой на реалии эмулятора. Благодаря этому, Ocelot эмулирует OpenComputers с ранее невиданной точностью. Вплоть до того, что в эмуляторе могут встречаться те же самые баги, что и в моде. Что он умеет? Практически всё. В перспективе. Ocelot позволяет воссоздать схему любой сложности из любого количества блоков - мониторов, компьютеров (любой конфигурации), проводов, модемов и прочих компонентов. Он позволяет управлять скоростью работы компьютеров, позволяет изменять "игровое" время, ставить его на паузу, сохранять состояние работы компьютеров и потом возобновлять работу с любого сохранения. Сейчас доступен базовый набор компонентов и блоков. Это кабель, корпус компьютера, APU/CPU, плашки памяти, видеокарты, дата-карты, EEPROM, дискеты, жесткие диски (managed и unmanaged режимов), интернет-карта, линкед-карта, сетевая карта (проводная и безпроводная), редстоун-карта / блок и монитор. Список будет расширяться. В перспективе будет эмуляция всех блоков и компонентов стандартного OC, роботов, дронов, микроконтроллеров, серверных стоек, плюс эмуляция адаптера и интеграции с ванильными блоками и блоками других модов. Что можно потрогать? Ocelot задуман как модульный проект. А именно: Ocelot Brain Основа эмулятора - это библиотека Ocelot Brain. Она написана на Scala и может быть подключена к любому другому проекта на Scala (и, может быть, Java). Ocelot Brain - это как раз переработанный код OpenComputers в компактной и удобной форме. Отвечает за всю эмуляцию кода и компонентов, а также сохранение / загрузку проектов. Вы можете использовать его для своих проектов, можете помочь с разработкой и патчами. Проект открыт и доступен по адресу: https://gitlab.com/cc-ru/ocelot/ocelot-brain На данный момент Ocelot Brain актуален версии OpenComputers 1.7.4. Ocelot Online На основе проекта Ocelot Brain, в качестве демонстрации его возможностей, создается проект Ocelot Online. Ocelot Online это эмулятор OpenComputers в виде сайта. Да. Всё что вам нужно для его запуска - это открыть сайт. Ссылка: https://ocelot.fomalhaut.me/ Исходный код тоже доступен: https://gitlab.com/cc-ru/ocelot/ocelot-online Поскольку проект пока находится в альфа-релизе, большая часть возможностей закрыта. Доступен только один монитор на всех, который позволяет взаимодействовать с уже настроенным демо-проектом. Конфигурация проекта: Креативный корпус, CPU T3, видеокарта T3, две планки памяти T3.5, managed жесткий диск T3, unmanaged жёсткий T3, интернет карта, редстоун карта T2, дисковод с дискетой Open OS, монитор T2, клавиатура и EEPROM с Advanced Loader от товарища Luca_S. Отличия от стандартного OpenComputers: * В OpenOS уже установлен HPM. Благодаря этому можно быстро ставить разные программы через hpm install. * Вставка текста заменена с Insert на Ctrl + V. Браузер не дает изменить этот хоткей. * В редакторе edit кнопка выхода заменена на Ctrl + E. Стандартная комбинация юзается браузером для закрытия вкладок - и переопределить её нельзя по соображениям безопасности. * Вместо OpenOS EEPROM используется Advanced Loader. Это сделано для удобства и наглядности. * Не работает лок на пользователя - по понятным причинам. Ocelot Online должен так же работать на смартфонах. Однако возможно придется отключить T9 - он портит эвенты клавиатуры. В разработке находится более сложная версия, где все получат возможность зарегистрировать аккаунт и создавать личные проекты любой конфигурации. Но это дело будущего. Ocelot Desktop Это классический вариант эмулятора Ocelot в виде программы, которую можно скачать и запустить на любой операционной системе, где есть Java. Построен на Ocelot Brain и библиотеке LWJGL (как и сам майнкрафт). Разработкой занимается товарищ @LeshaInc. Протестировать проект, сообщить о багах и поддержать разработчиков можно в топике Ocelot Desktop: Альфа-тест Итак, дорогие пользователи, пишите ваши хотелки, сообщайте о багах, обо всем что работает не так как должно, и как в оригинальном OC. Я, со своей стороны, постараюсь проект не забрасывать, развивать и своевременно (или не очень) обновлять. Благодарности Над проектом также работали: @LeshaInc, @Laine_prikol, @Fingercomp и @MeXaN1cK. За что им огромное спасибо и респект. Не забудем также всех, кто помогал с альфа-тестированием, Сангара - за чудесный мод, и мейнтейнеров OpenComputers за то что его не забросили. Enjoy!
  20. В связи с тем что компоновка OT мне не нравится, было решено сделать ребрендинг ОТ. Фичи из ОТ будут перенесены и доработаны, не все конечно. Здесь мы будет информировать вас о грядущих обновлениях, здесь принимаем фидбек, идеи, баги, так же можем иногда выкладывать видео-ролики с интересными фичами мода, например как этот ниже. Разработчики мода: @NEO @vx13 Большое спасибо @LeshaInc за модель солнечной панели. GitLab: https://gitlab.com/Moon1Light/oma Сырая версия для ознакомления .
  21. Среди всех компонентов в OC у интернет-платы самый ужасный API. Неудивительно, что правильно использовать его умеют немногие. Даже за Vexatos мне приходилось чинить tape.lua — программку для записи кассет. Плюс в ирке нередко спрашивают, как отправить HTTP-запрос на сервер. Значит, пришло время написать, как же всё-таки использовать интернет-плату. Гайд строится на следующих предположениях (сорри за педантизм): Вы умеете прогать на Lua, в том числе знаете о двух основных способах возвращать ошибку. Вы писали уже программы для OpenComputers, которые использовали API этого мода или OpenOS, особенно либу event. Вы как-то использовали (или пытались использовать) интернет-карточку в программах. Секции 1, 3: вы понимаете основные принципы HTTP. Секции 2, 4: вы понимаете, как пользоваться TCP-сокетами и зачем (не обязательно в Lua). Секция 4: вас не смущает setmetatable и вы понимаете, как делать ООП на прототипах. Секции 2, 4: у вас OC 1.6.0 или выше. Секции 1, 3, 5: у вас OC 1.7.5 или выше. Текущая версия мода — 1.7.5, а в новой ничего не изменилось. У инет-карты есть две разных фичи — HTTP-запросы и TCP-сокеты. Кратко пробежимся по API и затем разберём детальнее применение. Рассматривать я буду API компонента: часто используют require("internet") — это не компонент, а обёртка. 1. Отправка HTTP-запросов: component.internet.request У этого метода 4 параметра: URL, на который надо послать запрос. На всякий случай, URL начинается со схемы (http: или https:), после которого идёт адрес хоста (например: //localhost, //127.0.0.1, //[::1], //google.com:443), за которым следует путь (/my-file.html). Пример: https://computercraft.ru/blogs/entry/666-profiliruem-programmy-pod-oc/. Данные запроса. Оно же тело запроса. Если мы отправляем GET/HEAD-запрос, то этот аргумент надо установить в nil. Хедеры, которыми запрос сопровождать. Можно поставить nil, тогда там по минимуму дефолтные подтянутся. Иначе передавать надо таблицу. Её ключи — это названия хедеров. Например, {["Content-Type"] = "application/json"}. Метод запроса. Если же этот аргумент не передавать, то возьмётся по дефолту GET или POST: это зависит от того, пуст ли аргумент 2 или нет. Если возникла ошибка, метод вернёт nil и сообщение об ошибке. Если же всё нормально, то метод вернёт handle — табличку с функциями. Вот что это за функции: handle.finishConnect() — проверяет, подключены ли мы к серверу. Если да, то вернёт true. Если к серверу ещё не подключены, то вернёт false. Если же возникла ошибка (например, 404 вернул сервер или закрыл соединение), то вернёт nil и сообщение об ошибке. Например, nil, "connection lost". В доках написано, что функция ошибку пробрасывает. На самом деле нет: она вообще не бросает исключения. handle.response() — возвращает мета-данные ответа с сервера. Если соединение ещё не установлено, вернёт nil. Если возникла ошибка, вернёт nil и сообщение об ошибке. Например, nil, "connection lost". В противном случае возвращает 3 значения: Код ответа (например, 200). Статус (например, "OK"). Таблицу с хедерами, которые отправил сервер. Выглядит примерно так: {["Content-Type"] = {"application/json", n = 1}, ["X-My-Header"] = {"value 1", "value 2", n = 2}}. Выпишу отдельно, что значения таблицы — это не строки, а ещё одни таблицы. handle.read([n: number]) — читает n байт (если n не задано, то сколько сможет). Если компьютер ещё не успел получить данные, то отдаст "". Если возникла ошибка, то выдаст nil и сообщение об ошибке. Например, nil, "connection lost". Если сервер закрыл соединение, то вернёт nil. В противном случае отдаст строку с частью ответа. handle.close() — закрывает соединение. 2. TCP-сокеты: component.internet.connect У метода есть 2 параметра: Адрес хоста. Например, 127.0.0.1. Здесь также можно указать порт: google.com:80. Порт. Если в первом аргументе порта нет, то второй параметр обязателен. Если возникла ошибка, он также вернёт nil и сообщение. Иначе возвращает handle — табличку с функциями. Вот такими: handle.finishConnect() — то же, что и выше. handle.read([n: number]) — то же, что и выше. handle.write(data: string) — отправляет data по сокету на сервер. Возвращает число переданных байт. Если соединение не установлено, это число равно 0. handle.close() — то же, что и выше. handle.id() — возвращает id сокета. 3. Как правильно отправить HTTP-запрос на сервер и получить ответ Чтобы было интереснее, реальная задача: написать аналог pastebin, только вместо пастбина использовать https://clbin.com/. Особенности: Для взаимодействия с сайтом нужно отправлять HTTP-запросы: GET и POST. Это всё OC умеет. Чтобы скачать, достаточно простого GET по ссылке. Это можно сделать даже через wget. А вот чтобы отправить файл, надо использовать MIME-тип multipart/form-data. OC не умеет из коробки такие формы отправлять. Мы напишем минимальную реализацию, которая бы нас устроила. Не забываем, что этот MIME-тип нужно установить в хедер. При этом мы хотим красиво обработать все ошибки и не допустить ошибок сами. Таким образом, использовать будем практически все фичи. 3.1. multipart/form-data Порядок особенностей нам не важен, поэтому начинаем с самого скучного. Сделаем функцию, которая принимает данные и обрамляет их согласно формату multipart/form-data. local function generateBorder(str) local longestOccurence = nil for match in str:gmatch("%-*cldata") do if not longestOccurence or #match > #longestOccurence then longestOccurence = match end end return longestOccurence and ("-" .. longestOccurence) or "cldata" end local function asFormData(str, fieldName) local border = generateBorder(str) local contentType = "multipart/form-data; boundary=" .. border return ([[ --%s Content-Disposition: form-data; name="%s" %s --%s--]]):format( border, fieldName, str, border ), contentType end Так как это не туториал по интернет-стандартам, вдаваться в детали реализации не буду. С помощью asFormData можно содержимое файла превратить в тело HTTP-запроса. Мы будем вызывать asFormData(str, "clbin"), ибо этого требует сайт. Кроме того, эта функция нам передаст значение хедера Content-Type. Он нам понадобится. 3.2. Взаимодействие с сайтом Напишем теперь функцию — обёртку над component.internet.request. local function request(url, body, headers, timeout) local handle, err = inet.request(url, body, headers) -- ① if not handle then return nil, ("request failed: %s"):format(err or "unknown error") end local start = comp.uptime() -- ② while true do local status, err = handle.finishConnect() -- ③ if status then -- ④ break end if status == nil then -- ⑤ return nil, ("request failed: %s"):format(err or "unknown error") end if comp.uptime() >= start + timeout then -- ⑥ handle.close() return nil, "request failed: connection timed out" end os.sleep(0.05) -- ⑦ end return handle -- ⑧ end Эту функцию можно прямо брать и копипастить в свои программы. Что она делает: ① — отправляем запрос. Сразу обрабатываем ошибку. ② — запрос доходит до сервера не мгновенно. Нужно подождать. Чтобы не зависнуть слишком долго, мы засекаем время начала. ③ — вызываем finishConnect, чтобы узнать статус подключения. ④ — finishConnect вернул true. Значит, соединение установлено. Уходим из цикла. ⑤ — finishConnect вернул nil. Мы специально проверяем через status == nil, потому что не нужно путать его с false. nil — это ошибка. Поэтому оформляем его как ошибку. ⑥ — проверяем, висим ли в цикле мы слишком долго. Если да, то тоже возвращаем ошибку. Не забываем закрыть за собой соединение. ⑦ — нам не нужен бизи-луп. Спим. ⑧ — мы не читаем сразу всё в память, чтобы экономить память. Вместо этого отдаём наружу handle. Частая ошибка — отсутствие элементов ②–⑦. Они нужны. Если до установки соединения мы вызовем handle.read(), то получим nil. Многие программы в этом случае сразу отчаются получить ответ и вернут ошибку. А надо было просто подождать. 3.3. Отправка файла Функция для отправки файла должна сначала прочесть его содержимое, затем сделать запрос и прочесть ответ. В ответе будет находиться URL файла. local function sendFile(path) local f, err = io.open(path, "r") -- ① if not f then return nil, ("could not open file for reading: %s"):format(err or "unknown error") end local contents = f:read("*a") -- ② f:close() local data, contentType = asFormData(contents, "clbin") -- ③ local headers = {["Content-Type"] = contentType} local handle, err = request("https://clbin.com", data, headers, 10) -- ④ if not handle then return nil, err end local url = {} -- ⑤ local read = 0 while true do local chunk, err = handle.read() if not chunk then -- ⑥ local _, _, responseHeaders = handle.response() -- ⑦ local length for k, v in pairs(responseHeaders) do -- ⑧ if k:lower() == "content-length" then length = tonumber(v) end end if not length or read >= length then -- ⑨ break end return nil, ("error occured while reading response: %s"):format(err or "unknown error") -- ⑩ end read = read + #chunk -- ⑪ table.insert(url, chunk) end return table.concat(url) -- ⑫ end ① — открываем файл для чтения. Обрабатываем ошибки. ② — считываем всё из файла. Не забываем закрыть его за собой. ③ — вызываем заранее написанную функцию asFormData. Мы получаем тело запроса и значение хедера Content-Type. Создаём таблицу хедеров. ④ — отправляем наш запрос. Обрабатываем ошибки. ⑤ — handle.read может не сразу вернуть весь ответ, а кусочками. Чтобы не забивать память кучей строк, кусочки мы будем класть в таблицу (получится что-то вроде {"htt", "p://", "clbi", "n.co", "m/ab", "cdef"}). Также мы храним число прочитанных байт. ⑥ — handle.read может ещё вернуть ошибку. В том числе если мы прочли весь ответ, и сервер закрыл соединение. Поэтому обработка ошибок будет немного сложной. ⑦ — мы хотим сверить число прочитанных байт с размером ответа. Для этого нам потребуется получить хедеры, отправленными сервером. Вызываем handle.response. ⑧ — размер ответа обычно пишется в заголовок Content-Length. Однако сервер может поиграться с регистром. Например, писать content-length или CONTENT-LENGTH. OpenComputers не трогает эти хедеры. Поэтому придётся пройтись по всем ключам таблицы и найти хедер без учёта регистра. ⑨ — если length не nil, то это число. Тогда проверяем, что столько байт мы прочли. Если же Content-Length не задан, то будем считать, что серверу не важно, сколько надо прочесть. В любом случае выходим из цикла и завершаем чтение. ⑩ — если мы прочли меньше, чем требуется, то явно ошибка какая-то. Обрабатываем. ⑪ — не забываем обновлять read. ⑫ — наконец, склеиваем таблицу в одну строку. Из цикла можно выйти только в случае ошибки. А при ошибке соединение уже закрыто. Следовательно, самим вызывать handle.close() не нужно. 3.4. Скачивание файлов Код для скачивания похож на предыдущий. Только вот в память мы записывать ответ с сервера уже не будем. Вместо этого напрямую пишем в файл. local function getFile(url, path) local f, err = io.open(path, "w") -- ① if not f then return nil, ("could not open file for writing: %s"):format(err or "unknown error") end local handle, err = request(url, nil, nil, 10) -- ② if not handle then return nil, err end local read = 0 while true do local chunk, err = handle.read() if not chunk then f:close() -- ③ local _, _, responseHeaders = handle.response() local length for k, v in pairs(responseHeaders) do if k:lower() == "content-length" then length = tonumber(v) end end if not length or read >= length then break end return nil, ("error occured while reading response: %s"):format(err or "unknown error") end read = read + #chunk f:write(chunk) end return true end ① — открываем файл, в этот раз для записи. Обрабатываем ошибки. ② — отправляем запрос без данных и с дефолтными хедерами. Обрабатываем ошибки. ③ — если мы сюда попали, то дальше каким-либо образом (ретурном или брейком) выпрыгнем из цикла. Поэтому не забываем закрывать за собой файл. Чтобы было удобнее копипастить, я оставил повторяющийся код в двух функциях. В своей программке можно sendFIle и getFile отрефакторить, выделить дублирующуюся часть в отдельную функцию. 3.5. UI Пришло время красивой каденции. Аккордом финальным в ней будет пользовательский интерфейс. Он к интернет-карте отношения уже не имеет, но для полноты приведу и его. local args, opts = shell.parse(...) local function printHelp() io.stderr:write([[ Usage: clbin { get [-f] <code> <path> | put <path> } clbin get [-f] <code> <path> Download a file from clbin to <path>. If the target file exists, -f overwrites it. clbin put <path> Upload a file to clbin. ]]) os.exit(1) end if args[1] == "get" then if #args < 3 then printHelp() end local code = args[2] local path = args[3] local url = ("https://clbin.com/%s"):format(code) path = fs.concat(shell.getWorkingDirectory(), path) if not (opts.f or opts.force) and fs.exists(path) then io.stderr:write("file already exists, pass -f to overwrite\n") os.exit(2) end local status, err = getFile(url, path) if status then print("Success! The file is written to " .. path) os.exit(0) else io.stderr:write(err .. "\n") os.exit(3) end elseif args[1] == "put" then if #args < 2 then printHelp() end local path = args[2] local url, err = sendFile(path) if url then url = url:gsub("[\r\n]", "") print("Success! The file is posted to " .. url) os.exit(0) else io.stderr:write(err .. "\n") os.exit(4) end else printHelp() end 3.6. Вуаля Осталось добавить реквайры, и мы получим полноценный клиент clbin. Результат — на гисте. 4. Как правильно установить соединение через TCP-сокет Прошлая секция была вроде интересной, поэтому здесь тоже запилим какую-нибудь программку. @Totoro вот сделал интернет-мост Stem. Напишем для него клиент. Правильно. Опять же, особенности: Работает через TCP-сокет. Протокол бинарный. И асинхронный. А ещё сессионный: у каждого TCP-соединения есть собственный стейт. Доки хранятся на вики. При разрыве соединения клиент должен переподключиться и восстановить стейт. Здесь снова придётся использовать все фичи интернет-карты. 4.1. Архитектура Мы разделим программу на 2 части — фронтенд и бэкенд. Фронт будет заниматься рисованием и приёмом данных от пользователя, и им займёмся в конце и без комментариев. Бэк — поддержанием соединения и коммуникации с сервером. Это куда больше имеет отношения к гайду, рассмотрим подробнее. Бэкенд реализуем через ООП. Создадим конструктор, напихаем методов, которые затем будет дёргать фронт. 4.2. Конструктор Привычно вбиваем ООП-шаблон в Lua. local newClient do local meta = { __index = {}, } function newClient(address, channels, connectionTimeout, readTimeout, maxReconnects) local obj = { __address = address, __channels = channels, __connectionTimeout = connectionTimeout, __readTimeout = readTimeout, __maxReconnects = maxReconnects; __socket = nil, __buffer = nil, __running = false, __reconnectCount = 0, } return setmetatable(obj, meta) end end Ну, тут всё мирно пока. Начнём боевые действия с протокола. 4.3. Протокол Для него наклепаем кучу методов, которые будут крафтить пакеты и писать их через write. Write сделаем позже. Также сразу сделаем персеры. local meta = { __index = { __opcodes = { message = 0, subscribe = 1, unsubscribe = 2, ping = 3, pong = 4, }, __craftPacket = function(self, opcode, data) return (">s2"):pack(string.char(opcode) .. data) end, __parsePacket = function(self, packet) local opcode, data = (">I1"):unpack(packet), packet:sub(2) return self.__parsers[opcode](data) end, send = function(self, channel, message) return self:write(self:__craftPacket(self.__opcodes.message, (">s1"):pack(channel) .. message)) end, subscribe = function(self, channel) return self:write(self:__craftPacket(self.__opcodes.subscribe, (">s1"):pack(channel))) end, unsubscribe = function(self, channel) return self:write(self:__craftPacket(self.__opcodes.unsubscribe, (">s1"):pack(channel))) end, ping = function(self, message) return self:write(self:__craftPacket(self.__opcodes.ping, message)) end, pong = function(self, message) return self:write(self:__craftPacket(self.__opcodes.pong, message)) end, }, } meta.__index.__parsers = { [meta.__index.__opcodes.message] = function(data) local channel, idx = (">s1"):unpack(data) return { type = "message", channel = channel, message = data:sub(idx), } end, [meta.__index.__opcodes.subscribe] = function(data) return { type = "subscribe", channel = (">s1"):unpack(data), } end, [meta.__index.__opcodes.unsubscribe] = function(data) return { type = "unsubscribe", channel = (">s1"):unpack(data), } end, [meta.__index.__opcodes.ping] = function(data) return { type = "ping", message = data, } end, [meta.__index.__opcodes.pong] = function(data) return { type = "pong", message = data, } end, } В коде я активно использую string.pack и string.unpack. Эти функции доступны только на Lua 5.3 и выше, но позволяют очень удобно работать с бинарными форматами. 4.4. Подключение к серверу Прежде чем реализуем write, нужно разобраться с подключением. Оно нетривиально. local meta = { __index = { ..., connect = function(self) local socketStream = assert(inet.socket(self.__address)) -- ① local socket = socketStream.socket -- ② local start = comp.uptime() -- ③ while true do local status, err = socket.finishConnect() if status then break end if status == nil then error(("connection failed: %s"):format(err or "unknown error")) -- ④ end if comp.uptime() >= start + self.__connectionTimeout then socket.close() error("connection failed: timed out") -- ④ end os.sleep(0.05) end self.__socket = socket -- ⑤ self.__buffer = buffer.new("rwb", socketStream) -- ⑥ self.__buffer:setTimeout(self.__readTimeout) -- ⑦ self.__buffer:setvbuf("no", 512) -- ⑧ for _, channel in ipairs(self.__channels) do -- ⑨ self:subscribe(channel) end end, }, } ① — я использую обёртку над component.internet. Она потом будет нужна, чтобы мы могли поместить сокет в буфер. Обращаю внимание, что вызов обёрнут в assert. Работает она так: если первое значение не nil и не false, то возвращает его, а иначе кидает ошибку, используя второе значение в качестве сообщения. Проще говоря, она превращает nil, "error message" в исключение. ② — а пока я вытягиваю из обёртки сокет... ③ — чтобы можно было проверить, установлено ли соединение. Код здесь аналогичен тому, что мы делали в прошлой секции. Не выдумываем. ④ — одно различие: вместо return nil, "error message" я сразу прокидываю исключение. Прежде всего потому, что ошибки мы прокидывать должны единообразно. Раз в ① кидаем исключение, и здесь делаем то же. Почему исключение, а не return nil, "error message"? Мы вызывать connect будем из всяких мест. Так как в случае ошибок бэкенд беспомощен, то лучше прокинуть ошибку до фронтенда и не усложнять код бэка проверками на nil. Кроме того, это громкая ошибка: если забыть где-то её обработать, она запринтится на экран, случайно пропустить её или подменить какой-нибудь непонятной "attempt to index a nil value" не получится. В конце концов, мне так проще. ⑤ — сокет я сохраняю в поле. socket.finishConnect нам ещё понадобится. ⑥ — пришло время обернуть сокет в буфер. Может показаться излишним, особенно учитывая ⑧. Причины станут ясны, когда будем делать чтение. rw — это буфер для чтения и записи. b — бинарный режим: buffer:read(2) вернёт 2 байта, а не 2 символа. Так как символы кодируются в UTF-8 и занимают 1 (латиница), 2 (кириллица, диакритика), 3 (BMP: куча письменностей, всякие графические символы, большая часть китайско-японско-корейских иероглифов) или 4 байта (всё, что не влезло в BMP, например emoji), то отсутствие этого режима может дать ощутимую разницу. В нашем случае протокол бинарный — ставим b. ⑦ — устанавливаем таймаут для чтения. Объясню подробнее, когда будем это чтение делать. ⑧ — отключаем буфер для записи. Он нам не нужен. ⑨ — здесь же подключаемся ко всем каналам. Итого мы получаем свойства __socket и __buffer. Сокет использовать будем, чтобы вызывать .finishConnect() и .id(). Буфер — для записи и чтения. 4.5. Запись Теперь, разобравшись с сокетами и буферами, мы можем запросто писать в сокет. Пилим write: local meta = { __index = { ..., write = function(self, data) return assert(self.__buffer:write(data)) end, }, } Здесь тоже оборачиваем write в assert, чтобы кидать исключения. Причины уже пояснял. 4.6. Чтение и обработка пакета Сначала делаем функцию readOne. Она будет пытаться читать ровно один пакет. Здесь требуется нестандартная обработка ошибок, поэтому код сложноват. local meta = { __index = { ..., readOne = function(self, callback) -- ⑥ self.__buffer:setTimeout(0) -- ① local status, head, err = pcall(self.__buffer.read, self.__buffer, 2) self.__buffer:setTimeout(self.__readTimeout) if not status and head:match("timeout$") then return end assert(status, head) -- ② local length = (">I2"):unpack(assert(head, err)) -- ③ local packet = self:__parsePacket(assert(self.__buffer:read(length))) -- ④ if packet.type == "ping" then -- ⑤ self:pong(packet.message) end callback(self, packet) -- ⑥ return true end, } } ① — рассмотрим эту мишуру по порядку: Любой пакет stem начинается с 2 байт, которыми кодируется длина остатка. Отсюда всплывает двойка. Автор buffer, к сожалению, не осилил реализовать адекватную обработку ошибок. Он использует и исключения, и тихие ошибки (nil, "error message"). В случае таймаута будет прокинуто исключение. Однако мы перед чтением поставили таймаут в 0. Если буфер не найдёт сразу 2 байта в сокете, то он сразу кинет ошибку. Мы хотим проверить, есть ли в сокете пакет, который бы можно было прочесть. Используем pcall. Сначала раскроем self.__buffer:read(2) как self.__buffer.read(self.__buffer, 2), а затем поместим функцию и её аргументы в pcall. pcall возвращать будет сразу 3 значения по следующему принципу: Если на сокете есть 2 непрочитанных байта, read вернёт их без ошибок. Тогда status будет равен true, в head сохранятся эти 2 байта, а в err запишется nil. Если на сокете этих байтов нет, то read прокинет исключение "timeout". status установится в false, head приравняется "/lib/buffer.lua:74: timeout", а err также будет nil. Если же при чтении с сокета возникла другая ошибка, то read вернёт её по-тихому: status будет true, head — nil, а сообщение об ошибке уйдёт в err. Не думаю, что этот случай возможен, однако read может кинуть исключение и не из-за таймаута. status установится в false, а ошибка сохранится в head. В if мы проверяем, был ли таймаут (ситуация 1.2). В таком случае мы не кидаем исключения, а тихо выходим. Наконец, не забываем вернуть прежнее значение таймаута. ② — обрабатываем случай 1.4. ③ — обрабатываем случай 1.3 с помощью assert. Последний оставшийся и единственный успешный случай (1.1) также покрывается: распаковываем 2 байта в целое беззнаковое число (uint16_t). ④ — в ③ мы получили длину оставшегося пакета. Очевидно, надо остаток дочитать, что и делаем. Здесь уже не надо отдельно обрабатывать таймаут, достаточно assert. Считанный пакет отдаём в __parsePacket. ⑤ — если сервер докопался до нас своим пингом, отправим ему понгу. ⑥ — функция readOne принимает коллбэк. Это функция, которая будет обрабатывать все пакеты. Коллбэк будет передавать фронтенд, а бэкенд займётся минимальной обработкой, чтобы в принципе работало. Как, например, ③. Отлично. Мы приготовили все примитивы, которые были нужны. Осталось собрать их воедино — в event loop. 4.7. Event loop и события Ивент луп — это цикл, который ждёт событий и что-то с ними делает. Пришло время разобраться, что за события есть в OC. Когда мы вызываем socket.read или socket.finishConnect, устанавливается "ловушка" (селектор). Она срабатывает, когда на сокет пришли новые байты. При этом компьютер получает событие internet_ready. После чего "ловушка" деактивируется до следующего вызова. internet_ready, таким образом, — это событие, извещающее нас о том, что на сокете валяются непрочитанные данные и пора вызвать socket.read, чтобы их собрать. У события два параметра. Первый — это адрес интернет-карты. Второй — id сокета. Тот id, который возвращает socket.id(). Поэтому мы сохранили сокет в поле __socket: сейчас будем использовать его. local meta = { __index = { ..., __run = function(self, callback) while self.__running do local e, _, id = event.pullMultiple(self.__readTimeout, "internet_ready", "stem%-client::stop") -- ① if e == "internet_ready" and id == self.__socket.id() then -- ② while self:readOne(callback) do self.__reconnectCount = 0 -- ③ end elseif e ~= "stem-client::stop" then self:ensureConnected() -- ④ end end end, stop = function(self) self.__running = false event.push("stem-client::stop") -- ⑤ end, } } ① — ждём события internet_ready или stem-client::stop. Так как в event.pullMultiple названия ивентов сверяются через string.match, дефис экранируем. Второй ивент нужен, чтобы принудительно прервать цикл из stop. ② — обрабатываем мы только internet_ready и только для нашего сокета. Проверяем. ③ — если поймался пакет или пакеты, то пытаемся обработать каждый в порядке прибытия. Когда мы закончили обрабатывать все пакеты, self:readOne вернёт nil, и цикл прервётся. Кстати говоря, если мы внутри цикла оказались, то соединение установилось. Не забываем отметить это. ④ — если же улов пуст, перепроверяем, подключены ли мы вообще. ⑤ — не забываем добавить метод, чтобы остановить наш цикл. Отсюда же отсылаем событие stem-client::stop. Отлично. Теперь пришло время ловить все наши прокидываемые исключения. 4.8. Обработка ошибок Последними 2 функциями, которые мы добавим, будут ensureConnected и run. С их помощью бэкенд будет автоматически переподключаться к серверу в случае проблем. local meta = { __index = { ..., ensureConnected = function(self) local status, err = self.__socket.finishConnect() -- ① if status == false then error("not yet connected") end return assert(status, err or "unknown error") end, run = function(self, callback) if self.__running then -- ② return end self:connect() -- ③ self.__running = true while self.__running do -- ④ local status, err = pcall(self.__run, self, callback) -- ⑤ if not status then if self.__reconnectCount == self.__maxReconnects then -- ⑥ return nil, ("connection lost: %s; reconnect limit is reached"):format(err or "unknown error") end self.__reconnectCount = self.__reconnectCount + 1 self.__buffer:close() -- ⑦ if not pcall(self.connect, self) then -- ⑧ if self.__socket then self.__socket:close() end if self.__buffer then self.__buffer:close() end os.sleep(1) end end end self.__buffer:close() end, }, } ① — ensureConnected просто прокинет ошибку, которую вернёт finishConnect(). ② — принимаем защитную позицию против дураков. Рекурсивно запускать циклы смысла нет. ③ — сначала подключаемся к серверу. Если всё отлично, то можно начинать. ④ — как и в __run, здесь мы оборачиваем код в цикл. Если вызван stop(), то сначала остановится self.__run, а затем и этот цикл. ⑤ — обработка исключений требует pcall. Потому что их надо словить. ⑥ — если мы старались-старались, но так и не смогли уложиться в self.__maxReconnects по реконнектам, кидаемся белым флагом. ⑦ — не забудем закрыть буфер. ⑧ — вспомним, что self.connect кидает исключение. Перехватываем. На всякий случае позакрываем то, что породил connect. 4.9. Фронтенд На этом наш бэкенд готов. Поздравляю. Остаётся лишь прицепить ввод-вывод. Опять же, даю готовый код без комментариев, ибо не об этом пост. local gpu = com.gpu local w, h = gpu.getResolution() local function writeLine(color, line) local oldFg if gpu.getForeground() ~= color then oldFg = gpu.setForeground(color) end local lines = 0 for line in text.wrappedLines(line, w + 1, w + 1) do lines = lines + 1 end gpu.copy(1, 1, w, h - 1, 0, -lines) local i = 0 for line in text.wrappedLines(line, w + 1, w + 1) do gpu.set(1, h - lines + i, (" "):rep(w)) gpu.set(1, h - lines + i, line) i = i + 1 end if oldFg then gpu.setForeground(oldFg) end end local channel = ... if not channel then io.stderr:write("Usage: stem <channel>\n") os.exit(1) end if #channel == 0 or #channel >= 256 then io.stderr:write("Invalid channel name\n") os.exit(2) end local client = newClient( "stem.fomalhaut.me:5733", {channel}, 10, 10, 5 ) require("thread").create(function() while true do term.setCursor(1, h) io.write("← ") local line = io.read() if not line then break end local status, err = pcall(client.send, client, channel, line) if not status then writeLine(0xff0000, ("Got error while sending: %s"):format(err or "unknown error")) break end end client:stop() end) client:run(function(client, evt) if evt.type == "message" then writeLine(0x66ff00, "→ " .. evt.message) elseif evt.type == "ping" or evt.type == "pong" then writeLine(0xa5a5a5, "Ping: " .. evt.message:gsub(".", function(c) return ("%02x"):format(c:byte()) end)) end end) os.exit(0) Здесь я упускаю одну вещь: обработку ошибок в client.send. Если мы попытаемся отправить сообщение, когда у нас потеряно соединение (или до того, как оно установлено), мы или словим ошибку, или потеряем сообщение. Починить это можно, добавив очередь отправляемых пакетов, но это в разы усложнит программу, поэтому оставим так. 4.10. Готово! Добавим реквайров... И у нас получился вполне рабочий клиент для Stem! Код программы — на гисте. 5. В чём различие между component.internet и require("internet") Первое — исходный компонент. Второе — обёртка над ним. У обёртки есть 3 функции: internet.request(url, data, headers, method) — обёртка над component.internet.request. Удобна тем, что все ошибки превращает в исключения за программиста. Кроме того, возвращаемое значение — итератор, и его можно поместить в цикл for. Тем не менее, код, который ждёт установки соединения, нужно писать самому. internet.socket(address, port) — промежуточная обёртка над component.internet.connect. Она используется для того, чтобы потом превратить её в буфер, как сделали мы. Сама по себе достаточно бесполезна. internet.open(address, port) — тоже обёртка над component.internet.connect. Она вызывает internet.socket(address, port) и сразу превращает результат в буфер. Проблема в том, что сам объект сокета использовать можно только через приватные свойства, которые могут ломаться между обновлениями OpenOS. Из-за этого функция исключительно ущербна. Для отправки HTTP-запросов я предпочитаю использовать API компонента. TCP-сокеты же проще создавать через обёртку (internet.socket), вручную проверять подключение и так же вручную укладывать обёртку в буфер, как показано выше. 6. Конец Самое сложное в использовании интернет-карты — это правильно обработать все ошибки. Они могут возникнуть на каждом шагу, при этом быть полноценными исключениями или тихими ошибками. Необработанные исключения крашат программу, из-за чего возникает желание весь код программы поместить в один большой pcall. Например, IRC-клиент, который на дискете поставляется, делает так. Тихие ошибки гораздо подлее. Необработанные, они тоже крашат программу, только вот сама ошибка теряется, подменяется другой (обычно "attempt to index a nil value"). В Lua обработать все ошибки — задача сложная, потому что механизм ошибок ужасен. В нормальных языках стэктрейс отделён от сообщения об ошибке, плюс каждая ошибка имеет свой тип, по которому можно безопасно определять вид ошибки. Lua этим не заморачивается: сообщение об ошибке включает позицию в коде, откуда ошибка прокинута. Есть или нет стэктрейс, зависит от выбора между pcall и xpcall. Если они находятся где-то в другой библиотеке, программист на выбор повлиять не может. В коде Stem-клиента единственный способ узнать, от таймаута ли ошибка прокинута, — матчить последние 7 символов на слово "timeout". Это эталонный костыль. Даже в JavaScript механизм лучше. Поэтому этот пост получился не столько про интернет-карту, сколько про обработку ошибок.
  22. Привет игроки форумчане! Сегодня я дописал свой биос для OC. Он позволяет выбирать конкретную файловую систему для загрузки и показывает основные параметры этих систем. Я хотел засунуть больше функционала в виде фейкового init'a но место на eeprom'ке очень мало. Код и так весит 4094 байта из 4096 доступных. Не рекомендуется использовать этот биос в OC 1.5 из-за кривого шрифта. Вот так он выглядит в эмуляторе OC 1.6 команда для загрузки: pastebin run gG0ppTD7 Вот смотрю я на наш форум и вижу одни темы по типу "Есть ли правила на этом форуме?". Это же форум от lua прогеров для lua прогеров. Так давайте проги писать, а не разбираться куда делись правила.
  23. Доброго времени суток. Я решил попробовать себя в написании игр на Lua и воссоздать клеточный автомат "Жизнь" Джона Конвеея для OpenComputers 1.7.5. Описание: В игре существуют два типа клеток - живая и мёртвая. Суть игры в том, чтобы расставить живые клетки и затем наблюдать за их "поведением". Правила автомата очень просты: Если вокруг мёртвой клетки ровно 3 живых - клетка оживает Если вокруг живой клетки меньше 2 или больше 3 живых - клетка умирает В остальных случаях клетки сохраняют своё состояние Известные недочёты: Вычисление каждого следующего поколения может занимать до полуминуты При контакте с краями поля, любая фигура постепенно погибает в соответствии с правилами автомата При запуске симуляции, её можно остановить только перезагрузкой в shell Скачать: Репозиторий на GitHub: https://github.com/Vladg24YT/Game-Of-Life Последняя версия (1.0.0): https://github.com/Vladg24YT/Game-Of-Life/releases/tag/1.0.0 Скриншот:
  24. Вступление: Решил я на днях написать хорошую IDE для OpenComputers и столкнулся с тем, что в ванильной OpenOS напрочь отсутствует подсветка синтаксиса. Надо это исправить! Если найдутся какие-либо недочеты в плане правильности подсветки - пишите, пофиксим. Скачать библиотеку: https://github.com/IgorTimofeev/MineOS/blob/2967e780d35c2e4363dd7fe81de5ba78310c3d55/lib/oldSyntax.lua Инструкция по использованию: --Подключаем библиотеку local syntax = require("syntax") --Создаем любую строку с операторами lua local stroka = "while true do print(\"Hello world!\")" --x, y - стартовые координаты отрисовки текста. --limit - ограничение отрисовки по ширине строки. Зачем рисовать лишние пиксели? local x, y, limit = 2, 2, 20 --Выводим строку на экран в подсвеченном варианте syntax.highlightAndDraw(x, y, limit, stroka) На экране отобразится следующее: Другие функции: ● syntax.setColorScheme(цветовая схема) Устанавливает цветовую схему по выбору пользователя. На данный момент есть лишь две схемы: "midnight" и "sunrise". ● syntax.highlightFileForDebug(путь к файлу [,цветовая схема]) Открывает обычный файл по указанному пути и выводит его содержимое в подсвеченном виде на экран. Размер содержимого ограничен размером экрана. Пример использования этой функции предоставлен на самом первом скриношоте. Насчет оптимизации и ускорения: Я постарался минимизировать задержки в выполнении скрипта различными путями. Во-первых, отрисовка выполняется не попиксельно, а построчно. То есть если мы имеем строку красного цвета, то функция отрисовки выведет на экран сразу всю строку, а не каждый символ по отдельности. Во-вторых, цвет текста изменяется только тогда, когда это необходимо, а не каждый раз при смене символов. На данный момент у программы есть существенный минус: при отрисовке выполняется поиск совпадений шаблонов по строке через оператор string.find(), однако изначально он не дружит с юникодом. Поэтому пришлось использовать костыль unicode.find(), существенно замедляющий процесс поиска. Если у кого-то имеется более оптимизированный костыль или способ поиска по юникоду, буду крайне благодарен за помощь в этом деле. Для ясности прикладываю видосик: http://www.youtube.com/watch?v=T-f8jGdYO0M&feature=youtu.be Исходный код unicode.find(): function unicode.find(str, pattern, init, plain) if init then if init < 0 then init = -#unicode.sub(str,init) elseif init > 0 then init = #unicode.sub(str,1,init-1)+1 end end a, b = string.find(str, pattern, init, plain) if a then local ap,bp = str:sub(1,a-1), str:sub(a,b) a = unicode.len(ap)+1 b = a + unicode.len(bp)-1 return a,b else return a end end
  25. Теперь компы работают В РАЗЫ быстрее!!! Вам лишь нужно смазать процессор девятью граммами... Всем привет! Я так долго не заходил на форум, что забыл вас оповестить о новом законном способе ускорить работу компов в игре о котом я узнал. Так делать не нужно!!! Мой комп страдал, чтобы ваш не горевал. Суть вот в чём. В моде ProjectE есть такой замечательный инструмент как часы времени. Ничего особенного они не делают кроме ускорения цикла дня/ночи. НО!!! Если их поставить на пьедестал и активизировать ПКМ, то они судя по описанию немного ускорять животных поблизости и дадут дополнительные 20 тиков блокам в радиусе 3 блоков рядом. (образует куб 7x7x7 с пьедесталом в центре) И тут мы переходим к самому интересному!!! Часы работают на любых блоках: на растениях, блоках из мода и даже на блоках из самого же ProjectE, которые позволяют собирать и накапливать ECM (местную валюту) {из-за чего кстати я сломал аддон к этому моду и быстро прокачался до максимума ECM - просто посмотрите на скрин и всё поймёте} Очень имбовая вещь, ей можно генерировать много пассивной энергии и тратить её в супер быстрый карьер. А эффекты от часов поблизости складываются!!! Так вот, ради чего мы здесь собрались... судя по логике совместимости модов: робот из OpenComputers должен быть блоком, но для часов времени - он не блок и на него ничего не действует. Зато часы действуют на статичные блоки вроде системного блока, монитора и прочего. Что позволяет использовать компы на очень быстрых скоростях. Правда есть и минус. Внутренние часы компа ничего не подозревают и задержки через os.sleep будут так же ускорены как и ВСЯ работа компа. Зато это открывает возможности делать и запускать реальные игры в майнкрафте. Не заботясь о том, что что-то не успеет прогрузится. Вот такие чудеса творятся в мире ProjectE. И его явно никто добавлять на сервера не будет. Зато попробовать программы позапускать с ним можно и в одиночной игре. Может кто-то что-нибудь с этим придумает.
×
×
  • Создать...