Companion Dogs/Modding
Vai al Manuale: FunzionalitàVai al Manuale: Razze
Entra nel Discord
Traduzione automatica, non revisionata. Questa pagina è stata tradotta a macchina dal portoghese e nessuno l'ha revisionata. Potrebbe contenere errori nei termini e nei numeri. In caso di dubbio, controlla la versione inglese o l'originale in portoghese. Leggi in inglese

Companion Dogs: Manuale di Modding

Come costruire il tuo add-on: una razza nuova, o un'intera specie nuova, come mod separata della Workshop che si aggancia a Companion Dogs.

Questo manuale è per chi scrive mod. Se vuoi solo giocare, leggi gli altri due manuali: Funzionalità per quello che fa il cane, e Razze per le razze.

Dà per scontato che tu sappia leggere il Lua e che tu abbia un modello riggato e animato per il tuo animale. La pipeline artistica non è trattata qui. La sezione 2 elenca cosa deve soddisfare il file del modello.

Tutto quello che questo manuale nomina è un contratto pubblico ed esiste per essere chiamato da fuori del mod. Quello che non è nominato qui è interno e può cambiare in qualsiasi versione senza preavviso.

1. Che cos'è un add-on

Un add-on è una normale mod della Workshop che dichiara require=CompanionDogs e chiama una funzione al caricamento. Aggiunge una razza (o più di una, se condividono un corpo) e niente altro.

La base possiede:

  • la macchina a stati dell'animazione, l'animation set e lo scheletro
  • il seguire, il pathing, il combattimento, la caccia, la pastorizia, la sentinella, i bisogni e il mantenimento
  • tutta l'interfaccia: la finestra del cane, il radiale, il menu contestuale, il canile, il segnaposto sulla mappa
  • addomesticamento, legame, riproduzione, cuccioli e meticci
  • le opzioni sandbox e la replica in multigiocatore

Il tuo add-on possiede:

  • i numeri che rendono il tuo animale diverso da un Caramelo
  • il suo modello del corpo, la sua texture, il suo ritratto e, se ce l'ha, l'icona del suo moodle
  • dove lo si trova nel mondo
  • il suo nome e la sua descrizione, in ogni lingua che pubblichi

Due colori che si comportano allo stesso modo restano due razze per la base (due nomi, due voci nel canile); li scrivi a partire da una sola tabella condivisa, come fa CD: Cats con i suoi cinque manti.

2. Cosa ti serve prima di iniziare

Il tuo animale ha bisogno di un .glb skinnato tutto suo sotto media/models_X/Skinned/, con la serie completa delle 21 clip di animazione Rac_*. Le clip non si ereditano tra file di modello: un modello con quindici clip ti dà un animale che si blocca la prima volta che gliene viene chiesta una delle altre sei. Il modello deve anche restare sotto i 60 ossi e avere il nodo di testa in identità, altrimenti il motore lancia un'eccezione a ogni frame e l'animale viene disegnato come una macchia nera. Altre due clip sono facoltative, Rac_WalkLimpFront e Rac_WalkLimpBack (cicli di camminata zoppicante); contano solo se imposti limpAnim = true nella definizione della razza.

Se non hai ancora un modello, puoi scrivere e provare tutto il resto puntando CD.applyDogModel su uno dei corpi della base. L'animale sembra un Caramelo, ma ogni sistema di questo manuale funziona.

Ti servono anche una texture per il corpo e un ritratto per la finestra del canile.

Decidi se il tuo animale è una razza o una specie prima di scrivere una riga di codice. Una razza è un cane con numeri diversi. Una specie è un animale a cui mancano del tutto alcuni dei mestieri. La scelta cambia quali campi scrivi; se ne occupa la sezione 6.

3. I file di un add-on

Questo è CD: Pug, l'add-on pubblicato più piccolo, con le parti che contano:

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/

E il 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 è la riga importante. Garantisce che tutto il Lua della base sia già girato prima del tuo, in ogni fase: prima shared, poi client, poi server. Senza di essa i tuoi file possono caricarsi per primi e ogni chiamata CD. è un indice nil.

versionMin non sa dire "serve Companion Dogs 0.6.8". Quel campo filtra solo la build del gioco, e un campo di mod.info per la versione di una dipendenza non esiste. Il minimo richiesto sulla base lo impone il guard della sezione seguente e lo annunci ai giocatori nella tua descrizione. Scrivilo lì, a parole, altrimenti l'unico sintomo che il giocatore vede è un animale che non compare mai.

4. Il guard di versione

Metti questo in cima a ogni file Lua del tuo add-on, con il numero che serve al tuo add-on:

local CD = CompanionDogs
if not (CD and CD.registerBreed and (CD.API_VERSION or 0) >= 3) then return end

Se la base manca o è troppo vecchia, il file esce alla seconda riga e l'add-on non fa niente: niente razza, niente spawn, niente moodle, nessuna riga di log.

Il guard è per file, e ogni file fa il guard su quello che usa. PugDefinitions.lua ha bisogno degli helper del modello, quindi fa il guard su quelli:

if not (CompanionDogs and CompanionDogs.applyDogModel and CompanionDogs.DOG_SOUNDS) then return end

Non creare tu la variabile globale. CompanionDogs = CompanionDogs or {} dentro un add-on trasforma una base assente in una tabella costruita a metà, e ogni guard a valle passa quando non dovrebbe.

5. Registrare la razza

Una chiamata, in un file 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 },
    },
})

La chiamata inserisce la razza, registra i suoi tre tipi di animale, ricostruisce l'ordine di riproduzione in modo che il tuo animale partecipi agli incroci, e registra i passi di spawn.

Una definizione sbagliata fa tornare nil alla chiamata invece di lanciare un errore, quindi il caricamento prosegue. Ogni rifiuto stampa nel log una riga con il suo nome.

Campi obbligatori

Campo Che cos'è
key l'identificatore della tua razza, unico tra tutte le mod. Non cambiarlo dopo l'uscita.
typePrefix prefisso dei tre tipi di animale: <prefix>pup, <prefix>female, <prefix>male. Sceglie la mesh del corpo.
nameKey chiave di traduzione del nome mostrato.
xpMult velocità di apprendimento per abilità: scent, combat, obedience, hunt, herding. 1.0 è il normale.
combatPower quanto picchia forte. Il Caramelo è a 0.20, una razza da combattimento sta sopra 1.0.
lethalityCurve { min, max }, come cresce il danno da Combattimento 0 a Combattimento 10.
geneRange intervallo alla nascita dei quattro geni: strength, aggressiveness, resistance, stress. Ognuno è { basso, alto } dentro 0 e 1.

Campi facoltativi

Campo Predefinito Che cosa fa
engineBreed la key nome della razza dato al motore. Sceglie la texture. Unico per razza.
descKey IGUI_PD_BreedDesc_<key> chiave della descrizione. Più razze possono condividere una chiave.
litter valore base { min, max } cuccioli per parto.
canKill true false significa che sfianca gli zombie ma non dà mai il colpo di grazia.
canKnockdown false true gli permette di buttare a terra uno zombie.
combatStressMult 1 quanto stress gli costa un combattimento.
panicThreshold valore base livello di stress al quale smette di combattere. panicImmune = true significa che non smette mai.
bagMult 1 moltiplicatore della capacità delle bisacce.
puppySize 0.6 scala visiva assoluta del cucciolo. Una razza piccola deve impostarla, altrimenti il cucciolo nasce della taglia di un adulto.
sentinelMult 1 moltiplica il raggio finale della sentinella.
barkNoiseMult 1 scala il raggio e il volume dell'abbaio d'allarme. Con l'opzione sandbox del rumore spenta, nessuna razza attira zombie abbaiando, qualunque sia il valore.
loyaltyDecayMult 1 moltiplica il calo giornaliero della lealtà. 0 significa che il legame non svanisce mai.
alertModeLocked false l'animale resta sempre in allerta piena: il padrone non può metterlo su discreto o silenzioso da nessuna parte.
huntFetchLevel 6 livello di Caccia dal quale riporta la preda al padrone.
huntDeliverTimeoutMin 5 minuti di gioco per cui continua a provare a consegnare prima di rinunciare.
distract spento { <kind> = { chance = 0..1, ... } }. La razza va dietro alle cose di sua iniziativa, senza bisogno di modalità né di livello.
idleAnimMs valore base durata della finestra dell'animazione di idle, in millisecondi. Impostalo quando le tue clip sono più corte di quelle del cane, altrimenti il ciclo riparte e taglia il gesto a metà.
restAnim true false tiene l'animale in piedi su Resta e Guardia invece di farlo sdraiare.
restPoses sdraiarsi una lista di pose di riposo da cui l'animale ne pesca una ogni volta che si sistema, con transizioni e variazioni di idle opzionali. Servono clip e nodi di animazione tuoi.
limpAnim false true fa zoppicare l'animale ferito mentre cammina. Impostalo solo se il tuo modello ha le due clip facoltative Rac_WalkLimpFront e Rac_WalkLimpBack (cicli di camminata da 1.0 s con lo stesso root motion di Rac_Walk). Senza le clip l'animale cammina fermo sul posto.
bandSkin spento { base, front, back, cutFront, cutBack }, cinque nomi di texture del corpo. Sulla zampa ferita l'animale porta la variante con il taglio mentre la ferita sanguina e quella fasciata mentre ha la fasciatura. Crea le quattro varianti con _dogrig/forge/_paw_band.py. Senza, l'animale si ferisce lo stesso, solo che non mostra nessun segno.
voices i suoni del cane { bark, growl, idle, wildbark, pet, whine, eat, drink }.
diet le liste del cane cosa l'animale non deve mangiare, cosa conta come carne per lui, e cosa mangia da solo da una mangiatoia.
maleChance 0.5 quota della razza che nasce maschio, da 0 a 1.
sterileMale false true tiene i maschi della razza fuori dalla riproduzione.
species "dog" la riproduzione è chiusa per specie. Sezione 6.
skills, canBreed, huntMaxPrey tutto acceso i blocchi strutturali. Sezione 6.

I numeri e i flag che aggiungi alla definizione si rileggono con CD.breedNumber(animal, field) e CD.breedFlag(animal, field), non con CD.getBreedDef(animal).field. Quelli reggono un campo mancante e continuano a funzionare quando la base cambia.

Istinto: distract

distract = {
    prey = { chance = 0.25 },
},

Una razza che lo dichiara ogni tanto nota qualcosa e ci va dietro, mollando quello che stava facendo. Lo fa sia da Resta sia da Segui. Finché dura, gli ordini tornano indietro rifiutati con lo stesso messaggio distracted che usa già il tiro di obbedienza. Vieni è l'unico comando che passa, e annulla la distrazione.

Ogni tipo accetta le stesse manopole facoltative. Quello che ometti usa i valori predefiniti della mod:

Campo Che cosa fa
chance 0..1, tirato solo quando il trigger del tipo ha trovato qualcosa. Scrivere 25 per "25%" viene rifiutato con una riga apposita nel log
radius caselle in cui cerca il trigger, quando il tipo cerca qualcosa
durationMin minuti di gioco che dura la finestra prima di scadere da sola
cooldownMin minuti di gioco prima che lo stesso animale possa distrarsi di nuovo

Il tipo prey

L'unico tipo che la mod base porta con sé. L'animale va dietro agli animali selvatici, e classes sceglie quali contano:

distract = {
    prey = { chance = 0.25, radius = 6, classes = { tiny = true, small = true } },
},

Due limiti valgono comunque, qualunque cosa tu dichiari:

  • Non va mai oltre il tuo huntMaxPrey: una razza limitata a small non insegue i cervi nemmeno se dichiara large.
  • Il filtro di livello salta solo per tiny. Un animale appena addomesticato con Caccia 0 prende già i roditori. Con small dichiarato, la razza insegue il coniglio a qualsiasi livello, ma per ucciderlo serve lo stesso livello di Caccia che serve al Labrador.

L'opzione sandbox della caccia agisce solo sull'uccisione: l'animale insegue lo stesso, ma la preda scappa.

Un'uccisione fatta così passa dal recupero normale, quindi è il huntFetchLevel della tua razza a decidere se il corpo torna al padrone. A 0 l'animale consegna dal primo giorno, ed è così che un gatto ti porta un ratto morto. La consegna sopravvive alla finestra di distrazione: appena la preda è morta l'animale torna a prendere ordini, e il viaggio di ritorno gira sul suo huntDeliverTimeoutMin.

Scrivere un tipo tuo

distract è un dispatcher. Registra il tuo:

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
})

Poi dichiaralo sulla razza come qualsiasi altro tipo: distract = { butterfly = { chance = 0.10 } }.

gate gira prima del budget di scansione globale della mod. find gira dopo il budget e fa la ricerca costosa. drive torna true finché sta ancora guidando l'animale e false quando ha finito, il che chiude la finestra. Il tuo handler gira dentro un pcall suo. Se lancia un errore, la mod scrive una riga apposita nel log, toglie quel tipo dal registro per la sessione e lascia funzionare il resto del compagno.

Nomi riservati per i tipi futuri della base: drink, eat, play. Oggi puoi registrare un tipo con uno di quei nomi, ma la base si riprende il nome quando pubblica il suo.

Pose di riposo

Un animale che riposa si sdraia. restPoses sostituisce quell'unica posa con una lista da cui l'animale pesca ogni volta che si sistema:

restPoses = {
    "cdRest",                                          -- the base lie-down
    { var = "cdSit", enter = "cdSitIn", exit = "cdSitOut",
      variation = { "cdSitGroom", "cdSitGroom2" } },
},

Una voce è il nome di una variabile di animazione oppure una tabella. var è il booleano che resta acceso per tutta la pausa. enter ed exit sono impulsi singoli suonati quando l'animale si sistema e quando si rialza. variation è una lista di impulsi singoli che suona ogni tanto mentre tiene la posa, con lo stesso orologio della variazione di idle. Solo var è obbligatorio.

L'estrazione è uniforme e avviene a ogni ingresso nel riposo, quindi due voci valgono 50% ciascuna e l'animale può pescare la stessa due volte di fila. Non è un'alternanza.

Ogni impulso dura quanto dice idleAnimMs per quella variabile, quindi passa la forma a tabella e dai una durata a ogni variabile, comprese quelle di idle che avevi già. Metti la durata reale della clip.

idleAnimMs = { cdIdle2 = 3800, cdIdle3 = 3800, cdSitIn = 1375, cdSitOut = 1417,
               cdSitGroom = 19833, cdSitGroom2 = 25167 },

Le clip e i nodi sono tuoi, non della base. Ogni variabile ha bisogno di un file di nodo nel tuo add-on sotto media/AnimSets/raccoon/idle/, e la clip a cui punta il suo m_AnimName deve esistere nel tuo modello. Un nodo che punta a una clip che il modello non ha fallisce in silenzio e l'animale resta in piedi nell'idle della base. Dai al nodo della posa una m_ConditionPriority di 10, come quelli della base, e un numero più alto a tutto ciò che deve suonare sopra: 11 per una variazione, 12 per le due transizioni. Un nodo suona la sua clip in loop se non dici altrimenti, quindi ogni nodo pensato per suonare una volta sola, sia le transizioni sia ogni variazione, ha bisogno di <m_Looped>false</m_Looped>. Senza, la clip riparte appena finisce e l'animale scatta visibilmente indietro e la rifà, perché la variabile si spegne solo al tick successivo del server. Tienili tutti solo in idle/. Un nodo di posa in pathfind/ suona una clip senza root motion e congela l'animale dove sta.

La voce

Senza voices ogni razza usa i suoni del cane, quindi un gatto abbaierebbe. Registra prima i tuoi suoni:

CD.registerVoices({
    CDCatMeow = "bark", CDCatGrowl = "bark", CDCatHiss = "bark", CDCatPurr = "bark",
    CDCatPet = "fx", CDCatMeowAmbient = "ambient",
})

Poi punta la razza su di essi:

voices = { bark = "CDCatMeow", growl = "CDCatGrowl", idle = "CDCatPurr",
           wildbark = "CDCatMeowAmbient", pet = "CDCatPet", whine = "CDCatHiss" },

Senza CD.registerVoices i suoni si sentono lo stesso, ma ignorano il cursore di categoria del giocatore e il volume degli effetti del gioco, e niente nel log lo segnala. Una chiave che ometti da voices ricade sul suono del cane.

whine è quello che l'animale dice quando si ferisce o si ammala. eat e drink sono il foley del mangiare e del bere. Quelli del cane sono loop che la base ferma quando l'animale ha finito; se anche i tuoi sono loop, mettili in CD.SOUND_LOOPED e la base li ferma anche quando chi ascolta esce dalla portata. Una base più vecchia della API 11 ignora le tre chiavi e suona come un cane.

Il terzo argomento è la distanza a cui si sente ogni suono, in caselle. Deve corrispondere al distanceMax che hai scritto nel tuo script dei suoni:

CD.registerVoices(map, nil, { CDCatMeow = 22, CDCatGrowl = 10 })

Su un server la base manda un suono solo ai giocatori dentro quella distanza. Ometti la distanza e la voce prende una distanza generica per categoria: un soffio che arriva a dieci caselle esce per tutti quelli entro trenta. Oltre il distanceMax il rolloff inverso di FMOD smette di attenuare invece di andare in silenzio, quindi un .ogg lasciato libero continua a sentirsi a distanceMin / distanceMax del suo volume a qualsiasi distanza. Per lo stesso motivo tieni piccolo il distanceMin sulle tue voci forti.

La dieta

Senza diet ogni razza mangia come un cane: lo stesso cibo che la avvelena, lo stesso cibo che conta come carne, e lo stesso cibo che bruca da una mangiatoia. Il campo sostituisce quelle tre liste solo per la tua razza.

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 },
},

Le due forme esistono perché le categorie del gioco arrivano fino a un certo punto. Cheese è un FoodType vero, quindi una sola chiave copre tutti i formaggi del gioco e di qualsiasi mod di cibo che riusi la categoria. Il tonno no: la scatoletta aperta è FoodType = Fish, insieme a tutti gli altri pesci, e quella chiusa non dichiara nessun tipo di cibo. badParts e proteinParts sono il modo di arrivare a quelli.

Ogni campo è una tabella di <key> = true (aggiungi) oppure <key> = false (togli quello che c'era nella base). Un valore che non è true né false viene scartato con una riga apposita nel log.

Campo Che cosa sono le sue chiavi
bad il FoodType del cibo che fa star male l'animale, gli costa lealtà e prende una voce rossa nel menu del cibo
badParts un pezzo del tipo dell'oggetto, in minuscolo, cercato in qualsiasi punto di esso
protein il FoodType che paga il debito di carne dell'animale, quello che guarda il moodle Debole
proteinParts un pezzo del tipo dell'oggetto, con la stessa ricerca di badParts
nonProtein un FoodType che sai non essere carne. Serve solo a zittire la riga di log che la base scrive per un tipo di cibo che non ha mai visto
trough il FoodType e l'AnimalFeedType che l'animale mangia da solo da una mangiatoia
troughItems il tipo completo dell'oggetto, per un oggetto tenuto nella mangiatoia che non ha un tipo di mangime suo

replace = true parte da una lista vuota invece che da quella del cane. Usalo quando il tuo animale non condivide quasi niente con un cane; un gatto di solito si scrive meglio come una manciata di aggiunte e di rimozioni.

Le tre liste seguono l'animale, quindi un cane e un gatto fermi alla stessa mangiatoia ci mangiano cose diverse. La ciotola della mod è l'eccezione: conserva punti di cibo, non l'oggetto che l'ha riempita, quindi riempirla passa dalla lista base per tutti. In una ciotola non si può mettere niente di velenoso, comunque.

Il sesso alla nascita

Ogni animale viene estratto maschio o femmina quando compare, metà e metà. maleChance è la quota che nasce maschio, da 0 a 1, e sterileMale tiene i maschi della razza fuori dalla riproduzione.

maleChance = 0.00033,
sterileMale = true,

Quella coppia è la gatta calico. Il mosaico di arancione e nero sta sul cromosoma X, quindi una calico è quasi sempre femmina, e il maschio che ogni tanto esce è XXY, circa uno su tremila, e non genera cucciolate. Un maschio di una razza con sterileMale non viene mai scelto come partner, e l'opzione per accoppiarlo sparisce dal suo menu contestuale. Le femmine si riproducono normalmente.

Un maschio di una razza così porta la marca "Sterile" ovunque il gioco mostri il suo sesso: la scheda, il menu contestuale, la scansione dei randagi e il canile, così il giocatore sa perché non si riproduce.

Entrambi i campi si leggono dalla definizione della razza, quindi non viene scritto nulla sull'animale e un salvataggio esistente non ha bisogno di migrazione. La stessa estrazione decide il sesso di un cucciolo alla nascita, quindi una razza che nel mondo è femmina resta femmina anche nelle cucciolate.

6. Spegnere un intero sistema

Le cinque abilità sono i cinque sistemi:

Abilità Il sistema che è
scent la sentinella: accorgersi degli zombie e avvisarti
combat il combattimento, la modalità Guardia e la difesa personale
obedience l'albero dei trucchi
hunt la caccia, più il bonus di raccolta che dà al padrone
herding badare al bestiame in un recinto

Quindi una specie dichiara quello che non ha:

skills = { combat = false, herding = false },
canBreed = false,
huntMaxPrey = "tiny",

Assente o true significa che l'animale ha quell'abilità.

Spegnere un'abilità le toglie anche l'esperienza e fa sparire dall'interfaccia la sua barra e i suoi comandi.

canBreed = false sta fuori dalla tabella perché la riproduzione non ha né barra né esperienza. L'animale non concepisce, non viene mai scelto come partner, e non si riproduce nemmeno con i suoi simili.

La specie

Un add-on che aggiunge un altro animale, e non un altro cane, lo dichiara:

CD.registerSpecies({ key = "cat",
                     nounKey  = "IGUI_PD_SpeciesNoun_cat",
                     youngKey = "IGUI_PD_Young_cat" })

CD.registerBreed({ ..., species = "cat" })

Il campo è assente in tutte le razze pubblicate finora, e assente significa "dog".

La riproduzione è chiusa per specie: una coppia concepisce solo se le due parti sono della stessa specie, nel comando, nel tiro orario passivo e nello strumento di debug. E l'interfaccia smette di chiamare cane il tuo animale: nounKey è il sostantivo nudo che riempie frasi come "Questo %1 appartiene già a un altro sopravvissuto", mentre youngKey è l'etichetta che il piccolo della tua specie porta nella finestra, nel menu contestuale e nel canile, dove un cane dice Cucciolo.

Quei due sostantivi coprono solo le frasi costruite attorno a un %1. Ogni altra frase con la parola cane scritta dentro ("Ispeziona il cane", "Il tuo cane ha fame", "Il cane abbaia di proposito") prende una chiave per specie dall'API 7 in poi: pubblica IGUI_PD_ScanStray_cat nei tuoi file di traduzione e la base la mostra al posto di IGUI_PD_ScanStray ogni volta che l'animale sullo schermo è un gatto. Una chiave che non hai pubblicato ricade sul testo del cane. Le chiavi che accettano il suffisso: ScanStray, ScanTitle, PauseGrowthTip, BadFood, AlertFullDesc, AlertQuietDesc, AlertLockedTip, HuntModeDesc, tutte le Trick*Desc, TrickDistractDone, TrickNeedsBag, TrickGotoPick, TrickSniffNoItem, TrickFetchNoItem, AlertGroup, DogLost, tutte le Refused_*, KennelStatusNoData e le Moodle_*_desc dei moodle di cura (Malato, Debole, Fame, Sete, Riposo, Lutto). Le frasi senza un animale sullo schermo (la scheda del registro, "Fai prima amicizia con un animale") nella base sono già neutre rispetto alla specie. Il gatto le pubblica tutte in quattordici lingue; copia il suo IG_UI.json come modello.

Il group dell'animale lo legge solo il menu di spawn del debug e il visualizzatore di clip, attraverso IGUI_Animal_Group_<group>. Imposta t.group = "cat" dopo l'helper del comportamento e pubblica quella chiave, altrimenti la tua specie compare lì sotto il nome del cane.

Dichiarare species senza registrarla non è fatale: il tuo animale continua a rifiutarsi di riprodursi fuori dalla sua specie, perché il filtro confronta la stringa grezza. Ricadono sui sostantivi del cane solo quei due, e il log lo dice con il loro nome.

Su una base più vecchia dell'API 6 il campo viene ignorato senza una riga di log, e il tuo animale si riproduce come un cane. Se non deve mai incrociarsi con uno, fai il guard sull'API 6, oppure tieni canBreed = false finché la base è più vecchia, che è quello che fa il gatto.

huntMaxPrey limita la taglia della preda dentro una caccia che comunque esiste; non sostituisce hunt = false. Accetta "tiny" (topi, ratti, scoiattoli), "small" (conigli, procioni) oppure "large" (cervi, il predefinito, nessun limite). Il tuo animale non insegue e non punta prede sopra il limite. Un valore fuori da quella scala ricade su "large" e stampa una riga apposita nel log.

Un'opzione bloccata sparisce dal radiale, dal menu contestuale e dalla finestra, e il server la rifiuta senza messaggio. Nessuno di questi campi aggiunge una chiave di traduzione, quindi spiega i limiti nella descrizione della tua razza.

7. Le definizioni dell'animale

Il motore lega la mesh al tipo di animale e non alla razza, quindi la tua razza ha bisogno di tre tipi suoi. La strada più corta è copiare il file di un add-on esistente e rinominarlo.

I tre pezzi, in un solo file shared sotto 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

Gli helper che la base ti dà:

Helper Cosa compila
CD.applyCompanionModel(t, "Body_Model") modello del corpo, scheletro, texture da macellazione e animation set
CD.applyCompanionBehaviour(t) tutti i flag di comportamento del motore che servono a un compagno
CD.applyCompanionAvatar(t) la camera del ritratto per la finestra dell'animale
CD.COMPANION_SOUNDS la tabella condivisa dei passi e del foley
CD.defineGenome("species") il genoma di una specie nuova, con la lista di geni standard (API 7)
CD.STRAY_NO_BLEEDOUT il valore che impedisce a un randagio spaventato di morire dissanguato
CD.defineCompanionParts(typePrefix, engineBreed, meat) le parti da macellazione di tutti e tre gli stadi. meat è facoltativo: { item, minNb, maxNb, pupMinNb, pupMaxNb }, e senza di esso l'animale rende la carne di cane della base. Il motore moltiplica sia il numero sia la fame di ogni pezzo per la taglia della carcassa, quindi una specie con una taglia grande (il gatto usa da 2.5 a 3.5 come scala visiva) ha bisogno di un oggetto suo con una fame base più bassa e un numero basso

I nomi neutri rispetto alla specie sono arrivati con l'API 7. Quelli più vecchi, CD.applyDogModel, CD.applyDogBehaviour, CD.applyDogAvatar, CD.DOG_SOUNDS, CD.defineDogParts e CD.DogMoodles, sono le stesse funzioni e le stesse tabelle sotto il loro primo nome. Un add-on che vuole caricarsi su una base più vecchia della 0.7.3 fa il guard sui nomi vecchi e prende il nuovo quando c'è: local applyModel = CD.applyCompanionModel or CD.applyDogModel.

L'animation set deve restare "raccoon". Un animation set forkato non carica la sua macchina a stati, e l'animale resta fermo sul posto.

Riusa il genoma. Una razza di cane punta alla lista della base: AnimalDefinitions.genome["dog"].genes. Una specie nuova chiama CD.defineGenome("cat") una volta e dà a ogni stadio la tabella che le torna. Il motore legge solo la lista genes di ogni stadio, e la chiave del genoma è una convenzione, quindi la tua specie ha una voce sua senza una lista di geni sua.

Non dichiarare mate. Con quel campo il motore fa il suo accoppiamento nativo dentro qualsiasi zona per animali, il che ignora il blocco di riproduzione della mod e le sue opzioni sandbox, e produce cuccioli senza legame. La base pulisce il campo all'avvio, ma non contarci.

Le parti sono obbligatorie. Tre righe in un file loro:

if CompanionDogs and CompanionDogs.defineDogParts then
    CompanionDogs.defineDogParts("pug", "pug")
end

Senza di esse, macellare il tuo animale manda in crash il codice vanilla, perché indicizza la definizione delle parti per tipo e razza e trova un nil.

Lo zoom del ritratto in CD.applyDogAvatar è tarato su un cane, e lo zoom dell'avatar è ingrandimento: più piccolo è l'animale, più grande è il numero. Dividi lo zoom per il rapporto di taglia rispetto a un cane e moltiplica gli offset per lo stesso rapporto, altrimenti il tuo animale sta minuscolo in fondo al suo stesso ritratto.

8. Lo script del modello

Un file sotto media/scripts/ dice al gioco che il tuo modello esiste:

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 tiene usabili le clip dentro il tuo file di modello. Senza, il modello si carica e non si anima mai.

L'attachment head è dove stanno i cappelli e simili. Se vuoi che le bisacce vadano bene al tuo animale, aggiungi saddlebags_l, saddlebags_r e saddlebags_c allo stesso modo, sull'osso della colonna. Quei numeri li trovi guardando l'animale in gioco e spostandoli a occhio. I valori misurati sul modello mettono la bisaccia nel centro geometrico della mesh, che non è dove sta bene sul corpo.

9. Dove compare l'animale

I randagi vengono tirati per edificio, per chunk, la prima volta che quel chunk si carica. I tuoi tiri si descrivono come una lista di passi, o dentro la definizione della razza come spawns, o con 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 },
},
Chiave Che cos'è
id nome del passo, per i log tuoi
class la classe di edificio su cui tira
chance una funzione che torna una percentuale, così può leggere il moltiplicatore sandbox al momento del tiro.
suffix chiave dell'archivio persistente. Unica tra tutte le mod, e mai cambiata.
breed la chiave della razza da far comparire
gate funzione facoltativa che torna un booleano, controllata prima del tiro. L'Husky usa CD.isWinter.
indoor probabilità percentuale di nascere dentro l'edificio invece che in strada. Predefinito 35.

Moltiplica la tua probabilità per CD.dogSpawnMultiplier(), oppure dividi CD.strayChancePerHouse() per una rarità tua. Tutte e due tengono in piedi l'impostazione sandbox del giocatore.

indoor è una preferenza: un edificio senza una casella interna libera in quel chunk ripiega su una casella fuori.

Le classi di edificio

La base ne registra quattro:

Classe Cosa prende
house edifici residenziali che non sono negozi
police stazioni di polizia e simili
petvet negozi di animali e cliniche veterinarie
farm edifici agricoli, ed è ammessa fuori città

Se nessuna ti va bene, registra la tua:

CD.registerBuildingClass("mineshaft", function(def) return def:isX() end,
                         { skipUrbanGate = true })

La funzione di confronto riceve la definizione dell'edificio e gira dentro una chiamata protetta, quindi un errore lì dentro non blocca il caricamento del chunk. skipUrbanGate permette alla classe di tirare in un chunk che non è urbano, cosa che serve a una fattoria o a una capanna nel bosco. Non impostare exclusive. Le classi della base sono esclusive tra loro, e una classe di add-on prende sopra di esse.

I suffissi già presi

Il suffisso fa parte della chiave sotto cui viene salvato "questo edificio è già stato tirato". Due mod che usano lo stesso suffisso si corrompono i tiri a vicenda.

Ogni suffisso comincia con una barra verticale. Questi sono già in uso:

  • Base: il suffisso vuoto, e g, h, hv, bc, gh, hh, bh
  • Rottweiler: rw, r
  • Doberman: db, dm, dh
  • Labrador: lb, lk
  • Pug: pg, pv
  • Malinois: ml, mm, mh
  • Cats: ogni suffisso che comincia con ct, cv o cs (una serie per manto)

10. Il moodle della razza

Un moodle è un file client. Aggiungi una tabella in coda a CD.DogMoodles, e la base lo disegna, lo segue e lo ripulisce:

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 torna il livello, oppure 0 per "non mostrare". apply riceve i minuti di gioco passati dall'ultima chiamata, quindi moltiplica il tuo effetto per elapsedMin.

Pubblica l'icona in sei misure, da 32 a 128 pixel. L'interfaccia sceglie la misura in base alla scala del giocatore, e una misura che manca si vede come un quadrato vuoto. L'icona compare nella striscia della mod, non tra i moodle vanilla.

11. Gli hook

Due liste di funzioni che la base chiama sul server. Aggiungici le tue da un file shared. Ogni handler gira dentro una chiamata protetta sua, quindi un errore in un add-on non ferma la base.

CD.onHuntDelivered, dall'API 2, viene chiamato subito dopo che l'animale ha lasciato una preda ai piedi del padrone:

CD.onHuntDelivered[#CD.onHuntDelivered + 1] = function(animal, owner)
    -- server side, so this is the right place to write mod data
end

CD.onUpkeepStress, dall'API 3, viene chiamato a ogni ciclo di mantenimento, dopo che la base ha sommato lo stress dei bisogni non soddisfatti e prima che venga scritto. Torna un numero, che viene sommato al totale:

CD.onUpkeepStress[#CD.onUpkeepStress + 1] = function(animal, needsStress)
    return CD.pugHeatRatio(animal) * CD.PUG_HEAT_STRESS_MAX
end

Il mantenimento ricalcola e sovrascrive il valore a ogni ciclo, quindi tutto quello che viene scritto da fuori si perde al tick successivo. Usa l'hook per una condizione che viene dall'ambiente, come il caldo, il freddo o la pioggia.

In multigiocatore il client non scrive mai i mod data. Una scrittura lato client sovrascrive la copia del server alla sincronizzazione successiva, e il sintomo salta fuori minuti dopo da tutt'altra parte. Fai il lavoro in un file server, o dentro uno di questi hook.

I numeri del pannello SERVER TUNING sono normali costanti CD.*, e un add-on può assegnarne una da un file shared o server al caricamento. Dall'API 9 il pannello prende il valore in vigore quando il server finisce di caricare come predefinito di quella manopola: resta finché un admin non ci scrive sopra, il pulsante di reset torna a lui, e il pannello lo mostra come predefinito. Su una base più vecchia il pannello riscriveva il valore di fabbrica a ogni avvio. Assegna al caricamento, non da un evento successivo, altrimenti la prossima modifica a qualsiasi manopola rimette il valore di fabbrica.

12. Le chiavi di traduzione

Pubblica un IG_UI.json per cartella di lingua, sotto media/lua/shared/Translate/<LANG>/. La base ne pubblica quattordici: EN, PTBR, CH, CN, DE, ES, FR, IT, JP, KO, RU, TH, UA, VI.

Le chiavi che servono a una razza:

Chiave Dove si vede
IGUI_PD_Breed_<key> il nome della razza, ovunque nell'interfaccia della mod
IGUI_PD_BreedDesc_<key> la descrizione sulla scheda dell'animale
IGUI_Breed_<engineBreed> il nome della razza nelle schermate del gioco
IGUI_AnimalType_<type> una per ognuno dei tuoi tre tipi
le chiavi della tua specie il nounKey e lo youngKey che hai passato a CD.registerSpecies
le chiavi del tuo moodle il nome e la descrizione
{
    "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."
}

Un segno di percentuale letterale dentro una stringa tradotta manda in crash la schermata che la mostra, perché il formattatore lo legge come un segnaposto. Un file di lingua con un byte order mark non compila, e l'errore nomina un file diverso da quello rotto.

13. Cosa ricorda il salvataggio

Tre dei tuoi identificatori vengono scritti nei file di salvataggio e riletti per sempre:

  • key, l'identificatore di razza salvato su ogni animale tuo
  • typePrefix, che forma i tipi di animale che il motore salva
  • suffix, la chiave dell'archivio dei "già tirati", uno per passo di spawn

Nessuno dei tre si può rinominare dopo l'uscita. Rinominare una key lascia orfano ogni animale vivo di quella razza. Rinominare un suffix fa ritirare ogni edificio di ogni salvataggio esistente, e un mondo giocato per mesi si riempie del tuo animale.

Un suffisso nuovo è anche il modo in cui una razza nuova arriva nei mondi già avviati. CD: Cats ha dato ai suoi quattro manti nuovi dei suffissi nuovi, così hanno iniziato a comparire nei salvataggi esistenti, mentre il gatto nero originale ha tenuto il suo vecchio suffisso e non ha tirato di nuovo.

Togliere un add-on da un salvataggio che ha animali vivi di quella razza li perde. Il motore scarta un animale di cui non conosce più il tipo. La base non va in crash, e il legame e il canile si degradano in modo pulito. Dillo sulla tua pagina della Workshop.

Se il tuo add-on manca, la base ricade sulla razza predefinita per qualsiasi cosa le venga chiesta, quindi un salvataggio aperto senza la tua mod si può leggere.

14. Prima di pubblicare

  • Il guard c'è su ogni file Lua, e nomina l'API che usi davvero.
  • Il minimo di versione della base è scritto a parole nella tua descrizione. versionMin non lo sa dire.
  • Hai letto il log al primo caricamento. Altrimenti CD.registerBreed rifiuta in silenzio, e ogni rifiuto stampa una riga con il suo nome.
  • CD.defineDogParts viene chiamata. Macella uno dei tuoi animali e verifica che non vada in crash.
  • La tua key, il tuo typePrefix e i tuoi suffissi di spawn sono unici, e non avrai bisogno di cambiarli.
  • Una razza piccola imposta puppySize, e hai visto un cucciolo per verificarlo.
  • Se è un'altra specie, CD.registerVoices viene chiamata e voices è compilato, con whine dentro, altrimenti abbaia e guaisce come un cane.
  • L'icona del moodle esiste in tutte e sei le misure.
  • Ogni cartella di lingua che pubblichi ha tutte le chiavi. Una chiave che manca mostra a schermo la chiave grezza.
  • Hai provato in multigiocatore, non solo in giocatore singolo. Posizione, proprietà e mod data lì si comportano diversamente.
  • La tua pagina della Workshop avvisa di non togliere l'add-on da un salvataggio con animali vivi.

15. Errori comuni

Sintomo Che cos'è
"Il mio animale non compare mai." Quasi sempre è il guard: l'add-on chiede un'API che la base installata non ha, quindi esce subito e non registra niente.
"Compare, ma è bloccato." L'animation set non è "raccoon", oppure al modello mancano delle clip.
"Si vede come una macchia nera e il log si riempie." Il modello ha più di 60 ossi.
"Macellarlo manda in crash." CD.defineDogParts non è stata chiamata.
"Il cucciolo nasce della taglia di un adulto." Una razza piccola non ha impostato puppySize. Il valore è assoluto e viene riapplicato a ogni passata, quindi l'intervallo di taglia del tipo non lo copre.
"Due delle mie razze si trasformano l'una nell'altra." Condividono un engineBreed. Il prefisso sceglie la mesh e può essere condiviso da una famiglia. engineBreed sceglie la texture e distingue le razze di quella famiglia, quindi deve essere unico per razza.
"Il suono si sente ma ignora il cursore del volume." CD.registerVoices non è stata chiamata per quel suono.
"In giocatore singolo funziona e su un server fa i capricci." Qualcosa scrive i mod data sul client. Spostalo sul server.
"Ha combattuto contro uno zombie anche se avevo spento il combattimento." La base su cui gira è più vecchia dell'API 5. Quella base non conosce il campo e lo ignora. Il guard deve bloccare su questa base invece di caricarsi con il campo mancante.

C'è qualcosa di sbagliato o che manca in questo manuale? Diccelo su Discord.