diff --git a/_datafiles/guides/building/scripting/FUNCTIONS_ACTORS.md b/_datafiles/guides/building/scripting/FUNCTIONS_ACTORS.md
index 45568d5cc..3124407ef 100644
--- a/_datafiles/guides/building/scripting/FUNCTIONS_ACTORS.md
+++ b/_datafiles/guides/building/scripting/FUNCTIONS_ACTORS.md
@@ -571,7 +571,19 @@ Returns the shorthand ID string to refer to the mob or player ( `@123` or `#122`
Uncurses any objects the target has equipped
## [ActorObject.GetPet()](/internal/scripting/actor_func.go)
-Returns the pet object for the actor, or null
+Returns the [PetObject](FUNCTIONS_PETS.md) for this actor, or `null` if the actor has no pet.
+
+_Note: Only meaningful for user actors. Mob actors always return `null`._
+
+_Note: The returned PetObject is a live reference. Mutations such as `SetName()`, `Feed()`, and `Starve()` take effect immediately on the owner's character data._
+
+**Example:**
+```javascript
+var pet = actor.GetPet();
+if (pet !== null) {
+ room.SendText(pet.Name() + ' is here!');
+}
+```
## [ActorObject.GrantXP(xpAmt int, reason string)](/internal/scripting/actor_func.go)
Gives experience points to the actor
diff --git a/_datafiles/guides/building/scripting/FUNCTIONS_PETS.md b/_datafiles/guides/building/scripting/FUNCTIONS_PETS.md
new file mode 100644
index 000000000..3b87d3fcc
--- /dev/null
+++ b/_datafiles/guides/building/scripting/FUNCTIONS_PETS.md
@@ -0,0 +1,202 @@
+# PetObject
+
+PetObject represents a pet owned by a player. It is obtained by calling
+[ActorObject.GetPet()](FUNCTIONS_ACTORS.md#actorobjectgetpet) and returns
+`null` when the actor has no pet.
+
+The object is a **live reference** into the owner's character data. Mutations
+take effect immediately and are persisted the next time the character is saved.
+
+- [PetObject](#petobject)
+ - [PetObject.Type() string](#petobjecttype-string)
+ - [PetObject.Name() string](#petobjectname-string)
+ - [PetObject.NameSimple() string](#petobjectnamesimple-string)
+ - [PetObject.SetName(name string)](#petobjectsetnamename-string)
+ - [PetObject.Level() int](#petobjectlevel-int)
+ - [PetObject.Food() string](#petobjectfood-string)
+ - [PetObject.FoodLevel() int](#petobjectfoodlevel-int)
+ - [PetObject.Feed()](#petobjectfeed)
+ - [PetObject.Starve()](#petobjectstarve)
+ - [PetObject.GetStatMod(statName string) int](#petobjectgetstatmodstatname-string-int)
+ - [PetObject.GetCapacity() int](#petobjectgetcapacity-int)
+ - [PetObject.ItemCount() int](#petobjectitemcount-int)
+ - [PetObject.HasScript() bool](#petobjecthasscript-bool)
+
+---
+
+## [PetObject.Type() string](/internal/scripting/pet_func.go)
+Returns the pet's type identifier, such as `"dog"`, `"cat"`, `"owl"`, or `"mule"`.
+
+**Example:**
+```javascript
+var pet = actor.GetPet();
+if (pet !== null && pet.Type() === 'dog') {
+ room.SendText(pet.NameSimple() + ' barks!');
+}
+```
+
+---
+
+## [PetObject.Name() string](/internal/scripting/pet_func.go)
+Returns the pet's full display name, including ANSI colour tags and any hunger
+indicator such as `(Hungry)` or `(Starving)`.
+
+Use this when sending the name to players as part of room or character output.
+Use [NameSimple()](#petobjectnamesimple-string) when you need a plain string
+for comparisons or concatenation.
+
+---
+
+## [PetObject.NameSimple() string](/internal/scripting/pet_func.go)
+Returns the plain text name of the pet with no colour tags or hunger indicator.
+Falls back to the type identifier if the player has not given the pet a custom
+name.
+
+**Example:**
+```javascript
+var pet = actor.GetPet();
+if (pet !== null) {
+ room.SendText(pet.NameSimple() + ' trots along behind you.');
+}
+```
+
+---
+
+## [PetObject.SetName(name string)](/internal/scripting/pet_func.go)
+Renames the pet. The change is reflected immediately in `Name()` and
+`NameSimple()`.
+
+Pass an empty string to clear the custom name and revert the display name to
+the pet's type identifier.
+
+| Argument | Explanation |
+| --- | --- |
+| name | The new plain text name, or `""` to clear it. |
+
+**Example:**
+```javascript
+var pet = actor.GetPet();
+if (pet !== null && pet.NameSimple() === pet.Type()) {
+ pet.SetName('Biscuit');
+ actor.SendText('You name your pet Biscuit.');
+}
+```
+
+---
+
+## [PetObject.Level() int](/internal/scripting/pet_func.go)
+Returns the pet's current level, from 1 (minimum) to 10 (maximum).
+
+Pet level increases when the pet is well-fed at the daily tick and decreases
+when the pet is starving.
+
+---
+
+## [PetObject.Food() string](/internal/scripting/pet_func.go)
+Returns the pet's current hunger state as a human-readable string.
+
+| Value | Meaning |
+| --- | --- |
+| `"Starving"` | Food level 0 — pet will lose a level at the next daily tick |
+| `"Hungry"` | Food level 1 |
+| `"Satisfied"` | Food level 2 |
+| `"Full"` | Food level 3 — pet will gain a level at the next daily tick |
+
+**Example:**
+```javascript
+var pet = actor.GetPet();
+if (pet !== null && pet.Food() === 'Starving') {
+ actor.SendText(pet.NameSimple() + ' looks at you with desperate eyes.');
+}
+```
+
+---
+
+## [PetObject.FoodLevel() int](/internal/scripting/pet_func.go)
+Returns the pet's raw hunger value: `0` (Starving) through `3` (Full).
+
+Useful when you need a numeric comparison rather than a string.
+
+**Example:**
+```javascript
+var pet = actor.GetPet();
+if (pet !== null && pet.FoodLevel() < 2) {
+ actor.SendText(pet.NameSimple() + ' nudges you hopefully.');
+}
+```
+
+---
+
+## [PetObject.Feed()](/internal/scripting/pet_func.go)
+Increases the pet's hunger level by one step, up to a maximum of 3 (Full).
+Has no effect if the pet is already full.
+
+_Note: This does not consume any item from the player's inventory. Use it when
+a script wants to feed the pet as a side-effect of some other action._
+
+---
+
+## [PetObject.Starve()](/internal/scripting/pet_func.go)
+Decreases the pet's hunger level by one step, down to a minimum of 0
+(Starving). Has no effect if the pet is already starving.
+
+---
+
+## [PetObject.GetStatMod(statName string) int](/internal/scripting/pet_func.go)
+Returns the stat modifier the pet currently grants its owner for the named
+stat. Returns `0` if the pet provides no modifier for that stat at its current
+level.
+
+Stat modifiers are defined per ability level in the pet's YAML definition and
+scale up as the pet levels.
+
+| Argument | Explanation |
+| --- | --- |
+| statName | A stat name such as `"strength"`, `"speed"`, `"smarts"`, `"vitality"`, `"mysticism"`, or `"perception"`. |
+
+**Example:**
+```javascript
+var pet = actor.GetPet();
+if (pet !== null) {
+ var speedBonus = pet.GetStatMod('speed');
+ if (speedBonus > 0) {
+ actor.SendText(pet.NameSimple() + ' makes you feel quicker. (+' + speedBonus + ' speed)');
+ }
+}
+```
+
+---
+
+## [PetObject.GetCapacity() int](/internal/scripting/pet_func.go)
+Returns the number of items the pet can carry at its current level. Returns `0`
+if the pet type has no carry ability (e.g. a cat or owl).
+
+**Example:**
+```javascript
+var pet = actor.GetPet();
+if (pet !== null && pet.GetCapacity() > 0) {
+ actor.SendText(pet.NameSimple() + ' can carry ' + pet.GetCapacity() + ' item(s).');
+}
+```
+
+---
+
+## [PetObject.ItemCount() int](/internal/scripting/pet_func.go)
+Returns the number of items the pet is currently carrying.
+
+**Example:**
+```javascript
+var pet = actor.GetPet();
+if (pet !== null) {
+ var free = pet.GetCapacity() - pet.ItemCount();
+ actor.SendText(pet.NameSimple() + ' has ' + free + ' free slot(s).');
+}
+```
+
+---
+
+## [PetObject.HasScript() bool](/internal/scripting/pet_func.go)
+Returns `true` if this pet type has a script file on disk.
+
+Useful in generic scripts that want to check whether a pet will respond to
+events before attempting to trigger them.
diff --git a/_datafiles/guides/building/scripting/README.md b/_datafiles/guides/building/scripting/README.md
index 7acda9267..18e5a0a02 100644
--- a/_datafiles/guides/building/scripting/README.md
+++ b/_datafiles/guides/building/scripting/README.md
@@ -14,6 +14,9 @@ See [Item Scripting](SCRIPTING_ITEMS.md)
# Buff Scripting
See [Buff Scripting](SCRIPTING_BUFFS.md)
+# Pet Scripting
+See [Pet Scripting](SCRIPTING_PETS.md)
+
# Spell Scripting
See [Spell Scripting](SCRIPTING_SPELLS.md)
@@ -27,6 +30,8 @@ See [Spell Scripting](SCRIPTING_SPELLS.md)
[ItemObject Functions](FUNCTIONS_ITEMS.md) - Functions that query or alter item data.
+[PetObject Functions](FUNCTIONS_PETS.md) - Functions that query or alter pet data.
+
[Utility Functions](FUNCTIONS_UTIL.md) - Helper and info functions.
[Messaging Functions](FUNCTIONS_MESSAGING.md) - Helper and info functions.
diff --git a/_datafiles/guides/building/scripting/SCRIPTING_PETS.md b/_datafiles/guides/building/scripting/SCRIPTING_PETS.md
new file mode 100644
index 000000000..a880250d2
--- /dev/null
+++ b/_datafiles/guides/building/scripting/SCRIPTING_PETS.md
@@ -0,0 +1,135 @@
+# Pet Scripting
+
+Example scripts:
+* [Dog pet script](/_datafiles/world/default/pets/scripts/dog.js)
+* [Cat pet script](/_datafiles/world/default/pets/scripts/cat.js)
+* [Owl pet script](/_datafiles/world/default/pets/scripts/owl.js)
+* [Mule pet script](/_datafiles/world/default/pets/scripts/mule.js)
+
+## Script paths
+
+Pet scripts reside in the same directory as the pet's YAML definition file,
+with `.js` replacing `.yaml` in the filename.
+
+For example, the `dog` pet type defined at:
+
+```
+_datafiles/world/default/pets/dog.yaml
+```
+
+loads its script from:
+
+```
+_datafiles/world/default/pets/dog.js
+```
+
+## Script scope
+
+Variables defined at the global scope of a pet script are shared across all
+owners of that pet type. If you need to store data specific to a single owner,
+use the owner's [SetTempData / GetTempData](FUNCTIONS_ACTORS.md#actorobjectsettempdatakey-string-value-any)
+or [SetMiscCharacterData / GetMiscCharacterData](FUNCTIONS_ACTORS.md#actorobjectsetmisccharacterdatakey-string-value-any).
+
+## Script functions
+
+The following functions are invoked automatically when defined in a pet script:
+
+---
+
+```
+function PetAct(pet PetObject, actor ActorObject, room RoomObject) {
+}
+```
+
+`PetAct()` is called each round with a probability determined by the pet
+type's `RoundActChance` property (0–100). If `RoundActChance` is 0 the
+function is never called. If it is 100 it is called every round.
+
+`PetAct` is **not** called while the pet's owner is in combat.
+
+The chance is evaluated by the engine before the function is invoked, so
+`PetAct` itself does not need a top-level probability check. Add your own
+`RandInt` guard inside the function only if you want behaviour that fires
+less often than the configured chance.
+
+There is no return value.
+
+| Argument | Explanation |
+| --- | --- |
+| pet | [PetObject](FUNCTIONS_PETS.md) — the pet. |
+| actor | [ActorObject](FUNCTIONS_ACTORS.md) — the player who owns the pet. |
+| room | [RoomObject](FUNCTIONS_ROOMS.md) — the room both are in. |
+
+**Example:**
+```javascript
+function PetAct(pet, actor, room) {
+ // ~5% chance per round to do something visible
+ if (RandInt(1, 100) <= 5) {
+ room.SendText(pet.NameSimple() + ' sniffs the air curiously.');
+ }
+}
+```
+
+---
+
+```
+function onCommand(cmd string, rest string, pet PetObject, actor ActorObject, room RoomObject) {
+}
+```
+
+`onCommand()` is called whenever the pet's owner types any command.
+
+Returning `true` halts further command processing (the command is consumed).
+Returning `false` allows the command to continue through the normal pipeline.
+
+This fires after buff `onCommand` handlers and before item `onCommand`
+handlers.
+
+| Argument | Explanation |
+| --- | --- |
+| cmd | The command word typed by the owner, such as `"look"` or `"attack"`. |
+| rest | Everything entered after the command word (may be empty). |
+| pet | [PetObject](FUNCTIONS_PETS.md) |
+| actor | [ActorObject](FUNCTIONS_ACTORS.md) — the owner. |
+| room | [RoomObject](FUNCTIONS_ROOMS.md) |
+
+**Example:**
+```javascript
+function onCommand(cmd, rest, pet, actor, room) {
+ if (cmd === 'attack') {
+ if (RandInt(1, 4) === 1) {
+ room.SendText(pet.NameSimple() + ' growls menacingly.');
+ }
+ }
+ return false;
+}
+```
+
+---
+
+```
+function onCommand_{command}(rest string, pet PetObject, actor ActorObject, room RoomObject) {
+}
+```
+
+`onCommand_{command}()` is called when the owner types the specific command
+named after the underscore.
+
+If a specific handler is defined, the generic `onCommand()` will **not** fire
+for that command.
+
+| Argument | Explanation |
+| --- | --- |
+| rest | Everything entered after the command word (may be empty). |
+| pet | [PetObject](FUNCTIONS_PETS.md) |
+| actor | [ActorObject](FUNCTIONS_ACTORS.md) — the owner. |
+| room | [RoomObject](FUNCTIONS_ROOMS.md) |
+
+**Example:**
+```javascript
+// Called only when the owner types 'pet' (the pet command)
+function onCommand_pet(rest, pet, actor, room) {
+ room.SendText(pet.NameSimple() + ' wags its tail happily.');
+ return false;
+}
+```
diff --git a/_datafiles/html/admin/pets-api.html b/_datafiles/html/admin/pets-api.html
index a2662d898..2bf2137dc 100644
--- a/_datafiles/html/admin/pets-api.html
+++ b/_datafiles/html/admin/pets-api.html
@@ -14,6 +14,7 @@
.method-get { background: var(--color-success-bg); color: var(--color-success-text); }
.method-post { background: var(--color-info-bg); color: var(--color-info-text); }
.method-patch { background: var(--color-warning-bg); color: var(--color-warning-text); }
+ .method-put { background: var(--color-warning-bg); color: var(--color-warning-text); }
.method-delete { background: var(--color-error-bg); color: var(--color-error-text); }
.api-path { font-family: monospace; font-size: 0.9rem; color: var(--color-primary); }
.api-desc { font-size: 0.85rem; color: var(--color-text-muted); margin-left: auto; text-align: right; }
@@ -42,13 +43,15 @@
.params-table td { padding: 0.4rem 0.6rem; border: 1px solid var(--color-border); vertical-align: top; }
.params-table td:first-child { font-family: monospace; color: var(--color-primary); }
.required { color: var(--color-error-text); font-size: 0.75rem; font-weight: 600; }
+ .section-label { font-size: 0.78rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--color-text-subtle); margin: 1rem 0 0.4rem; }
Pets API Reference
-
REST endpoints for listing, creating, updating, and deleting pet type definitions. View / Edit
+
REST endpoints for listing, creating, updating, deleting, and scripting pet type definitions. View / Edit
Creates a new pet type definition. The type field is the unique identifier and is lowercased automatically. Returns 409 if the type already exists.
+
Creates a new pet type definition. The type field is the unique identifier and is lowercased automatically. Returns 400 if the type already exists.
Field
Type
Description
@@ -103,7 +107,17 @@
Pets API Reference
type required
string
Unique pet type identifier (e.g. wolf). Lowercased on save.
name
string
Display name shown when the player has not named their pet.
namestyle
string
Optional color pattern applied to the pet name (e.g. :rainbow).
-
abilities
object[]
Array of ability definitions. Each ability has levelgranted, combatchance, damage, attackmessages, statmods, buffids, and capacity. attackmessages is only meaningful when damage is set; fields: toowner, totarget, toroom, miss. When damage is set, combatchance is enforced to be at least 1.
+
roundactchance
int
0–100 chance per round that PetAct() is invoked. Defaults to 0 (disabled). Not called while the owner is in combat.
+
abilities
object[]
+ Array of ability objects. Each entry may contain:
+ • levelgranted (int) — pet level at which this ability unlocks (1–10)
+ • combatchance (int, 0–100) — percent chance per combat round the pet attacks
+ • damage ({diceroll: string}) — damage dice roll (e.g. "1d6+2")
+ • attackmessages ({toowner, totarget, toroom, miss}) — optional custom combat messages; tokens: {petname}, {damage}, {targetname}
+ • statmods (object) — stat bonuses granted to the owner (e.g. {"strength": 2})
+ • buffids (int[]) — buff IDs permanently applied to the owner
+ • capacity (int) — number of items the pet can carry
+
- 400Conflict - type already exists
+ 400Bad Request — type already exists
{"success":false,"error":"pet type \"wolf\" already exists"}
+
PATCH
@@ -143,7 +159,7 @@
Pets API Reference
Update a pet type
-
{petname} is the pet type identifier (e.g. wolf). Fields in the request body overwrite the existing definition. The type is always preserved from the URL.
+
{petname} is the pet type identifier (e.g. wolf). The request body is merged over the existing definition. The type is always preserved from the URL.
Parameter
In
Description
@@ -152,29 +168,27 @@
Pets API Reference
+
Request body accepts the same fields as POST: name, namestyle, roundactchance, abilities. The type field in the body is ignored; the URL value is always used.
{"success":false,"error":"pet type \"wolf\" not found"}
+
{"success":false,"error":"pet type not found: wolf"}
+
DELETE
@@ -182,7 +196,7 @@
Pets API Reference
Delete a pet type
-
{petname} is the pet type identifier. Removes the YAML file from disk and unregisters the type from memory.
+
{petname} is the pet type identifier. Removes the YAML definition and any associated .js script file from disk, and unregisters the type from memory.
Parameter
In
Description
@@ -207,6 +221,86 @@
Pets API Reference
+
+
+
+ GET
+ /admin/api/v1/pets/{petname}/script
+ Retrieve the script for a pet type
+
+
+
Returns the JavaScript source for the pet type's script file. The script lives alongside the pet's YAML definition (e.g. pets/wolf.js). Returns an empty string if no script file exists on disk.
{"success":false,"error":"pet type not found: wolf"}
+
+
+
+
+
+
+
+
+ PUT
+ /admin/api/v1/pets/{petname}/script
+ Save or delete the script for a pet type
+
+
+
Writes the provided JavaScript to the pet's script file alongside its YAML definition (e.g. pets/wolf.js). Send an empty string to delete the script file. The in-memory VM cache for this pet type is invalidated automatically so the new script is used on the next invocation.
+
+
+
Parameter / Field
In
Description
+
+
petname required
path
Pet type identifier.
+
script required
body (JSON)
JavaScript source string. Pass "" to remove the script file.
+
+
+
+
curl-s-uadmin:password-X PUT \
+ -H"Content-Type: application/json" \
+ -d'{
+ "script": "function PetAct(pet, actor, room) {\n room.SendText(pet.NameSimple() + \" howls at the moon.\");\n}\n"
+}' \
+ http://{{.CONFIG.FilePaths.WebDomain}}/admin/api/v1/pets/wolf/script
Leave blank to use default. Prefix with : for a color pattern.
+
+
+
+
+ 0%
+
+
Chance per round (0–100%) that the pet’s PetAct() script function is called. Set to 0 to disable scripted ambient behaviour. Not called while the owner is in combat.
+
+
Abilities
@@ -166,6 +195,19 @@
Pets
Each ability unlocks at a given pet level. Pets level 1-10 based on hunger.
+
+
+
Script
+
+
+
+
+
+
+
Saved as a .js file alongside the pet’s .yaml definition. Clear the field and save to delete the script.
+
+
+
@@ -209,6 +251,7 @@
Combat Message Tokens
let petsData = {}; // type -> Pet
let activeType = null;
let isNew = false;
+ let scriptSync = null;
const KNOWN_STATMODS = [
'strength', 'speed', 'smarts', 'vitality', 'mysticism', 'perception',
@@ -221,6 +264,7 @@