Гайды

[Гайд] Все механики разведения в Cobblemon (мод Cobbreeding)

Автор: sanlover · Обновлён:

Гайд по всем механикам разведения в Cobblemon (мод Cobbreeding)

Этот гайд — результат анализа кода мода Cobbreeding. В нём нет воды и устаревших догадок — только реальная логика, на которую действительно опирается игра при формировании яйца.

На момент публикации: официальной вики или документации по Cobbreeding не существует. Этот гайд — единственный источник с цифрами, основанный на исходном коде самого мода.

Гайд подробно объясняет:

  • Как формируется яйцо
  • Какие параметры могут быть унаследованы
  • Как влияют различные удерживаемые предметы
  • Как работает шанс получения шайни
  • Как передаются способности, приёмы, шары и аспекты

Предполагается, что читатель уже имеет общее представление о моде Cobblemon и возможности размножения.


Оглавление

  1. Формирование яйца
  2. Механика передачи IV при размножении покемонов
  3. Наследование характера
  4. Наследование способностей
  5. image.png.d8ac5dbd4caf17ae0636116e6b4736d2.png Наследование Покебола
  6. Наследование шайни-статуса
  7. Наследование приёмов
  8. mirror_herb.png.173bcb80ba7fdcefe19b8a0a02c5a886.png Наследование яйцевых приёмов через Зеркальный Лист
  9. Наследование аспектов
  10. Правила подбора родителей и определение возможных яиц
  11. Полезные ссылки

Формирование яйца

Когда выбрана пара совместимых родителей, начинается процесс формирования яйца. На этом этапе система последовательно рассчитывает все параметры будущего покемона — от вида до приёмов.

Что рассчитывается?

  • Вид и форма
  • Индивидуальные значения IV
  • Характер
  • Способность
  • Покебол
  • Шайн-статус
  • Egg Moves
  • Аспекты

Общий алгоритм

  1. Выбирается родительская пара.
  2. Определяется вид и форма будущего покемона.
  3. Рассчитываются все свойства потомка.
  4. Эти данные объединяются в новое яйцо с готовыми параметрами.

Реализация в коде

Скрытый текст
fun chooseEgg(form: FormData, parents: Pair<Pokemon, Pokemon>): PokemonProperties {
    return PokemonProperties.Builder(form)
        .setParent1(parents.first.uuid)           // UUID первого родителя
        .setParent2(parents.second.uuid)          // UUID второго родителя
        .setIVs(calcStats(parents))               // Расчёт IV (уникальных параметров)
        .setNature(calcNature(parents))           // Характер
        .setAbility(calcAbility(form, parents))   // Способность
        .setCaughtBall(calcBall(parents))         // Покебол
        .setShiny(calcShiny(parents))             // Шайн-статус
        .setEggMoves(calcEggMoves(parents, form)) // Наследуемые приёмы
        .setAspects(calcAspects(parents))         // Аспекты (визуальные/сезонные)
        .build()                                   // Построение объекта потомка
}

Механика передачи IV при размножении покемонов

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

Всего у покемона 6 характеристик:

  • HP — Очки здоровья
  • Attack — Атака
  • Defence — Защита
  • Special Attack — Спец. атака
  • Special Defence — Спец. защита
  • Speed — Скорость

Общая логика наследования

  1. В начале для потомка создаются случайные IV для всех 6 характеристик.
  2. Затем система определяет, какие параметры будут унаследованы от родителей, в зависимости от:
    • наличия удерживаемых предметов (Power Items);
    • использования узла судьбы (Destiny Knot).
  3. Всего может быть унаследовано до 5 параметров, остальные останутся случайными.

Удерживаемые предметы (Power Items) и их влияние

  • power_weight.png.f92d5957c3f074a383fec483cdb68d4a.png Груз силы (power_weight) — Очки здоровья (HP)
  • power_bracer.png.88d4c54fc69487d6fb799aaa386e2d15.png Наруч силы (power_bracer) — Атака (Attack)
  • power_belt.png.090dad4aeb622bbb8afa3be96107456e.png Пояс силы (power_belt) — Защита (Defence)
  • power_lens.png.d6c87fae5b80807e56e48cff140e2c33.png Линзы силы (power_lens) — Спец. атака (Special Attack)
  • power_band.png.dee4d2a65bd39c9665b7cb4646d3bc06.png Повязка силы (power_band) — Спец. защита (Special Defence)
  • power_anklet.png.bf349ea49e39df6a189fca72e044329a.png Браслет силы (power_anklet) — Скорость (Speed)

Как работают Power Items:

  • Если один родитель держит Power Item — он обязательно передаёт соответствующий параметр.
  • Если оба родителя держат Power Items, выбирается один случайный родитель, и передаётся только один параметр.
  • Если ни один родитель не использует Power Item, система случайным образом выбирает:
    • одну характеристику;
    • одного родителя, от которого она будет унаследована.

Этот переданный параметр всегда становится первым унаследованным значением в итоговых IV.

Влияние узла судьбы

После передачи первого параметра определяется, сколько дополнительных характеристик будет унаследовано от родителей:

  • Если у хотя бы одного родителя есть destiny_knot.png.92410df67ca316f48cd8d04ae6fffb2a.png узел судьбы (Destiny Knot), будет передано ещё 4 параметра.
  • Если destiny_knot.png.92410df67ca316f48cd8d04ae6fffb2a.png узел судьбы не используется, передаются ещё 2 параметра.

Каждый параметр выбирается по следующему принципу:

  1. Из оставшихся характеристик выбирается случайная.
  2. Выбирается случайный родитель.
  3. Если у него есть значение IV по этому параметру — оно передаётся.
  4. Характеристика исключается из дальнейшего выбора.

В результате потомок получает 3 или 5 унаследованных параметров, в зависимости от наличия узла судьбы.

Остальные IV

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

Даже при идеальных условиях (Power Item + Destiny Knot) нельзя передать все 6 параметров — один из них всегда будет случайным.

Реализация в коде

Скрытый текст
private fun calcStats(parents: Pair<Pokemon, Pokemon>): IVs {
    // Создаём случайные IV для потомка
    val finalStats = IVs.createRandomIVs()

    // Извлекаем родителей из пары
    val (father, mother) = parents

    // Набор всех возможных характеристик (IV)
    val ivs = mutableSetOf(
        Stats.HP,
        Stats.ATTACK,
        Stats.DEFENCE,
        Stats.SPECIAL[/i]ATTACK,
        Stats.SPECIAL[/i]DEFENCE,
        Stats.SPEED
    )

    // Определяем, какие IV будут переданы с помощью удерживаемых предметов (Power Items)
    val ivsToChooseFrom = mapOf(
        Pair(father, powerItemToIV(father.heldItem().item)),
        Pair(mother, powerItemToIV(mother.heldItem().item))
    ).filterNot { it.value == null }

    // Временные переменные для текущего IV и родителя
    var iv: Stats
    var parent: Pokemon

    // Если ни у одного из родителей нет Power Item — выбираем случайную характеристику и случайного родителя
    if (ivsToChooseFrom.isEmpty()) {
        iv = ivs.random()
        parent = parents.random()
    } else {
        // Если Power Items есть у обоих — выбираем одного случайным образом и сохраняем соответствующий параметр
        val (pokemon, stat) = ivsToChooseFrom.toList().random()
        iv = stat!!
        parent = pokemon
    }

    // Устанавливаем выбранное значение IV у потомка
    parent.ivs[iv]?.let { finalStats[iv] = it }

    // Исключаем переданный параметр из списка оставшихся
    ivs.remove(iv)

    // Определяем, сколько параметров будет дополнительно унаследовано:
    // если есть узел судьбы (Destiny Knot) — 4 параметра, иначе — 2
    var ivCount =
        if (father.heldItem().item == CobblemonItems.DESTINY[/i]KNOT  |  |  mother.heldItem().item == CobblemonItems.DESTINY[/i]KNOT) 4
        else 2

    // Передаём оставшиеся характеристики, пока не будет достигнуто нужное количество
    while (ivCount != 0) {
        // Выбираем случайную характеристику из оставшихся
        iv = ivs.random()
        // Выбираем случайного родителя
        parent = parents.random()
        // Если у родителя есть значение IV — передаём его потомку
        parent.ivs[iv]?.let { finalStats[iv] = it }
        // Удаляем характеристику из оставшихся
        ivs.remove(iv)
        // Уменьшаем счётчик
        ivCount--
    }

    // Возвращаем сформированные IV потомка
    return finalStats
}

Наследование характера

Каждый покемон имеет характер (Nature), который влияет на развитие его параметров. Один параметр усиливается на 10 %, другой — уменьшается на 10 %. Некоторые характеры — нейтральные и ничего не изменяют.

Как определяется характер у потомка:

  • Если ни один родитель не держит everstone.png.c89b46f62d7e13e5991312db9c52c3da.png Камень вечности (Everstone) — характер потомка будет случайным.
  • Если один родитель держит everstone.png.c89b46f62d7e13e5991312db9c52c3da.png Камень вечности — потомок наследует характер именно этого родителя.
  • Если everstone.png.c89b46f62d7e13e5991312db9c52c3da.png Камень вечности есть у обоих родителей, один из них выбирается случайно, и его характер передаётся потомку.

everstone.png.c89b46f62d7e13e5991312db9c52c3da.png Камень вечности — это единственный способ гарантировать нужный характер у потомка.

Таблица характеров и их влияния

image.png.d990eeeb5225b4019eae74e3a891fa52.png

Характеры без влияния (—) называются нейтральными и не влияют на параметры.

Переводы характеров соответствуют официальной русской локализации мода Cobblemon.

Авторские предпочтения

Некоторые характеры чаще используются для определённых ролей. Не силён в этом, но вот небольшая подборка:

Физический атакующий:

  • Adamant — усиливает Атаку, снижает Спец. атаку.
  • Jolly — увеличивает Скорость, снижает Спец. атаку.

Специальный атакующий:

  • Modest — повышает Спец. атаку, снижает обычную атаку.
  • Timid — увеличивает Скорость, снижает обычную атаку.

Танки и защитники:

  • Bold — усиливает Защиту, снижает Атаку.
  • Calm — усиливает Спец. защиту, снижает Атаку.
  • Relaxed — усиливает Защиту, снижает Скорость (часто не критично).

Реализация в коде

Код реализации
private fun calcNature(parents: Pair < Pokemon, Pokemon > ): Nature {
    // Извлекаем родителей
    val(father, mother) = parents

    // Проверяем, держит ли кто-то из родителей Камень вечности (Everstone)
    val parentsItems = mapOf(
        Pair(father, father.heldItem().item),
        Pair(mother, mother.heldItem().item)
    ).filterNot {
        it.value != CobblemonItems.EVERSTONE
    }

    // Если ни у кого нет Everstone — возвращаем случайный характер
    if (parentsItems.isEmpty())
        return Natures.getRandomNature()

    // Иначе выбираем одного из родителей с Everstone и передаём его характер
    return parentsItems.toList().random().first.nature
}

4. Наследование способностей

При размножении покемон может получить одну из доступных способностей ((Ability) своей формы. Форма покемона может иметь до трёх способностей: две обычные и одну скрытую.

Виды способностей

image.png.46f567771609166a6ef850cf8b859614.png

Как выбирается способность

  1. Определяется родитель, от которого будет передаваться способность:
    • если в паре есть Дитто — используется родитель, не являющийся Дитто;
    • если Дитто нет — используется мать.
  2. Система пытается сохранить способность этого родителя у потомка.
  3. Если родительская способность не сохраняется (по шансам), выбирается другая доступная способность формы.
  4. Если альтернатив нет — способность родителя сохраняется в любом случае.

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

Шансы сохранить способность

NVIDIA_Overlay_rj5IT2RSvF.png.34c356bea7dbd46190d414cddae9ebe5.png

Если альтернативная способность недоступна — родительская способность сохраняется автоматически.

Если у формы покемона только одна способность — шанс не применяется, потомок получает её гарантированно.

Реализация в коде

Код реализации
private fun calcAbility(form: FormData, parents: Pair<Pokemon, Pokemon>): String {
    val (father, mother) = parents

    // Определяем родителя, от которого будет передаваться способность:
    // если в паре участвует Дитто — используем второго родителя,
    // если нет — используем мать.
    val ancestor =
        if (mother.species == getByName("ditto")) father
        else mother
    val oldAbility = ancestor.ability

    // Если способность "заставлена" (forced) и соответствующая настройка включена,
    // то возвращаем её напрямую, без шанса.
    // Однако по умолчанию (если настройка отключена), это поведение игнорируется.
    return if (oldAbility.forced && Cobbreeding.config.forcedAbilitiesEnabled) oldAbility.name
    else {
        // Получаем приоритет и индекс способности родителя.
        // Если индекс определён (≥ 0), используем его напрямую.
        val (priority, index) = if (oldAbility.index >= 0) Pair(oldAbility.priority, oldAbility.index)
        else {
            // Если индекс не задан, пытаемся определить его вручную:
            // ищем в мапе способностей ту, что соответствует шаблону родителя.
            // Если не находим — по умолчанию берём 0 и наименьший приоритет.
            val entry = ancestor.form.abilities.mapping.entries.firstOrNull { ([/i], abilities) ->
                abilities.map { it.template }.contains(oldAbility.template)
            }
            entry?.run {
                Pair(key, value.map { it.template }.indexOf(oldAbility.template))
            } ?: Pair(Priority.LOWEST, 0)
        }

        // Определяем унаследованную способность:
        // 1) сперва пытаемся взять способность на той же позиции и с тем же приоритетом;
        // 2) если не находим — берём первую из этого приоритета;
        // 3) если и тут не находим — первую из самого низкого приоритета;
        // 4) если вообще ничего нет — подставляем "заглушку" (DUMMY).
        val inheritedAbility = form.abilities.mapping[priority]?.getOrNull(index)?.template ?: 
                               form.abilities.mapping[priority]?.firstOrNull()?.template ?:  
                               form.abilities.mapping[Priority.LOWEST]?.firstOrNull()?.template ?:  
                               Abilities.DUMMY

        // Формируем список возможных альтернативных способностей (не совпадающих с inheritedAbility).
        // Если скрытые способности разрешены — используем все приоритеты.
        // Если запрещены — исключаем способности с приоритетом LOW (скрытые).
        val remainingAbilities =
            if (Cobbreeding.config.hiddenAbilitiesEnabled)
                form.abilities.mapping.values
                    .flatten()
                    .map { it.template }
                    .filterNot { it == inheritedAbility }
            else
                form.abilities.mapping.values
                    .flatten()
                    .filterNot { it.priority == Priority.LOW }
                    .map { it.template }
                    .filterNot { it == inheritedAbility }

        // Определяем шанс сохранить способность родителя:
        // 60% для скрытых, 80% для остальных.
        val chance = when (priority) {
            Priority.LOW -> 6u  // 60% для приоритета LOW (Hidden)
            else -> 8u          // 80% для остальных
        }

        // Если список альтернатив пуст или шанс прошёл — оставляем способность родителя.
        // Иначе выбираем случайную из альтернативных.
        val template = if (remainingAbilities.isEmpty()  |  |  Random.nextUInt(10u) < chance) inheritedAbility
        else remainingAbilities.random()

        // Возвращаем имя итоговой способности
        return template.name
    }
}

Настройка hiddenAbilitiesEnabled в конфиге включена — скрытые способности передаются.
Настройка forcedAbilitiesEnabled по умолчанию выключена — «заставленные» способности игнорируются.


5. image.png.d8ac5dbd4caf17ae0636116e6b4736d2.png Наследование Покебола

Покебол, в котором был пойман родитель, также может передаться потомку. Это влияет только на внешний вид и не даёт игрового преимущества, но может быть важно для коллекционеров.

Как выбирается покебол:

  1. По умолчанию потомок получает Покебол от матери.
  2. Если родители — одного вида, потомок получает Покебол одного из них случайным образом.
  3. Если мать — Дитто, Покебол передаётся от отца.
  4. Некоторые Покеболы не могут быть унаследованы и заменяются на стандартный: Черишбол (Cherish Ball) и Мастербол (Master Ball).

Дитто никогда не передаёт Покебол, даже если он — второй родитель.

Реализация в коде

Код реализации
private fun calcBall(parents: Pair < Pokemon, Pokemon > ): String {
    var ball = parents.second.caughtBall // По умолчанию — Покебол матери

    // Если оба родителя одного вида — Покебол выбирается случайно от одного из них
    if (parents.first.species.name == parents.second.species.name)
        ball = parents.random().caughtBall

    // Если мать — Дитто, Покебол наследуется от отца
    if (parents.second.species.name == "Ditto")
        ball = parents.first.caughtBall

    // Некоторые Покеболы нельзя передать напрямую: заменяются на обычный
    if (ball == PokeBalls.CHERISH[/i]BALL  |  |  ball == PokeBalls.MASTER[/i]BALL)
        ball = PokeBalls.POKE[/i]BALL

    return ball.name.toString()
}

6. Наследование шайни-статуса

Покемон может родиться шайни — с редкой и уникальной расцветкой. Это чисто визуальный эффект, не влияющий на силу или характеристики, но высоко ценится среди коллекционеров.

Базовая вероятность

  • По умолчанию шанс рождения шайни — 1 к 8192.
  • Этот шанс может увеличиться, если активированы специальные параметры в конфигурации сервера и соблюдены некоторые условия.

Методы увеличения шанса

image.png.8bfdc21845464e02f46713d3dd32628d.png

По умолчанию включён только masuda. Метод crystal доступен, но неизвестно, активен ли он на сервере mcskill.

Как рассчитывается шанс

  1. Начальный шанс рождения шайни: 1 / 8192.
  2. За каждого родителя-шайни (если активен crystal) — шанс умножается на 4.
  3. Если родители пойманы разными тренерами (masuda) — шанс также умножается на 4.
  4. Итоговый шанс — это результат деления 8192 на 4 за каждое выполненное условие.

Примеры итоговых шансов

image.png.dc0db0f2576653b206220858b88ed534.png

Если crystal отключён, максимальный шанс, который можно получить, — 1 / 2048 (только за счёт masuda). Значение 1 / 128 возможно только если crystal включён и оба родителя — шайни.

Реализация в коде

Код реализации
private fun calcShiny(parents: Pair<Pokemon, Pokemon>): Boolean? {
    // Получаем список включённых методов повышения шанса (из конфигурации)
    val shinyMethod = Cobbreeding.config.shinyMethod

    // Если shiny-статус отключён полностью — не рассчитываем, возвращаем null
    if ("disabled" in shinyMethod) return null

    // Множитель, на который делится шанс при срабатывании условий (по умолчанию: 4)
    val shinyMultiplier = Cobbreeding.config.shinyMultiplier

    // Базовый шанс получения шайни (по умолчанию: 1 / 8192)
    var shinyOdds = Cobblemon.config.shinyRate

    // Метод crystal: увеличивает шанс, если один или оба родителя — шайни
    if ("crystal" in shinyMethod) {
        // Если первый родитель — шайни
        if (parents.first.shiny)
            shinyOdds /= shinyMultiplier

        // Если второй родитель — шайни
        if (parents.second.shiny)
            shinyOdds /= shinyMultiplier
    }

    // Метод masuda: увеличивает шанс, если родители от разных оригинальных тренеров
    if ("masuda" in shinyMethod) {
        // Получаем UUID оригинальных тренеров обоих родителей
        val owner1 = parents.first.originalTrainer ?: parents.first.getOwnerPlayer()?.stringUUID
        val owner2 = parents.second.originalTrainer ?: parents.second.getOwnerPlayer()?.stringUUID

        // Если тренеры отличаются — делим шанс ещё раз
        if (owner1 != null && owner2 != null && owner1 != owner2)
            shinyOdds /= shinyMultiplier
    }

    // Если шанс меньше 1 (например, при методе "always") — потомок точно будет шайни
    if (shinyOdds < 1) return true

    // Генерируем случайное число от 0 до shinyOdds
    // Если выпадает 0 — потомок будет шайни
    return Random.nextInt(0, shinyOdds.toInt()) == 0
}

7. Наследование приёмов

Яйцевые приёмы — это особые атаки, которые нельзя получить через уровень, ТМ или тренера. Они передаются только при размножении, если родитель владеет или имеет доступ к такому приёму.

На сервере механика упрощена: можно использовать ТМ/TR для изучения приёмов яйцевых групп.

Общая логика наследования

  1. У формы покемона есть список доступных яйцевых приёмов.
  2. У каждого из родителей проверяются:
    • все изученные приёмы;
    • все потенциальные приёмы, к которым у него есть доступ.
  3. Если хотя бы один из этих приёмов совпадает с доступным яйцевым — он добавляется к потомку.
  4. Исключение: если рождается Пичу (Pichu)), и у одного из родителей есть Световой шар (Light Ball)), то приём Volt Tackle добавляется всегда.

Достаточно, чтобы один из родителей мог выучить нужный приём, — не обязательно, чтобы он уже его знал.

Пример

  • У Пикачу есть доступ к приёму Fake Out, а у Риолу — это яйцевый приём.
  • Если Пикачу участвует в размножении — Риолу может унаследовать Fake Out.

Реализация в коде

Код реализации
private fun calcEggMoves(parents: Pair < Pokemon, Pokemon > , child: FormData): List < String > {
    // Получаем список возможных яйцевых приёмов у данной формы потомка
    val possibleEggMoves = child.moves.eggMoves
    val eggMoves = LinkedList < String > ()

    // Перебираем каждого родителя
    for (parent in parents.toList()) {
        // Получаем все потенциально доступные приёмы (включая наследуемые, ТМ и по уровню)
        val allAccessibleMoves = parent.allAccessibleMoves.toMutableSet()

        // Добавляем текущие выученные приёмы
        for (move in parent.moveSet.getMoves())
            allAccessibleMoves += move.template

        // Сравниваем с яйцевыми приёмами потомка — добавляем совпадения
        for (accessibleMove in allAccessibleMoves) {
            if (possibleEggMoves.contains(accessibleMove))
                eggMoves.add(accessibleMove.name)
        }
    }

    // Специальный случай: если вылупляется Пичу и один из родителей держит Light Ball
    if (
        child.species.name == "Pichu" &&
        parents.toList().any {
            p - > p.heldItem().item == CobblemonItems.LIGHT[/i]BALL
        }
    )
        eggMoves.add("volttackle")

    return eggMoves
}

8. mirror_herb.png.173bcb80ba7fdcefe19b8a0a02c5a886.png Наследование яйцевых приёмов через Зеркальный Лист

Некоторые приёмы покемоны могут перенять напрямую, без размножения. Это становится возможным, если покемон держит специальный предмет — mirror_herb.png.173bcb80ba7fdcefe19b8a0a02c5a886.png Зеркальный Лист (Mirror Herb).

Как работает механика

  1. В пастбище проверяются все покемоны.
  2. Если покемон держит Зеркальный Лист:
    • Определяется список приёмов, которые он теоретически может получить при рождении.
  3. Все остальные покемоны в загоне перебираются:
    • Сравниваются их доступные и выученные приёмы.
  4. Если какой-либо приём совпадает с потенциальными приёмами покемона с Зеркальным Листом, он добавляется этому покемону.

Что это даёт?

  • Позволяет выучить уникальные или редкие приёмы, не прибегая к размножению.
  • Работает, даже если покемон-источник приёма не изучал его напрямую, а просто имеет к нему доступ.
  • Применимо к одиночному покемону — не требует пары.

Реализация в коде

Код реализации
fun applyMirrorHerb(pokemon: List<Pokemon?>)
{
    // Ищем покемонов, держащих Mirror Herb
    for (holder in pokemon)
    {
        if (holder != null && holder.heldItem().item == CobblemonItems.MIRROR_HERB)
        {
            // Получаем список приёмов, которые этот покемон может получить при рождении
            val possibleEggMoves = getBaby(holder.form).moves.eggMoves

            // Перебираем всех других покемонов в загоне
            for (other in pokemon)
            {
                if (other != null)
                {
                    // Собираем все приёмы, к которым у них есть доступ
                    val allAccessibleMoves = other.allAccessibleMoves.toMutableSet()
                    for (move in other.moveSet.getMoves())
                        allAccessibleMoves.add(move.template)

                    // Если у другого покемона есть приём, совпадающий с возможностями holder'а — передаём
                    for (accessibleMove in allAccessibleMoves) {
                        if (possibleEggMoves.contains(accessibleMove))
                            holder.benchedMoves.add(BenchedMove(accessibleMove, 0))
                    }
                }
            }
        }
    }
}

9. Наследование аспектов

Аспекты — это скрытые метки, прикреплённые к покемону. Они используются для хранения нестандартных характеристик: например, визуальных эффектов, пола (male, female), шайни-статуса (shiny), а также уникальных особенностей, добавленных через датапаки или моды (например, сезонные или ивентовые аспекты).

Как работает наследование аспектов

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

  1. Аспекты наследуются от матери, если в паре нет Дитто.
  2. Если мать — Дитто, аспекты передаются от отца.
  3. Перед передачей из набора аспектов удаляются следующие системные теги:
    • shiny
    • male
    • female

Это делается для того, чтобы не нарушить механику шайни-статуса и пола, которые определяются отдельно.

Что можно передать через аспекты?

  • Особые формы покемонов, не влияющие на параметры, но изменяющие внешний вид (например, цвет глаз, узоры, зимние окраски).
  • Модифицированные метки, добавленные другими модами, если они реализованы как aspect.

Пример

Если у матери установлен аспект snowfest2023, то этот аспект будет передан потомку. А вот аспект shiny — не будет передан, так как он определяется отдельной системой.

Реализация в коде

Код реализации
private fun calcAspects(parents: Pair<Pokemon, Pokemon>): List<String> {
    // Определяем родителя, от которого будут унаследованы аспекты:
    // если второй родитель — Дитто, используем первого (отца),
    // иначе — второго (матери). Т.е. при наличии Дитто аспекты не берутся от него.
    val mother = if (parents.second.species == getByName("ditto")) parents.first else parents.second

    // Копируем все аспекты выбранного родителя (например, сезонные, пользовательские и т.п.)
    val aspects = mother.aspects.toMutableList()

    // Удаляем аспекты, которые не должны передаваться:
    // shiny — рассчитывается отдельно;
    // male/female — определяется системой пола.
    aspects.remove("shiny")
    aspects.remove("male")
    aspects.remove("female")

    // Возвращаем оставшиеся аспекты — они будут применены к потомку
    return aspects
}

Правила подбора родителей и определение возможных яиц

Не любая пара покемонов может оставить яйцо. Перед созданием яйца система проверяет совместимость родителей — их пол, группы яиц, исключения и участие Дитто.

Условия совместимости

  1. Оба родителя должны принадлежать к одной и той же группе яиц, если только один из них не является Дитто.
  2. Минимум один из родителей должен быть мужского пола, другой — женского.
  3. Покемоны из группы Undiscovered не способны к размножению.
  4. Дитто может спариваться почти с любым другим покемоном.

Кто определяет вид потомка?

  • Если в паре участвует Дитто — потомок всегда получается второго родителя (не-Дитто).
  • В обычной паре (без Дитто) вид потомка определяется по женскому покемону.

Особые случаи

image.png.fa2dca57d9f12fe37b2369743e166173.png

Как определяется возможный потомок

  1. Если у покемона есть цепочка эволюций — отслеживается базовая форма. Потомок всегда будет этой первой эволюцией.
  2. Некоторые формы (например, региональные) имеют исключения: их базовая форма может быть нестандартной.
  3. Для таких форм вручную заданы специальные варианты потомков:
    NVIDIA_Overlay_LP9mST9YYs.png.80e33ce430d5c7c6150b580cd086959e.png

Реализация в коде

Код определения яиц и формы потомка

Определение возможных яиц

fun getPossibleEggs(pokemon: List<Pokemon>): Collection<Map.Entry<FormData, List<Pair<Pokemon, Pokemon>>>> {
    // Словарь, где ключ — форма потенциального потомка, значение — список родительских пар
    val eggs = HashMap<FormData, MutableList<Pair<Pokemon, Pokemon>>>()

    // Перебираем все возможные уникальные пары покемонов
    for (i in pokemon.indices) {
        for (j in i + 1 until pokemon.size) {
            val pokemon1 = pokemon[i]
            val species1 = pokemon1.species
            val eggGroups1 = species1.eggGroups

            val pokemon2 = pokemon[j]
            val species2 = pokemon2.species
            val eggGroups2 = species2.eggGroups

            // Проверяем, что оба покемона не находятся в группе Undiscovered
            // (такие покемоны не могут размножаться вообще)
            if (!eggGroups1.contains(EggGroup.UNDISCOVERED) && !eggGroups2.contains(EggGroup.UNDISCOVERED)) {

                // Если один из покемонов — Дитто
                if (eggGroups1.contains(EggGroup.DITTO)) {
                    // Потомок формируется от второго покемона
                    dittoBreed(pokemon2)?.let {
                        // Добавляем пару (отец, мать) в список потомков соответствующей формы
                        if (eggs.containsKey(it)) eggs[it]?.add(Pair(pokemon2, pokemon1))
                        else eggs[it] = mutableListOf(Pair(pokemon2, pokemon1))
                    }
                } else if (eggGroups2.contains(EggGroup.DITTO)) {
                    dittoBreed(pokemon1)?.let {
                        if (eggs.containsKey(it)) eggs[it]?.add(Pair(pokemon1, pokemon2))
                        else eggs[it] = mutableListOf(Pair(pokemon1, pokemon2))
                    }
                }

                // Если оба покемона НЕ Дитто и их группы яиц пересекаются
                else if (eggGroups1.any(eggGroups2::contains)) {
                    // Женщина-самка слева
                    if (pokemon1.gender == Gender.FEMALE && pokemon2.gender == Gender.MALE) {
                        // Вид потомка определяется по самке
                        getBaby(pokemon1).let {
                            if (eggs.containsKey(it)) eggs[it]?.add(Pair(pokemon2, pokemon1))
                            else eggs[it] = mutableListOf(Pair(pokemon2, pokemon1))
                        }
                    }
                    // Женщина-самка справа
                    else if (pokemon2.gender == Gender.FEMALE && pokemon1.gender == Gender.MALE) {
                        getBaby(pokemon2).let {
                            if (eggs.containsKey(it)) eggs[it]?.add(Pair(pokemon1, pokemon2))
                            else eggs[it] = mutableListOf(Pair(pokemon1, pokemon2))
                        }
                    }
                }
            }
        }
    }

    // Специальный случай: Нидоран♀ и Нидоран♂ — всегда выводятся вместе
    if (eggs.containsKey(getByName("nidoranf")!!.standardForm)) {
        eggs[getByName("nidoranm")!!.standardForm] = eggs[getByName("nidoranf")!!.standardForm]!!
    } else if (eggs.containsKey(getByName("nidoranm")!!.standardForm)) {
        eggs[getByName("nidoranf")!!.standardForm] = eggs[getByName("nidoranm")!!.standardForm]!!
    }

    // То же самое для Volbeat и Illumise — возможны оба результата
    if (eggs.containsKey(getByName("illumise")!!.standardForm)) {
        eggs[getByName("volbeat")!!.standardForm] = eggs[getByName("illumise")!!.standardForm]!!
    } else if (eggs.containsKey(getByName("volbeat")!!.standardForm)) {
        eggs[getByName("illumise")!!.standardForm] = eggs[getByName("volbeat")!!.standardForm]!!
    }

    // Возвращаем мапу: форма → список допустимых родительских пар
    return eggs.mapValues { (_, v) -> v.toList() }.entries
}

Определение формы потомка

fun getBaby(pokemon: FormData): FormData {
    // Переход к базовому виду — пока у текущего вида есть предэволюция
    var species = pokemon.species
    while (species.preEvolution != null) {
        species = species.preEvolution!!.species
    }

    // Обработка исключений для региональных форм:
    // У этих форм потомок — не стандартная базовая форма, а конкретный слот формы
    if (pokemon.species.name == "Sirfetch’d" ||
        pokemon.species.name == "Cursola" ||
        pokemon.species.name == "Obstagoon" ||
        pokemon.species.name == "Runerigus" ||
        pokemon.species.name == "Clodsire" ||
        pokemon.species.name == "Overqwil" ||
        pokemon.species.name == "Sneasler")
        return species.forms[1]

    // Исключения, у которых потомок — третья форма (слот 2)
    if (pokemon.species.name == "Perrserker" ||
        pokemon.species.name == "Basculegion")
        return species.forms[2]

    // В остальных случаях: ищем подходящую форму по showdownId
    return species.forms.find {
        it.formOnlyShowdownId().contains(pokemon.formOnlyShowdownId())
    } ?: species.standardForm
}

Полезные ссылки

  • Репозиторий мода Cobbreeding
  • Репозиторий мода Cobblemon
  • Информация о шансах и местах спавна покемонов
  • Расшифровка тегов биомов
  • Краткая и частично устаревшая wiki от разработчика Cobbreeding (информация в гайде считается более приоритетной)

Послесловие

На этом я заканчиваю и благодарю вас за внимание. Буду рад комментариям, вопросам и обсуждению. Играю на втором сервере, бываю часто — не стесняйтесь писать в игре.

На всякий случай добавлю, что шанс вылупления любого яйца — 50 % каждые 10 минут. Знаю, что у некоторых игроков всё ещё возникают сомнения в работе механик, особенно касательно узла судьбы и power-айтемов. Специально для вас я вывел 54 потомка от фиксированной пары родителей, отследил все IV и провёл краткий анализ.

Результаты и визуализация — в отдельном исследовании: https://www.kaggle.com/code/sanlovty/breeding-theory-check-by-sanlover

Если вы замечаете неточности или хотите дополнить гайд — пишите. Я постараюсь продолжить отслеживать обновления мода и по возможности буду дополнять материал.

P.s.: Если руки дойдут оформить гайд, ждите гайд на cobblenav со сломанным pokefinder'om.

Благодарности

  • wt04d — информация о возможности изучения яйцевых атак через ТМ/TR.

Похожие гайды