Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion projects/Cooking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Use JDK 21, install the matching TLibs Maven artifact (`me.plugins:tlibs`, versi

## Runtime and configuration

The plugin registers food item paths with TLibs and integrates furniture stations, TLibs inventory scanning, crops and husbandry. The manifest requires TLibs and InteractibleFurniture, and the POM pins TLibs 2.4.1, SimpleFactions 4.0.0 and RPCharacters 2.16.0; RPCharacters, MMOCore, CustomCrops, SimpleFactions and CustomFishing enable additional integrations. `/cooking` requires `cooking.admin` (op by default). Nutrition follows the shared ready character session and needs RPCharacters 2.16.0 or newer. The guides below define nutrition, crop and husbandry behaviour and their configuration boundaries.
The plugin registers food item paths with TLibs and integrates furniture stations, TLibs inventory scanning, crops and husbandry. The manifest requires TLibs and InteractibleFurniture, and the POM pins TLibs 2.6.0, SimpleFactions 4.0.0 and RPCharacters 2.16.0; RPCharacters, MMOCore, CustomCrops, SimpleFactions and CustomFishing enable additional integrations. `/cooking` requires `cooking.admin` (op by default). Nutrition follows the shared ready character session and needs RPCharacters 2.16.0 or newer. The guides below define nutrition, crop and husbandry behaviour and their configuration boundaries.

## Guides

Expand Down
154 changes: 142 additions & 12 deletions projects/Cooking/docs/husbandry.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Keep this guide aligned with the implementation and configuration on `main`.

## Persistence

**SQLite is the source of truth.** Main thread only. WAL mode. Write on ownership change, tick flush, death, chunk unload, plugin disable.
**SQLite is the source of truth.** Main thread only. WAL mode with FULL synchronous ownership commits. Write on ownership change, tick flush, death, chunk unload, plugin disable.

Entity **PDC is persistent** in Paper (saved with the entity on unload). We still do **not** treat PDC as the database. Genetics, owners, care, states, and cooldowns live in SQLite.

Expand All @@ -46,11 +46,18 @@ Owned/managed entities must `setPersistent(true)` and `setRemoveWhenFarAway(fals
- `neutered`
- `world`, `x`, `y`, `z` (last block the animal was seen at; null until it loads once)

`owners`
`character_owners`

- `animal_uuid`, `player_uuid`, `role` (`owner` / `coowner`)
- unique pair
- `COUNT(*)` per player vs `max-animals`
- `animal_uuid`, `account_uuid`, `character_uuid`, `role` (`owner` / `coowner`)
- primary key `(animal_uuid, character_uuid)`; several characters on one account may co-own the same animal
- capacity counts distinct animal UUIDs per account, including unresolved legacy slots

`owners` remains the legacy account table. Its resolved rows never grant gameplay rights.
`husbandry_owner_archive` is an immutable SQLite copy of every legacy owner column,
without cascading foreign keys. `husbandry_owner_plan` records the fixed reviewed
character or a held reason for each original account slot; `husbandry_owner_meta`
records completion. Character ownership uses its own expansion tables, separately
from `PRAGMA user_version`.

### Entity PDC flags

Expand Down Expand Up @@ -112,14 +119,134 @@ Chunk loaders / another player at the pen = **loaded**. The 6h affliction loop i

## Ownership

- `max-animals` default **15**. Co-ownership **counts for every** owner.
- Reject tame / co-own if that player is at cap (`cooking.admin` bypasses cap only).
- Ownership belongs to the selected character. Another character on the same account has no owner/co-owner rights.
- `max-animals` default **15** stays account-wide. Count each distinct animal once per account across every character and held legacy slot. Co-ownership counts toward each owning account; a second character's slot on the same animal does not consume another place.
- Reject voluntary tame/co-own acquisitions at cap (`cooking.admin` bypasses cap). Inheritance keeps every animal, including overflow; overflow prevents further acquisitions.
- A resolved co-owner retains access when another legacy owner slot is held. Held slots prevent unowned claims and bearer-token self-claims. Staff can inspect `husbandry_owner_plan`; unknown roles or identities remain held.
- Tame/co-own actions require an exact current character session belonging to that account; stale or misrouted provider sessions are refused. Native vanilla taming, sitting and tameable inventory interactions do not override existing Cooking ownership.
- Chickens and llamas count. Bees are **vanilla** (not in the 15, not wiped).
- Tameable vanilla types (horses, etc.) must be vanilla-tamed before plugin-own.
- Tame token (anvil-named) claims the animal. No SQLite row yet: insert then claim. Existing UNTAMED row: keep genetics, then claim. Token name is applied only after a successful claim. First tame plays spore blossom particles and the enchantment-table sound; renames are silent.
- Feed and glove require ownership (or staff).
- Items: TLibs paths in `husbandry.yml` (glove, universal feed, tame, co-own). Not MMOItems.

## Character migration and estates

Cooking pins TLibs **2.6.0**. Identity-dependent ownership fails closed without a
ready character. New heir/estate entry points are capability-probed before their
bridge classes load: an older provider retains legacy slots or dead characters'
property instead of guessing a current character.

The boot task waits for `Characters.legacyHeirsReady()` and retries pending
migration every minute. Identical pending warnings are suppressed; after
publication, immutable held plans are checked and reported hourly.
Each original account slot maps only through its reviewed `legacyHeir`; a dead
reviewed heir remains the migration recipient. One SQLite transaction copies all
legacy owner columns into the immutable archive, verifies its count, writes the
fixed plan, inserts resolved character slots and writes completion. The archive
includes committed WAL content through SQLite itself and cannot cascade away
when an animal dies. SQL failures and runtime exceptions roll back the archive,
plan and ownership publication together. Under TLibs 2.6.0, a JVM `Error` inside
the transaction can commit partial rows instead; an unfinished migration then
requires staff repair using the preserved archive and legacy rows.
Missing heirs, malformed identities, unknown roles and
conflicting slots remain recorded for staff; another character never claims them
by opening a menu. Animal rows, snapshots, care, genes, timers and unknown columns
are retained. Normal animal updates patch known fields rather than rebuilding rows.

The `cooking-animals` estate participant moves only the deceased character's slot
to the decided property recipient. WAITING/FROZEN slots stay with the testator;
other co-owners keep their own rights. A recipient already sharing the animal
merges to one slot, retaining the stronger recognized role (`owner` over
`coowner`). Unknown/conflicting role metadata blocks settlement for staff.
Capacity does not reject inheritance. No animals are copied or respawned by an
ownership transfer.

All slots, recipient merges and receipts for one estate revision commit in one
SQLite transaction. `husbandry_estate_intents` records the full decision identity,
revision and from/to refs; `husbandry_estate_slots` records IDs and before/after
roles. The decision text is Cooking's fixed rendering of all twelve TLibs 2.6.0 fields,
independent of runtime record text. Future fields require an explicit versioned
encoding upgrade that continues validating these captured receipts; unknown
recipient kinds block without writing ownership or receipt rows.

Exact `Estates.guardHolds` checks before mutation and before commit reject
revival, re-death, changed recipients and newer revisions. Repeated settlement
reports the cumulative moved IDs without moving them again, including later WAITING/FROZEN revisions. Raw source/recipient
rows, including unknown nullable or BLOB fields, remain in
`husbandry_estate_owner_archive` independently of the live slot's deletion.

If a previously transferred animal reappears in the testator's owner rows while
its completed receipt remains, settlement is BLOCKED across all revisions.
Preserve both rows and receipts for staff reconciliation. A completed revision
with newly resolved, untouched testator holdings also blocks that revision;
a reviewed new revision may transfer those untouched holdings. Receipt account
and character references must agree with the recorded decision.
Completed IDs must also agree with the preserved source archive for that
death/revision; missing or changed receipt IDs cannot authorize another transfer.

SQL failures, runtime exceptions and process interruption roll back the
transaction. The TLibs 2.6.0 JVM-error boundary above also applies to estates.
A retained incomplete intent
or conflicting same-revision receipt is BLOCKED for review; affected ownership
writers refuse changes while resolved co-owners can still care for or ride their
animal. Transient database failures report RETRY. Use the estate staff retry/waive
workflow after resolving the recorded cause; never clear a held intent merely to
force another recipient. Reconciliation runs at boot and hourly, independently
per death/revision.

Ownership and token use are sequential checked operations, not one transaction
with Minecraft inventory or entity saves. An arbitrary crash after a slot commit
and before token consumption is not proved exactly-once; preserve the database
and player body for staff reconciliation rather than blindly repeating effects.
Public milk/shear/egg/shed harvesting and existing animal care clocks retain their
rules.

### Offline rollback

Stop the Minecraft application through its normal deployment procedure before
using these commands. From the Cooking checkout, preview against the plugin's
`Data/husbandry.db`:

```sh
python3 tools/husbandry-ownership-rollback.py /path/to/plugins/Cooking/Data/husbandry.db
```

The JSON preview reports archived/restorable slots, held slots, current character
slots and changed resolved slots. Review ownership grants, removals and estates
since migration. To restore the original account slots for an old Cooking JAR:

```sh
python3 tools/husbandry-ownership-rollback.py /path/to/plugins/Cooking/Data/husbandry.db --apply --backup /path/to/husbandry-before-rollback.db
```

The tool creates its new recovery file exclusively with mode `0600`, refuses to
overwrite a backup, makes a verified SQLite backup including
committed WAL pages, and restores only archived account slots whose animal still
exists. It never resurrects a deleted animal. Current animal progress/snapshots
and the character tables/journals remain preserved; old Cooking ignores the
character tables. Account authority after rollback discards post-migration
character grants/removals/estate transfers from gameplay, so reconcile those
changes using the recovery image before proceeding. Keep that image and do not
re-upgrade after old-version ownership writes without reviewing both sets.
Deleting expansion tables or immutable archives is a separate later migration.

### Verification

Run Java 21 `tfmc-mvn -o -B clean verify` from the Cooking worktree. The zero-missed
production-line gate covers two characters on one account, stale menus, session
end/start mounts, held co-owners/token refusal, account capacity, immutable archive
and unknown-column preservation, injected SQLite publication faults, estate role
merges, replay and strict revival/re-death/revision guards. SQLite rollback tests
use isolated databases; they do not touch a running server.

Native Paper testing must additionally exercise an owned horse: mount as A,
end A's already-noncurrent session, verify dismount, start B on the same account,
verify B cannot remount, then return to A. Include missing identity and held legacy
ownership while preserving ordinary unowned riding. A controlled identity-provider
fixture proves the husbandry hooks on real Paper; it does not prove RPCharacters'
complete select-screen/body flow or arbitrary-crash inventory delivery.

## Care and states

Happy = no Hungry and no Dirty.
Expand Down Expand Up @@ -193,7 +320,7 @@ A forced stop can kill the server before it saves chunks, so an owned animal tha

- Every minute, on chunk unload and on disable, each loaded **owned** animal is serialized into the `snapshots` table (one row per animal, cascading from `animals`).
- At start, after the saved-chunk scan, an owned animal found in no saved chunk of its world and under no logged-out rider (`playerdata` `RootVehicle`) is lost. If it has a snapshot, Cooking spawns it again at the captured position with its UUID, name, coat, gear, owners and stats, and logs `Restored lost animal …`. If the entity turns up with its chunk first, nothing is spawned. If the spawn is refused, it stays `Missing` and is retried at the next start.
- Without a snapshot, the row and owners are deleted and `Dropped ghost animal …` is logged.
- Without a snapshot, the row and owners are deleted and `Dropped ghost animal …` is logged. An incomplete estate intent prevents ghost deletion: the record stays `Missing` for staff review, retaining existing animal, owner and snapshot rows and account capacity. The scan continues with other animals and counts these records as held.
- A scan that could not read some chunk or player file restores and drops nothing.
- Death deletes the snapshot with the row. Any other removal except a chunk unload or a rider logging out (plugin removal, discard) deletes the snapshot too, so a deliberately removed animal is not brought back.
- `restore-lost-animals: false` turns restoring off; lost animals are then dropped as ghosts.
Expand All @@ -208,7 +335,7 @@ Mount types in code: all `AbstractHorse` (horse, donkey, mule, camel, llama). At
- Wild/spawn-egg mounts keep vanilla stats until first interact, tame, or inspect enrolls a row (`0 … initial-genetic-max`, care 0)
- Health, speed, and jump are then written from genetics and care: `max * (min-pct + genetics-pct * genetics/max-genetics + care-pct * care/care-max)`, floored at the type min
- Bred foals roll genetics from parents, then the same formula is applied one tick after birth (vanilla breed stats are overwritten)
- Owner/co-owner (or staff) only ride when the mount has owners; untamed mounts stay rideable
- The current owner/co-owner character (or staff) may ride owned mounts. Unowned mounts stay rideable with a ready character session. Missing records or identity refuse mounting. Session end dismounts the outgoing body before the next character starts; session start also rejects any retained unauthorized mount.

## Damage

Expand All @@ -226,7 +353,10 @@ Applies to entities with a SQLite row. If `other-players` is false, cancel playe

Sneak empty-hand (or configured `items.inspect` TLibs path; blank = sneak-only) on a managed animal, including mounts.

**54-slot** double-chest inventory for all animals (livestock and mounts).
**54-slot** double-chest inventory for all animals (livestock and mounts). The menu
captures the opening character session; a stale remove-ownership click closes the
menu without changing a slot. Owner labels use character names, with an explicit
staff-review label for held slots.

Layout uses **gray/green concrete bars** (5 centered segments each). One empty row separates the two bars. Each segment shows title (`Care` / `Genetics`) and current/max only.

Expand All @@ -251,9 +381,9 @@ Stars are **not** shown in the GUI (visible on slaughter quality instead). No af

Players (no permission node):

- `/animals` (alias `/livestock`) — list every animal you own or co-own. Each line is name, species, co-owner when shared, growing when immature, Happy / Hungry / Dirty, then the world and block coordinates last recorded for that animal. Click the coordinates to copy them. Animals in unloaded chunks are listed too. When the server starts, Cooking reads the saved entity chunks to fill in or correct where each unloaded animal is. An owned animal lost from the save is restored or dropped at that scan (see [Lost animals](#lost-animals)). One that could not be restored shows `Missing` (with `last seen` coordinates if any were recorded) until it loads again. `location not recorded yet` only appears when neither a recorded spot nor a saved chunk is available, for example when some chunks could not be read. The header is `Your animals (owned/max)`.
- `/animals` (alias `/livestock`) — list every animal the selected character owns or co-owns. Each line is name, species, co-owner when shared, growing when immature, Happy / Hungry / Dirty, then the world and block coordinates last recorded for that animal. Click the coordinates to copy them. Animals in unloaded chunks are listed too. When the server starts, Cooking reads the saved entity chunks to fill in or correct where each unloaded animal is. An owned animal lost from the save is restored or dropped at that scan (see [Lost animals](#lost-animals)). One that could not be restored shows `Missing` (with `last seen` coordinates if any were recorded) until it loads again. `location not recorded yet` only appears when neither a recorded spot nor a saved chunk is available, for example when some chunks could not be read. The header counts the selected character's animals; a separate `Account animal places: used/max` line shows capacity across all its characters and held slots.

Staff with `cooking.admin`, including the console, can run `/animals <player>` for someone who has joined. A player without that permission cannot list another player's animals.
Staff with `cooking.admin`, including the console, can run `/animals <player>` for someone who has joined. This read-only account view aggregates distinct animals across that account's resolved character slots. A player without that permission cannot list another player's animals.

The last location is written when the animal loads, on the one-minute tick while loaded, on unload, and on plugin disable if the entity is still present. Listing refreshes a loaded animal from the live entity before printing.

Expand Down