Companion Dogs: посібник з моддингу
Як зробити власний аддон: нову породу або цілий новий вид, окремим модом Майстерні, що чіпляється до Companion Dogs.
Цей посібник для тих, хто пише моди. Якщо ви лише граєте, читайте два інші посібники: Можливості про те, що вміє собака, і Породи про породи.
Він припускає, що ви читаєте Lua і маєте модель тварини з ригом та анімацією. Художній конвеєр тут не описано. Розділ 2 перелічує, чому має відповідати файл моделі.
Усе, що назване в цьому посібнику, є публічним контрактом і призначене для виклику ззовні мода. Те, що тут не назване, є внутрішнім і може змінитися в будь-якій версії без попередження.
1. Що таке аддон
Аддон це звичайний мод Майстерні, який оголошує require=CompanionDogs і під час завантаження викликає одну функцію. Він додає одну породу (або кілька, якщо вони ділять одне тіло), і більше нічого.
База володіє:
- машиною станів анімації, набором анімацій і скелетом
- слідуванням, пошуком шляху, боєм, полюванням, випасом, вартуванням, потребами й обслуговуванням
- усім інтерфейсом: вікном собаки, радіальним меню, контекстним меню, вольєром, міткою на карті
- приручанням, прив'язаністю, розведенням, цуценятами й метисами
- налаштуваннями пісочниці та реплікацією в мультиплеєрі
Ваш аддон володіє:
- числами, які відрізняють вашу тварину від Карамело
- моделлю тіла, текстурою, портретом і, якщо він є, іконкою мудла
- тим, де її знаходять у світі
- її назвою та описом кожною мовою, яку ви постачаєте
Два забарвлення з однаковою поведінкою для бази все одно дві породи (дві назви, два записи у вольєрі); ви пишете їх з однієї спільної таблиці, як CD: Cats робить зі своїми п'ятьма забарвленнями.
2. Що потрібно перед початком
Вашій тварині потрібен власний скінований .glb у media/models_X/Skinned/, з повним набором із 21 анімаційного кліпу Rac_*. Кліпи не успадковуються між файлами моделей: модель із п'ятнадцятьма кліпами дає тварину, яка застигає першого ж разу, коли їй потрібен один із решти шести. Модель також має вкладатися в 60 кісток і мати верхній вузол в одиничній матриці, інакше рушій кидає помилку щокадру, а тварина малюється чорною плямою. Ще два кліпи необов'язкові, Rac_WalkLimpFront і Rac_WalkLimpBack (цикли ходьби з кульганням); вони важать лише тоді, коли ви ставите limpAnim = true у визначенні породи.
Якщо моделі ще немає, ви можете написати й перевірити все інше, вказавши CD.applyDogModel на одне з тіл бази. Тварина виглядає як Карамело, але всі системи з цього посібника працюють.
Ще потрібні текстура тіла і портрет для вікна вольєра.
Вирішіть до першого рядка коду, ваша тварина це порода чи вид. Порода це собака з іншими числами. Вид це тварина, у якої частини занять немає взагалі. Вибір змінює те, які поля ви пишете; про це розділ 6.
3. Файли аддона
Ось CD: Pug, найменший опублікований аддон, з тими частинами, що мають значення:
CompanionDogsPug/
42/
mod.info
icon.png
poster.png
media/
models_X/Skinned/Pug_Body.glb
scripts/models_pug.txt
textures/Body/Pug.png
textures/CDPortrait_pug.png
lua/shared/CompanionDogsPug_Breed.lua
lua/shared/Definitions/animal/PugDefinitions.lua
lua/shared/Definitions/animal/CompanionDogsPug_Parts.lua
lua/client/CompanionDogsPug_Moodle.lua
lua/shared/Translate/EN/IG_UI.json
lua/shared/Translate/PTBR/IG_UI.json
common/
І mod.info:
id=CompanionDogsPug
name=CD: Pug
description=Pugs for Companion Dogs. REQUIRES the Companion Dogs mod (0.6.8 or newer).
poster=poster.png
icon=icon.png
require=CompanionDogs
modversion=0.1.0
pzversion=42.19.0
versionMin=42.18.0
Головний рядок це require=CompanionDogs. Він гарантує, що весь Lua бази виконався до вашого, у кожній фазі: спочатку shared, потім client, потім server. Без нього ваші файли можуть завантажитися першими, і кожен виклик CD. стає звертанням до nil.
versionMin не вміє сказати «потрібен Companion Dogs 0.6.8». Це поле обмежує лише збірку гри, а поля для версії залежності в mod.info немає. Нижню межу бази тримає перевірка з наступного розділу, а гравцям її повідомляє ваш опис. Напишіть її там словами, інакше єдиний симптом, який отримає гравець, це тварина, яка ніколи не з'являється.
4. Перевірка версії
Ставте це на початку кожного файлу Lua вашого аддона, з тим числом, яке потрібне вашому аддону:
local CD = CompanionDogs
if not (CD and CD.registerBreed and (CD.API_VERSION or 0) >= 3) then return end
Якщо бази немає або вона застара, файл виходить на другому рядку, і аддон не робить нічого: ні породи, ні спавну, ні мудла, ні рядка в логах.
Перевірка стоїть у кожному файлі, і кожен файл перевіряє те, чим користується. PugDefinitions.lua потребує помічників моделі, тож перевіряє саме їх:
if not (CompanionDogs and CompanionDogs.applyDogModel and CompanionDogs.DOG_SOUNDS) then return end
Не створюйте глобальну змінну. CompanionDogs = CompanionDogs or {} в аддоні перетворює відсутню базу на недобудовану таблицю, і кожна наступна перевірка проходить там, де не мала б.
5. Реєстрація породи
Один виклик, у файлі shared:
CD.registerBreed({
key = "pug",
engineBreed = "pug",
typePrefix = "pug",
nameKey = "IGUI_PD_Breed_pug",
litter = { 1, 2 },
xpMult = { scent = 1.5, combat = 1, obedience = 2.0, hunt = 1.2, herding = 1.0 },
combatPower = 0.15,
lethalityCurve = { min = 0.40, max = 1.3 },
canKill = false,
canKnockdown = false,
combatStressMult = 0.9,
panicThreshold = 0.70,
bagMult = 0.6,
sentinelMult = 1.35,
barkNoiseMult = 1.5,
alertModeLocked = true,
loyaltyDecayMult = 0,
geneRange = {
strength = { 0.00, 0.10 },
aggressiveness = { 0.05, 0.20 },
resistance = { 0.00, 0.15 },
stress = { 0.55, 0.85 },
},
spawns = {
{ id = "pughouse", class = "house", suffix = "|pg", breed = "pug",
chance = function() return CD.strayChancePerHouse() / CD.PUG_HOUSE_RARITY end },
},
})
Виклик вставляє породу, реєструє три її типи тварин, перебудовує порядок розведення, щоб ваша тварина брала участь у схрещуванні, і реєструє кроки спавну.
Погане визначення змушує виклик повернути nil замість помилки, тож завантаження триває далі. Кожне відхилення друкує в логах іменований рядок.
Обов'язкові поля
| Поле | Що це |
|---|---|
key |
ваш ідентифікатор породи, унікальний серед усіх модів. Не міняйте його після релізу. |
typePrefix |
префікс трьох типів тварини: <prefix>pup, <prefix>female, <prefix>male. Вибирає меш тіла. |
nameKey |
ключ перекладу для показуваної назви. |
xpMult |
швидкість навчання за навичками: scent, combat, obedience, hunt, herding. 1.0 це звичайна. |
combatPower |
наскільки сильно б'є. У Карамело це 0.20, у бійцівської породи вище 1.0. |
lethalityCurve |
{ min, max }, як росте шкода від Бою 0 до Бою 10. |
geneRange |
діапазон при народженні для чотирьох генів: strength, aggressiveness, resistance, stress. Кожен це { low, high } у межах від 0 до 1. |
Необов'язкові поля
| Поле | За замовчуванням | Що робить |
|---|---|---|
engineBreed |
key |
назва породи, яку отримує рушій. Вона вибирає текстуру. Унікальна для кожної породи. |
descKey |
IGUI_PD_BreedDesc_<key> |
ключ опису. Кілька порід можуть ділити один ключ. |
litter |
значення бази | { min, max } цуценят за один окіт. |
canKill |
true | false означає, що вона виснажує зомбі, але ніколи не завдає смертельного удару. |
canKnockdown |
false | true дозволяє їй збивати зомбі з ніг. |
combatStressMult |
1 | скільки стресу коштує їй бій. |
panicThreshold |
значення бази | рівень стресу, на якому вона припиняє битися. panicImmune = true означає, що не припиняє ніколи. |
bagMult |
1 | множник місткості сідельних сумок. |
puppySize |
0.6 | абсолютний візуальний масштаб цуценяти. Мала порода мусить його задати, інакше цуценя народжується розміром з дорослого. |
sentinelMult |
1 | множить підсумковий радіус вартування. |
barkNoiseMult |
1 | масштабує радіус і гучність тривожного гавкоту. Коли в пісочниці вимкнено шум, жодна порода не приваблює зомбі гавкотом, хоч би яке було значення. |
loyaltyDecayMult |
1 | множить добове падіння лояльності. 0 означає, що прив'язаність не згасає ніколи. |
alertModeLocked |
false | тварина лишається в повній тривозі: господар ніде не може поставити тихий або беззвучний режим. |
huntFetchLevel |
6 | рівень Полювання, з якого вона несе здобич господарю. |
huntDeliverTimeoutMin |
5 | ігрові хвилини, скільки вона намагається донести, перш ніж кинути. |
distract |
вимкнено | { <kind> = { chance = 0..1, ... } }. Порода сама йде за речами, без режиму й без потрібного рівня. |
idleAnimMs |
значення бази | довжина вікна анімації спокою, у мілісекундах. Задайте його, коли ваші кліпи коротші за собачі, інакше цикл перезапускається і ріже жест посередині. |
restAnim |
true | false лишає тварину стояти на «На місці» та «Охороняти» замість того, щоб лягати. |
restPoses |
лягти | список поз відпочинку, з якого тварина тягне одну щоразу, коли вмощується, з необов'язковими переходами та варіаціями спокою. Потрібні власні кліпи та власні вузли анімації. |
limpAnim |
false | true змушує поранену тварину кульгати під час ходьби. Ставте лише тоді, коли у вашої моделі є два необов'язкові кліпи Rac_WalkLimpFront і Rac_WalkLimpBack (цикли ходьби на 1.0 с з тим самим рухом кореня, що й Rac_Walk). Без них тварина йде застиглою на місці. |
bandSkin |
вимкнено | { base, front, back, cutFront, cutBack }, п'ять назв текстур тіла. На пораненій лапі тварина носить варіант із порізом, поки рана кровоточить, і варіант із перев'язкою, поки її накладено. Чотири варіанти зберіть скриптом _dogrig/forge/_paw_band.py. Без цього поля тварина все одно отримує рани, просто без видимої позначки. |
voices |
звуки собаки | { bark, growl, idle, wildbark, pet, whine, eat, drink }. |
diet |
собачі списки | чого тварині не можна їсти, що рахується для неї м'ясом і що вона сама бере з годівниці. |
maleChance |
0.5 | частка породи, що народжується самцями, від 0 до 1. |
sterileMale |
false | true прибирає самців породи з розведення. |
species |
"dog" |
розведення замкнене всередині виду. Розділ 6. |
skills, canBreed, huntMaxPrey |
усе ввімкнено | структурні блоки. Розділ 6. |
Числа й прапорці, які ви додали у визначення, читаються назад через CD.breedNumber(animal, field) і CD.breedFlag(animal, field), а не через CD.getBreedDef(animal).field. Вони переживають відсутнє поле і працюють далі, коли база змінюється.
Інстинкт: distract
distract = {
prey = { chance = 0.25 },
},
Порода, яка це оголосила, час від часу щось помічає і кидається за ним, покидаючи те, що робила. Вона робить це і в режимі «На місці», і в «Слідувати». Поки триває відволікання, накази повертаються відмовою з тим самим повідомленням distracted, яке вже використовує перевірка послуху. Проходить лише команда «Іди сюди», і вона скасовує відволікання.
Кожен різновид має ті самі необов'язкові ручки. Усе, що ви пропустите, бере значення мода за замовчуванням:
| Поле | Що робить |
|---|---|
chance |
0..1, кидається лише тоді, коли тригер різновиду щось знайшов. Написання 25 замість «25%» відхиляється з іменованим рядком у логах |
radius |
скільки клітинок обшукує тригер, якщо цей різновид узагалі шукає |
durationMin |
ігрові хвилини, скільки живе вікно, поки не закінчиться саме |
cooldownMin |
ігрові хвилини до того, як ту саму тварину можна відволікти знову |
Різновид prey
Єдиний різновид, який постачає базовий мод. Тварина кидається за дикими тваринами, а classes вибирає, які саме рахуються:
distract = {
prey = { chance = 0.25, radius = 6, classes = { tiny = true, small = true } },
},
Два обмеження діють хоч би що ви оголосили:
- Вона ніколи не виходить за ваш
huntMaxPrey: порода з межеюsmallне поженеться за оленем, навіть якщо оголосилаlarge. - Перевірка рівня пропускається лише для
tiny. Щойно приручена тварина з Полюванням 0 уже ловить гризунів. З оголошенимsmallпорода женеться за кроликом на будь-якому рівні, але для вбивства потрібен той самий рівень Полювання, що й лабрадору.
Перемикач полювання в пісочниці впливає тільки на вбивство: тварина все одно женеться, але здобич тікає.
Убита так здобич проходить звичайне принесення, тож huntFetchLevel вашої породи вирішує, чи понесе вона тіло господарю. На 0 тварина доставляє з першого дня, саме так кіт приносить вам дохлого щура. Доставка переживає вікно відволікання: щойно здобич мертва, тварина знову слухає накази, а дорога додому йде за власним huntDeliverTimeoutMin.
Як написати свій різновид
distract це диспетчер. Зареєструйте власний:
CD.registerDistraction("butterfly", {
gate = function(animal, cfg) return animal:isOutside() end, -- optional, cheap pre-check
find = function(animal, cfg) return findButterflyNear(animal, cfg.radius) end,
drive = function(animal, owner, d, cfg) return chaseIt(animal, d) end,
stop = function(animal, d) end, -- optional, cleanup
})
Далі оголосіть його в породі, як і будь-який інший різновид: distract = { butterfly = { chance = 0.10 } }.
gate виконується до глобального бюджету сканування мода. find виконується після бюджету і робить дорогий пошук. drive повертає true, поки ще керує твариною, і false, коли закінчив, що закриває вікно. Ваш обробник працює у власному pcall. Якщо він кидає помилку, мод пише іменований рядок у логах, знімає реєстрацію цього різновиду до кінця сесії і лишає решту компаньйона робочою.
Імена, зарезервовані для майбутніх різновидів бази: drink, eat, play. Сьогодні ви можете зареєструвати різновид під одним із них, але база забере ім'я собі, коли випустить власний.
Пози відпочинку
Тварина, що відпочиває, лягає. restPoses замінює цю єдину позу списком, з якого тварина тягне одну щоразу, коли вмощується:
restPoses = {
"cdRest", -- the base lie-down
{ var = "cdSit", enter = "cdSitIn", exit = "cdSitOut",
variation = { "cdSitGroom", "cdSitGroom2" } },
},
Запис це або ім'я змінної анімації, або таблиця. var це булеве значення, яке горить усю паузу. enter та exit це одноразові імпульси, що програються, коли тварина вмощується і коли встає. variation це список одноразових імпульсів, які вона час від часу програє, поки тримає позу, за тим самим годинником, що й варіація спокою. Обов'язковий лише var.
Вибір рівномірний і відбувається при кожному вході у відпочинок, тож два записи дають по 50% кожному, і тварина може витягти той самий двічі поспіль. Це не чергування.
Кожен імпульс триває стільки, скільки idleAnimMs каже для тієї змінної, тому передавайте табличну форму і задайте довжину кожній змінній, включно зі змінними спокою, які у вас уже були. Вказуйте справжню довжину кліпу.
idleAnimMs = { cdIdle2 = 3800, cdIdle3 = 3800, cdSitIn = 1375, cdSitOut = 1417,
cdSitGroom = 19833, cdSitGroom2 = 25167 },
Кліпи та вузли ваші, а не бази. Кожній змінній потрібен файл вузла у вашому аддоні в media/AnimSets/raccoon/idle/, а кліп, на який вказує його m_AnimName, має існувати у вашій моделі. Вузол, що вказує на кліп, якого модель не має, мовчки провалюється, і тварина просто стоїть у спокої бази. Дайте вузлу пози m_ConditionPriority 10, як у базових, і більше число всьому, що має грати поверх нього: 11 для варіації, 12 для двох переходів. Вузол крутить свій кліп по колу, якщо не сказати інакше, тому кожному вузлу, який має зіграти один раз, тобто обом переходам і кожній варіації, потрібен <m_Looped>false</m_Looped>. Без нього кліп починається знову, щойно закінчившись, і тварина помітно смикається назад і повторює рух, бо змінна гасне лише на наступному тіку сервера. Тримайте їх лише в idle/. Вузол пози в pathfind/ грає кліп без root motion і заморожує тварину там, де вона стоїть.
Голос
Без voices кожна порода бере звуки собаки, тож кіт гавкав би. Спершу зареєструйте власні звуки:
CD.registerVoices({
CDCatMeow = "bark", CDCatGrowl = "bark", CDCatHiss = "bark", CDCatPurr = "bark",
CDCatPet = "fx", CDCatMeowAmbient = "ambient",
})
Потім вкажіть на них у породі:
voices = { bark = "CDCatMeow", growl = "CDCatGrowl", idle = "CDCatPurr",
wildbark = "CDCatMeowAmbient", pet = "CDCatPet", whine = "CDCatHiss" },
Без CD.registerVoices звуки все одно грають, але ігнорують повзунок категорії у гравця і гучність ефектів гри, і ніщо в логах на це не вказує. Ключ, який ви не вписали у voices, відкочується до собачого звуку.
whine -- те, що тварина каже, коли поранена або захворіла. eat і drink -- звуки їжі та пиття. Собачі зациклені, і база зупиняє їх, коли тварина закінчила; якщо ваші теж зациклені, додайте їх у CD.SOUND_LOOPED, і база зупинить їх і тоді, коли слухач вийде за межі радіуса. База, старша за API 11, ігнорує ці три ключі й грає собачі звуки.
Третій аргумент це чутність кожного звуку в клітинках. Вона має збігатися з distanceMax, який ви написали у власному скрипті звуку:
CD.registerVoices(map, nil, { CDCatMeow = 22, CDCatGrowl = 10 })
На сервері база надсилає звук лише гравцям усередині цього радіуса. Якщо радіус не вказано, голос отримує загальний радіус за категорією: шипіння, чутне на десять клітинок, піде до всіх у межах тридцяти. За distanceMax зворотний спад FMOD перестає глушити замість того, щоб замовкнути, тож недбалий .ogg звучить на будь-якій відстані з гучністю distanceMin / distanceMax. З тієї ж причини тримайте distanceMin малим для гучних голосів.
Раціон
Без diet кожна порода їсть як собака: та сама їжа, що її отруює, та сама, що рахується м'ясом, і та сама, яку вона сама бере з годівниці. Поле перевизначає ці три списки тільки для вашої породи.
diet = {
bad = { Fruits = true }, -- fruit now makes it sick
badParts = { onion = false }, -- onion no longer does
protein = { Cheese = true }, -- cheese now counts as meat
proteinParts = { tuna = true }, -- and so does anything with "tuna" in its type
trough = { Vegetables = false }, -- it will not graze vegetables
troughItems = { ["Base.CatFoodOpen"] = true },
},
Дві форми існують тому, що власних категорій гри вистачає не на все. Cheese це справжній FoodType, тож один ключ покриває будь-який сир у грі й у будь-якому моді на їжу, що бере ту саму категорію. З тунцем не так: відкрита банка це FoodType = Fish разом з усією іншою рибою, а запаяна банка не оголошує типу їжі взагалі. badParts і proteinParts існують саме для таких випадків.
Кожне поле це таблиця з <key> = true (додати) або <key> = false (прибрати те, що було в базі). Значення, яке не true і не false, відкидається з іменованим рядком у логах.
| Поле | Що в його ключах |
|---|---|
bad |
FoodType їжі, від якої тварині зле, яка коштує їй лояльності і яка отримує червоний запис у меню годування |
badParts |
шматок типу предмета, малими літерами, збіг у будь-якому місці |
protein |
FoodType, який закриває м'ясний борг тварини, той, за яким стежить мудл «Ослаблений» |
proteinParts |
шматок типу предмета, збіг такий самий, як у badParts |
nonProtein |
FoodType, про який ви знаєте, що це не м'ясо. Він лише глушить рядок логу, який база пише для незнайомого типу їжі |
trough |
FoodType і AnimalFeedType, які тварина їсть сама з годівниці |
troughItems |
повний тип предмета, для предмета в годівниці, який не має власного типу корму |
replace = true починає з порожнього списку замість собачого. Беріть його, коли ваша тварина майже нічого не поділяє з собакою; кота зазвичай легше описати кількома додаваннями і прибираннями.
Три списки прив'язані до тварини, тож собака й кіт біля однієї годівниці їдять з неї різне. Виняток це власна миска мода: вона зберігає очки їжі, а не предмет, яким її наповнили, тож наповнення йде за списком бази для всіх. Отруйне в миску однаково не покласти.
Стать при народженні
Кожна тварина при появі отримує стать випадково, порівну. maleChance задає частку, що народжується самцями, від 0 до 1, а sterileMale прибирає самців породи з розведення.
maleChance = 0.00033,
sterileMale = true,
Ця пара і є триколірна кішка. Мозаїка з рудого і чорного лежить на X-хромосомі, тому триколірна майже завжди самка, а самець, який усе ж трапляється, це XXY, приблизно один на три тисячі, і потомства він не дає. Самця породи з sterileMale ніколи не обирають у пару, а з його контекстного меню зникає пункт парування. Самки розводяться як звичайно.
Самець такої породи отримує позначку «Безплідний» скрізь, де гра показує його стать: у картці, у контекстному меню, в огляді безпритульного і у вольєрі, щоб гравець розумів, чому той не дає потомства.
Обидва поля читаються з опису породи, тож на тварині нічого не зберігається і наявному збереженню не потрібна міграція. Той самий кидок вирішує стать цуценяти при народженні, тому порода, жіноча у світі, лишається жіночою і в приплодах.
6. Як вимкнути цілу систему
П'ять навичок це п'ять систем:
| Навичка | Яка це система |
|---|---|
scent |
вартовий: помічає зомбі й попереджає вас |
combat |
бій, режим Охорони й самозахист |
obedience |
дерево трюків |
hunt |
полювання, плюс бонус до збиральництва, який воно дає хазяїнові |
herding |
догляд за худобою в загоні |
Отже, вид оголошує те, чого в нього немає:
skills = { combat = false, herding = false },
canBreed = false,
huntMaxPrey = "tiny",
Відсутнє або true означає, що навичка в тварини є.
Вимкнення навички також зупиняє набір досвіду по ній і прибирає смугу та її команди з інтерфейсу.
canBreed = false стоїть окремо від таблиці, бо в розмноження немає ні смуги, ні досвіду. Тварина не вагітніє, її ніколи не обирають партнером, і зі своїм видом вона теж не розмножується.
Вид
Доповнення, що додає іншу тварину, а не ще одного собаку, оголошує це:
CD.registerSpecies({ key = "cat",
nounKey = "IGUI_PD_SpeciesNoun_cat",
youngKey = "IGUI_PD_Young_cat" })
CD.registerBreed({ ..., species = "cat" })
Поле відсутнє в кожній опублікованій досі породі, а відсутнє означає "dog".
Розмноження закрите за видом: пара вагітніє тільки тоді, коли обидві сторони одного виду, і в команді, і в пасивному погодинному розіграші, і в інструменті відладки. А інтерфейс перестає називати вашу тварину собакою: nounKey це голий іменник, що заповнює речення на кшталт "Цей %1 уже належить іншому вцілілому", а youngKey це підпис, який дитинча вашого виду носить у вікні, контекстному меню й вольєрі, де собака каже Цуценя.
Ці два іменники покривають лише речення, побудовані навколо %1. Кожне інше речення зі словом собака, вписаним прямо в текст ("Оглянути собаку", "Ваш собака голодний", "Собака гавкає навмисне"), починаючи з API 7 бере ключ по виду: покладіть у свої файли перекладу IGUI_PD_ScanStray_cat, і база покаже його замість IGUI_PD_ScanStray щоразу, коли на екрані кішка. Ключ, якого ви не поклали, відкочується на текст собаки. Ключі, що приймають суфікс: ScanStray, ScanTitle, PauseGrowthTip, BadFood, AlertFullDesc, AlertQuietDesc, AlertLockedTip, HuntModeDesc, кожен Trick*Desc, TrickDistractDone, TrickNeedsBag, TrickGotoPick, TrickSniffNoItem, TrickFetchNoItem, AlertGroup, DogLost, кожен Refused_*, KennelStatusNoData і Moodle_*_desc мудлів догляду (Sick, Weak, Hunger, Thirst, Rested, Grief). Речення, де тварини на екрані немає (вкладка реєстру, "Спочатку приручіть тварину"), у базі вже видонейтральні. Кішка постачає їх усі чотирнадцятьма мовами; беріть її IG_UI.json за шаблон.
group тварини читає тільки меню спавну в режимі відладки й переглядач кліпів, через IGUI_Animal_Group_<group>. Поставте t.group = "cat" після помічника поведінки й покладіть цей ключ, інакше ваш вид з'явиться там під назвою собаки.
Оголосити species, не зареєструвавши його, не смертельно: ваша тварина все одно відмовляється розмножуватися поза своїм видом, бо перевірка порівнює сирий рядок. Відкочуються лише два іменники, на собачі, і лог каже про це поіменно.
На базі, старішій за API 6, поле ігнорується без рядка в лозі, і ваша тварина розмножується як собака. Якщо вона не має схрещуватися з собакою ніколи, ставте захист на API 6 або тримайте canBreed = false, поки база стара, як робить кішка.
huntMaxPrey обмежує розмір здобичі всередині полювання, що досі існує; воно не замінює hunt = false. Приймає "tiny" (миші, щури, білки), "small" (кролі, єноти) або "large" (олені, стандарт, без обмеження). Ваша тварина не женеться й не робить стійку на здобич більшу за межу. Значення поза цією драбиною відкочується на "large" і друкує іменований рядок у лозі.
Заблокована опція зникає з радіального меню, контекстного меню й вікна, а сервер відмовляє в ній без повідомлення. Жодне з цих полів не додає ключа перекладу, тож поясніть обмеження в описі своєї породи.
7. Визначення тварини
Рушій прив'язує меш до типу тварини, а не до породи, тож вашій породі потрібні три власні типи. Найкоротший шлях це скопіювати файл наявного доповнення й перейменувати.
Три частини, в одному файлі shared під Definitions/animal/:
AnimalDefinitions.stages["pug"] = {}
AnimalDefinitions.stages["pug"].stages = {}
AnimalDefinitions.stages["pug"].stages["pugpup"] = {}
AnimalDefinitions.stages["pug"].stages["pugpup"].ageToGrow = 3 * 30
AnimalDefinitions.stages["pug"].stages["pugpup"].nextStage = "pugfemale"
AnimalDefinitions.stages["pug"].stages["pugpup"].nextStageMale = "pugmale"
AnimalDefinitions.breeds["pug"].breeds["pug"].texture = "Pug"
AnimalDefinitions.breeds["pug"].breeds["pug"].textureMale = "Pug"
AnimalDefinitions.breeds["pug"].breeds["pug"].rottenTexture = "Raccoon_Rotting"
AnimalDefinitions.breeds["pug"].breeds["pug"].invIconMale = "CDDogPaw_64"
AnimalDefinitions.breeds["pug"].breeds["pug"].invIconMaleDead = "CDDogPawDead_64"
local pugfemale = {}
CD.applyDogModel(pugfemale, "Pug_Body")
CD.applyDogBehaviour(pugfemale)
pugfemale.female = true
pugfemale.babyType = "pugpup"
pugfemale.minSize = 1.5
pugfemale.maxSize = 2.0
pugfemale.minWeight = 5
pugfemale.maxWeight = 8
pugfemale.wildFleeTimeUntilDeadTimer = CD.STRAY_NO_BLEEDOUT
pugfemale.breeds = AnimalDefinitions.breeds["pug"].breeds
pugfemale.stages = AnimalDefinitions.stages["pug"].stages
pugfemale.genes = AnimalDefinitions.genome["dog"].genes
AnimalDefinitions.animals["pugfemale"] = pugfemale
Помічники, які дає база:
| Помічник | Що заповнює |
|---|---|
CD.applyCompanionModel(t, "Body_Model") |
модель тіла, скелет, текстури розбирання й набір анімацій |
CD.applyCompanionBehaviour(t) |
усі прапорці поведінки рушія, потрібні супутникові |
CD.applyCompanionAvatar(t) |
камера портрета для вікна тварини |
CD.COMPANION_SOUNDS |
спільна таблиця кроків і фолі |
CD.defineGenome("species") |
геном нового виду, зі стандартним списком генів (API 7) |
CD.STRAY_NO_BLEEDOUT |
значення, що не дає переляканій вуличній тварині стекти кров'ю |
CD.defineCompanionParts(typePrefix, engineBreed, meat) |
частини розбирання всіх трьох стадій. meat необов'язковий: { item, minNb, maxNb, pupMinNb, pupMaxNb }, а без нього тварина дає базове собаче м'ясо. Рушій множить і кількість, і ситність кожного шматка на розмір туші, тож видові з великим розміром (кішка використовує 2.5 до 3.5 як візуальний масштаб) потрібен власний предмет із меншою базовою ситністю й малою кількістю |
Видонейтральні назви прийшли з API 7. Старіші, CD.applyDogModel, CD.applyDogBehaviour, CD.applyDogAvatar, CD.DOG_SOUNDS, CD.defineDogParts і CD.DogMoodles, це ті самі функції й таблиці під першим іменем. Доповнення, що хоче вантажитися на базі, старішій за 0.7.3, ставить захист на старі назви й бере нову, коли вона є: local applyModel = CD.applyCompanionModel or CD.applyDogModel.
Набір анімацій має лишатися "raccoon". Відгалужений набір анімацій не вантажить свою машину станів, і тварина стоїть на місці замороженою.
Використовуйте геном повторно. Собача порода вказує на список бази: AnimalDefinitions.genome["dog"].genes. Новий вид один раз викликає CD.defineGenome("cat") і дає кожній стадії таблицю, яку той повернув. Рушій читає лише список genes кожної стадії, а ключ геному це домовленість, тож ваш вид отримує власний запис без власного списку генів.
Не оголошуйте mate. З ним рушій запускає своє рідне парування всередині будь-якої зони тварин, яке ігнорує блок розмноження мода і його опції пісочниці, і дає цуценят без зв'язку. База чистить поле на старті, але не покладайтеся на це.
Частини обов'язкові. Три рядки в окремому файлі:
if CompanionDogs and CompanionDogs.defineDogParts then
CompanionDogs.defineDogParts("pug", "pug")
end
Без них розбирання вашої тварини валить ванільний код, бо той індексує визначення частин за типом і породою й натикається на nil.
Зум портрета в CD.applyDogAvatar відкалібровано під собаку, а зум аватара це збільшення: що менша тварина, то більше число. Поділіть зум на співвідношення розміру проти собаки й помножте на нього зсуви, інакше ваша тварина сидітиме крихітною внизу власного портрета.
8. Скрипт моделі
Один файл під media/scripts/ каже грі, що ваша модель існує:
module Base
{
model Pug_Body
{
mesh = Skinned/Pug_Body,
shader = animalEffect,
static = false,
animationsMesh = PugAnim,
attachment head
{
offset = -0.0061 0.1671 0.0507,
rotate = -179.0 -5.0 95.0,
bone = Dummy01,
}
}
animationsMesh PugAnim
{
meshFile = Skinned/Pug_Body,
keepMeshAnimations = true,
}
}
keepMeshAnimations = true залишає кліпи всередині вашого файлу моделі придатними. Без цього модель вантажиться й ніколи не анімується.
Прикріплення head це місце, де сидять капелюхи й подібне. Якщо хочете, щоб сідельні сумки сиділи на вашій тварині, додайте saddlebags_l, saddlebags_r і saddlebags_c так само, на кістці хребта. Ці числа шукайте, дивлячись на тварину в грі й підштовхуючи їх. Значення, зміряні з моделі, ставлять сумку в геометричний центр меша, а це не там, де вона добре виглядає на тілі.
9. Де з'являється тварина
Вуличні тварини розігруються по будівлях, по чанках, коли той чанк завантажується вперше. Свої розіграші ви описуєте списком кроків, або всередині визначення породи як spawns, або через CD.registerStraySpawns(list).
spawns = {
{ id = "pughouse", class = "house", suffix = "|pg", breed = "pug", indoor = 85,
chance = function() return CD.strayChancePerHouse() / CD.PUG_HOUSE_RARITY end },
{ id = "pugpetvet", class = "petvet", suffix = "|pv", breed = "pug",
chance = function() return CD.PUG_PETVET_CHANCE * CD.dogSpawnMultiplier() end },
},
| Ключ | Що це |
|---|---|
id |
назва кроку, для ваших власних логів |
class |
клас будівлі, на якому йде розіграш |
chance |
функція, що повертає відсоток, тож вона може прочитати множник пісочниці в момент розіграшу. |
suffix |
ключ постійного сховища. Унікальний серед усіх модів, і ніколи не змінюється. |
breed |
ключ породи, яку спавнити |
gate |
необов'язкова функція, що повертає булеве значення, перевіряється перед розіграшем. Хаскі використовує CD.isWinter. |
indoor |
відсоток шансу народитися всередині будівлі замість вулиці. Стандартно 35. |
Множте свій шанс на CD.dogSpawnMultiplier() або ділите CD.strayChancePerHouse() на власну рідкісність. Обидва варіанти залишають налаштування пісочниці робочим.
indoor це побажання: будівля без вільної клітинки всередині в тому чанку відкочується на клітинку надворі.
Класи будівель
База реєструє чотири:
| Клас | Що підходить |
|---|---|
house |
житлові будівлі, які не є магазинами |
police |
поліцейські дільниці й подібне |
petvet |
зоомагазини й ветеринарні клініки |
farm |
фермерські будівлі, і дозволено за межами міста |
Якщо жоден не підходить, зареєструйте свій:
CD.registerBuildingClass("mineshaft", function(def) return def:isX() end,
{ skipUrbanGate = true })
Функція збігу отримує визначення будівлі й виконується всередині захищеного виклику, тож помилка в ній не зупиняє завантаження чанка. skipUrbanGate дозволяє класу розігруватися в чанку, що не є міським, чого потребує ферма чи лісова хатина. Не ставте exclusive. Базові класи взаємно виключні між собою, а клас доповнення збігається поверх них.
Уже зайняті суфікси
Суфікс це частина ключа, під яким зберігається "ця будівля вже розіграна". Два моди з однаковим суфіксом псують розіграші одне одного.
Кожен суфікс починається з вертикальної риски. Уже використовуються:
- База: порожній суфікс, а також
g,h,hv,bc,gh,hh,bh - Ротвейлер:
rw,r - Доберман:
db,dm,dh - Лабрадор:
lb,lk - Мопс:
pg,pv - Малінуа:
ml,mm,mh - Кішки: кожен суфікс, що починається з
ct,cvабоcs(один набір на кожне забарвлення)
10. Мудл породи
Мудл це клієнтський файл. Ви додаєте таблицю в CD.DogMoodles, а база малює його, стежить за ним і прибирає його:
CD.DogMoodles[#CD.DogMoodles + 1] = {
id = "shadow",
breed = "pug",
nameKey = "IGUI_PD_Moodle_Shadow",
descKey = "IGUI_PD_Moodle_Shadow_desc",
icon = "CD_Moodle_Shadow",
fg = "CD_MoodShadowFG",
tintR = 0.92, tintG = 0.52, tintB = 0.68,
condition = function(player, dog)
if not dog or dog:isDead() then return 0 end
if CD.getBreed(dog) ~= "pug" then return 0 end
if CD.isDisloyal(dog) then return 0 end
return 1
end,
apply = function(player, dog, elapsedMin)
CD.relieveMood(player, CD.moodleReliefPerMin() * elapsedMin)
end,
}
condition повертає рівень, або 0 для "не показувати". apply отримує ігрові хвилини від попереднього виклику, тож множте свій ефект на elapsedMin.
Постачайте іконку в шести розмірах, від 32 до 128 пікселів. Інтерфейс обирає розмір за масштабуванням гравця, а відсутній розмір показується порожнім квадратом. Іконка з'являється у власній смузі мода, не серед ванільних мудлів.
11. Хуки
Два списки функцій, які база викликає на сервері. Додавайте до них із файлу shared. Кожен обробник виконується у власному захищеному виклику, тож помилка в одному доповненні не зупиняє базу.
CD.onHuntDelivered, з API 2, викликається одразу після того, як тварина кладе здобич біля ніг хазяїна:
CD.onHuntDelivered[#CD.onHuntDelivered + 1] = function(animal, owner)
-- server side, so this is the right place to write mod data
end
CD.onUpkeepStress, з API 3, викликається щоцикл утримання, після того як база додала стрес від незадоволених потреб і перед тим, як його записати. Поверніть число, яке додасться до підсумку:
CD.onUpkeepStress[#CD.onUpkeepStress + 1] = function(animal, needsStress)
return CD.pugHeatRatio(animal) * CD.PUG_HEAT_STRESS_MAX
end
Утримання перераховує й перезаписує значення щоцикл, тож усе, записане ззовні, губиться на наступному тіку. Використовуйте хук для стану, що йде від середовища, як спека, холод чи дощ.
У мультиплеєрі клієнт ніколи не пише mod data. Запис із боку клієнта перезаписує серверну копію на наступній синхронізації, а симптом виринає через хвилини й в іншому місці. Робіть це у серверному файлі або в одному з цих хуків.
Числа в панелі SERVER TUNING це звичайні константи CD.*, і аддон може присвоїти одну з них під час завантаження з shared або серверного файлу. Починаючи з API 9 панель бере значення, чинне на момент, коли сервер закінчив завантаження, як значення за замовчуванням для цієї ручки: воно тримається, доки адмін не впише своє, кнопка скидання повертає до нього, і панель показує його як значення за замовчуванням. На старішій базі панель при кожному запуску записувала назад заводське значення. Присвоюйте під час завантаження, а не з пізнішої події, інакше наступна зміна будь-якої ручки поверне заводське значення.
12. Ключі перекладу
Постачайте один IG_UI.json на теку мови, у media/lua/shared/Translate/<LANG>/. База постачає чотирнадцять: EN, PTBR, CH, CN, DE, ES, FR, IT, JP, KO, RU, TH, UA, VI.
Ключі, потрібні породі:
| Ключ | Де показується |
|---|---|
IGUI_PD_Breed_<key> |
назва породи, скрізь в інтерфейсі мода |
IGUI_PD_BreedDesc_<key> |
опис на картці тварини |
IGUI_Breed_<engineBreed> |
назва породи на власних екранах гри |
IGUI_AnimalType_<type> |
по одному на кожен із ваших трьох типів |
| ваші ключі виду | nounKey і youngKey, які ви передали в CD.registerSpecies |
| ваші ключі мудла | назва й опис |
{
"IGUI_Breed_pug": "Pug",
"IGUI_AnimalType_pugmale": "Pug (male)",
"IGUI_AnimalType_pugfemale": "Pug (female)",
"IGUI_AnimalType_pugpup": "Pug (puppy)",
"IGUI_PD_Breed_pug": "Pug",
"IGUI_PD_BreedDesc_pug": "The house dog: the first companion whose worth is not fighting."
}
Літеральний знак відсотка в перекладеному рядку валить екран, що його показує, бо форматувальник читає його як заповнювач. Файл мови з міткою порядку байтів не компілюється, а помилка називає інший файл, не зламаний.
13. Що запам'ятовує збереження
Три ваші ідентифікатори записуються у файли збереження й читаються звідти назавжди:
key, ідентифікатор породи, збережений на кожній вашій твариніtypePrefix, з якого утворюються типи тварин, що їх зберігає рушійsuffix, ключ сховища "вже розіграно", по одному на крок спавну
Жоден із трьох не можна перейменувати після релізу. Перейменування key осиротить кожну живу тварину тієї породи. Перейменування suffix змусить кожну будівлю в кожному наявному збереженні розігратися знову, і світ, у який грали місяцями, наповниться вашою твариною.
Новий суфікс це також спосіб, яким нова порода доходить до світів, що вже йдуть. CD: Cats дав чотирьом своїм новим забарвленням нові суфікси, тож вони почали з'являтися в наявних збереженнях, а первісна чорна кішка залишила свій старий суфікс і не розігрувалася знову.
Зняття доповнення зі збереження, де є живі тварини тієї породи, губить їх. Рушій відкидає тварину, типу якої більше не знає. База не падає, а зв'язок і вольєр деградують чисто. Напишіть про це на своїй сторінці у Workshop.
Якщо вашого доповнення немає, база відкочується до стандартної породи для всього, про що її питають, тож збереження, відкрите без вашого мода, читається.
14. Перед публікацією
- Захист стоїть у кожному файлі Lua й називає той API, яким ви справді користуєтеся.
- Мінімальна версія бази записана в описі, словами.
versionMinцього сказати не може. - Ви прочитали лог на першому завантаженні. Інакше
CD.registerBreedвідмовляє мовчки, а кожна відмова друкує іменований рядок. CD.defineDogPartsвикликано. Розбирайте одну зі своїх тварин і подивіться, що воно не падає.- Ваші
key,typePrefixі суфікси спавну унікальні, і міняти їх не доведеться. - Мала порода ставить
puppySize, і ви бачили цуценя, щоб це довести. - Якщо це інший вид,
CD.registerVoicesвикликано йvoicesзаповнено, включно зwhine, інакше воно гавкає і скиглить, як собака. - Іконка мудла існує в усіх шести розмірах.
- Кожна тека мови, яку ви постачаєте, має всі ключі. Відсутній ключ показує на екрані сирий ключ.
- Ви тестували в мультиплеєрі, не лише в одиночній грі. Позиція, власність і mod data поводяться там інакше.
- Ваша сторінка у Workshop попереджає про зняття доповнення зі збереження з живими тваринами.
15. Часті помилки
| Симптом | Що це |
|---|---|
| "Моя тварина ніколи не з'являється." | Майже завжди це охоронна перевірка: аддону потрібен API, якого немає у встановленої бази, тож він виходить одразу й нічого не реєструє. |
| "Вона з'являється, але застигла." | Набір анімацій не "raccoon", або в моделі бракує кліпів. |
| "Вона малюється чорною плямою, а лог засипає помилками." | У моделі понад 60 костей. |
| "Розбирання тушки валить гру." | Не викликано CD.defineDogParts. |
| "Щеня народжується розміром із дорослого." | Дрібна порода не задала puppySize. Значення абсолютне і застосовується знову на кожному проході, тож діапазон розмірів типу його не покриває. |
| "Дві мої породи постійно перетворюються одна в одну." | Вони мають спільний engineBreed. Префікс обирає меш і може бути спільним для родини. engineBreed обирає текстуру й відрізняє породи цієї родини, тож він має бути унікальним для кожної породи. |
| "Звук грає, але ігнорує повзунок гучності." | Для цього звуку не викликано CD.registerVoices. |
| "У одиночній грі працює, а на сервері поводиться дивно." | Щось пише ModData на клієнті. Перенесіть це на сервер. |
| "Вона побилася із зомбі, хоч я вимкнув бій." | База, на якій вона працює, старіша за API 5. Така база не знає поля й ігнорує його. Охоронна перевірка має блокувати на цій базі, а не завантажуватися з відсутнім полем. |
Щось у цьому посібнику неправильно або чогось бракує? Скажіть про це в Discord.