Skip to content

Конфігурація ​

opys.config.mjs це звичайний файл JavaScript. Він описує одну збірку.

Найменша конфігурація ​

mjs
import { defineConfig, userDataDir } from '@opys/dev';
import { forge, java } from '@opys/minecraft';

export default defineConfig({
  output: 'game.opys',
  plugins: [
    forge({ version: '1.20.1' }), // the game, with Forge
    java({ version: '17' }), // the Java that runs it
  ],
  manifest: {
    command: '@forge.command',
    args: ['@forge.jvmArgs', '@forge.mainClass', '@forge.gameArgs'],
    workdir: '${game_directory}',
  },
  run: (manifest) => ({
    manifest: {
      vars: {
        ...manifest.vars,
        root: userDataDir('my-pack'),
        username: 'Player',
        uuid: '00000000-0000-0000-0000-000000000001',
        token: '0',
      },
    },
  }),
});

Чотири частини, завжди ті самі:

ЧастинаЩо означає
outputКуди opys build записує бандл?
pluginsЗ чого складається збірка?
manifestЯк запускається гра?
runЩо знає лише комп'ютер гравця?

defineConfig нічого не змінює. Він потрібен, щоб ваш редактор перевіряв конфігурацію, поки ви пишете.

Решта цієї сторінки проходить по кожній можливості: що вона робить, коли вона вам потрібна і чому вона влаштована саме так.

Плагіни ​

js
plugins: [
  forge({ version: '1.20.1' }),
  java({ version: '17' }),
],

Плагін додає частину збірки: файли, змінні, частини командного рядка. Список плаский, і кожен плагін приймає один об'єкт з параметрами.

Чому плагіни? Справжнє встановлення це тисячі файлів. Ніхто не має писати цей список вручну, і кожен плагін добре знає свою частину.

Коли два плагіни сперечаються, виграє пізніший у списку. Для змінних збирання також друкує попередження, бо зазвичай це випадковість.

Які плагіни існують і що кожен з них додає: Плагіни.

Рядок запуску ​

js
manifest: {
  command: '@forge.command',
  args: ['@forge.jvmArgs', '-Xmx4G', '@forge.mainClass', '@forge.gameArgs'],
  workdir: '${game_directory}',
},

Рядок, що починається з @, це посилання: '@forge.jvmArgs' стає аргументами JVM, які обчислив плагін forge. Все інше передається як написано.

Коли ви його чіпаєте: щоб додати власні прапорці (-Xmx4G) або поставити аргументи іншого плагіна в певне місце, наприклад агента авторизації, який має йти першим.

Чому ви пишете його самі: порядок аргументів важливий для Java, і лише ви знаєте, що потрібно вашій збірці. Тому opys нічого не вгадує. Він дає вам частини, а ви їх розставляєте.

Одруки перехоплюються. Посилання, що не називає нічого, зупиняє збирання і перелічує те, що існує. Ваш редактор підсвітить його ще раніше.

Два невеликі правила
  • Для аргумента, який справді починається з @, пишіть '\\@args.txt'.
  • Беріть command із завантажувача ('@forge.command'), а не з Java напряму. Тоді ваша конфігурація працюватиме далі, якщо завантажувачу колись знадобиться запускатися інакше.

Змінні ​

js
workdir: '${game_directory}',

${name} це заповнювач. Він заповнюється на комп'ютері гравця, коли гра встановлюється та запускається.

Чому: бандл це той самий файл для кожного гравця, але кожен гравець встановлює в інше місце. Тому шляхи пишуться відносно ${root}, а комп'ютер гравця каже, чому дорівнює root.

Обережно: ім'я, яке ніхто не визначив, лишається як написано, мовчки. Якщо гра показує гравця на ім'я ${username}, сталося саме це.

Усі імена: Змінні.

run ​

js
run: (manifest) => ({
  manifest: {
    vars: {
      ...manifest.vars,
      root: userDataDir('my-pack'),
      username: 'Player',
      uuid: '00000000-0000-0000-0000-000000000001',
      token: '0',
    },
  },
}),

run викликається на комп'ютері запуску щоразу, коли гра стартує. Він повертає дві речі, обидві необов'язкові:

ПолеЩо це
manifestЧастини маніфесту, які треба замінити.
featuresПрапорці, які треба ввімкнути, так само як --feature.
js
run: (manifest) => ({
  manifest: { vars: { ...manifest.vars, root: '/srv/minecraft' } },
  features: ['eula'],
}),

Коли він потрібен: завжди, для opys launch. Він дає теку встановлення та гравця.

Чому він окремо від manifest: все під manifest запечене в бандл і спільне для кожного гравця. Ваша домашня тека і ваш токен там бути не повинні. run ніколи не запечений.

Класична помилка

userDataDir() у manifest.vars запече вашу домашню теку в бандл. Тоді збірка працює у вас і ні в кого більше. Тримайте його в run.

Залишайте ...manifest.vars. Кожне поле під manifest замінює старе цілком, тож без цього поширення змінні плагінів губляться.

Бандл не містить run. Той, хто запускає бандл, передає ті самі значення через --var або зі свого лаунчера.

Власні змінні та середовище ​

js
manifest: {
  vars: { pack_version: '1.4.0' },
  envs: { PACK_VERSION: '${pack_version}' },
},

vars додає змінні. envs задає змінні середовища для гри.

Коли: стала, яку ви хочете в кількох місцях, або значення, яке мод читає із середовища.

Також для перевизначень. manifest.vars виграє в будь-якого плагіна, без попередження. Це місце, де вирішується суперечка, коли змінну задають два плагіни.

Лише сталі. Вони запечені в бандл, тож ніяких шляхів і ніяких секретів.

Параметри ​

Що гравець може змінити перед запуском: пам'ять, роздільну здатність, перемикач.

js
import { defineConfig, options } from '@opys/dev';

export default defineConfig({
  // ...
  options: options()
    .slider('xmx', {
      title: 'RAM',
      unit: 'MB',
      min: 1024, max: 16384, step: 512, default: 4096,
    })
    .select('preset', {
      title: 'Graphics',
      choices: { low: 'Low', high: 'High' },
      default: 'high',
    })
    .feature('custom_java', {
      title: 'Custom Java',
      options: options().directory('java_home', { title: 'Java folder' }),
    }),
});

Кожен вид додає налаштування: ім'я, яке воно задає, і його поля одним об'єктом.

ВидЗадаєУПотребуєМоже мати
.slider(name, fields)зміннуЧисло в діапазоні.min, max, step, defaultunit
.select(name, fields)зміннуОдин із choices.choices, як { value: label }default
.text(name, fields)зміннуРядок тексту.placeholder, default
.file(name, fields)зміннуШлях файла.
.directory(name, fields)зміннуШлях теки.
.feature(name, fields)прапорецьУвімкнено або вимкнено.default, options

Кожен вид також бере title, без якого не обійтися, і subtitle.

  • Ім'я це змінна або прапорець, які ваш маніфест уже використовує: ${xmx} в аргументі, allow.features.custom_java у правилі.
  • title обов'язковий. Без нього це помилка типів, а бандл відхиляє заголовок без нього.
  • Вибір починається з першого варіанта, якщо default не каже інакше.
  • Прапорець вимкнений, якщо немає default: true.
  • Налаштування під прапорцем мають значення, лише поки він увімкнений.
  • opys build відхиляє ім'я, використане двічі, і типову повзунка поза його діапазоном.

WARNING

Варіанти показані в порядку написання, крім значень, схожих на цілі числа. JavaScript перелічує такі першими, у числовому порядку: { b: 'B', 2: 'Two', 1: 'One' } показаний як One, Two, B.

По одному. Кожен вид це також функція, для списку:

js
import { directory, feature, slider } from '@opys/dev';

options: [
  slider('xmx', { title: 'RAM', unit: 'MB', min: 1024, max: 16384, step: 512, default: 4096 }),
  feature('custom_java', {
    title: 'Custom Java',
    options: [directory('java_home', { title: 'Java folder' })],
  }),
],

Це опис, а не значення. Налаштування йдуть у бандл, щоб лаунчер міг намалювати екран налаштувань. Лаунчер передає вибране гравцем як vars і features. opys launch їх не читає: використовуйте --var і --feature.

Переклади ​

Будь-який текст, який читає гравець, можна дати кожною мовою: title, subtitle, unit, placeholder і підпис варіанта. Мову, якою написана конфігурація, називає locale:

js
export default defineConfig({
  locale: 'en',
  options: options()
    .slider('xmx', {
      title: { en: 'RAM', uk: "Пам'ять" },
      unit: 'MB',
      min: 1024, max: 16384, step: 512, default: 4096,
    })
    .select('preset', {
      title: { en: 'Graphics', uk: 'Графіка' },
      choices: { low: { en: 'Low', uk: 'Низька' }, high: 'High' },
    }),
});
  • Звичайний рядок написаний мовою locale.
  • Переклад мусить містити locale. opys build відхиляє той, що не містить, і будь-який переклад у конфігурації без locale.
  • Мова це тег: en, uk, pt-BR.
  • Імена, значення варіантів і default ніколи не перекладаються: їх читає маніфест.
  • Лаунчер обирає мову гравця, а інакше бере locale.

cleanup ​

js
manifest: {
  cleanup: [{ includes: ['${game_directory}/mods/*.jar'] }],
},

Видаляє файли після встановлення.

Коли: ви прибрали мод зі збірки. Без cleanup гравці, які його вже мають, збережуть jar назавжди, бо opys лише додає та оновлює.

Чому це безпечно: файл, який встановлює ваша збірка, ніколи не видаляється, що б не казало правило. Тож правило вище означає «кожен jar у mods, який не є частиною цієї збірки».

js
cleanup: [
  { includes: ['${game_directory}/config/**'], excludes: ['**/options.txt'] },
],

excludes щадить файли. * лишається всередині однієї теки, ** переходить через теки.

Тримайте правила вузькими

Правило, що збігається зі збереженнями гравця, видаляє його збереження. opys відхиляє очевидно небезпечні (відносний шлях, .., порожня змінна), але він не може знати, що важливе для гравця.

Налаштування плагіна ​

js
modrinth({ versions, to })
  .exclude('**/*-sources.jar')
  .addRule('**/sodium-*.jar', 'disallow.os.osx'),

Кожен плагін можна налаштувати заднім числом.

Коли: плагін майже правильний. Один файл, якого ви не хочете, один мод, що ламається на macOS, одне завантаження, яке ви хочете з власного дзеркала.

МетодЩо робить
.exclude(match)Відкидає файли, що збігаються.
.addRule(match, rule)Обмежує їх платформою.
.updateFirst(match, patch)Змінює поля першого збігу.
.updateMany(match, patch)Змінює поля кожного збігу.
.removeIntegrity(match)Встановлює їх без перевірки хешу.
.overwrite(match, 'never')Лишає їх гравцю.

match це глоб над шляхами файлів або функція. Починайте глоб з **/: він порівнюється зі шляхом до заповнення змінних.

.overwrite(match, 'never') для файла, який змінює гравець, як-от options.txt: він встановлюється там, де його немає, і далі зберігається. .removeIntegrity відхиляється для файла з бандла, який завжди звіряється з власними байтами; зберігає його 'never'.

js
files({ from: 'client', to: (file) => `${GAME}/${file.rel}` }).overwrite(
  '**/options.txt',
  'never',
);

Той самий плагін двічі ​

js
modrinth({ versions: mods, to: intoMods }),
modrinth({ versions: packs, to: intoResourcePacks }).as('resourcepacks'),

Коли: один плагін, два призначення.

Чому .as(): посилання йдуть за ім'ям плагіна, тож два плагіни не можуть ділити одне. .as() дає другому власне.

Розробка та випуск з однієї конфігурації ​

js
export default defineConfig(({ mode }) => {
  const dev = mode === 'dev';
  return {
    output: dev ? 'pack-dev.opys' : 'pack.opys',
    // ...
  };
});
sh
opys build --mode dev

Коли: тестове збирання з зайвими модами, меншою пам'яттю або іншою текою.

Без --mode значення mode це ім'я команди: build, install або launch. Функція може бути async.

Один файл вручну ​

js
manifest: {
  artifacts: [
    {
      path: '${game_directory}/server-icon.png',
      source: { url: 'https://example.com/icon.png' },
      integrity: { sha256: '…' },
    },
  ],
},

Коли: рідко. links робить те саме і знаходить хеш за вас. Це запасний вихід.

Усе разом ​

Повна збірка: Fabric, два моди з Modrinth, власні файли конфігурації, варіант для розробки та очищення вилучених модів.

mjs
import { defineConfig, files, userDataDir } from '@opys/dev';
import { fabric, java, modrinth } from '@opys/minecraft';

export default defineConfig(({ mode }) => {
  const dev = mode === 'dev';

  return {
    output: dev ? 'pack-dev.opys' : 'pack.opys',

    plugins: [
      fabric({ version: '1.21.1' }),
      java({ version: '21' }),

      // Mods published on Modrinth, pinned by hash at build time.
      modrinth({
        to: (file) => '${game_directory}/mods/' + file.filename,
        versions: [
          'JjCVwmVA', // Sodium
          'N08Z8wog', // Lithium
        ],
      }).addRule('**/sodium-*.jar', 'disallow.os.osx'),

      // Our own files, carried inside the bundle.
      files({
        from: 'config',
        to: (file) => '${game_directory}/config/' + file.rel,
      }),
    ],

    manifest: {
      command: '@fabric.command',
      args: [
        '@fabric.jvmArgs',
        dev ? '-Xmx2G' : '-Xmx4G',
        '@fabric.mainClass',
        '@fabric.gameArgs',
      ],
      workdir: '${game_directory}',

      vars: { pack_version: '1.4.0' },
      envs: { PACK_VERSION: '${pack_version}' },

      // A mod dropped from the list above is deleted from players' disks.
      cleanup: [{ includes: ['${game_directory}/mods/*.jar'] }],
    },

    // Runs on the player's machine, every launch. Never baked into the bundle.
    run: (manifest) => ({
      manifest: {
        vars: {
          ...manifest.vars,
          root: userDataDir(dev ? 'my-pack-dev' : 'my-pack'),
          username: 'Player',
          uuid: '00000000-0000-0000-0000-000000000001',
          token: '0',
        },
      },
    }),
  };
});