Companion Dogs/Modding
Zum Funktionen-HandbuchZum Rassen-Handbuch
Discord beitreten

Companion Dogs: Modding-Handbuch

Wie du dein eigenes Addon baust: eine neue Rasse oder eine ganze neue Art, als eigener Workshop-Mod, der sich in Companion Dogs einklinkt.

Dieses Handbuch ist für Leute, die Mods schreiben. Wenn du nur spielen willst, lies die anderen beiden Handbücher: Funktionen für das, was der Hund tut, und Rassen für die Rassen.

Es setzt voraus, dass du Lua lesen kannst und ein geriggtes, animiertes Modell für dein Tier hast. Die Art-Pipeline steht hier nicht drin. Abschnitt 2 listet auf, was die Modelldatei erfüllen muss.

Alles, was dieses Handbuch benennt, ist ein öffentlicher Vertrag und dafür gedacht, von außerhalb des Mods aufgerufen zu werden. Was hier nicht benannt ist, ist intern und kann sich in jeder Version ohne Ankündigung ändern.

1. Was ein Addon ist

Ein Addon ist ein normaler Workshop-Mod, der require=CompanionDogs deklariert und beim Laden eine Funktion aufruft. Es fügt eine Rasse hinzu (oder mehrere, wenn sie sich einen Körper teilen), und sonst nichts.

Der Basis-Mod besitzt:

  • die Animations-Zustandsmaschine, das Animationsset und das Skelett
  • Folgen, Pathing, Kampf, Jagd, Hüten, die Wache, Bedürfnisse und den Upkeep
  • die komplette Oberfläche: das Hundefenster, das Radialmenü, das Kontextmenü, den Zwinger, die Kartenmarkierung
  • Zähmen, Bindung, Zucht, Welpen und Mischlinge
  • die Sandbox-Optionen und die Multiplayer-Replikation

Dein Addon besitzt:

  • die Zahlen, die dein Tier von einem Caramelo unterscheiden
  • sein Körpermodell, seine Textur, sein Porträt und, falls vorhanden, sein Moodle-Symbol
  • wo es in der Welt zu finden ist
  • seinen Namen und seine Beschreibung, in jeder Sprache, die du mitlieferst

Zwei Farben, die sich gleich verhalten, sind für den Basis-Mod trotzdem zwei Rassen (zwei Namen, zwei Einträge im Zwinger); du schreibst sie aus einer gemeinsamen Tabelle, so wie CD: Cats es mit seinen fünf Fellfarben macht.

2. Was du brauchst, bevor du anfängst

Dein Tier braucht seine eigene geskinnte .glb unter media/models_X/Skinned/, mit dem kompletten Satz der 21 Rac_*-Animationsclips. Clips werden nicht zwischen Modelldateien vererbt: ein Modell mit fünfzehn Clips gibt dir ein Tier, das beim ersten Aufruf eines der anderen sechs einfriert. Das Modell muss außerdem unter 60 Knochen bleiben und seinen obersten Knoten auf Identität haben, sonst wirft die Engine einmal pro Frame und das Tier wird als schwarzer Fleck gerendert. Zwei weitere Clips sind optional, Rac_WalkLimpFront und Rac_WalkLimpBack (Laufzyklen mit Humpeln); sie zählen nur, wenn du in der Rassendefinition limpAnim = true setzt.

Wenn du noch kein Modell hast, kannst du alles andere schreiben und testen, indem du CD.applyDogModel auf einen der Basis-Körper zeigen lässt. Das Tier sieht aus wie ein Caramelo, aber jedes System aus diesem Handbuch läuft.

Du brauchst außerdem eine Textur für den Körper und ein Porträt für das Zwingerfenster.

Entscheide vor der ersten Zeile Code, ob dein Tier eine Rasse oder eine Art ist. Eine Rasse ist ein Hund mit anderen Zahlen. Eine Art ist ein Tier, dem manche Aufgaben komplett fehlen. Die Entscheidung ändert, welche Felder du schreibst; Abschnitt 6 behandelt das.

3. Die Dateien eines Addons

Das hier ist CD: Pug, das kleinste veröffentlichte Addon, mit den Teilen, auf die es ankommt:

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/

Und die 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 ist die wichtige Zeile. Sie garantiert, dass das gesamte Lua des Basis-Mods vor deinem gelaufen ist, in jeder Phase: erst shared, dann client, dann server. Ohne sie können deine Dateien zuerst laden und jeder CD.-Aufruf ist ein Nil-Index.

versionMin kann nicht ausdrücken "braucht Companion Dogs 0.6.8". Dieses Feld regelt nur den Spiel-Build, und für eine Abhängigkeitsversion gibt es kein Feld in der mod.info. Die Untergrenze für den Basis-Mod setzt die Prüfung aus dem nächsten Abschnitt durch, und deine Beschreibung sagt sie den Spielern an. Schreib sie dort hin, in Worten, sonst ist das einzige Symptom für einen Spieler ein Tier, das nie auftaucht.

4. Die Versionsprüfung

Setz das hier an den Anfang jeder Lua-Datei deines Addons, mit der Zahl, die dein Addon braucht:

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

Fehlt der Basis-Mod oder ist er zu alt, kehrt die Datei in Zeile zwei zurück und das Addon tut nichts: keine Rasse, kein Spawn, kein Moodle, keine Logzeile.

Die Prüfung gilt pro Datei, und jede Datei prüft auf das, was sie benutzt. PugDefinitions.lua braucht die Modell-Helfer, also prüft sie auf die:

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

Leg das Global nicht selbst an. CompanionDogs = CompanionDogs or {} in einem Addon macht aus einem fehlenden Basis-Mod eine halb gebaute Tabelle, und jede Prüfung weiter unten geht durch, obwohl sie es nicht sollte.

5. Die Rasse registrieren

Ein Aufruf, in einer shared-Datei:

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

Der Aufruf trägt die Rasse ein, registriert ihre drei Tiertypen, baut die Zuchtreihenfolge neu auf, damit dein Tier an der Kreuzung teilnimmt, und registriert die Spawn-Schritte.

Eine fehlerhafte Definition lässt den Aufruf nil zurückgeben, statt zu werfen, also läuft das Laden weiter. Jede Ablehnung schreibt eine benannte Zeile ins Log.

Pflichtfelder

Feld Was es ist
key die Kennung deiner Rasse, eindeutig über alle Mods hinweg. Nach der Veröffentlichung nicht mehr ändern.
typePrefix Präfix der drei Tiertypen: <prefix>pup, <prefix>female, <prefix>male. Wählt das Körper-Mesh.
nameKey Übersetzungsschlüssel des Anzeigenamens.
xpMult Lerntempo pro Fertigkeit: scent, combat, obedience, hunt, herding. 1.0 ist normal.
combatPower wie hart es zuschlägt. Der Caramelo liegt bei 0.20, eine Kampfrasse über 1.0.
lethalityCurve { min, max }, wie der Schaden von Kampf 0 bis Kampf 10 wächst.
geneRange Geburtsspanne der vier Gene: strength, aggressiveness, resistance, stress. Jedes ist ein { low, high } zwischen 0 und 1.

Optionale Felder

Feld Standard Was es tut
engineBreed der key Rassenname, der an die Engine geht. Er wählt die Textur. Pro Rasse eindeutig.
descKey IGUI_PD_BreedDesc_<key> Beschreibungsschlüssel. Mehrere Rassen können sich einen Schlüssel teilen.
litter Basiswert { min, max } Welpen pro Wurf.
canKill true false heißt, es zermürbt Zombies, setzt aber nie den tödlichen Schlag.
canKnockdown false true lässt es einen Zombie umwerfen.
combatStressMult 1 wie viel Stress ein Kampf es kostet.
panicThreshold Basiswert Stresslevel, ab dem es den Kampf abbricht. panicImmune = true heißt, es bricht nie ab.
bagMult 1 Multiplikator für die Kapazität der Satteltasche.
puppySize 0.6 absolute optische Größe des Welpen. Eine kleine Rasse muss das setzen, sonst kommt der Welpe in Erwachsenengröße zur Welt.
sentinelMult 1 multipliziert den endgültigen Wachradius.
barkNoiseMult 1 skaliert Radius und Lautstärke des Alarmbellens. Ist die Lärm-Option der Sandbox aus, lockt keine Rasse mit Bellen Zombies an, egal welcher Wert hier steht.
loyaltyDecayMult 1 multipliziert den täglichen Loyalitätsverfall. 0 heißt, die Bindung verblasst nie.
alertModeLocked false das Tier bleibt in voller Alarmbereitschaft: der Besitzer kann nirgends auf leise oder still stellen.
huntFetchLevel 6 Jagd-Stufe, ab der es die Beute zum Besitzer zurückträgt.
huntDeliverTimeoutMin 5 Spielminuten, die es die Ablieferung versucht, bevor es aufgibt.
distract aus { <kind> = { chance = 0..1, ... } }. Die Rasse geht von sich aus Dingen nach, ohne Modus und ohne Stufenanforderung.
idleAnimMs Basiswert Länge des Fensters der Leerlaufanimation, in Millisekunden. Setz das, wenn deine Clips kürzer sind als die der Hunde, sonst startet die Schleife neu und schneidet die Geste in der Mitte ab.
restAnim true false lässt das Tier bei Bleib und Bewachen stehen, statt sich hinzulegen.
restPoses hinlegen eine Liste von Ruheposen, aus der das Tier jedes Mal zieht, wenn es sich niederlässt, mit optionalen Übergängen und Leerlaufvariationen. Braucht eigene Clips und eigene Animationsknoten.
limpAnim false true lässt ein verwundetes Tier beim Laufen humpeln. Setz das nur, wenn dein Modell die beiden optionalen Clips Rac_WalkLimpFront und Rac_WalkLimpBack hat (Laufzyklen von 1,0 s mit derselben Root Motion wie Rac_Walk). Ohne die Clips läuft das Tier auf der Stelle fest.
bandSkin aus { base, front, back, cutFront, cutBack }, fünf Namen von Körpertexturen. An der verletzten Pfote trägt das Tier die Variante mit Schnitt, solange die Wunde blutet, und die mit Verband, solange es verbunden ist. Die vier Varianten baust du mit _dogrig/forge/_paw_band.py. Ohne das Feld wird das Tier trotzdem verwundet, es zeigt nur keine Markierung.
voices Hundegeräusche { bark, growl, idle, wildbark, pet, whine, eat, drink }.
diet die Hundelisten was das Tier nicht fressen darf, was für es als Fleisch zählt und was es von sich aus aus einem Trog frisst.
maleChance 0.5 Anteil der Rasse, der männlich geboren wird, von 0 bis 1.
sterileMale false true hält die Männchen der Rasse aus der Zucht heraus.
species "dog" Zucht ist pro Art geschlossen. Abschnitt 6.
skills, canBreed, huntMaxPrey alles an die strukturellen Blöcke. Abschnitt 6.

Zahlen und Flags, die du der Definition hinzufügst, liest du mit CD.breedNumber(animal, field) und CD.breedFlag(animal, field) wieder aus, nicht mit CD.getBreedDef(animal).field. Die beiden kommen mit einem fehlenden Feld klar und funktionieren weiter, wenn sich der Basis-Mod ändert.

Instinkt: distract

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

Eine Rasse, die das deklariert, bemerkt hin und wieder etwas und geht ihm nach, egal was sie gerade tat. Sie tut das aus Bleib genauso wie aus Folgen. Solange es anhält, kommen Befehle mit derselben Meldung distracted zurück, die auch der Gehorsamswurf benutzt. Komm ist der eine Befehl, der durchkommt, und er bricht die Ablenkung ab.

Jede Art nimmt dieselben optionalen Stellschrauben. Was du weglässt, benutzt die Standardwerte des Mods:

Feld Was es tut
chance 0..1, wird nur gewürfelt, wenn der Auslöser der Art etwas gefunden hat. 25 für "25 %" zu schreiben, wird mit einer benannten Zeile im Log abgelehnt
radius Felder, die der Auslöser absucht, sofern die Art überhaupt sucht
durationMin Spielminuten, die das Fenster offen bleibt, bevor es von selbst abläuft
cooldownMin Spielminuten, bis dasselbe Tier wieder abgelenkt werden kann

Die Art prey

Die einzige Art, die der Basis-Mod mitbringt. Das Tier geht Wildtieren nach, und classes wählt, welche davon zählen:

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

Zwei Grenzen gelten, egal was du deklarierst:

  • Es geht nie über dein huntMaxPrey hinaus: eine Rasse mit Deckel bei small jagt keine Hirsche, auch wenn sie large deklariert.
  • Die Stufensperre entfällt nur bei tiny. Ein frisch gezähmtes Tier mit Jagd 0 fängt schon Nager. Mit deklariertem small jagt die Rasse das Kaninchen auf jeder Stufe, aber der Kill braucht dieselbe Jagd-Stufe wie beim Labrador.

Der Jagd-Schalter der Sandbox betrifft nur den Kill: das Tier jagt weiterhin, aber die Beute kommt davon.

Ein so erlegtes Tier läuft durch das normale Zurückholen, also entscheidet das huntFetchLevel deiner Rasse, ob sie den Körper zum Besitzer trägt. Bei 0 liefert das Tier ab dem ersten Tag ab, und genau so bringt dir eine Katze eine tote Ratte. Die Ablieferung überlebt das Ablenkungsfenster: sobald die Beute tot ist, nimmt das Tier wieder Befehle an, und der Heimweg läuft über sein eigenes huntDeliverTimeoutMin.

Eine eigene Art schreiben

distract ist ein Verteiler. Registriere deine eigene:

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

Dann deklarierst du sie an der Rasse wie jede andere Art: distract = { butterfly = { chance = 0.10 } }.

gate läuft vor dem globalen Scan-Budget des Mods. find läuft nach dem Budget und macht die teure Suche. drive gibt true zurück, solange es das Tier noch steuert, und false, wenn es fertig ist, was das Fenster schließt. Dein Handler läuft in seinem eigenen pcall. Wirft er, schreibt der Mod eine benannte Zeile ins Log, meldet diese Art für die Sitzung ab und lässt den Rest des Begleiters laufen.

Für künftige Arten des Basis-Mods reservierte Namen: drink, eat, play. Du kannst heute eine Art unter einem dieser Namen registrieren, aber der Basis-Mod übernimmt den Namen, sobald er seine eigene mitbringt.

Ruheposen

Ein ruhendes Tier legt sich hin. restPoses ersetzt diese eine Pose durch eine Liste, aus der das Tier jedes Mal zieht, wenn es sich niederlässt:

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

Ein Eintrag ist entweder der Name einer Animationsvariable oder eine Tabelle. var ist der Boolean, der die ganze Pause über an bleibt. enter und exit sind einmalige Impulse, die beim Niederlassen und beim Aufstehen abgespielt werden. variation ist eine Liste einmaliger Impulse, die das Tier hin und wieder abspielt, während es die Pose hält, im selben Takt wie die Leerlaufvariation. Nur var ist Pflicht.

Die Ziehung ist gleichverteilt und passiert bei jedem Eintritt in die Ruhe: zwei Einträge heißen also 50% für jeden, und das Tier kann zweimal hintereinander denselben ziehen. Es ist kein Abwechseln.

Jeder Impuls dauert so lange, wie idleAnimMs für diese Variable sagt. Übergib also die Tabellenform und gib jeder Variable eine Länge, auch den Leerlaufvariablen, die du schon hattest. Gib die echte Länge des Clips an.

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

Die Clips und die Knoten sind deine, nicht die des Basis-Mods. Jede Variable braucht eine Knotendatei in deinem Addon unter media/AnimSets/raccoon/idle/, und der Clip, auf den ihr m_AnimName zeigt, muss in deinem Modell existieren. Ein Knoten, der auf einen Clip zeigt, den das Modell nicht hat, scheitert still, und das Tier steht einfach im Leerlauf des Basis-Mods. Gib dem Posenknoten eine m_ConditionPriority von 10 wie die des Basis-Mods, und eine höhere Zahl allem, was darüber spielen soll: 11 für eine Variation, 12 für die beiden Übergänge. Ein Knoten spielt seinen Clip in Schleife, wenn du nichts anderes sagst. Jeder Knoten, der nur einmal spielen soll, also beide Übergänge und jede Variation, braucht deshalb <m_Looped>false</m_Looped>. Ohne das startet der Clip neu, sobald er endet, und das Tier springt sichtbar zurück und macht es noch einmal, weil die Variable erst im nächsten Server-Tick ausgeht. Lass sie alle nur in idle/. Ein Posenknoten in pathfind/ spielt einen Clip ohne Root Motion und friert das Tier ein, wo es steht.

Die Stimme

Ohne voices benutzt jede Rasse die Hundegeräusche, eine Katze würde also bellen. Registriere zuerst deine eigenen Geräusche:

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

Dann zeigst du die Rasse auf sie:

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

Ohne CD.registerVoices spielen die Geräusche zwar, aber sie ignorieren den Kategorieregler des Spielers und die Effektlautstärke des Spiels, und nichts im Log weist darauf hin. Ein Schlüssel, den du in voices weglässt, fällt auf das Hundegeräusch zurück.

whine ist das, was das Tier sagt, wenn es sich verletzt oder krank wird. eat und drink sind die Fressgeräusche. Die des Hundes sind Schleifen, die die Basis stoppt, wenn das Tier fertig ist; wenn deine ebenfalls Schleifen sind, trage sie in CD.SOUND_LOOPED ein, dann stoppt die Basis sie auch, wenn der Zuhörer außer Reichweite geht. Eine Basis älter als API 11 ignoriert die drei Schlüssel und spielt die Hundegeräusche.

Das dritte Argument ist die Hörweite jedes Geräuschs in Feldern. Sie sollte zu dem distanceMax passen, das du in deinem eigenen Sound-Skript geschrieben hast:

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

Auf einem Server schickt der Basis-Mod ein Geräusch nur an die Spieler innerhalb dieser Reichweite. Lässt du die Reichweite weg, bekommt die Stimme eine generische Reichweite nach Kategorie: ein Fauchen, das zehn Felder weit trägt, geht an alle im Umkreis von dreißig. Jenseits von distanceMax hört FMODs inverse Abschwächung auf zu dämpfen, statt still zu werden, also spielt eine lose .ogg in jeder Entfernung weiter mit distanceMin / distanceMax ihrer Lautstärke. Halte distanceMin bei deinen lauten Stimmen aus demselben Grund klein.

Die Ernährung

Ohne diet frisst jede Rasse wie ein Hund: dasselbe Futter vergiftet sie, dasselbe Futter zählt als Fleisch, und dasselbe Futter nimmt sie aus einem Futtertrog. Das Feld überschreibt diese drei Listen nur für deine Rasse.

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

Die zwei Formen gibt es, weil die Kategorien des Spiels nur so weit reichen. Cheese ist ein echter FoodType, ein Schlüssel deckt also jeden Käse im Spiel ab und in jedem Food-Mod, der die Kategorie weiterverwendet. Thunfisch ist keiner: die offene Dose ist FoodType = Fish, zusammen mit jedem anderen Fisch, und die verschlossene Dose deklariert überhaupt keinen Food-Type. Über badParts und proteinParts kommst du an die heran.

Jedes Feld ist eine Tabelle aus <key> = true (hinzufügen) oder <key> = false (wegnehmen, was der Basis-Mod hatte). Ein Wert, der weder true noch false ist, wird mit einer benannten Zeile im Log verworfen.

Feld Was seine Schlüssel sind
bad FoodType von Futter, das dem Tier schadet, es Loyalität kostet und im Füttern-Menü einen roten Eintrag bekommt
badParts ein Stück des Item-Typs, kleingeschrieben, an beliebiger Stelle darin gefunden
protein FoodType, der die Fleischschuld des Tiers begleicht, die das Moodle Schwach beobachtet
proteinParts ein Stück des Item-Typs, dieselbe Suche wie bei badParts
nonProtein FoodType, von dem du weißt, dass er kein Fleisch ist. Er bringt nur die Logzeile zum Schweigen, die der Basis-Mod für einen nie gesehenen Food-Type schreibt
trough FoodType und AnimalFeedType, die das Tier von sich aus aus einem Futtertrog frisst
troughItems vollständiger Item-Typ, für ein Item im Trog, das keinen eigenen Feed-Type hat

replace = true startet von einer leeren Liste statt von der des Hundes. Nimm das, wenn dein Tier fast nichts mit einem Hund gemeinsam hat; eine Katze schreibt sich meist leichter als eine Handvoll Hinzufügungen und Streichungen.

Die drei Listen hängen am Tier, ein Hund und eine Katze am selben Trog fressen also Verschiedenes daraus. Der Napf des Mods ist die Ausnahme: er speichert Futterpunkte, nicht das Item, das ihn gefüllt hat, also läuft das Füllen für alle über die Basisliste. Etwas Giftiges kommt ohnehin nicht in einen Napf.

Geschlecht bei der Geburt

Jedes Tier wird beim Auftauchen als männlich oder weiblich gezogen, halbe halbe. maleChance ist der Anteil, der männlich geboren wird, von 0 bis 1, und sterileMale hält die Männchen der Rasse aus der Zucht heraus.

maleChance = 0.00033,
sterileMale = true,

Dieses Paar ist die Glückskatze. Das Mosaik aus Orange und Schwarz sitzt auf dem X-Chromosom, also ist eine Glückskatze fast immer weiblich, und das Männchen, das doch auftaucht, ist XXY, etwa eines von dreitausend, und zeugt keinen Wurf. Ein Männchen einer sterileMale-Rasse wird nie als Partner gewählt, und die Option zum Decken verschwindet aus seinem Kontextmenü. Die Weibchen decken normal.

Ein Männchen einer solchen Rasse trägt überall dort, wo das Spiel sein Geschlecht zeigt, die Marke "Unfruchtbar": in der Tierkarte, im Kontextmenü, im Streunerscan und im Zwinger, damit der Spieler weiß, warum es sich nicht fortpflanzt.

Beide Felder werden aus der Rassendefinition gelesen, es wird also nichts am Tier gespeichert und ein bestehender Spielstand braucht keine Migration. Dieselbe Ziehung entscheidet das Geschlecht eines Welpen bei der Geburt, eine Rasse, die in der Welt weiblich ist, bleibt also auch in den Würfen weiblich.

6. Ein ganzes System abschalten

Die fünf Fertigkeiten sind die fünf Systeme:

Fertigkeit Welches System sie ist
scent die Wache: Zombies bemerken und dich warnen
combat Kämpfen, der Bewachen-Modus und Selbstverteidigung
obedience der Trickbaum
hunt Jagen, dazu der Sammelbonus für den Besitzer
herding Vieh in einem Gehege hüten

Eine Art deklariert also, was sie nicht hat:

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

Fehlend oder true heißt, das Tier hat diese Fertigkeit.

Eine abgeschaltete Fertigkeit sammelt auch keine Erfahrung mehr, und ihre Leiste und ihre Befehle verschwinden aus der Oberfläche.

canBreed = false steht getrennt von der Tabelle, weil Zucht weder Leiste noch Erfahrung hat. Das Tier empfängt nicht, wird nie als Partner gewählt und paart sich auch nicht mit seinesgleichen.

Die Art

Ein Addon, das ein anderes Tier hinzufügt statt eines weiteren Hundes, deklariert das:

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

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

Bei jeder bisher veröffentlichten Rasse fehlt das Feld, und fehlend heißt "dog".

Zucht ist pro Art geschlossen: ein Paar empfängt nur, wenn beide Seiten dieselbe Art sind, im Befehl, im passiven stündlichen Wurf und im Debug-Werkzeug. Und die Oberfläche nennt dein Tier nicht länger einen Hund: nounKey ist das nackte Substantiv, das Sätze wie "Dieser %1 gehört bereits einem anderen Überlebenden" füllt, und youngKey ist die Bezeichnung, die der Nachwuchs deiner Art im Fenster, im Kontextmenü und im Zwinger trägt, wo ein Hund Welpe sagt.

Diese zwei Substantive decken nur die Sätze ab, die um ein %1 herum gebaut sind. Jeder andere Satz mit dem Wort Hund darin ("Hund untersuchen", "Dein Hund ist hungrig", "Der Hund bellt absichtlich") nimmt ab API 7 einen Schlüssel pro Art: liefere IGUI_PD_ScanStray_cat in deinen Übersetzungsdateien mit, und der Basis-Mod zeigt ihn statt IGUI_PD_ScanStray, sobald das Tier auf dem Bildschirm eine Katze ist. Ein Schlüssel, den du nicht mitgeliefert hast, fällt auf den Hundetext zurück. Die Schlüssel, die das Suffix annehmen: ScanStray, ScanTitle, PauseGrowthTip, BadFood, AlertFullDesc, AlertQuietDesc, AlertLockedTip, HuntModeDesc, jedes Trick*Desc, TrickDistractDone, TrickNeedsBag, TrickGotoPick, TrickSniffNoItem, TrickFetchNoItem, AlertGroup, DogLost, jedes Refused_*, KennelStatusNoData und die Moodle_*_desc der Pflege-Moodles (Krank, Schwach, Hunger, Durst, Ausgeruht, Trauer). Sätze ohne Tier auf dem Bildschirm (der Registry-Reiter, "Freunde dich erst mit einem Tier an") sind im Basis-Mod schon artneutral. Die Katze liefert alle davon in vierzehn Sprachen mit; nimm ihre IG_UI.json als Vorlage.

Die group des Tiers liest nur das Debug-Spawn-Menü und der Clip-Betrachter, über IGUI_Animal_Group_<group>. Setz t.group = "cat" nach dem Verhaltens-Helfer und liefere diesen Schlüssel mit, sonst taucht deine Art dort unter dem Namen des Hundes auf.

species zu deklarieren, ohne es zu registrieren, ist nicht fatal: dein Tier verweigert die Zucht außerhalb seiner eigenen Art weiterhin, weil die Sperre die rohe Zeichenkette vergleicht. Nur die zwei Substantive fallen auf die des Hundes zurück, und das Log sagt das beim Namen.

Auf einem Basis-Mod älter als API 6 wird das Feld ohne Logzeile ignoriert, und dein Tier züchtet wie ein Hund. Wenn es sich nie mit einem kreuzen darf, prüfe auf API 6, oder halte canBreed = false, solange der Basis-Mod älter ist, so wie die Katze es macht.

huntMaxPrey deckelt die Beutegröße innerhalb einer Jagd, die es weiterhin gibt; es ersetzt nicht hunt = false. Es nimmt "tiny" (Mäuse, Ratten, Eichhörnchen), "small" (Kaninchen, Waschbären) oder "large" (Hirsche, der Standard, kein Deckel). Dein Tier jagt keine Beute oberhalb des Deckels und zeigt sie auch nicht an. Ein Wert außerhalb dieser Leiter fällt auf "large" zurück und schreibt eine benannte Zeile ins Log.

Eine gesperrte Option verschwindet aus dem Radialmenü, dem Kontextmenü und dem Fenster, und der Server lehnt sie ohne Meldung ab. Keines dieser Felder fügt einen Übersetzungsschlüssel hinzu, also erkläre die Grenzen in deiner Rassenbeschreibung.

7. Die Tierdefinitionen

Die Engine bindet das Mesh an den Tiertyp und nicht an die Rasse, deine Rasse braucht also drei eigene Typen. Der kürzeste Weg ist, die Datei eines vorhandenen Addons zu kopieren und umzubenennen.

Die drei Teile, in einer shared-Datei unter 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

Die Helfer, die der Basis-Mod dir gibt:

Helfer Was er ausfüllt
CD.applyCompanionModel(t, "Body_Model") Körpermodell, Skelett, Zerlege-Texturen und das Animationsset
CD.applyCompanionBehaviour(t) jedes Verhaltens-Flag der Engine, das ein Begleiter braucht
CD.applyCompanionAvatar(t) die Porträtkamera für das Tierfenster
CD.COMPANION_SOUNDS die gemeinsame Tabelle für Schritte und Foley
CD.defineGenome("species") das Genom einer neuen Art, mit der Standard-Genliste (API 7)
CD.STRAY_NO_BLEEDOUT der Wert, der einen verängstigten Streuner nicht verbluten lässt
CD.defineCompanionParts(typePrefix, engineBreed, meat) die Zerlegeteile aller drei Stufen. meat ist optional: { item, minNb, maxNb, pupMinNb, pupMaxNb }, und ohne das gibt das Tier das Hundefleisch des Basis-Mods. Die Engine multipliziert Anzahl und Sättigung jedes Stücks mit der Größe des Kadavers, eine Art mit großer Size (die Katze nimmt 2.5 bis 3.5 als optische Skala) braucht also ein eigenes Item mit kleinerer Grundsättigung und niedriger Anzahl

Die artneutralen Namen kamen mit API 7. Die älteren, CD.applyDogModel, CD.applyDogBehaviour, CD.applyDogAvatar, CD.DOG_SOUNDS, CD.defineDogParts und CD.DogMoodles, sind dieselben Funktionen und Tabellen unter ihrem ersten Namen. Ein Addon, das auf einem Basis-Mod älter als 0.7.3 laden will, prüft auf die alten Namen und nimmt den neuen, wenn es ihn gibt: local applyModel = CD.applyCompanionModel or CD.applyDogModel.

Das Animationsset muss "raccoon" bleiben. Ein abgespaltenes Animationsset lädt seine Zustandsmaschine nicht, und das Tier steht bewegungslos da.

Verwende das Genom weiter. Eine Hunderasse zeigt auf die Liste des Basis-Mods: AnimalDefinitions.genome["dog"].genes. Eine neue Art ruft einmal CD.defineGenome("cat") auf und gibt jeder Stufe die Tabelle, die zurückkommt. Die Engine liest nur die genes-Liste jeder Stufe, und der Genom-Schlüssel ist Konvention, deine Art bekommt also ihren eigenen Eintrag ohne eine eigene Genliste.

Deklariere kein mate. Damit fährt die Engine ihre eigene native Paarung in jeder Tierzone, die den Zuchtblock des Mods und seine Sandbox-Optionen ignoriert und Welpen ohne Bindung erzeugt. Der Basis-Mod räumt das Feld beim Start ab, aber verlass dich nicht darauf.

Parts sind Pflicht. Drei Zeilen in einer eigenen Datei:

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

Ohne sie stürzt der Vanilla-Code beim Zerlegen deines Tiers ab, weil er die Parts-Definition über Typ und Rasse indexiert und auf ein nil trifft.

Der Porträt-Zoom in CD.applyDogAvatar ist auf einen Hund geeicht, und Avatar-Zoom ist Vergrößerung: je kleiner das Tier, desto größer die Zahl. Teile den Zoom durch das Größenverhältnis zum Hund und multipliziere die Offsets damit, sonst sitzt dein Tier winzig am unteren Rand seines eigenen Porträts.

8. Das Modell-Skript

Eine Datei unter media/scripts/ sagt dem Spiel, dass dein Modell existiert:

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 hält die Clips in deiner Modelldatei nutzbar. Ohne das lädt das Modell und animiert nie.

Das head-Attachment ist die Stelle, an der Hüte und dergleichen sitzen. Wenn die Satteltasche an dein Tier passen soll, füg saddlebags_l, saddlebags_r und saddlebags_c genauso hinzu, am Wirbelsäulenknochen. Diese Zahlen findest du, indem du dir das Tier im Spiel ansiehst und sie nachschiebst. Am Modell gemessene Werte setzen die Tasche in den geometrischen Mittelpunkt des Mesh, und dort sieht sie am Körper nicht richtig aus.

9. Wo das Tier auftaucht

Streuner werden pro Gebäude und pro Chunk ausgewürfelt, beim ersten Laden dieses Chunks. Du beschreibst deine Würfe als Liste von Schritten, entweder in der Rassendefinition als spawns oder mit 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 },
},
Schlüssel Was er ist
id Name des Schritts, für deine eigenen Logs
class die Gebäudeklasse, auf der gewürfelt wird
chance eine Funktion, die einen Prozentwert zurückgibt, damit sie den Sandbox-Multiplikator zur Wurfzeit lesen kann.
suffix Schlüssel des dauerhaften Speichers. Über alle Mods hinweg eindeutig, und nie geändert.
breed der Rassenschlüssel, der gespawnt wird
gate optionale Funktion, die einen Boolean zurückgibt, geprüft vor dem Wurf. Der Husky benutzt CD.isWinter.
indoor Prozentchance, im Gebäude statt auf der Straße geboren zu werden. Standard 35.

Multipliziere deine Chance mit CD.dogSpawnMultiplier(), oder teile CD.strayChancePerHouse() durch eine eigene Seltenheit. Beides hält die Sandbox-Einstellung des Spielers wirksam.

indoor ist eine Vorliebe: ein Gebäude ohne freies Innenfeld in diesem Chunk fällt auf ein Feld draußen zurück.

Die Gebäudeklassen

Der Basis-Mod registriert vier:

Klasse Trifft auf
house Wohngebäude, die keine Läden sind
police Polizeiwachen und Ähnliches
petvet Zoohandlungen und Tierarztpraxen
farm Hofgebäude, und sie ist auch außerhalb der Stadt erlaubt

Passt keine davon, registriere deine eigene:

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

Die Trefferfunktion bekommt die Gebäudedefinition und läuft in einem geschützten Aufruf, ein Fehler darin hält also das Laden des Chunks nicht auf. skipUrbanGate lässt die Klasse in einem Chunk würfeln, der nicht städtisch ist, was ein Hof oder eine Waldhütte braucht. Setz kein exclusive. Die Basisklassen sind untereinander exklusiv, und eine Addon-Klasse trifft zusätzlich zu ihnen.

Schon vergebene Suffixe

Das Suffix ist Teil des Schlüssels, unter dem "dieses Gebäude wurde schon gewürfelt" gespeichert wird. Zwei Mods mit demselben Suffix zerschießen sich gegenseitig die Würfe.

Jedes Suffix beginnt mit einem senkrechten Strich. Diese sind schon in Gebrauch:

  • Basis-Mod: das leere Suffix, dazu g, h, hv, bc, gh, hh, bh
  • Rottweiler: rw, r
  • Dobermann: db, dm, dh
  • Labrador: lb, lk
  • Mops: pg, pv
  • Malinois: ml, mm, mh
  • Katzen: jedes Suffix, das mit ct, cv oder cs beginnt (ein Satz pro Fellfarbe)

10. Das Rassen-Moodle

Ein Moodle ist eine Client-Datei. Du hängst eine Tabelle an CD.DogMoodles an, und der Basis-Mod zeichnet sie, verfolgt sie und räumt sie wieder ab:

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 gibt die Stufe zurück, oder 0 für "wird nicht angezeigt". apply bekommt die Spielminuten seit dem letzten Aufruf, multipliziere deinen Effekt also mit elapsedMin.

Liefere das Symbol in sechs Größen mit, von 32 bis 128 Pixel. Die Oberfläche wählt die Größe nach der Skalierung des Spielers, und eine fehlende Größe erscheint als leeres Quadrat. Das Symbol erscheint in der eigenen Leiste des Mods, nicht bei den Vanilla-Moodles.

11. Hooks

Zwei Listen von Funktionen, die der Basis-Mod auf dem Server aufruft. Häng sie aus einer shared-Datei an. Jeder Handler läuft in seinem eigenen geschützten Aufruf, ein Fehler in einem Addon hält den Basis-Mod also nicht auf.

CD.onHuntDelivered, ab API 2, wird direkt aufgerufen, nachdem das Tier eine Beute vor den Füßen seines Besitzers abgelegt hat:

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

CD.onUpkeepStress, ab API 3, wird in jedem Upkeep-Zyklus aufgerufen, nachdem der Basis-Mod den Stress aus unerfüllten Bedürfnissen addiert hat und bevor er geschrieben wird. Gib eine Zahl zurück, die zur Summe addiert wird:

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

Der Upkeep rechnet den Wert in jedem Zyklus neu und überschreibt ihn, alles von außen Geschriebene ist also im nächsten Tick weg. Nimm den Hook für einen Zustand, der aus der Umgebung kommt, etwa Hitze, Kälte oder Regen.

Im Multiplayer schreibt der Client nie Mod-Daten. Ein Schreibvorgang auf Client-Seite überschreibt beim nächsten Sync die Kopie des Servers, und das Symptom zeigt sich Minuten später an einer ganz anderen Stelle. Erledige die Arbeit in einer Server-Datei oder in einem dieser Hooks.

Die Zahlen im Panel SERVER TUNING sind gewöhnliche CD.*-Konstanten, und ein Addon darf eine davon beim Laden aus einer shared- oder Server-Datei setzen. Ab API 9 nimmt das Panel den Wert, der gilt, wenn der Server fertig geladen hat, als Standard dieses Reglers: er bleibt, bis ein Admin ihn überschreibt, der Reset-Knopf kehrt zu ihm zurück, und das Panel zeigt ihn als Standard an. Auf einem älteren Basis-Mod schrieb das Panel bei jedem Start den Werkswert zurück. Setz den Wert beim Laden, nicht aus einem späteren Ereignis, sonst stellt die nächste Änderung an irgendeinem Regler den Werkswert wieder her.

12. Übersetzungsschlüssel

Liefere pro Sprachordner eine IG_UI.json mit, unter media/lua/shared/Translate/<LANG>/. Der Basis-Mod liefert vierzehn: EN, PTBR, CH, CN, DE, ES, FR, IT, JP, KO, RU, TH, UA, VI.

Die Schlüssel, die eine Rasse braucht:

Schlüssel Wo er erscheint
IGUI_PD_Breed_<key> der Rassenname, überall in der Oberfläche des Mods
IGUI_PD_BreedDesc_<key> die Beschreibung auf der Karte des Tiers
IGUI_Breed_<engineBreed> der Rassenname in den Bildschirmen des Spiels selbst
IGUI_AnimalType_<type> einer für jeden deiner drei Typen
deine Art-Schlüssel der nounKey und der youngKey, die du an CD.registerSpecies übergeben hast
deine Moodle-Schlüssel der Name und die Beschreibung
{
    "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."
}

Ein wörtliches Prozentzeichen in einer übersetzten Zeichenkette lässt den Bildschirm abstürzen, der sie anzeigt, weil der Formatierer es als Platzhalter liest. Eine Sprachdatei mit Byte Order Mark kompiliert nicht, und der Fehler nennt eine andere Datei als die kaputte.

13. Was der Spielstand sich merkt

Drei deiner Kennungen werden in Speicherdateien geschrieben und für immer wieder gelesen:

  • key, die Rassenkennung, die an jedem deiner Tiere hängt
  • typePrefix, aus dem die Tiertypen entstehen, die die Engine speichert
  • suffix, der Schlüssel des "schon gewürfelt"-Speichers, einer pro Spawn-Schritt

Keine der drei lässt sich nach der Veröffentlichung umbenennen. Ein umbenannter key macht jedes lebende Tier dieser Rasse zur Waise. Ein umbenanntes suffix lässt jedes Gebäude in jedem bestehenden Spielstand erneut würfeln, und eine monatelang gespielte Welt füllt sich mit deinem Tier.

Ein neues Suffix ist zugleich der Weg, auf dem eine neue Rasse in schon laufende Welten kommt. CD: Cats hat seinen vier neuen Fellfarben neue Suffixe gegeben, damit sie in bestehenden Spielständen auftauchen, während die ursprüngliche schwarze Katze ihr altes Suffix behielt und nicht erneut gewürfelt wurde.

Ein Addon aus einem Spielstand zu entfernen, in dem lebende Tiere dieser Rasse sind, verliert sie. Die Engine verwirft ein Tier, dessen Typ sie nicht mehr kennt. Der Basis-Mod stürzt nicht ab, und Bindung und Zwinger bauen sauber ab. Schreib das auf deine Workshop-Seite.

Fehlt dein Addon, fällt der Basis-Mod bei allem, wonach er gefragt wird, auf die Standardrasse zurück, ein ohne deinen Mod geöffneter Spielstand ist also lesbar.

14. Bevor du veröffentlichst

  • Die Prüfung steht in jeder Lua-Datei und nennt die API, die du wirklich benutzt.
  • Die Untergrenze der Basis-Version steht in Worten in deiner Beschreibung. versionMin kann sie nicht ausdrücken.
  • Du hast beim ersten Laden das Log gelesen. Sonst lehnt CD.registerBreed still ab, und jede Ablehnung schreibt eine benannte Zeile.
  • CD.defineDogParts wird aufgerufen. Zerleg eines deiner Tiere und sieh nach, dass nichts abstürzt.
  • Dein key, dein typePrefix und deine Spawn-Suffixe sind eindeutig, und du wirst sie nicht ändern müssen.
  • Eine kleine Rasse setzt puppySize, und du hast einen Welpen gesehen, der das belegt.
  • Ist es eine andere Art, wird CD.registerVoices aufgerufen und voices ist ausgefüllt, mit whine darin, sonst bellt und winselt sie wie ein Hund.
  • Das Moodle-Symbol existiert in allen sechs Größen.
  • Jeder Sprachordner, den du mitlieferst, hat jeden Schlüssel. Ein fehlender Schlüssel zeigt den rohen Schlüssel auf dem Bildschirm.
  • Du hast im Multiplayer getestet, nicht nur im Einzelspieler. Position, Besitz und Mod-Daten verhalten sich dort anders.
  • Deine Workshop-Seite warnt davor, das Addon aus einem Spielstand mit lebenden Tieren zu entfernen.

15. Häufige Fehler

Symptom Was dahintersteckt
"Mein Tier taucht nie auf." Fast immer die Prüfung: das Addon braucht eine API, die der installierte Basis-Mod nicht hat, also kehrt es früh zurück und registriert nichts.
"Es taucht auf, steht aber wie eingefroren da." Das Animationsset ist nicht "raccoon", oder dem Modell fehlen Clips.
"Es wird als schwarzer Fleck gerendert und das Log läuft über." Das Modell hat mehr als 60 Knochen.
"Das Zerlegen stürzt ab." CD.defineDogParts wurde nicht aufgerufen.
"Der Welpe kommt in Erwachsenengröße zur Welt." Eine kleine Rasse hat puppySize nicht gesetzt. Der Wert ist absolut und wird bei jedem Durchlauf erneut angewendet, die Größenspanne des Typs deckt ihn also nicht ab.
"Zwei meiner Rassen verwandeln sich ständig ineinander." Sie teilen sich einen engineBreed. Das Präfix wählt das Mesh und darf einer Familie gehören. engineBreed wählt die Textur und hält die Rassen dieser Familie auseinander, er muss also pro Rasse eindeutig sein.
"Der Ton spielt, ignoriert aber den Lautstärkeregler." Für diesen Ton wurde CD.registerVoices nicht aufgerufen.
"Im Einzelspieler läuft es, auf einem Server spinnt es." Irgendetwas schreibt Mod-Daten auf dem Client. Verleg das auf den Server.
"Es hat gekämpft, obwohl ich Kampf abgeschaltet habe." Der Basis-Mod, auf dem es läuft, ist älter als API 5. Der kennt das Feld nicht und ignoriert es. Die Prüfung muss auf diesem Basis-Mod blockieren, statt mit fehlendem Feld zu laden.

Etwas in diesem Handbuch falsch oder fehlt dir etwas? Sag es auf Discord.