Перейти к публикации

В ближайшее время постараюсь разобраться с картой сервера/ЛК/бб кодами

Внимание, с 14 февраля до 20 февраля могут проходить работы на сервере, где также находится лаунчсервер. В связи с этим авторизация в лаунчере может не работать

Doob

Пользователи
  • Публикации

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

  • Посещение

  • Дней в лидерах

    65

Все публикации пользователя Doob

  1. Все-таки добывать руду слоями дольше, чем свободным обходом всех доступных блоков. На тестовом стенде свободный - 2 минуты 50 секунд, послойный - 3 минуты 9 секунд. Тут очень сильно напрашивается дополнительная эвристика на сокращение поворотов и прямых одноблочных ходов. И упаковку запускать только при нужде.
  2. Doob

    Робот с геосканером. Часть #7 [способы добычи]

    Да, хорошо, штраф сделать не просто разницей между верхним Y и текущим. А умножением, относительно всей доступной высоты. Так и сделаю. Повороты можно даже не штрафовать, а делать анализ, на этапе поиска цели.
  3. Для настройки работы программы, надо узнать плотность добываемых блоков и плотность мусора, чтобы сделать предварительный фильтр на этапе сканирования. Плотность одной руды в разных модах отличается, но она близка к ванильной т. к. механика работы инструментов одна. Сделал шпаргалку с информацией о плотностях из разных модов. Плотность, уровень инструмента, название. Minecraft 3 2 Алмазная руда 3 2 Изумрудная руда 3 2 Золотая руда 3 2 Красная руда 3 1 Железная руда 3 1 Лазуритовая руда 3 0 Угольная руда 3 0 Кварцевая руда 0.3 -1 Светокамень -1 -1 Бедрок 50 3 Обсидиан 3 -1 Эндерняк 2.5 0 Сундук 2 0 Булыжник 2 -1 Адский кирпич 2 0 Доски 1.5 0 Камень 1.5 -1 Каменный кирпич 1.25 -1 Терракота 0.8 0 Песчаник 0.6 0 Трава 0.6 0 Гравий 0.6 0 Глина 0.5 0 Земля 0.5 0 Песок 0.4 0 Адский камень 0.5 0 Песок душ -------------------------------------------- IC2 4 2 Урановая руда 3 1 Медная руда 3 1 Оловянная руда 2 1 Свинцовая руда -------------------------------------------- AE2 50 3 Небесный камень 50 0 Сундук из небесного камня 3 0 Кварцевая руда -------------------------------------------- Mekanism 3 -1 Осмиевая руда 3 -1 Медная руда 3 -1 Оловянная руда -------------------------------------------- Forestry 3 1 Апатитовая руда 3 1 Медная руда 3 1 Оловянная руда -------------------------------------------- TConstruct 10 4 Кобальтовая руда 10 4 Ардитовая руда -------------------------------------------- ThermalFoundation 3 1 Медная руда 3 1 Оловянная руда 3 2 Серебряная руда 3 2 Свинцовая руда 3 1 Алюминиевая руда 3 2 Никелевая руда 3 3 Платиновая руда 3 3 Иридиевая руда 3 3 Мифриловая руда -------------------------------------------- Galacticraft 6 1 Алюминиевая руда 5 1 Оловянная руда 5 1 Медная руда 3 2 Кремниевая руда Moon 5 1 Медная руда 5 1 Оловянная руда 5 -1 Сапфировая руда 3 1 Сырная руда 1.5 0 Лунный камень 0.5 0 Лунный грунт 0.5 -1 Лунный дерн Mars 2.2 3 Деш руда 2.2 1 Железная руда 2.2 1 Оловянная руда 2.2 1 Медная руда 2.2 0 Булыжник 2.2 0 Реголит Asteroids 3 2 Алюминиевая руда 3 3 Ильменитовая руда 3 2 Железная руда 3 0 Камень Venus 5 -1 Алюминиевая руда 5 -1 Медная руда 5 -1 Свинцовая руда 5 -1 Кварцевая руда 5 -1 Кремниевая руда 5 -1 Оловянная руда 5 -1 Солнечная пыль 2.2 1 Магма 2.2 1 Пемза 1.5 1 Твердый камень 0.9 1 Мягкий камень 0.9 -1 Выжженный камень
  4. Добытые ресурсы надобно рассортировать. Для этого дела задействуем контроллер инвентаря. Надобно пройти по всем слотам, получить информацию о содержимом и сравнить название со списком ненужных предметов (который предварительно составим), при совпадении опустошать. Но это не вся функция. У нас есть еще верстак, который может помочь, очень сильно ужать, некоторые ресурсы (уголь, редстоун, алмазы, изумруды, лазурит). Верстак занимает в инвентаре 9 слотов, еще 1 слот добавим на результат, если не все влезет в блок. Поэтому, пока робот ищет мусор, пусть считает пустые слоты, для верстака. В начале уберем блоки сверху и снизу, чтобы случайно не перемешать мусор с излишками добра. для удобоваримости сократил некоторые имена: inventory - размер инвентаря, получим в начале работы программы, controller - контроллер инвентаря, tails - список названий лишних предметов (без префикса "minecraft:", можно добавлять названия из любого мода) robot.swing(0) -- освободить место для мусора robot.swing(1) -- освободить место для буфера ------- сброс мусора ------- local empty = 0 -- создать счетчик пустых слотов for slot = 1, inventory do -- пройти по слотам инвентаря local item = controller.getStackInInternalSlot(slot) -- получить информацию о предмете if item then -- если есть предмет for name = 1, #tails do -- пройти по таблице хвостов if item.name:gsub('%g+:', '') == tails[name] then -- проверить на совпадение robot.select(slot) -- выбрать слот robot.drop(0) -- выбросить к отходам empty = empty + 1 -- обновить счетчик break -- прервать цикл сравнения end end else empty = empty + 1 -- обновить счетчик end end Далее следует проверить и выкинуть наверх предметы, которые будут мешать при крафте. Подсчитанные пустые слоты отнимем от требуемого количества для крафта, пройдем по инвентарю уберем их. -- упаковка предметов в блоки -- if crafting then -- если есть верстак -- перенос лишних предметов в буфер -- if empty < 10 then -- если пустых слотов меньше 10 empty = 10-empty -- увеличить количество пустых слотов для обратного отсчета for slot = 1, inventory do -- просканировать инвентарь if robot.count(slot) > 0 then -- если слот не пуст robot.select(slot) -- выбрать слот robot.drop(1) -- выбросить в буфер empty = empty - 1 -- обновить счетчик end if empty == 0 then -- если место освободилось break -- прервать цикл end end end Предварительно создадим таблицу fragments, в которой будут храниться названия предметов, которые можно сложить в блоки. Теперь создадим таблицу, в которой будут счетчики для каждого типа фрагментов. Пройдем по инвентарю, получим информацию о слоте, сравним, прибавим - все как в первом цикле, можно было бы даже их объединить, но на предыдущем шаге мы выкинули какие-то предметы. Чтобы узнать какие именно - придется городить еще один цикл, оставим как есть. -- подсчет предметов доступных для упаковки -- local available = {} -- создать таблицу счетчиков for slot = 1, inventory do -- пройти по слотам инвентаря local item = controller.getStackInInternalSlot(slot) -- получить информацию о предмете if item then -- если есть предмет for n = 1, #fragments do -- пройти по списку названий фрагментов if item.name:gsub('%g+:', '') == fragments[n] then -- сравнить по имени if available[n] then -- если есть подобные фрагменты available[n] = available[n] + item.size -- обновить else -- иначе available[n] = item.size -- создать end break end end end end Наконец-то можно крафтить. Хотя, нет. Надо расчистить слоты верстака, чтобы в него сложить рецепт. Будем перебирать слоты от 1 до 9, но в роботе верстак занимает слоты с другими номерами, а именно 1 2 3 5 6 7 9 10 11, можно было бы составить условие от 1 до 11, с исключением целых по модулю 4. Сделаем проще - номера слотов занесем в таблицу "workbench", которая будет служить списком ссылок с системы 1-9 на 1-11. Вынесем ее подальше, чтобы она не создавалась при каждом запуске. В цикле проверяем количество предметов в слоте, если оно не нулевое - ищем в инвентаре пустой слот, исключая слоты верстака. Переносим предметы в найденный слот. Если перенести не удалось - что-то попало в верстак после крафта. Забираем предметы из буфера и завершаем функцию, возвращая true, что будет сообщать о перегрузе и времени выдвигаться домой. for c_slot = 1, 9 do -- цикл чистки зоны верстака if robot.count(workbench[c_slot]) > 0 then -- если слот не пуст robot.select(workbench[c_slot]) -- выбрать слот верстака for slot = 4, inventory do -- обойти весь инвентарь, кроме рабочей зоны if robot.count(slot) == 0 and (slot == 4 or slot == 8 or slot > 11) then -- если есть свободный robot.transferTo(slot) -- освободить слот break -- выйти из цикла end end if robot.count() > 0 then -- проверить на перегрузку robot.suck(1) -- забрать из буфера return true -- остановить упаковку end end end Верстак расчищен, пора заняться упаковкой. Перебираем слоты инвентаря, исключая верстак, сравниваем названия со списком. При совпадении, делим содержимое на 9, заполняем верстак И крафтим блок. ------- основной цикл крафта ------- for slot = 4, inventory do -- цикл поиска фрагментов local item = controller.getStackInInternalSlot(slot) -- получить информацию о предмете if item and (slot == 4 or slot == 8 or slot > 11) then -- если есть предмет вне рабочей зоны if item.name:gsub('%g+:', '') == fragments[i] then -- сравнить по названию фрагмента robot.select(slot) -- при совпадении выбрать слот for n = 1, 9 do -- цикл заполнения рабочей зоны robot.transferTo(workbench[n], item.size/9) -- разделить текущий стак на 9 частей и перенести в верстак end if robot.count(1) == 64 then -- сброс при заполнении верстака break end end end end crafting.craft() -- создание блока Можно заметить fragments, откуда i? Об этом позже. После крафта могли остаться какие-то остатки, если не все слоты поделились на 9. Проверяем содержимое в слотах, если предметов меньше 64 - перебираем слоты после текущего и сравниваем, при совпадении содержимого пробуем перенести. При опустошении текущего слота - прерываем перебор со сравнением. -- цикл сортировки остатков for A = 1, inventory do -- основной проход local size = robot.count(A) -- получить количество предметов if size > 0 and size < 64 then -- если слот не пуст и не полон robot.select(A) -- выбрать слот for B = A+1, inventory do -- проход сравнения if robot.compareTo(B) then -- если предметы одинаковые robot.transferTo(B, 64-robot.count(B)) -- перенести до заполнения end if robot.count() == 0 then -- если слот освободился break -- прервать сравнение end end end end Последние три цикла заворачиваем в такую конструкцию: for i = 1, #fragments do -- перебор всех названий if available[i] then -- если в инвентаре такой есть for j = 1, math.ceil(available[i]/576) do -- разделить результат на стаки ... end end end Первый цикл перебирает названия фрагментов. Условный оператор проверяет наличие такого типа в инвентаре. Внутренний цикл повторяет чистку, крафт и сортировку, если в результате будет больше стака блоков. Новые используемые переменные: local tails = {'cobblestone','dirt','gravel','sand','stained_hardened_clay','sandstone','stone','grass','end_stone','hardened_clay','mossy_cobblestone','planks','fence','torch','nether_brick','nether_brick_fence','nether_brick_stairs','netherrack','soul_sand'} local workbench = {1,2,3,5,6,7,9,10,11} local fragments = {'redstone','coal','dye','diamond','emerald'} local controller = add_component('inventory_controller') local crafting = add_component('crafting') local inventory = robot.inventorySize() Полный текст функции, с более рациональным вызовом robot.count(): local function sorter() -- сортировка лута robot.swing(0) -- освободить место для мусора robot.swing(1) -- освободить место для буфера ------- сброс мусора ------- local empty = 0 -- создать счетчик пустых слотов for slot = 1, inventory do -- пройти по слотам инвентаря local item = controller.getStackInInternalSlot(slot) -- получить информацию о предмете if item then -- если есть предмет for name = 1, #tails do -- пройти по таблице хвостов if item.name:gsub('%g+:', '') == tails[name] then -- проверить на совпадение robot.select(slot) -- выбрать слот robot.drop(0) -- выбросить к отходам empty = empty + 1 -- обновить счетчик break -- прервать цикл сравнения end end else empty = empty + 1 -- обновить счетчик end end -- упаковка предметов в блоки -- if crafting and empty < 12 then -- если есть верстак и переполнение -- перенос лишних предметов в буфер -- if empty < 10 then -- если пустых слотов меньше 10 empty = 10-empty -- увеличить количество пустых слотов для обратного отсчета for slot = 1, inventory do -- просканировать инвентарь if robot.count(slot) > 0 then -- если слот не пуст robot.select(slot) -- выбрать слот robot.drop(1) -- выбросить в буфер empty = empty - 1 -- обновить счетчик end if empty == 0 then -- если место освободилось break -- прервать цикл end end end -- подсчет предметов доступных для упаковки -- local available = {} -- создать таблицу счетчиков for slot = 1, inventory do -- пройти по слотам инвентаря local item = controller.getStackInInternalSlot(slot) -- получить информацию о предмете if item then -- если есть предмет for n = 1, #fragments do -- пройти по списку названий фрагментов if item.name:gsub('%g+:', '') == fragments[n] then -- сравнить по имени if available[n] then -- если есть подобные фрагменты available[n] = available[n] + item.size -- обновить else -- иначе available[n] = item.size -- создать end break end end end end ------- основной цикл крафта ------- for i = 1, #fragments do -- перебор всех названий if available[i] then -- если в инвентаре такой есть for j = 1, math.ceil(available[i]/576) do -- разделить результат на стаки for c_slot = 1, 9 do -- цикл чистки зоны верстака if robot.count(workbench[c_slot]) > 0 then -- если слот не пуст for slot = 4, inventory do -- обойти весь инвентарь, кроме рабочей зоны if robot.count(slot) == 0 and (slot == 4 or slot == 8 or slot > 11) then -- если есть свободный robot.select(workbench[c_slot]) -- выбрать слот верстака robot.transferTo(slot) -- освободить слот break -- выйти из цикла end end if robot.count() > 0 then -- проверить на перегрузку robot.suck(1) -- забрать из буфера return true -- остановить упаковку end end end ------- основной цикл крафта ------- for slot = 4, inventory do -- цикл поиска фрагментов local item = controller.getStackInInternalSlot(slot) -- получить информацию о предмете if item and (slot == 4 or slot == 8 or slot > 11) then -- если есть предмет вне рабочей зоны if item.name:gsub('%g+:', '') == fragments[i] then -- сравнить по названию фрагмента robot.select(slot) -- при совпадении выбрать слот for n = 1, 9 do -- цикл заполнения рабочей зоны robot.transferTo(workbench[n], item.size/9) -- разделить текущий стак на 9 частей и перенести в верстак end if robot.count(1) == 64 then -- сброс при заполнении верстака break end end end end crafting.craft() -- создание блока -- цикл сортировки остатков for A = 1, inventory do -- основной проход local size = robot.count(A) -- получить количество предметов if size > 0 and size < 64 then -- если слот не пуст и не полон for B = A+1, inventory do -- проход сравнения if robot.compareTo(B) then -- если предметы одинаковые robot.select(A) -- выбрать слот robot.transferTo(B, 64-robot.count(B)) -- перенести до заполнения end if robot.count() == 0 then -- если слот освободился break -- прервать сравнение end end end end end end end end robot.suck(1) --- забрать предметы из буфера end
  5. Doob

    Робот с геосканером. Часть #7 [способы добычи]

    Предпочтения игрока тут не учесть, руды все одной плотности (ic2 свинец это недоразумение, а не руда). Послойная обработка не выгодна тем, что робот нарезает круги, когда в паре слоев от него есть руда. Сплошную поддерживают оба алгоритма -- пока не будет добыт весь кластер, робот не уйдет, но послойный оставит недоступный слой на потом. На стандартных генерациях ванилы, IC2, AE2 (тестировались отдельно) свободный обход существенно превосходит послойный (4 чанка, высота 64). Поэтому, статистика говорит в сторону этого подхода, а что админ накрутит генерацию - проблема индейцев. Изначально, я планировать сделать гибрид из этих двух алгоритмов - робот свободно выбирает цель, но получает огромный штраф на ход по вертикали +Y. Добыча велась бы максимально оптимально и робот заканчивал чанк наверху, откуда короче путь к старту следующего чанка. На стенде подобрал расположение руд, чтобы оно было похоже на реальную генерацию и оба алгоритма были бы в одних условиях (руда в каждом слое, свободный пробег 1600 блоков) Меня больше интересуют советы по оптимизации упаковщика, потому-что на каждый сундук робот тратит около 30 секунд. В прошлой версии скорость была фантастической, но хак, который я использовал убрали после какой-то обновы.
  6. Иногда надо связать несколько компьютеров на большом расстоянии или между мирами, или даже между серверами. Так как у нас есть интернет-карта, можно провернуть несложный хак - пустить информацию через внешний сервер. Но чтобы не писать серверную часть, можно использовать готовый протокол с готовыми серверами. Получается крутая связанная карта для многих компов. Данная программа реализует IRC мост, по которому могут общаться не только опенкомпы, но и любые устройства с интернетом. Например, можно с мобильника дать команду своим компьютерам на нескольких игровых серверах. Использование: Скачать программу pastebin get CmLFFF7g /bin/imodem.lua Проверить наличие интернет-карты в компьютере Запустить программу imodem В течении некоторого времени будет устанавливаться связь, а затем можно работать с модемом. Чтобы послать сообщение всем устройствам на канале, есть функция imodem.broadcast( сообщение: строка ) Чтобы послать сообщение по определенному адресу - imodem.send( получатель: строка, сообщение: строка ) Для разрыва связи и прекращения работы - imodem.stop() А чтобы принимать сообщения, достаточно ловить событие modem_message, как у обычных модемов. Адрес сервера и канал можно изменить в коде программы, желательно иметь сервер без обрезания ников и защиты от флуда.
  7. Странно, у меня личные сообщения нормально обрабатываются. Флаг для логирования можно тоже сунуть в конфиг. Конфиг следует сделать в виде lua-таблицы - быстрей и меньше кода. Изначально все задумывалось как программный модем между компами. Логин для NickServ и прочие изощрения поверх стандарта, пользователь должен был организовывать в своей программе. imodem это всего-лишь интерфейс, совместимый с OpenOS. Хотя, да не сделал пинг в сторону сервера, каюсь.
  8. Чтобы программа могла контролировать движения робота, добавим систему координат и функционал связанный с ней. Так как робот будет шахтером, то все движения должны сопровождаться разрушением блоков, он будет ползать сквозь породу, попутно захватывая руду. Описание основной двигательной деятельности занимает всего четыре функции (можно и три, но в прошлой версии, в процессе борьбы за место, пришлось одну разделить) Приведу базовый код, затем опишу, что он делает. local component = require('component') -- подгрузить обертку из OpenOS local X, Y, Z, D = 0, 0, 0, 0 local WORLD = {x = {}, y = {}, z = {}} local function add_component(name) -- получение прокси компонента name = component.list(name)() -- получить адрес по имени if name then -- если есть адрес return component.proxy(name) -- вернуть прокси end end local robot = add_component('robot') -- загрузка компонента local function step(side) -- функция движения на 1 блок local state, type = robot.swing(side) -- тестовый свинг if not state and type == 'block' then -- если блок нельзя разрушить print('bedrock') os.exit() -- временная заглушка else while robot.swing(side) do end -- копать пока возможно end if robot.move(side) then -- если робот сдвинулся, обновить координаты if side == 0 then Y = Y-1 elseif side == 1 then Y = Y+1 elseif side == 3 then if D == 0 then Z = Z+1 elseif D == 1 then X = X-1 elseif D == 2 then Z = Z-1 else X = X+1 end end end if #WORLD.x ~= 0 then -- если таблица меток не пуста for i = 1, #WORLD.x do -- пройти по всем позициям if X == WORLD.x[i] and (Y-1 <= WORLD.y[i] and Y+1 >= WORLD.y[i]) and Z == WORLD.z[i] then if WORLD.y[i] == Y+1 then -- добыть блок сверху, если есть robot.swing(1) elseif WORLD.y[i] == Y-1 then -- добыть блок снизу robot.swing(0) end table.remove(WORLD.x, i) -- удалить метку из таблицы table.remove(WORLD.y, i) table.remove(WORLD.z, i) end end end end local function turn(side) -- поворот в сторону side = side or false if robot.turn(side) then -- если робот повернулся, обновить переменную направления if side then D = (D+1)%4 else D = (D-1)%4 end end end local function smart_turn(side) -- поворот в определенную сторону света while D ~= side do turn((side-D)%4==1) end end local function go(x, y, z) -- переход по указанным координатам while Y ~= y do if Y < y then step(1) elseif Y > y then step(0) end end if X < x then smart_turn(3) elseif X > x then smart_turn(1) end while X ~= x do step(3) end if Z < z then smart_turn(0) elseif Z > z then smart_turn(2) end while Z ~= z do step(3) end end Сначала создаются переменные для локальных координат робота. X, Y, Z - собственно, позиция робота, относительно стартовой точки. D - направление, куда смотрит мордочка робота. при старте программы она относительная. Поэтому, чтобы привязать ее к сторонам света, надо будет произвести некоторое шаманство при помощи геосканера. Таблица WORLD - это метки, которые будут устанавливаться в процессе сканирования. Таблица разделена на три, это смежные хранилища переменных для каждой координаты, например, сканер обнаружил блок с подходящей плотностью по координатам x15, y-10, z3, в таблицу они будут добавлены по одному индексу. Допустим, таблица была пустая, после добавления будет иметь вид WORLD.x[1] = 15, WORLD.y[1] = -10, WORLD.z[1] = 3 или WORLD = {x = {15}, y = {-10}, z = {3}} Далее следует функция, упрощающая добавление компонентов. На вход получает имя нужного компонента и, если он есть, выдает прокси к нему. Функция step() - основное движение робота. Учитывая, что программа используется исключительно для копания, копание будет в каждом шаге. Робот не тыкается носом в породу и не спрашивает какой блок перед ним. Махнул инструментом и смотрит результат. Если есть блок, но добыть его не получилось - следовательно, дальше делать нечего, там бедрок или еще чего похуже, потом добавим правильную обработку и эвакуацию по хлебным крошкам, а пока пусть будет заглушка. Если махнул удачно - пробуем еще раз и еще, до посинения. Это своеобразная защита от лагающего гравия/песка и назойливых сущностей (в виде гномиков). Далее, если функция движения была совершена удачно, то обновляем локальные координаты, учитывая направление движения. Ну и в конце функции сканируем таблицу меток, ищем метки с текущей позицией и удаляем, т. к. по нашим данным робот находится в блоке руды, следовательно, он его добыл. Еще есть дополнительная проверка по вертикали - если есть руда сверху или снизу, то захватываем, это позволит сократить общую сумму переходов между метками и ускорить добычу. Функция turn() - основной поворотник (аналог robot.turn(), но с обновлением переменной направления) Робот поворачивается, записывая результат в переменную, добавляя/отнимая единицу по модулю 4 при каждом повороте. Функция smart_turn() - поворот на желаемую сторону света, с минимумом действий. Вычисляет разницу между текущим и целевым направлением, запуская результат по модулю 4 через turn() Функции поворота можно будет объединить, но пока оставлю так. Функция go() - великий ход конем до нужных координат. Принимает координаты целевого блока, двигается по вертикали, поворачивает на цель X, двигается до цели, поворачивает на цель Z, двигается до цели. Для поворота использует smart_turn(), т. к. оси x и z глобальные, это стороны света.
  9. Doob

    Робот с геосканером. Часть #1 [движения]

    Экономия скорости не большая, по сравнению с потреблением памяти. В прошлой версии, робот сканировал и добывал слой за слоем, меток было мало. В этой версии такой подход не очень подходит, эксперименты показывают, что искать ближайший блок по всему чанку выгодней, чем послойно, даже если добывать слой 3 блока.
  10. При запуске программы надо оценить возможности робота, чтобы в дальнейшем, можно было точно знать количество энергии для перехода на точку старта. Робот должен измерить уровень энергии, сделать шаг, измерить еще раз и вычислить разницу. Эту разницу будет учитывать при измерении расстояния и принимать решение - идти домой или не идти. Функция robot.durability() не показывает правильный износ для зачарованных инструментов. Придется несколько раз ставить и разрушать блок, пока не обнаружится износ. Вынесем пока это все в отдельную функцию calibration() local function calibration() -- калибровка при запуске local energy = computer.energy() -- получить уровень энергии step(0) -- сделать шаг E_C = math.ceil(energy-computer.energy()) -- записать уровень потребления energy = robot.durability() -- получить уровень износа/разряда инструмента while energy == robot.durability() do -- пока не обнаружена разница robot.place(1) -- установить блок robot.swing(1) -- разрушить блок end W_R = energy-robot.durability() -- записать результат step(1) -- вернуться на место end Переменные E_C и W_R выносим в обую область видимости. Во время работы, например, после четырех сканов, робот будет оценивать количество доступных шагов по этой формуле: math.min(robot.durability()/W_R, computer.energy()/E_C) и сравнивать с расстоянием до точки старта. Из-за магической механики, зачарованные инструменты (Unbreaking) изнашиваются неравномерно, но роботу это нисколько не мешает.
  11. Doob

    Робот с геосканером. Часть #5 [тонкий расчет]

    По-другому никак. Естественно, все условия надо будет проверять при запуске и пинать пользователя, чтобы он соблюдал инструкцию.
  12. Doob

    Робот с геосканером. Часть #5 [тонкий расчет]

    Как это неизвестно? Первые три строчки как раз про это самое. Получаем заряд батарей computer.energy() Делаем шаг, который будет основной функцией во время работы. В коде шага есть взмах инструмента, за время работы функции, у робота отнимется энергия на работу всех компонентов - робот 0.25, чтение диска 0.1, слип 0.1, чанклоадер (если предварительно включили) и что там еще энергию потребляет. Измеряем заряд батарей заново, отнимаем от старого новый. energy-computer.energy() Округляем вверх, записываем в переменную E_C На каждый шаг, робот будет тратить энергию, в количестве E_C. Делим текущий заряд на это количество, получаем количество доступных шагов, сравниваем с расстоянием до базы и принимаем решение копать дальше ..или идти домой.
  13. Может опечатка? Вот рабочий код: local robot = component.proxy(component.list('robot')()) while true do computer.beep(20, 0.5) if robot.detect(3) then robot.swing(3) end end
  14. Doob

    Робот с геосканером. Часть #5 [тонкий расчет]

    Со стандартным конфигом 1.7.4 робот расходует любые инструменты на 4 действия эффективней игрока. А с зачарованными/модифицированными инструментами куча багов, вроде-бы появились после апгрейда функции robot.swing(), а может были и раньше. Но работать можно. В функции step() есть взмах инструментом и перемещение, до любой точки робот может дойти, сделав всего 3 поворота. Дополнительный запас хода будем учитывать в основном цикле, когда будет ясно, какие действия нужно выполнить, кроме движения (будет еще чистка инвентаря, упаковка и сброс лута).
  15. Геосканер потребляет много энергии, а функция compass() делает по 4 скана, пока не установит направление. Надо это исправить. Пусть робот сначала проверит наличие блока перед носом, сделает скан всех блоков вокруг себя, затем сломает блок и проверит разницу в полученных данных. Таким образом, будет производиться всего два сканирования, в прошлой версии их могло быть бесконечно много - если рядом нет блоков, робот бы крутился и молотил инструментом по воздуху, попутно делая по 4 сканирования. Улучшенная функция будет выглядеть так: local function compass() local sides = {2, 1, 3, 0} -- линки сторон света, для сырых данных D = nil -- обнуление направления while not D do -- пока не найдено направление if robot.detect(3) then -- проверить наличие блока перед носом local A = geolyzer.scan(-1, -1, 0, 3, 3, 1) -- сделать первый скан robot.swing(3) -- сломать блок local B = geolyzer.scan(-1, -1, 0, 3, 3, 1) -- сделать второй скан for n = 2, 8, 2 do -- обойти смежные блоки в таблице if math.ceil(B[n])-math.ceil(A[n])<0 then -- если блок исчез D = sides[n/2] -- установить новое направление break -- выйти из цикла end end else turn() -- задействовать простой поворот end end end
  16. Doob

    Робот с геосканером. Часть #4 [оптимизация компаса]

    Это да. Я оставлял условия запуска на совесть пользователя, но можно сделать остановку программы, при их несоблюдении.
  17. Тут какое-то сильное колдунство. В 1.7.10 чанки дюпают это всем понятно, но в данном случае программа должна выполняться дальше, даже после сотни дюпов. Я немного не понял, как будет вести себя computer.uptime() после отката чанка, вроде-бы оно привязано к времени всего мира. Но в любом случае, назад оно не пойдет. Надо повесить хук и при очередном таком баге отловить значения computer.uptime() и timestamp в основном цикле до проверки.
  18. Робот может двигаться, пора добавить функцию сканирования породы и калибровки компаса. (Пока тестировал, обнаружил баг работы с зачарованными инструментами, пришлось немного переделать функцию step() - теперь после неудачного свинга, робот дополнительно проверяет наличие блока. Можно будет оставить, даже когда разрабы это исправят) Чтобы отфильтровать блоки по плотности, надо получить плотность нужных блоков с учетом шумов. На расстоянии x8 z8 y1 от геосканера, максимальная плотность бедрока равна -0.317, внесем в фильтр -0.31. Для руды 3.683, но это ванильная руда, в модах бывает и больше. Минимальная плотность обсидиана 49.312, значит, eсли он не нужен, установим для полезных блоков максимальную плотность 40. C минимальной плотностью не все так гладко. Свинцовая руда из индастриала имеет плотность 2.5 это как у деревянных предметов, разброс с учетом шума от 1.3 до 2.7, это пересекается с камнем, у которого 0.8 - 2.2. Вот таблица некоторых блоков с минимальной и максимальной плотностью: Руда 2.312 - 3.683 Стекло -0.388 - 0.983 Камень 0.812 - 2.183 Грязь -0.188 - 1.183 Сундук 1.312 - 2.683 Обсидиан 49.312 - 50.683 Видно, что плотность стекла пересекается с плотностью коренной породы, но у бедрока приоритет выше, поэтому лучше лишний раз обойти. Исходя из этих данных, полезные блоки будут отмечаться с минимальной плотностью 2.3 и максимальной 40 Теперь опишем функцию сканирования. Заглянем в подсказку. Чтобы получить сырые данные, зададим координаты и размеры квадрата, относительно сканера. geolyzer.scan(позиция_х, позиция_z, позиция_y, ширина, длина, высота) Так как один раз можно отканировать только 64 блока, будем делать 4 подхода, получая координаты квадрата по горизонтали из вызывающей функции. Преобразовываем данные в координаты, попутно анализируя плотность условным оператором и устанавливаем метки. При обнаружении бедрока устанавливаем соответсвующий флаг во внешней для всех функций переменной. Получаем функцию scan(), выглядеть она будет примерно так: local function scan(xx, zz) -- сканирование квадрата x8 относительно робота local raw, index = geolyzer.scan(xx, zz, -1, 8, 8, 1), 1 -- получить сырые данные, установить индекс в начало таблицы for z = zz, zz+7 do -- развертка данных по z for x = xx, xx+7 do -- развертка данных по х if raw[index] >= 2.3 and raw[index] <= 40 then -- если обнаружен блок с плотностью от 2.3 до 40 table.insert(WORLD.x, X+x) --| записать метку в список table.insert(WORLD.y, Y-1) --| с коррекцией локальных table.insert(WORLD.z, Z+z) --| координат геосканера elseif raw[index] < -0.31 then -- если обнаружен блок с отрицательной плотностью border = true -- сделать отметку end index = index + 1 -- переход к следующему индексу сырых даннх end end end Раз уже взялись за геосканер, напишем и компас. Чтобы определить стороны света, надо сломать блок перед носом, просканировать его, затем установить обратно и, если есть разница - выдать результат. Для большей надежности добавим вращение вокруг своей оси, т. к. блока перед носом может и не быть или быть, но не тот. Координаты блоков задаем в таблице, сбрасываем текущее направление, определяем заново, вот и вся функция. Назовем ее compass() local function compass() -- определение сторон света local sides = {{-1,0}, {0,-1}, {1,0}, [0]={0,1}} -- привязка значений сторон света к смежным блокам D = nil -- обнуление текущего направления while not D do -- пока направление не найдено for n = 0, 3 do -- перебор сторон света robot.swing(3) -- разрушение блока if geolyzer.scan(sides[n][1], sides[n][2], 0, 1, 1, 1)[1] == 0 and robot.place(3) then -- тестовое сканирование и установка блока if geolyzer.scan(sides[n][1], sides[n][2], 0, 1, 1, 1)[1] > 0 then -- если обнаружена разница в сканах D = n -- установить новое направление break -- выйти из цикла end end end turn() -- задействовать простой поворот end end Самые важные функции готовы, можно приступить к тестированию.
  19. Ядро копателя готово, теперь можно и пощупать. Напишем пробную функцию сканирования и добычи одного слоя. Сначала откалибруем компас и зададим таблицу с координатами сканируемых квадратов. Затем, отсканируем квадрат 16 на 16 блоков, выведем количество обнаруженных блоков. И в цикле обойдем все метки. Вроде бы все просто. Ах, да... будем искать ближайший блок к текущей позиции, чтобы быстрее закончить работу. Есть много подходов к определению расстояний. Например квадрат гипотенузы равен сумме квадратов катетов, формула для нашего случая будет math.sqrt((X-x)^2+(Z-z)^2), где X,Z - координаты робота, x,z - координаты метки, можно выкинуть квадратный корень, в нашем случае бесполезный и даже вредный. Но тут есть одно "но", мы получили гипотенузу, а это наименьшее расстояние между точками, а роботы по диагонали не ходят. Я буду вычислять дельту между точками, суммируя реальное расстояние, которое пройдет робот по формуле math.abs(X-x)+math.abs(Z-z) Эта операция в сферическом вакууме потребляет на 5% больше процессорного времени, чем предыдущая, но с лихвой окупается сэкономленными шагами. В цикле будем обходить таблицу с метками, до каждой вычисляя расстояние, самый лучший результат с индексом будем хранить в отдельных переменных. По окончании работы цикла, будем посылать робота в ближайшую точку. Код всей тестовой программы под спойлером. А вот и видео с демонстрацией. Можно добавить штрафы на повороты, тогда он будет меньше крутиться и собирать кучи линейкой, а не змейкой.
  20. И так, наконец-то возвращаюсь к роботу-копателю. Да будут новые баги и новые фичи! Краткий план внедряемых фич: Улучшенное сканирование руд. Робот сканирует под собой квадрат 16x16 блоков, опускаясь блок за блоком. При обнаружении бедрока запускается функция добычи. При добыче робот поднимается и в цикле ищет ближайшие по горизонтали блоки руды, захватывая три слоя - Y+1, Y, Y-1 Определение энергопотребления сборки при запуске. На старте, робот запоминает количество потребленной энергии на один шаг + прочность инструмента. Это будет служить константой при проверке статуса, чтобы была возможность гарантированно вернуться на точку старта. Умная упаковка добычи. Перед обработкой рассыпухи, теперь будет точнее анализироваться свободное место, упаковка не будет происходить механическим перебором, из-за которого бывали внезапные сбои. При наличии генератора, робот всегда будет с собой таскать уголь, при разгрузке на точке старта будет забирать стак угля или угольных блоков. Текущие константа энергопотребления и координаты будут записываться в EEPROM. Следовательно, при наличии сетевой/связанной карты, робота можно будет будить и не бояться выгрузки чанков, лагов, космических лучей. Скорее всего, добавлю функцию аварийного крафта кирок из булыги, в случае работы с ванильными инструментами. Планируется утилита, собирающая программу по параметрам, заданным пользователем. ...Или не планируется, скорее всего все возможности копалки не получится впихнуть на EEPROM, поэтому на EEPROM будет загрузчик с main функцией, а дополнительные модули придется записывать на жесткий диск. Планируется поддержка модов, например, возможность возить с собой и разворачивать заправочную станцию в виде генератора и зарядника. Или скидывать предметы в межпространственные сундуки. Тут надо будет смотреть, как будут развиваться моды. В блоге буду описывать каждую функцию, чтобы отследить создание программы шаг за шагом, надеюсь, кому-то это поможет.
  21. Просто в прошлой версии был свитч переключения режимов - визуальный с красивостями и только текст, в режиме текста все теги добавлялись нормально руками.
  22. Doob

    Робот с геосканером. Часть #0 [планы]

    Никак, стандартный уровень шума не изменился. Первая версия сканировала 8x8 блоков, я жестко задал плотности, которые получил при сканировании дальних блоков (самая мягкая руда и бедрок) В этой версии изменять ничего не придется, т. к. так же сканируются 4 квадрата x8, только теперь робот не будет смещаться по горизонтали, пока не дойдет до бедрока. С таким подходом уровень шумов по краям будет постоянным, ускорится добыча руды и общий профит.
  23. Как пользоваться функцией scan? Итак, в этой функции можно задать два параметра, это относительные координаты по горизонтали (относительно блока геосканера/планшета или робота с геосканером), возвращаемое значение - таблица. Таблица содержит список плотностей блоков по вертикали. Чем дальше по вертикали находится блок (относительно геосканера), тем больше помех дает геосканер. Уровень помех можно настроить в конфиге мода. Ну тут вроде-как все понятно, разберемся с вертикальными координатами. При сканировании, геосканер возвращает таблицу, которая представляет собой столб из блоков. Кроме плотности ничего узнать нельзя. Возьмем, к примеру, scan(0, 0) это будет столб, выстой 64 блока, в котором находится устройство (геосканер/робот/планшет) В стандартнои конфиге радиус действия геосканера = 32 блока, следовательно можно просканировать кубоид 65x65x64 блока. Геосканер у нас находится на уровне 33 (индекс таблицы), на этом уровне при сканировании устройстрво почти не дает шумов, т. е. по горизонтали можно легко узнать истинную плотность блока. Индексы в таблице возрастают с высотой, т. е. блок с индексом 1 это -32 блок относительно устройства, 2 это -31 ... 33 это 0 и т. д. Теперь разберемся с горизонтальными координатами. Горизонтальные координаты хоть и относительные, но они не зависят от положения устройства в пространстве, т. е. отсчет идет по сторонам света. scan(1, 0) это столб на один блок на восток от устройства, следовательно, scan(-1, 0) - на запад. scan(0, 1) это южный столб, scan(0, -1) - северный. Для примера возьмем двойной цикл, который будет сканировать столбы и заносить их в таблицу. local tbl = {} for x = -32, 32 do for y = -32, 32 do if not tbl[x] then tbl[x] = {} end tbl[x][y] = geolyzer.scan(x, y) end end Он у нас будет сканировать с северо-запада на юго-восток, т. е. будет захватывать всю доступную зону. К примеру, нам надо сканировать определенный квадрат, стоящий по-диагонали от геосканера, обозначим их вот так: NE - северо-восточный, NW - северо-западный, SE - юго-восточный, SW - юго-западный. local function scan(d, r) -- направление, дальность if d == 'NE' then x1, x2, y1, y2 = 1, r, -r, -1 elseif d == 'NW' then x1, x2, y1, y2 = -r, -1, -r, -1 elseif d == 'SE' then x1, x2, y1, y2 = 1, r, 1, r elseif d == 'SW' then x1, x2, y1, y2 = -r, -1, 1, r end for x = x1, x2 do for y = y1, y2 do geolyzer.scan(x, y) -- тут должна быть фунция обработки или сохранения результатов сканирования end end end Чтобы просканировать квадрат со стороной 20 блоков, находящийся на юго-восток от геосканера надо запустить scan('SE', 20) Вроде-бы все очевидно. Функция analyze() намного проще, она возвращает таблицу с информацией о блоке, стоящем вплотную к устройству. Индексы таблицы не числовые, например, чтобы узнать плотность блока вводим analyze(сторона).hardness Чтобы задать сторону можно использовать sides API, либо просто задать цифрой. ...Будет дополняться...
  24. Doob

    Гайд по геосканеру

    Недавнo завезли нескoлькo нoвых функций. canSeeSky():boolean - вoзвращает true, если виднo небo. Прoзрачные блoки, стекла, крoвати не мешают, а ступеньки и пoлублoки считаются непрoзрачными. detect(стoрoна:number):boolean, string - кoпия функции component.robot, прoверяет наличие блoка с указаннoй стoрoны. Вoзмoжные варианты выдачи: (false, 'air') - пустo, блoк вoздуха. (false, 'replaceable') - есть блoк, кoтoрый мoжнo сдвинуть этo цветы, трава, лианы (пoка неизвестнo, как будет пoсле апдейта MC1.14). В приватах мoжнo считать за true, т. к. рoбoтoм лoмаются тoлькo кактусы. (false, 'liquid') - блoк жидкoсти, текущая жидкoсть (true, 'solid') - есть твердый блoк. (true, 'entity') - какая-тo сущнoсть. (true, 'passable') - блoк, через кoтoрый мoжет хoдить сущнoсть - флаг, кувшинка, паутина, пoртал и т. п. Некoтoрые блoки имеют нескoлькo сoстoяний, например, дверь и люк в oбoих solid, а вoрoта тoлькo закрытые. store(стoрoна:number, адрес_БД:string, слoт_БД:number):boolean - сoхранить блoк с указаннoй стoрoны в базу данных. isSunVisible():boolean - вoзвращает true, если пoгoда ясная, вo время дoждя или грoзы - false
  25. Их есть у меня. Год назад придумал годный квест, даже с админами договорился об организации, но в связи с переездом так ничего и не запустил. Если наберётся хотя бы 10 игроков, то могу допилить до ума.
×