Skip to content

Pets

hobbs_pets, current version 1.0.0

Overview

Pets and falconry for VORP. Players buy an animal at a dealer, give it a name, call it, feed and water it, bathe it, pet it and teach it tricks. Dogs and wolves go after people and game, find a kill in the grass and bring small game back in their mouth. Small animals ride on the shoulder, dogs and cats ride along behind the saddle. Birds circle above the player or fly beside him, perch on his shoulder and go out to hunt, gather, scavenge or fish, and a parrot learns to speak.

Every animal has hunger, thirst, cleanliness, health, sickness and a bond with its owner. It gets sick when it is neglected, collapses when it is starved or hurt, and a vet can help. Animals of an animal keeper have young. Every animal grows up, grows old and one day passes away, leaving a keepsake. Pets can be sold to other players, given away in a crate or returned to the dealer.

The catalog ships with 57 animals and 29 birds: dogs, a cat, chickens, rabbits, raccoons, foxes, deer, moose, bears, cougars, panthers, an alligator, songbirds, corvids, gulls, parrots, vultures, hawks, owls, eagles, herons, pelicans and more. All of it is driven by the config, so you remove what you do not want and change the prices to fit your economy.

How it plays

  1. A player walks up to the animal dealer and presses E. Anyone may browse, but only players with a buyer job (by default the animal keeper) see prices and may buy. He picks an animal on the left, it appears on the display spot in the yard, and he turns it and picks a coat or plumage. He types a name, picks male or female, and pays in cash.
  2. The animal is his now and comes running straight away unless he already has the most animals out with him. From then on he opens Your Animals with PAGEUP (or /pets) to see every animal he owns, where it is, how it is doing and what it eats, to call one, send it home, rename it, offer it to another player or put it in a crate.
  3. Out in the open he looks at his pet and a prompt group appears with its name: pet, feed, water, bathe, treat, heel or stay, attack, send home, and a second page with sit, lie down, sleep, shoulder and the tricks. Hunger, thirst and dirt only drop while the pet is out, so a pet at home never goes hungry.
  4. Petting costs nothing and builds trust. With enough trust the pet learns tricks, later it attacks at his word, and a loyal pet jumps in on its own when he is hit.
  5. If he lets it go hungry, thirsty or dirty, it gets sick and loses health. At zero health it lies unconscious. A revival kit brings it back, otherwise it is carried home after ten minutes. An animal keeper, doctor or quack can treat and revive other people's animals.
  6. With a bird on his shoulder he sends it off to hunt, gather or fish. It is gone for ten to twenty-five minutes and comes back with game, carrion, fish or finds. Every catch trains it, and a trained bird brings better prey.
  7. An animal keeper with a devoted male and female of the same kind standing by him finds Mate in Your Animals. He pays a fee per young one, the female carries for a week, and the young wait at home.
  8. After ten to twelve real weeks the animal has lived its life. It walks away, and the owner gets a keepsake locket with its name.

Requirements

Resource Note
vorp_core Characters, jobs, cash and notifications
vorp_inventory Food, water, soap, kits and the crate, including the crate's metadata
nss_libs Prompts, blips, the dealer NPC, client and server events, Discord log
oxmysql The hobbs_pets table

Jobs are plain VORP job names from your config. Secondary jobs (VORP multiJobs) count like the main job unless you turn Config.SecondaryJobs off. The defaults name animalkeeper, Government, doctor, quack, birdtrainer and wapiti. None of them has to exist, but a job that does not exist simply never matches, so change them to your own job names.

Installation

  1. Unpack the resource into resources and keep the folder name hobbs_pets.
  2. Run sql/install.sql. It only adds the twelve items to the items table with INSERT IGNORE, so items you already have are skipped. The pets table is not in the file, the resource creates it itself.
  3. Copy the item images from install/ to vorp_inventory/html/img/items/.
  4. Create the jobs you want to use for buyers, breeders and vets, or change the job names in the config to jobs you already have.
  5. Add it to server.cfg after its dependencies:

    ensure oxmysql
    ensure vorp_core
    ensure vorp_inventory
    ensure nss_libs
    ensure hobbs_pets
    
  6. Start the server. On first start the table hobbs_pets is created. Then the script checks the items table and prints a red [hobbs_pets] line for every required item that is missing. It also sends every pet that was still out home, so nothing is lost on a crash.

  7. Set Config.Locale and the dealer position in config/dealers.lua, then check the Discord webhook in server_config.lua.

Warning

If a table named hobbs_pets with a different layout already exists, the resource refuses to start and tells you so in the console. Rename the old table (RENAME TABLE hobbs_pets TO hobbs_pets_old;) and restart the resource.

Configuration

Everything you may change lives in config/ and in server_config.lua. The rest of the script is encrypted. config/config.lua holds the general settings, the animals are in config/animals.lua and config/birds.lua, their poses in config/poses.lua, the dealers in config/dealers.lua, what they eat in config/diet.lua, falconry in config/falconry.lua, config/falconry_areas.lua and config/falconry_voices.lua, and all texts in config/locales.lua.

config.lua: general

Config.Debug  = false  -- extra console output and the in-game adjust commands
Config.Locale = "en"   -- "en" or "de"
Config.CoatProbe = "petcoats"
Config.SecondaryJobs = true

Debug turns on console output and the adjust commands listed under commands. Leave it off on a live server. CoatProbe is the name of the debug command that counts how many coats an animal model really has, useful when you add a model to the catalog.

config.lua: limits and names

Config.Limits = {
    own = 5,
    ownJobs = { animalkeeper = 10 },
    birds = 1,
    birdJobs = { animalkeeper = 3 },
    out = 2,
}

Config.Names = { min = 3, max = 24 }

own is how many animals a character may keep, birds how many of those may be birds, out how many may be out with the player at once. Pets in a crate do not count. Unborn young already take their places while the female carries. ownJobs and birdJobs raise the limit for a job, so an animal keeper keeps ten animals and three birds by default. If you want a hunter job to keep two birds, add hunter = 2 to birdJobs.

config.lua: dealer tabs

Config.Groups = {
    { key = "dog",      label = "Dogs" },
    { key = "cat",      label = "Cats" },
    { key = "bird",     label = "Birds" },
    { key = "other",    label = "Other" },
    { key = "predator", label = "Predators" },
}

The tabs at the dealer, in this order. Every animal names one of these keys as its group. Remove a tab and the script prints a warning for every animal that still points to it.

config.lua: the menu key and the prompts

Config.Key = {
    command = "pets",
    default = "PAGEUP",
    hash    = 0x446258B6,
}

Config.Prompts = {
    main  = { pet = "G", feed = "C", water = "V", bath = "F", heal = "R", follow = "E", attack = "Q", home = "DOWN",
              tricks = "RIGHT", menu = "LEFT" },
    page  = { "G", "C", "V", "R", "E", "DOWN", "RIGHT", "LEFT", "Q" },
    back  = "F",
    hide  = { 30, 34, 35, 52 },
}

Config.Key opens Your Animals. command is also the chat command, and players can rebind the key in the game settings. hash is only used on builds without RegisterKeyMapping.

Config.Prompts are the prompts when you aim at your own pet. main is the first page. page are the keys of the second page in order: sit, lie down, sleep, shoulder (if the kind fits on the shoulder), then the tricks, then "find the kill" for dogs and wolves. back goes back to the first page. hide are the game's own prompt types hidden on the pet, so they do not sit on top of yours. Key names come from the list in client/keys.lua: G, E, F, R, Q, C, V, DOWN, LEFT, RIGHT, ENTER, BACKSPACE, UP, Q_FREE and R_FREE. The script checks on start that every key exists and that no key is used twice on the same page, and prints a [hobbs_pets] line if it finds a clash.

config.lua: calling and following

Config.Call = {
    cooldownSec    = 8,
    returnDistance = 150.0,
    catchUp        = { foot = 50.0, mounted = 100.0 },
}

Config.Follow = {
    interiors = true,
    offset    = { x = 0.5, y = -0.1, z = 0.0 },
    mounted   = { x = 2.0, y = -0.1, z = 0.0 },
    speed     = -1.0,
    comeSpeed = 3.0,
    stopping  = 0.5,
    retask    = 6.0,
    idle      = { radius = 5.0, afterMs = 2000, minWait = 3.0, maxWait = 6.0 },
    checkMs   = 1000,
}

A pet further than returnDistance from its owner walks home on its own, and the owner is told. Beyond catchUp it is placed next to the player again, so a rider does not lose his dog. Follow.offset is where the pet walks next to the player on foot, mounted next to the horse, speed = -1 matches the player's pace. When the player stands still for idle.afterMs, the pet wanders within idle.radius. Set interiors = false if you do not want pets following into buildings.

config.lua: care

Config.Care = {
    reach = 2.5,
    feed  = { cure = 5 },
    water = { item = "petwater", also = { "water" }, value = 50, cure = 5 },
    bath  = { item = "pet_soap", value = 50, cure = 10, wear = 10, worn = 90 },
    heal  = { item = "petheal",  value = 50, cure = 10 },
    pet   = { cooldownSec = 60, interaction = { dog = "Interaction_Dog_Patting" } },
    come       = { dist = 1.5, timeoutMs = 6000 },
    turnMs     = 1000,
    durationMs = 9000,
}

reach is how close the player must stand for feeding, watering, bathing, treating and petting. value is what one use restores, cure how much sickness it takes away. water.also lists other items that count as water (they are used up, but not registered as usable items), so a plain water item from your server works too. If the pet stands in water, it drinks without any item. The soap is not used up at once: every bath adds wear percent to it, and above worn it is gone, so one bar lasts ten baths. Petting raises the bond once every cooldownSec. Dogs get the game's patting interaction, every other kind gets a kneel animation. What each animal eats is in config/diet.lua.

config.lua: hunger, thirst and sickness

Config.Metabolism = {
    tickSec        = 60,
    huntingRate    = 0.5,
    saveEveryTicks = 5,
    hungerPerHour  = 100 / 3,
    thirstPerHour  = 100 / 3,
    bathPerHour    = 16,
    starve         = { everySec = 300, health = 40 },
    recoverPerHour = 20,
    warnBelow      = 25,
    disease = {
        gain = {
            bath   = { { below = 5, perHour = 14 }, { below = 10, perHour = 7 } },
            hunger = { { below = 5, perHour = 11 }, { below = 10, perHour = 6 } },
            thirst = { { below = 5, perHour = 11 }, { below = 10, perHour = 6 } },
        },
        healAbove      = 50,
        healPerHour    = 5,
        damage         = { { from = 86, perHour = 16 }, { from = 61, perHour = 7 }, { from = 36, perHour = 2.4 } },
        recoverBelow   = 10,
        sickFrom       = 50,
    },
}

All values run from 0 to 100 and only change while the pet is out. With the defaults a pet is hungry and thirsty after three hours out, so a player who takes his dog along all evening has to feed it twice. A bird out hunting eats at half the rate. When food or water hit zero, the pet loses starve.health every starve.everySec seconds and collapses after fifteen minutes. Above half food and water it regains health. The pet tells its owner when a value drops below warnBelow and when it falls sick. Sickness grows while a value sits below 10 and heals on its own once food, water and cleanliness are all above healAbove. From sickFrom the pet counts as sick, and from damage.from it loses health per hour. Values are written to the database every saveEveryTicks minutes, and whenever something is done to the pet.

If your players are online for short sessions, halve hungerPerHour and thirstPerHour so pets do not come home starved every evening.

config.lua: bond

Config.Bond = {
    start    = 10,
    perPet   = 3,
    perCare  = 1,
    tricks   = 25,
    attack   = 50,
    words    = {
        { from = 80, key = "bond_loyal" },
        { from = 50, key = "bond_attached" },
        { from = 20, key = "bond_trusting" },
        { from = 0,  key = "bond_shy" },
    },
}

A new pet starts at 10. Petting adds 3, each feeding, watering, bath or treatment adds 1, and the group's own food, prey or favorite food adds 2 on top. At tricks the tricks open up, at attack it goes after people, at Config.Defend.bond (80 by default) it defends its owner on its own, and Config.Breeding.minBond (50) is needed for breeding. The words are what the player reads in the particulars.

config.lua: life, age and kinds

Config.Keepsake = { item = "pet_keepsake" }

Config.Life = {
    weeks          = { 10, 12 },
    senior         = 0.80,
    startScale     = 0.6,
    birdStartScale = 0.8,
    farewellSec    = 8,
}

Config.Kinds = {
    default   = { years = 12, adult = 1,    litter = 1, outDays = 28 },
    dog       = { years = 13, adult = 1.5,  litter = 2, outDays = 70 },
    cat       = { years = 15, adult = 1,    litter = 2, outDays = 70 },
    parrot    = { years = 50, adult = 3,    litter = 1, outDays = 112 },
    -- ...
}

Every pet lives a random number of real weeks between the two values in weeks. In the game it shows the age of its kind: a dog reaches 13 years in that time, a parrot 50. adult is the age at which it is grown (and may breed), litter the most young per litter, outDays the in-game days a young one stays with its mother before it may be called, boxed or returned. A newborn appears at startScale of its size and grows to full size until it is adult. From senior of its life it counts as old. A pet that is out when its time comes walks away for farewellSec seconds, then its owner gets the keepsake. Bought animals are already past outDays, so they can come along straight away.

A key is a kind from the catalog, a model name as key overrides its kind. Missing kinds use default. To make every pet live about half a year, set weeks = { 24, 28 }.

config.lua: breeding

Config.Breeding = {
    enabled        = true,
    jobs           = { animalkeeper = 0 },
    feeShare       = 0.70,
    pregnancyHours = 168,
    restHours      = 168,
    maxLitters     = 5,
    reach          = 6.0,
    minBond        = 50,
}

Two pets of the same kind (birds only of the same model), male and female, both out, both within reach of the player, both grown and not old, not related, each with at least minBond. jobs lists who may breed (job = minimum grade), nil lets everyone. The fee is feeShare of the parents' average price per young one. The litter is a random number up to the kind's litter, capped by the free places the player has. The female carries for pregnancyHours, then the young lie at home, named "Young one". A young one takes the mother's model or, with a 50 percent chance, the father's. After a litter the female rests for restHours, and after maxLitters she has no more. A young one carries the names of both parents and of the breeder, and those names stay even when the parents are gone.

config.lua: knocked out and vets

Config.Down = {
    item       = "petrevive",
    revived    = 100,
    minutes    = 10,
    birdHealth = 25,
    holdMs     = 1000,
    keys       = { revive = "G", giveUp = "DOWN" },
}

Config.Vet = {
    enabled    = true,
    jobs       = { animalkeeper = 0, doctor = 0, quack = 0 },
    key        = "G",
    recheckSec = 60,
}

A pet at zero health lies unconscious where it fell. Its owner revives it with the revival kit (G) or holds DOWN to have it carried home at once. After minutes it is carried home on its own, and it stays unconscious at home until the owner calls it and revives it there. Birds never go down, they fly home with birdHealth and need rest. Players with a Vet.jobs job see "Treat" (G) when aiming at another player's pet and "Revive" when they stand next to an unconscious one. The vet uses his own healing kit or revival kit, and the owner is told who helped. recheckSec is how often the client asks the server whether the player still has a vet job.

config.lua: attacking, hunting and defending

Config.Attack = {
    enabled = true,
    players = true,
    reach   = 35.0,
    bite    = { range = 1.8, everyMs = 1500, damage = 20, animMs = 900 },
    key     = "RIGHT",
    stopKey = "R_FREE",
    select  = {
        radius = 15.0,
        view   = 0.35,
        keys   = { prev = "LEFT", next = "RIGHT", attack = "ENTER", cancel = "BACKSPACE" },
    },
    hunt = {
        kinds  = { dog = true, wolf = true, wolf_legend = true },
        damage = 40,
        prey   = { "a_c_alligator_01", "a_c_bear_01", "a_c_rabbit_01", --[[ ... ]] },
    },
}

Config.Defend = {
    enabled = true,
    bond    = 80,
    reach   = 35.0,
    players = true,
}

Only animals with attack = true in the catalog go after anyone. The player aims at a person and presses RIGHT, or he picks "Attack" at his pet (Q) and chooses a target in front of the camera with LEFT and RIGHT, ENTER to send it, BACKSPACE to cancel. The call off prompt uses stopKey. players = false restricts it to NPCs. A bite on a player does bite.damage every bite.everyMs, and the server checks range, bond and interval before any damage lands. hunt.kinds also go after the wild animals in prey and do hunt.damage to them, so a dog brings a rabbit or a bear to bay. They never attack another player's pet. Defend makes a pet with at least bond jump in when its owner is hit, whether by a player, an NPC or a wild animal.

config.lua: find the kill

Config.Fetch = {
    kinds     = { dog = true, wolf = true, wolf_legend = true },
    radius    = 100.0,
    sniffMs   = 3000,
    pointMs   = 5000,
    giveUpSec = 80,
    forgetSec = 300,
    carry     = { "a_c_rabbit_01", "a_c_squirrel_01", "a_c_duck_01", --[[ ... ]] },
}

"Find the kill" on the tricks page. The pet sniffs for the nearest dead animal within radius and leads the player to it. Models listed in carry (small game, fowl, snakes) are brought back in the mouth, bigger carcasses are pointed out with a bark and the player walks over. After giveUpSec without reaching it the pet comes back, and a carcass it has already found is skipped for forgetSec.

config.lua: riding and the shoulder

Config.Ride = {
    reach     = 6.0,
    kinds  = {
        dog = "big", fox = "big", coyote = "big",
        cat = "small", rabbit = "rabbit", armadillo = "armadillo", iguana = "iguana", --[[ ... ]]
    },
    places = {
        big   = { bone = nil, offset = { x = 0.12, y = -0.33, z = 0.79 }, rotation = { x = 0.0, y = 0.0, z = 95.0 } },
        small = { bone = nil, offset = { x = -0.06, y = -0.34, z = 0.74 }, rotation = { x = 0.0, y = 0.0, z = 265.0 } },
        -- ...
    },
    tune   = "petridepos",
}

Config.Shoulder = {
    key       = "DOWN",
    kinds  = { squirrel = "small", rabbit = "small", quail = "small", possum = "small", muskrat = "small",
               iguana = "small", skunk = "small", raccoon = "small" },
    places = {
        small = {
            male   = { bone = "CP_L_Shoulder", offset = { x = 0.02, y = 0.03, z = 0.05 } },
            female = { bone = "CP_L_Shoulder", offset = { x = 0.02, y = 0.03, z = 0.05 } },
            rotation = { x = 0.0, y = 0.0, z = 160.0 },
        },
    },
    tune   = "petshoulderpos",
}

Riding is started from Your Animals while mounted, the pet must stand within reach of the horse and the seat behind the saddle must be free. It jumps down when the player dismounts, or from the menu. kinds maps a kind to a place, places sets the position behind the saddle. The shoulder works the same way for very small animals: "Onto your shoulder" on the tricks page or in the menu, DOWN to set it down. A shouldered animal stays put when the player mounts up. Only one animal fits on the shoulder, and a bird perching there counts. Both tune commands adjust a place in the game while Config.Debug is on, see commands.

config.lua: trading, crate, notifications, blip and rate limit

Config.Market = { reach = 3.0, petReach = 8.0, leave = 3.0, maxPrice = 100000, key = "C" }
Config.Box = { item = "petbox" }
Config.Notify = { dict = "generic_textures", icon = "tick", icons = { warn = "cross" }, duration = 5000 }
Config.PetBlip = { enabled = true, icon = "blip_animal" }
Config.RateLimit = { windowMs = 3000, default = 6 }

Trading: the seller offers a pet that is out and within petReach of him, from the Trade side in Your Animals. A buyer standing within reach of the seller sees "Buy" (C) with name, kind and price. The offer ends when the seller walks leave meters from where he made it, logs out or the pet goes down. The sold pet goes to the buyer's home. maxPrice caps the asking price.

The crate: in Your Animals the owner holds "Put in a crate", the pet (which must be at home, awake and grown) goes into an empty petbox from his pack, and the crate gets the pet's name and kind as label and description. Whoever uses that crate owns the animal, as long as he has room for it.

Notifications go through vorp_core with the title texts from the locale. PetBlip puts a blip on every own pet that is out. RateLimit is how many requests of one kind a player may send in windowMs.

animals.lua

Config.Animals = {
    ['a_c_doghound_01'] = { group = 'dog', label = 'Bloodhound', kind = 'dog', price = 165, coats = { 0, 1, 2, 3, 4, 5, 6, 7 },
                            sits = true, flies = false, attack = true, breed = true, jobs = nil },
    ['a_c_cat_01']      = { group = 'cat', label = 'Cat', kind = 'cat', price = 150, coats = { 0, 1, 2, 3, 4, 5 },
                            sits = true, flies = false, attack = false, breed = true },
    ['a_c_bear_01']     = { group = 'predator', label = 'Grizzly Bear', kind = 'bear', price = 700, coats = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
                            sits = true, flies = false, attack = true, breed = true },
    -- ...
}

The catalog. Only models listed here can be bought or called, and the key is the model name in lower case.

Key Meaning
group Tab at the dealer, one of Config.Groups
label Name shown to players
kind Which poses, tricks, diet and life values it uses (config/poses.lua, Config.Kinds, config/diet.lua)
price Price in dollars
coats Outfit presets offered at purchase. Use the petcoats command to count them
sits Can sit on command
flies A flying bird, set up in config/birds.lua
attack Attacks on command and defends its owner
breed false turns breeding off for this animal
mate Breeds only with this model. Without it, all animals of the same kind breed together
jobs Only these jobs may buy it ({ jobname = minimum grade }), nil for anyone who may buy at the dealer
shop false takes it off the dealer's list. Owners keep theirs, and it can still have young

Fifty-seven animals are included, from a stray for 150 dollars to a legendary panther for 1400. The Lion Dog and the Walker Foxhound are not sold and do not breed because their only coat shows wounds. Every animal has a description in config/locales.lua (info_<model>) that the dealer shows. To keep bears for the animal keeper only, set jobs = { animalkeeper = 0 } on the two bear entries.

birds.lua

['a_c_hawk_01'] = bird({ label = 'Hawk', kind = 'eagle', role = 'hunt', hunts = { small = true, medium = true },
    variants = {
        [0] = { label = 'Ferruginous Hawk',  price = 299, image = 'animal_hawk_ferruginous' },
        [1] = { label = 'Rough-legged Hawk', price = 309, image = 'animal_hawk_roughlegged' },
        [2] = { label = 'Red-tailed Hawk',   price = 315, image = 'animal_hawk_redtailed' },
    } }),
['a_c_pelican_01'] = bird({ label = 'Pelican', kind = 'pelican', role = 'fish',
    fishes = { small = true, medium = true, salmon = true },
    variants = { --[[ ... ]] } }),
['a_c_robin_01'] = bird({ label = 'American Robin', kind = 'sparrow', price = 85, role = 'gather', finds = 'seeds' }),

Same fields as the animals, plus:

Key Meaning
variants Per coat a label, a price and an image (html/img/birds/<image>.png). The dealer shows the cheapest, the purchase charges the chosen one
role What it fetches on command: hunt game, carrion a dead animal, gather finds, fish fish, nil company only
hunts Raptors: prey classes it takes (small, medium, water, night)
night Hunts only at night
finds Gatherers: which list of finds (corvid, parrot, gull, seeds, bait)
fishes Fishers: which fish (small, medium, salmon, shore)

A bird always flies, does not sit on command and never attacks. Birds breed only with their own model, since several birds share one pose kind. Twenty-nine birds are included: songbirds, woodpeckers, pigeon, blue jay, crow, raven, gull, parrot, parakeet, turkey vulture, condor, hawk, horned owl, a legendary owl, eagle, booby, cormorant, crane, heron, pelican, spoonbill, egret and loon.

dealers.lua

Config.Dealers = {
    valentine = {
        label   = 'Valentine Animal Dealer',
        npc     = { model = 'a_m_m_valfarmer_01', coords = vector4(-279.95, 689.48, 113.42, 239.47), scenario = 'WORLD_HUMAN_SMOKE_NERVOUS_STRESSED' },
        reach   = 2.5,
        preview = vector4(-280.27, 680.81, 112.41, 330.73),
        buyers  = { animalkeeper = 0, Government = 0 },
        blip    = false,
    },
}

Config.DealerReturnShare = 0.10
Config.DealerKey = 'E'

Config.DealerView = { distance = 3.0, angle = 25.0, height = 1.1, lookAt = 0.45, fov = 38.0 }

One dealer in Valentine is included as an example, add your own or move it. Each dealer has a man (npc), a spot where the chosen animal stands on display (preview), a reach and a list of buyers. Anyone may look, only buyers see prices and buy, and buyers = nil lets everyone buy. blip is a blip sprite name, false for none. view on a dealer overrides Config.DealerView for that one spot. The camera only moves once the player picks an animal and stands on the line from the display spot toward the dealer.

DealerReturnShare is what the dealer pays back when an animal is returned: ten percent of its catalog price. Only animals at home that are awake, not carrying young and grown can be returned, and anyone may return his own animal, buyer or not.

Tip

If you want a free market, set buyers = nil on the dealer and give the animal keeper only the higher limits and the breeding.

diet.lua

Config.Diet = {
    value = { best = 60, prey = 60, favorite = 40, natural = 40, any = 20 },
    bond  = { best = 2, prey = 2, favorite = 2 },
    any   = 'petfood',

    groups = {
        canine    = { best = 'dogfood', favorite = { 'offal' }, natural = { 'Gamey_Meat', 'musclemeat', 'meat', 'bird_meat' } },
        feline    = { best = 'catfood', natural = join(FISH, { 'meat', 'bird_meat' }) },
        raptor    = { prey = { 'meat', 'bird_meat', 'Gamey_Meat' } },
        grain     = { best = 'birdseed', natural = GRAIN },
        small     = { best = 'smallfood', favorite = { 'rabbit_food' }, natural = { 'carrot', 'Wild_Carrot', 'corn' } },
        -- ...
    },

    kinds = {
        dog = 'canine', wolf = 'canine', cat = 'feline', bear = 'bear', eagle = 'raptor', owl = 'raptor',
        heron = 'fisher', sparrow = 'grain', parrot = 'fruit', rabbit = 'small', cow = 'livestock', pig = 'pig',
        -- ...
    },
}

Every kind belongs to a diet group. "Feed" takes the first matching item the player carries, in this order: the group's own food (best), its main prey (prey, for fishers, raptors and scavengers), its favorite natural food, what it eats in nature, and last the plain petfood that every animal takes. value is how much each tier fills, bond what it adds to the trust. Anything not listed is refused and stays in the bag.

Only the group foods and petfood are registered as usable items. Raw meat, fish and grain are usually usable items of other scripts, so animals eat those only through "Feed" at the animal or in Your Animals. Those item names (offal, Gamey_Meat, carrot, the fish and so on) are examples from one item database. Replace them with the names from yours, missing ones are listed in the console with Config.Debug on. The text players read about a group is diet_<group> in the locale, so change both together.

falconry.lua

Config.Falconry = {
    circle = { height = 8.0, radius = 3.1, speed = 4.1, retaskMs = 2000 },
    hover  = { side = 0.35, forward = 0.6, height = 1.4, speed = 6.0, teleport = 60.0 },
    land   = { landFrom = 2.0, landTimeoutMs = 20000 },
    call     = { distance = 40.0, height = 25.0 },
    huntBack = { distance = 70.0, height = 40.0 },

    keys = {
        home   = 'ENTER',       -- hold to send home
        hunt   = 'BACKSPACE',   -- on the shoulder: hunt, gather or fish
        perch  = 'BACKSPACE',   -- in the air and on the ground: onto the shoulder
        fly    = 'UP',          -- on the shoulder and on the ground: take off
        camera = 'UP',          -- in the air: bird's view
        ground = 'LEFT',        -- land ahead of the player and walk along
        flight = 'RIGHT',       -- switch between circling and hovering
        talk   = 'Q_FREE',      -- parrot and parakeet: speak
        hide   = 'ENTER',       -- short press: show or hide the group on the shoulder
    },
    homeHoldMs = 1000,

    hunt = {
        goSec = 15,
        time = {
            gather  = { 600, 900 },
            carrion = { 600, 900 },
            fish    = { 600, 1200 },
            hunt    = { 900, 1500 },
        },
        jobs = nil, bonusJobs = { birdtrainer = 0, wapiti = 0 }, jobFactor = 2,
    },
    night = { from = 20, to = 5 },

    training = {
        max   = 1000,
        gain  = { gather = 8, carrion = 8, fish = 10, hunt = 13 },
        words = { { from = 1000, key = 'train_master' }, { from = 600, key = 'train_good' }, --[[ ... ]] },
    },

    talk = { from = 83, range = 10.0, models = { a_c_parrot_01 = true, a_c_carolinaparakeet_01 = true } },

    tune      = 'petperchpos',
    perchTest = 'petperchtest',
    attach = { default = { male = { index = 296, offset = { x = 0.15, y = 0.01, z = 0.12 } }, --[[ ... ]] } },

    prey  = { ['a_c_rabbit_01'] = { class = 'medium', from = 200 }, --[[ ... ]] },
    finds = { corvid = { { name = 'feather', amount = 1, from = 0 }, --[[ ... ]] }, --[[ ... ]] },
    fish  = { { model = 'a_c_fishbluegil_01_sm', class = 'small', from = 0 }, --[[ ... ]] },
}

A bird is not aimed at like the other animals. Its commands sit in a prompt group of their own that shows the bird's name and only the actions possible where the bird is: on the shoulder, in the air or on the ground. The same actions are in Your Animals.

  • Flying: circle makes the bird circle above the player, hover makes it fly close beside him. The player switches with RIGHT and the bird remembers the choice. land is how it comes down to the shoulder, call and huntBack where a called bird and a returning bird appear.
  • Hunting: with the bird on the shoulder, BACKSPACE sends it off. goSec later it flies away and stays gone for the time of its role, in seconds. A raptor drops its prey dead at the player's feet, ready to skin, a scavenger does the same with carrion, a fisher brings a fish, a gatherer puts a find into the inventory. jobs limits who may send a bird out, nil lets everyone. A player with one of the bonusJobs trains his bird jobFactor times as fast. The owl hunts only between night.from and night.to game hours.
  • Training: every catch adds the gain of its role, up to max. With the defaults a bird is fully trained after about 25 hours away. Prey, fish and finds each have a from value, so a trained bird brings better things.
  • Speaking: parrot and parakeet speak a line from config/falconry_voices.lua once their training reaches talk.from, and everyone within range hears it.
  • Prey: which animals a raptor or scavenger can catch, with class and training needed. Which animals live where comes from config/falconry_areas.lua. Animals not listed here are never hunted.
  • Finds: one list per gatherer kind with item name, amount and training needed. Every item must exist in your item database, and itemLabels is how each one is named in the notification. The defaults are examples from one item database (feathers, bait, berries, seeds, eggs, odd coins), replace them with your own. Keep finds cheap, the bird is away for ten minutes per find.
  • Fish: the fish and shore prey a fisher takes, by class and training.
  • Perch position: where each bird sits on the shoulder, per model and player sex. tune and perchTest are debug commands for adjusting.

falconry_areas.lua

Config.FalconryAreas.areas = {
    {"W_4_ABANDONED_MISSION", 0xD1113443, -787401661, true,
    {
        [1] = { model = `a_c_rat_01`, preset = 0 },
        [2] = { model = `a_c_woodpecker_01`, preset = 0 },
        [5] = { model = `a_c_rabbit_01`, preset = 0 },
        -- ...
    },
    "Abandoned Mission"},
    -- ...
}

The hunting grounds: for every map zone the animals a raptor or scavenger can find there. Each entry is the zone name, its hash, the signed hash, whether hunting is allowed there, the prey list (model and outfit preset) and the area name shown in texts ("%s goes hunting in Abandoned Mission"). Zones are checked in three lists, areas first, then districts, then districts2. The file covers the whole map and is long, you normally leave it alone. To close a zone for hunting, set its fourth value to false.

falconry_voices.lua

Config.FalconryVoices = {
    [1] = {
        ["Name"] = "ARTHUR",
        Variants = { [1] = "AMBUSH_BRP_V1_ILO_NEG", [2] = "AMBUSH_BRP_V1_ILO_POS", --[[ ... ]] },
    },
    -- ...
}

The lines a parrot or parakeet can repeat: game speech banks and their lines. The server picks a bank and a line at random and only plays what is listed here. Remove a bank if you do not want its voice.

poses.lua

Config.Poses = {
    dog = {
        bite  = { 'creatures_mammal@dog_pers@melee@streamed_core', 'attack' },
        eat   = { 'amb_creature_mammal@world_dog_eating_ground@base', 'base' },
        drink = { 'amb_creature_mammal@prop_dog_drink_bowl@base', 'base' },
        sit   = { 'amb_creature_mammal@world_dog_sitting@base', 'base' },
        lie   = { 'amb_creature_mammal@world_dog_resting@base', 'base' },
        sleep = { 'amb_creature_mammal@world_dog_sleeping@base', 'base' },
        beg   = { 'amb_creature_mammal@world_dog_begging@idle', 'idle_b' },
        sniff = { 'amb_creature_mammal@world_dog_sniffing_ground@idle', 'idle_a' },
        point = { 'amb_creature_mammal@world_dog_barking_ground@idle', 'idle_a' },
        tricks = {
            { key = 'jump', label = 'Jump', anim = { 'script_re@lost_dog@dog@squirrel', 'jump_01_dog' } },
            { key = 'bark', label = 'Bark', anims = { --[[ several, one picked at random ]] } },
            { key = 'dig',  label = 'Dig',  anims = { --[[ ... ]] } },
            { key = 'howl', label = 'Howl', anims = { --[[ ... ]] } },
            { key = 'roll', label = 'Roll over', anims = { --[[ ... ]] } },
        },
    },
    -- ...
}

What each kind does on command, every animation as dictionary and clip.

Key Meaning
eat, drink Played when feeding and watering
sit, lie, sleep The three poses. Leave out what the kind cannot do, the command is then grayed out
perch How a bird sits, instead of sit
beg The reaction to petting
bite The bite when it attacks a player. Without it, the animal never goes after a player
sniff, point Find the kill: it sniffs, and at the find it points. Every kind in Config.Fetch.kinds needs both
still The idle of its model, for kinds without sit and lie, so it holds still during care
tricks The tricks in order, at most five, each with a key, a label and anim or anims

Forty-three kinds are covered. A kind with more than five tricks prints a warning and only the first five are shown.

locales.lua

Locales.en = {
    notify_title_pet  = "Your Animal",
    notify_coming     = "%s heard you and is coming.",
    act_follow        = "Heel",
    -- ...
}

Every text in the script, English and German: notifications, prompts, the menu, the dealer, the error messages, the diet texts, the animal descriptions, the Discord log titles, and the Animal Primer, a guide inside Your Animals where the animal keeper explains keeping, care, trust, breeding and birds to new players. Add a language by copying a block and setting Config.Locale to its key.

server_config.lua

Config.Webhook = {
    url  = "",
    name = "Pets",
    events = {
        buy      = true,
        giveBack = true,
        trade    = true,
        box      = true,
        breed    = true,
        passed   = true,
        vet      = true,
    },
}

This file is only read by the server and never sent to players. Put a Discord webhook URL into url to log purchases, returns, trades between players, crates, mating and births, deaths and vet treatments, each with player name, character id and the pet's name and number. Leave url empty to turn the log off, or switch single events off.

Items

Item What it does
petfood Pet Food. Plain food any pet takes, fills 20
dogfood Dog Food for dogs, foxes, coyotes and wolves, fills 60
catfood Cat Food for cats, fills 60
wildfood Wild Animal Food for big cats, bears and alligators, fills 60
birdseed Bird Seed for songbirds, pigeons, parrots and fowl, fills 60
smallfood Small Animal Food for rabbits, squirrels, beavers and other small animals, fills 60
petwater Pet Water, restores 50 thirst. A plain water item works too
pet_soap Animal Soap, restores 50 cleanliness and wears by 10 percent per bath
petheal Animal Medicine, restores 50 health and takes 10 off the sickness. Vets use it on other people's pets
petrevive Animal Revival Kit, brings an unconscious pet back with full health
petbox Pet Crate. Holds one pet that is at home, whoever opens it owns the animal
pet_keepsake Keepsake. Given to the owner when a pet passes away, with the pet's name on it

The six foods, the water, the medicine, the revival kit and the crate are usable from the inventory. Used from the bag, food, water and medicine go to the nearest own pet the player is looking at, the revival kit to the nearest unconscious one. Meat, fish, grain, fruit and the other natural foods come from your own item database (see diet.lua) and are optional. Their images are in install/.

Commands and keys

Command What it does
/pets Opens Your Animals (same as the key)
/petcoats <model> Debug: shows a model and steps through its coats, also row <model>, list [group] and clear
/petridepos, /petshoulderpos, /petperchpos Debug: adjust a riding, shoulder or perch place in the game (x 0.05, x+, rx 10, bone NAME, show, reset)
/petperchtest Debug: puts every bird without its own perch entry on the shoulder in turn

The debug commands only exist while Config.Debug is on.

Everywhere:

Key Action
PAGEUP Your Animals (rebindable in the game settings)
E At the dealer: look at the animals
C Buy the pet a player next to you offers, or withdraw your own offer

Aiming at your own pet, first page:

Key Action
G Pet
C Feed
V Water
F Bathe
R Treat
E Heel or Stay
Q Attack (opens the target choice)
DOWN (hold) Send home
RIGHT Tricks page
LEFT Your Animals

Tricks page: G, C, V, R, E, DOWN, RIGHT, LEFT and Q in that order for sit, lie down, sleep, onto your shoulder, the tricks and find the kill, as far as the kind has them. F goes back.

Aiming at a person or a wild animal:

Key Action
RIGHT Sic 'em (the nearest pet that may attack goes after the target)
R_FREE Call off, while a pet attacks (a control of its own, listed in client/keys.lua)

Target choice after Q at the pet: LEFT and RIGHT pick a target in front of the camera, ENTER sends the pet, BACKSPACE cancels.

Next to an unconscious pet:

Key Action
G Revive (own pet with your kit, or another player's pet as vet)
DOWN (hold) Have it carried home

Vet aiming at another player's pet: G treats it.

Shoulder: DOWN sets a shouldered animal down.

Bird group (shows while a bird is with you):

Key On the shoulder In the air On the ground
ENTER (hold) Send home Send home Send home
ENTER (short) Show or hide the group
BACKSPACE Hunt, gather, seek carrion or fish Onto your shoulder Onto your shoulder
UP Take off Bird's view, back to you Take off
LEFT Land ahead of you Land ahead of you
RIGHT Circle above you or fly beside you same same
Q_FREE Speak (parrot, parakeet)

Every key here can be changed in config/config.lua and config/falconry.lua.

FAQ

The prompts do not show on my pet. You have to look at it with the game's targeting or aim at it, and be on foot. Prompts are hidden while you are mounted, while a menu is open, while the pet is on your shoulder, heading home or unconscious. Birds never get the aimed prompts, they have the bird group instead.

The dealer shows no prices. The player has none of the jobs in the dealer's buyers. Anyone may look at the animals, only buyers see prices and buy. Set buyers = nil to sell to everyone. A single animal can carry its own jobs on top.

How do I add a second dealer? Copy the valentine block in config/dealers.lua under a new key, set the NPC position and heading, the preview spot where the animal stands on display, and the reach. The included dealer is an example you replace.

Where does the money go? Everything is cash. Purchases and breeding fees are taken from the player and vanish. A return pays ten percent of the catalog price back. In a trade the buyer's cash goes straight to the seller, nothing is taken off. There is no way to earn money from the script itself, only from other players.

My players' pets starve every evening. Food and water drop by a third per hour while the pet is out, so three hours out means an empty belly. Lower Config.Metabolism.hungerPerHour and thirstPerHour, or tell players to send the pet home when they do not need it. At home nothing drops.

Can a pet die for good? Only of old age, after the ten to twelve real weeks in Config.Life.weeks. Damage and neglect knock it unconscious, never kill it. If it lies unconscious with nobody reviving it, it is carried home after ten minutes and waits there. A pet that passes away is deleted from the table and its owner gets the keepsake.

What happens on a restart? On stop every pet in the world is removed. On start every pet that was still marked as out is sent home with its values saved. Players who log out get their pets sent home as well. Nothing is lost, and a pet always comes back from home when called.

How do I give a pet to another player? Three ways. Offer it in Your Animals and have the other player stand next to you and press C. Put it into a crate and hand the crate over, whoever opens it owns the animal. Or let a player with a buyer job buy at the dealer. In every case the new owner needs a free place.

The bird finds nothing to hunt. Raptors and scavengers only hunt in zones from config/falconry_areas.lua, and each prey needs the bird's training from Config.Falconry.prey. An untrained hawk only catches rats, squirrels and the like. The owl hunts only at night. Gatherers and fishers work everywhere.

A find or a food item does not exist on my server. Finds in Config.Falconry.finds and the natural foods in config/diet.lua are item names from one item database and are optional. With Config.Debug on the server lists on start which of them your items table does not have. Replace them with your own item names.

Does the script hurt performance? Pets are peds owned by the player's client and follow with the game's own tasks. The server ticks every pet that is out once a minute and writes to the database every five minutes per pet. Prompts only run while a player is aiming at a pet. The hunting areas file is large but is only read on start.

Players spam the server. Every request kind is limited to six per three seconds and player (Config.RateLimit), and everything that moves money or a pet runs under a lock, so two players cannot buy the same offer or open the same crate twice.

Changelog

1.0.0

First release.